From ff846002cc490ac4617546a9bcd95a776c1d9d8b Mon Sep 17 00:00:00 2001 From: ntruchsess Date: Sun, 27 Jan 2013 19:50:22 +0000 Subject: [PATCH] Initial commit FRM modules (for Arduino/Firmata) git-svn-id: https://svn.fhem.de/fhem/trunk@2581 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/10_FRM.pm | 448 ++++++++++++++++++++++++++++++++++++++++ fhem/FHEM/20_FRM_AD.pm | 114 ++++++++++ fhem/FHEM/20_FRM_I2C.pm | 108 ++++++++++ fhem/FHEM/20_FRM_IN.pm | 114 ++++++++++ fhem/FHEM/20_FRM_OUT.pm | 111 ++++++++++ fhem/FHEM/20_FRM_PWM.pm | 105 ++++++++++ 6 files changed, 1000 insertions(+) create mode 100755 fhem/FHEM/10_FRM.pm create mode 100755 fhem/FHEM/20_FRM_AD.pm create mode 100755 fhem/FHEM/20_FRM_I2C.pm create mode 100755 fhem/FHEM/20_FRM_IN.pm create mode 100755 fhem/FHEM/20_FRM_OUT.pm create mode 100755 fhem/FHEM/20_FRM_PWM.pm diff --git a/fhem/FHEM/10_FRM.pm b/fhem/FHEM/10_FRM.pm new file mode 100755 index 000000000..93ea096bb --- /dev/null +++ b/fhem/FHEM/10_FRM.pm @@ -0,0 +1,448 @@ +############################################## +package main; + +use strict; +use warnings; +use Device::Firmata::Constants qw/ :all /; +use Device::Firmata::IO; +use Device::Firmata::Protocol; +use Device::Firmata::Platform; + +sub FRM_Set($@); +sub FRM_Attr(@); +sub Log($$); + +##################################### +sub FRM_Initialize($) { + my ($hash) = @_; + + require "$main::attr{global}{modpath}/FHEM/DevIo.pm"; + + # Provider + $hash->{Clients} = + ":FRM_IN:FRM_OUT:FRM_AD:FRM_PWM:FRM_I2C:"; + $hash->{ReadyFn} = "FRM_Ready"; + $hash->{ReadFn} = "FRM_Read"; + + # Consumer + $hash->{DefFn} = "FRM_Define"; + $hash->{UndefFn} = "FRM_Undef"; + $hash->{GetFn} = "FRM_Get"; + $hash->{SetFn} = "FRM_Set"; + $hash->{AttrFn} = "FRM_Attr"; + + $hash->{AttrList} = "model:nano dummy:1,0 loglevel:0,1,2,3,4,5 sampling-interval i2c-config $main::readingFnAttributes"; +} + +##################################### +sub FRM_Define($$) { + my ( $hash, $def ) = @_; + my @a = split( "[ \t][ \t]*", $def ); + my $po; + + DevIo_CloseDev($hash); + + my $name = $a[0]; + my $dev = $a[2]; + + if ( $dev eq "none" ) { + Log 1, "FRM device is none, commands will be echoed only"; + $main::attr{$name}{dummy} = 1; + return undef; + } + $hash->{DeviceName} = $dev; + my $ret = DevIo_OpenDev($hash, 0, "FRM_DoInit"); + main::readingsSingleUpdate($hash,"state","initialized", 1); + return $ret; +} + +##################################### +sub FRM_Undef($) { + my $hash = @_; + FRM_forall_clients($hash,\&FRM_Client_Unassign,undef); + DevIo_CloseDev($hash); + my $device = $hash->{FirmataDevice}; + if (defined $device) { + if (defined $device->{io}) { + delete $hash->{FirmataDevice}->{io}->{handle} if defined $hash->{FirmataDevice}->{io}->{handle}; + delete $hash->{FirmataDevice}->{io}; + } + delete $device->{protocol} if defined $device->{protocol}; + delete $hash->{FirmataDevice}; + } + return undef; +} + +##################################### +sub FRM_Set($@) { + my ( $hash, @a ) = @_; + my $u1 = "Usage: set reset/reinit\n"; + + return $u1 if ( int(@a) < 2 ); + my $name = $hash->{DeviceName}; + + if ( $a[1] eq 'reset' ) { + DevIo_CloseDev($hash); + my $ret = DevIo_OpenDev($hash, 0, "FRM_DoInit"); + return $ret; + } elsif ( $a[1] eq 'reinit' ) { + FRM_forall_clients($hash,\&FRM_Init_Client,undef); + } else { + return "Unknown argument $a[1], supported arguments are 'reset', 'reinit'"; + } + return undef; +} + +##################################### +sub FRM_Get($@) { + my ( $hash, @a ) = @_; + return "\"get FRM\" needs only one parameter" if ( @a != 2 ); + shift @a; + my $spec = shift @a; + if ( $spec eq "firmware" ) { + if (defined $hash->{FirmataDevice}) { + return $hash->{FirmataDevice}->{metadata}->{firmware}; + } else { + return "not connected to FirmataDevice"; + } + } elsif ( $spec eq "version" ) { + if (defined $hash->{FirmataDevice}) { + return $hash->{FirmataDevice}->{metadata}->{firmware_version}; + } else { + return "not connected to FirmataDevice"; + } + } +} + +##################################### +# called from the global loop, when the select for hash->{FD} reports data +sub FRM_Read($) { + my ( $hash ) = @_; + my $device = $hash->{FirmataDevice} or return; + $device->poll(); +} + +sub FRM_Ready($) { + + my ($hash) = @_; + return DevIo_OpenDev($hash, 1, "FRM_DoInit") if($hash->{READINGS}{state} eq "disconnected"); +} + +sub FRM_Attr(@) { + my ($command,$name,$attribute,$value) = @_; + if ($command eq "set") { + $main::attr{$name}{$attribute}=$value; + if ($attribute eq "sampling-interval" + or $attribute eq "i2c-config" ) { + FRM_apply_attribute($main::defs{$name},$attribute); + } + } +} + +sub FRM_apply_attribute { + my ($hash,$attribute) = @_; + my $firmata = $hash->{FirmataDevice}; + my $name = $hash->{NAME}; + if (defined $firmata) { + if ($attribute eq "sampling-interval") { + $firmata->sampling_interval(main::AttrVal($name,$attribute,"1000")); + } elsif ($attribute eq "i2c-config") { + my $i2cattr = main::AttrVal($name,$attribute,undef); + if (defined $i2cattr) { + my @a = split(" ", $i2cattr); + my $i2cpins = $firmata->{metadata}{i2c_pins}; + if (defined $i2cpins and scalar @$i2cpins) { + foreach my $i2cpin (@$i2cpins) { + $firmata->pin_mode($i2cpin,PIN_I2C); + } + $firmata->i2c_config(@a); + $firmata->observe_i2c(\&FRM_i2c_observer,$hash); + } else { + Log 1,"Error, arduino doesn't support I2C"; + } + } + } + } +} + +sub FRM_DoInit($) { + + my ($hash) = @_; + + my $name = $hash->{NAME}; + $hash->{loglevel} = main::GetLogLevel($name); + + my $firmata_io = Firmata_IO->new($hash); + my $device = Device::Firmata::Platform->attach($firmata_io) or return 1; + + $hash->{FirmataDevice} = $device; + $device->observe_string(\&FRM_string_observer,$hash); + + my $found; # we cannot call $device->probe() here, as it doesn't select bevore read, so it would likely cause IODev to close the connection on the first attempt to read from empty stream + do { + $device->system_reset(); + $device->firmware_version_query(); + for (my $i=0;$i<50;$i++) { + my ($rout, $rin) = ('', ''); + vec($rin, $hash->{FD}, 1) = 1; + my $nfound = select($rout=$rin, undef, undef, 0.1); + my $mfound = vec($rout, $hash->{FD}, 1); + if($mfound) { + $device->poll(); + if ($device->{metadata}{firmware} && $device->{metadata}{firmware_version}){ + $device->{protocol}->{protocol_version} = $device->{metadata}{firmware_version}; + $main::defs{$name}{firmware} = $device->{metadata}{firmware}; + $main::defs{$name}{firmware_version} = $device->{metadata}{firmware_version}; + $device->analog_mapping_query(); + $device->capability_query(); + for (my $j=0;$j<100;$j++) { + my ($rout, $rin) = ('', ''); + vec($rin, $hash->{FD}, 1) = 1; + my $nfound = select($rout=$rin, undef, undef, 0.1); + my $mfound = vec($rout, $hash->{FD}, 1); + if ($mfound) { + $device->poll(); + if (($device->{metadata}{analog_mappings}) and ($device->{metadata}{capabilities})) { + my $inputpins = $device->{metadata}{input_pins}; + $main::defs{$name}{input_pins} = join(",", sort{$a<=>$b}(@$inputpins)); + my $outputpins = $device->{metadata}{output_pins}; + $main::defs{$name}{output_pins} = join(",", sort{$a<=>$b}(@$outputpins)); + my $analogpins = $device->{metadata}{analog_pins}; + $main::defs{$name}{analog_pins} = join(",", sort{$a<=>$b}(@$analogpins)); + my $i2cpins = $device->{metadata}{i2c_pins}; + $main::defs{$name}{i2c_pins} = join(",", sort{$a<=>$b}(@$i2cpins)); + my $onewirepins = $device->{metadata}{onewire_pins}; + $main::defs{$name}{onewire_pins} = join(",", sort{$a<=>$b}(@$onewirepins)); + $found = 1; + last; + } + } else { + select (undef,undef,undef,0.1); + } + } + $found = 1; + last; + } + } else { + select (undef,undef,undef,0.1); + } + } + } while (!$found); + + FRM_apply_attribute($hash,"sampling-interval"); + FRM_apply_attribute($hash,"i2c-config"); + FRM_forall_clients($hash,\&FRM_Init_Client,undef); + + return undef; +} + +sub +FRM_forall_clients($$$) +{ + my ($hash,$fn,$args) = @_; + foreach my $d ( sort keys %main::defs ) { + if ( defined( $main::defs{$d} ) + && defined( $main::defs{$d}{IODev} ) + && $main::defs{$d}{IODev} == $hash ) { + &$fn($main::defs{$d},$args); + } + } + return undef; +} + +sub +FRM_Init_Client($$) { + my ($hash,$args) = @_; + $hash->{loglevel} = main::GetLogLevel($hash->{NAME}); + main::CallFn($hash->{NAME},"InitFn",$hash,$args); +} + +sub +FRM_Init_Pin_Client($$) { + my ($hash,$args) = @_; + my $u = "wrong syntax: define FRM_XXX pin"; + return $u if(int(@$args) < 3); + $hash->{PIN} = @$args[2]; +} + +sub +FRM_Client_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + main::readingsSingleUpdate($hash,"state","defined",0); + + main::AssignIoPort($hash); + FRM_Init_Client($hash,\@a); + + return undef; +} + +sub +FRM_Client_Undef($$) +{ + my ($hash, $name) = @_; +} + +sub +FRM_Client_Unassign($) +{ + my ($dev) = @_; + delete $dev->{IODev} if defined $dev->{IODev}; + main::readingsSingleUpdate($dev,"state","defined",0); +} + +package Firmata_IO { + + sub new { + my ($class,$hash) = @_; + return bless { + hash => $hash, + }, $class; + } + + sub data_write { + my ( $self, $buf ) = @_; + main::Log 5, ">".join(",",map{sprintf"%02x",ord$_}split//,$buf); + main::DevIo_SimpleWrite($self->{hash},$buf); + } + + sub data_read { + my ( $self, $bytes ) = @_; + my $string = main::DevIo_SimpleRead($self->{hash}); + if (defined $string ) { + main::Log 5,"<".join(",",map{sprintf"%02x",ord$_}split//,$string); + } + return $string; + } +} + +sub +FRM_i2c_observer +{ + my ($data,$hash) = @_; + main::Log 5,"onI2CMessage address: '".$data->{address}."', register: '".$data->{register}."' data: '".$data->{data}."'"; + FRM_forall_clients($hash,\&FRM_i2c_update_device,$data); +} + +sub FRM_i2c_update_device +{ + my ($hash,$data) = @_; + if (defined $hash->{"i2c-address"} && $hash->{"i2c-address"}==$data->{address}) { + my $replydata = $data->{data}; + my @values = split(" ",main::ReadingsVal($hash->{NAME},"values","")); + splice(@values,$data->{register},@$replydata, @$replydata); + main::readingsBeginUpdate($hash); + main::readingsBulkUpdate($hash,"state","active",0); + main::readingsBulkUpdate($hash,"values",join (" ",@values),1); + main::readingsEndUpdate($hash,undef); + } +} + +sub FRM_string_observer +{ + my ($string,$hash) = @_; + main::Log 4, "received String_data: ".$string; + main::readingsSingleUpdate($hash,"error",$string,1); +} + +1; + +=pod +=begin html + + +

