############################################## # $Id$ ############################################## 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 I2C_LCD [
]"; 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

I2C_LCD

    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 http://arduino.cc/en/Tutorial/LiquidCrystal for details about how to hook up the LCD to the arduino. Requires a defined I2C-device to work.
    this I2C-device has to be configures for i2c by setting attr 'i2c-config' on the I2C-device
    Define
      define <name> I2C_LCD <size-x> <size-y> <i2c-address>
      Specifies the I2C_LCD device.
    • size-x is the number of characters per line
    • size-y is the numbers of rows.
    • i2c-address is the (device-specific) address of the ic on the i2c-bus

    Set
    • set <name> text <text to be displayed>
    • set <name> home
    • set <name> clear
    • set <name> display on|off
    • set <name> cursor <...>
    • set <name> scroll left|right
    • set <name> backlight on|off
    • set <name> reset
    • set <name> writeXY x-pos,y-pos,len[,l] <text to be displayed>
    Get
      N/A

    Attributes
    • backLight <on|off>
    • autoClear <on|off>
    • autoBreak <on|off>
    • restoreOnStartup <on|off>
    • restoreOnReconnect <on|off>
    • replaceRegex ä=ae,cd+=ef,g=\x{DF}
      specify find=replace regex pattern eg for non-printable characters. \x{DF} will become char 223, which is º on my lcd.
    • customChar<0-7>
      up to 8 5x8px custom chars, see http://www.quinapalus.com/hd44780udg.html for a generator, use \x{00} to \x{07} to display
    • IODev
      Specify which I2C to use. (Optional, only required if there is more than one I2C-device defined.)
    • eventMap
    • readingFnAttributes

=end html =cut