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@23054 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
jensb 2020-10-30 18:16:24 +00:00
parent b94eb42175
commit 79a664abe5
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;
@ -46,10 +51,9 @@ BEGIN {
}; };
}; };
use Device::Firmata::Constants qw/ :all /;
##################################### #####################################
# get command default return values
my %gets = ( my %gets = (
"reading" => "", "reading" => "",
"state" => "", "state" => "",
@ -57,8 +61,7 @@ my %gets = (
"alarm-lower-threshold" => "off", "alarm-lower-threshold" => "off",
); );
sub sub FRM_AD_Initialize
FRM_AD_Initialize($)
{ {
my ($hash) = @_; my ($hash) = @_;
@ -71,40 +74,55 @@ FRM_AD_Initialize($)
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);
return $ret if (defined $ret);
my $firmata = $hash->{IODev}->{FirmataDevice};
my $name = $hash->{NAME}; my $name = $hash->{NAME};
if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
}
my $ret = FRM_Init_Pin_Client($hash, $args, Device::Firmata::Constants->PIN_ANALOG);
if (defined($ret)) {
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
eval {
my $firmata = FRM_Client_FirmataDevice($hash);
my $resolution = 10; my $resolution = 10;
if (defined $firmata->{metadata}{analog_resolutions}) { if (defined $firmata->{metadata}{analog_resolutions}) {
$resolution = $firmata->{metadata}{analog_resolutions}{$hash->{PIN}} $resolution = $firmata->{metadata}{analog_resolutions}{$hash->{PIN}}
} }
$hash->{resolution} = $resolution; $hash->{resolution} = $resolution;
$hash->{".max"} = defined $resolution ? (1<<$resolution)-1 : 1024; $hash->{".max"} = defined $resolution ? (1<<$resolution)-1 : 1024;
eval {
$firmata->observe_analog($hash->{PIN},\&FRM_AD_observer,$hash); $firmata->observe_analog($hash->{PIN},\&FRM_AD_observer,$hash);
}; };
return FRM_Catch($@) if $@; if ($@) {
if (! (defined AttrVal($name,"stateFormat",undef))) { $ret = FRM_Catch($@);
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
if (!(defined AttrVal($name,"stateFormat",undef))) {
$main::attr{$name}{"stateFormat"} = "reading"; $main::attr{$name}{"stateFormat"} = "reading";
} }
if (! (defined AttrVal($name,"event-min-interval",undef))) {
if (!(defined AttrVal($name,"event-min-interval",undef))) {
$main::attr{$name}{"event-min-interval"} = 5; $main::attr{$name}{"event-min-interval"} = 5;
} }
main::readingsSingleUpdate($hash,"state","Initialized",1); main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef; 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");
@ -130,19 +148,23 @@ FRM_AD_observer
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,7 +201,7 @@ 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
@ -187,15 +209,34 @@ FRM_AD_Attr($$$$) {
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>
@ -204,7 +245,7 @@ FRM_AD_Attr($$$$) {
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>
@ -212,13 +253,13 @@ FRM_AD_Attr($$$$) {
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>
@ -237,26 +278,32 @@ FRM_AD_Attr($$$$) {
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>
<a name="upper-threshold"/>
<li>upper-threshold<br> <li>upper-threshold<br>
sets the 'upper-threshold'. Whenever the 'reading' exceeds this value 'alarm-upper-threshold' is set to 'on'<br> sets the 'upper-threshold'. Whenever the 'reading' exceeds this value 'alarm-upper-threshold' is set to 'on'<br>
As soon 'reading' falls below the 'upper-threshold' 'alarm-upper-threshold' turns 'off' again<br> As soon 'reading' falls below the 'upper-threshold' 'alarm-upper-threshold' turns 'off' again<br>
Defaults to the max pin resolution plus one.</li> Defaults to the max pin resolution plus one.</li>
<a name="lower-threshold"/>
<li>lower-threshold<br> <li>lower-threshold<br>
sets the 'lower-threshold'. Whenever the 'reading' falls below this value 'alarm-lower-threshold' is set to 'on'<br> 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> As soon 'reading' rises above the 'lower-threshold' 'alarm-lower-threshold' turns 'off' again<br>
Defaults to -1.</li> Defaults to -1.</li>
<a name="IODev"/>
<li><a href="#IODev">IODev</a><br> <li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
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>
<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>
@ -267,4 +314,15 @@ FRM_AD_Attr($$$$) {
</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;
@ -46,15 +51,15 @@ BEGIN {
}; };
}; };
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,60 +91,77 @@ FRM_IN_PinModePullupSupported($)
return defined($pullupPins); return defined($pullupPins);
} }
sub sub FRM_IN_Init
FRM_IN_Init($$)
{ {
my ($hash,$args) = @_; my ($hash,$args) = @_;
my $name = $hash->{NAME};
if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
}
if (FRM_IN_PinModePullupSupported($hash)) { if (FRM_IN_PinModePullupSupported($hash)) {
my $pullup = AttrVal($hash->{NAME},"internal-pullup","off"); my $pullup = AttrVal($name, "internal-pullup", "off");
my $ret = FRM_Init_Pin_Client($hash,$args,defined($pullup) && ($pullup eq "on")? PIN_PULLUP : PIN_INPUT); my $ret = FRM_Init_Pin_Client($hash,$args,defined($pullup) && ($pullup eq "on")? Device::Firmata::Constants->PIN_PULLUP : Device::Firmata::Constants->PIN_INPUT);
return $ret if (defined $ret); return $ret if (defined $ret);
eval { eval {
my $firmata = FRM_Client_FirmataDevice($hash); my $firmata = FRM_Client_FirmataDevice($hash);
my $pin = $hash->{PIN}; my $pin = $hash->{PIN};
$firmata->observe_digital($pin,\&FRM_IN_observer,$hash); $firmata->observe_digital($pin,\&FRM_IN_observer,$hash);
}; };
return FRM_Catch($@) if $@; if ($@) {
my $ret = FRM_Catch($@);
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
} else { } else {
my $ret = FRM_Init_Pin_Client($hash,$args,PIN_INPUT); my $ret = FRM_Init_Pin_Client($hash, $args, Device::Firmata::Constants->PIN_INPUT);
return $ret if (defined $ret); return $ret if (defined $ret);
eval { eval {
my $firmata = FRM_Client_FirmataDevice($hash); my $firmata = FRM_Client_FirmataDevice($hash);
my $pin = $hash->{PIN}; my $pin = $hash->{PIN};
if (defined (my $pullup = AttrVal($hash->{NAME},"internal-pullup",undef))) { if (defined(my $pullup = AttrVal($name, "internal-pullup", undef))) {
$firmata->digital_write($pin,$pullup eq "on" ? 1 : 0); $firmata->digital_write($pin,$pullup eq "on" ? 1 : 0);
} }
$firmata->observe_digital($pin,\&FRM_IN_observer,$hash); $firmata->observe_digital($pin,\&FRM_IN_observer,$hash);
}; };
return FRM_Catch($@) if $@; if ($@) {
my $ret = FRM_Catch($@);
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
} }
if (! (defined AttrVal($hash->{NAME},"stateFormat",undef))) {
$main::attr{$hash->{NAME}}{"stateFormat"} = "reading";
} }
if (!(defined AttrVal($name, "stateFormat", undef))) {
$main::attr{$name}{"stateFormat"} = "reading";
}
main::readingsSingleUpdate($hash,"state","Initialized",1); main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef; 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); my $old = ReadingsVal($name, "reading", undef);
if (defined($old)) { if (defined($old)) {
$old = $old eq "on" ? PIN_HIGH : PIN_LOW; $old = $old eq "on" ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW;
} }
if (AttrVal($hash->{NAME},"activeLow","no") eq "yes") { if (AttrVal($hash->{NAME},"activeLow","no") eq "yes") {
$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;
} }
Log3 $name, 5, "$name observer 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 : "--");
my $changed = !defined($old) || $old != $new; my $changed = !defined($old) || $old != $new;
if ($changed) { if ($changed) {
main::readingsBeginUpdate($hash); main::readingsBeginUpdate($hash);
if (defined (my $mode = main::AttrVal($name,"count-mode",undef))) { if (defined (my $mode = main::AttrVal($name,"count-mode",undef))) {
if (($mode eq "both") if (($mode eq "both")
or (($mode eq "rising") and ($new == PIN_HIGH)) or (($mode eq "rising") and ($new == Device::Firmata::Constants->PIN_HIGH))
or (($mode eq "falling") and ($new == PIN_LOW))) { or (($mode eq "falling") and ($new == Device::Firmata::Constants->PIN_LOW))) {
my $count = main::ReadingsVal($name,"count",0); my $count = main::ReadingsVal($name,"count",0);
$count++; $count++;
if (defined (my $threshold = main::AttrVal($name,"count-threshold",undef))) { if (defined (my $threshold = main::AttrVal($name,"count-threshold",undef))) {
@ -157,64 +177,74 @@ FRM_IN_observer($$$$)
main::readingsBulkUpdate($hash,"count",$count,1); main::readingsBulkUpdate($hash,"count",$count,1);
} }
}; };
main::readingsBulkUpdate($hash,"reading",$new == PIN_HIGH ? "on" : "off", 1); main::readingsBulkUpdate($hash, "reading", $new == Device::Firmata::Constants->PIN_HIGH ? "on" : "off", 1);
main::readingsEndUpdate($hash,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,7 +358,7 @@ 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
@ -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,7 +431,7 @@ 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>
@ -389,31 +447,48 @@ FRM_IN_Attr($$$$) {
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>
<a name="activeLow"/>
<li>activeLow yes|no<br> <li>activeLow yes|no<br>
inverts the logical state of the pin reading if set to yes (defaults to 'no').</li> inverts the logical state of the pin reading if set to yes (defaults to 'no').
</li>
<a name="count-mode"/>
<li>count-mode none|rising|falling|both<br> <li>count-mode none|rising|falling|both<br>
Determines whether 'rising' (transitions from 'off' to 'on') of falling (transitions from 'on' to 'off') Determines whether 'rising' (transitions from 'off' to 'on') of falling (transitions from 'on' to 'off')
edges (or 'both') are counted (defaults to 'none').</li> edges (or 'both') are counted (defaults to 'none').
</li>
<a name="count-threshold"/>
<li>count-threshold &lt;number&gt;<br> <li>count-threshold &lt;number&gt;<br>
sets the threshold-value for the counter - if defined whenever 'count' exceeds the 'count-threshold' the 'alarm' reading is sets the threshold-value for the counter - if defined whenever 'count' exceeds the 'count-threshold'
set to 'on' (defaults to undefined). Use 'set alarm off' to clear the alarm.</li> the 'alarm' reading is set to 'on' (defaults to undefined). Use 'set alarm off' to clear the alarm.
</li>
<a name="reset-on-threshold-reached"/>
<li>reset-on-threshold-reached yes|no<br> <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'). if set to 'yes' reset the counter to 0 when the threshold is reached (defaults to 'no').
</li> </li>
<a name="internal-pullup"/>
<li>internal-pullup on|off<br> <li>internal-pullup on|off<br>
enables/disables the internal pullup resistor of the Firmata pin (defaults to 'off'). Requires hardware and firmware support. enables/disables the internal pullup resistor of the Firmata pin (defaults to 'off'). Requires hardware
and firmware support.
</li> </li>
<a name="IODev"/>
<li><a href="#IODev">IODev</a><br> <li><a href="#IODev">IODev</a><br>
specify which <a href="#FRM">FRM</a> to use. 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>
<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;
@ -46,12 +51,17 @@ BEGIN {
}; };
}; };
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) = @_;
@ -63,83 +73,114 @@ FRM_OUT_Initialize($)
$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);
if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
}
my $ret = FRM_Init_Pin_Client($hash, $args, Device::Firmata::Constants->PIN_OUTPUT);
if (defined($ret)) {
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
eval { eval {
my $firmata = FRM_Client_FirmataDevice($hash); my $firmata = FRM_Client_FirmataDevice($hash);
my $pin = $hash->{PIN}; my $pin = $hash->{PIN};
$firmata->observe_digital($pin,\&FRM_OUT_observer,$hash); $firmata->observe_digital($pin,\&FRM_OUT_observer,$hash);
}; };
my $name = $hash->{NAME}; if ($@) {
if (! (defined AttrVal($name,"stateFormat",undef))) { $ret = FRM_Catch($@);
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
if (!(defined AttrVal($name,"stateFormat",undef))) {
$main::attr{$name}{"stateFormat"} = "value"; $main::attr{$name}{"stateFormat"} = "value";
} }
my $value = ReadingsVal($name,"value",undef); my $value = ReadingsVal($name,"value",undef);
if (!defined($value)) { if (!defined($value)) {
readingsSingleUpdate($hash,"value","off",0); readingsSingleUpdate($hash,"value","off",0);
} }
if (AttrVal($hash->{NAME},"restoreOnReconnect", "on") eq "on") {
if (AttrVal($name, "restoreOnReconnect", "on") eq "on") {
FRM_OUT_Set($hash,$name,$value); FRM_OUT_Set($hash,$name,$value);
} }
main::readingsSingleUpdate($hash,"state","Initialized",1); main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef; 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");
SETHANDLER: {
$cmd eq "on" and do {
$value = $invert eq "yes" ? Device::Firmata::Constants->PIN_LOW : Device::Firmata::Constants->PIN_HIGH;
last;
};
$cmd eq "off" and do {
$value = $invert eq "yes" ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW;
last;
};
};
eval { eval {
FRM_Client_FirmataDevice($hash)->digital_write($hash->{PIN},$value); FRM_Client_FirmataDevice($hash)->digital_write($hash->{PIN},$value);
if (AttrVal($hash->{NAME}, "valueMode", "send") ne "receive") { if (AttrVal($hash->{NAME}, "valueMode", "send") ne "receive") {
main::readingsSingleUpdate($hash,"value",$cmd, 1); main::readingsSingleUpdate($hash,"value",$cmd, 1);
} }
}; };
return $@; if ($@) {
} else { my $ret = FRM_Catch($@);
return "no command specified"; $hash->{STATE} = "set $cmd error: " . $ret;
return $hash->{STATE};
} }
return undef;
} }
sub FRM_OUT_State($$$$) sub FRM_OUT_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_OUT_Set($hash,$hash->{NAME},$sval); FRM_OUT_Set($hash,$hash->{NAME},$sval);
@ -149,8 +190,8 @@ STATEHANDLER: {
} }
} }
sub sub FRM_OUT_Attr
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,28 +231,53 @@ 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>
@ -220,14 +287,14 @@ FRM_OUT_Attr($$$$) {
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>
@ -236,27 +303,35 @@ FRM_OUT_Attr($$$$) {
<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>
<a name="restoreOnStartup"/>
<li>restoreOnStartup &lt;on|off&gt;, default: on<br> <li>restoreOnStartup &lt;on|off&gt;, default: on<br>
Set output value in Firmata device on FHEM startup (if device is already connected) and Set output value in Firmata device on FHEM startup (if device is already connected) and
whenever the <em>setstate</em> command is used. whenever the <em>setstate</em> command is used.
</li> </li>
<a name="restoreOnReconnect"/>
<li>restoreOnReconnect &lt;on|off&gt;, default: on<br> <li>restoreOnReconnect &lt;on|off&gt;, default: on<br>
Set output value in Firmata device after IODev is initialized. Set output value in Firmata device after IODev is initialized.
</li> </li>
<a name="activeLow"/>
<li>activeLow &lt;yes|no&gt;, default: no</li> <li>activeLow &lt;yes|no&gt;, default: no</li>
<a name="IODev"/>
<li><a href="#IODev">IODev</a><br> <li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
than one FRM-device defined.)
</li> </li>
<a name="valueMode"/>
<li>valueMode &lt;send|receive|bidirectional&gt;, default: send<br> <li>valueMode &lt;send|receive|bidirectional&gt;, default: send<br>
Define how the reading <em>value</em> is updated:<br> Define how the reading <em>value</em> is updated:<br>
<ul> <ul>
@ -265,11 +340,13 @@ FRM_OUT_Attr($$$$) {
<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>
@ -277,14 +354,25 @@ FRM_OUT_Attr($$$$) {
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;
@ -46,30 +51,27 @@ BEGIN {
}; };
}; };
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) = @_;
@ -85,14 +87,23 @@ FRM_PWM_Initialize($)
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);
return $ret if (defined $ret);
my $firmata = $hash->{IODev}->{FirmataDevice};
my $name = $hash->{NAME}; my $name = $hash->{NAME};
if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
}
my $ret = FRM_Init_Pin_Client($hash, $args, Device::Firmata::Constants->PIN_PWM);
if (defined($ret)) {
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
eval {
my $firmata = FRM_Client_FirmataDevice($hash);
my $resolution = 8; my $resolution = 8;
if (defined $firmata->{metadata}{pwm_resolutions}) { if (defined $firmata->{metadata}{pwm_resolutions}) {
$resolution = $firmata->{metadata}{pwm_resolutions}{$hash->{PIN}} $resolution = $firmata->{metadata}{pwm_resolutions}{$hash->{PIN}}
@ -101,30 +112,43 @@ FRM_PWM_Init($$)
$hash->{".max"} = defined $resolution ? (1<<$resolution)-1 : 255; $hash->{".max"} = defined $resolution ? (1<<$resolution)-1 : 255;
$hash->{".dim"} = 0; $hash->{".dim"} = 0;
$hash->{".toggle"} = "off"; $hash->{".toggle"} = "off";
if (! (defined AttrVal($name,"stateFormat",undef))) { };
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"; $main::attr{$name}{"stateFormat"} = "value";
} }
my $value = ReadingsVal($name,"value",undef); my $value = ReadingsVal($name,"value",undef);
if (defined $value and AttrVal($hash->{NAME},"restoreOnReconnect","on") eq "on") { if (defined $value and AttrVal($hash->{NAME},"restoreOnReconnect","on") eq "on") {
FRM_PWM_Set($hash,$name,"value",$value); FRM_PWM_Set($hash,$name,"value",$value);
} }
main::readingsSingleUpdate($hash,"state","Initialized",1); main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef; return undef;
} }
sub sub FRM_PWM_Set
FRM_PWM_Set($@)
{ {
my ($hash, $name, $cmd, @a) = @_; my ($hash, $name, $cmd, @a) = @_;
return "set command missing" if(!defined($cmd));
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets ); my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
#-- check argument
return SetExtensions($hash, join(" ", keys %sets), $name, $cmd, @a) unless @match == 1; 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 "$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";
@ -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"};
@ -231,17 +252,18 @@ 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,13 +271,12 @@ 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 {
@ -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,29 +336,53 @@ 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>
@ -350,8 +390,8 @@ FRM_PWM_Attr($$$$)
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>
@ -389,26 +429,39 @@ FRM_PWM_Attr($$$$)
</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>
<a name="restoreOnStartup"/>
<li>restoreOnStartup &lt;on|off&gt;</li> <li>restoreOnStartup &lt;on|off&gt;</li>
<a name="restoreOnReconnect"/>
<li>restoreOnReconnect &lt;on|off&gt;</li> <li>restoreOnReconnect &lt;on|off&gt;</li>
<a name="IODev"/>
<li><a href="#IODev">IODev</a><br> <li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
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>
<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>
@ -419,4 +472,15 @@ FRM_PWM_Attr($$$$)
</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) = @_;
@ -59,27 +92,33 @@ FRM_RGB_Initialize($)
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);
@ -109,16 +148,20 @@ FRM_RGB_Init($$)
return undef; return undef;
} }
sub sub FRM_RGB_Set
FRM_RGB_Set($@)
{ {
my ($hash, $name, $cmd, @a) = @_; my ($hash, $name, $cmd, @a) = @_;
return "set command missing" if(!defined($cmd));
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets ); my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
#-- check argument
return SetExtensions($hash, join(" ", keys %sets), $name, $cmd, @a) unless @match == 1; 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 "$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 {
@ -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,7 +221,7 @@ 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;
}; };
@ -196,20 +238,20 @@ 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 {
@ -220,13 +262,11 @@ 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) = @_;
@ -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,18 +316,49 @@ 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>
@ -297,7 +366,7 @@ FRM_RGB_Attr($$$$)
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>
@ -306,7 +375,7 @@ FRM_RGB_Attr($$$$)
</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>
@ -332,8 +401,10 @@ FRM_RGB_Attr($$$$)
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>
@ -349,20 +420,37 @@ FRM_RGB_Attr($$$$)
<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>
<a name="restoreOnStartup"/>
<li>restoreOnStartup &lt;on|off&gt;</li> <li>restoreOnStartup &lt;on|off&gt;</li>
<a name="restoreOnReconnect"/>
<li>restoreOnReconnect &lt;on|off&gt;</li> <li>restoreOnReconnect &lt;on|off&gt;</li>
<a name="IODev"/>
<li><a href="#IODev">IODev</a><br> <li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
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>
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
</ul> </ul>
</ul> </ul><br>
<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;
@ -15,30 +51,28 @@ BEGIN {
}; };
}; };
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,33 +81,48 @@ FRM_ROTENC_Initialize($)
main::LoadModule("FRM"); main::LoadModule("FRM");
} }
sub sub FRM_ROTENC_Define
FRM_ROTENC_Init($$) {
my ($hash, $def) = @_;
# verify define arguments
my $usage = "usage: define <name> FRM_ROTENC pinA pinB [id]";
my @a = split("[ \t]+", $def);
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 ($hash,$args) = @_;
my $u = "wrong syntax: define <name> FRM_ROTENC pinA pinB [id]";
return $u unless defined $args and int(@$args) > 1;
my $pinA = @$args[0];
my $pinB = @$args[1];
my $encoder = defined @$args[2] ? @$args[2] : 0;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
$hash->{PINA} = $pinA; if (defined($main::defs{$name}{IODev_ERROR})) {
$hash->{PINB} = $pinB; return 'Perl module Device::Firmata not properly installed';
}
$hash->{ENCODERNUM} = $encoder;
eval { eval {
FRM_Client_AssignIOPort($hash); FRM_Client_AssignIOPort($hash);
my $firmata = FRM_Client_FirmataDevice($hash); my $firmata = FRM_Client_FirmataDevice($hash);
$firmata->encoder_attach($encoder,$pinA,$pinB); $firmata->encoder_attach($hash->{ENCODERNUM}, $hash->{PINA}, $hash->{PINB});
$firmata->observe_encoder($encoder, \&FRM_ROTENC_observer, $hash ); $firmata->observe_encoder($hash->{ENCODERNUM}, \&FRM_ROTENC_observer, $hash );
}; };
if ($@) { if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/; my $ret = FRM_Catch($@);
$hash->{STATE} = "error initializing: ".$1; readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return "error initializing '$name': $1"; return $ret;
} }
if (! (defined AttrVal($name,"stateFormat",undef))) { if (! (defined AttrVal($name,"stateFormat",undef))) {
@ -83,37 +132,36 @@ FRM_ROTENC_Init($$)
$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: {
$cmd eq "reset" and do {
if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
} }
return "Unknown argument $a[1], choose one of " . join(" ", @commands);
}
COMMAND_HANDLER: {
$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,15 +255,46 @@ 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>
@ -239,13 +303,16 @@ FRM_ROTENC_State($$$$)
</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>
<a name="IODev"/>
<li><a href="#IODev">IODev</a><br> <li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
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>
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
</ul> </ul>
</ul> </ul><br>
<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;
@ -15,16 +51,14 @@ BEGIN {
}; };
}; };
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) = @_;
@ -38,24 +72,39 @@ FRM_SERVO_Initialize($)
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,6 +119,9 @@ 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) {
if (defined($main::defs{$name}{IODev_ERROR})) {
die 'Perl module Device::Firmata not properly installed';
}
$main::attr{$name}{$attribute}=$value; $main::attr{$name}{$attribute}=$value;
FRM_SERVO_apply_attribute($hash,$attribute); FRM_SERVO_apply_attribute($hash,$attribute);
} }
@ -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,28 +147,69 @@ 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>
@ -124,7 +217,7 @@ FRM_SERVO_Set($@)
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>
@ -132,32 +225,52 @@ FRM_SERVO_Set($@)
</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>
<a name="IODev"/>
<li><a href="#IODev">IODev</a><br> <li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
than one FRM-device defined.)
</li> </li>
<a name="min-pulse"/>
<li>min-pulse<br> <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> sets the minimum puls-width to use. Defaults to 544. For most servos this translates into a rotation of 180° counterclockwise.
</li>
<a name="max-pulse"/>
<li>max-pulse<br> <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> sets the maximum puls-width to use. Defaults to 2400. For most servos this translates into a rotation of 180° clockwise.
<li><a href="#eventMap">eventMap</a><br></li> </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> </ul>
</ul> </ul><br>
<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;
@ -15,28 +51,26 @@ BEGIN {
}; };
}; };
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";
@ -46,19 +80,21 @@ FRM_STEPPER_Initialize($)
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 @a = split("[ \t][ \t]*", $def);
my $args = [@a[2..scalar(@a)-1]];
return $usage unless defined $args;
my $driver = shift @$args; my $driver = shift @$args;
return $usage unless ( $driver eq 'DRIVER' or $driver eq 'TWO_WIRE' or $driver eq 'FOUR_WIRE' );
return $u 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));
return $u if (($driver eq 'DRIVER' or $driver eq 'TWO_WIRE') and (scalar(@$args) < 3 or scalar(@$args) > 4)); return $usage if (($driver eq 'FOUR_WIRE') and (scalar(@$args) < 5 or scalar(@$args) > 6));
return $u if (($driver eq 'FOUR_WIRE') and (scalar(@$args) < 5 or scalar(@$args) > 6));
$hash->{DRIVER} = $driver; $hash->{DRIVER} = $driver;
@ -73,12 +109,28 @@ FRM_STEPPER_Init($$)
$hash->{STEPSPERREV} = shift @$args; $hash->{STEPSPERREV} = shift @$args;
$hash->{STEPPERNUM} = 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 { eval {
FRM_Client_AssignIOPort($hash); FRM_Client_AssignIOPort($hash);
my $firmata = FRM_Client_FirmataDevice($hash); my $firmata = FRM_Client_FirmataDevice($hash);
$firmata->stepper_config( $firmata->stepper_config(
$hash->{STEPPERNUM}, $hash->{STEPPERNUM},
$driver, $hash->{DRIVER},
$hash->{STEPSPERREV}, $hash->{STEPSPERREV},
$hash->{PIN1}, $hash->{PIN1},
$hash->{PIN2}, $hash->{PIN2},
@ -87,26 +139,28 @@ FRM_STEPPER_Init($$)
$firmata->observe_stepper(0, \&FRM_STEPPER_observer, $hash ); $firmata->observe_stepper(0, \&FRM_STEPPER_observer, $hash );
}; };
if ($@) { if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/; my $ret = FRM_Catch($@);
$hash->{STATE} = "error initializing: ".$1; readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return "error initializing '".$hash->{NAME}."': ".$1; return $ret;
} }
$hash->{POSITION} = 0; $hash->{POSITION} = 0;
$hash->{DIRECTION} = 0; $hash->{DIRECTION} = 0;
$hash->{STEPS} = 0; $hash->{STEPS} = 0;
if (! (defined AttrVal($hash->{NAME},"stateFormat",undef))) { if (! (defined AttrVal($name,"stateFormat",undef))) {
$main::attr{$hash->{NAME}}{"stateFormat"} = "position"; $main::attr{$name}{"stateFormat"} = "position";
} }
main::readingsSingleUpdate($hash,"state","Initialized",1); main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef; return undef;
} }
sub sub FRM_STEPPER_observer
FRM_STEPPER_observer
{ {
my ( $stepper, $hash ) = @_; my ( $stepper, $hash ) = @_;
my $name = $hash->{NAME}; 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; 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}; my $position = $hash->{DIRECTION} ? $hash->{POSITION} - $hash->{STEPS} : $hash->{POSITION} + $hash->{STEPS};
$hash->{POSITION} = $position; $hash->{POSITION} = $position;
$hash->{DIRECTION} = 0; $hash->{DIRECTION} = 0;
@ -114,30 +168,27 @@ FRM_STEPPER_observer
main::readingsSingleUpdate($hash,"position",$position,1); main::readingsSingleUpdate($hash,"position",$position,1);
} }
sub sub FRM_STEPPER_Set
FRM_STEPPER_Set
{ {
my ($hash, @a) = @_; my ($hash, $name, $cmd, @a) = @_;
return "Need at least one parameters" if(@a < 2);
shift @a; return "set command missing" if(!defined($cmd));
my $name = $hash->{NAME}; my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
my $command = shift @a; return "unknown set command '$cmd', choose one of " . join(" ", sort keys %sets) if ($cmd eq '?' || @match == 0);
if(!defined($sets{$command})) { return "$cmd requires (at least) $sets{$match[0]} argument(s)" unless (@a >= $sets{$match[0]});
my @commands = ();
foreach my $key (sort keys %sets) { my $value = shift @a;
push @commands, $sets{$key} ? $key.":".join(",",$sets{$key}) : $key; SETHANDLER: {
} $cmd eq "reset" and do {
return "Unknown argument $command, choose one of " . join(" ", @commands);
}
COMMAND_HANDLER: {
$command 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,31 +224,42 @@ 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);
@ -201,8 +269,8 @@ STATEHANDLER: {
} }
} }
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,21 +287,55 @@ 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>
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_STEPPERdefine"></a> <a name="FRM_STEPPERdefine"></a>
@ -243,91 +345,125 @@ FRM_STEPPER_Attr($$$$) {
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
direction to turn, the other triggers one step per impluse.
</li>
<li>FOUR_WIRE: motor is attached via four wires each driving one coil individually.</li> <li>FOUR_WIRE: motor is attached via four wires each driving one coil individually.</li>
<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>TWO_WIRE: motor is attached via two wires. This mode makes use of the fact that at any time two of
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> the four motor 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>
</ul> </ul>
</li> </li>
<li> <li>
<ul> <ul>
<li>The sequence of control signals for 4 control wires is as follows:<br> <li>The sequence of control signals for 4 control wires is as follows:<br><br>
<br>
<code> <code>
Step C0 C1 C2 C3<br> Step C0 C1 C2 C3<br>
1 1 0 1 0<br> 1 1 0 1 0<br>
2 0 1 1 0<br> 2 0 1 1 0<br>
3 0 1 0 1<br> 3 0 1 0 1<br>
4 1 0 0 1<br> 4 1 0 0 1<br>
</code> </code>
</li> </li>
<li>The sequence of controls signals for 2 control wires is as follows:<br> <li>The sequence of controls signals for 2 control wires is as follows:<br>
(columns C1 and C2 from above):<br> (columns C1 and C2 from above):<br><br>
<br>
<code> <code>
Step C0 C1<br> Step C0 C1<br>
1 0 1<br> 1 0 1<br>
2 1 1<br> 2 1 1<br>
3 1 0<br> 3 1 0<br>
4 0 0<br> 4 0 0<br>
</code> </code>
</li> </li>
</ul> </ul>
</li> </li>
<li> <li>
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> If your stepper-motor does not move or does move but only in a single direction you will have to rearrage
that can be archived either by rearranging the physical connections, or by mapping the connection to the pin-definitions in FRM_STEPPERS define:<br> the pin-numbers to match the control sequence. That can be archived either by rearranging the physical
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> connections, or by mapping the connection to the pin-definitions in FRM_STEPPERS define:<br>
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>
<code>define stepper FRM_STEPPER FOUR_WIRE 7 5 6 8 64 0</code><br> <code>define stepper FRM_STEPPER FOUR_WIRE 7 5 6 8 64 0</code><br>
when being connected to the arduio with:<br> when being connected to the arduio with:<br><br>
<code>motor pin1 <-> arduino pin5<br>
<code>
motor pin1 <-> arduino pin5<br>
motor pin2 <-> arduino pin6<br> motor pin2 <-> arduino pin6<br>
motor pin3 <-> arduino pin7<br> motor pin3 <-> arduino pin7<br>
motor pin4 <-> arduino pin8<br> motor pin4 <-> arduino pin8<br>
motor pin5 <-> ground</code><br> motor pin5 <-> ground
</li> </code>
</ul> </li><br>
</ul><br>
<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.
At 2048 steps per revolution (28byj-48) a speed of 30 results in 3 rev/min<br>
acceleration and deceleration are optional.<br> acceleration and deceleration are optional.<br>
</li> </li><br>
<br>
<code>set &lt;name&gt; step &lt;stepstomove&gt; [speed] [accel] [decel]</code> <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> <li>moves the motor the number of steps specified. positive or negative integer<br>
speed, accelleration and deceleration are optional.<br> speed, accelleration and deceleration are optional.<br>
</li> </li>
</ul> </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>
<a name="restoreOnStartup"/>
<li>restoreOnStartup &lt;on|off&gt;</li> <li>restoreOnStartup &lt;on|off&gt;</li>
<a name="restoreOnReconnect"/>
<li>restoreOnReconnect &lt;on|off&gt;</li> <li>restoreOnReconnect &lt;on|off&gt;</li>
<a name="IODev"/>
<li><a href="#IODev">IODev</a><br> <li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
than one FRM-device defined.)
</li> </li>
<a name="speed"/>
<li>>speed (same meaning as in 'set position')</li> <li>>speed (same meaning as in 'set position')</li>
<a name="acceleration"/>
<li>acceleration (same meaning as in 'set position')</li> <li>acceleration (same meaning as in 'set position')</li>
<a name="deceleration"/>
<li>deceleration (same meaning as in 'set position')</li> <li>deceleration (same meaning as in 'set position')</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> </ul>
</ul> </ul><br>
<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