FRM

+
    + connects fhem to Arduino using + the Firmata protocol. +

    + A single FRM device can serve multiple FRM-clients.

    + Clients of FRM are:

    + FRM_IN for digital input
    + FRM_OUT for digital out
    + FRM_AD for analog input
    + FRM_PWM for analog (pulse_width_modulated) output
    + FRM_I2C to read data from integrated circutes attached + to Arduino supporting the + i2c-protocol.

    + + Each client stands for a Pin of the Arduino configured for a specific use + (digital/analog in/out) or an integrated circuit connected to Arduino by i2c.

    + + Note: this module requires the Device::Firmata module (perl-firmata). + You can download it as a single zip file from github. + Copy 'lib/Device' (with all subdirectories) to e.g. FHEM directory (or other location within perl include path)

    + + Note: this module may require the Device::SerialPort or Win32::SerialPort + module if you attach the device via USB and the OS sets strange default + parameters for serial devices.

    + + + Define +
      + define <name> FRM <device>
      + Specifies the FRM device.
    + +
    +
      + USB-connected devices:
        + <device> specifies the serial port to communicate with the Arduino. + The name of the serial-device depends on your distribution, under + linux the cdc_acm kernel module is responsible, and usually a + /dev/ttyACM0 device will be created. If your distribution does not have a + cdc_acm module, you can force usbserial to handle the Arduino by the + following command:
          modprobe usbserial vendor=0x03eb + product=0x204b
        In this case the device is most probably + /dev/ttyUSB0.

        + + You can also specify a baudrate if the device name contains the @ + character, e.g.: /dev/ttyACM0@38400

        + + If the baudrate is "directio" (e.g.: /dev/ttyACM0@directio), then the + perl module Device::SerialPort is not needed, and fhem opens the device + with simple file io. This might work if the operating system uses sane + defaults for the serial parameters, e.g. some Linux distributions and + OSX.

        + + The Arduino has to run 'StandardFirmata'. You can find StandardFirmata + in the Arduino-IDE under 'Examples->Firmata->StandardFirmata

        + +
      + Network-connected devices:
        + <device> specifies the host:port of the device. E.g. + 192.168.0.244:2323
        + As of now EthernetFirmata is still eperimental. +
      +
      + If the device is called none, then no device will be opened, so you + can experiment without hardware attached.
      +
    + +
    + + Set +
      + N/A
      +

    + + Attributes
    +
      +
    • i2c-config
      + Configure the arduino for ic2 communication. This will enable i2c on the + i2c_pins received by the capability-query issued during initialization of FRM.
      + As of Firmata 2.3 you can set a delay-time (in microseconds) that will be inserted into i2c + protocol when switching from write to read.
      + See: Firmata Protocol details about I2C
      +

    • +
    • sampling-interval
      + Configure the interval Firmata reports data to FRM. Unit is milliseconds.
      + See: Firmata Protocol details about Sampling Interval
      +
    • +
    +
