2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-04-22 08:11:44 +00:00

incorporate perl-firmata (Device::Firmata)

git-svn-id: https://svn.fhem.de/fhem/trunk@2596 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
ntruchsess 2013-01-29 21:03:38 +00:00
parent f610aa060b
commit 3d20b4a6d4
10 changed files with 2965 additions and 0 deletions

View File

@ -0,0 +1,72 @@
package Device::Firmata;
use strict;
use warnings;
use Device::Firmata::Constants;
use Device::Firmata::Base
ISA => 'Device::Firmata::Base',
FIRMATA_ATTRIBS => {
};
=head1 NAME
Device::Firmata - A host interface to Firmata for the arduino platform.
=head1 VERSION
Version 0.50
=cut
our $VERSION = '0.50';
our $DEBUG = 0;
=head1 SYNOPSIS
use strict;
use warnings;
use Device::Firmata::Constants qw/ :all /;
use Device::Firmata;
$|++;
use Time::HiRes 'sleep';
my $led_pin = 13;
my $device = Device::Firmata->open('/dev/ttyUSB0') or die "Could not connect to Firmata Server";
$device->pin_mode($led_pin=>PIN_OUTPUT);
my $iteration = 0;
while (1) {
my $strobe_state = $iteration++%2;
$device->digital_write($led_pin=>$strobe_state);
sleep 0.5;
}
=head1 SUBROUTINES/METHODS
=head2 open
establish communication to the device. Single argument is the name of the device file mapped to the arduino. Typicaly '/dev/ttyUSB0'
=cut
sub open {
# --------------------------------------------------
# Establish a connection with the serial port
#
my ( $self, $serial_port, $opts ) = @_;
# 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);
# Figure out what platform we're running on
$device->probe;
return $device;
}
1;

View File

@ -0,0 +1,398 @@
package Device::Firmata::Base;
use strict 'vars', 'subs';
use vars qw/
$AUTOLOAD
$FIRMATA_DEBUG_LEVEL
$FIRMATA_ERROR_CLASS
$FIRMATA_ERROR
$FIRMATA_ATTRIBS
$FIRMATA_DEBUGGING
$FIRMATA_LOCALE
$FIRMATA_LOCALE_PATH
$FIRMATA_LOCALE_MESSAGES
/;
=head1 NAME
Device::Firmata::Base -- Abstract baseclass for Device::Firmata modules
=cut
$FIRMATA_DEBUGGING = 1;
$FIRMATA_ATTRIBS = {
};
$FIRMATA_LOCALE = 'en';
$FIRMATA_LOCALE_PATH = '.';
$FIRMATA_DEBUG_LEVEL = 0;
$FIRMATA_ERROR_CLASS = 'Device::Firmata::Error';
=head1 METHODS
=head2 import
Ease the use of setting up configuration options
=cut
sub import {
my $self = shift;
my $pkg = caller;
my $config_opts = {
debugging => $FIRMATA_DEBUGGING,
};
if ( @_ ) {
my $opts = $self->parameters( @_ );
if ( my $attrs = $opts->{FIRMATA_ATTRIBS} ) {
*{$pkg.'::FIRMATA_ATTRIBS'} = \$attrs;
}
unless ( ref *{$pkg.'::ISA'} eq 'ARRAY' and @${$pkg.'::ISA'}) {
my @ISA = ref $opts->{ISA} ? @{$opts->{ISA}} :
$opts->{ISA} ? $opts->{ISA} :
__PACKAGE__;
*{$pkg.'::ISA'} = \@ISA;
}
use strict;
$self->SUPER::import( @_ );
}
}
=head2 new
=cut
sub new {
my $pkg = shift;
my $basis = copy_struct( $pkg->init_class_attribs );
my $self = bless $basis, $pkg;
@_ = $self->pre_init( @_ ) if $self->{_biofunc_pre_init};
if ( $self->{_biofunc_init} ) {
$self->init( @_ );
}
else {
$self->init_instance_attribs( @_ );
}
return $self->post_init if $self->{_biofunc_post_init};
return $self;
}
=head2 create
A soft new as some objects will override new and
we don't want to cause problems but still want
to invoice our creation code
=cut
sub create {
my $self = shift;
my $basis = copy_struct( $self->init_class_attribs );
@$self{ keys %$basis } = values %$basis;
@_ = $self->pre_init( @_ ) if $self->{_biofunc_pre_init};
if ( $self->{_biofunc_init} ) {
$self->init( @_ );
}
else {
$self->init_instance_attribs( @_ );
}
return $self->post_init if $self->{_biofunc_post_init};
return $self;
}
=head2 init_instance_attribs
=cut
sub init_instance_attribs {
# --------------------------------------------------
my $self = shift;
my $opts = $self->parameters( @_ );
foreach my $k ( keys %$self ) {
next unless exists $opts->{$k};
next if $k =~ /^_biofunc/;
$self->{$k} = $opts->{$k};
}
return $self;
}
=head2 init_class_attribs
=cut
sub init_class_attribs {
# --------------------------------------------------
my $class = ref $_[0] || shift;
my $track = { $class => 1, @_ ? %{$_[0]} : () };
return ${"${class}::ABSOLUTE_ATTRIBS"} if ${"${class}::ABSOLUTE_ATTRIBS"};
my $u = ${"${class}::FIRMATA_ATTRIBS"} || {};
for my $c ( @{"${class}::ISA"} ) {
next unless ${"${c}::FIRMATA_ATTRIBS"};
my $h;
if ( ${"${c}::ABSOLUTE_ATTRIBS"} ) {
$h = ${"${c}::ABSOLUTE_ATTRIBS"};
}
else {
$c->fatal( "Cyclic dependancy!" ) if $track->{$c};
$h = $c->init_class_attribs( $c, $track );
}
foreach my $k ( keys %$h ) {
next if exists $u->{$k};
$u->{$k} = copy_struct( $h->{$k} );
}
}
foreach my $f ( qw( pre_init init post_init ) ) {
$u->{"_biofunc_" . $f} = $class->can( $f ) ? 1 : 0;
}
${"${class}::ABSOLUTE_ATTRIBS"} = $u;
return $u;
}
# logging/exception functions
# Utilty functions
=head2 parameters
=cut
sub parameters {
# --------------------------------------------------
return {} unless @_ > 1;
if ( @_ == 2 ) {
return $_[1] if ref $_[1];
return; # something wierd happened
}
@_ % 2 or $_[0]->warn( "Even number of elements were not passed to call.", join( " ", caller() ) );
shift;
return {@_};
}
=head2 copy_struct
=cut
sub copy_struct {
# --------------------------------------------------
my $s = shift;
if ( ref $s ) {
if ( UNIVERSAL::isa( $s, 'HASH' ) ) {
return {
map { my $v = $s->{$_}; (
$_ => ref $v ? copy_struct( $v ) : $v
)} keys %$s
};
}
elsif ( UNIVERSAL::isa( $s, 'ARRAY' ) ) {
return [
map { ref $_ ? copy_struct($_) : $_ } @$s
];
}
die "Cannot copy struct! : ".ref($s);
}
return $s;
}
=head2 locale
=cut
sub locale {
# --------------------------------------------------
@_ >= 2 and shift;
$FIRMATA_LOCALE = shift;
}
=head2 locale_path
=cut
sub locale_path {
# --------------------------------------------------
@_ >= 2 and shift;
$FIRMATA_LOCALE_PATH = shift;
}
=head2 language
=cut
sub language {
# --------------------------------------------------
my $self = shift;
require Device::Firmata::Language;
return Device::Firmata::Language->language(@_);
}
=head2 error
=cut
sub error {
# --------------------------------------------------
# Handle any error messages
#
my $self = shift;
if ( @_ ) {
my $err_msg = $self->init_error->error(@_);
$self->{error} = $err_msg;
return;
}
my $err_msg = $self->{error};
$self->{error} = '';
return $err_msg;
}
=head2 init_error
Creates the global error object that will collect
all error messages generated on the system. This
function can be called as many times as desired.
=cut
sub init_error {
# --------------------------------------------------
#
$FIRMATA_ERROR and return $FIRMATA_ERROR;
if ( $FIRMATA_ERROR_CLASS eq 'Device::Firmata::Error' ) {
require Device::Firmata::Error;
return $FIRMATA_ERROR = $FIRMATA_ERROR_CLASS;
}
# Try and load the file. Use default if fails
eval "require $FIRMATA_ERROR_CLASS";
$@ and return $FIRMATA_ERROR = $FIRMATA_ERROR_CLASS;
# Try and init the error object. Use default if fails
eval { $FIRMATA_ERROR = $FIRMATA_ERROR_CLASS->new(); };
$@ and return $FIRMATA_ERROR = $FIRMATA_ERROR_CLASS;
return $FIRMATA_ERROR;
}
=head2 fatal
Handle tragic and unrecoverable messages
=cut
sub fatal {
# --------------------------------------------------
#
my $self = shift;
return $self->error( -1, @_ );
}
=head2 warn
Handle tragic and unrecoverable messages
=cut
sub warn {
# --------------------------------------------------
#
my $self = shift;
return $self->error( 0, @_ );
}
=head2 debug
=cut
sub debug {
# --------------------------------------------------
my ( $self, $debug ) = @_;
$FIRMATA_DEBUG_LEVEL = $debug;
}
=head2 DESTROY
=cut
sub DESTROY {
# --------------------------------------------------
my $self = shift;
}
=head2 AUTOLOAD
=cut
sub AUTOLOAD {
# --------------------------------------------------
my $self = shift;
my ($attrib) = $AUTOLOAD =~ /::([^:]+)$/;
if ( $self and UNIVERSAL::isa( $self, 'Device::Firmata::Base' ) ) {
$self->error( FIRMATA__unhandled => $attrib, join( " ", caller() ) );
die $self->error;
}
else {
die "Tried to call function '$attrib' via object '$self' @ ", join( " ", caller(1) ), "\n";
}
}
####################################################
# Object instantiation code
####################################################
=head2 object_load
Load the appropriate package and attempt to initialize
the object as well
=cut
sub object_load {
# --------------------------------------------------
my $self = shift;
my $object_class = shift;
return unless $object_class =~ /^\w+(?:::\w+)*$/; # TODO ERROR MESSAGE
eval "require $object_class; 1" or die $@;
my $object = $object_class->new(@_);
return $object;
}
1;

