diff --git a/fhem/FHEM/10_FRM.pm b/fhem/FHEM/10_FRM.pm index 05e2b82b2..5ac6c46d3 100755 --- a/fhem/FHEM/10_FRM.pm +++ b/fhem/FHEM/10_FRM.pm @@ -28,7 +28,7 @@ sub FRM_Initialize($) { require "$main::attr{global}{modpath}/FHEM/DevIo.pm"; # Provider - $hash->{Clients} = ":FRM_IN:FRM_OUT:FRM_AD:FRM_PWM:FRM_I2C:FRM_SERVO:OWX:"; + $hash->{Clients} = ":FRM_IN:FRM_OUT:FRM_AD:FRM_PWM:FRM_I2C:FRM_SERVO:OWX:FRM_LCD:"; $hash->{ReadyFn} = "FRM_Ready"; $hash->{ReadFn} = "FRM_Read"; @@ -159,7 +159,6 @@ sub FRM_Ready($) { my ($hash) = @_; my $name = $hash->{NAME}; - print "ready: $name\n"; if ($name=~/^^FRM:.+:\d+$/) { # this is a closed tcp-connection, remove it TcpServer_Close($hash); delete $main::defs{$hash->{SNAME}}{FirmataDevice} if (defined $hash->{SNAME} && defined $main::defs{$hash->{SNAME}}{FirmataDevice}); diff --git a/fhem/FHEM/20_FRM_LCD.pm b/fhem/FHEM/20_FRM_LCD.pm new file mode 100755 index 000000000..e90bcd949 --- /dev/null +++ b/fhem/FHEM/20_FRM_LCD.pm @@ -0,0 +1,246 @@ +############################################# +package main; + +use strict; +use warnings; +use Device::Firmata; +use Device::Firmata::Constants qw/ :all /; + +##################################### + +my %sets = ( + "text" => "", + "home" => "", + "clear" => "", + "display" => "on,off", + "cursor" => "", + "scroll" => "left,right", +); + +my %gets = ( +); + +sub +FRM_LCD_Initialize($) +{ + my ($hash) = @_; + + $hash->{DefFn} = "FRM_Client_Define"; + $hash->{InitFn} = "FRM_LCD_Init"; + $hash->{SetFn} = "FRM_LCD_Set"; + $hash->{UndefFn} = "FRM_LCD_Undef"; + $hash->{AttrFn} = "FRM_LCD_Attr"; + + $hash->{AttrList} = "IODev model backLight:on,off blink:on,off autoClear:on,off autoBreak:on,off loglevel:0,1,2,3,4,5 $main::readingFnAttributes"; + # autoScroll:on,off direction:leftToRight,rightToLeft do not work reliably +} + +sub +FRM_LCD_Init($) +{ + my ($hash,$args) = @_; + my $u = "wrong syntax: define FRM_LCD [
]"; + + return $u if(int(@$args) < 3); + + $hash->{type} = shift @$args; + $hash->{sizex} = shift @$args; + $hash->{sizey} = shift @$args; + $hash->{address} = shift @$args if (@$args); + + return "no IODev set" unless defined $hash->{IODev}; + return "no FirmataDevice assigned to ".$hash->{IODev}->{NAME} unless defined $hash->{IODev}->{FirmataDevice}; + + if (($hash->{type} eq "i2c") and defined $hash->{address}) { + require LiquidCrystal_I2C; + my $lcd = LiquidCrystal_I2C->new($hash->{address},$hash->{sizex},$hash->{sizey}); + $lcd->attach($hash->{IODev}->{FirmataDevice}); + $lcd->init(); + $hash->{lcd} = $lcd; + my $name = $hash->{NAME}; +# FRM_LCD_Apply_Attribute($name,"backlight"); +# FRM_LCD_Apply_Attribute($name,"autoscroll"); +# FRM_LCD_Apply_Attribute($name,"direction"); +# FRM_LCD_Apply_Attribute($name,"blink"); + if (! (defined AttrVal($name,"stateFormat",undef))) { + $main::attr{$name}{"stateFormat"} = "text"; + } + } + + return undef; +} + +sub FRM_LCD_Attr(@) { + my ($command,$name,$attribute,$value) = @_; + my $hash = $main::defs{$name}; + if ($command eq "set") { + $main::attr{$name}{$attribute}=$value; + FRM_LCD_Apply_Attribute($name,$attribute); + } +} + +sub FRM_LCD_Apply_Attribute { + my ($name,$attribute) = @_; + my $lcd = $main::defs{$name}{lcd}; + if (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; + }; + } + } else { + main::Log (3, "no lcd found"); + } +} + +sub FRM_LCD_Set(@) { + my ($hash, @a) = @_; + return "Need at least one parameters" if(@a < 2); + my $command = $a[1]; + my $value = $a[2]; + return "Unknown argument $a[1], choose one of " . join(" ", sort keys %sets) + if(!defined($sets{$command})); + my $lcd = $hash->{lcd}; + COMMAND_HANDLER: { + $command eq "text" and do { + if (AttrVal($hash->{NAME},"autoClear","on") eq "on") { + $lcd->clear(); + } + 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); + } + main::readingsSingleUpdate($hash,"text",$value,1); + last; + }; + $command eq "home" and do { + $lcd->home(); + 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(); + } + 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(); + } + last; + }; + } +} + +sub +FRM_LCD_Undef($$) +{ + my ($hash, $name) = @_; +} + +1; + +=pod +=begin html + + +