+
+ +=end html +=cut diff --git a/fhem/FHEM/20_FRM_AD.pm b/fhem/FHEM/20_FRM_AD.pm new file mode 100755 index 000000000..80e0f5518 --- /dev/null +++ b/fhem/FHEM/20_FRM_AD.pm @@ -0,0 +1,114 @@ +############################################# +package main; + +use strict; +use warnings; +use Device::Firmata; +use Device::Firmata::Constants qw/ :all /; + +##################################### +sub +FRM_AD_Initialize($) +{ + my ($hash) = @_; + + $hash->{GetFn} = "FRM_AD_Get"; + $hash->{DefFn} = "FRM_Client_Define"; + $hash->{InitFn} = "FRM_AD_Init"; + $hash->{UndefFn} = "FRM_AD_Undef"; + $hash->{AttrFn} = "FRM_Attr"; + + $hash->{AttrList} = "IODev loglevel:0,1,2,3,4,5 $main::readingFnAttributes"; +} + +sub +FRM_AD_Init($$) +{ + my ($hash,$args) = @_; + FRM_Init_Pin_Client($hash,$args); + if (defined $hash->{IODev}) { + my $firmata = $hash->{IODev}->{FirmataDevice}; + if (defined $firmata and defined $hash->{PIN}) { + $firmata->pin_mode($hash->{PIN},PIN_ANALOG); + $firmata->observe_analog($hash->{PIN},\&FRM_AD_observer,$hash); + main::readingsSingleUpdate($hash,"state","initialized",1); + return undef; + } + } + return 1; +} + +sub +FRM_AD_observer +{ + my ($pin,$old,$new,$hash) = @_; + main::Log(6,"onAnalogMessage for pin ".$pin.", old: ".(defined $old ? $old : "--").", new: ".(defined $new ? $new : "--")); + main::readingsSingleUpdate($hash,"state",$new, 1); +} + +sub +FRM_AD_Get($) +{ + my ($hash) = @_; + my $iodev = $hash->{IODev}; + if (defined $iodev and defined $iodev->{FirmataDevice} and defined $iodev->{FD}) { + my $ret = $iodev->{FirmataDevice}->analog_read($hash->{PIN}); + return $ret; + } else { + return $hash->{NAME}." no IODev assigned" if (!defined $iodev); + return $hash->{NAME}.", ".$iodev->{NAME}." is not connected"; + } +} + +sub +FRM_AD_Undef($$) +{ + my ($hash, $name) = @_; +} + +1; + +=pod +=begin html + + +