View File

@ -0,0 +1,291 @@
package Device::Firmata::Constants;
=head1 NAME
Device::Firmata::Constants - constants used in the system
=cut
use strict;
use Exporter;
use vars qw/
@ISA @EXPORT_OK %EXPORT_TAGS
$BASE
$DEVICES
$COMMANDS $COMMAND_NAMES
$COMMAND_LOOKUP
/;
@ISA = 'Exporter';
# Basic commands and constants
use constant (
$BASE = {
PIN_INPUT => 0,
PIN_OUTPUT => 1,
PIN_ANALOG => 2,
PIN_PWM => 3,
PIN_SERVO => 4,
PIN_SHIFT => 5,
PIN_I2C => 6,
PIN_ONEWIRE => 7,
PIN_LOW => 0,
PIN_HIGH => 1,
}
);
$DEVICES = { 'arduino_dumilanove' => {}, };
# We need to apply all the available protocols
use constant (
$COMMANDS = {
V_2_01 => {
MAX_DATA_BYTES =>
32, # max number of data bytes in non-Sysex messages
# message command bytes (128-255/0x80-0xFF)
DIGITAL_MESSAGE => 0x90, # send data for a digital pin
ANALOG_MESSAGE => 0xE0, # send data for an analog pin (or PWM)
REPORT_ANALOG => 0xC0, # enable analog input by pin #
REPORT_DIGITAL => 0xD0, # enable digital input by port pair
SET_PIN_MODE => 0xF4, # set a pin to INPUT/OUTPUT/PWM/etc
REPORT_VERSION => 0xF9, # report protocol version
SYSTEM_RESET => 0xFF, # reset from MIDI
START_SYSEX => 0xF0, # start a MIDI Sysex message
END_SYSEX => 0xF7, # end a MIDI Sysex message
# extended command set using sysex (0-127/0x00-0x7F)
SERVO_CONFIG => 0x70, # set max angle, minPulse, maxPulse, freq
STRING_DATA => 0x71, # a string message with 14-bits per char
SHIFT_DATA => 0x75, # a bitstream to/from a shift register
I2C_REQUEST => 0x76, # send an I2C read/write request
I2C_REPLY => 0x77, # a reply to an I2C read request
I2C_CONFIG => 0x78, # config I2C settings such as delay times and power pins
REPORT_FIRMWARE => 0x79, # report name and version of the firmware
SAMPLING_INTERVAL => 0x7A, # set the poll rate of the main loop
SYSEX_NON_REALTIME => 0x7E, # MIDI Reserved for non-realtime messages
SYSEX_REALTIME => 0x7F, # MIDI Reserved for realtime messages
# these are DEPRECATED to make the naming more consistent
FIRMATA_STRING => 0x71, # same as STRING_DATA
SYSEX_I2C_REQUEST => 0x76, # same as I2C_REQUEST
SYSEX_I2C_REPLY => 0x77, # same as I2C_REPLY
SYSEX_SAMPLING_INTERVAL => 0x7A, # same as SAMPLING_INTERVAL
# pin modes
INPUT => 0x00, # digital pin in digitalOut mode
OUTPUT => 0x01, # digital pin in digitalInput mode
ANALOG => 0x02, # analog pin in analogInput mode
PWM => 0x03, # digital pin in PWM output mode
SERVO => 0x04, # digital pin in Servo output mode
SHIFT => 0x05, # shiftIn/shiftOut mode
I2C => 0x06, # pin included in I2C setup
# Deprecated entries
deprecated => [
qw( FIRMATA_STRING SYSEX_I2C_REQUEST SYSEX_I2C_REPLY SYSEX_SAMPLING_INTERVAL )
],
}, # /Constants for Version 2.1
V_2_02 => {
MAX_DATA_BYTES =>
32, # max number of data bytes in non-Sysex messages
# message command bytes (128-255/0x80-0xFF)
DIGITAL_MESSAGE => 0x90, # send data for a digital pin
ANALOG_MESSAGE => 0xE0, # send data for an analog pin (or PWM)
REPORT_ANALOG => 0xC0, # enable analog input by pin #
REPORT_DIGITAL => 0xD0, # enable digital input by port pair
SET_PIN_MODE => 0xF4, # set a pin to INPUT/OUTPUT/PWM/etc
REPORT_VERSION => 0xF9, # report protocol version
SYSTEM_RESET => 0xFF, # reset from MIDI
START_SYSEX => 0xF0, # start a MIDI Sysex message
END_SYSEX => 0xF7, # end a MIDI Sysex message
# 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).
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
CAPABILITY_RESPONSE => 0x6C, # reply with supported modes and resolution
PIN_STATE_QUERY => 0x6D, # ask for a pin's current mode and value
PIN_STATE_RESPONSE => 0x6E, # reply with a pin's current mode and value
EXTENDED_ANALOG => 0x6F, # analog write (PWM, Servo, etc) to any pin
SERVO_CONFIG => 0x70, # set max angle, minPulse, maxPulse, freq
STRING_DATA => 0x71, # a string message with 14-bits per char
SHIFT_DATA => 0x75, # shiftOut config/data message (34 bits)
I2C_REQUEST => 0x76, # send an I2C read/write request
I2C_REPLY => 0x77, # a reply to an I2C read request
I2C_CONFIG => 0x78, # config I2C settings such as delay times and power pins
REPORT_FIRMWARE => 0x79, # report name and version of the firmware
SAMPLING_INTERVAL => 0x7A, # set the poll rate of the main loop
SYSEX_NON_REALTIME => 0x7E, # MIDI Reserved for non-realtime messages
SYSEX_REALTIME => 0x7F, # MIDI Reserved for realtime messages
# pin modes
INPUT => 0x00, # digital pin in digitalOut mode
OUTPUT => 0x01, # digital pin in digitalInput mode
ANALOG => 0x02, # analog pin in analogInput mode
PWM => 0x03, # digital pin in PWM output mode
SERVO => 0x04, # digital pin in Servo output mode
SHIFT => 0x05, # shiftIn/shiftOut mode
I2C => 0x06, # pin included in I2C setup
# Deprecated entries
deprecated => [
qw( FIRMATA_STRING SYSEX_I2C_REQUEST SYSEX_I2C_REPLY SYSEX_SAMPLING_INTERVAL )
],
}, # /Constants for Version 2.2
V_2_03 => {
MAX_DATA_BYTES =>
32, # max number of data bytes in non-Sysex messages
# message command bytes (128-255/0x80-0xFF)
DIGITAL_MESSAGE => 0x90, # send data for a digital pin
ANALOG_MESSAGE => 0xE0, # send data for an analog pin (or PWM)
REPORT_ANALOG => 0xC0, # enable analog input by pin #
REPORT_DIGITAL => 0xD0, # enable digital input by port pair
SET_PIN_MODE => 0xF4, # set a pin to INPUT/OUTPUT/PWM/etc
REPORT_VERSION => 0xF9, # report protocol version
SYSTEM_RESET => 0xFF, # reset from MIDI
START_SYSEX => 0xF0, # start a MIDI Sysex message
END_SYSEX => 0xF7, # end a MIDI Sysex message
# 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).
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
CAPABILITY_RESPONSE => 0x6C, # reply with supported modes and resolution
PIN_STATE_QUERY => 0x6D, # ask for a pin's current mode and value
PIN_STATE_RESPONSE => 0x6E, # reply with a pin's current mode and value
EXTENDED_ANALOG => 0x6F, # analog write (PWM, Servo, etc) to any pin
SERVO_CONFIG => 0x70, # set max angle, minPulse, maxPulse, freq
STRING_DATA => 0x71, # a string message with 14-bits per char
SHIFT_DATA => 0x75, # shiftOut config/data message (34 bits)
I2C_REQUEST => 0x76, # send an I2C read/write request
I2C_REPLY => 0x77, # a reply to an I2C read request
I2C_CONFIG => 0x78, # config I2C settings such as delay times and power pins
REPORT_FIRMWARE => 0x79, # report name and version of the firmware
SAMPLING_INTERVAL => 0x7A, # set the poll rate of the main loop
SYSEX_NON_REALTIME => 0x7E, # MIDI Reserved for non-realtime messages
SYSEX_REALTIME => 0x7F, # MIDI Reserved for realtime messages
# pin modes
INPUT => 0x00, # digital pin in digitalOut mode
OUTPUT => 0x01, # digital pin in digitalInput mode
ANALOG => 0x02, # analog pin in analogInput mode
PWM => 0x03, # digital pin in PWM output mode
SERVO => 0x04, # digital pin in Servo output mode
SHIFT => 0x05, # shiftIn/shiftOut mode
I2C => 0x06, # pin included in I2C setup
# Deprecated entries
deprecated => [
qw( FIRMATA_STRING SYSEX_I2C_REQUEST SYSEX_I2C_REPLY SYSEX_SAMPLING_INTERVAL )
],
}, # /Constants for Version 2.3 (same as V_2_02)
V_2_04 => {
MAX_DATA_BYTES => 64, # max number of data bytes in non-Sysex messages
# message command bytes (128-255/0x80-0xFF)
DIGITAL_MESSAGE => 0x90, # send data for a digital pin
ANALOG_MESSAGE => 0xE0, # send data for an analog pin (or PWM)
REPORT_ANALOG => 0xC0, # enable analog input by pin #
REPORT_DIGITAL => 0xD0, # enable digital input by port pair
SET_PIN_MODE => 0xF4, # set a pin to INPUT/OUTPUT/PWM/etc
REPORT_VERSION => 0xF9, # report protocol version
SYSTEM_RESET => 0xFF, # reset from MIDI
START_SYSEX => 0xF0, # start a MIDI Sysex message
END_SYSEX => 0xF7, # end a MIDI Sysex message
# 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).
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
CAPABILITY_RESPONSE => 0x6C, # reply with supported modes and resolution
PIN_STATE_QUERY => 0x6D, # ask for a pin's current mode and value
PIN_STATE_RESPONSE => 0x6E, # reply with a pin's current mode and value
EXTENDED_ANALOG => 0x6F, # analog write (PWM, Servo, etc) to any pin
SERVO_CONFIG => 0x70, # set max angle, minPulse, maxPulse, freq
STRING_DATA => 0x71, # a string message with 14-bits per char
ONEWIRE_REQUEST => 0x73, # send an OneWire read/write/reset/select/skip/search request
ONEWIRE_REPLY => 0x7D, # reply to a OneWire read/search request
SHIFT_DATA => 0x75, # shiftOut config/data message (34 bits)
I2C_REQUEST => 0x76, # send an I2C read/write request
I2C_REPLY => 0x77, # a reply to an I2C read request
I2C_CONFIG => 0x78, # config I2C settings such as delay times and power pins
REPORT_FIRMWARE => 0x79, # report name and version of the firmware
SAMPLING_INTERVAL => 0x7A, # set the poll rate of the main loop
SCHEDULER_REQUEST => 0x7B, # send a createtask/deletetask/addtotask/schedule/querytasks/querytask request to the scheduler
SCHEDULER_REPLY => 0x7C, # a reply to a querytasks/querytask-request from the scheduler
SYSEX_NON_REALTIME => 0x7E, # MIDI Reserved for non-realtime messages
SYSEX_REALTIME => 0x7F, # MIDI Reserved for realtime messages
# pin modes
INPUT => 0x00, # digital pin in digitalOut mode
OUTPUT => 0x01, # digital pin in digitalInput mode
ANALOG => 0x02, # analog pin in analogInput mode
PWM => 0x03, # digital pin in PWM output mode
SERVO => 0x04, # digital pin in Servo output mode
SHIFT => 0x05, # shiftIn/shiftOut mode
I2C => 0x06, # pin included in I2C setup
ONEWIRE => 0x07,
# Deprecated entries
deprecated => [
qw( FIRMATA_STRING SYSEX_I2C_REQUEST SYSEX_I2C_REPLY SYSEX_SAMPLING_INTERVAL )
],
}, # /Constants for Version 2.4
}
);
# Handle the reverse lookups of the protocol
$COMMAND_LOOKUP = {};
while ( my ( $protocol_version, $protocol_commands ) = each %$COMMANDS ) {
my $protocol_lookup = $COMMAND_LOOKUP->{$protocol_version} = {};
my $deprecated = $protocol_lookup->{deprecated} || [];
my $deprecated_lookup = { map { ( $_ => 1 ) } @$deprecated };
while ( my ( $protocol_command, $command_value ) =
each %$protocol_commands )
{
next if $protocol_command eq 'deprecated';
next if $deprecated_lookup->{$protocol_command};
$protocol_lookup->{$command_value} = $protocol_command;
}
}
# Now we consolidate all the string keynames into a single master list.
use constant (
$COMMAND_NAMES = {
map {
map { ( $_ => $_ ) }
keys %$_
} values %$COMMANDS
}
);
use constant { COMMAND_NAMES => [ $COMMAND_NAMES = [ keys %$COMMAND_NAMES ] ] };
@EXPORT_OK = (
@$COMMAND_NAMES, keys %$BASE,
keys %$COMMANDS,
qw( $COMMANDS $COMMAND_NAMES $COMMAND_LOOKUP )
);
%EXPORT_TAGS = ( all => \@EXPORT_OK );
1;

