######################################################################################## # $Id$ ######################################################################################## =encoding UTF-8 =head1 NAME FHEM module for one Firmata digial output pin =head1 LICENSE AND COPYRIGHT Copyright (C) 2013 ntruchess Copyright (C) 2016 jensb All rights reserved This script is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This script is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this script; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. A copy of the GNU General Public License, Version 2 can also be found at http://www.gnu.org/licenses/old-licenses/gpl-2.0. This copyright notice MUST APPEAR in all copies of the script! =cut package main; use strict; use warnings; #add FHEM/lib to @INC if it's not already included. Should rather be in fhem.pl than here though... BEGIN { if (!grep(/FHEM\/lib$/,@INC)) { foreach my $inc (grep(/FHEM$/,@INC)) { push @INC,$inc."/lib"; }; }; }; use SetExtensions; ##################################### # number of arguments my %sets = ( "on:noArg" => 0, "off:noArg" => 0, ); sub FRM_OUT_Initialize { my ($hash) = @_; $hash->{SetFn} = "FRM_OUT_Set"; $hash->{DefFn} = "FRM_Client_Define"; $hash->{InitFn} = "FRM_OUT_Init"; $hash->{UndefFn} = "FRM_Client_Undef"; $hash->{AttrFn} = "FRM_OUT_Attr"; $hash->{StateFn} = "FRM_OUT_State"; $hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off activeLow:yes,no IODev valueMode:send,receive,bidirectional $main::readingFnAttributes"; main::LoadModule("FRM"); } sub FRM_OUT_Init { my ($hash,$args) = @_; my $name = $hash->{NAME}; if (defined($main::defs{$name}{IODev_ERROR})) { return 'Perl module Device::Firmata not properly installed'; } my $ret = FRM_Init_Pin_Client($hash, $args, Device::Firmata::Constants->PIN_OUTPUT); if (defined($ret)) { readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1); return $ret; } eval { my $firmata = FRM_Client_FirmataDevice($hash); my $pin = $hash->{PIN}; $firmata->observe_digital($pin,\&FRM_OUT_observer,$hash); }; if ($@) { $ret = FRM_Catch($@); readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1); return $ret; } if (!(defined AttrVal($name,"stateFormat",undef))) { $main::attr{$name}{"stateFormat"} = "value"; } my $value = ReadingsVal($name,"value",undef); if (!defined($value)) { readingsSingleUpdate($hash,"value","off",0); } if (AttrVal($name, "restoreOnReconnect", "on") eq "on") { FRM_OUT_Set($hash,$name,$value); } main::readingsSingleUpdate($hash,"state","Initialized",1); return undef; } sub FRM_OUT_observer { my ($pin,$old,$new,$hash) = @_; my $name = $hash->{NAME}; Log3 $name, 5, "$name: observer pin: ".$pin.", old: ".(defined $old? $old : "--").", new: ".(defined $new? $new : "--"); if (AttrVal($hash->{NAME}, "activeLow", "no") eq "yes") { $old = $old == Device::Firmata::Constants->PIN_LOW ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW if (defined $old); $new = $new == Device::Firmata::Constants->PIN_LOW ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW; } my $changed = !defined($old) || ($old != $new); if ($changed && (AttrVal($hash->{NAME}, "valueMode", "send") ne "send")) { main::readingsSingleUpdate($hash, "value", $new == Device::Firmata::Constants->PIN_HIGH? "on" : "off", 1); } } sub FRM_OUT_Set { my ($hash, $name, $cmd, @a) = @_; my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets ); return SetExtensions($hash, join(" ", keys %sets), $name, $cmd, @a) unless @match == 1; return "$cmd requires $sets{$match[0]} arguments" unless (@a == $sets{$match[0]}); if (defined($main::defs{$name}{IODev_ERROR})) { return 'Perl module Device::Firmata not properly installed'; } my $value = Device::Firmata::Constants->PIN_LOW; my $invert = AttrVal($hash->{NAME}, "activeLow", "no"); SETHANDLER: { $cmd eq "on" and do { $value = $invert eq "yes" ? Device::Firmata::Constants->PIN_LOW : Device::Firmata::Constants->PIN_HIGH; last; }; $cmd eq "off" and do { $value = $invert eq "yes" ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW; last; }; }; eval { FRM_Client_FirmataDevice($hash)->digital_write($hash->{PIN},$value); if (AttrVal($hash->{NAME}, "valueMode", "send") ne "receive") { main::readingsSingleUpdate($hash,"value",$cmd, 1); } }; if ($@) { my $ret = FRM_Catch($@); $hash->{STATE} = "set $cmd error: " . $ret; return $hash->{STATE}; } return undef; } sub FRM_OUT_State { my ($hash, $tim, $sname, $sval) = @_; STATEHANDLER: { $sname eq "value" and do { if (AttrVal($hash->{NAME},"restoreOnStartup", "on") eq "on") { FRM_OUT_Set($hash,$hash->{NAME},$sval); } last; } } } sub FRM_OUT_Attr { my ($command,$name,$attribute,$value) = @_; my $hash = $main::defs{$name}; eval { if ($command eq "set") { ARGUMENT_HANDLER: { $attribute eq "IODev" and do { if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $value)) { FRM_Client_AssignIOPort($hash,$value); FRM_Init_Client($hash) if (defined ($hash->{IODev})); } last; }; $attribute eq "activeLow" and do { my $oldval = AttrVal($hash->{NAME},"activeLow", "no"); if ($oldval ne $value) { # toggle output with attribute change $main::attr{$hash->{NAME}}{activeLow} = $value; if ($main::init_done) { my $value = ReadingsVal($name,"value",undef); FRM_OUT_Set($hash,$hash->{NAME},$value); } }; last; }; } } }; if ($@) { my $ret = FRM_Catch($@); $hash->{STATE} = "$command $attribute error: " . $ret; return $hash->{STATE}; } } 1; =pod =head1 CHANGES 2016 jensb o new sub FRM_OUT_observer, modified sub FRM_OUT_Init to receive output state from Firmata device o support attribute "activeLow" 01.01.2018 jensb o create reading "value" in FRM_OUT_Init if missing 02.01.2018 jensb o new attribute "valueMode" to control how "value" reading is updated 14.01.2018 jensb o fix "uninitialised" when calling FRM_OUT_Set without command 23.08.2020 jensb o check for IODev install error in Init o prototypes removed o set argument metadata added o set argument verifier improved 22.10.2020 jensb o annotaded module help of attributes for FHEMWEB =cut =pod =head1 FHEM COMMANDREF METADATA =over =item device =item summary Firmata: digital output =item summary_DE Firmata: digitaler Ausang =back =head1 INSTALLATION AND CONFIGURATION =begin html
define <name> FRM_OUT <pin>
set <name> on|off
setPinModeCallback
": || mode == OUTPUT
" to the if condition for "portConfigInputs[pin / 8] |= (1 << (pin & 7));
" to enable