diff --git a/Changes b/Changes index ad326e0..a73e073 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,11 @@ Revision history for Device-Firmata +0.61 2016.01.09 - Jens Beyer + added serial pin support (Platform, Protocol, Constants) + added protocol version query (Platform) + fixed messages_handle: REPORT_VERSION returns protocol version (Platform) + added method get_max_compatible_protocol_version (Protocol) + 0.60 2014.06.28 - Norbert Truchsess Fixed formating of Firmata.pm as Windows line-endings break automatic install from CPAN diff --git a/README b/README index d950c87..790b77b 100644 --- a/README +++ b/README @@ -37,6 +37,7 @@ LICENSE AND COPYRIGHT Copyright (C) 2011 amimato Copyright (C) 2012 ntruchsess +Copyright (C) 2016 jnsbyr This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published diff --git a/lib/Device/Firmata/Constants.pm b/lib/Device/Firmata/Constants.pm index 01ca350..f2e0108 100644 --- a/lib/Device/Firmata/Constants.pm +++ b/lib/Device/Firmata/Constants.pm @@ -29,6 +29,7 @@ use constant ( PIN_ONEWIRE => 7, PIN_STEPPER => 8, PIN_ENCODER => 9, + PIN_SERIAL => 10, PIN_LOW => 0, PIN_HIGH => 1, } @@ -260,6 +261,8 @@ use constant ( # extended command set using sysex (0-127/0x00-0x7F) RESERVED_COMMAND => 0x00, # 2nd SysEx data byte is a chip-specific command (AVR, PIC, TI, etc). + SERIAL_DATA => 0x60, # serial port config/write/read/close/flush/listen request and read reply + ENCODER_DATA => 0x61, # receive rotary-encoders current positions ANALOG_MAPPING_QUERY => 0x69, # ask for mapping of analog to pin numbers ANALOG_MAPPING_RESPONSE => 0x6A, # reply with mapping info CAPABILITY_QUERY => 0x6B, # ask for supported modes and resolution of all pins @@ -291,6 +294,8 @@ use constant ( I2C => 0x06, # pin included in I2C setup ONEWIRE => 0x07, # pin configured for 1-Wire commuication STEPPER => 0x08, # pin configured for stepper motor + SERIAL => 0x0A, # pin configured for serial port + # Deprecated entries deprecated => [ qw( FIRMATA_STRING SYSEX_I2C_REQUEST SYSEX_I2C_REPLY SYSEX_SAMPLING_INTERVAL ) @@ -315,6 +320,7 @@ use constant ( # extended command set using sysex (0-127/0x00-0x7F) RESERVED_COMMAND => 0x00, # 2nd SysEx data byte is a chip-specific command (AVR, PIC, TI, etc). + SERIAL_DATA => 0x60, # serial port config/write/read/close/flush/listen request and read reply ENCODER_DATA => 0x61, # receive rotary-encoders current positions ANALOG_MAPPING_QUERY => 0x69, # ask for mapping of analog to pin numbers ANALOG_MAPPING_RESPONSE => 0x6A, # reply with mapping info @@ -348,14 +354,14 @@ use constant ( ONEWIRE => 0x07, # pin configured for 1-Wire commuication STEPPER => 0x08, # pin configured for stepper motor ENCODER => 0x09, # pin configured for rotary-encoders - + SERIAL => 0x0A, # pin configured for serial port # Deprecated entries deprecated => [ qw( FIRMATA_STRING SYSEX_I2C_REQUEST SYSEX_I2C_REPLY SYSEX_SAMPLING_INTERVAL ) ], - }, # /Constants for Version 2.6 + }, # /Constants for Version 2.6 } ); diff --git a/lib/Device/Firmata/Platform.pm b/lib/Device/Firmata/Platform.pm index 6a93d12..39a7790 100644 --- a/lib/Device/Firmata/Platform.pm +++ b/lib/Device/Firmata/Platform.pm @@ -30,6 +30,7 @@ use Device::Firmata::Base servo_resolutions => {}, stepper_resolutions => {}, encoder_resolutions => {}, + serial_resolutions => {}, ports => [], input_ports => [], pins => {}, @@ -44,6 +45,7 @@ use Device::Firmata::Base onewire_observer => [], stepper_observer => [], encoder_observer => [], + serial_observer => [], scheduler_observer => undef, string_observer => undef, @@ -93,6 +95,7 @@ sub detach { $self->{onewire_observer} = []; $self->{stepper_observer} = []; $self->{encoder_observer} = []; + $self->{serial_observer} = []; $self->{scheduler_observer} = undef; $self->{tasks} = []; $self->{metadata} = {}; @@ -119,6 +122,7 @@ sub system_reset { $self->{onewire_observer} = []; $self->{stepper_observer} = []; $self->{encoder_observer} = []; + $self->{serial_observer} = []; $self->{scheduler_observer} = undef; $self->{tasks} = []; $self->{metadata} = {}; @@ -186,7 +190,7 @@ sub messages_handle { # Handle metadata information $command eq 'REPORT_VERSION' and do { - $self->{metadata}{firmware_version} = sprintf "V_%i_%02i", + $self->{metadata}{protocol_version} = sprintf "V_%i_%02i", @$data; last; }; @@ -251,6 +255,7 @@ sub sysex_handle { my @onewirepins; my @stepperpins; my @encoderpins; + my @serialpins; foreach my $pin (keys %$capabilities) { if (defined $capabilities->{$pin}) { @@ -289,6 +294,10 @@ sub sysex_handle { push @encoderpins, $pin; $self->{metadata}{encoder_resolutions}{$pin} = $capabilities->{$pin}->{PIN_ENCODER+0}->{resolution}; } + if ($capabilities->{$pin}->{PIN_SERIAL+0}) { + push @serialpins, $pin; + $self->{metadata}{serial_resolutions}{$pin} = $capabilities->{$pin}->{PIN_SERIAL+0}->{resolution}; + } } } $self->{metadata}{input_pins} = \@inputpins; @@ -301,6 +310,7 @@ sub sysex_handle { $self->{metadata}{onewire_pins} = \@onewirepins; $self->{metadata}{stepper_pins} = \@stepperpins; $self->{metadata}{encoder_pins} = \@encoderpins; + $self->{metadata}{serial_pins} = \@serialpins; last; }; @@ -373,6 +383,15 @@ sub sysex_handle { }; last; }; + + $sysex_message->{command_str} eq 'SERIAL_DATA' and do { + my $serialPort = $data->{port}; + my $observer = $self->{serial_observer}[$serialPort]; + if (defined $observer) { + $observer->{method}( $data, $observer->{context} ); + } + last; + }; } } @@ -392,14 +411,16 @@ sub probe { my ($self) = @_; $self->{metadata}{firmware} = ''; $self->{metadata}{firmware_version} = ''; + $self->{metadata}{protocol_version} = ''; # Wait for 5 seconds only my $end_tics = time + 5; $self->firmware_version_query(); + $self->protocol_version_query(); while ( $end_tics >= time ) { - 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}; + select( undef, undef, undef, 0.2 ); # wait for responses + if ( $self->poll && $self->{metadata}{firmware} && $self->{metadata}{firmware_version} && $self->{metadata}{protocol_version} ) { + $self->{protocol}->{protocol_version} = $self->{protocol}->get_max_supported_protocol_version($self->{metadata}{protocol_version}); if ( $self->{metadata}{capabilities} ) { if ( $self->{metadata}{analog_mappings} ) { return 1; @@ -409,8 +430,10 @@ sub probe { } else { $self->capability_query(); } - } else { - $self->firmware_version_query() unless $end_tics - 2 >= time; # version query on last 2 sec only + } elsif ($end_tics - 2 < time) { + # version query on last 2 sec only + $self->firmware_version_query(); + $self->protocol_version_query(); } } return; @@ -540,6 +563,12 @@ pmw_write is an alias for analog_write *pwm_write = *analog_write; +sub protocol_version_query { + my $self = shift; + my $protocol_version_query_packet = $self->{protocol}->packet_query_version; + return $self->{io}->data_write($protocol_version_query_packet); +} + sub firmware_version_query { my $self = shift; my $firmware_version_query_packet = $self->{protocol}->packet_query_firmware; @@ -831,6 +860,26 @@ sub encoder_detach { return $self->{io}->data_write($self->{protocol}->packet_encoder_detach( $encoderNum )); } +sub serial_write { + my ( $self, $port, @data ) = @_; + return $self->{io}->data_write($self->{protocol}->packet_serial_write( $port, @data )); +} + +sub serial_read { + my ( $self, $port, $numbytes ) = @_; + return $self->{io}->data_write($self->{protocol}->packet_serial_read( $port, 0x00, $numbytes )); +} + +sub serial_stopreading { + my ( $self, $port) = @_; + return $self->{io}->data_write($self->{protocol}->packet_serial_read( $port, 0x01, 0 )); +} + +sub serial_config { + my ( $self, $port, $baud ) = @_; + return $self->{io}->data_write($self->{protocol}->packet_serial_config( $port, $baud )); +} + =head2 poll Call this function every once in a while to @@ -921,6 +970,16 @@ sub observe_encoder { return 1; } +sub observe_serial { + my ( $self, $port, $observer, $context ) = @_; + return undef if (defined $self->{metadata}->{serialpins} && @$self->{metadata}->{serialpins} == 0 ); + $self->{serial_observer}[$port] = { + method => $observer, + context => $context, + }; + return 1; +} + sub observe_scheduler { my ( $self, $observer, $context ) = @_; $self->{scheduler_observer} = { diff --git a/lib/Device/Firmata/Protocol.pm b/lib/Device/Firmata/Protocol.pm index 0e4acd3..2556605 100644 --- a/lib/Device/Firmata/Protocol.pm +++ b/lib/Device/Firmata/Protocol.pm @@ -16,6 +16,7 @@ use constant { MIDI_PARSE_SYSEX => 1, MIDI_START_SYSEX => 0xf0, MIDI_END_SYSEX => 0xf7, + MAX_PROTOCOL_VERSION => 'V_2_05', # highest Firmata protocol version currently implemented }; use Device::Firmata::Constants qw/ :all /; @@ -24,7 +25,7 @@ use Device::Firmata::Base FIRMATA_ATTRIBS => { buffer => [], parse_status => MIDI_PARSE_NORMAL, - protocol_version => 'V_2_04', # We are starting with the highest protocol + protocol_version => MAX_PROTOCOL_VERSION, # We are starting with the highest protocol }; $MIDI_DATA_SIZES = { @@ -93,6 +94,13 @@ our $ENCODER_COMMANDS = { ENCODER_DETACH => 5, }; +our $SERIAL_COMMANDS = { + SERIAL_CONFIG => 0x10, # config serial port stetting such as baud rate and pins + SERIAL_WRITE => 0x20, # write to serial port + SERIAL_READ => 0x30, # read request to serial port + SERIAL_REPLY => 0x40, # read reply from serial port +}; + our $MODENAMES = { 0 => 'INPUT', 1 => 'OUTPUT', @@ -104,6 +112,7 @@ our $MODENAMES = { 7 => 'ONEWIRE', 8 => 'STEPPER', 9 => 'ENCODER', + 10 => 'SERIAL', }; =head1 DESCRIPTION @@ -322,6 +331,11 @@ sub sysex_parse { last; }; + $command == $protocol_commands->{SERIAL_DATA} and do { + $return_data = $self->handle_serial_reply($sysex_data); + last; + }; + $command == $protocol_commands->{RESERVED_COMMAND} and do { $return_data = $sysex_data; last; @@ -1012,6 +1026,112 @@ sub handle_encoder_response { return \@retval; } +#/* serial config +# * ------------------------------- +# * 0 START_SYSEX (0xF0) +# * 1 SERIAL_DATA (0x60) // command byte +# * 2 SERIAL_CONFIG (0x10) // OR with port (0x11 = SERIAL_CONFIG | HW_SERIAL1) +# * 3 baud (bits 0 - 6) +# * 4 baud (bits 7 - 13) +# * 5 baud (bits 14 - 20) // need to send 3 bytes for baud even if value is < 14 bits +# * 6 rxPin (0-127) [optional] // only set if platform requires RX pin number @TODO +# * 7 txPin (0-127) [optional] // only set if platform requires TX pin number @TODO +# * 6|8 END_SYSEX (0xF7) +# */ + +sub packet_serial_config { + my ( $self, $port, $baud ) = @_; + return $self->packet_sysex_command( SERIAL_DATA, + $SERIAL_COMMANDS->{SERIAL_CONFIG} | $port, + $baud & 0x7f, + ($baud >> 7) & 0x7f, + ($baud >> 14) & 0x7f + ); +} + +#/* serial write +# * ------------------------------- +# * 0 START_SYSEX (0xF0) +# * 1 SERIAL_DATA (0x60) +# * 2 SERIAL_WRITE (0x20) // OR with port (0x21 = SERIAL_WRITE | HW_SERIAL1) +# * 3 data 0 (LSB) +# * 4 data 0 (MSB) +# * 5 data 1 (LSB) +# * 6 data 1 (MSB) +# * ... // up to max buffer - 5 +# * n END_SYSEX (0xF7) +# */ + +sub packet_serial_write { + my ( $self, $port, @serialdata ) = @_; + + if (scalar @serialdata) { + my @data; + push_array_as_two_7bit(\@serialdata,\@data); + return $self->packet_sysex_command( SERIAL_DATA, + $SERIAL_COMMANDS->{SERIAL_WRITE} | $port, + @data + ); + } else { + return $self->packet_sysex_command( SERIAL_DATA, + $SERIAL_COMMANDS->{SERIAL_WRITE} | $port + ); + } +} + +#/* serial read +# * ------------------------------- +# * 0 START_SYSEX (0xF0) +# * 1 SERIAL_DATA (0x60) +# * 2 SERIAL_READ (0x30) // OR with port (0x31 = SERIAL_READ | HW_SERIAL1) +# * 3 SERIAL_READ_MODE (0x00) // 0x00 => read continuously, 0x01 => stop reading +# * 4 maxBytesToRead (lsb) // 0x00 for all bytes available [optional] +# * 5 maxBytesToRead (msb) // 0x00 for all bytes available [optional] +# * 4|6 END_SYSEX (0xF7) +# */ + +sub packet_serial_read { + my ( $self, $port, $command, $maxBytes ) = @_; + + if ($maxBytes > 0) { + return $self->packet_sysex_command( SERIAL_DATA, + $SERIAL_COMMANDS->{SERIAL_READ} | $port, + $command, + $maxBytes & 0x7f, + ($maxBytes >> 7) & 0x7f + ); + } else { + return $self->packet_sysex_command( SERIAL_DATA, + $SERIAL_COMMANDS->{SERIAL_READ} | $port, + $command + ); + } +} + +#/* serial reply +# * ------------------------------- +# * 0 START_SYSEX (0xF0) +# * 1 SERIAL_DATA (0x60) +# * 2 SERIAL_REPLY (0x40) // OR with port (0x41 = SERIAL_REPLY | HW_SERIAL1) +# * 3 data 0 (LSB) +# * 4 data 0 (MSB) +# * 3 data 1 (LSB) +# * 4 data 1 (MSB) +# * ... // up to max buffer - 5 +# * n END_SYSEX (0xF7) +# */ + +sub handle_serial_reply { + my ( $self, $sysex_data ) = @_; + + my $command = shift @$sysex_data; + my $port = $command & 0xF; + my @data = double_7bit_to_array($sysex_data); + return { + port => $port, + data => \@data, + }; +} sub shift14bit { my $data = shift; @@ -1133,4 +1253,25 @@ sub unpack_from_7bit { return @outdata; } +=head2 get_max_compatible_protocol_version + +Search list of implemented protocols for identical or next lower version. + +=cut + +sub get_max_supported_protocol_version { + my ( $self, $deviceProtcolVersion ) = @_; + return "" unless (defined($deviceProtcolVersion)); + return $deviceProtcolVersion if (defined($COMMANDS->{$deviceProtcolVersion})); + + my $maxSupportedProtocolVersion = undef; + foreach my $protocolVersion (sort keys %{$COMMANDS}) { + if ($protocolVersion lt $deviceProtcolVersion) { + $maxSupportedProtocolVersion = $protocolVersion; + } + } + + return $maxSupportedProtocolVersion; +} + 1;