View File

@ -0,0 +1,94 @@
package Device::Firmata::Error;
# ==================================================================
=head1 NAME
Device::Firmata::Error - Error handlers
=cut
use strict;
use Exporter;
use vars qw/
@ISA
%ERRORS
@EXPORT
$FIRMATA_ERROR_DEFAULT
@ERROR_STACK
/;
use Device::Firmata::Base;
@ISA = 'Exporter';
@EXPORT = qw();
$FIRMATA_ERROR_DEFAULT = -1;
=head2 error
The base error reporting system. All errors will be
stored in this object until the errors flush code is called.
This will allow the system to collect all errors that occur
in various parts of the system in one place. Very useful
for error reporting since it's a simple call to find
out the last error.
Invocation of this function
$err->error( [numerical error level], ErrorMessage, ... parameters ... );
ErrorMessage can be in the format "KEY" that will be referenced by
Device::Firmata::Base->language or "KEY:Message" where if ->language does
not map to anything, the error will default to Message
=cut
sub error {
# --------------------------------------------------
#
my $self = shift;
my $error_level = $_[0] =~ /^\-?\d+$/ ? shift : $FIRMATA_ERROR_DEFAULT;
my $message = shift;
my $error_code;
if ( $message =~ /^([A-Z0-9_]+)\s*:\s*/ ) {
$error_code = $1;
}
else {
$error_code = $message;
};
my $text = Device::Firmata::Base->language($message,@_);
push @ERROR_STACK, [ $text, $error_level, $text ];
if ( $error_level < 1 ) {
my $i = 1;
my ( $pkg, $fn, $line );
# Proceed up the call stack until we find out where the error likely occured (ie. Not in Device::Firmata::Base)
do { ( $pkg, $fn, $line ) = caller($i); $i++; } while ( $pkg eq 'Device::Firmata::Base' );
$error_level < 0 ? die "\@$fn:$pkg:$line". ' : ' . $text . "\n"
: warn "\@$fn:$pkg:$line". ' : ' . $text . "\n";
};
# warn "Error called wih args: @_ from " . join( " ", caller() ) . "\n";
# require Carp;
# Carp::cluck();
return $text;
}
=head2 errors_flush
=cut
sub errors_flush {
# --------------------------------------------------
@ERROR_STACK = ();
}
1;