FRM_AD

+
    + represents a pin of an Arduino running Firmata + configured for analog input.
    + The value read is stored in reading 'state'. Range is from 0 to 1.
    + Requires a defined FRM-device to work.

    + + + Define +
      + define <name> FRM_AD <pin>
      + Specifies the FRM_AD device. +
    + +
    + + Set
    +
      + N/A
      +

    + + Get
    +
      + N/A
      +

    + + Attributes
    + +
+
+ +=end html +=cut diff --git a/fhem/FHEM/20_FRM_I2C.pm b/fhem/FHEM/20_FRM_I2C.pm new file mode 100755 index 000000000..c15581543 --- /dev/null +++ b/fhem/FHEM/20_FRM_I2C.pm @@ -0,0 +1,108 @@ +############################################# +package main; + +use strict; +use warnings; +use Device::Firmata; +use Device::Firmata::Constants qw/ :all /; + +##################################### +sub +FRM_I2C_Initialize($) +{ + my ($hash) = @_; + + $hash->{DefFn} = "FRM_Client_Define"; + $hash->{InitFn} = "FRM_I2C_Init"; + $hash->{UndefFn} = "FRM_I2C_Undef"; + $hash->{AttrFn} = "FRM_I2C_Attr"; + + $hash->{AttrList} = "IODev loglevel:0,1,2,3,4,5 $main::readingFnAttributes"; +} + +sub +FRM_I2C_Init($) +{ + my ($hash,$args) = @_; + my $u = "wrong syntax: define FRM_I2C address register numbytes"; + + return $u if(int(@$args) < 5); + + $hash->{"i2c-address"} = @$args[2]; + $hash->{"i2c-register"} = @$args[3]; + $hash->{"i2c-bytestoread"} = @$args[4]; + + if (defined $hash->{IODev}) { + my $firmata = $hash->{IODev}->{FirmataDevice}; + if (defined $firmata) { + $firmata->i2c_read(@$args[2],@$args[3],@$args[4]); + } + return undef; + } + return 1; +} + +sub FRM_I2C_Attr(@) { + my ($command,$name,$attribute,$value) = @_; + my $hash = $main::defs{$name}; + if ($command eq "set") { + $main::attr{$name}{$attribute}=$value; + } +} + +sub +FRM_I2C_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/20_FRM_IN.pm b/fhem/FHEM/20_FRM_IN.pm new file mode 100755 index 000000000..f824b3ecc --- /dev/null +++ b/fhem/FHEM/20_FRM_IN.pm @@ -0,0 +1,114 @@ +############################################# +package main; + +use strict; +use warnings; +use Device::Firmata; +use Device::Firmata::Constants qw/ :all /; + +##################################### +sub +FRM_IN_Initialize($) +{ + my ($hash) = @_; + + $hash->{GetFn} = "FRM_IN_Get"; + $hash->{DefFn} = "FRM_Client_Define"; + $hash->{InitFn} = "FRM_IN_Init"; + $hash->{UndefFn} = "FRM_IN_Undef"; + $hash->{AttrFn} = "FRM_Attr"; + + $hash->{AttrList} = "IODev loglevel:0,1,2,3,4,5 $main::readingFnAttributes"; +} + +sub +FRM_IN_Init($$) +{ + my ($hash,$args) = @_; + FRM_Init_Pin_Client($hash,$args); + if (defined $hash->{IODev}) { + my $firmata = $hash->{IODev}->{FirmataDevice}; + if (defined $firmata and defined $hash->{PIN}) { + $firmata->pin_mode($hash->{PIN},PIN_INPUT); + $firmata->observe_digital($hash->{PIN},\&FRM_IN_observer,$hash); + main::readingsSingleUpdate($hash,"state","initialized",1); + return undef; + } + } + return 1; +} + +sub +FRM_IN_observer +{ + my ($pin,$old,$new,$hash) = @_; + main::Log(6,"onDigitalMessage for pin ".$pin.", old: ".(defined $old ? $old : "--").", new: ".(defined $new ? $new : "--")); + main::readingsSingleUpdate($hash,"state",$new == PIN_HIGH ? "on" : "off", 1); +} + +sub +FRM_IN_Get($) +{ + my ($hash) = @_; + my $iodev = $hash->{IODev}; + if (defined $iodev and defined $iodev->{FirmataDevice} and defined $iodev->{FD}) { + my $ret = $iodev->{FirmataDevice}->digital_read($hash->{PIN}); + return $ret == PIN_HIGH ? "on" : "off"; + } else { + return $hash->{NAME}." no IODev assigned" if (!defined $iodev); + return $hash->{NAME}.", ".$iodev->{NAME}." is not connected"; + } +} + +sub +FRM_IN_Undef($$) +{ + my ($hash, $name) = @_; +} + +1; + +=pod +=begin html + + +

