| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487 |
- ##############################################
- # $Id: 52_I2C_LCD.pm 6722 2014-10-09 11:20:55Z ntruchsess $
- ##############################################
- package main;
- use strict;
- use warnings;
- #add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
- BEGIN {
- if (!grep(/FHEM\/lib$/,@INC)) {
- foreach my $inc (grep(/FHEM$/,@INC)) {
- push @INC,$inc."/lib";
- };
- };
- };
- #####################################
- my %sets = (
- "text" => "",
- "home" => "noArg",
- "clear" => "noArg",
- "display" => "on,off",
- "cursor" => "",
- "scroll" => "left,right",
- "backlight" => "on,off",
- "reset" => "noArg",
- "writeXY" => ""
- );
- my %gets = (
- );
- my %mapping = (
- 'P0' => 'RS',
- 'P1' => 'RW',
- 'P2' => 'E',
- 'P3' => 'LED',
- 'P4' => 'D4',
- 'P5' => 'D5',
- 'P6' => 'D6',
- 'P7' => 'D7',
- );
- my @LEDPINS = sort values %mapping;
- sub
- I2C_LCD_Initialize($)
- {
- my ($hash) = @_;
- $hash->{DefFn} = "I2C_LCD_Define";
- $hash->{InitFn} = "I2C_LCD_Init";
- $hash->{SetFn} = "I2C_LCD_Set";
- $hash->{AttrFn} = "I2C_LCD_Attr";
- $hash->{StateFn} = "I2C_LCD_State";
-
- $hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off IODev model pinMapping"
- ." customChar0 customChar1 customChar2 customChar3 customChar4 customChar5 customChar6 customChar7"
- ." backLight:on,off blink:on,off autoClear:on,off autoBreak:on,off replaceRegex $main::readingFnAttributes";
- # autoScroll:on,off direction:leftToRight,rightToLeft do not work reliably
- }
- sub
- I2C_LCD_Define($$)
- {
- my ($hash, $def) = @_;
- my @a = split("[ \t][ \t]*", $def);
- $hash->{STATE}="defined";
- my @keyvalue = ();
- while (my ($key, $value) = each %mapping) {
- push @keyvalue,"$key=$value";
- };
- $main::attr{$a[0]}{"pinMapping"} = join (',',sort @keyvalue);
- $hash->{mapping} = \%mapping;
-
- if ($main::init_done) {
- eval {
- I2C_LCD_Init($hash,[@a[2..scalar(@a)-1]]);
- };
- return I2C_LCD_Catch($@) if $@;
- }
- return undef;
- }
- sub
- I2C_LCD_Init($$)
- {
- my ($hash,$args) = @_;
- my $u = "wrong syntax: define <name> I2C_LCD <size-x> <size-y> [<address>]";
- return $u if(int(@$args) < 2);
-
- $hash->{sizex} = shift @$args;
- $hash->{sizey} = shift @$args;
- if (defined (my $address = shift @$args)) {
- $hash->{I2C_Address} = $address =~ /^0.*$/ ? oct($address) : $address;
- }
- my $name = $hash->{NAME};
- if (defined $hash->{I2C_Address}) {
- eval {
- main::AssignIoPort($hash,AttrVal($hash->{NAME},"IODev",undef));
- require LiquidCrystal;
- my $lcd = LiquidCrystal->new($hash->{sizex},$hash->{sizey});
- $lcd->setMapping($hash->{mapping});
- $lcd->attach(I2C_LCD_IO->new($hash));
- $lcd->init();
- $hash->{lcd} = $lcd;
- I2C_LCD_Apply_Attribute($name,"backLight");
- # I2C_LCD_Apply_Attribute($name,"autoscroll");
- # I2C_LCD_Apply_Attribute($name,"direction");
- I2C_LCD_Apply_Attribute($name,"blink");
- foreach (0..7) {
- I2C_LCD_Apply_Attribute($name,"customChar".$_);
- }
- };
- return I2C_LCD_Catch($@) if $@;
- }
- if (! (defined AttrVal($name,"stateFormat",undef))) {
- $main::attr{$name}{"stateFormat"} = "text";
- }
- if (AttrVal($hash->{NAME},"restoreOnReconnect","on") eq "on") {
- foreach my $reading (("display","scroll","backlight","text","writeXY")) {
- if (defined (my $value = ReadingsVal($name,$reading,undef))) {
- I2C_LCD_Set($hash,$name,$reading,split " ", $value);
- }
- }
- }
- return undef;
- }
- sub
- I2C_LCD_Attr($$$$) {
- my ($command,$name,$attribute,$value) = @_;
- my $hash = $main::defs{$name};
- eval {
- if ($command eq "set") {
- ARGUMENT_HANDLER: {
- $attribute eq "IODev" and do {
- if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $value)) {
- main::AssignIoPort($hash,$value);
- my @def = split (' ',$hash->{DEF});
- I2C_LCD_Init($hash,\@def) if (defined ($hash->{IODev}));
- }
- last;
- };
- $attribute eq "pinMapping" and do {
- my %newMapping = ();
- foreach my $keyvalue (split (/,/,$value)) {
- my ($key,$value) = split (/=/,$keyvalue);
- #Log3 ($name,5,"pinMapping, token: $key=$value, current mapping: $mapping{$key}");
- die "unknown token $key in attribute pinMapping, valid tokens are ".join (',',keys %mapping) unless (defined $mapping{$key});
- die "undefined or invalid value for token $key in attribute pinMapping, valid LED-Pins are ".join (',',@LEDPINS) unless $value and grep (/$value/,@LEDPINS);
- $newMapping{$key} = $value;
- }
- $hash->{mapping} = \%newMapping;
- my @def = split (' ',$hash->{DEF});
- I2C_LCD_Init($hash,\@def) if ($main::init_done);
- last;
- };
- $attribute =~ /customChar[0-7]/ and do {
- my @vals = split(/, */, $value);
- die "wrong number of elements (must be 8) in '$value'" if ( @vals != 8 );
- foreach (@vals) {
- die "$_ is out of range 0-31" if ($_ < 0 || $_ > 31 );
- }
- };
- $main::attr{$name}{$attribute}=$value;
- I2C_LCD_Apply_Attribute($name,$attribute);
- }
- }
- };
- my $ret = I2C_LCD_Catch($@) if $@;
- if ($ret) {
- $hash->{STATE} = "error setting $attribute to $value: ".$ret;
- return "cannot $command attribute $attribute to $value for $name: ".$ret;
- }
- }
- sub I2C_LCD_Apply_Attribute {
- my ($name,$attribute) = @_;
- my $lcd = $main::defs{$name}{lcd};
- if ($main::init_done and defined $lcd) {
- ATTRIBUTE_HANDLER: {
- $attribute eq "backLight" and do {
- if (AttrVal($name,"backLight","on") eq "on") {
- $lcd->backlight();
- } else {
- $lcd->noBacklight();
- }
- last;
- };
- $attribute eq "autoScroll" and do {
- if (AttrVal($name,"autoScroll","on") eq "on") {
- $lcd->autoscroll();
- } else {
- $lcd->noAutoscroll();
- }
- last;
- };
- $attribute eq "direction" and do {
- if (AttrVal($name,"direction","leftToRight") eq "leftToRight") {
- $lcd->leftToRight();
- } else {
- $lcd->rightToLeft();
- }
- last;
- };
- $attribute eq "blink" and do {
- if (AttrVal($name,"blink","off") eq "on") {
- $lcd->blink();
- } else {
- $lcd->noBlink();
- }
- last;
- };
- $attribute =~ /customChar([0-7])/ and do {
- my $nr = $1;
- my $p = AttrVal($name,$attribute,"0,0,0,0,0,0,0,0");
- my @vals = split(/, */, $p);
- $lcd->createChar($nr,\@vals);
- }
- }
- }
- }
- sub I2C_LCD_Set(@) {
- my ($hash, @a) = @_;
- return "Need at least one parameters" if(@a < 2);
- my $command = $a[1];
- my $value = $a[2];
- if(!defined($sets{$command})) {
- my @commands = ();
- foreach my $key (sort keys %sets) {
- push @commands, $sets{$key} ? $key.":".join(",",$sets{$key}) : $key;
- }
- return "Unknown argument $a[1], choose one of " . join(" ", @commands);
- }
- my $lcd = $hash->{lcd};
- return unless defined $lcd;
- eval {
- COMMAND_HANDLER: {
- $command eq "text" and do {
- shift @a;
- shift @a;
- $value = join(" ", @a);
- if (AttrVal($hash->{NAME},"autoClear","on") eq "on") {
- $lcd->clear();
- }
- # set reading prior to regexp, could contain unprintable chars!
- main::readingsSingleUpdate($hash,"text",$value,1);
- $value = _i2c_lcd_replace($hash,$value);
- if (AttrVal($hash->{NAME},"autoBreak","on") eq "on") {
- my $sizex = $hash->{sizex};
- my $sizey = $hash->{sizey};
- my $start = 0;
- my $len = length $value;
- for (my $line = 0;$line<$sizey;$line++) {
- $lcd->setCursor(0,$line);
- if ($start<$len) {
- $lcd->print(substr $value, $start, $sizex);
- } else {
- last;
- }
- $start+=$sizex;
- }
- } else {
- $lcd->print($value);
- }
- last;
- };
- $command eq "home" and do {
- $lcd->home();
- last;
- };
- $command eq "reset" and do {
- $lcd->init();
- # $hash->{lcd} = $lcd;
- last;
- };
- $command eq "clear" and do {
- $lcd->clear();
- main::readingsSingleUpdate($hash,"text","",1);
- last;
- };
- $command eq "display" and do {
- if ($value ne "off") {
- $lcd->display();
- } else {
- $lcd->noDisplay();
- }
- main::readingsSingleUpdate($hash,"display",$value,1);
- last;
- };
- $command eq "cursor" and do {
- my ($x,$y) = split ",",$value;
- $lcd->setCursor($x,$y);
- last;
- };
- $command eq "scroll" and do {
- if ($value eq "left") {
- $lcd->scrollDisplayLeft();
- } else {
- $lcd->scrollDisplayRight();
- }
- main::readingsSingleUpdate($hash,"scroll",$value,1);
- last;
- };
- $command eq "backlight" and do {
- if ($value eq "on") {
- $lcd->backlight();
- } else {
- $lcd->noBacklight();
- }
- main::readingsSingleUpdate($hash,"backlight",$value,1);
- last;
- };
- $command eq "writeXY" and do {
- my ($x,$y,$l,$al) = split(",",$value);
- $lcd->setCursor($x,$y);
- shift @a; shift @a; shift @a;
- my $t = join(" ", @a);
- # set reading prior to regexp, could contain unprintable chars!
- main::readingsSingleUpdate($hash,"writeXY",$value." ".$t,1);
- $t = _i2c_lcd_replace($hash,$t);
- my $sl = length $t;
- if ($sl > $l) {
- $t = substr($t,0,$l);
- }
- if ($sl < $l) {
- my $dif = "";
- for (my $i=$sl; $i<$l; $i++) {
- $dif .= " ";
- }
- $t = ($al eq "l") ? $t.$dif : $dif.$t;
- }
- $lcd->print($t);
- last; #"X=$x|Y=$y|L=$l|Text=$t";
- };
- }
- };
- return I2C_LCD_Catch($@) if $@;
- return undef;
- }
- sub I2C_LCD_Catch($) {
- my $exception = shift;
- if ($exception) {
- $exception =~ /^(.*)( at.*FHEM.*)$/;
- return $1;
- }
- return undef;
- }
- sub I2C_LCD_State($$$$)
- {
- my ($hash, $tim, $sname, $sval) = @_;
-
- STATEHANDLER: {
- $sname eq "text" and do {
- if (AttrVal($hash->{NAME},"restoreOnStartup","on") eq "on") {
- I2C_LCD_Set($hash,$hash->{NAME},$sname,$sval);
- }
- last;
- }
- }
- }
- sub _i2c_lcd_replace($$){
- my ($hash, $txt) = @_;
- my($lcdReplaceRegex)=AttrVal($hash->{NAME},"replaceRegex","none");
- if($lcdReplaceRegex ne "none"){
- my(@rex)=split(/,/,$lcdReplaceRegex);
- foreach(@rex){
- my($search,$replace)=split(/=/,$_);
- $txt=~s/$search/$replace/g;
- }
- }
- $txt =~ s/\\(
- (?:x\{[0-9a-fA-F]+\}) | # more than 2 digit hex
- (?:N\{U\+[0-9a-fA-F]{2,4}\}) # unicode by hex
- )/"qq|\\$1|"/geex;
- return $txt;
- }
- package I2C_LCD_IO;
- sub new {
- my ($class,$hash) = @_;
- return bless {
- hash => $hash,
- }, $class;
- }
- sub write {
- my ( $self, @data ) = @_;
- my $hash = $self->{hash};
- if (defined (my $iodev = $hash->{IODev})) {
- main::CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
- i2caddress => $hash->{I2C_Address},
- direction => "i2cwrite",
- data => join (' ',@data)
- });
- } else {
- die "no IODev assigned to '$hash->{NAME}'";
- }
- }
- 1;
- =pod
- =begin html
- <a name="I2C_LCD"></a>
- <h3>I2C_LCD</h3>
- <ul>
- drives LiquidCrystal Displays (LCD) that are connected to Firmata (via I2C).
- Supported are Displays that use a PCF8574T as I2C Bridge (as found on eBay when searching for
- 'LCD' and 'I2C'). Tested is the 1602 type (16 characters, 2 Lines), the 2004 type (and other cheap chinise-made
- I2C-LCDs for Arduino) ship with the same library, so they should work as well.
- See <a name="LiquidCrystal tutorial">http://arduino.cc/en/Tutorial/LiquidCrystal</a> for details about
- how to hook up the LCD to the arduino.
- Requires a defined <a href="#I2C">I2C</a>-device to work.<br>
- this I2C-device has to be configures for i2c by setting attr 'i2c-config' on the I2C-device<br>
-
- <a name="I2C_LCDdefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> I2C_LCD <size-x> <size-y> <i2c-address></code> <br>
- Specifies the I2C_LCD device.<br>
- <li>size-x is the number of characters per line</li>
- <li>size-y is the numbers of rows.</li>
- <li>i2c-address is the (device-specific) address of the ic on the i2c-bus</li>
- </ul>
-
- <br>
- <a name="I2C_LCDset"></a>
- <b>Set</b><br>
- <ul>
- <li><code>set <name> text <text to be displayed></code><br></li>
- <li><code>set <name> home</code><br></li>
- <li><code>set <name> clear</code><br></li>
- <li><code>set <name> display on|off</code><br></li>
- <li><code>set <name> cursor <...></code><br></li>
- <li><code>set <name> scroll left|right</code><br></li>
- <li><code>set <name> backlight on|off</code><br></li>
- <li><code>set <name> reset</code><br></li>
- <li><code>set <name> writeXY x-pos,y-pos,len[,l] <text to be displayed></code><br></li>
- </ul>
-
- <a name="I2C_I2Cget"></a>
- <b>Get</b><br>
- <ul>
- N/A<br>
- </ul><br>
- <a name="I2C_LCDattr"></a>
- <b>Attributes</b><br>
- <ul>
- <li>backLight <on|off></li>
- <li>autoClear <on|off></li>
- <li>autoBreak <on|off></li>
- <li>restoreOnStartup <on|off></li>
- <li>restoreOnReconnect <on|off></li>
- <li>replaceRegex ä=ae,cd+=ef,g=\x{DF}<br/>
- specify find=replace regex pattern eg for non-printable characters. \x{DF} will become char 223, which is º on my lcd.
- </li>
- <li>customChar<0-7><br/>
- up to 8 5x8px custom chars, see http://www.quinapalus.com/hd44780udg.html for a generator, use \x{00} to \x{07} to display</li>
- <li><a href="#IODev">IODev</a><br>
- Specify which <a href="#I2C">I2C</a> to use. (Optional, only required if there is more
- than one I2C-device defined.)
- </li>
- <li><a href="#eventMap">eventMap</a><br></li>
- <li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
- </ul>
- </ul>
- <br>
- =end html
- =cut
|