View File

@ -0,0 +1,85 @@
package Device::Firmata::IO;
=head1 NAME
Device::Firmata::IO - implement the low level serial IO
=cut
use strict;
use warnings;
use vars qw/ $SERIAL_CLASS /;
use Device::Firmata::Base
ISA => 'Device::Firmata::Base',
FIRMATA_ATTRIBS => {
handle => undef,
baudrate => 57600,
};
$SERIAL_CLASS = $^O eq 'MSWin32' ? 'Win32::Serialport'
: 'Device::SerialPort';
eval "require $SERIAL_CLASS";
=head2 open
=cut
sub open {
# --------------------------------------------------
my ( $pkg, $serial_port, $opts ) = @_;
my $self = ref $pkg ? $pkg : $pkg->new($opts);
my $serial_obj = $SERIAL_CLASS->new( $serial_port, 1, 0 ) or return;
$self->attach($serial_obj,$opts);
$self->{handle}->baudrate($self->{baudrate});
$self->{handle}->databits(8);
$self->{handle}->stopbits(1);
return $self;
}
sub attach {
my ( $pkg, $serial_obj, $opts ) = @_;
my $self = ref $pkg ? $pkg : $pkg->new($opts);
$self->{handle} = $serial_obj;
return $self;
}
=head2 data_write
Dump a bunch of data into the comm port
=cut
sub data_write {
# --------------------------------------------------
my ( $self, $buf ) = @_;
$Device::Firmata::DEBUG and print ">".join(",",map{sprintf"%02x",ord$_}split//,$buf)."\n";
return $self->{handle}->write( $buf );
}
=head2 data_read
We fetch up to $bytes from the comm port
This function is non-blocking
=cut
sub data_read {
# --------------------------------------------------
my ( $self, $bytes ) = @_;
my ( $count, $string ) = $self->{handle}->read($bytes);
if ( $Device::Firmata::DEBUG and $string ) {
print "<".join(",",map{sprintf"%02x",ord$_}split//,$string)."\n";
}
return $string;
}
1;

View File

@ -0,0 +1,108 @@
package Device::Firmata::Language;
# ==================================================================
=head1 NAME
Device::Firmata::Language - Localization
=cut
use strict;
use vars qw/
$FIRMATA_LOCALE
$FIRMATA_LOCALE_PATH
$FIRMATA_LOCALE_MESSAGES
/;
use Device::Firmata::Base
ISA => 'Device::Firmata::Base',
FIRMATA_ATTRIBS => {
messages => {},
};
$FIRMATA_LOCALE_MESSAGES = {
};
$FIRMATA_LOCALE = 'en';
$FIRMATA_LOCALE_PATH = '.';
=head2 numbers
=cut
sub numbers {
# --------------------------------------------------
}
=head2 date
=cut
sub date {
# --------------------------------------------------
}
=head2 language
=cut
sub language {
# --------------------------------------------------
my $self = shift;
my $messages = $FIRMATA_LOCALE_MESSAGES->{$FIRMATA_LOCALE} ||= do {
my $target_fpath = "$FIRMATA_LOCALE_PATH/$FIRMATA_LOCALE.txt";
my $m;
require Symbol;
my $fh = Symbol::gensym();
if ( -f $target_fpath ) {
open $fh, "<$target_fpath" or die $!;
}
else {
$fh = \*DATA;
}
while ( my $l = <$fh> ) {
next if $l =~ /^\s*$/;
$l =~ /([^\s]*)\s+(.*)/;
( $m ||= {} )->{$1} = $2;
}
close $fh;
$m;
};
# This will parse messages coming through such that it will
# be possible to encode a language string with a code in the
# following formats:
#
# ->language( "CODE", $parametrs ... )
# ->language( "CODE:Default Message %s", $parametrs ... )
#
my $message = shift or return;
$message =~ s/^([\w_]+)\s*:?\s*//;
my $key = $1;
my $message_template;
# Get the message template in the following order:
# 1. The local object if available
# 2. The global message object
# 3. The provided default message
#
ref $self and $message_template = $self->{messages}{$key};
$message_template ||= $messages->{$key} || $message;
return sprintf( $message_template, @_ );
}
1;
__DATA__
FIRMATA__unhandled Unhandled attribute '%s' called
FIRMATA__unknown Unknown/Unhandled error encountered: %s
FIRMATA__separator ,

View File

@ -0,0 +1,834 @@
package Device::Firmata::Platform;
=head1 NAME
Device::Firmata::Platform - platform specifics
=cut
use strict;
use Time::HiRes qw/time/;
use Device::Firmata::Constants qw/ :all /;
use Device::Firmata::IO;
use Device::Firmata::Protocol;
use Device::Firmata::Base
ISA => 'Device::Firmata::Base',
FIRMATA_ATTRIBS => {
# Object handlers
io => undef,
protocol => undef,
# Used for internal tracking of events/parameters
#protocol_version => undef,
#sysex_mode => undef,
sysex_data => [],
# To track internal status
analog_pins => [],
ports => [],
pins => {},
pin_modes => {},
# To notify on events
digital_observer => [],
analog_observer => [],
sysex_observer => undef,
i2c_observer => undef,
onewire_observer => [],
scheduler_observer => undef,
string_observer => undef,
# To track scheduled tasks
tasks => [],
# For information about the device. eg: firmware version
metadata => {},
# latest STRING_DATA response:
stringresponse => {},
};
=head2 open
Connect to the IO port and do some basic operations
to find out how to connect to the device
=cut
sub open {
# --------------------------------------------------
my ( $pkg, $port, $opts ) = @_;
my $self = ref $pkg ? $pkg : $pkg->new($opts);
my $ioport = Device::Firmata::IO->open( $port, $opts ) or return;
return $self->attach( $ioport, $opts );
}
sub attach {
# --------------------------------------------------
# Attach to an open IO port and do some basic operations
# to find out how to connect to the device
#
my ( $pkg, $port, $opts ) = @_;
my $self = ref $pkg ? $pkg : $pkg->new($opts);
$self->{io} = $port or return;
$self->{protocol} = Device::Firmata::Protocol->new($opts) or return;
return $self;
}
sub detach {
my $self = shift;
delete $self->{io};
}
sub system_reset {
my $self = shift;
$self->{io}->data_write($self->{protocol}->message_prepare( SYSTEM_RESET => 0 ));
$self->{sysex_data} = [];
$self->{analog_pins} = [];
$self->{ports} = [];
$self->{pins} = {};
$self->{pin_modes} = {};
$self->{digital_observer} = [];
$self->{analog_observer} = [];
$self->{sysex_observer} = undef;
$self->{i2c_observer} = undef;
$self->{onewire_observer} = [];
$self->{scheduler_observer} = undef;
$self->{tasks} = [];
$self->{metadata} = {};
}
=head2 messages_handle
Receive identified message packets and convert them
into their appropriate structures and parse
them as required
=cut
sub messages_handle {
# --------------------------------------------------
my ( $self, $messages ) = @_;
return unless $messages;
return unless @$messages;
# Now, handle the messages
my $proto = $self->{protocol};
for my $message (@$messages) {
my $command = $message->{command_str};
my $data = $message->{data};
COMMAND_HANDLE: {
#* digital I/O message 0x90 port LSB(bits 0-6) MSB(bits 7-13)
# Handle pin messages
$command eq 'DIGITAL_MESSAGE' and do {
my $port_number = $message->{command} & 0x0f;
my $port_state = $data->[0] | ( $data->[1] << 7 );
my $old_state = $self->{ports}[$port_number];
my $changed_state =
defined $old_state ? $old_state ^ $port_state : 0xFF;
my $observers = $self->{digital_observer};
my $pinbase = $port_number << 3;
for ( my $i = 0 ; $i < 8 ; $i++ ) {
my $pin = $pinbase + $i;
my $observer = $observers->[$pin];
if ($observer) {
my $pin_mask = 1 << $i;
if ( $changed_state & $pin_mask ) {
$observer->{method}(
$pin,
defined $old_state
? ( $old_state & $pin_mask ) > 0
? 1
: 0
: undef,
( $port_state & $pin_mask ) > 0 ? 1 : 0,
$observer->{context}
);
}
}
}
$self->{ports}[$port_number] = $port_state;
};
# Handle analog pin messages
$command eq 'ANALOG_MESSAGE' and do {
my $pin_number = $message->{command} & 0x0f;
my $pin_value = ( $data->[0] | ( $data->[1] << 7 ) ) / 1023;
if (defined $self->{metadata}{analog_mappings}) {
$pin_number = $self->{metadata}{analog_mappings}{$pin_number};
}
my $observer = $self->{analog_observer}[$pin_number];
if ($observer) {
my $old_value = $self->{analog_pins}[$pin_number];
if ( !defined $old_value or !($old_value eq $pin_value) ) {
$observer->{method}( $pin_number, $old_value, $pin_value, $observer->{context} );
}
}
$self->{analog_pins}[$pin_number] = $pin_value;
};
# Handle metadata information
$command eq 'REPORT_VERSION' and do {
$self->{metadata}{firmware_version} = sprintf "V_%i_%02i",
@$data;
last;
};
# SYSEX handling
$command eq 'START_SYSEX' and do {
last;
};
$command eq 'DATA_SYSEX' and do {
my $sysex_data = $self->{sysex_data};
push @$sysex_data, @$data;
last;
};
$command eq 'END_SYSEX' and do {
my $sysex_data = $self->{sysex_data};
my $sysex_message = $proto->sysex_parse($sysex_data);
if ( defined $sysex_message ) {
my $observer = $self->{sysex_observer};
if (defined $observer) {
$observer->{method} ($sysex_message, $observer->{context});
}
$self->sysex_handle($sysex_message);
}
$self->{sysex_data} = [];
last;
};
}
$Device::Firmata::DEBUG and print " < $command\n";
}
}
=head2 sysex_handle
Receive identified sysex packets and convert them
into their appropriate structures and parse
them as required
=cut
sub sysex_handle {
# --------------------------------------------------
my ( $self, $sysex_message ) = @_;
my $data = $sysex_message->{data};
COMMAND_HANDLER: {
$sysex_message->{command_str} eq 'REPORT_FIRMWARE' and do {
$self->{metadata}{firmware_version} = sprintf "V_%i_%02i",
$data->{major_version}, $data->{minor_version};
$self->{metadata}{firmware} = $data->{firmware};
last;
};
$sysex_message->{command_str} eq 'CAPABILITY_RESPONSE' and do {
my $capabilities = $data->{capabilities};
$self->{metadata}{capabilities} = $capabilities;
my @analogpins;
my @inputpins;
my @outputpins;
my @i2cpins;
my @onewirepins;
foreach my $pin (keys %$capabilities) {
if (defined $capabilities->{$pin}) {
if ($capabilities->{$pin}->{PIN_INPUT+0}) {
push @inputpins, $pin;
}
if ($capabilities->{$pin}->{PIN_OUTPUT+0}) {
push @outputpins, $pin;
}
if ($capabilities->{$pin}->{PIN_ANALOG+0}) {
push @analogpins, $pin;
}
if ($capabilities->{$pin}->{PIN_I2C+0}) {
push @i2cpins, $pin;
}
if ($capabilities->{$pin}->{PIN_OUTPUT+0}) {
push @onewirepins, $pin;
}
}
}
$self->{metadata}{input_pins} = \@inputpins;
$self->{metadata}{output_pins} = \@outputpins;
$self->{metadata}{analog_pins} = \@analogpins;
$self->{metadata}{i2c_pins} = \@i2cpins;
$self->{metadata}{onewire_pins} = \@onewirepins;
last;
};
$sysex_message->{command_str} eq 'ANALOG_MAPPING_RESPONSE' and do {
$self->{metadata}{analog_mappings} = $data->{mappings};
last;
};
$sysex_message->{command_str} eq 'PIN_STATE_RESPONSE' and do {
if (!defined $self->{metadata}{pinstates}) {
$self->{metadata}{pinstates} = {};
};
$self->{metadata}{pinstates}{ $data->{pin} } = {
mode => $data->{mode},
state => $data->{state},
};
last;
};
$sysex_message->{command_str} eq 'I2C_REPLY' and do {
my $observer = $self->{i2c_observer};
if (defined $observer) {
$observer->{method}( $data, $observer->{context} );
}
last;
};
$sysex_message->{command_str} eq 'ONEWIRE_REPLY' and do {
my $pin = $data->{pin};
my $observer = $self->{onewire_observer}[$pin];
if (defined $observer) {
$observer->{method}( $data, $observer->{context} );
}
last;
};
$sysex_message->{command_str} eq 'SCHEDULER_REPLY' and do {
my $observer = $self->{scheduler_observer};
if (defined $observer) {
$observer->{method}( $data, $observer->{context} );
}
last;
};
$sysex_message->{command_str} eq 'STRING_DATA' and do {
my $observer = $self->{string_observer};
$self->{stringresponse} = $data->{string};
if (defined $observer) {
$observer->{method}( $data->{string}, $observer->{context} );
}
last;
}
}
}
=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 ;)
=cut
sub probe {
# --------------------------------------------------
my ($self) = @_;
my $proto = $self->{protocol};
my $io = $self->{io};
$self->{metadata}{firmware_version} = '';
# Wait for 10 seconds only
my $end_tics = time + 10;
# Query every .5 seconds
my $query_tics = time;
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();
}
return 1;
}
$query_tics = time + 0.5;
}
select (undef,undef,undef,0.1);
}
return undef;
}
=head2 pin_mode
Similar to the pinMode function on the
arduino
=cut
sub pin_mode {
# --------------------------------------------------
my ( $self, $pin, $mode ) = @_;
return undef unless $self->is_supported_mode($pin,$mode);
PIN_MODE_HANDLER: {
( $mode == PIN_INPUT or $mode == PIN_OUTPUT ) and do {
my $port_number = $pin >> 3;
$self->{io}->data_write($self->{protocol}->message_prepare( REPORT_DIGITAL => $port_number, 1 ));
$self->{io}->data_write($self->{protocol}->message_prepare( SET_PIN_MODE => 0, $pin, $mode ));
last;
};
$mode == PIN_ANALOG and do {
my $port_number = $pin >> 3;
$self->{io}->data_write($self->{protocol}->message_prepare( REPORT_ANALOG => $port_number, 1 ));
$self->{io}->data_write($self->{protocol}->message_prepare( SET_PIN_MODE => 0, $pin, $mode ));
last;
};
( $mode == PIN_PWM || $mode == PIN_I2C || $mode == PIN_ONEWIRE ) and do {
$self->{io}->data_write($self->{protocol}->message_prepare( SET_PIN_MODE => 0, $pin, $mode ));
last;
};
};
$self->{pin_modes}->{$pin} = $mode;
return 1;
}
=head2 digital_write
Analogous to the digitalWrite function on the
arduino
=cut
sub digital_write {
# --------------------------------------------------
my ( $self, $pin, $state ) = @_;
return undef unless $self->is_configured_mode($pin,PIN_OUTPUT);
my $port_number = $pin >> 3;
my $pin_offset = $pin % 8;
my $pin_mask = 1 << $pin_offset;
my $port_state = $self->{ports}[$port_number] ||= 0;
if ($state) {
$port_state |= $pin_mask;
}
else {
$port_state &= $pin_mask ^ 0xff;
}
$self->{ports}[$port_number] = $port_state;
$self->{io}->data_write($self->{protocol}->message_prepare( DIGITAL_MESSAGE => $port_number, $port_state ));
return 1;
}
=head2 digital_read
Analogous to the digitalRead function on the
arduino
=cut
sub digital_read {
# --------------------------------------------------
my ( $self, $pin ) = @_;
return undef unless $self->is_configured_mode($pin,PIN_INPUT);
my $port_number = $pin >> 3;
my $pin_offset = $pin % 8;
my $pin_mask = 1 << $pin_offset;
my $port_state = $self->{ports}[$port_number] ||= 0;
return ( $port_state & $pin_mask ? 1 : 0 );
}
=head2 analog_read
Fetches the analog value of a pin
=cut
sub analog_read {
# --------------------------------------------------
#
my ( $self, $pin ) = @_;
return undef unless $self->is_configured_mode($pin,PIN_ANALOG);
return $self->{analog_pins}[$pin];
}
=head2 analog_write
=cut
sub analog_write {
# --------------------------------------------------
# Sets the PWM value on an arduino
#
my ( $self, $pin, $value ) = @_;
return undef unless $self->is_configured_mode($pin,PIN_PWM);
# FIXME: 8 -> 7 bit translation should be done in the protocol module
my $byte_0 = $value & 0x7f;
my $byte_1 = $value >> 7;
return $self->{io}->data_write($self->{protocol}->message_prepare( ANALOG_MESSAGE => $pin, $byte_0, $byte_1 ));
}
=head2 pwm_write
pmw_write is an alias for analog_write
=cut
*pwm_write = *analog_write;
sub firmware_version_query {
my $self = shift;
my $firmware_version_query_packet = $self->{protocol}->packet_query_firmware;
return $self->{io}->data_write($firmware_version_query_packet);
}
sub capability_query {
my $self = shift;
my $capability_query_packet = $self->{protocol}->packet_query_capability();
return $self->{io}->data_write($capability_query_packet);
}
sub analog_mapping_query {
my $self = shift;
my $analog_mapping_query_packet = $self->{protocol}->packet_query_analog_mapping();
return $self->{io}->data_write($analog_mapping_query_packet);
}
sub pin_state_query {
my ($self,$pin) = @_;
my $pin_state_query_packet = $self->{protocol}->packet_query_pin_state($pin);
return $self->{io}->data_write($pin_state_query_packet);
}
sub sampling_interval {
my ( $self, $sampling_interval ) = @_;
my $sampling_interval_packet =
$self->{protocol}->packet_sampling_interval($sampling_interval);
return $self->{io}->data_write($sampling_interval_packet);
}
sub i2c_write {
my ($self,$address,@data) = @_;
return $self->{io}->data_write($self->{protocol}->packet_i2c_request($address,0x0,@data));
}
sub i2c_readonce {
my ($self,$address,$register,$numbytes) = @_;
my $packet = (defined $numbytes)
? $self->{protocol}->packet_i2c_request($address,0x8,$register,$numbytes)
: $self->{protocol}->packet_i2c_request($address,0x8,$register);
return $self->{io}->data_write($packet);
}
sub i2c_read {
my ($self,$address,$register,$numbytes) = @_;
return $self->{io}->data_write($self->{protocol}->packet_i2c_request($address,0x10,$register,$numbytes));
}
sub i2c_stopreading {
my ($self,$address) = @_;
return $self->{io}->data_write($self->{protocol}->packet_i2c_request($address,0x18));
}
sub i2c_config {
my ( $self, $delay, @data ) = @_;
return $self->{io}->data_write($self->{protocol}->packet_i2c_config($delay,@data));
}
sub scheduler_create_task {
my $self = shift;
my $id=-1;
my $tasks = $self->{tasks};
for my $task (@$tasks) {
if ($id < $task->{id}) {
$id = $task->{id};
}
}
$id++;
my $newtask = {
id => $id,
data => [],
time_ms => undef,
};
push @$tasks,$newtask;
return $id;
}
sub scheduler_delete_task {
my ($self,$id) = @_;
my $tasks = $self->{tasks};
for my $task (@$tasks) {
if ($id == $task->{id}) {
if (defined $task->{time_ms}) {
my $packet = $self->{protocol}->packet_delete_task($id);
$self->{io}->data_write($packet);
}
delete $self->{tasks}[$id]; # delete $array[index]; (not delete @array[index];)
last;
}
}
}
sub scheduler_add_to_task {
my ($self,$id,$packet) = @_;
my $tasks = $self->{tasks};
for my $task (@$tasks) {
if ($id == $task->{id}) {
my $data = $task->{data};
push @$data,unpack "C*", $packet;
last;
}
}
}
sub scheduler_schedule_task {
my ($self,$id,$time_ms) = @_;
my $tasks = $self->{tasks};
for my $task (@$tasks) {
if ($id == $task->{id}) {
if (!(defined $task->{time_ms})) { # TODO - a bit unclear why I put this test here in the first place. -> TODO: investigate and remove this check if not nessesary
my $data = $task->{data};
my $len = @$data;
my $packet = $self->{protocol}->packet_create_task($id,$len);
$self->{io}->data_write($packet);
my $bytesPerPacket = 53; # (64-1)*7/8-2 (1 byte command, 1 byte for subcommand, 1 byte taskid)
my $j=0;
my @packetdata;
for (my $i=0;$i<$len;$i++) {
push @packetdata,@$data[$i];
$j++;
if ($j==$bytesPerPacket) {
$j=0;
$packet = $self->{protocol}->packet_add_to_task($id,@packetdata);
$self->{io}->data_write($packet);
@packetdata = ();
}
}
if ($j>0) {
$packet = $self->{protocol}->packet_add_to_task($id,@packetdata);
$self->{io}->data_write($packet);
}
}
my $packet = $self->{protocol}->packet_schedule_task($id,$time_ms);
$self->{io}->data_write($packet);
last;
}
}
}
sub scheduler_reset {
my $self = shift;
my $packet = $self->{protocol}->packet_reset_scheduler;
$self->{io}->data_write($packet);
$self->{tasks} = [];
}
sub scheduler_query_all_tasks {
my $self = shift;
my $packet = $self->{protocol}->packet_query_all_tasks;
$self->{io}->data_write($packet);
}
sub scheduler_query_task {
my ($self,$id) = @_;
my $packet = $self->{protocol}->packet_query_task($id);
$self->{io}->data_write($packet);
}
# SEARCH_REQUEST,
# CONFIG_REQUEST,
#$args = {
# reset => undef | 1,
# skip => undef | 1,
# select => undef | device,
# read => undef | short int,
# delay => undef | long int,
# write => undef | bytes[],
#}
sub onewire_search {
my ( $self, $pin ) = @_;
return undef unless $self->is_configured_mode($pin,PIN_ONEWIRE);
return $self->{io}->data_write($self->{protocol}->packet_onewire_search_request( $pin ));
}
sub onewire_search_alarms {
my ( $self, $pin ) = @_;
return undef unless $self->is_configured_mode($pin,PIN_ONEWIRE);
return $self->{io}->data_write($self->{protocol}->packet_onewire_search_alarms_request( $pin ));
}
sub onewire_config {
my ( $self, $pin, $power ) = @_;
return undef unless $self->is_configured_mode($pin,PIN_ONEWIRE);
return $self->{io}->data_write($self->{protocol}->packet_onewire_config_request( $pin, $power ));
}
sub onewire_reset {
my ( $self, $pin ) = @_;
return $self->onewire_command_series( $pin, {reset => 1} );
}
sub onewire_skip {
my ( $self, $pin ) = @_;
return $self->onewire_command_series( $pin, {skip => 1} );
}
sub onewire_select {
my ( $self, $pin, $device ) = @_;
return $self->onewire_command_series( $pin, {select => $device} );
}
sub onewire_read {
my ( $self, $pin, $numBytes ) = @_;
return $self->onewire_command_series( $pin, {read => $numBytes} );
}
sub onewire_write {
my ( $self, $pin, @data ) = @_;
return $self->onewire_command_series( $pin, {write => \@data} );
}
sub onewire_command_series {
my ( $self, $pin, $args ) = @_;
return undef unless $self->is_configured_mode($pin,PIN_ONEWIRE);
return $self->{io}->data_write($self->{protocol}->packet_onewire_request( $pin, $args ));
}
=head2 poll
Call this function every once in a while to
check up on the status of the comm port, receive
and process data from the arduino
=cut
sub poll {
# --------------------------------------------------
my $self = shift;
my $buf = $self->{io}->data_read(100) or return;
my $messages = $self->{protocol}->message_data_receive($buf);
$self->messages_handle($messages);
return $messages;
}
sub observe_digital {
my ( $self, $pin, $observer, $context ) = @_;
return undef unless ($self->is_supported_mode($pin,PIN_INPUT));
$self->{digital_observer}[$pin] = {
method => $observer,
context => $context,
};
return 1;
}
sub observe_analog {
my ( $self, $pin, $observer, $context ) = @_;
return undef unless ($self->is_supported_mode($pin,PIN_ANALOG));
$self->{analog_observer}[$pin] = {
method => $observer,
context => $context,
};
return 1;
}
sub observe_sysex {
my ( $self, $observer, $context ) = @_;
$self->{sysex_observer} = {
method => $observer,
context => $context,
};
return 1;
}
sub observe_i2c {
my ( $self, $observer, $context ) = @_;
return undef if (defined $self->{metadata}->{i2cpins} && @$self->{metadata}->{i2cpins} == 0 );
$self->{i2c_observer} = {
method => $observer,
context => $context,
};
return 1;
}
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));
$self->{onewire_observer}[$pin] = {
method => $observer,
context => $context,
};
return 1;
}
sub observe_scheduler {
my ( $self, $observer, $context ) = @_;
$self->{scheduler_observer} = {
method => $observer,
context => $context,
};
return 1;
}
sub observe_string {
my ( $self, $observer, $context ) = @_;
$self->{string_observer} = {
method => $observer,
context => $context,
};
return 1;
}
sub is_supported_mode {
my ($self,$pin,$mode) = @_;
die "unsupported mode '".$mode."' for pin '".$pin."'" if (defined $self->{metadata}->{capabilities} and (!(defined $self->{metadata}->{capabilities}->{$pin}) or !(defined $self->{metadata}->{capabilities}->{$pin}->{$mode})));
return 1;
}
sub is_configured_mode {
my ($self,$pin,$mode) = @_;
die "pin '".$pin."' is not configured for mode '".$mode."'" if (!defined $self->{pin_modes}->{$pin} or $self->{pin_modes}->{$pin} != $mode);
return 1;
}
1;