FRM_IN

+
    + represents a pin of an Arduino running Firmata + configured for digital input.
    + The current state of the arduino-pin is stored in reading 'state'. Values are 'on' and 'off'.
    + Requires a defined FRM-device to work.

    + + + Define +
      + define <name> FRM_IN <pin>
      + Specifies the FRM_IN device. +
    + +
    + + Set
    +
      + N/A
      +
    + + Get
    +
      + N/A
      +

    + + Attributes
    + +
+
+ +=end html +=cut diff --git a/fhem/FHEM/20_FRM_OUT.pm b/fhem/FHEM/20_FRM_OUT.pm new file mode 100755 index 000000000..35f716864 --- /dev/null +++ b/fhem/FHEM/20_FRM_OUT.pm @@ -0,0 +1,111 @@ +############################################# +package main; + +use strict; +use warnings; +use Device::Firmata; +use Device::Firmata::Constants qw/ :all /; + +##################################### +sub +FRM_OUT_Initialize($) +{ + my ($hash) = @_; + + $hash->{SetFn} = "FRM_OUT_Set"; + $hash->{DefFn} = "FRM_Client_Define"; + $hash->{InitFn} = "FRM_OUT_Init"; + $hash->{UndefFn} = "FRM_OUT_Undef"; + $hash->{AttrFn} = "FRM_Attr"; + + $hash->{AttrList} = "IODev loglevel:0,1,2,3,4,5 $main::readingFnAttributes"; +} + +sub +FRM_OUT_Init($$) +{ + my ($hash,$args) = @_; + FRM_Init_Pin_Client($hash,$args); + if (defined $hash->{IODev}) { + my $firmata = $hash->{IODev}->{FirmataDevice}; + if (defined $firmata and defined $hash->{PIN}) { + $firmata->pin_mode($hash->{PIN},PIN_OUTPUT); + main::readingsSingleUpdate($hash,"state","initialized",1); + } + } +} + +sub +FRM_OUT_Set($@) +{ + my ($hash, @a) = @_; + my $value; + if ($a[1] eq "on") { + $value=PIN_HIGH; + } elsif ($a[1] eq "off") { + $value=PIN_LOW; + } else { + return "illegal value '".$a[1]."', allowed are 'on' and 'off'"; + } + my $iodev = $hash->{IODev}; + if (defined $iodev and defined $iodev->{FirmataDevice} and defined $iodev->{FD}) { + $iodev->{FirmataDevice}->digital_write($hash->{PIN},$value); + main::readingsSingleUpdate($hash,"state",$a[1], 1); + } else { + return $hash->{NAME}." no IODev assigned" if (!defined $iodev); + return $hash->{NAME}.", ".$iodev->{NAME}." is not connected"; + } + return undef; +} + +sub +FRM_OUT_Undef($$) +{ + my ($hash, $name) = @_; +} + +1; + +=pod +=begin html + + +