FRM_I2C

+
    + represents an integrated curcuit connected to the i2c-pins of an Arduino + running Firmata
    + Requires a defined FRM-device to work.
    + this FRM-device has to be configures for i2c by setting attr 'i2c-config' on the FRM-device
    + it reads out the ic-internal storage in intervals of 'sampling-interval' as set on the FRM-device

    + + + Define +
      + define <name> FRM_I2C <i2c-address> <register> <bytes-to-read>
      + Specifies the FRM_I2C device.
      +
    • i2c-address is the (device-specific) address of the ic on the i2c-bus
    • +
    • register is the (device-internal) address to start reading bytes from.
    • +
    • bytes-to-read is the number of bytes read from the ic
    • +
    + +
    + + Set
    +
      + N/A
      +
    + + Get
    +
      + N/A
      +

    + + Attributes
    + +
+
+ +=end html +=cut diff --git a/fhem/FHEM/lib/Device/Firmata.pm b/fhem/FHEM/lib/Device/Firmata.pm index 1f7b36813..d5e1fcf05 100644 --- a/fhem/FHEM/lib/Device/Firmata.pm +++ b/fhem/FHEM/lib/Device/Firmata.pm @@ -60,7 +60,7 @@ sub open { # We're going to try and create the device connection first... my $package = "Device::Firmata::Platform"; eval "require $package"; - my $device = $package->open($serial_port,$opts); + my $device = $package->open($serial_port,$opts) or die "Could not connect to Firmata Server"; # Figure out what platform we're running on $device->probe; diff --git a/fhem/FHEM/lib/Device/Firmata/Constants.pm b/fhem/FHEM/lib/Device/Firmata/Constants.pm index 35b6284b3..f2351adcf 100644 --- a/fhem/FHEM/lib/Device/Firmata/Constants.pm +++ b/fhem/FHEM/lib/Device/Firmata/Constants.pm @@ -28,6 +28,7 @@ use constant ( PIN_SHIFT => 5, PIN_I2C => 6, PIN_ONEWIRE => 7, + PIN_STEPPER => 8, PIN_LOW => 0, PIN_HIGH => 1, diff --git a/fhem/FHEM/lib/Device/Firmata/Platform.pm b/fhem/FHEM/lib/Device/Firmata/Platform.pm index 27a9834a1..82c2ee468 100644 --- a/fhem/FHEM/lib/Device/Firmata/Platform.pm +++ b/fhem/FHEM/lib/Device/Firmata/Platform.pm @@ -351,11 +351,12 @@ sub sysex_handle { =head2 probe -Request the version of the protocol that the -target device is using. Sometimes, we'll have to -wait a couple of seconds for the response so we'll -try for 2 seconds and rapidly fire requests if -we don't get a response quickly enough ;) +On device boot time we wait 3 seconds for firmware name +that the target device is using. +If not received the starting message, then we wait for +response another 2 seconds and fire requests for version. +If the response received, then we store protocol version +and analog mapping and capability. =cut @@ -364,46 +365,33 @@ sub probe { # -------------------------------------------------- my ($self) = @_; - my $proto = $self->{protocol}; - my $io = $self->{io}; + $self->{metadata}{firmware} = ''; $self->{metadata}{firmware_version} = ''; - # Wait for 10 seconds only - my $end_tics = time + 10; - - # Query every .5 seconds - my $query_tics = time; + # Wait for 5 seconds only + my $end_tics = time + 5; + $self->firmware_version_query(); while ( $end_tics >= time ) { - - if ( $query_tics <= time ) { - - # Query the device for information on the firmata firmware_version - $self->firmware_version_query(); - select (undef,undef,undef,0.1); - - # Try to get a response - $self->poll; - - if ( $self->{metadata}{firmware} - && $self->{metadata}{firmware_version} ) - { - $self->{protocol}->{protocol_version} = $self->{metadata}{firmware_version}; - - $self->analog_mapping_query(); - $self->capability_query(); - while ($end_tics >= time) { - if (($self->{metadata}{analog_mappings}) and ($self->{metadata}{capabilities})) { - return 1; - } - $self->poll(); + select( undef, undef, undef, 0.2 ); # wait for response + if ( $self->poll && $self->{metadata}{firmware} && $self->{metadata}{firmware_version} ) { + $self->{protocol}->{protocol_version} = $self->{metadata}{firmware_version}; + if ( $self->{metadata}{capabilities} ) { + if ( $self->{metadata}{analog_mappings} ) { + return 1; + } + else { + $self->analog_mapping_query(); } - return 1; } - $query_tics = time + 0.5; + else { + $self->capability_query(); + } + } + else { + $self->firmware_version_query() unless $end_tics - 2 >= time; # version query on last 2 sec only } - select (undef,undef,undef,0.1); } - return undef; + return; } =head2 pin_mode @@ -436,10 +424,7 @@ sub pin_mode { last; }; - ( $mode == PIN_PWM || $mode == PIN_I2C || $mode == PIN_ONEWIRE || $mode == PIN_SERVO ) and do { - $self->{io}->data_write($self->{protocol}->message_prepare( SET_PIN_MODE => 0, $pin, $mode )); - last; - }; + $self->{io}->data_write($self->{protocol}->message_prepare( SET_PIN_MODE => 0, $pin, $mode )); }; $self->{pin_modes}->{$pin} = $mode; return 1; @@ -786,7 +771,7 @@ sub poll { # -------------------------------------------------- my $self = shift; - my $buf = $self->{io}->data_read(100) or return; + my $buf = $self->{io}->data_read(512) or return; my $messages = $self->{protocol}->message_data_receive($buf); $self->messages_handle($messages); return $messages; @@ -833,8 +818,7 @@ sub observe_i2c { sub observe_onewire { my ( $self, $pin, $observer, $context ) = @_; - return undef unless ($self->is_supported_mode($pin,PIN_INPUT)); - return undef unless ($self->is_supported_mode($pin,PIN_OUTPUT)); + return undef unless ($self->is_supported_mode($pin,PIN_ONEWIRE)); $self->{onewire_observer}[$pin] = { method => $observer, context => $context, diff --git a/fhem/FHEM/lib/Device/Firmata/Protocol.pm b/fhem/FHEM/lib/Device/Firmata/Protocol.pm index 6baadd1d6..9f9f1ce2a 100644 --- a/fhem/FHEM/lib/Device/Firmata/Protocol.pm +++ b/fhem/FHEM/lib/Device/Firmata/Protocol.pm @@ -305,7 +305,11 @@ sub sysex_parse { $return_data = $self->handle_scheduler_response($sysex_data); last; }; - + + $command == $protocol_commands->{RESERVED_COMMAND} and do { + $return_data = $sysex_data; + last; + }; } return { @@ -313,6 +317,11 @@ sub sysex_parse { command_str => $command_str, data => $return_data }; + } else { + return { + command => $command, + data => $sysex_data + } } } return undef; diff --git a/fhem/FHEM/lib/LiquidCrystal_I2C.pm b/fhem/FHEM/lib/LiquidCrystal_I2C.pm new file mode 100644 index 000000000..5f22f7849 --- /dev/null +++ b/fhem/FHEM/lib/LiquidCrystal_I2C.pm @@ -0,0 +1,433 @@ +# www.DFRobot.com +# last updated on 21/12/2011 +# Tim Starling Fix the reset bug (Thanks Tim) +# wiki doc http://www.dfrobot.com/wiki/index.php?title=I2C/TWI_LCD1602_Module_(SKU:_DFR0063) +# Support Forum: http://www.dfrobot.com/forum/ +# Compatible with the Arduino IDE 1.0 +# Library version:1.1 + +# When the display powers up, it is configured as follows: +# +# 1. Display clear +# 2. Function set: +# DL = 1; 8-bit interface data +# N = 0; 1-line display +# F = 0; 5x8 dot character font +# 3. Display on/off control: +# D = 0; Display off +# C = 0; Cursor off +# B = 0; Blinking off +# 4. Entry mode set: +# I/D = 1; Increment by 1 +# S = 0; No shift +# +# Note, however, that resetting the Arduino doesn't reset the LCD, so we +# can't assume that its in that state when a sketch starts (and the +# LiquidCrystal constructor is called). + +package LiquidCrystal_I2C; + +use warnings; +use strict; + +#Basic commands and constants +use constant LCD_CLEARDISPLAY => 0x01; +use constant LCD_RETURNHOME => 0x02; +use constant LCD_ENTRYMODESET => 0x04; +use constant LCD_DISPLAYCONTROL => 0x08; +use constant LCD_CURSORSHIFT => 0x10; +use constant LCD_FUNCTIONSET => 0x20; +use constant LCD_SETCGRAMADDR => 0x40; +use constant LCD_SETDDRAMADDR => 0x80; + +# flags for display entry mode +use constant LCD_ENTRYRIGHT => 0x00; +use constant LCD_ENTRYLEFT => 0x02; +use constant LCD_ENTRYSHIFTINCREMENT => 0x01; +use constant LCD_ENTRYSHIFTDECREMENT => 0x00; + +# flags for display on/off control +use constant LCD_DISPLAYON => 0x04; +use constant LCD_DISPLAYOFF => 0x00; +use constant LCD_CURSORON => 0x02; +use constant LCD_CURSOROFF => 0x00; +use constant LCD_BLINKON => 0x01; +use constant LCD_BLINKOFF => 0x00; + +# flags for display/cursor shift +use constant LCD_DISPLAYMOVE => 0x08; +use constant LCD_CURSORMOVE => 0x00; +use constant LCD_MOVERIGHT => 0x04; +use constant LCD_MOVELEFT => 0x00; + +# flags for function set +use constant LCD_8BITMODE => 0x10; +use constant LCD_4BITMODE => 0x00; +use constant LCD_2LINE => 0x08; +use constant LCD_1LINE => 0x00; +use constant LCD_5x10DOTS => 0x04; +use constant LCD_5x8DOTS => 0x00; + +# flags for backlight control +use constant LCD_BACKLIGHT => 0x08; +use constant LCD_NOBACKLIGHT => 0x00; + +use constant En => 0b00000100; # Enable bit +use constant Rw => 0b00000010; # Read / Write bit +use constant Rs => 0b00000001; # Register select bit + +sub print($$) { + my ($self,$c) = @_; + my @buf = unpack "c*",$c; + foreach my $s (@buf) { + $self->write($s); + } +}; + +sub write($$) { + my ( $self, $value ) = @_; + $self->send( $value, Rs ); + return 0; +} + +sub new($$$$) { + my ( $class, $lcd_Addr, $lcd_cols, $lcd_rows ) = @_; + return bless { + Addr => $lcd_Addr, + cols => $lcd_cols, + rows => $lcd_rows, + backlightval => LCD_NOBACKLIGHT, + }, $class; +} + +sub init($) { + my $self = shift; + $self->init_priv(); +} + +sub attach($$) { + my ($self,$dev) = @_; + $self->{FirmataDevice} = $dev; +} + +sub init_priv($) { + my $self = shift; + + $self->{displayfunction} = + LCD_4BITMODE | LCD_1LINE | + LCD_5x8DOTS; + $self->begin( $self->{cols}, $self->{rows} ); +} + +sub begin($$$$) { + my ( $self, $cols, $lines, $dotsize ) = @_; + if ( $lines > 1 ) { + $self->{displayfunction} |= LCD_2LINE; + } + $self->{numlines} = $lines; + + # for some 1 line displays you can select a 10 pixel high font + if ( (defined $dotsize) && ($dotsize != 0) && ( $lines == 1 ) ) { + $self->{displayfunction} |= LCD_5x10DOTS; + } + + # SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION! + # according to datasheet, we need at least 40ms after power rises above 2.7V + # before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50 + select( undef, undef, undef, 0.050 ); + + # Now we pull both RS and R/W low to begin commands + $self->expanderWrite( $self->{backlightval} ) + ; # reset expanderand turn backlight off (Bit 8 =1) + select( undef, undef, undef, 1 ); + + #put the LCD into 4 bit mode + # this is according to the hitachi HD44780 datasheet + # figure 24, pg 46 + + # we start in 8bit mode, try to set 4 bit mode + $self->write4bits( 0x03 << 4 ); + select( undef, undef, undef, 0.0045 ); # wait min 4.1ms + + # second try + $self->write4bits( 0x03 << 4 ); + select( undef, undef, undef, 0.0045 ); # wait min 4.1ms + + # third go! + $self->write4bits( 0x03 << 4 ); + select( undef, undef, undef, 0.00015 ); + + # finally, set to 4-bit interface + $self->write4bits( 0x02 << 4 ); + + # set # lines, font size, etc. + $self->command( + LCD_FUNCTIONSET | $self->{displayfunction} ); + + # turn the display on with no cursor or blinking default + $self->{displaycontrol} = + LCD_DISPLAYON | LCD_CURSOROFF | + LCD_BLINKOFF; + $self->display(); + + # clear it off + $self->clear(); + + # Initialize to default text direction (for roman languages) + $self->{displaymode} = + LCD_ENTRYLEFT | + LCD_ENTRYSHIFTDECREMENT; + + # set the entry mode + $self->command( + LCD_ENTRYMODESET | $self->{displaymode} ); + + $self->home(); + +} + +#********** high level commands, for the user! + +sub clear($) { + my $self = shift; + $self->command(LCD_CLEARDISPLAY) + ; # clear display, set cursor position to zero + select( undef, undef, undef, 0.002 ); # this command takes a long time! +} + +sub home($) { + my $self = shift; + $self->command(LCD_RETURNHOME) + ; # set cursor position to zero + select( undef, undef, undef, 0.002 ); # this command takes a long time! +} + +sub setCursor($$$) { + my ( $self, $col, $row ) = @_; + my @row_offsets = ( 0x00, 0x40, 0x14, 0x54 ); + if ( $row > $self->{numlines} ) { + $row = $self->{numlines} - 1; # we count rows starting w/0 + } + $self->command( + LCD_SETDDRAMADDR | ( $col + $row_offsets[$row] ) ); +} + +# Turn the display on/off (quickly) +sub noDisplay($) { + my $self = shift; + $self->{displaycontrol} &= + ~LCD_DISPLAYON; #TODO validate '~' + $self->command( + LCD_DISPLAYCONTROL | $self->{displaycontrol} ); +} + +sub display($) { + my $self = shift; + $self->{displaycontrol} |= LCD_DISPLAYON; + $self->command( + LCD_DISPLAYCONTROL | $self->{displaycontrol} ); +} + +# Turns the underline cursor on/off +sub noCursor($) { + my $self = shift; + $self->{displaycontrol} &= + ~LCD_CURSORON; #TODO validate '~' + $self->command( + LCD_DISPLAYCONTROL | $self->{displaycontrol} ); +} + +sub cursor($) { + my $self = shift; + $self->{displaycontrol} |= LCD_CURSORON; + $self->command( + LCD_DISPLAYCONTROL | $self->{displaycontrol} ); +} + +# Turn on and off the blinking cursor +sub noBlink($) { + my $self = shift; + $self->{displaycontrol} &= + ~LCD_BLINKON; #TODO validate '~' + $self->command( + LCD_DISPLAYCONTROL | $self->{displaycontrol} ); +} + +sub blink($) { + my $self = shift; + $self->{displaycontrol} |= LCD_BLINKON; + $self->command( + LCD_DISPLAYCONTROL | $self->{displaycontrol} ); +} + +# These commands scroll the display without changing the RAM +sub scrollDisplayLeft($) { + my $self = shift; + $self->command( LCD_CURSORSHIFT | + LCD_DISPLAYMOVE | + LCD_MOVELEFT ); +} + +sub scrollDisplayRight($) { + my $self = shift; + $self->command( LCD_CURSORSHIFT | + LCD_DISPLAYMOVE | + LCD_MOVERIGHT ); +} + +# This is for text that flows Left to Right +sub leftToRight($) { + my $self = shift; + $self->{displaymode} |= LCD_ENTRYLEFT; + $self->command( + LCD_ENTRYMODESET | $self->{displaymode} ); +} + +# This is for text that flows Right to Left +sub rightToLeft($) { + my $self = shift; + $self->{displaymode} &= + ~LCD_ENTRYLEFT; #TODO validate '~' + $self->command( + LCD_ENTRYMODESET | $self->{displaymode} ); +} + +# This will 'right justify' text from the cursor +sub autoscroll($) { + my $self = shift; + $self->{displaymode} |= LCD_ENTRYSHIFTINCREMENT; + $self->command( + LCD_ENTRYMODESET | $self->{displaymode} ); +} + +# This will 'left justify' text from the cursor +sub noAutoscroll($) { + my $self = shift; + $self->{displaymode} &= + ~LCD_ENTRYSHIFTINCREMENT; #TODO validate '~' + $self->command( + LCD_ENTRYMODESET | $self->{displaymode} ); +} + +# Allows us to fill the first 8 CGRAM locations +# with custom characters +sub createChar($$$) { + my ( $self, $location, $charmap ) = @_; + $location &= 0x7; # we only have 8 locations 0-7 + $self->command( LCD_SETCGRAMADDR | ( $location << 3 ) ); + for ( my $i = 0 ; $i < 8 ; $i++ ) { + $self->write( @$charmap[$i] ); + } +} + +# Turn the (optional) backlight off/on +sub noBacklight($) { + my $self = shift; + $self->{backlightval} = LCD_NOBACKLIGHT; + $self->expanderWrite(0); +} + +sub backlight($) { + my $self = shift; + $self->{backlightval} = LCD_BACKLIGHT; + $self->expanderWrite(0); +} + +#*********** mid level commands, for sending data/cmds + +sub command($$) { + my ( $self, $value ) = @_; + $self->send( $value, 0 ); +} + +#************ low level data pushing commands ********** + +# write either command or data +sub send($$$) { + my ( $self, $value, $mode ) = @_; + my $highnib = $value & 0xf0; + my $lownib = ( $value << 4 ) & 0xf0; + $self->write4bits( ($highnib) | $mode ); + $self->write4bits( ($lownib) | $mode ); +} + +sub write4bits($$) { + my ( $self, $value ) = @_; + $self->expanderWrite($value); + $self->pulseEnable($value); +} + +sub expanderWrite($$) { + my ( $self, $data ) = @_; + + $self->{FirmataDevice}->i2c_write($self->{Addr},($data) | $self->{backlightval}); +} + +sub pulseEnable($$) { + my ( $self, $data ) = @_; + $self->expanderWrite( $data | En ); # En high + select( undef, undef, undef, 0.000001 ); # enable pulse must be >450ns + $self->expanderWrite( $data & ~En ) + ; # En low TODO: validate '~' + select( undef, undef, undef, 0.000050 ); # commands need > 37us to settle +} + +# Alias functions + +sub cursor_on($) { + my $self = shift; + $self->cursor(); +} + +sub cursor_off($) { + my $self = shift; + $self->noCursor(); +} + +sub blink_on($) { + my $self = shift; + $self->blink(); +} + +sub blink_off($) { + my $self = shift; + $self->noBlink(); +} + +sub load_custom_character($$$) { + my ( $self, $char_num, $rows ) = @_; + $self->createChar( $char_num, $rows ); +} + +sub setBacklight($$) { + my ( $self, $new_val ) = @_; + if ($new_val) { + $self->backlight(); # turn backlight on + } + else { + $self->noBacklight(); # turn backlight off + } +} + +# unsupported API functions +sub off($) { } +sub on($) { } +sub setDelay ($$$) { } + +sub status($) { + return 0; +} + +sub keypad ($) { + return 0; +} + +sub init_bargraph($$) { + return 0; +} + +sub draw_horizontal_graph($$$$$) { } +sub draw_vertical_graph($$$$$) { } +sub setContrast($$) { } + +1;