20_FRM_*.pm: feature update, see forum #114552 msg #1087982

- check for IODev install error in Init, Get, Set, Attr and Undef
- missing get/set argument metadata added
- get/set argument verifier improved
- moved define argument verification and decoding from Init to Define
- error behaviour of Init, Get, Set and Attr standardized
- annotaded module help of attributes for FHEMWEB

git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@23054 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
jensb 2020-10-30 18:16:24 +00:00
parent fb82c7f272
commit 67ec739fc4
10 changed files with 1872 additions and 1167 deletions

View File

@ -1,5 +1,8 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
# Do not insert empty lines here, update check depends on it. # Do not insert empty lines here, update check depends on it.
- feature: 10_FRM: Device::Firmata, receiveTimeout, ... (forum #114552)
- feature: 20_FRM_*: Device::Firmata, ... (forum #114552)
- change: 20_FRM_LCD: removed, use I2C_LCD (forum #114552)
- bugfix: 70_DENON_AVR: fixed multizone bug (thx timmib) - bugfix: 70_DENON_AVR: fixed multizone bug (thx timmib)
- feature: 49_Arlo: Added 2-factor authentication - feature: 49_Arlo: Added 2-factor authentication
- bugfix: 73_AutoShuttersControl: fix IsDay Fn for weekend condition - bugfix: 73_AutoShuttersControl: fix IsDay Fn for weekend condition

View File

@ -1,37 +1,42 @@
######################################################################################## ########################################################################################
#
# $Id$ # $Id$
#
# FHEM module for one Firmata analog input pin
#
########################################################################################
#
# 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.
#
# The GNU General Public License can be found at
# http://www.gnu.org/copyleft/gpl.html.
# A copy is found in the textfile GPL.txt and important notices to the license
# from the author is found in LICENSE.txt distributed with these scripts.
#
# 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.
#
# This copyright notice MUST APPEAR in all copies of the script!
#
######################################################################################## ########################################################################################
=encoding UTF-8
=head1 NAME
FHEM module for one Firmata analog input 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; package main;
use strict; use strict;
@ -39,26 +44,24 @@ use warnings;
#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though... #add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
BEGIN { BEGIN {
if (!grep(/FHEM\/lib$/,@INC)) { if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) { foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib"; push @INC,$inc."/lib";
}; };
}; };
}; };
use Device::Firmata::Constants qw/ :all /;
##################################### #####################################
# get command default return values
my %gets = ( my %gets = (
"reading" => "", "reading" => "",
"state" => "", "state" => "",
"alarm-upper-threshold" => "off", "alarm-upper-threshold" => "off",
"alarm-lower-threshold" => "off", "alarm-lower-threshold" => "off",
); );
sub sub FRM_AD_Initialize
FRM_AD_Initialize($)
{ {
my ($hash) = @_; my ($hash) = @_;
@ -66,83 +69,102 @@ FRM_AD_Initialize($)
$hash->{GetFn} = "FRM_AD_Get"; $hash->{GetFn} = "FRM_AD_Get";
$hash->{DefFn} = "FRM_Client_Define"; $hash->{DefFn} = "FRM_Client_Define";
$hash->{InitFn} = "FRM_AD_Init"; $hash->{InitFn} = "FRM_AD_Init";
$hash->{AttrList} = "IODev upper-threshold lower-threshold $main::readingFnAttributes"; $hash->{AttrList} = "IODev upper-threshold lower-threshold $main::readingFnAttributes";
main::LoadModule("FRM"); main::LoadModule("FRM");
} }
sub sub FRM_AD_Init
FRM_AD_Init($$)
{ {
my ($hash,$args) = @_; my ($hash,$args) = @_;
my $ret = FRM_Init_Pin_Client($hash,$args,PIN_ANALOG); my $name = $hash->{NAME};
return $ret if (defined $ret);
my $firmata = $hash->{IODev}->{FirmataDevice}; if (defined($main::defs{$name}{IODev_ERROR})) {
my $name = $hash->{NAME}; return 'Perl module Device::Firmata not properly installed';
my $resolution = 10; }
if (defined $firmata->{metadata}{analog_resolutions}) {
$resolution = $firmata->{metadata}{analog_resolutions}{$hash->{PIN}} my $ret = FRM_Init_Pin_Client($hash, $args, Device::Firmata::Constants->PIN_ANALOG);
} if (defined($ret)) {
$hash->{resolution} = $resolution; readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
$hash->{".max"} = defined $resolution ? (1<<$resolution)-1 : 1024; return $ret;
eval { }
$firmata->observe_analog($hash->{PIN},\&FRM_AD_observer,$hash);
}; eval {
return FRM_Catch($@) if $@; my $firmata = FRM_Client_FirmataDevice($hash);
if (! (defined AttrVal($name,"stateFormat",undef))) { my $resolution = 10;
$main::attr{$name}{"stateFormat"} = "reading"; if (defined $firmata->{metadata}{analog_resolutions}) {
} $resolution = $firmata->{metadata}{analog_resolutions}{$hash->{PIN}}
if (! (defined AttrVal($name,"event-min-interval",undef))) { }
$main::attr{$name}{"event-min-interval"} = 5; $hash->{resolution} = $resolution;
} $hash->{".max"} = defined $resolution ? (1<<$resolution)-1 : 1024;
main::readingsSingleUpdate($hash,"state","Initialized",1); $firmata->observe_analog($hash->{PIN},\&FRM_AD_observer,$hash);
return undef; };
if ($@) {
$ret = FRM_Catch($@);
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
if (!(defined AttrVal($name,"stateFormat",undef))) {
$main::attr{$name}{"stateFormat"} = "reading";
}
if (!(defined AttrVal($name,"event-min-interval",undef))) {
$main::attr{$name}{"event-min-interval"} = 5;
}
main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
} }
sub sub FRM_AD_observer
FRM_AD_observer
{ {
my ($pin,$old,$new,$hash) = @_; my ($pin,$old,$new,$hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 $name,5,"onAnalogMessage for pin ".$pin.", old: ".(defined $old ? $old : "--").", new: ".(defined $new ? $new : "--"); Log3 $name, 5, "$name: observer pin: ".$pin.", old: ".(defined $old ? $old : "--").", new: ".(defined $new ? $new : "--");
main::readingsBeginUpdate($hash); main::readingsBeginUpdate($hash);
main::readingsBulkUpdate($hash,"reading",$new,1); main::readingsBulkUpdate($hash,"reading",$new,1);
my $upperthresholdalarm = ReadingsVal($name,"alarm-upper-threshold","off"); my $upperthresholdalarm = ReadingsVal($name,"alarm-upper-threshold","off");
if ( $new < AttrVal($name,"upper-threshold",$hash->{".max"}) ) { if ( $new < AttrVal($name,"upper-threshold",$hash->{".max"}) ) {
if ( $upperthresholdalarm eq "on" ) { if ( $upperthresholdalarm eq "on" ) {
main::readingsBulkUpdate($hash,"alarm-upper-threshold","off",1); main::readingsBulkUpdate($hash,"alarm-upper-threshold","off",1);
} }
my $lowerthresholdalarm = ReadingsVal($name,"alarm-lower-threshold","off"); my $lowerthresholdalarm = ReadingsVal($name,"alarm-lower-threshold","off");
if ( $new > AttrVal($name,"lower-threshold",-1) ) { if ( $new > AttrVal($name,"lower-threshold",-1) ) {
if ( $lowerthresholdalarm eq "on" ) { if ( $lowerthresholdalarm eq "on" ) {
main::readingsBulkUpdate($hash,"alarm-lower-threshold","off",1); main::readingsBulkUpdate($hash,"alarm-lower-threshold","off",1);
} }
} else { } else {
if ( $lowerthresholdalarm eq "off" ) { if ( $lowerthresholdalarm eq "off" ) {
main::readingsBulkUpdate($hash,"alarm-lower-threshold","on",1); main::readingsBulkUpdate($hash,"alarm-lower-threshold","on",1);
} }
} }
} else { } else {
if ( $upperthresholdalarm eq "off" ) { if ( $upperthresholdalarm eq "off" ) {
main::readingsBulkUpdate($hash,"alarm-upper-threshold","on",1); main::readingsBulkUpdate($hash,"alarm-upper-threshold","on",1);
} }
}; };
main::readingsEndUpdate($hash,1); main::readingsEndUpdate($hash,1);
} }
sub sub FRM_AD_Get
FRM_AD_Get($)
{ {
my ($hash,@a) = @_; my ($hash, $name, $cmd, @a) = @_;
my $name = shift @a;
my $cmd = shift @a; return "get command missing" if(!defined($cmd));
my $ret; return "unknown get command '$cmd', choose one of " . join(":noArg ", sort keys %gets) . ":noArg" if(!defined($gets{$cmd}));
ARGUMENT_HANDLER: { ARGUMENT_HANDLER: {
$cmd eq "reading" and do { $cmd eq "reading" and do {
eval { my $result = eval {
if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
}
return FRM_Client_FirmataDevice($hash)->analog_read($hash->{PIN}); return FRM_Client_FirmataDevice($hash)->analog_read($hash->{PIN});
}; };
return $@; return FRM_Catch($@) if ($@);
return $result;
}; };
( $cmd eq "alarm-upper-threshold" or $cmd eq "alarm-lower-threshold" or $cmd eq "state" ) and do { ( $cmd eq "alarm-upper-threshold" or $cmd eq "alarm-lower-threshold" or $cmd eq "state" ) and do {
return main::ReadingsVal($name,"count",$gets{$cmd}); return main::ReadingsVal($name,"count",$gets{$cmd});
@ -151,8 +173,8 @@ FRM_AD_Get($)
return undef; return undef;
} }
sub sub FRM_AD_Attr
FRM_AD_Attr($$$$) { {
my ($command,$name,$attribute,$value) = @_; my ($command,$name,$attribute,$value) = @_;
my $hash = $main::defs{$name}; my $hash = $main::defs{$name};
eval { eval {
@ -169,9 +191,9 @@ FRM_AD_Attr($$$$) {
} }
}; };
if ($@) { if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/; my $ret = FRM_Catch($@);
$hash->{STATE} = "error setting $attribute to $value: ".$1; $hash->{STATE} = "$command $attribute error: " . $ret;
return "cannot $command attribute $attribute to $value for $name: ".$1; return $hash->{STATE};
} }
} }
@ -179,46 +201,65 @@ FRM_AD_Attr($$$$) {
=pod =pod
CHANGES =head1 CHANGES
2016 jensb 2016 jensb
o modified sub FRM_AD_Init to catch exceptions and return error message o modified sub FRM_AD_Init to catch exceptions and return error message
19.01.2018 jensb 19.01.2018 jensb
o support analog resolution depending on device capability o support analog resolution depending on device capability
24.08.2020 jensb
o check for IODev install error in Init and Get
o prototypes removed
o set argument verifier added
22.10.2020 jensb
o annotaded module help of attributes for FHEMWEB
=cut =cut
=pod
=head1 FHEM COMMANDREF METADATA
=over
=item device =item device
=item summary Firmata: analog input =item summary Firmata: analog input
=item summary_DE Firmata: analog Eingang
=item summary_DE Firmata: analoger Eingang
=back
=head1 INSTALLATION AND CONFIGURATION
=begin html =begin html
<a name="FRM_AD"></a> <a name="FRM_AD"/>
<h3>FRM_AD</h3> <h3>FRM_AD</h3>
<ul> <ul>
This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a> This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a>
that should be configured as an analog input.<br><br> that should be configured as an analog input.<br><br>
Requires a defined <a href="#FRM">FRM</a> device to work. The pin must be listed in the internal reading "<a href="#FRMinternals">analog_pins</a>"<br> Requires a defined <a href="#FRM">FRM</a> device to work. The pin must be listed in the internal reading "<a href="#FRMinternals">analog_pins</a>"<br>
of the FRM device (after connecting to the Firmata device) to be used as analog input.<br><br> of the FRM device (after connecting to the Firmata device) to be used as analog input.<br><br>
<a name="FRM_ADdefine"></a> <a name="FRM_ADdefine"/>
<b>Define</b> <b>Define</b>
<ul> <ul>
<code>define &lt;name&gt; FRM_AD &lt;pin&gt;</code><br><br> <code>define &lt;name&gt; FRM_AD &lt;pin&gt;</code><br><br>
Defines the FRM_AD device. &lt;pin&gt; is the arduino-pin to use. Defines the FRM_AD device. &lt;pin&gt; is the arduino-pin to use.
</ul><br> </ul><br>
<a name="FRM_ADset"></a> <a name="FRM_ADset"/>
<b>Set</b><br> <b>Set</b><br>
<ul> <ul>
N/A<br> N/A<br>
</ul><br> </ul><br>
<a name="FRM_ADget"></a> <a name="FRM_ADget"/>
<b>Get</b><br> <b>Get</b><br>
<ul> <ul>
<li>reading<br> <li>reading<br>
@ -236,35 +277,52 @@ FRM_AD_Attr($$$$) {
<li>state<br> <li>state<br>
returns the 'state' reading</li> returns the 'state' reading</li>
</ul><br> </ul><br>
<a name="FRM_ADattr"></a> <a name="FRM_ADattr"/>
<b>Attributes</b><br> <b>Attributes</b><br>
<ul> <ul>
<li>upper-threshold<br> <a name="upper-threshold"/>
sets the 'upper-threshold'. Whenever the 'reading' exceeds this value 'alarm-upper-threshold' is set to 'on'<br> <li>upper-threshold<br>
As soon 'reading' falls below the 'upper-threshold' 'alarm-upper-threshold' turns 'off' again<br> sets the 'upper-threshold'. Whenever the 'reading' exceeds this value 'alarm-upper-threshold' is set to 'on'<br>
Defaults to the max pin resolution plus one.</li> As soon 'reading' falls below the 'upper-threshold' 'alarm-upper-threshold' turns 'off' again<br>
<li>lower-threshold<br> Defaults to the max pin resolution plus one.</li>
sets the 'lower-threshold'. Whenever the 'reading' falls below this value 'alarm-lower-threshold' is set to 'on'<br>
As soon 'reading' rises above the 'lower-threshold' 'alarm-lower-threshold' turns 'off' again<br> <a name="lower-threshold"/>
Defaults to -1.</li> <li>lower-threshold<br>
<li><a href="#IODev">IODev</a><br> sets the 'lower-threshold'. Whenever the 'reading' falls below this value 'alarm-lower-threshold' is set to 'on'<br>
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more As soon 'reading' rises above the 'lower-threshold' 'alarm-lower-threshold' turns 'off' again<br>
than one FRM-device defined.) Defaults to -1.</li>
</li>
<li><a href="#eventMap">eventMap</a><br></li> <a name="IODev"/>
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li> <li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
</li>
<li><a href="#attributes">global attributes</a></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
</ul><br> </ul><br>
<a name="FRM_ADnotes"></a> <a name="FRM_ADnotes"/>
<b>Notes</b><br> <b>Notes</b><br>
<ul> <ul>
<li>attribute <i>stateFormat</i><br> <li>attribute <i>stateFormat</i><br>
In most cases it is a good idea to assign "reading" to the attribute <i>stateFormat</i>. This will show the In most cases it is a good idea to assign "reading" to the attribute <i>stateFormat</i>. This will show the
current value of the pin in the web interface. current value of the pin in the web interface.
</li> </li>
</ul> </ul>
</ul><br> </ul><br>
=end html =end html
=begin html_DE
<a name="FRM_AD"/>
<h3>FRM_AD</h3>
<ul>
Die Modulbeschreibung von FRM_AD gibt es nur auf <a href="commandref.html#FRM_AD">Englisch</a>. <br>
</ul> <br>
=end html_DE
=cut =cut

View File

@ -1,37 +1,42 @@
######################################################################################## ########################################################################################
#
# $Id$ # $Id$
#
# FHEM module for one Firmata digial input pin
#
########################################################################################
#
# LICENSE AND COPYRIGHT
#
# Copyright (C) 2013 ntruchess
# Copyright (C) 2018 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.
#
# The GNU General Public License can be found at
# http://www.gnu.org/copyleft/gpl.html.
# A copy is found in the textfile GPL.txt and important notices to the license
# from the author is found in LICENSE.txt distributed with these scripts.
#
# 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.
#
# This copyright notice MUST APPEAR in all copies of the script!
#
######################################################################################## ########################################################################################
=encoding UTF-8
=head1 NAME
FHEM module for one Firmata digial input pin
=head1 LICENSE AND COPYRIGHT
Copyright (C) 2013 ntruchess
Copyright (C) 2018 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; package main;
use strict; use strict;
@ -39,22 +44,22 @@ use warnings;
#add FHEM/lib to @INC if it's not already included. Should rather be in fhem.pl than here though... #add FHEM/lib to @INC if it's not already included. Should rather be in fhem.pl than here though...
BEGIN { BEGIN {
if (!grep(/FHEM\/lib$/,@INC)) { if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) { foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib"; push @INC,$inc."/lib";
}; };
}; };
}; };
use Device::Firmata::Constants qw/ :all /;
##################################### #####################################
# default values for Attr
my %sets = ( my %sets = (
"alarm" => "", "alarm" => "",
"count" => 0, "count" => 0,
); );
# default values for Get
my %gets = ( my %gets = (
"reading" => "", "reading" => "",
"state" => "", "state" => "",
@ -62,8 +67,7 @@ my %gets = (
"alarm" => "off" "alarm" => "off"
); );
sub sub FRM_IN_Initialize
FRM_IN_Initialize($)
{ {
my ($hash) = @_; my ($hash) = @_;
@ -78,8 +82,7 @@ FRM_IN_Initialize($)
main::LoadModule("FRM"); main::LoadModule("FRM");
} }
sub sub FRM_IN_PinModePullupSupported
FRM_IN_PinModePullupSupported($)
{ {
my ($hash) = @_; my ($hash) = @_;
my $iodev = $hash->{IODev}; my $iodev = $hash->{IODev};
@ -88,133 +91,160 @@ FRM_IN_PinModePullupSupported($)
return defined($pullupPins); return defined($pullupPins);
} }
sub sub FRM_IN_Init
FRM_IN_Init($$)
{ {
my ($hash,$args) = @_; my ($hash,$args) = @_;
if (FRM_IN_PinModePullupSupported($hash)) { my $name = $hash->{NAME};
my $pullup = AttrVal($hash->{NAME},"internal-pullup","off");
my $ret = FRM_Init_Pin_Client($hash,$args,defined($pullup) && ($pullup eq "on")? PIN_PULLUP : PIN_INPUT); if (defined($main::defs{$name}{IODev_ERROR})) {
return $ret if (defined $ret); return 'Perl module Device::Firmata not properly installed';
eval { }
my $firmata = FRM_Client_FirmataDevice($hash);
my $pin = $hash->{PIN}; if (FRM_IN_PinModePullupSupported($hash)) {
$firmata->observe_digital($pin,\&FRM_IN_observer,$hash); my $pullup = AttrVal($name, "internal-pullup", "off");
}; my $ret = FRM_Init_Pin_Client($hash,$args,defined($pullup) && ($pullup eq "on")? Device::Firmata::Constants->PIN_PULLUP : Device::Firmata::Constants->PIN_INPUT);
return FRM_Catch($@) if $@; return $ret if (defined $ret);
} else { eval {
my $ret = FRM_Init_Pin_Client($hash,$args,PIN_INPUT); my $firmata = FRM_Client_FirmataDevice($hash);
return $ret if (defined $ret); my $pin = $hash->{PIN};
eval { $firmata->observe_digital($pin,\&FRM_IN_observer,$hash);
my $firmata = FRM_Client_FirmataDevice($hash); };
my $pin = $hash->{PIN}; if ($@) {
if (defined (my $pullup = AttrVal($hash->{NAME},"internal-pullup",undef))) { my $ret = FRM_Catch($@);
$firmata->digital_write($pin,$pullup eq "on" ? 1 : 0); readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
} return $ret;
$firmata->observe_digital($pin,\&FRM_IN_observer,$hash); }
}; } else {
return FRM_Catch($@) if $@; my $ret = FRM_Init_Pin_Client($hash, $args, Device::Firmata::Constants->PIN_INPUT);
} return $ret if (defined $ret);
if (! (defined AttrVal($hash->{NAME},"stateFormat",undef))) { eval {
$main::attr{$hash->{NAME}}{"stateFormat"} = "reading"; my $firmata = FRM_Client_FirmataDevice($hash);
} my $pin = $hash->{PIN};
main::readingsSingleUpdate($hash,"state","Initialized",1); if (defined(my $pullup = AttrVal($name, "internal-pullup", undef))) {
return undef; $firmata->digital_write($pin,$pullup eq "on" ? 1 : 0);
}
$firmata->observe_digital($pin,\&FRM_IN_observer,$hash);
};
if ($@) {
my $ret = FRM_Catch($@);
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
}
if (!(defined AttrVal($name, "stateFormat", undef))) {
$main::attr{$name}{"stateFormat"} = "reading";
}
main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
} }
sub sub FRM_IN_observer
FRM_IN_observer($$$$)
{ {
my ($pin,$last,$new,$hash) = @_; my ($pin,$last,$new,$hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $old = ReadingsVal($name, "reading", undef);
if (defined($old)) { my $old = ReadingsVal($name, "reading", undef);
$old = $old eq "on" ? PIN_HIGH : PIN_LOW; if (defined($old)) {
} $old = $old eq "on" ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW;
if (AttrVal($hash->{NAME},"activeLow","no") eq "yes") { }
$new = $new == PIN_LOW ? PIN_HIGH : PIN_LOW; if (AttrVal($hash->{NAME},"activeLow","no") eq "yes") {
} $new = $new == Device::Firmata::Constants->PIN_LOW ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW;
Log3 $name, 5, "$name observer pin: $pin, old: ".(defined $old ? $old : "--").", new: ".(defined $new ? $new : "--"); }
my $changed = !defined($old) || $old != $new; Log3 $name, 5, "$name: observer pin: $pin, old: ".(defined $old ? $old : "--").", new: ".(defined $new ? $new : "--");
if ($changed) {
main::readingsBeginUpdate($hash); my $changed = !defined($old) || $old != $new;
if (defined (my $mode = main::AttrVal($name,"count-mode",undef))) { if ($changed) {
if (($mode eq "both") main::readingsBeginUpdate($hash);
or (($mode eq "rising") and ($new == PIN_HIGH)) if (defined (my $mode = main::AttrVal($name,"count-mode",undef))) {
or (($mode eq "falling") and ($new == PIN_LOW))) { if (($mode eq "both")
my $count = main::ReadingsVal($name,"count",0); or (($mode eq "rising") and ($new == Device::Firmata::Constants->PIN_HIGH))
$count++; or (($mode eq "falling") and ($new == Device::Firmata::Constants->PIN_LOW))) {
if (defined (my $threshold = main::AttrVal($name,"count-threshold",undef))) { my $count = main::ReadingsVal($name,"count",0);
if ( $count > $threshold ) { $count++;
if (AttrVal($name,"reset-on-threshold-reached","no") eq "yes") { if (defined (my $threshold = main::AttrVal($name,"count-threshold",undef))) {
$count=0; if ( $count > $threshold ) {
main::readingsBulkUpdate($hash,"alarm","on",1); if (AttrVal($name,"reset-on-threshold-reached","no") eq "yes") {
} elsif ( main::ReadingsVal($name,"alarm","off") ne "on" ) { $count=0;
main::readingsBulkUpdate($hash,"alarm","on",1); main::readingsBulkUpdate($hash,"alarm","on",1);
} } elsif ( main::ReadingsVal($name,"alarm","off") ne "on" ) {
} main::readingsBulkUpdate($hash,"alarm","on",1);
} }
main::readingsBulkUpdate($hash,"count",$count,1); }
} }
}; main::readingsBulkUpdate($hash,"count",$count,1);
main::readingsBulkUpdate($hash,"reading",$new == PIN_HIGH ? "on" : "off", 1); }
main::readingsEndUpdate($hash,1); };
} main::readingsBulkUpdate($hash, "reading", $new == Device::Firmata::Constants->PIN_HIGH ? "on" : "off", 1);
main::readingsEndUpdate($hash,1);
}
} }
sub sub FRM_IN_Set
FRM_IN_Set($@)
{ {
my ($hash, @a) = @_; my ($hash, $name, $cmd, @a) = @_;
return "set command missing" if(@a < 2 || !defined($a[1]));
return "unknown set command '$a[1]', choose one of " . join(" ", sort keys %sets) if(!defined($sets{$a[1]})); return "set command missing" if(!defined($cmd));
my $command = $a[1]; return "unknown set command '$cmd', choose one of " . join(" ", sort keys %sets) if(!defined($sets{$cmd}));
my $value = $a[2]; return "$cmd requires 1 argument" unless (@a == 1);
my $value = shift @a;
COMMAND_HANDLER: { COMMAND_HANDLER: {
$command eq "alarm" and do { $cmd eq "alarm" and do {
return undef if (!($value eq "off" or $value eq "on")); return undef if (!($value eq "off" or $value eq "on"));
main::readingsSingleUpdate($hash,"alarm",$value,1); main::readingsSingleUpdate($hash,"alarm",$value,1);
last; last;
}; };
$command eq "count" and do { $cmd eq "count" and do {
main::readingsSingleUpdate($hash,"count",$value,1); main::readingsSingleUpdate($hash,"count",$value,1);
last; last;
}; };
} }
} }
sub sub FRM_IN_Get
FRM_IN_Get($@)
{ {
my ($hash, @a) = @_; my ($hash, $name, $cmd, @a) = @_;
return "get command missing" if(@a < 2 || !defined($a[1]));
return "unknown get command '$a[1]', choose one of " . join(":noArg ", sort keys %gets) . ":noArg" if(!defined($gets{$a[1]})); return "get command missing" if(!defined($cmd));
my $name = shift @a; return "unknown get command '$cmd', choose one of " . join(":noArg ", sort keys %gets) . ":noArg" if(!defined($gets{$cmd}));
my $cmd = shift @a;
ARGUMENT_HANDLER: { ARGUMENT_HANDLER: {
( $cmd eq "reading" ) and do { $cmd eq "reading" and do {
my $last; my $last;
eval { eval {
if (defined($main::defs{$name}{IODev_ERROR})) {
die 'Perl module Device::Firmata not properly installed';
}
$last = FRM_Client_FirmataDevice($hash)->digital_read($hash->{PIN}); $last = FRM_Client_FirmataDevice($hash)->digital_read($hash->{PIN});
if (AttrVal($hash->{NAME},"activeLow","no") eq "yes") { if (AttrVal($hash->{NAME},"activeLow","no") eq "yes") {
$last = $last == PIN_LOW ? PIN_HIGH : PIN_LOW; $last = $last == Device::Firmata::Constants->PIN_LOW ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW;
} }
}; };
return FRM_Catch($@) if $@; if ($@) {
return $last == PIN_HIGH ? "on" : "off"; my $ret = FRM_Catch($@);
$hash->{STATE} = "get $cmd error: " . $ret;
return $hash->{STATE};
}
return $last == Device::Firmata::Constants->PIN_HIGH ? "on" : "off";
}; };
( $cmd eq "count" or $cmd eq "alarm" or $cmd eq "state" ) and do {
($cmd eq "count" or $cmd eq "alarm" or $cmd eq "state") and do {
return main::ReadingsVal($name,$cmd,$gets{$cmd}); return main::ReadingsVal($name,$cmd,$gets{$cmd});
}; };
} }
return undef; return undef;
} }
sub sub FRM_IN_Attr
FRM_IN_Attr($$$$) { {
my ($command,$name,$attribute,$value) = @_; my ($command,$name,$attribute,$value) = @_;
my $hash = $main::defs{$name}; my $hash = $main::defs{$name};
my $pin = $hash->{PIN}; my $pin = $hash->{PIN};
eval { eval {
if ($command eq "set") { if ($command eq "set") {
ARGUMENT_HANDLER: { ARGUMENT_HANDLER: {
@ -225,12 +255,14 @@ FRM_IN_Attr($$$$) {
} }
last; last;
}; };
$attribute eq "count-mode" and do { $attribute eq "count-mode" and do {
if ($value ne "none" and !defined main::ReadingsVal($name,"count",undef)) { if ($value ne "none" and !defined main::ReadingsVal($name,"count",undef)) {
main::readingsSingleUpdate($main::defs{$name},"count",$sets{count},1); main::readingsSingleUpdate($main::defs{$name},"count",$sets{count},1);
} }
last; last;
}; };
$attribute eq "reset-on-threshold-reached" and do { $attribute eq "reset-on-threshold-reached" and do {
if ($value eq "yes" if ($value eq "yes"
and defined (my $threshold = main::AttrVal($name,"count-threshold",undef))) { and defined (my $threshold = main::AttrVal($name,"count-threshold",undef))) {
@ -240,6 +272,7 @@ FRM_IN_Attr($$$$) {
} }
last; last;
}; };
$attribute eq "count-threshold" and do { $attribute eq "count-threshold" and do {
if (main::ReadingsVal($name,"count",0) > $value) { if (main::ReadingsVal($name,"count",0) > $value) {
main::readingsBeginUpdate($hash); main::readingsBeginUpdate($hash);
@ -253,11 +286,15 @@ FRM_IN_Attr($$$$) {
} }
last; last;
}; };
$attribute eq "internal-pullup" and do { $attribute eq "internal-pullup" and do {
if ($main::init_done) { if ($main::init_done) {
if (defined($main::defs{$name}{IODev_ERROR})) {
die 'Perl module Device::Firmata not properly installed';
}
my $firmata = FRM_Client_FirmataDevice($hash); my $firmata = FRM_Client_FirmataDevice($hash);
if (FRM_IN_PinModePullupSupported($hash)) { if (FRM_IN_PinModePullupSupported($hash)) {
$firmata->pin_mode($pin,$value eq "on"? PIN_PULLUP : PIN_INPUT); $firmata->pin_mode($pin, $value eq "on"? Device::Firmata::Constants->PIN_PULLUP : Device::Firmata::Constants->PIN_INPUT);
} else { } else {
$firmata->digital_write($pin,$value eq "on" ? 1 : 0); $firmata->digital_write($pin,$value eq "on" ? 1 : 0);
#ignore any errors here, the attribute-value will be applied next time FRM_IN_init() is called. #ignore any errors here, the attribute-value will be applied next time FRM_IN_init() is called.
@ -265,11 +302,15 @@ FRM_IN_Attr($$$$) {
} }
last; last;
}; };
$attribute eq "activeLow" and do { $attribute eq "activeLow" and do {
my $oldval = AttrVal($hash->{NAME},"activeLow","no"); my $oldval = AttrVal($hash->{NAME},"activeLow","no");
if ($oldval ne $value) { if ($oldval ne $value) {
$main::attr{$hash->{NAME}}{activeLow} = $value; $main::attr{$hash->{NAME}}{activeLow} = $value;
if ($main::init_done) { if ($main::init_done) {
if (defined($main::defs{$name}{IODev_ERROR})) {
die 'Perl module Device::Firmata not properly installed';
}
my $firmata = FRM_Client_FirmataDevice($hash); my $firmata = FRM_Client_FirmataDevice($hash);
FRM_IN_observer($pin,undef,$firmata->digital_read($pin),$hash); FRM_IN_observer($pin,undef,$firmata->digital_read($pin),$hash);
} }
@ -280,16 +321,23 @@ FRM_IN_Attr($$$$) {
} elsif ($command eq "del") { } elsif ($command eq "del") {
ARGUMENT_HANDLER: { ARGUMENT_HANDLER: {
$attribute eq "internal-pullup" and do { $attribute eq "internal-pullup" and do {
if (defined($main::defs{$name}{IODev_ERROR})) {
die 'Perl module Device::Firmata not properly installed';
}
my $firmata = FRM_Client_FirmataDevice($hash); my $firmata = FRM_Client_FirmataDevice($hash);
if (FRM_IN_PinModePullupSupported($hash)) { if (FRM_IN_PinModePullupSupported($hash)) {
$firmata->pin_mode($pin,PIN_INPUT); $firmata->pin_mode($pin, Device::Firmata::Constants->PIN_INPUT);
} else { } else {
$firmata->digital_write($pin,0); $firmata->digital_write($pin,0);
} }
last; last;
}; };
$attribute eq "activeLow" and do { $attribute eq "activeLow" and do {
if (AttrVal($hash->{NAME},"activeLow","no") eq "yes") { if (AttrVal($hash->{NAME},"activeLow","no") eq "yes") {
if (defined($main::defs{$name}{IODev_ERROR})) {
die 'Perl module Device::Firmata not properly installed';
}
delete $main::attr{$hash->{NAME}}{activeLow}; delete $main::attr{$hash->{NAME}}{activeLow};
my $firmata = FRM_Client_FirmataDevice($hash); my $firmata = FRM_Client_FirmataDevice($hash);
FRM_IN_observer($pin,undef,$firmata->digital_read($pin),$hash); FRM_IN_observer($pin,undef,$firmata->digital_read($pin),$hash);
@ -299,9 +347,10 @@ FRM_IN_Attr($$$$) {
} }
} }
}; };
if (my $error = FRM_Catch($@)) { if ($@) {
$hash->{STATE} = "error setting $attribute to $value: ".$error; my $ret = FRM_Catch($@);
return "cannot $command attribute $attribute to $value for $name: ".$error; $hash->{STATE} = "$command $attribute error: " . $ret;
return $hash->{STATE};
} }
} }
@ -309,11 +358,11 @@ FRM_IN_Attr($$$$) {
=pod =pod
CHANGES =head1 CHANGES
15.02.2019 jensb 15.02.2019 jensb
o bugfix: change detection no longer assumes that reading "reading" is defined o bugfix: change detection no longer assumes that reading "reading" is defined
04.11.2018 jensb 04.11.2018 jensb
o bugfix: get alarm/reading/state o bugfix: get alarm/reading/state
o feature: remove unused FHEMWEB input field from all get commands o feature: remove unused FHEMWEB input field from all get commands
@ -324,8 +373,17 @@ FRM_IN_Attr($$$$) {
03.01.2018 jensb 03.01.2018 jensb
o implemented Firmata 2.5 feature PIN_MODE_PULLUP (requires perl-firmata 0.64 or higher) o implemented Firmata 2.5 feature PIN_MODE_PULLUP (requires perl-firmata 0.64 or higher)
24.08.2020 jensb
o check for IODev install error in Init, Get and Attr
o prototypes removed
o set argument verifier improved
19.10.2020 jensb
o annotaded module help of attributes for FHEMWEB
=cut =cut
=pod =pod
=head1 FHEM COMMANDREF METADATA =head1 FHEM COMMANDREF METADATA
@ -344,7 +402,7 @@ FRM_IN_Attr($$$$) {
=begin html =begin html
<a name="FRM_IN"></a> <a name="FRM_IN"/>
<h3>FRM_IN</h3> <h3>FRM_IN</h3>
<ul> <ul>
This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a> This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a>
@ -354,7 +412,7 @@ FRM_IN_Attr($$$$) {
the internal reading <a href="#FRMinternals">"input_pins" or "pullup_pins"</a> the internal reading <a href="#FRMinternals">"input_pins" or "pullup_pins"</a>
of the FRM device (after connecting to the Firmata device) to be used as digital input with or without pullup.<br><br> of the FRM device (after connecting to the Firmata device) to be used as digital input with or without pullup.<br><br>
<a name="FRM_INdefine"></a> <a name="FRM_INdefine"/>
<b>Define</b> <b>Define</b>
<ul> <ul>
<code>define &lt;name&gt; FRM_IN &lt;pin&gt;</code> <br> <code>define &lt;name&gt; FRM_IN &lt;pin&gt;</code> <br>
@ -362,7 +420,7 @@ FRM_IN_Attr($$$$) {
</ul> </ul>
<br> <br>
<a name="FRM_INset"></a> <a name="FRM_INset"/>
<b>Set</b><br> <b>Set</b><br>
<ul> <ul>
<li>alarm on|off<br> <li>alarm on|off<br>
@ -373,47 +431,64 @@ FRM_IN_Attr($$$$) {
The counter is incremented depending on the attribute 'count-mode'.</li> The counter is incremented depending on the attribute 'count-mode'.</li>
</ul><br> </ul><br>
<a name="FRM_INget"></a> <a name="FRM_INget"/>
<b>Get</b> <b>Get</b>
<ul> <ul>
<li>reading<br> <li>reading<br>
returns the logical state of the input pin last received from the Firmata device depending on the attribute 'activeLow'. returns the logical state of the input pin last received from the Firmata device depending on the attribute 'activeLow'.
Values are 'on' and 'off'.<br></li> Values are 'on' and 'off'.<br></li>
<li>count<br> <li>count<br>
returns the current counter value. Contains the number of toggles reported by the Fimata device on this input pin. returns the current counter value. Contains the number of toggles reported by the Fimata device on this input pin.
Depending on the attribute 'count-mode' every rising or falling edge (or both) is counted.</li> Depending on the attribute 'count-mode' every rising or falling edge (or both) is counted.</li>
<li>alarm<br> <li>alarm<br>
returns the 'alarm' reading. Values are 'on' and 'off' (Defaults to 'off'). returns the 'alarm' reading. Values are 'on' and 'off' (Defaults to 'off').
The 'alarm' reading doesn't clear itself, it has to be set to 'off' explicitly.</li> The 'alarm' reading doesn't clear itself, it has to be set to 'off' explicitly.</li>
<li>state<br> <li>state<br>
returns the 'state' reading</li> returns the 'state' reading</li>
</ul><br> </ul><br>
<a name="FRM_INattr"></a> <a name="FRM_INattr"/>
<b>Attributes</b><br> <b>Attributes</b><br>
<ul> <ul>
<li>activeLow yes|no<br> <a name="activeLow"/>
inverts the logical state of the pin reading if set to yes (defaults to 'no').</li> <li>activeLow yes|no<br>
<li>count-mode none|rising|falling|both<br> inverts the logical state of the pin reading if set to yes (defaults to 'no').
Determines whether 'rising' (transitions from 'off' to 'on') of falling (transitions from 'on' to 'off') </li>
edges (or 'both') are counted (defaults to 'none').</li>
<li>count-threshold &lt;number&gt;<br> <a name="count-mode"/>
sets the threshold-value for the counter - if defined whenever 'count' exceeds the 'count-threshold' the 'alarm' reading is <li>count-mode none|rising|falling|both<br>
set to 'on' (defaults to undefined). Use 'set alarm off' to clear the alarm.</li> Determines whether 'rising' (transitions from 'off' to 'on') of falling (transitions from 'on' to 'off')
<li>reset-on-threshold-reached yes|no<br> edges (or 'both') are counted (defaults to 'none').
if set to 'yes' reset the counter to 0 when the threshold is reached (defaults to 'no'). </li>
</li>
<li>internal-pullup on|off<br> <a name="count-threshold"/>
enables/disables the internal pullup resistor of the Firmata pin (defaults to 'off'). Requires hardware and firmware support. <li>count-threshold &lt;number&gt;<br>
</li> sets the threshold-value for the counter - if defined whenever 'count' exceeds the 'count-threshold'
<li><a href="#IODev">IODev</a><br> the 'alarm' reading is set to 'on' (defaults to undefined). Use 'set alarm off' to clear the alarm.
specify which <a href="#FRM">FRM</a> to use. </li>
</li>
<li><a href="#eventMap">eventMap</a><br></li> <a name="reset-on-threshold-reached"/>
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li> <li>reset-on-threshold-reached yes|no<br>
if set to 'yes' reset the counter to 0 when the threshold is reached (defaults to 'no').
</li>
<a name="internal-pullup"/>
<li>internal-pullup on|off<br>
enables/disables the internal pullup resistor of the Firmata pin (defaults to 'off'). Requires hardware
and firmware support.
</li>
<a name="IODev"/>
<li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
</li>
<li><a href="#attributes">global attributes</a></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
</ul><br> </ul><br>
<a name="FRM_INnotes"></a> <a name="FRM_INnotes"/>
<b>Notes</b><br> <b>Notes</b><br>
<ul> <ul>
<li>attribute <i>stateFormat</i><br> <li>attribute <i>stateFormat</i><br>
@ -430,7 +505,7 @@ FRM_IN_Attr($$$$) {
=begin html_DE =begin html_DE
<a name="FRM_IN"></a> <a name="FRM_IN"/>
<h3>FRM_IN</h3> <h3>FRM_IN</h3>
<ul> <ul>
Die Modulbeschreibung von FRM_IN gibt es nur auf <a href="commandref.html#FRM_IN">Englisch</a>. <br> Die Modulbeschreibung von FRM_IN gibt es nur auf <a href="commandref.html#FRM_IN">Englisch</a>. <br>

View File

@ -1,37 +1,42 @@
######################################################################################## ########################################################################################
#
# $Id$ # $Id$
#
# FHEM module for one Firmata digial output pin
#
########################################################################################
#
# 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.
#
# The GNU General Public License can be found at
# http://www.gnu.org/copyleft/gpl.html.
# A copy is found in the textfile GPL.txt and important notices to the license
# from the author is found in LICENSE.txt distributed with these scripts.
#
# 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.
#
# This copyright notice MUST APPEAR in all copies of the script!
#
######################################################################################## ########################################################################################
=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; package main;
use strict; use strict;
@ -39,19 +44,24 @@ use warnings;
#add FHEM/lib to @INC if it's not already included. Should rather be in fhem.pl than here though... #add FHEM/lib to @INC if it's not already included. Should rather be in fhem.pl than here though...
BEGIN { BEGIN {
if (!grep(/FHEM\/lib$/,@INC)) { if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) { foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib"; push @INC,$inc."/lib";
}; };
}; };
}; };
use Device::Firmata::Constants qw/ :all /;
use SetExtensions; use SetExtensions;
##################################### #####################################
sub
FRM_OUT_Initialize($) # number of arguments
my %sets = (
"on:noArg" => 0,
"off:noArg" => 0,
);
sub FRM_OUT_Initialize
{ {
my ($hash) = @_; my ($hash) = @_;
@ -61,96 +71,127 @@ FRM_OUT_Initialize($)
$hash->{UndefFn} = "FRM_Client_Undef"; $hash->{UndefFn} = "FRM_Client_Undef";
$hash->{AttrFn} = "FRM_OUT_Attr"; $hash->{AttrFn} = "FRM_OUT_Attr";
$hash->{StateFn} = "FRM_OUT_State"; $hash->{StateFn} = "FRM_OUT_State";
$hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off activeLow:yes,no IODev valueMode:send,receive,bidirectional $main::readingFnAttributes"; $hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off activeLow:yes,no IODev valueMode:send,receive,bidirectional $main::readingFnAttributes";
main::LoadModule("FRM"); main::LoadModule("FRM");
} }
sub sub FRM_OUT_Init
FRM_OUT_Init($$)
{ {
my ($hash,$args) = @_; my ($hash,$args) = @_;
my $ret = FRM_Init_Pin_Client($hash,$args,PIN_OUTPUT); my $name = $hash->{NAME};
return $ret if (defined $ret);
eval { if (defined($main::defs{$name}{IODev_ERROR})) {
my $firmata = FRM_Client_FirmataDevice($hash); return 'Perl module Device::Firmata not properly installed';
my $pin = $hash->{PIN}; }
$firmata->observe_digital($pin,\&FRM_OUT_observer,$hash);
}; my $ret = FRM_Init_Pin_Client($hash, $args, Device::Firmata::Constants->PIN_OUTPUT);
my $name = $hash->{NAME}; if (defined($ret)) {
if (! (defined AttrVal($name,"stateFormat",undef))) { readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
$main::attr{$name}{"stateFormat"} = "value"; return $ret;
} }
my $value = ReadingsVal($name,"value",undef);
if (!defined($value)) { eval {
readingsSingleUpdate($hash,"value","off",0); my $firmata = FRM_Client_FirmataDevice($hash);
} my $pin = $hash->{PIN};
if (AttrVal($hash->{NAME},"restoreOnReconnect", "on") eq "on") { $firmata->observe_digital($pin,\&FRM_OUT_observer,$hash);
FRM_OUT_Set($hash,$name,$value); };
} if ($@) {
main::readingsSingleUpdate($hash,"state","Initialized",1); $ret = FRM_Catch($@);
return undef; 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 sub FRM_OUT_observer
FRM_OUT_observer($$$$)
{ {
my ($pin,$old,$new,$hash) = @_; my ($pin,$old,$new,$hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 $name, 5, "onDigitalMessage for pin ".$pin.", old: ".(defined $old? $old : "--").", new: ".(defined $new? $new : "--"); Log3 $name, 5, "$name: observer pin: ".$pin.", old: ".(defined $old? $old : "--").", new: ".(defined $new? $new : "--");
if (AttrVal($hash->{NAME}, "activeLow", "no") eq "yes") { if (AttrVal($hash->{NAME}, "activeLow", "no") eq "yes") {
$old = $old == PIN_LOW ? PIN_HIGH : PIN_LOW if (defined $old); $old = $old == Device::Firmata::Constants->PIN_LOW ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW if (defined $old);
$new = $new == PIN_LOW ? PIN_HIGH : PIN_LOW; $new = $new == Device::Firmata::Constants->PIN_LOW ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW;
} }
my $changed = !defined($old) || ($old != $new); my $changed = !defined($old) || ($old != $new);
if ($changed && (AttrVal($hash->{NAME}, "valueMode", "send") ne "send")) { if ($changed && (AttrVal($hash->{NAME}, "valueMode", "send") ne "send")) {
main::readingsSingleUpdate($hash, "value", $new == PIN_HIGH? "on" : "off", 1); main::readingsSingleUpdate($hash, "value", $new == Device::Firmata::Constants->PIN_HIGH? "on" : "off", 1);
} }
} }
sub sub FRM_OUT_Set
FRM_OUT_Set($$$)
{ {
my ($hash, $name, $cmd, @a) = @_; my ($hash, $name, $cmd, @a) = @_;
my $value;
my $invert = AttrVal($hash->{NAME},"activeLow", "no"); my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
if (defined ($cmd)) { return SetExtensions($hash, join(" ", keys %sets), $name, $cmd, @a) unless @match == 1;
if ($cmd eq "on") { return "$cmd requires $sets{$match[0]} arguments" unless (@a == $sets{$match[0]});
$value = $invert eq "yes" ? PIN_LOW : PIN_HIGH;
} elsif ($cmd eq "off") { if (defined($main::defs{$name}{IODev_ERROR})) {
$value = $invert eq "yes" ? PIN_HIGH : PIN_LOW; return 'Perl module Device::Firmata not properly installed';
} else { }
my $list = "on off";
return SetExtensions($hash, $list, $name, $cmd, @a); my $value = Device::Firmata::Constants->PIN_LOW;
} my $invert = AttrVal($hash->{NAME}, "activeLow", "no");
eval { SETHANDLER: {
FRM_Client_FirmataDevice($hash)->digital_write($hash->{PIN},$value); $cmd eq "on" and do {
if (AttrVal($hash->{NAME}, "valueMode", "send") ne "receive") { $value = $invert eq "yes" ? Device::Firmata::Constants->PIN_LOW : Device::Firmata::Constants->PIN_HIGH;
main::readingsSingleUpdate($hash,"value",$cmd, 1); last;
}
}; };
return $@; $cmd eq "off" and do {
} else { $value = $invert eq "yes" ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW;
return "no command specified"; 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_State($$$$) sub FRM_OUT_Attr
{ {
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 ($command,$name,$attribute,$value) = @_;
my $hash = $main::defs{$name}; my $hash = $main::defs{$name};
eval { eval {
@ -163,6 +204,7 @@ FRM_OUT_Attr($$$$) {
} }
last; last;
}; };
$attribute eq "activeLow" and do { $attribute eq "activeLow" and do {
my $oldval = AttrVal($hash->{NAME},"activeLow", "no"); my $oldval = AttrVal($hash->{NAME},"activeLow", "no");
if ($oldval ne $value) { if ($oldval ne $value) {
@ -179,9 +221,9 @@ FRM_OUT_Attr($$$$) {
} }
}; };
if ($@) { if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/; my $ret = FRM_Catch($@);
$hash->{STATE} = "error setting $attribute to $value: ".$1; $hash->{STATE} = "$command $attribute error: " . $ret;
return "cannot $command attribute $attribute to $value for $name: ".$1; return $hash->{STATE};
} }
} }
@ -189,102 +231,148 @@ FRM_OUT_Attr($$$$) {
=pod =pod
CHANGES =head1 CHANGES
2016 jensb 2016 jensb
o new sub FRM_OUT_observer, modified sub FRM_OUT_Init o new sub FRM_OUT_observer, modified sub FRM_OUT_Init
to receive output state from Firmata device to receive output state from Firmata device
o support attribute "activeLow" o support attribute "activeLow"
01.01.2018 jensb 01.01.2018 jensb
o create reading "value" in FRM_OUT_Init if missing o create reading "value" in FRM_OUT_Init if missing
02.01.2018 jensb 02.01.2018 jensb
o new attribute "valueMode" to control how "value" reading is updated o new attribute "valueMode" to control how "value" reading is updated
14.01.2018 jensb 14.01.2018 jensb
o fix "uninitialised" when calling FRM_OUT_Set without command 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 =cut
=pod =pod
=head1 FHEM COMMANDREF METADATA
=over
=item device =item device
=item summary Firmata: digital output =item summary Firmata: digital output
=item summary_DE Firmata: digitaler Ausang =item summary_DE Firmata: digitaler Ausang
=back
=head1 INSTALLATION AND CONFIGURATION
=begin html =begin html
<a name="FRM_OUT"></a> <a name="FRM_OUT"/>
<h3>FRM_OUT</h3> <h3>FRM_OUT</h3>
<ul> <ul>
This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a> This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a>
that should be configured as a digital output.<br><br> that should be configured as a digital output.<br><br>
Requires a defined <a href="#FRM">FRM</a> device to work. The pin must be listed in Requires a defined <a href="#FRM">FRM</a> device to work. The pin must be listed in
the internal reading "<a href="#FRMinternals">output_pins</a>"<br> the internal reading "<a href="#FRMinternals">output_pins</a>"<br>
of the FRM device (after connecting to the Firmata device) to be used as digital output.<br><br> of the FRM device (after connecting to the Firmata device) to be used as digital output.<br><br>
<a name="FRM_OUTdefine"></a> <a name="FRM_OUTdefine"/>
<b>Define</b> <b>Define</b>
<ul> <ul>
<code>define &lt;name&gt; FRM_OUT &lt;pin&gt;</code> <br> <code>define &lt;name&gt; FRM_OUT &lt;pin&gt;</code> <br>
Defines the FRM_OUT device. &lt;pin&gt> is the arduino-pin to use. Defines the FRM_OUT device. &lt;pin&gt> is the arduino-pin to use.
</ul><br> </ul><br>
<a name="FRM_OUTset"></a> <a name="FRM_OUTset"/>
<b>Set</b><br> <b>Set</b><br>
<ul> <ul>
<code>set &lt;name&gt; on|off</code><br><br> <code>set &lt;name&gt; on|off</code><br><br>
</ul> </ul>
<ul> <ul>
<a href="#setExtensions">set extensions</a> are supported<br> <a href="#setExtensions">set extensions</a> are supported<br>
</ul><br> </ul><br>
<a name="FRM_OUTget"></a> <a name="FRM_OUTget"/>
<b>Get</b><br> <b>Get</b><br>
<ul> <ul>
N/A N/A
</ul><br> </ul><br>
<a name="FRM_OUTattr"></a> <a name="FRM_OUTattr"/>
<b>Attributes</b><br> <b>Attributes</b><br>
<ul> <ul>
<li>restoreOnStartup &lt;on|off&gt;, default: on<br> <a name="restoreOnStartup"/>
Set output value in Firmata device on FHEM startup (if device is already connected) and <li>restoreOnStartup &lt;on|off&gt;, default: on<br>
whenever the <em>setstate</em> command is used. Set output value in Firmata device on FHEM startup (if device is already connected) and
</li> whenever the <em>setstate</em> command is used.
<li>restoreOnReconnect &lt;on|off&gt;, default: on<br> </li>
Set output value in Firmata device after IODev is initialized.
</li> <a name="restoreOnReconnect"/>
<li>activeLow &lt;yes|no&gt;, default: no</li> <li>restoreOnReconnect &lt;on|off&gt;, default: on<br>
<li><a href="#IODev">IODev</a><br> Set output value in Firmata device after IODev is initialized.
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more </li>
than one FRM-device defined.)
</li> <a name="activeLow"/>
<li>valueMode &lt;send|receive|bidirectional&gt;, default: send<br> <li>activeLow &lt;yes|no&gt;, default: no</li>
Define how the reading <em>value</em> is updated:<br>
<a name="IODev"/>
<li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
</li>
<a name="valueMode"/>
<li>valueMode &lt;send|receive|bidirectional&gt;, default: send<br>
Define how the reading <em>value</em> is updated:<br>
<ul> <ul>
<li>send - after sending</li> <li>send - after sending</li>
<li>receive - after receiving</li> <li>receive - after receiving</li>
<li>bidirectional - after sending and receiving</li> <li>bidirectional - after sending and receiving</li>
</ul> </ul>
</li> </li>
<li><a href="#eventMap">eventMap</a><br></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li> <li><a href="#attributes">global attributes</a></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
</ul><br> </ul><br>
<a name="FRM_OUTnotes"></a> <a name="FRM_OUTnotes"/>
<b>Notes</b><br> <b>Notes</b><br>
<ul> <ul>
<li>attribute <i>stateFormat</i><br> <li>attribute <i>stateFormat</i><br>
In most cases it is a good idea to assign "value" to the attribute <i>stateFormat</i>. This will show the state In most cases it is a good idea to assign "value" to the attribute <i>stateFormat</i>. This will show the state
of the pin in the web interface. of the pin in the web interface.
</li> </li>
<li>attribute <i>valueMode</i><br> <li>attribute <i>valueMode</i><br>
For modes "receive<" and "bidirectional" to work the default Firmata application code must For modes "receive" and "bidirectional" to work the default Firmata application code must
be modified in function "<code>setPinModeCallback</code>":<br> be modified in function "<code>setPinModeCallback</code>":<br>
add "<ins> || mode == OUTPUT</ins>" to the if condition for "<code>portConfigInputs[pin / 8] |= (1 << (pin & 7));</code>" to enable<br> add "<code> || mode == OUTPUT</code>" to the if condition for "<code>portConfigInputs[pin / 8] |= (1 << (pin & 7));</code>" to enable<br>
reporting the output state (as if the pin were an input). This is of interest if you have custom code in your Firmata device that can change<br> reporting the output state (as if the pin were an input). This is of interest if you have custom code in your Firmata device that my change to pin state.<br>
the state of an output or you want a feedback from the Firmata device after the output state was changed. the state of an output or you want a feedback from the Firmata device after the output state was changed.
</li> </li>
</ul> </ul>
</ul><br> </ul><br>
=end html =end html
=begin html_DE
<a name="FRM_OUT"/>
<h3>FRM_OUT</h3>
<ul>
Die Modulbeschreibung von FRM_OUT gibt es nur auf <a href="commandref.html#FRM_OUT">Englisch</a>. <br>
</ul><br>
=end html_DE
=cut =cut

View File

@ -1,37 +1,42 @@
######################################################################################## ########################################################################################
#
# $Id$ # $Id$
#
# FHEM module for one Firmata PWM output pin
#
########################################################################################
#
# 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.
#
# The GNU General Public License can be found at
# http://www.gnu.org/copyleft/gpl.html.
# A copy is found in the textfile GPL.txt and important notices to the license
# from the author is found in LICENSE.txt distributed with these scripts.
#
# 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.
#
# This copyright notice MUST APPEAR in all copies of the script!
#
######################################################################################## ########################################################################################
=encoding UTF-8
=head1 NAME
FHEM module for one Firmata PWM 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; package main;
use strict; use strict;
@ -39,37 +44,34 @@ use warnings;
#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though... #add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
BEGIN { BEGIN {
if (!grep(/FHEM\/lib$/,@INC)) { if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) { foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib"; push @INC,$inc."/lib";
}; };
}; };
}; };
use Device::Firmata::Constants qw/ :all /;
use SetExtensions qw/ :all /; use SetExtensions qw/ :all /;
##################################### #####################################
my %gets = ( my %gets = (
"dim" => 0, "dim" => "",
"value" => 0, "value" => "",
"devStateIcon" => 0,
); );
# number of arguments
my %sets = ( my %sets = (
"on" => 0, "on:noArg" => 0,
"off" => 0, "off:noArg" => 0,
"toggle" => 0, "toggle:noArg" => 0,
"value" => 1, "value" => 1,
"dim:slider,0,1,100" => 1, "dim:slider,0,1,100" => 1,
"fadeTo" => 2, "dimUp:noArg" => 0,
"dimUp" => 0, "dimDown:noArg" => 0,
"dimDown" => 0,
); );
sub sub FRM_PWM_Initialize
FRM_PWM_Initialize($)
{ {
my ($hash) = @_; my ($hash) = @_;
@ -80,51 +82,73 @@ FRM_PWM_Initialize($)
$hash->{UndefFn} = "FRM_Client_Undef"; $hash->{UndefFn} = "FRM_Client_Undef";
$hash->{AttrFn} = "FRM_PWM_Attr"; $hash->{AttrFn} = "FRM_PWM_Attr";
$hash->{StateFn} = "FRM_PWM_State"; $hash->{StateFn} = "FRM_PWM_State";
$hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off IODev $main::readingFnAttributes"; $hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off IODev $main::readingFnAttributes";
main::LoadModule("FRM"); main::LoadModule("FRM");
} }
sub sub FRM_PWM_Init
FRM_PWM_Init($$)
{ {
my ($hash,$args) = @_; my ($hash,$args) = @_;
my $ret = FRM_Init_Pin_Client($hash,$args,PIN_PWM); my $name = $hash->{NAME};
return $ret if (defined $ret);
my $firmata = $hash->{IODev}->{FirmataDevice};
my $name = $hash->{NAME};
my $resolution = 8;
if (defined $firmata->{metadata}{pwm_resolutions}) {
$resolution = $firmata->{metadata}{pwm_resolutions}{$hash->{PIN}}
}
$hash->{resolution} = $resolution;
$hash->{".max"} = defined $resolution ? (1<<$resolution)-1 : 255;
$hash->{".dim"} = 0;
$hash->{".toggle"} = "off";
if (! (defined AttrVal($name,"stateFormat",undef))) {
$main::attr{$name}{"stateFormat"} = "value";
}
my $value = ReadingsVal($name,"value",undef);
if (defined $value and AttrVal($hash->{NAME},"restoreOnReconnect","on") eq "on") {
FRM_PWM_Set($hash,$name,"value",$value);
}
main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
}
sub if (defined($main::defs{$name}{IODev_ERROR})) {
FRM_PWM_Set($@) return 'Perl module Device::Firmata not properly installed';
{ }
my ($hash, $name, $cmd, @a) = @_;
my $ret = FRM_Init_Pin_Client($hash, $args, Device::Firmata::Constants->PIN_PWM);
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets ); if (defined($ret)) {
#-- check argument readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return SetExtensions($hash, join(" ", keys %sets), $name, $cmd, @a) unless @match == 1; return $ret;
return "$cmd expects $sets{$match[0]} parameters" unless (@a eq $sets{$match[0]}); }
eval {
my $firmata = FRM_Client_FirmataDevice($hash);
my $resolution = 8;
if (defined $firmata->{metadata}{pwm_resolutions}) {
$resolution = $firmata->{metadata}{pwm_resolutions}{$hash->{PIN}}
}
$hash->{resolution} = $resolution;
$hash->{".max"} = defined $resolution ? (1<<$resolution)-1 : 255;
$hash->{".dim"} = 0;
$hash->{".toggle"} = "off";
};
if ($@) {
my $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 and AttrVal($hash->{NAME},"restoreOnReconnect","on") eq "on") {
FRM_PWM_Set($hash,$name,"value",$value);
}
main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
}
sub FRM_PWM_Set
{
my ($hash, $name, $cmd, @a) = @_;
return "set command missing" if(!defined($cmd));
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
return SetExtensions($hash, join(" ", keys %sets), $name, $cmd, @a) unless @match == 1;
return "$cmd requires $sets{$match[0]} argument(s)" unless (@a == $sets{$match[0]});
if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
}
my $value = shift @a;
eval { eval {
SETHANDLER: { SETHANDLER: {
my $value = $a[0] if @a;
$cmd eq "on" and do { $cmd eq "on" and do {
FRM_PWM_writeOut($hash,$hash->{".max"}); FRM_PWM_writeOut($hash,$hash->{".max"});
$hash->{".toggle"} = "on"; $hash->{".toggle"} = "on";
@ -141,7 +165,7 @@ FRM_PWM_Set($@)
$toggle eq "off" and do { $toggle eq "off" and do {
FRM_PWM_writeOut($hash,$hash->{".dim"}); FRM_PWM_writeOut($hash,$hash->{".dim"});
$hash->{".toggle"} = "up"; $hash->{".toggle"} = "up";
last; last;
}; };
$toggle eq "up" and do { $toggle eq "up" and do {
FRM_PWM_writeOut($hash,$hash->{".max"}); FRM_PWM_writeOut($hash,$hash->{".max"});
@ -151,7 +175,7 @@ FRM_PWM_Set($@)
$toggle eq "on" and do { $toggle eq "on" and do {
FRM_PWM_writeOut($hash,$hash->{".dim"}); FRM_PWM_writeOut($hash,$hash->{".dim"});
$hash->{".toggle"} = "down"; $hash->{".toggle"} = "down";
last; last;
}; };
$toggle eq "down" and do { $toggle eq "down" and do {
FRM_PWM_writeOut($hash,0); FRM_PWM_writeOut($hash,0);
@ -197,9 +221,6 @@ FRM_PWM_Set($@)
}; };
last; last;
}; };
$cmd eq "fadeTo" and do {
die "fadeTo not implemented yet";
};
$cmd eq "dimUp" and do { $cmd eq "dimUp" and do {
my $dim = $hash->{".dim"}; my $dim = $hash->{".dim"};
my $max = $hash->{".max"}; my $max = $hash->{".max"};
@ -230,18 +251,19 @@ FRM_PWM_Set($@)
}; };
} }
}; };
if ($@) { if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/; my $ret = FRM_Catch($@);
$hash->{STATE} = "error setting '$cmd': ".(defined $1 ? $1 : $@); $hash->{STATE} = "set $cmd error: " . $ret;
return "error setting '$hash->{NAME} $cmd': ".(defined $1 ? $1 : $@); return $hash->{STATE};
} }
return undef;
return undef;
} }
sub sub FRM_PWM_writeOut
FRM_PWM_writeOut($$)
{ {
my ($hash,$value) = @_; my ($hash,$value) = @_;
FRM_Client_FirmataDevice($hash)->analog_write($hash->{PIN},$value); FRM_Client_FirmataDevice($hash)->analog_write($hash->{PIN},$value);
readingsBeginUpdate($hash); readingsBeginUpdate($hash);
readingsBulkUpdate($hash,"value",$value, 1); readingsBulkUpdate($hash,"value",$value, 1);
@ -249,14 +271,13 @@ FRM_PWM_writeOut($$)
readingsEndUpdate($hash, 1); readingsEndUpdate($hash, 1);
} }
sub sub FRM_PWM_Get
FRM_PWM_Get($@)
{ {
my ($hash, $name, $cmd, @a) = @_; my ($hash, $name, $cmd, @a) = @_;
return "FRM_PWM: Get with unknown argument $cmd, choose one of ".join(" ", sort keys %gets) return "get command missing" if(!defined($cmd));
unless defined($gets{$cmd}); return "unknown get command '$cmd', choose one of " . join(":noArg ", sort keys %gets) . ":noArg" if(!defined($gets{$cmd}));
GETHANDLER: { GETHANDLER: {
$cmd eq 'dim' and do { $cmd eq 'dim' and do {
return ReadingsVal($name,"dim",undef); return ReadingsVal($name,"dim",undef);
@ -264,14 +285,10 @@ FRM_PWM_Get($@)
$cmd eq 'value' and do { $cmd eq 'value' and do {
return ReadingsVal($name,"value",undef); return ReadingsVal($name,"value",undef);
}; };
$cmd eq 'devStateIcon' and do {
return return "not implemented yet";
};
} }
} }
sub sub FRM_PWM_State
FRM_PWM_State($$$$)
{ {
my ($hash, $tim, $sname, $sval) = @_; my ($hash, $tim, $sname, $sval) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@ -291,8 +308,7 @@ FRM_PWM_State($$$$)
return 0; # default processing by fhem.pl return 0; # default processing by fhem.pl
} }
sub sub FRM_PWM_Attr
FRM_PWM_Attr($$$$)
{ {
my ($command,$name,$attribute,$value) = @_; my ($command,$name,$attribute,$value) = @_;
my $hash = $main::defs{$name}; my $hash = $main::defs{$name};
@ -310,9 +326,9 @@ FRM_PWM_Attr($$$$)
} }
}; };
if ($@) { if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/; my $ret = FRM_Catch($@);
$hash->{STATE} = "error setting $attribute to $value: ".$1; $hash->{STATE} = "$command $attribute error: " . $ret;
return "cannot $command attribute $attribute to $value for $name: ".$1; return $hash->{STATE};
} }
} }
@ -320,103 +336,151 @@ FRM_PWM_Attr($$$$)
=pod =pod
CHANGES =head1 CHANGES
2016 jensb 2016 jensb
o modified subs FRM_PWM_Init and FRM_PWM_State to support attribute "restoreOnStartup" o modified subs FRM_PWM_Init and FRM_PWM_State to support attribute "restoreOnStartup"
24.08.2020 jensb
o check for IODev install error in Init and Set
o prototypes removed
o set argument metadata added
o get/set argument verifier improved
o module help updated
22.10.2020 jensb
o annotaded module help of attributes for FHEMWEB
=cut =cut
=pod =pod
=head1 FHEM COMMANDREF METADATA
=over
=item device =item device
=item summary Firmata: PWM output =item summary Firmata: PWM output
=item summary_DE Firmata: PWM Ausgang =item summary_DE Firmata: PWM Ausgang
=back
=head1 INSTALLATION AND CONFIGURATION
=begin html =begin html
<a name="FRM_PWM"></a> <a name="FRM_PWM"/>
<h3>FRM_PWM</h3> <h3>FRM_PWM</h3>
<ul> <ul>
This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a> This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a>
that should be configured as a pulse width modulated output (PWM).<br><br> that should be configured as a pulse width modulated output (PWM).<br><br>
Requires a defined <a href="#FRM">FRM</a> device to work. The pin must be listed in the internal reading "<a href="#FRMinternals">pwm_pins</a>"<br> Requires a defined <a href="#FRM">FRM</a> device to work. The pin must be listed in the internal reading
of the FRM device (after connecting to the Firmata device) to be used as PWM output.<br><br> "<a href="#FRMinternals">pwm_pins</a>" of the FRM device (after connecting to the Firmata device) to be
used as PWM output.<br><br>
<a name="FRM_PWMdefine"></a>
<a name="FRM_PWMdefine"/>
<b>Define</b> <b>Define</b>
<ul> <ul>
<code>define &lt;name&gt; FRM_PWM &lt;pin&gt;</code><br><br> <code>define &lt;name&gt; FRM_PWM &lt;pin&gt;</code><br><br>
Defines the FRM_PWM device. &lt;pin&gt> is the arduino-pin to use. Defines the FRM_PWM device. &lt;pin&gt> is the arduino-pin to use.
</ul><br> </ul><br>
<a name="FRM_PWMset"></a> <a name="FRM_PWMset"/>
<b>Set</b><br> <b>Set</b>
<ul> <ul>
<li><code>set &lt;name&gt; on</code><br> <li><code>set &lt;name&gt; on</code><br>
sets the pulse-width to 100%<br> sets the pulse-width to 100%<br>
</li> </li>
<li> <li>
<code>set &lt;name&gt; off</code><br> <code>set &lt;name&gt; off</code><br>
sets the pulse-width to 0%<br> sets the pulse-width to 0%<br>
</li> </li>
<li> <li>
<a href="#setExtensions">set extensions</a> are supported<br> <a href="#setExtensions">set extensions</a> are supported<br>
</li> </li>
<li> <li>
<code>set &lt;name&gt; toggle</code><br> <code>set &lt;name&gt; toggle</code><br>
toggles the pulse-width in between to the last value set by 'value' or 'dim' and 0 respectivly 100%<br> toggles the pulse-width in between to the last value set by 'value' or 'dim' and 0 respectivly 100%<br>
</li> </li>
<li> <li>
<code>set &lt;name&gt; value &lt;value&gt;</code><br> <code>set &lt;name&gt; value &lt;value&gt;</code><br>
sets the pulse-width to the value specified<br> sets the pulse-width to the value specified<br>
The min value is zero and the max value depends on the Firmata device (see internal reading<br> The min value is zero and the max value depends on the Firmata device (see internal reading<br>
"<a href="#FRMinternals">pwm_resolutions</a>" of the FRM device). For 8 bits resolution the range "<a href="#FRMinternals">pwm_resolutions</a>" of the FRM device). For 8 bits resolution the range
is 0 to 255 (also see <a href="http://arduino.cc/en/Reference/AnalogWrite">analogWrite()</a> for details)<br> is 0 to 255 (also see <a href="http://arduino.cc/en/Reference/AnalogWrite">analogWrite()</a> for details)<br>
</li> </li>
<li> <li>
<code>set &lt;name&gt; dim &lt;value&gt;</code><br> <code>set &lt;name&gt; dim &lt;value&gt;</code><br>
sets the pulse-width to the value specified in percent<br> sets the pulse-width to the value specified in percent<br>
Range is from 0 to 100<br> Range is from 0 to 100<br>
</li> </li>
<li> <li>
<code>set &lt;name&gt; dimUp</code><br> <code>set &lt;name&gt; dimUp</code><br>
increases the pulse-width by 10%<br> increases the pulse-width by 10%<br>
</li> </li>
<li> <li>
<code>set &lt;name&gt; dimDown</code><br> <code>set &lt;name&gt; dimDown</code><br>
decreases the pulse-width by 10%<br> decreases the pulse-width by 10%<br>
</li> </li>
</ul><br> </ul><br>
<a name="FRM_PWMget"></a> <a name="FRM_PWMget"/>
<b>Get</b><br> <b>Get</b><br>
<ul> <ul>
N/A <li>
<code>get &lt;dim&gt;</code><br>
returns current dim setting in percent, see description for set command for more details<br>
</li>
<li>
<code>get &lt;value&gt;</code><br>
returns current dim setting, see description for set command for more details<br>
</li>
</ul><br> </ul><br>
<a name="FRM_PWMattr"></a> <a name="FRM_PWMattr"/>
<b>Attributes</b><br> <b>Attributes</b><br>
<ul> <ul>
<li>restoreOnStartup &lt;on|off&gt;</li> <a name="restoreOnStartup"/>
<li>restoreOnReconnect &lt;on|off&gt;</li> <li>restoreOnStartup &lt;on|off&gt;</li>
<li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more <a name="restoreOnReconnect"/>
than one FRM-device defined.) <li>restoreOnReconnect &lt;on|off&gt;</li>
</li>
<li><a href="#eventMap">eventMap</a><br></li> <a name="IODev"/>
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li> <li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
</li>
<li><a href="#attributes">global attributes</a></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
</ul><br> </ul><br>
<a name="FRM_PWMnotes"></a> <a name="FRM_PWMnotes"/>
<b>Notes</b><br> <b>Notes</b><br>
<ul> <ul>
<li>attribute <i>stateFormat</i><br> <li>attribute <i>stateFormat</i><br>
In most cases it is a good idea to assign "value" to the attribute <i>stateFormat</i>. This will show the In most cases it is a good idea to assign "value" to the attribute <i>stateFormat</i>. This will show the
current value of the pin in the web interface. current value of the pin in the web interface.
</li> </li>
</ul> </ul>
</ul><br> </ul><br>
=end html =end html
=begin html_DE
<a name="FRM_PWM"/>
<h3>FRM_PWM</h3>
<ul>
Die Modulbeschreibung von FRM_PWM gibt es nur auf <a href="commandref.html#FRM_PWM">Englisch</a>. <br>
</ul><br>
=end html_DE
=cut =cut

View File

@ -1,6 +1,42 @@
############################################## ########################################################################################
# $Id$ # $Id$
############################################## ########################################################################################
=encoding UTF-8
=head1 NAME
FHEM module for one Firmata PWM output pin for controlling RGB LEDs
=head1 LICENSE AND COPYRIGHT
Copyright (C) 2013 ntruchess
Copyright (C) 2020 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; package main;
use vars qw{%attr %defs $readingFnAttributes}; use vars qw{%attr %defs $readingFnAttributes};
@ -16,32 +52,29 @@ BEGIN {
}; };
}; };
use Device::Firmata::Constants qw/ :all /;
use Color qw/ :all /; use Color qw/ :all /;
use SetExtensions qw/ :all /; use SetExtensions qw/ :all /;
##################################### #####################################
my %gets = ( my %gets = (
"rgb" => 0, "rgb" => "",
"RGB" => 0, "RGB" => "",
"pct" => 0, "pct" => "",
"devStateIcon" => 0,
); );
# number of arguments
my %sets = ( my %sets = (
"on" => 0, "on:noArg" => 0,
"off" => 0, "off:noArg" => 0,
"toggle" => 0, "toggle:noArg" => 0,
"rgb:colorpicker,RGB" => 1, "rgb:colorpicker,RGB" => 1,
"pct:slider,0,1,100" => 1, "pct:slider,0,1,100" => 1,
"fadeTo" => 2, "dimUp:noArg" => 0,
"dimUp" => 0, "dimDown:noArg" => 0,
"dimDown" => 0,
); );
sub sub FRM_RGB_Initialize
FRM_RGB_Initialize($)
{ {
my ($hash) = @_; my ($hash) = @_;
@ -52,34 +85,40 @@ FRM_RGB_Initialize($)
$hash->{UndefFn} = "FRM_Client_Undef"; $hash->{UndefFn} = "FRM_Client_Undef";
$hash->{AttrFn} = "FRM_RGB_Attr"; $hash->{AttrFn} = "FRM_RGB_Attr";
$hash->{StateFn} = "FRM_RGB_State"; $hash->{StateFn} = "FRM_RGB_State";
$hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off IODev loglevel:0,1,2,3,4,5 $readingFnAttributes"; $hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off IODev loglevel:0,1,2,3,4,5 $readingFnAttributes";
LoadModule("FRM"); LoadModule("FRM");
FHEM_colorpickerInit(); FHEM_colorpickerInit();
} }
sub sub FRM_RGB_Define
FRM_RGB_Define($$)
{ {
my ($hash, $def) = @_; my ($hash, $def) = @_;
$attr{$hash->{NAME}}{webCmd} = "rgb:rgb ff0000:rgb 00ff00:rgb 0000ff:toggle:on:off"; $attr{$hash->{NAME}}{webCmd} = "rgb:rgb ff0000:rgb 00ff00:rgb 0000ff:toggle:on:off";
return FRM_Client_Define($hash,$def); return FRM_Client_Define($hash,$def);
} }
sub sub FRM_RGB_Init
FRM_RGB_Init($$)
{ {
my ($hash,$args) = @_; my ($hash,$args) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $ret = FRM_Init_Pin_Client($hash,$args,PIN_PWM);
return $ret if (defined $ret); 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_PWM);
if (defined($ret)) {
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
my @pins = (); my @pins = ();
eval { eval {
my $firmata = FRM_Client_FirmataDevice($hash); my $firmata = FRM_Client_FirmataDevice($hash);
$hash->{PIN} = ""; $hash->{PIN} = "";
foreach my $pin (@{$args}) { foreach my $pin (@{$args}) {
$firmata->pin_mode($pin,PIN_PWM); $firmata->pin_mode($pin, Device::Firmata::Constants->PIN_PWM);
push @pins,{ push @pins,{
pin => $pin, pin => $pin,
"shift" => defined $firmata->{metadata}{pwm_resolutions} ? $firmata->{metadata}{pwm_resolutions}{$pin}-8 : 0, "shift" => defined $firmata->{metadata}{pwm_resolutions} ? $firmata->{metadata}{pwm_resolutions}{$pin}-8 : 0,
@ -89,11 +128,11 @@ FRM_RGB_Init($$)
$hash->{PINS} = \@pins; $hash->{PINS} = \@pins;
}; };
if ($@) { if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/; $ret = FRM_Catch($@);
$hash->{STATE} = "error initializing: ".$1; readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return "error initializing '".$hash->{NAME}."': ".$1; return $ret;
} }
if (! (defined AttrVal($name,"stateFormat",undef))) { if (!(defined AttrVal($name,"stateFormat",undef))) {
$attr{$name}{"stateFormat"} = "rgb"; $attr{$name}{"stateFormat"} = "rgb";
} }
my $value = ReadingsVal($name,"rgb",undef); my $value = ReadingsVal($name,"rgb",undef);
@ -103,22 +142,26 @@ FRM_RGB_Init($$)
$hash->{toggle} = "off"; $hash->{toggle} = "off";
$hash->{".dim"} = { $hash->{".dim"} = {
bri => 50, bri => 50,
channels => [(255) x @{$hash->{PINS}}], channels => [(255) x @{$hash->{PINS}}],
}; };
readingsSingleUpdate($hash,"state","Initialized",1); readingsSingleUpdate($hash,"state","Initialized",1);
return undef; return undef;
} }
sub sub FRM_RGB_Set
FRM_RGB_Set($@)
{ {
my ($hash, $name, $cmd, @a) = @_; my ($hash, $name, $cmd, @a) = @_;
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
#-- check argument
return SetExtensions($hash, join(" ", keys %sets), $name, $cmd, @a) unless @match == 1;
return "$cmd expects $sets{$match[0]} parameters" unless (@a eq $sets{$match[0]});
return "set command missing" if(!defined($cmd));
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
return SetExtensions($hash, join(" ", keys %sets), $name, $cmd, @a) unless @match == 1;
return "$cmd requires $sets{$match[0]} argument(s)" unless (@a == $sets{$match[0]});
if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
}
my $value = shift @a;
eval { eval {
SETHANDLER: { SETHANDLER: {
$cmd eq "on" and do { $cmd eq "on" and do {
@ -137,7 +180,7 @@ FRM_RGB_Set($@)
$toggle eq "off" and do { $toggle eq "off" and do {
$hash->{toggle} = "up"; $hash->{toggle} = "up";
FRM_RGB_SetChannels($hash,BrightnessToChannels($hash->{".dim"})); FRM_RGB_SetChannels($hash,BrightnessToChannels($hash->{".dim"}));
last; last;
}; };
$toggle eq "up" and do { $toggle eq "up" and do {
FRM_RGB_SetChannels($hash,(0xFF) x @{$hash->{PINS}}); FRM_RGB_SetChannels($hash,(0xFF) x @{$hash->{PINS}});
@ -147,7 +190,7 @@ FRM_RGB_Set($@)
$toggle eq "on" and do { $toggle eq "on" and do {
$hash->{toggle} = "down"; $hash->{toggle} = "down";
FRM_RGB_SetChannels($hash,BrightnessToChannels($hash->{".dim"})); FRM_RGB_SetChannels($hash,BrightnessToChannels($hash->{".dim"}));
last; last;
}; };
$toggle eq "down" and do { $toggle eq "down" and do {
FRM_RGB_SetChannels($hash,(0x0) x @{$hash->{PINS}}); FRM_RGB_SetChannels($hash,(0x0) x @{$hash->{PINS}});
@ -158,18 +201,17 @@ FRM_RGB_Set($@)
last; last;
}; };
$cmd eq "rgb" and do { $cmd eq "rgb" and do {
my $arg = $a[0];
my $numPins = scalar(@{$hash->{PINS}}); my $numPins = scalar(@{$hash->{PINS}});
my $nybles = $numPins << 1; my $nybles = $numPins << 1;
die "$arg is not the right format" unless( $arg =~ /^[\da-f]{$nybles}$/i ); die "$value is not the right format" unless( $value =~ /^[\da-f]{$nybles}$/i );
my @channels = RgbToChannels($arg,$numPins); my @channels = RgbToChannels($value,$numPins);
FRM_RGB_SetChannels($hash,@channels); FRM_RGB_SetChannels($hash,@channels);
RGBHANDLER: { RGBHANDLER: {
$arg =~ /^0{$nybles}$/ and do { $value =~ /^0{$nybles}$/ and do {
$hash->{toggle} = "off"; $hash->{toggle} = "off";
last; last;
}; };
$arg =~ /^f{$nybles}$/i and do { $value =~ /^f{$nybles}$/i and do {
$hash->{toggle} = "on"; $hash->{toggle} = "on";
last; last;
}; };
@ -179,14 +221,14 @@ FRM_RGB_Set($@)
last; last;
}; };
$cmd eq "pct" and do { $cmd eq "pct" and do {
$hash->{".dim"}->{bri} = $a[0]; $hash->{".dim"}->{bri} = $value;
FRM_RGB_SetChannels($hash,BrightnessToChannels($hash->{".dim"})); FRM_RGB_SetChannels($hash,BrightnessToChannels($hash->{".dim"}));
last; last;
}; };
$cmd eq "dimUp" and do { $cmd eq "dimUp" and do {
$hash->{".dim"}->{bri} = $hash->{".dim"}->{bri} > 90 ? 100 : $hash->{".dim"}->{bri}+10; $hash->{".dim"}->{bri} = $hash->{".dim"}->{bri} > 90 ? 100 : $hash->{".dim"}->{bri}+10;
FRM_RGB_SetChannels($hash,BrightnessToChannels($hash->{".dim"})); FRM_RGB_SetChannels($hash,BrightnessToChannels($hash->{".dim"}));
last; last;
}; };
$cmd eq "dimDown" and do { $cmd eq "dimDown" and do {
$hash->{".dim"}->{bri} = $hash->{".dim"}->{bri} < 10 ? 0 : $hash->{".dim"}->{bri}-10; $hash->{".dim"}->{bri} = $hash->{".dim"}->{bri} < 10 ? 0 : $hash->{".dim"}->{bri}-10;
@ -195,22 +237,22 @@ FRM_RGB_Set($@)
}; };
} }
}; };
if ($@) { if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/; my $ret = FRM_Catch($@);
$hash->{STATE} = "error setting '$cmd': ".(defined $1 ? $1 : $@); $hash->{STATE} = "set $cmd error: " . $ret;
return "error setting '$hash->{NAME} $cmd': ".(defined $1 ? $1 : $@); return $hash->{STATE};
} }
return undef; return undef;
} }
sub sub FRM_RGB_Get
FRM_RGB_Get($@)
{ {
my ($hash, $name, $cmd, @a) = @_; my ($hash, $name, $cmd, @a) = @_;
return "FRM_RGB: Get with unknown argument $cmd, choose one of ".join(" ", sort keys %gets) return "get command missing" if(!defined($cmd));
unless defined($gets{$cmd}); return "unknown get command '$cmd', choose one of " . join(":noArg ", sort keys %gets) . ":noArg" if(!defined($gets{$cmd}));
GETHANDLER: { GETHANDLER: {
$cmd eq 'rgb' and do { $cmd eq 'rgb' and do {
return ReadingsVal($name,"rgb",undef); return ReadingsVal($name,"rgb",undef);
@ -220,20 +262,18 @@ FRM_RGB_Get($@)
}; };
$cmd eq 'pct' and do { $cmd eq 'pct' and do {
return $hash->{".dim"}->{bri}; return $hash->{".dim"}->{bri};
return undef;
}; };
} }
} }
sub sub FRM_RGB_SetChannels
FRM_RGB_SetChannels($$)
{ {
my ($hash,@channels) = @_; my ($hash,@channels) = @_;
my $firmata = FRM_Client_FirmataDevice($hash); my $firmata = FRM_Client_FirmataDevice($hash);
my @pins = @{$hash->{PINS}}; my @pins = @{$hash->{PINS}};
my @values = @channels; my @values = @channels;
while(@values) { while(@values) {
my $pin = shift @pins; my $pin = shift @pins;
my $value = shift @values; my $value = shift @values;
@ -250,8 +290,7 @@ FRM_RGB_SetChannels($$)
readingsEndUpdate($hash, 1); readingsEndUpdate($hash, 1);
} }
sub sub FRM_RGB_State
FRM_RGB_State($$$$)
{ {
my ($hash, $tim, $sname, $sval) = @_; my ($hash, $tim, $sname, $sval) = @_;
if ($sname eq "rgb") { if ($sname eq "rgb") {
@ -259,8 +298,7 @@ FRM_RGB_State($$$$)
} }
} }
sub sub FRM_RGB_Attr
FRM_RGB_Attr($$$$)
{ {
my ($command,$name,$attribute,$value) = @_; my ($command,$name,$attribute,$value) = @_;
my $hash = $main::defs{$name}; my $hash = $main::defs{$name};
@ -278,91 +316,141 @@ FRM_RGB_Attr($$$$)
} }
}; };
if ($@) { if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/; my $ret = FRM_Catch($@);
$hash->{STATE} = "error setting $attribute to $value: ".$1; $hash->{STATE} = "$command $attribute error: " . $ret;
return "cannot $command attribute $attribute to $value for $name: ".$1; return $hash->{STATE};
} }
} }
1; 1;
=pod =pod
=head1 CHANGES
30.08.2020 jensb
o check for IODev install error in Init and Set
o prototypes removed
o set argument metadata added
o get/set argument verifier improved
19.10.2020 jensb
o annotaded module help of attributes for FHEMWEB
=cut
=pod
=head1 FHEM COMMANDREF METADATA
=over
=item device
=item summary Firmata: PWM output for RGB-LED
=item summary_DE Firmata: PWM Ausgang für RGB-LED
=back
=head1 INSTALLATION AND CONFIGURATION
=begin html =begin html
<a name="FRM_RGB"></a> <a name="FRM_RGB"/>
<h3>FRM_RGB</h3> <h3>FRM_RGB</h3>
<ul> <ul>
allows to drive LED-controllers and other multichannel-devices that use PWM as input by an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a> allows to drive LED-controllers and other multichannel-devices that use PWM as input by an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a>
<br> <br>
The value set will be output by the specified pins as pulse-width-modulated signals.<br> The value set will be output by the specified pins as pulse-width-modulated signals.<br>
Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br> Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br>
<a name="FRM_RGBdefine"></a> <a name="FRM_RGBdefine"/>
<b>Define</b> <b>Define</b>
<ul> <ul>
<code>define &lt;name&gt; FRM_RGB &lt;pin&gt; &lt;pin&gt; &lt;pin&gt; [pin...]</code> <br> <code>define &lt;name&gt; FRM_RGB &lt;pin&gt; &lt;pin&gt; &lt;pin&gt; [pin...]</code> <br>
Defines the FRM_RGB device. &lt;pin&gt> are the arduino-pin to use.<br> Defines the FRM_RGB device. &lt;pin&gt> are the arduino-pin to use.<br>
For rgb-controlled devices first pin drives red, second pin green and third pin blue. For rgb-controlled devices first pin drives red, second pin green and third pin blue.
</ul> </ul>
<br> <br>
<a name="FRM_RGBset"></a> <a name="FRM_RGBset"/>
<b>Set</b><br> <b>Set</b><br>
<ul> <ul>
<code>set &lt;name&gt; on</code><br> <code>set &lt;name&gt; on</code><br>
sets the pulse-width of all configured pins to 100%</ul><br> sets the pulse-width of all configured pins to 100%</ul><br>
<ul> <ul>
<code>set &lt;name&gt; off</code><br> <code>set &lt;name&gt; off</code><br>
sets the pulse-width of all configured pins to 0%</ul><br> sets the pulse-width of all configured pins to 0%</ul><br>
<ul> <ul>
<a href="#setExtensions">set extensions</a> are supported</ul><br> <a href="#setExtensions">set extensions</a> are supported</ul><br>
<ul> <ul>
<code>set &lt;name&gt; toggle</code><br> <code>set &lt;name&gt; toggle</code><br>
toggles in between the last dimmed value, 0% and 100%. If no dimmed value was set before defaults to pulsewidth 50% on all channels</ul><br> toggles in between the last dimmed value, 0% and 100%. If no dimmed value was set before defaults to pulsewidth 50% on all channels</ul><br>
<ul> <ul>
<code>set &lt;name&gt; rgb &lt;value&gt;</code><br> <code>set &lt;name&gt; rgb &lt;value&gt;</code><br>
sets the pulse-width of all channels at once. Also sets the value toggle can switch to<br> sets the pulse-width of all channels at once. Also sets the value toggle can switch to<br>
Value is encoded as hex-string, 2-digigs per channel (e.g. FFFFFF for reguler rgb)</ul><br> Value is encoded as hex-string, 2-digigs per channel (e.g. FFFFFF for reguler rgb)</ul><br>
<ul> <ul>
<code>set &lt;name&gt; pct &lt;value&gt;</code><br> <code>set &lt;name&gt; pct &lt;value&gt;</code><br>
dims all channels at once while leving the ratio in between the channels unaltered.<br> dims all channels at once while leving the ratio in between the channels unaltered.<br>
Range is 0-100 ('pct' stands for 'percent')</ul><br> Range is 0-100 ('pct' stands for 'percent')</ul><br>
<ul> <ul>
<code>set &lt;name&gt; dimUp</code><br> <code>set &lt;name&gt; dimUp</code><br>
dims up by 10%</ul><br> dims up by 10%</ul><br>
<ul> <ul>
<code>set &lt;name&gt; dimDown</code><br> <code>set &lt;name&gt; dimDown</code><br>
dims down by 10%</ul><br> dims down by 10%
<a name="FRM_RGBget"></a> </ul><br>
<a name="FRM_RGBget"/>
<b>Get</b><br> <b>Get</b><br>
<ul> <ul>
<code>get &lt;name&gt; rgb</code><br> <code>get &lt;name&gt; rgb</code><br>
returns the values set for all channels. Format is hex, 2 nybbles per channel. returns the values set for all channels. Format is hex, 2 nybbles per channel.
</ul><br> </ul><br>
<ul> <ul>
<code>get &lt;name&gt; RGB</code><br> <code>get &lt;name&gt; RGB</code><br>
returns the values set for all channels in normalized format. Format is hex, 2 nybbles per channel. returns the values set for all channels in normalized format. Format is hex, 2 nybbles per channel.
Values are scaled such that the channel with the highest value is set to FF. The real values are calculated Values are scaled such that the channel with the highest value is set to FF. The real values are calculated
by multipying each byte with the value of 'pct'. by multipying each byte with the value of 'pct'.
</ul><br> </ul><br>
<ul> <ul>
<code>get &lt;name&gt; pct</code><br> <code>get &lt;name&gt; pct</code><br>
returns the value of the channel with the highest value scaled to the range of 0-100 (percent). returns the value of the channel with the highest value scaled to the range of 0-100 (percent).
</ul><br> </ul><br>
<a name="FRM_RGBattr"></a>
<a name="FRM_RGBattr"/>
<b>Attributes</b><br> <b>Attributes</b><br>
<ul> <ul>
<li>restoreOnStartup &lt;on|off&gt;</li> <a name="restoreOnStartup"/>
<li>restoreOnReconnect &lt;on|off&gt;</li> <li>restoreOnStartup &lt;on|off&gt;</li>
<li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more <a name="restoreOnReconnect"/>
than one FRM-device defined.) <li>restoreOnReconnect &lt;on|off&gt;</li>
</li>
<li><a href="#eventMap">eventMap</a><br></li> <a name="IODev"/>
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li> <li><a href="#IODev">IODev</a><br>
</ul> Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
</li>
<li><a href="#attributes">global attributes</a></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
</ul> </ul>
<br> </ul><br>
=end html =end html
=begin html_DE
<a name="FRM_RGB"/>
<h3>FRM_RGB</h3>
<ul>
Die Modulbeschreibung von FRM_RGB gibt es nur auf <a href="commandref.html#FRM_RGB">Englisch</a>. <br>
</ul> <br>
=end html_DE
=cut =cut

View File

@ -1,6 +1,42 @@
############################################## ########################################################################################
# $Id$ # $Id$
############################################## ########################################################################################
=encoding UTF-8
=head1 NAME
FHEM module for two Firmata rotary encoder input pins
=head1 LICENSE AND COPYRIGHT
Copyright (C) 2013 ntruchess
Copyright (C) 2020 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; package main;
use strict; use strict;
@ -8,37 +44,35 @@ use warnings;
#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though... #add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
BEGIN { BEGIN {
if (!grep(/FHEM\/lib$/,@INC)) { if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) { foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib"; push @INC,$inc."/lib";
}; };
}; };
}; };
use Device::Firmata::Constants qw/ :all /;
##################################### #####################################
# number of arguments
my %sets = ( my %sets = (
"reset" => "noArg", "reset:noArg" => 0,
"offset"=> "", "offset" => 1,
); );
my %gets = ( my %gets = (
"position" => "noArg", "position" => "",
"offset" => "noArg", "offset" => "",
"value" => "noArg", "value" => "",
); );
sub sub FRM_ROTENC_Initialize
FRM_ROTENC_Initialize($)
{ {
my ($hash) = @_; my ($hash) = @_;
$hash->{SetFn} = "FRM_ROTENC_Set"; $hash->{SetFn} = "FRM_ROTENC_Set";
$hash->{GetFn} = "FRM_ROTENC_Get"; $hash->{GetFn} = "FRM_ROTENC_Get";
$hash->{AttrFn} = "FRM_ROTENC_Attr"; $hash->{AttrFn} = "FRM_ROTENC_Attr";
$hash->{DefFn} = "FRM_Client_Define"; $hash->{DefFn} = "FRM_ROTENC_Define";
$hash->{InitFn} = "FRM_ROTENC_Init"; $hash->{InitFn} = "FRM_ROTENC_Init";
$hash->{UndefFn} = "FRM_ROTENC_Undef"; $hash->{UndefFn} = "FRM_ROTENC_Undef";
$hash->{StateFn} = "FRM_ROTENC_State"; $hash->{StateFn} = "FRM_ROTENC_State";
@ -47,73 +81,87 @@ FRM_ROTENC_Initialize($)
main::LoadModule("FRM"); main::LoadModule("FRM");
} }
sub sub FRM_ROTENC_Define
FRM_ROTENC_Init($$)
{ {
my ($hash,$args) = @_; my ($hash, $def) = @_;
my $u = "wrong syntax: define <name> FRM_ROTENC pinA pinB [id]"; # verify define arguments
return $u unless defined $args and int(@$args) > 1; my $usage = "usage: define <name> FRM_ROTENC pinA pinB [id]";
my $pinA = @$args[0];
my $pinB = @$args[1];
my $encoder = defined @$args[2] ? @$args[2] : 0;
my $name = $hash->{NAME};
$hash->{PINA} = $pinA;
$hash->{PINB} = $pinB;
$hash->{ENCODERNUM} = $encoder;
eval {
FRM_Client_AssignIOPort($hash);
my $firmata = FRM_Client_FirmataDevice($hash);
$firmata->encoder_attach($encoder,$pinA,$pinB);
$firmata->observe_encoder($encoder, \&FRM_ROTENC_observer, $hash );
};
if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/;
$hash->{STATE} = "error initializing: ".$1;
return "error initializing '$name': $1";
}
if (! (defined AttrVal($name,"stateFormat",undef))) { my @a = split("[ \t]+", $def);
$main::attr{$name}{"stateFormat"} = "position"; return $usage if (scalar(@a) < 4);
} my $args = [@a[2..scalar(@a)-1]];
$hash->{PINA} = @$args[0];
$hash->{PINB} = @$args[1];
$hash->{ENCODERNUM} = defined @$args[2] ? @$args[2] : 0;
my $ret = FRM_Client_Define($hash, $def);
if ($ret) {
return $ret;
}
return undef;
}
sub FRM_ROTENC_Init
{
my ($hash,$args) = @_;
my $name = $hash->{NAME};
if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
}
eval {
FRM_Client_AssignIOPort($hash);
my $firmata = FRM_Client_FirmataDevice($hash);
$firmata->encoder_attach($hash->{ENCODERNUM}, $hash->{PINA}, $hash->{PINB});
$firmata->observe_encoder($hash->{ENCODERNUM}, \&FRM_ROTENC_observer, $hash );
};
if ($@) {
my $ret = FRM_Catch($@);
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
if (! (defined AttrVal($name,"stateFormat",undef))) {
$main::attr{$name}{"stateFormat"} = "position";
}
$hash->{offset} = ReadingsVal($name,"position",0); $hash->{offset} = ReadingsVal($name,"position",0);
main::readingsSingleUpdate($hash,"state","Initialized",1); main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
return undef;
} }
sub sub FRM_ROTENC_observer
FRM_ROTENC_observer
{ {
my ( $encoder, $value, $hash ) = @_; my ($encoder, $value, $hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 ($name,5,"onEncoderMessage for pins ".$hash->{PINA}.",".$hash->{PINB}." encoder: ".$encoder." position: ".$value."\n"); Log3 ($name, 5, "$name: observer pins: ".$hash->{PINA}.", ".$hash->{PINB}." encoder: ".$encoder." position: ".$value."\n");
main::readingsBeginUpdate($hash); main::readingsBeginUpdate($hash);
main::readingsBulkUpdate($hash,"position",$value+$hash->{offset}, 1); main::readingsBulkUpdate($hash,"position",$value+$hash->{offset}, 1);
main::readingsBulkUpdate($hash,"value",$value, 1); main::readingsBulkUpdate($hash,"value",$value, 1);
main::readingsEndUpdate($hash,1); main::readingsEndUpdate($hash,1);
} }
sub sub FRM_ROTENC_Set
FRM_ROTENC_Set
{ {
my ($hash, @a) = @_; my ($hash, $name, $cmd, @a) = @_;
return "Need at least one parameters" if(@a < 2);
my $command = $a[1]; return "set command missing" if(!defined($cmd));
my $value = $a[2]; my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
if(!defined($sets{$command})) { return "unknown set command '$cmd', choose one of " . join(" ", sort keys %sets) if ($cmd eq '?' || @match == 0);
my @commands = (); return "$cmd requires $sets{$match[0]} argument(s)" unless (@a == $sets{$match[0]});
foreach my $key (sort keys %sets) {
push @commands, $sets{$key} ? $key.":".join(",",$sets{$key}) : $key; my $value = shift @a;
} SETHANDLER: {
return "Unknown argument $a[1], choose one of " . join(" ", @commands); $cmd eq "reset" and do {
} if (defined($main::defs{$name}{IODev_ERROR})) {
COMMAND_HANDLER: { return 'Perl module Device::Firmata not properly installed';
$command eq "reset" and do { }
eval { eval {
FRM_Client_FirmataDevice($hash)->encoder_reset_position($hash->{ENCODERNUM}); FRM_Client_FirmataDevice($hash)->encoder_reset_position($hash->{ENCODERNUM});
}; };
@ -123,46 +171,37 @@ FRM_ROTENC_Set
main::readingsEndUpdate($hash,1); main::readingsEndUpdate($hash,1);
last; last;
}; };
$command eq "offset" and do { $cmd eq "offset" and do {
$hash->{offset} = $value; $hash->{offset} = $value;
readingsSingleUpdate($hash,"position",ReadingsVal($hash->{NAME},"value",0)+$value,1); readingsSingleUpdate($hash,"position",ReadingsVal($name,"value",0)+$value,1);
last; last;
}; };
} }
} }
sub sub FRM_ROTENC_Get
FRM_ROTENC_Get($)
{ {
my ($hash, @a) = @_; my ($hash, $name, $cmd, @a) = @_;
return "Need at least one parameters" if(@a < 2);
my $command = $a[1]; return "get command missing" if(!defined($cmd));
my $value = $a[2]; return "unknown get command '$cmd', choose one of " . join(":noArg ", sort keys %gets) . ":noArg" if(!defined($gets{$cmd}));
if(!defined($gets{$command})) {
my @commands = (); GETHANDLER: {
foreach my $key (sort keys %gets) {
push @commands, $gets{$key} ? $key.":".join(",",$gets{$key}) : $key;
}
return "Unknown argument $a[1], choose one of " . join(" ", @commands);
}
my $name = shift @a;
my $cmd = shift @a;
ARGUMENT_HANDLER: {
$cmd eq "position" and do { $cmd eq "position" and do {
return ReadingsVal($hash->{NAME},"position","0"); return ReadingsVal($name,"position","0");
}; };
$cmd eq "offset" and do { $cmd eq "offset" and do {
return $hash->{offset}; return $hash->{offset};
}; };
$cmd eq "value" and do { $cmd eq "value" and do {
return ReadingsVal($hash->{NAME},"value","0"); return ReadingsVal($name,"value","0");
}; };
} }
return undef; return undef;
} }
sub sub FRM_ROTENC_Attr
FRM_ROTENC_Attr($$$$) { {
my ($command,$name,$attribute,$value) = @_; my ($command,$name,$attribute,$value) = @_;
my $hash = $main::defs{$name}; my $hash = $main::defs{$name};
eval { eval {
@ -179,38 +218,32 @@ FRM_ROTENC_Attr($$$$) {
} }
}; };
if ($@) { if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/; my $ret = FRM_Catch($@);
$hash->{STATE} = "error setting $attribute to $value: ".$1; $hash->{STATE} = "$command $attribute error: " . $ret;
return "cannot $command attribute $attribute to $value for $name: ".$1; return $hash->{STATE};
} }
} }
sub sub FRM_ROTENC_Undef
FRM_ROTENC_Undef($$)
{ {
my ($hash, $name) = @_; my ($hash, $name) = @_;
my $pinA = $hash->{PINA}; my $pinA = $hash->{PINA};
my $pinB = $hash->{PINB}; my $pinB = $hash->{PINB};
eval { eval {
my $firmata = FRM_Client_FirmataDevice($hash); my $firmata = FRM_Client_FirmataDevice($hash);
$firmata->encoder_detach($hash->{ENCODERNUM}); $firmata->encoder_detach($hash->{ENCODERNUM});
$firmata->pin_mode($pinA,PIN_ANALOG);
$firmata->pin_mode($pinB,PIN_ANALOG);
}; };
if ($@) {
eval { $hash->{PIN} = $hash->{PINA};
my $firmata = FRM_Client_FirmataDevice($hash); FRM_Client_Undef($hash, $name);
$firmata->pin_mode($pinA,PIN_INPUT); $hash->{PIN} = $hash->{PINB};
$firmata->digital_write($pinA,0); FRM_Client_Undef($hash, $name);
$firmata->pin_mode($pinB,PIN_INPUT);
$firmata->digital_write($pinB,0);
};
}
return undef; return undef;
} }
sub sub FRM_ROTENC_State
FRM_ROTENC_State($$$$)
{ {
my ($hash, $tim, $sname, $sval) = @_; my ($hash, $tim, $sname, $sval) = @_;
if ($sname eq "position") { if ($sname eq "position") {
@ -222,30 +255,64 @@ FRM_ROTENC_State($$$$)
1; 1;
=pod =pod
=head1 CHANGES
05.09.2020 jensb
o check for IODev install error in Init, Set and Undef
o prototypes removed
o set argument verifier improved
o moved define argument verification and decoding from Init to Define
19.10.2020 jensb
o annotaded module help of attributes for FHEMWEB
=cut
=pod
=head1 FHEM COMMANDREF METADATA
=over
=item device
=item summary Firmata: rotary encoder input
=item summary_DE Firmata: Drehgeber Eingang
=back
=head1 INSTALLATION AND CONFIGURATION
=begin html =begin html
<a name="FRM_ROTENC"></a> <a name="FRM_ROTENC"/>
<h3>FRM_ROTENC</h3> <h3>FRM_ROTENC</h3>
<ul> <ul>
represents a rotary-encoder attached to two pins of an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a><br> represents a rotary-encoder attached to two pins of an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a><br>
Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br> Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br>
<a name="FRM_ROTENCdefine"></a> <a name="FRM_ROTENCdefine"/>
<b>Define</b> <b>Define</b>
<ul> <ul>
<code>define &lt;name&gt; FRM_ROTENC &lt;pinA&gt; &lt;pinB&gt; [id]</code> <br> <code>define &lt;name&gt; FRM_ROTENC &lt;pinA&gt; &lt;pinB&gt; [id]</code> <br>
Defines the FRM_ROTENC device. &lt;pinA&gt> and &lt;pinA&gt> are the arduino-pins to use.<br> Defines the FRM_ROTENC device. &lt;pinA&gt> and &lt;pinA&gt> are the arduino-pins to use.<br>
[id] is the instance-id of the encoder. Must be a unique number per FRM-device (rages from 0-4 depending on Firmata being used, optional if a single encoder is attached to the arduino).<br> [id] is the instance-id of the encoder. Must be a unique number per FRM-device (rages from 0-4 depending on Firmata being used, optional if a single encoder is attached to the arduino).<br>
</ul> </ul>
<br> <br>
<a name="FRM_ROTENCset"></a> <a name="FRM_ROTENCset"/>
<b>Set</b><br> <b>Set</b><br>
<ul>
<li>reset<br> <li>reset<br>
resets to value of 'position' to 0<br></li> resets to value of 'position' to 0<br></li>
<li>offset &lt;value&gt;<br> <li>offset &lt;value&gt;<br>
set offset value of 'position'<br></li> set offset value of 'position'<br></li>
<a name="FRM_ROTENCget"></a> </ul><br>
<a name="FRM_ROTENCget"/>
<b>Get</b> <b>Get</b>
<ul> <ul>
<li>position<br> <li>position<br>
@ -258,18 +325,31 @@ FRM_ROTENC_State($$$$)
returns the raw position value as it's reported by the rotary-encoder attached to pinA and pinB of the arduino<br> returns the raw position value as it's reported by the rotary-encoder attached to pinA and pinB of the arduino<br>
this value is reset to 0 whenever Arduino restarts or Firmata is reinitialized<br></li> this value is reset to 0 whenever Arduino restarts or Firmata is reinitialized<br></li>
</ul><br> </ul><br>
<a name="FRM_ROTENCattr"></a>
<a name="FRM_ROTENCattr"/>
<b>Attributes</b><br> <b>Attributes</b><br>
<ul> <ul>
<li><a href="#IODev">IODev</a><br> <a name="IODev"/>
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more <li><a href="#IODev">IODev</a><br>
than one FRM-device defined.) Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
</li> </li>
<li><a href="#eventMap">eventMap</a><br></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li> <li><a href="#attributes">global attributes</a></li>
</ul>
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
</ul> </ul>
<br> </ul><br>
=end html =end html
=begin html_DE
<a name="FRM_ROTENC"/>
<h3>FRM_ROTENC</h3>
<ul>
Die Modulbeschreibung von FRM_ROTENC gibt es nur auf <a href="commandref.html#FRM_ROTENC">Englisch</a>. <br>
</ul><br>
=end html_DE
=cut =cut

View File

@ -1,6 +1,42 @@
############################################## ########################################################################################
# $Id$ # $Id$
############################################## ########################################################################################
=encoding UTF-8
=head1 NAME
FHEM module for one Firmata PMW controlled servo output
=head1 LICENSE AND COPYRIGHT
Copyright (C) 2013 ntruchess
Copyright (C) 2020 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; package main;
use strict; use strict;
@ -8,23 +44,21 @@ use warnings;
#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though... #add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
BEGIN { BEGIN {
if (!grep(/FHEM\/lib$/,@INC)) { if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) { foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib"; push @INC,$inc."/lib";
}; };
}; };
}; };
use Device::Firmata::Constants qw/ :all /;
##################################### #####################################
# number of arguments
my %sets = ( my %sets = (
"angle" => "", "angle" => 1,
); );
sub sub FRM_SERVO_Initialize
FRM_SERVO_Initialize($)
{ {
my ($hash) = @_; my ($hash) = @_;
@ -33,29 +67,44 @@ FRM_SERVO_Initialize($)
$hash->{InitFn} = "FRM_SERVO_Init"; $hash->{InitFn} = "FRM_SERVO_Init";
$hash->{UndefFn} = "FRM_Client_Undef"; $hash->{UndefFn} = "FRM_Client_Undef";
$hash->{AttrFn} = "FRM_SERVO_Attr"; $hash->{AttrFn} = "FRM_SERVO_Attr";
$hash->{AttrList} = "min-pulse max-pulse IODev $main::readingFnAttributes"; $hash->{AttrList} = "min-pulse max-pulse IODev $main::readingFnAttributes";
main::LoadModule("FRM"); main::LoadModule("FRM");
} }
sub sub FRM_SERVO_Init
FRM_SERVO_Init($$)
{ {
my ($hash,$args) = @_; my ($hash,$args) = @_;
my $ret = FRM_Init_Pin_Client($hash,$args,PIN_SERVO); my $name = $hash->{NAME};
return $ret if (defined $ret);
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_SERVO);
if (defined($ret)) {
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
eval { eval {
my $firmata = FRM_Client_FirmataDevice($hash); my $firmata = FRM_Client_FirmataDevice($hash);
$hash->{resolution}=$firmata->{metadata}{servo_resolutions}{$hash->{PIN}} if (defined $firmata->{metadata}{servo_resolutions}); $hash->{resolution}=$firmata->{metadata}{servo_resolutions}{$hash->{PIN}} if (defined $firmata->{metadata}{servo_resolutions});
FRM_SERVO_apply_attribute($hash,"max-pulse"); #sets min-pulse as well FRM_SERVO_apply_attribute($hash,"max-pulse"); #sets min-pulse as well
}; };
return FRM_Catch($@) if $@; if ($@) {
$ret = FRM_Catch($@);
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
main::readingsSingleUpdate($hash,"state","Initialized",1); main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef; return undef;
} }
sub sub FRM_SERVO_Attr
FRM_SERVO_Attr($$$$) { {
my ($command,$name,$attribute,$value) = @_; my ($command,$name,$attribute,$value) = @_;
my $hash = $main::defs{$name}; my $hash = $main::defs{$name};
eval { eval {
@ -70,7 +119,10 @@ FRM_SERVO_Attr($$$$) {
}; };
($attribute eq "min-pulse" || $attribute eq "max-pulse") and do { ($attribute eq "min-pulse" || $attribute eq "max-pulse") and do {
if ($main::init_done) { if ($main::init_done) {
$main::attr{$name}{$attribute}=$value; if (defined($main::defs{$name}{IODev_ERROR})) {
die 'Perl module Device::Firmata not properly installed';
}
$main::attr{$name}{$attribute}=$value;
FRM_SERVO_apply_attribute($hash,$attribute); FRM_SERVO_apply_attribute($hash,$attribute);
} }
last; last;
@ -78,15 +130,15 @@ FRM_SERVO_Attr($$$$) {
} }
} }
}; };
my $ret = FRM_Catch($@) if $@; if ($@) {
if ($ret) { my $ret = FRM_Catch($@);
$hash->{STATE} = "error setting $attribute to $value: ".$ret; $hash->{STATE} = "$command $attribute error: " . $ret;
return "cannot $command attribute $attribute to $value for $name: ".$ret; return $hash->{STATE};
} }
return undef;
} }
sub FRM_SERVO_apply_attribute { sub FRM_SERVO_apply_attribute
{
my ($hash,$attribute) = @_; my ($hash,$attribute) = @_;
if ( $attribute eq "min-pulse" || $attribute eq "max-pulse" ) { if ( $attribute eq "min-pulse" || $attribute eq "max-pulse" ) {
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@ -95,69 +147,130 @@ sub FRM_SERVO_apply_attribute {
} }
} }
sub sub FRM_SERVO_Set
FRM_SERVO_Set($@)
{ {
my ($hash, @a) = @_; my ($hash, $name, $cmd, @a) = @_;
return "Need at least one parameters" if(@a < 2);
return "Unknown argument $a[1], choose one of " . join(" ", sort keys %sets) return "set command missing" if(!defined($cmd));
if(!defined($sets{$a[1]})); my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
my $command = $a[1]; return "unknown set command '$cmd', choose one of " . join(" ", sort keys %sets) if ($cmd eq '?' || @match == 0);
my $value = $a[2]; return "$cmd requires $sets{$match[0]} argument" unless (@a == $sets{$match[0]});
if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
}
my $value = shift @a;
eval { eval {
FRM_Client_FirmataDevice($hash)->servo_write($hash->{PIN},$value); FRM_Client_FirmataDevice($hash)->servo_write($hash->{PIN},$value);
main::readingsSingleUpdate($hash,"state",$value, 1); main::readingsSingleUpdate($hash,"state",$value, 1);
}; };
return $@; if ($@) {
my $ret = FRM_Catch($@);
$hash->{STATE} = "set $cmd error: " . $ret;
return $hash->{STATE};
}
return undef;
} }
1; 1;
=pod =pod
=head1 CHANGES
05.09.2020 jensb
o check for IODev install error in Init and Set
o prototypes removed
o set argument verifier improved
19.10.2020 jensb
o annotaded module help of attributes for FHEMWEB
=cut
=pod
=head1 FHEM COMMANDREF METADATA
=over
=item device
=item summary Firmata: PWM controlled servo output
=item summary_DE Firmata: PWM gesteuerter Servo Ausgang
=back
=head1 INSTALLATION AND CONFIGURATION
=begin html =begin html
<a name="FRM_SERVO"></a> <a name="FRM_SERVO"/>
<h3>FRM_SERVO</h3> <h3>FRM_SERVO</h3>
<ul> <ul>
represents a pin of an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a> represents a pin of an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a>
configured to drive a pwm-controlled servo-motor.<br> configured to drive a pwm-controlled servo-motor.<br>
The value set will be drive the shaft of the servo to the specified angle. see <a href="http://arduino.cc/en/Reference/ServoWrite">Servo.write</a> for values and range<br> The value set will be drive the shaft of the servo to the specified angle. see <a href="http://arduino.cc/en/Reference/ServoWrite">Servo.write</a> for values and range<br>
Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br> Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br>
<a name="FRM_SERVOdefine"></a> <a name="FRM_SERVOdefine"/>
<b>Define</b> <b>Define</b>
<ul> <ul>
<code>define &lt;name&gt; FRM_SERVO &lt;pin&gt;</code> <br> <code>define &lt;name&gt; FRM_SERVO &lt;pin&gt;</code> <br>
Defines the FRM_SERVO device. &lt;pin&gt> is the arduino-pin to use. Defines the FRM_SERVO device. &lt;pin&gt> is the arduino-pin to use.
</ul> </ul>
<br> <br>
<a name="FRM_SERVOset"></a> <a name="FRM_SERVOset"/>
<b>Set</b><br> <b>Set</b><br>
<ul> <ul>
<code>set &lt;name&gt; angle &lt;value&gt;</code><br>sets the angle of the servo-motors shaft to the value specified (in degrees).<br> <code>set &lt;name&gt; angle &lt;value&gt;</code><br>sets the angle of the servo-motors shaft to the value specified (in degrees).<br>
</ul> </ul>
<a name="FRM_SERVOget"></a>
<a name="FRM_SERVOget"/>
<b>Get</b><br> <b>Get</b><br>
<ul> <ul>
N/A N/A
</ul><br> </ul><br>
<a name="FRM_SERVOattr"></a>
<a name="FRM_SERVOattr"/>
<b>Attributes</b><br> <b>Attributes</b><br>
<ul> <ul>
<li><a href="#IODev">IODev</a><br> <a name="IODev"/>
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more <li><a href="#IODev">IODev</a><br>
than one FRM-device defined.) Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
</li> </li>
<li>min-pulse<br>
sets the minimum puls-width to use. Defaults to 544. For most servos this translates into a rotation of 180° counterclockwise.</li> <a name="min-pulse"/>
<li>max-pulse<br> <li>min-pulse<br>
sets the maximum puls-width to use. Defaults to 2400. For most servos this translates into a rotation of 180° clockwise</li> sets the minimum puls-width to use. Defaults to 544. For most servos this translates into a rotation of 180° counterclockwise.
<li><a href="#eventMap">eventMap</a><br></li> </li>
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
</ul> <a name="max-pulse"/>
<li>max-pulse<br>
sets the maximum puls-width to use. Defaults to 2400. For most servos this translates into a rotation of 180° clockwise.
</li>
<li><a href="#attributes">global attributes</a></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
</ul> </ul>
<br> </ul><br>
=end html =end html
=begin html_DE
<a name="FRM_SERVO"/>
<h3>FRM_SERVO</h3>
<ul>
Die Modulbeschreibung von FRM_SERVO gibt es nur auf <a href="commandref.html#FRM_SERVO">Englisch</a>. <br>
</ul><br>
=end html_DE
=cut =cut

View File

@ -1,6 +1,42 @@
############################################## ########################################################################################
# $Id$ # $Id$
############################################## ########################################################################################
=encoding UTF-8
=head1 NAME
FHEM module for two/four Firmata stepper motor output pins
=head1 LICENSE AND COPYRIGHT
Copyright (C) 2013 ntruchess
Copyright (C) 2020 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; package main;
use strict; use strict;
@ -8,136 +44,151 @@ use warnings;
#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though... #add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
BEGIN { BEGIN {
if (!grep(/FHEM\/lib$/,@INC)) { if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) { foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib"; push @INC,$inc."/lib";
}; };
}; };
}; };
use Device::Firmata::Constants qw/ :all /;
##################################### #####################################
# (min) number of arguments
my %sets = ( my %sets = (
"reset" => "noArg", "reset:noArg" => 0,
"position" => "", "position" => 1,
"step" => "", "step" => 1,
); );
my %gets = ( my %gets = (
"position" => "noArg", "position" => "",
); );
sub sub FRM_STEPPER_Initialize
FRM_STEPPER_Initialize($)
{ {
my ($hash) = @_; my ($hash) = @_;
$hash->{SetFn} = "FRM_STEPPER_Set"; $hash->{SetFn} = "FRM_STEPPER_Set";
$hash->{GetFn} = "FRM_STEPPER_Get"; $hash->{GetFn} = "FRM_STEPPER_Get";
$hash->{DefFn} = "FRM_Client_Define"; $hash->{DefFn} = "FRM_STEPPER_Define";
$hash->{InitFn} = "FRM_STEPPER_Init"; $hash->{InitFn} = "FRM_STEPPER_Init";
$hash->{UndefFn} = "FRM_Client_Undef"; $hash->{UndefFn} = "FRM_Client_Undef";
$hash->{AttrFn} = "FRM_STEPPER_Attr"; $hash->{AttrFn} = "FRM_STEPPER_Attr";
$hash->{StateFn} = "FRM_STEPPER_State"; $hash->{StateFn} = "FRM_STEPPER_State";
$hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off speed acceleration deceleration IODev $main::readingFnAttributes"; $hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off speed acceleration deceleration IODev $main::readingFnAttributes";
main::LoadModule("FRM"); main::LoadModule("FRM");
} }
sub sub FRM_STEPPER_Define
FRM_STEPPER_Init($$)
{ {
my ($hash,$args) = @_; my ($hash, $def) = @_;
my $u = "wrong syntax: define <name> FRM_STEPPER [DRIVER|TWO_WIRE|FOUR_WIRE] directionPin stepPin [motorPin3 motorPin4] stepsPerRev [id]"; # verify define arguments
return $u unless defined $args; my $usage = "usage: define <name> FRM_STEPPER [DRIVER|TWO_WIRE|FOUR_WIRE] directionPin stepPin [motorPin3 motorPin4] stepsPerRev [id]";
my $driver = shift @$args;
return $u unless ( $driver eq 'DRIVER' or $driver eq 'TWO_WIRE' or $driver eq 'FOUR_WIRE' );
return $u if (($driver eq 'DRIVER' or $driver eq 'TWO_WIRE') and (scalar(@$args) < 3 or scalar(@$args) > 4));
return $u if (($driver eq 'FOUR_WIRE') and (scalar(@$args) < 5 or scalar(@$args) > 6));
$hash->{DRIVER} = $driver;
$hash->{PIN1} = shift @$args;
$hash->{PIN2} = shift @$args;
if ($driver eq 'FOUR_WIRE') {
$hash->{PIN3} = shift @$args;
$hash->{PIN4} = shift @$args;
}
$hash->{STEPSPERREV} = shift @$args;
$hash->{STEPPERNUM} = shift @$args;
eval {
FRM_Client_AssignIOPort($hash);
my $firmata = FRM_Client_FirmataDevice($hash);
$firmata->stepper_config(
$hash->{STEPPERNUM},
$driver,
$hash->{STEPSPERREV},
$hash->{PIN1},
$hash->{PIN2},
$hash->{PIN3},
$hash->{PIN4});
$firmata->observe_stepper(0, \&FRM_STEPPER_observer, $hash );
};
if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/;
$hash->{STATE} = "error initializing: ".$1;
return "error initializing '".$hash->{NAME}."': ".$1;
}
$hash->{POSITION} = 0;
$hash->{DIRECTION} = 0;
$hash->{STEPS} = 0;
if (! (defined AttrVal($hash->{NAME},"stateFormat",undef))) {
$main::attr{$hash->{NAME}}{"stateFormat"} = "position";
}
main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
}
sub my @a = split("[ \t][ \t]*", $def);
FRM_STEPPER_observer my $args = [@a[2..scalar(@a)-1]];
{ return $usage unless defined $args;
my ( $stepper, $hash ) = @_;
my $name = $hash->{NAME};
Log3 $name,5,"onStepperMessage for pins ".$hash->{PIN1}.",".$hash->{PIN2}.(defined ($hash->{PIN3}) ? ",".$hash->{PIN3} : ",-").(defined ($hash->{PIN4}) ? ",".$hash->{PIN4} : ",-")." stepper: ".$stepper;
my $position = $hash->{DIRECTION} ? $hash->{POSITION} - $hash->{STEPS} : $hash->{POSITION} + $hash->{STEPS};
$hash->{POSITION} = $position;
$hash->{DIRECTION} = 0;
$hash->{STEPS} = 0;
main::readingsSingleUpdate($hash,"position",$position,1);
}
sub my $driver = shift @$args;
FRM_STEPPER_Set return $usage unless ( $driver eq 'DRIVER' or $driver eq 'TWO_WIRE' or $driver eq 'FOUR_WIRE' );
{ return $usage if (($driver eq 'DRIVER' or $driver eq 'TWO_WIRE') and (scalar(@$args) < 3 or scalar(@$args) > 4));
my ($hash, @a) = @_; return $usage if (($driver eq 'FOUR_WIRE') and (scalar(@$args) < 5 or scalar(@$args) > 6));
return "Need at least one parameters" if(@a < 2);
shift @a; $hash->{DRIVER} = $driver;
my $name = $hash->{NAME};
my $command = shift @a; $hash->{PIN1} = shift @$args;
if(!defined($sets{$command})) { $hash->{PIN2} = shift @$args;
my @commands = ();
foreach my $key (sort keys %sets) { if ($driver eq 'FOUR_WIRE') {
push @commands, $sets{$key} ? $key.":".join(",",$sets{$key}) : $key; $hash->{PIN3} = shift @$args;
} $hash->{PIN4} = shift @$args;
return "Unknown argument $command, choose one of " . join(" ", @commands);
} }
COMMAND_HANDLER: {
$command eq "reset" and do { $hash->{STEPSPERREV} = shift @$args;
$hash->{STEPPERNUM} = shift @$args;
my $ret = FRM_Client_Define($hash, $def);
if ($ret) {
return $ret;
}
return undef;
}
sub FRM_STEPPER_Init
{
my ($hash,$args) = @_;
my $name = $hash->{NAME};
if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
}
eval {
FRM_Client_AssignIOPort($hash);
my $firmata = FRM_Client_FirmataDevice($hash);
$firmata->stepper_config(
$hash->{STEPPERNUM},
$hash->{DRIVER},
$hash->{STEPSPERREV},
$hash->{PIN1},
$hash->{PIN2},
$hash->{PIN3},
$hash->{PIN4});
$firmata->observe_stepper(0, \&FRM_STEPPER_observer, $hash );
};
if ($@) {
my $ret = FRM_Catch($@);
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
$hash->{POSITION} = 0;
$hash->{DIRECTION} = 0;
$hash->{STEPS} = 0;
if (! (defined AttrVal($name,"stateFormat",undef))) {
$main::attr{$name}{"stateFormat"} = "position";
}
main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
}
sub FRM_STEPPER_observer
{
my ( $stepper, $hash ) = @_;
my $name = $hash->{NAME};
Log3 $name, 5, "$name: observer pins: ".$hash->{PIN1}.",".$hash->{PIN2}.(defined ($hash->{PIN3}) ? ",".$hash->{PIN3} : ",-").(defined ($hash->{PIN4}) ? ",".$hash->{PIN4} : ",-")." stepper: ".$stepper;
my $position = $hash->{DIRECTION} ? $hash->{POSITION} - $hash->{STEPS} : $hash->{POSITION} + $hash->{STEPS};
$hash->{POSITION} = $position;
$hash->{DIRECTION} = 0;
$hash->{STEPS} = 0;
main::readingsSingleUpdate($hash,"position",$position,1);
}
sub FRM_STEPPER_Set
{
my ($hash, $name, $cmd, @a) = @_;
return "set command missing" if(!defined($cmd));
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
return "unknown set command '$cmd', choose one of " . join(" ", sort keys %sets) if ($cmd eq '?' || @match == 0);
return "$cmd requires (at least) $sets{$match[0]} argument(s)" unless (@a >= $sets{$match[0]});
my $value = shift @a;
SETHANDLER: {
$cmd eq "reset" and do {
$hash->{POSITION} = 0; $hash->{POSITION} = 0;
main::readingsSingleUpdate($hash,"position",0,1); main::readingsSingleUpdate($hash,"position",0,1);
last; last;
}; };
$command eq "position" and do { $cmd eq "position" and do {
if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
}
my $position = $hash->{POSITION}; my $position = $hash->{POSITION};
my $value = shift @a;
my $direction = $value < $position ? 1 : 0; my $direction = $value < $position ? 1 : 0;
my $steps = $direction ? $position - $value : $value - $position; my $steps = $direction ? $position - $value : $value - $position;
my $speed = shift @a; my $speed = shift @a;
@ -149,13 +200,19 @@ FRM_STEPPER_Set
$hash->{DIRECTION} = $direction; $hash->{DIRECTION} = $direction;
$hash->{STEPS} = $steps; $hash->{STEPS} = $steps;
eval { eval {
# $stepperNum, $direction, $numSteps, $stepSpeed, $accel, $decel
FRM_Client_FirmataDevice($hash)->stepper_step($hash->{STEPPERNUM},$direction,$steps,$speed,$accel,$decel); FRM_Client_FirmataDevice($hash)->stepper_step($hash->{STEPPERNUM},$direction,$steps,$speed,$accel,$decel);
}; };
if ($@) {
my $ret = FRM_Catch($@);
$hash->{STATE} = "set $cmd error: " . $ret;
return $hash->{STATE};
}
last; last;
}; };
$command eq "step" and do { $cmd eq "step" and do {
my $value = shift @a; if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
}
my $direction = $value < 0 ? 1 : 0; my $direction = $value < 0 ? 1 : 0;
my $steps = abs $value; my $steps = abs $value;
my $speed = shift @a; my $speed = shift @a;
@ -167,42 +224,53 @@ FRM_STEPPER_Set
$hash->{DIRECTION} = $direction; $hash->{DIRECTION} = $direction;
$hash->{STEPS} = $steps; $hash->{STEPS} = $steps;
eval { eval {
# $stepperNum, $direction, $numSteps, $stepSpeed, $accel, $decel
FRM_Client_FirmataDevice($hash)->stepper_step($hash->{STEPPERNUM},$direction,$steps,$speed,$accel,$decel); FRM_Client_FirmataDevice($hash)->stepper_step($hash->{STEPPERNUM},$direction,$steps,$speed,$accel,$decel);
}; };
if ($@) {
my $ret = FRM_Catch($@);
$hash->{STATE} = "set $cmd error: " . $ret;
return $hash->{STATE};
}
last; last;
}; };
} }
return undef;
} }
sub sub FRM_STEPPER_Get
FRM_STEPPER_Get
{ {
my ($hash, @a) = @_; my ($hash, $name, $cmd, @a) = @_;
return "Need at least one parameters" if(@a < 2);
shift @a; return "get command missing" if(!defined($cmd));
my $name = $hash->{NAME}; return "unknown get command '$cmd', choose one of " . join(":noArg ", sort keys %gets) . ":noArg" if(!defined($gets{$cmd}));
my $command = shift @a;
return "Unknown argument $command, choose one of " . join(" ", sort keys %gets) unless defined($gets{$command}); GETHANDLER: {
$cmd eq 'position' and do {
return $hash->{POSITION};
};
}
return undef;
} }
sub FRM_STEPPER_State($$$$) sub FRM_STEPPER_State
{ {
my ($hash, $tim, $sname, $sval) = @_; my ($hash, $tim, $sname, $sval) = @_;
STATEHANDLER: { STATEHANDLER: {
$sname eq "value" and do { $sname eq "value" and do {
if (AttrVal($hash->{NAME},"restoreOnStartup","on") eq "on") { if (AttrVal($hash->{NAME},"restoreOnStartup","on") eq "on") {
FRM_STEPPER_Set($hash,$hash->{NAME},$sval); FRM_STEPPER_Set($hash,$hash->{NAME},$sval);
} }
last; last;
} }
} }
} }
sub sub FRM_STEPPER_Attr
FRM_STEPPER_Attr($$$$) { {
my ($command,$name,$attribute,$value) = @_; my ($command,$name,$attribute,$value) = @_;
my $hash = $main::defs{$name}; my $hash = $main::defs{$name};
eval { eval {
@ -219,115 +287,183 @@ FRM_STEPPER_Attr($$$$) {
} }
}; };
if ($@) { if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/; my $ret = FRM_Catch($@);
$hash->{STATE} = "error setting $attribute to $value: ".$1; $hash->{STATE} = "$command $attribute error: " . $ret;
return "cannot $command attribute $attribute to $value for $name: ".$1; return $hash->{STATE};
} }
} }
1; 1;
=pod =pod
=head1 CHANGES
05.09.2020 jensb
o check for IODev install error in Init and Set
o prototypes removed
o get position implemented
o set argument verifier improved
o module help updated
o moved define argument verification and decoding from Init to Define
22.10.2020 jensb
o annotaded module help of attributes for FHEMWEB
=cut
=pod
=head1 FHEM COMMANDREF METADATA
=over
=item device
=item summary Firmata: rotary encoder input
=item summary_DE Firmata: Drehgeber Eingang
=back
=head1 INSTALLATION AND CONFIGURATION
=begin html =begin html
<a name="FRM_STEPPER"></a> <a name="FRM_STEPPER"></a>
<h3>FRM_STEPPER</h3> <h3>FRM_STEPPER</h3>
<ul> <ul>
represents a stepper-motor attached to digital-i/o pins of an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a><br> represents a stepper-motor attached to digital-i/o pins of an <a href="http://www.arduino.cc">Arduino</a>
Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br> running <a href="http://www.firmata.org">Firmata</a><br>
Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br>
<a name="FRM_STEPPERdefine"></a> <a name="FRM_STEPPERdefine"></a>
<b>Define</b> <b>Define</b>
<ul> <ul>
<code>define &lt;name&gt; FRM_STEPPER [DRIVER|TWO_WIRE|FOUR_WIRE] &lt;directionPin&gt &lt;stepPin&gt [motorPin3 motorPin4] stepsPerRev [stepper-id]</code><br> <code>define &lt;name&gt; FRM_STEPPER [DRIVER|TWO_WIRE|FOUR_WIRE] &lt;directionPin&gt &lt;stepPin&gt [motorPin3 motorPin4] stepsPerRev [stepper-id]</code><br>
Defines the FRM_STEPPER device. Defines the FRM_STEPPER device.
<li>[DRIVER|TWO_WIRE|FOUR_WIRE] defines the control-sequence being used to drive the motor. <li>[DRIVER|TWO_WIRE|FOUR_WIRE] defines the control-sequence being used to drive the motor.
<ul> <ul>
<li>DRIVER: motor is attached via a smart circuit that is controlled via two lines: 1 line defines the direction to turn, the other triggers one step per impluse.</li> <li>DRIVER: motor is attached via a smart circuit that is controlled via two lines: 1 line defines the
<li>FOUR_WIRE: motor is attached via four wires each driving one coil individually.</li> direction to turn, the other triggers one step per impluse.
<li>TWO_WIRE: motor is attached via two wires. This mode makes use of the fact that at any time two of the four motor </li>
coils are the inverse of the other two so by using an inverting circuit to drive the motor the number of control connections can be reduced from 4 to 2.</li> <li>FOUR_WIRE: motor is attached via four wires each driving one coil individually.</li>
</ul> <li>TWO_WIRE: motor is attached via two wires. This mode makes use of the fact that at any time two of
</li> the four motor coils are the inverse of the other two so by using an inverting circuit to drive the motor
<li> the number of control connections can be reduced from 4 to 2.
<ul> </li>
<li>The sequence of control signals for 4 control wires is as follows:<br> </ul>
<br> </li>
<code> <li>
Step C0 C1 C2 C3<br> <ul>
1 1 0 1 0<br> <li>The sequence of control signals for 4 control wires is as follows:<br><br>
2 0 1 1 0<br>
3 0 1 0 1<br> <code>
4 1 0 0 1<br> Step C0 C1 C2 C3<br>
</code> 1 1 0 1 0<br>
</li> 2 0 1 1 0<br>
<li>The sequence of controls signals for 2 control wires is as follows:<br> 3 0 1 0 1<br>
(columns C1 and C2 from above):<br> 4 1 0 0 1<br>
<br> </code>
<code> </li>
Step C0 C1<br> <li>The sequence of controls signals for 2 control wires is as follows:<br>
1 0 1<br> (columns C1 and C2 from above):<br><br>
2 1 1<br>
3 1 0<br> <code>
4 0 0<br> Step C0 C1<br>
</code> 1 0 1<br>
</li> 2 1 1<br>
</ul> 3 1 0<br>
</li> 4 0 0<br>
<li> </code>
If your stepper-motor does not move or does move but only in a single direction you will have to rearrage the pin-numbers to match the control sequence.<br> </li>
that can be archived either by rearranging the physical connections, or by mapping the connection to the pin-definitions in FRM_STEPPERS define:<br> </ul>
e.g. the widely used cheap 28byj-48 you can get for few EUR on eBay including a simple ULN2003 driver interface may be defined by<br> </li>
<code>define stepper FRM_STEPPER FOUR_WIRE 7 5 6 8 64 0</code><br> <li>
when being connected to the arduio with:<br> If your stepper-motor does not move or does move but only in a single direction you will have to rearrage
<code>motor pin1 <-> arduino pin5<br> the pin-numbers to match the control sequence. That can be archived either by rearranging the physical
motor pin2 <-> arduino pin6<br> connections, or by mapping the connection to the pin-definitions in FRM_STEPPERS define:<br>
motor pin3 <-> arduino pin7<br> e.g. the widely used cheap 28byj-48 you can get for few EUR on eBay including a simple ULN2003 driver
motor pin4 <-> arduino pin8<br> interface may be defined by<br>
motor pin5 <-> ground</code><br> <code>define stepper FRM_STEPPER FOUR_WIRE 7 5 6 8 64 0</code><br>
</li> when being connected to the arduio with:<br><br>
</ul>
<code>
<br> motor pin1 <-> arduino pin5<br>
motor pin2 <-> arduino pin6<br>
motor pin3 <-> arduino pin7<br>
motor pin4 <-> arduino pin8<br>
motor pin5 <-> ground
</code>
</li><br>
</ul><br>
<a name="FRM_STEPPERset"></a> <a name="FRM_STEPPERset"></a>
<b>Set</b><br> <b>Set</b><br>
<ul> <ul>
<code>set &lt;name&gt; reset</code> <code>set &lt;name&gt; reset</code>
<li>resets the reading 'position' to 0 without moving the motor</li> <li>resets the reading 'position' to 0 without moving the motor</li><br>
<br>
<code>set &lt;name&gt; position &lt;position&gt; [speed] [acceleration] [deceleration]</code> <code>set &lt;name&gt; position &lt;position&gt; [speed] [acceleration] [deceleration]</code>
<li>moves the motor to the absolute position specified. positive or negative integer<br> <li>moves the motor to the absolute position specified. positive or negative integer<br>
speed (10 * revolutions per minute, optional), defaults to 30, higher numbers are faster) At 2048 steps per revolution (28byj-48) a speed of 30 results in 3 rev/min<br> speed (10 * revolutions per minute, optional), defaults to 30, higher numbers are faster.
acceleration and deceleration are optional.<br> At 2048 steps per revolution (28byj-48) a speed of 30 results in 3 rev/min<br>
</li> acceleration and deceleration are optional.<br>
<br> </li><br>
<code>set &lt;name&gt; step &lt;stepstomove&gt; [speed] [accel] [decel]</code>
<li>moves the motor the number of steps specified. positive or negative integer<br> <code>set &lt;name&gt; step &lt;stepstomove&gt; [speed] [accel] [decel]</code>
speed, accelleration and deceleration are optional.<br> <li>moves the motor the number of steps specified. positive or negative integer<br>
</li> speed, accelleration and deceleration are optional.<br>
</ul> </li>
</ul><br>
<a name="FRM_STEPPERget"></a> <a name="FRM_STEPPERget"></a>
<b>Get</b><br> <b>Get</b><br>
<ul> <ul>
N/A <code>get &lt;position&gt;</code>
<li>returns the current position value</li>
</ul><br> </ul><br>
<a name="FRM_STEPPERattr"></a> <a name="FRM_STEPPERattr"></a>
<b>Attributes</b><br> <b>Attributes</b><br>
<ul> <ul>
<li>restoreOnStartup &lt;on|off&gt;</li> <a name="restoreOnStartup"/>
<li>restoreOnReconnect &lt;on|off&gt;</li> <li>restoreOnStartup &lt;on|off&gt;</li>
<li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more <a name="restoreOnReconnect"/>
than one FRM-device defined.) <li>restoreOnReconnect &lt;on|off&gt;</li>
</li>
<li>>speed (same meaning as in 'set position')</li> <a name="IODev"/>
<li>acceleration (same meaning as in 'set position')</li> <li><a href="#IODev">IODev</a><br>
<li>deceleration (same meaning as in 'set position')</li> Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
<li><a href="#eventMap">eventMap</a><br></li> </li>
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
</ul> <a name="speed"/>
<li>>speed (same meaning as in 'set position')</li>
<a name="acceleration"/>
<li>acceleration (same meaning as in 'set position')</li>
<a name="deceleration"/>
<li>deceleration (same meaning as in 'set position')</li>
<li><a href="#attributes">global attributes</a></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
</ul> </ul>
<br> </ul><br>
=end html =end html
=begin html_DE
<a name="FRM_STEPPER"></a>
<h3>FRM_STEPPER</h3>
<ul>
Die Modulbeschreibung von FRM_STEPPER gibt es nur auf <a href="commandref.html#FRM_STEPPER">Englisch</a>. <br>
</ul><br>
=end html_DE
=cut =cut

View File

@ -118,15 +118,14 @@ FHEM/18_CUL_HOERMANN.pm rudolfkoenig SlowRF
FHEM/19_Revolt.pm yoda_gh SlowRF FHEM/19_Revolt.pm yoda_gh SlowRF
FHEM/19_VBUSIF.pm Tobias/pejonp Sonstige Systeme FHEM/19_VBUSIF.pm Tobias/pejonp Sonstige Systeme
FHEM/20_FRM_AD.pm jensb Sonstige Systeme FHEM/20_FRM_AD.pm jensb Sonstige Systeme
FHEM/20_FRM_I2C.pm ntruchsess Sonstige Systeme FHEM/20_FRM_I2C.pm jensb Sonstige Systeme
FHEM/20_FRM_IN.pm jensb Sonstige Systeme FHEM/20_FRM_IN.pm jensb Sonstige Systeme
FHEM/20_FRM_LCD.pm ntruchsess Sonstige Systeme (deprecated)
FHEM/20_FRM_OUT.pm jensb Sonstige Systeme FHEM/20_FRM_OUT.pm jensb Sonstige Systeme
FHEM/20_FRM_PWM.pm jensb Sonstige Systeme FHEM/20_FRM_PWM.pm jensb Sonstige Systeme
FHEM/20_FRM_RGB.pm ntruchsess Sonstige Systeme FHEM/20_FRM_RGB.pm jensb Sonstige Systeme
FHEM/20_FRM_ROTENC.pm ntruchsess Sonstige Systeme FHEM/20_FRM_ROTENC.pm jensb Sonstige Systeme
FHEM/20_FRM_SERVO.pm ntruchsess Sonstige Systeme FHEM/20_FRM_SERVO.pm jensb Sonstige Systeme
FHEM/20_FRM_STEPPER.pm ntruchsess Sonstige Systeme FHEM/20_FRM_STEPPER.pm jensb Sonstige Systeme
FHEM/20_GUEST.pm loredo Automatisierung FHEM/20_GUEST.pm loredo Automatisierung
FHEM/20_N4HBUS.pm okoerber Sonstige Systeme FHEM/20_N4HBUS.pm okoerber Sonstige Systeme
FHEM/20_OWFS.pm mfr69bs 1Wire (deprecated) FHEM/20_OWFS.pm mfr69bs 1Wire (deprecated)
@ -618,6 +617,7 @@ contrib/AttrTemplate/* Beta-User (depends on attrTemplate)
contrib/commandref* rudolfkoenig Sonstiges contrib/commandref* rudolfkoenig Sonstiges
contrib/pre-commit rudolfkoenig Sonstiges contrib/pre-commit rudolfkoenig Sonstiges
contrib/DEBIAN/* betateilchen Sonstiges contrib/DEBIAN/* betateilchen Sonstiges
contrib/deprecated/20_FRM_LCD.pm ntruchsess (deprecated)
contrib/deprecated/70_Pushalot.pm Talkabout (deprecated) contrib/deprecated/70_Pushalot.pm Talkabout (deprecated)
contrib/deprecated/95_PachLog.pm rudolfkoenig/orphan (deprecated) contrib/deprecated/95_PachLog.pm rudolfkoenig/orphan (deprecated)
contrib/DoorPi/70_DoorPi.pm pahenning Automatisierung contrib/DoorPi/70_DoorPi.pm pahenning Automatisierung