FRM_OUT

+
    + represents a pin of an Arduino running Firmata + configured for digital input.
    + Requires a defined FRM-device to work.

    + + + Define +
      + define <name> FRM_OUT <pin>
      + Specifies the FRM_OUT device. +
    + +
    + + Set
    +
      + set <name> on|off

      +
    + + Get
    +
      + N/A +

    + + Attributes
    + +
+
+ +=end html +=cut diff --git a/fhem/FHEM/20_FRM_PWM.pm b/fhem/FHEM/20_FRM_PWM.pm new file mode 100755 index 000000000..bd29f820a --- /dev/null +++ b/fhem/FHEM/20_FRM_PWM.pm @@ -0,0 +1,105 @@ +############################################# +package main; + +use strict; +use warnings; +use Device::Firmata; +use Device::Firmata::Constants qw/ :all /; + +##################################### +sub +FRM_PWM_Initialize($) +{ + my ($hash) = @_; + + $hash->{SetFn} = "FRM_PWM_Set"; + $hash->{DefFn} = "FRM_Client_Define"; + $hash->{InitFn} = "FRM_PWM_Init"; + $hash->{UndefFn} = "FRM_PWM_Undef"; + $hash->{AttrFn} = "FIR_Attr"; + + $hash->{AttrList} = "IODev loglevel:0,1,2,3,4,5 $main::readingFnAttributes"; +} + +sub +FRM_PWM_Init($$) +{ + my ($hash,$args) = @_; + FRM_Init_Pin_Client($hash,$args); + if (defined $hash->{IODev}) { + my $firmata = $hash->{IODev}->{FirmataDevice}; + if (defined $firmata and defined $hash->{PIN}) { + $firmata->pin_mode($hash->{PIN},PIN_PWM); + main::readingsSingleUpdate($hash,"state","initialized",1); + } + } +} + +sub +FRM_PWM_Set($@) +{ + my ($hash, @a) = @_; + my $value = $a[1]; + my $iodev = $hash->{IODev}; + if (defined $iodev and defined $iodev->{FirmataDevice} and defined $iodev->{FD}) { + $iodev->{FirmataDevice}->analog_write($hash->{PIN},$value); + main::readingsSingleUpdate($hash,"state",$a[1], 1); + } else { + return $hash->{NAME}." no IODev assigned" if (!defined $iodev); + return $hash->{NAME}.", ".$iodev->{NAME}." is not connected"; + } + return undef; +} + +sub +FRM_PWM_Undef($$) +{ + my ($hash, $name) = @_; +} + +1; + +=pod +=begin html + + +

FRM_PWM

+
    + represents a pin of an Arduino running Firmata + configured for analog output.
    + The value set will be output by the specified pin as a pulse-width-modulated signal.
    + Requires a defined FRM-device to work.

    + + + Define +
      + define <name> FRM_PWM <pin>
      + Specifies the FRM_PWM device. +
    + +
    + + Set
    +
      + set <name> <value>

      +
    + + Get
    +
      + N/A +

    + + Attributes
    + +
+
+ +=end html +=cut