View File

@ -0,0 +1,19 @@
package Device::Firmata::Platform::Arduino;
=head1 NAME
Device::Firmata::Platform::Arduino - subclass for the arduino itself
=head1 DESCRIPTION
No customization requried at this time so this is just a specification of the
Device::Firmata::Platform class
=cut
use strict;
use Device::Firmata::Platform;
use Device::Firmata::Base
ISA => 'Device::Firmata::Platform';
1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,46 @@
Device-Firmata
Device::Firmata provides an host side interface to the Firmata protocol for talking to arduino microcontroler platform. See http://firmata.org/wiki/Main_Page for some details.
INSTALLATION
To install this module, run the following commands:
perl Makefile.PL
make
make test
make install
SUPPORT AND DOCUMENTATION
After installing, you can find documentation for this module with the
perldoc command.
perldoc Device::Firmata
You can also look for information at:
RT, CPAN's request tracker
http://rt.cpan.org/NoAuth/Bugs.html?Dist=Device-Firmata
AnnoCPAN, Annotated CPAN documentation
http://annocpan.org/dist/Device-Firmata
CPAN Ratings
http://cpanratings.perl.org/d/Device-Firmata
Search CPAN
http://search.cpan.org/dist/Device-Firmata/
LICENSE AND COPYRIGHT
Copyright (C) 2011 amimato
Copyright (C) 2012 ntruchsess
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
by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.