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

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

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

View File

@ -1,5 +1,8 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
# 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)
- feature: 49_Arlo: Added 2-factor authentication
- bugfix: 73_AutoShuttersControl: fix IsDay Fn for weekend condition

View File

@ -1,37 +1,42 @@
########################################################################################
#
# $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;
use strict;
@ -39,26 +44,24 @@ use warnings;
#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
BEGIN {
if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib";
};
};
if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib";
};
};
};
use Device::Firmata::Constants qw/ :all /;
#####################################
# get command default return values
my %gets = (
"reading" => "",
"state" => "",
"alarm-upper-threshold" => "off",
"alarm-lower-threshold" => "off",
"reading" => "",
"state" => "",
"alarm-upper-threshold" => "off",
"alarm-lower-threshold" => "off",
);
sub
FRM_AD_Initialize($)
sub FRM_AD_Initialize
{
my ($hash) = @_;
@ -66,83 +69,102 @@ FRM_AD_Initialize($)
$hash->{GetFn} = "FRM_AD_Get";
$hash->{DefFn} = "FRM_Client_Define";
$hash->{InitFn} = "FRM_AD_Init";
$hash->{AttrList} = "IODev upper-threshold lower-threshold $main::readingFnAttributes";
main::LoadModule("FRM");
}
sub
FRM_AD_Init($$)
sub FRM_AD_Init
{
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 $resolution = 10;
if (defined $firmata->{metadata}{analog_resolutions}) {
$resolution = $firmata->{metadata}{analog_resolutions}{$hash->{PIN}}
}
$hash->{resolution} = $resolution;
$hash->{".max"} = defined $resolution ? (1<<$resolution)-1 : 1024;
eval {
$firmata->observe_analog($hash->{PIN},\&FRM_AD_observer,$hash);
};
return FRM_Catch($@) if $@;
if (! (defined AttrVal($name,"stateFormat",undef))) {
$main::attr{$name}{"stateFormat"} = "reading";
}
if (! (defined AttrVal($name,"event-min-interval",undef))) {
$main::attr{$name}{"event-min-interval"} = 5;
}
main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
my ($hash,$args) = @_;
my $name = $hash->{NAME};
if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
}
my $ret = FRM_Init_Pin_Client($hash, $args, Device::Firmata::Constants->PIN_ANALOG);
if (defined($ret)) {
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
eval {
my $firmata = FRM_Client_FirmataDevice($hash);
my $resolution = 10;
if (defined $firmata->{metadata}{analog_resolutions}) {
$resolution = $firmata->{metadata}{analog_resolutions}{$hash->{PIN}}
}
$hash->{resolution} = $resolution;
$hash->{".max"} = defined $resolution ? (1<<$resolution)-1 : 1024;
$firmata->observe_analog($hash->{PIN},\&FRM_AD_observer,$hash);
};
if ($@) {
$ret = FRM_Catch($@);
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
if (!(defined AttrVal($name,"stateFormat",undef))) {
$main::attr{$name}{"stateFormat"} = "reading";
}
if (!(defined AttrVal($name,"event-min-interval",undef))) {
$main::attr{$name}{"event-min-interval"} = 5;
}
main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
}
sub
FRM_AD_observer
sub FRM_AD_observer
{
my ($pin,$old,$new,$hash) = @_;
my $name = $hash->{NAME};
Log3 $name,5,"onAnalogMessage for pin ".$pin.", old: ".(defined $old ? $old : "--").", new: ".(defined $new ? $new : "--");
main::readingsBeginUpdate($hash);
main::readingsBulkUpdate($hash,"reading",$new,1);
my $upperthresholdalarm = ReadingsVal($name,"alarm-upper-threshold","off");
if ( $new < AttrVal($name,"upper-threshold",$hash->{".max"}) ) {
if ( $upperthresholdalarm eq "on" ) {
main::readingsBulkUpdate($hash,"alarm-upper-threshold","off",1);
}
my $lowerthresholdalarm = ReadingsVal($name,"alarm-lower-threshold","off");
if ( $new > AttrVal($name,"lower-threshold",-1) ) {
if ( $lowerthresholdalarm eq "on" ) {
main::readingsBulkUpdate($hash,"alarm-lower-threshold","off",1);
}
} else {
if ( $lowerthresholdalarm eq "off" ) {
main::readingsBulkUpdate($hash,"alarm-lower-threshold","on",1);
}
}
} else {
if ( $upperthresholdalarm eq "off" ) {
main::readingsBulkUpdate($hash,"alarm-upper-threshold","on",1);
}
};
main::readingsEndUpdate($hash,1);
my ($pin,$old,$new,$hash) = @_;
my $name = $hash->{NAME};
Log3 $name, 5, "$name: observer pin: ".$pin.", old: ".(defined $old ? $old : "--").", new: ".(defined $new ? $new : "--");
main::readingsBeginUpdate($hash);
main::readingsBulkUpdate($hash,"reading",$new,1);
my $upperthresholdalarm = ReadingsVal($name,"alarm-upper-threshold","off");
if ( $new < AttrVal($name,"upper-threshold",$hash->{".max"}) ) {
if ( $upperthresholdalarm eq "on" ) {
main::readingsBulkUpdate($hash,"alarm-upper-threshold","off",1);
}
my $lowerthresholdalarm = ReadingsVal($name,"alarm-lower-threshold","off");
if ( $new > AttrVal($name,"lower-threshold",-1) ) {
if ( $lowerthresholdalarm eq "on" ) {
main::readingsBulkUpdate($hash,"alarm-lower-threshold","off",1);
}
} else {
if ( $lowerthresholdalarm eq "off" ) {
main::readingsBulkUpdate($hash,"alarm-lower-threshold","on",1);
}
}
} else {
if ( $upperthresholdalarm eq "off" ) {
main::readingsBulkUpdate($hash,"alarm-upper-threshold","on",1);
}
};
main::readingsEndUpdate($hash,1);
}
sub
FRM_AD_Get($)
sub FRM_AD_Get
{
my ($hash,@a) = @_;
my $name = shift @a;
my $cmd = shift @a;
my $ret;
my ($hash, $name, $cmd, @a) = @_;
return "get command missing" if(!defined($cmd));
return "unknown get command '$cmd', choose one of " . join(":noArg ", sort keys %gets) . ":noArg" if(!defined($gets{$cmd}));
ARGUMENT_HANDLER: {
$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 $@;
return FRM_Catch($@) if ($@);
return $result;
};
( $cmd eq "alarm-upper-threshold" or $cmd eq "alarm-lower-threshold" or $cmd eq "state" ) and do {
return main::ReadingsVal($name,"count",$gets{$cmd});
@ -151,8 +173,8 @@ FRM_AD_Get($)
return undef;
}
sub
FRM_AD_Attr($$$$) {
sub FRM_AD_Attr
{
my ($command,$name,$attribute,$value) = @_;
my $hash = $main::defs{$name};
eval {
@ -169,9 +191,9 @@ FRM_AD_Attr($$$$) {
}
};
if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/;
$hash->{STATE} = "error setting $attribute to $value: ".$1;
return "cannot $command attribute $attribute to $value for $name: ".$1;
my $ret = FRM_Catch($@);
$hash->{STATE} = "$command $attribute error: " . $ret;
return $hash->{STATE};
}
}
@ -179,46 +201,65 @@ FRM_AD_Attr($$$$) {
=pod
CHANGES
=head1 CHANGES
2016 jensb
o modified sub FRM_AD_Init to catch exceptions and return error message
19.01.2018 jensb
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
=pod
=head1 FHEM COMMANDREF METADATA
=over
=item device
=item summary Firmata: analog input
=item summary_DE Firmata: analog Eingang
=item summary_DE Firmata: analoger Eingang
=back
=head1 INSTALLATION AND CONFIGURATION
=begin html
<a name="FRM_AD"></a>
<a name="FRM_AD"/>
<h3>FRM_AD</h3>
<ul>
This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a>
This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a>
that should be configured as an analog input.<br><br>
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>
<a name="FRM_ADdefine"></a>
of the FRM device (after connecting to the Firmata device) to be used as analog input.<br><br>
<a name="FRM_ADdefine"/>
<b>Define</b>
<ul>
<code>define &lt;name&gt; FRM_AD &lt;pin&gt;</code><br><br>
Defines the FRM_AD device. &lt;pin&gt; is the arduino-pin to use.
<code>define &lt;name&gt; FRM_AD &lt;pin&gt;</code><br><br>
Defines the FRM_AD device. &lt;pin&gt; is the arduino-pin to use.
</ul><br>
<a name="FRM_ADset"></a>
<a name="FRM_ADset"/>
<b>Set</b><br>
<ul>
N/A<br>
</ul><br>
<a name="FRM_ADget"></a>
<a name="FRM_ADget"/>
<b>Get</b><br>
<ul>
<li>reading<br>
@ -236,35 +277,52 @@ FRM_AD_Attr($$$$) {
<li>state<br>
returns the 'state' reading</li>
</ul><br>
<a name="FRM_ADattr"></a>
<a name="FRM_ADattr"/>
<b>Attributes</b><br>
<ul>
<li>upper-threshold<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>
Defaults to the max pin resolution plus one.</li>
<li>lower-threshold<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>
Defaults to -1.</li>
<li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
than one FRM-device defined.)
</li>
<li><a href="#eventMap">eventMap</a><br></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
<a name="upper-threshold"/>
<li>upper-threshold<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>
Defaults to the max pin resolution plus one.</li>
<a name="lower-threshold"/>
<li>lower-threshold<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>
Defaults to -1.</li>
<a name="IODev"/>
<li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
</li>
<li><a href="#attributes">global attributes</a></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
</ul><br>
<a name="FRM_ADnotes"></a>
<a name="FRM_ADnotes"/>
<b>Notes</b><br>
<ul>
<li>attribute <i>stateFormat</i><br>
In most cases it is a good idea to assign "reading" to the attribute <i>stateFormat</i>. This will show the
current value of the pin in the web interface.
</li>
</ul>
<li>attribute <i>stateFormat</i><br>
In most cases it is a good idea to assign "reading" to the attribute <i>stateFormat</i>. This will show the
current value of the pin in the web interface.
</li>
</ul>
</ul><br>
=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

View File

@ -1,37 +1,42 @@
########################################################################################
#
# $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;
use strict;
@ -39,22 +44,22 @@ use warnings;
#add FHEM/lib to @INC if it's not already included. Should rather be in fhem.pl than here though...
BEGIN {
if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib";
};
};
if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib";
};
};
};
use Device::Firmata::Constants qw/ :all /;
#####################################
# default values for Attr
my %sets = (
"alarm" => "",
"count" => 0,
);
# default values for Get
my %gets = (
"reading" => "",
"state" => "",
@ -62,8 +67,7 @@ my %gets = (
"alarm" => "off"
);
sub
FRM_IN_Initialize($)
sub FRM_IN_Initialize
{
my ($hash) = @_;
@ -78,8 +82,7 @@ FRM_IN_Initialize($)
main::LoadModule("FRM");
}
sub
FRM_IN_PinModePullupSupported($)
sub FRM_IN_PinModePullupSupported
{
my ($hash) = @_;
my $iodev = $hash->{IODev};
@ -88,133 +91,160 @@ FRM_IN_PinModePullupSupported($)
return defined($pullupPins);
}
sub
FRM_IN_Init($$)
sub FRM_IN_Init
{
my ($hash,$args) = @_;
if (FRM_IN_PinModePullupSupported($hash)) {
my $pullup = AttrVal($hash->{NAME},"internal-pullup","off");
my $ret = FRM_Init_Pin_Client($hash,$args,defined($pullup) && ($pullup eq "on")? PIN_PULLUP : PIN_INPUT);
return $ret if (defined $ret);
eval {
my $firmata = FRM_Client_FirmataDevice($hash);
my $pin = $hash->{PIN};
$firmata->observe_digital($pin,\&FRM_IN_observer,$hash);
};
return FRM_Catch($@) if $@;
} else {
my $ret = FRM_Init_Pin_Client($hash,$args,PIN_INPUT);
return $ret if (defined $ret);
eval {
my $firmata = FRM_Client_FirmataDevice($hash);
my $pin = $hash->{PIN};
if (defined (my $pullup = AttrVal($hash->{NAME},"internal-pullup",undef))) {
$firmata->digital_write($pin,$pullup eq "on" ? 1 : 0);
}
$firmata->observe_digital($pin,\&FRM_IN_observer,$hash);
};
return FRM_Catch($@) if $@;
}
if (! (defined AttrVal($hash->{NAME},"stateFormat",undef))) {
$main::attr{$hash->{NAME}}{"stateFormat"} = "reading";
}
main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
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)) {
my $pullup = AttrVal($name, "internal-pullup", "off");
my $ret = FRM_Init_Pin_Client($hash,$args,defined($pullup) && ($pullup eq "on")? Device::Firmata::Constants->PIN_PULLUP : Device::Firmata::Constants->PIN_INPUT);
return $ret if (defined $ret);
eval {
my $firmata = FRM_Client_FirmataDevice($hash);
my $pin = $hash->{PIN};
$firmata->observe_digital($pin,\&FRM_IN_observer,$hash);
};
if ($@) {
my $ret = FRM_Catch($@);
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
} else {
my $ret = FRM_Init_Pin_Client($hash, $args, Device::Firmata::Constants->PIN_INPUT);
return $ret if (defined $ret);
eval {
my $firmata = FRM_Client_FirmataDevice($hash);
my $pin = $hash->{PIN};
if (defined(my $pullup = AttrVal($name, "internal-pullup", undef))) {
$firmata->digital_write($pin,$pullup eq "on" ? 1 : 0);
}
$firmata->observe_digital($pin,\&FRM_IN_observer,$hash);
};
if ($@) {
my $ret = FRM_Catch($@);
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
}
if (!(defined AttrVal($name, "stateFormat", undef))) {
$main::attr{$name}{"stateFormat"} = "reading";
}
main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
}
sub
FRM_IN_observer($$$$)
sub FRM_IN_observer
{
my ($pin,$last,$new,$hash) = @_;
my $name = $hash->{NAME};
my $old = ReadingsVal($name, "reading", undef);
if (defined($old)) {
$old = $old eq "on" ? PIN_HIGH : PIN_LOW;
}
if (AttrVal($hash->{NAME},"activeLow","no") eq "yes") {
$new = $new == PIN_LOW ? PIN_HIGH : PIN_LOW;
}
Log3 $name, 5, "$name observer pin: $pin, old: ".(defined $old ? $old : "--").", new: ".(defined $new ? $new : "--");
my $changed = !defined($old) || $old != $new;
if ($changed) {
main::readingsBeginUpdate($hash);
if (defined (my $mode = main::AttrVal($name,"count-mode",undef))) {
if (($mode eq "both")
or (($mode eq "rising") and ($new == PIN_HIGH))
or (($mode eq "falling") and ($new == PIN_LOW))) {
my $count = main::ReadingsVal($name,"count",0);
$count++;
if (defined (my $threshold = main::AttrVal($name,"count-threshold",undef))) {
if ( $count > $threshold ) {
if (AttrVal($name,"reset-on-threshold-reached","no") eq "yes") {
$count=0;
main::readingsBulkUpdate($hash,"alarm","on",1);
} elsif ( main::ReadingsVal($name,"alarm","off") ne "on" ) {
main::readingsBulkUpdate($hash,"alarm","on",1);
}
}
}
main::readingsBulkUpdate($hash,"count",$count,1);
}
};
main::readingsBulkUpdate($hash,"reading",$new == PIN_HIGH ? "on" : "off", 1);
main::readingsEndUpdate($hash,1);
}
my ($pin,$last,$new,$hash) = @_;
my $name = $hash->{NAME};
my $old = ReadingsVal($name, "reading", undef);
if (defined($old)) {
$old = $old eq "on" ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW;
}
if (AttrVal($hash->{NAME},"activeLow","no") eq "yes") {
$new = $new == Device::Firmata::Constants->PIN_LOW ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW;
}
Log3 $name, 5, "$name: observer pin: $pin, old: ".(defined $old ? $old : "--").", new: ".(defined $new ? $new : "--");
my $changed = !defined($old) || $old != $new;
if ($changed) {
main::readingsBeginUpdate($hash);
if (defined (my $mode = main::AttrVal($name,"count-mode",undef))) {
if (($mode eq "both")
or (($mode eq "rising") and ($new == Device::Firmata::Constants->PIN_HIGH))
or (($mode eq "falling") and ($new == Device::Firmata::Constants->PIN_LOW))) {
my $count = main::ReadingsVal($name,"count",0);
$count++;
if (defined (my $threshold = main::AttrVal($name,"count-threshold",undef))) {
if ( $count > $threshold ) {
if (AttrVal($name,"reset-on-threshold-reached","no") eq "yes") {
$count=0;
main::readingsBulkUpdate($hash,"alarm","on",1);
} elsif ( main::ReadingsVal($name,"alarm","off") ne "on" ) {
main::readingsBulkUpdate($hash,"alarm","on",1);
}
}
}
main::readingsBulkUpdate($hash,"count",$count,1);
}
};
main::readingsBulkUpdate($hash, "reading", $new == Device::Firmata::Constants->PIN_HIGH ? "on" : "off", 1);
main::readingsEndUpdate($hash,1);
}
}
sub
FRM_IN_Set($@)
sub FRM_IN_Set
{
my ($hash, @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]}));
my $command = $a[1];
my $value = $a[2];
my ($hash, $name, $cmd, @a) = @_;
return "set command missing" if(!defined($cmd));
return "unknown set command '$cmd', choose one of " . join(" ", sort keys %sets) if(!defined($sets{$cmd}));
return "$cmd requires 1 argument" unless (@a == 1);
my $value = shift @a;
COMMAND_HANDLER: {
$command eq "alarm" and do {
$cmd eq "alarm" and do {
return undef if (!($value eq "off" or $value eq "on"));
main::readingsSingleUpdate($hash,"alarm",$value,1);
last;
};
$command eq "count" and do {
$cmd eq "count" and do {
main::readingsSingleUpdate($hash,"count",$value,1);
last;
};
}
}
sub
FRM_IN_Get($@)
sub FRM_IN_Get
{
my ($hash, @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]}));
my $name = shift @a;
my $cmd = shift @a;
my ($hash, $name, $cmd, @a) = @_;
return "get command missing" if(!defined($cmd));
return "unknown get command '$cmd', choose one of " . join(":noArg ", sort keys %gets) . ":noArg" if(!defined($gets{$cmd}));
ARGUMENT_HANDLER: {
( $cmd eq "reading" ) and do {
$cmd eq "reading" and do {
my $last;
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});
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 $@;
return $last == PIN_HIGH ? "on" : "off";
if ($@) {
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 undef;
}
sub
FRM_IN_Attr($$$$) {
sub FRM_IN_Attr
{
my ($command,$name,$attribute,$value) = @_;
my $hash = $main::defs{$name};
my $pin = $hash->{PIN};
eval {
if ($command eq "set") {
ARGUMENT_HANDLER: {
@ -225,12 +255,14 @@ FRM_IN_Attr($$$$) {
}
last;
};
$attribute eq "count-mode" and do {
if ($value ne "none" and !defined main::ReadingsVal($name,"count",undef)) {
main::readingsSingleUpdate($main::defs{$name},"count",$sets{count},1);
}
last;
};
$attribute eq "reset-on-threshold-reached" and do {
if ($value eq "yes"
and defined (my $threshold = main::AttrVal($name,"count-threshold",undef))) {
@ -240,6 +272,7 @@ FRM_IN_Attr($$$$) {
}
last;
};
$attribute eq "count-threshold" and do {
if (main::ReadingsVal($name,"count",0) > $value) {
main::readingsBeginUpdate($hash);
@ -253,11 +286,15 @@ FRM_IN_Attr($$$$) {
}
last;
};
$attribute eq "internal-pullup" and do {
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);
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 {
$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.
@ -265,11 +302,15 @@ FRM_IN_Attr($$$$) {
}
last;
};
$attribute eq "activeLow" and do {
my $oldval = AttrVal($hash->{NAME},"activeLow","no");
if ($oldval ne $value) {
$main::attr{$hash->{NAME}}{activeLow} = $value;
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);
FRM_IN_observer($pin,undef,$firmata->digital_read($pin),$hash);
}
@ -280,16 +321,23 @@ FRM_IN_Attr($$$$) {
} elsif ($command eq "del") {
ARGUMENT_HANDLER: {
$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);
if (FRM_IN_PinModePullupSupported($hash)) {
$firmata->pin_mode($pin,PIN_INPUT);
$firmata->pin_mode($pin, Device::Firmata::Constants->PIN_INPUT);
} else {
$firmata->digital_write($pin,0);
}
last;
};
$attribute eq "activeLow" and do {
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};
my $firmata = FRM_Client_FirmataDevice($hash);
FRM_IN_observer($pin,undef,$firmata->digital_read($pin),$hash);
@ -299,9 +347,10 @@ FRM_IN_Attr($$$$) {
}
}
};
if (my $error = FRM_Catch($@)) {
$hash->{STATE} = "error setting $attribute to $value: ".$error;
return "cannot $command attribute $attribute to $value for $name: ".$error;
if ($@) {
my $ret = FRM_Catch($@);
$hash->{STATE} = "$command $attribute error: " . $ret;
return $hash->{STATE};
}
}
@ -309,11 +358,11 @@ FRM_IN_Attr($$$$) {
=pod
CHANGES
=head1 CHANGES
15.02.2019 jensb
o bugfix: change detection no longer assumes that reading "reading" is defined
04.11.2018 jensb
o bugfix: get alarm/reading/state
o feature: remove unused FHEMWEB input field from all get commands
@ -324,8 +373,17 @@ FRM_IN_Attr($$$$) {
03.01.2018 jensb
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
=pod
=head1 FHEM COMMANDREF METADATA
@ -344,7 +402,7 @@ FRM_IN_Attr($$$$) {
=begin html
<a name="FRM_IN"></a>
<a name="FRM_IN"/>
<h3>FRM_IN</h3>
<ul>
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>
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>
<ul>
<code>define &lt;name&gt; FRM_IN &lt;pin&gt;</code> <br>
@ -362,7 +420,7 @@ FRM_IN_Attr($$$$) {
</ul>
<br>
<a name="FRM_INset"></a>
<a name="FRM_INset"/>
<b>Set</b><br>
<ul>
<li>alarm on|off<br>
@ -373,47 +431,64 @@ FRM_IN_Attr($$$$) {
The counter is incremented depending on the attribute 'count-mode'.</li>
</ul><br>
<a name="FRM_INget"></a>
<a name="FRM_INget"/>
<b>Get</b>
<ul>
<li>reading<br>
returns the logical state of the input pin last received from the Firmata device depending on the attribute 'activeLow'.
Values are 'on' and 'off'.<br></li>
<li>count<br>
returns the current counter value. Contains the number of toggles reported by the Fimata device on this input pin.
returns the current counter value. Contains the number of toggles reported by the Fimata device on this input pin.
Depending on the attribute 'count-mode' every rising or falling edge (or both) is counted.</li>
<li>alarm<br>
returns the 'alarm' reading. Values are 'on' and 'off' (Defaults to 'off').
returns the 'alarm' reading. Values are 'on' and 'off' (Defaults to 'off').
The 'alarm' reading doesn't clear itself, it has to be set to 'off' explicitly.</li>
<li>state<br>
returns the 'state' reading</li>
</ul><br>
<a name="FRM_INattr"></a>
<a name="FRM_INattr"/>
<b>Attributes</b><br>
<ul>
<li>activeLow yes|no<br>
inverts the logical state of the pin reading if set to yes (defaults to 'no').</li>
<li>count-mode none|rising|falling|both<br>
Determines whether 'rising' (transitions from 'off' to 'on') of falling (transitions from 'on' to 'off')
edges (or 'both') are counted (defaults to 'none').</li>
<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
set to 'on' (defaults to undefined). Use 'set alarm off' to clear the alarm.</li>
<li>reset-on-threshold-reached yes|no<br>
if set to 'yes' reset the counter to 0 when the threshold is reached (defaults to 'no').
</li>
<li>internal-pullup on|off<br>
enables/disables the internal pullup resistor of the Firmata pin (defaults to 'off'). Requires hardware and firmware support.
</li>
<li><a href="#IODev">IODev</a><br>
specify which <a href="#FRM">FRM</a> to use.
</li>
<li><a href="#eventMap">eventMap</a><br></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
<a name="activeLow"/>
<li>activeLow yes|no<br>
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>
Determines whether 'rising' (transitions from 'off' to 'on') of falling (transitions from 'on' to 'off')
edges (or 'both') are counted (defaults to 'none').
</li>
<a name="count-threshold"/>
<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 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>
if set to 'yes' reset the counter to 0 when the threshold is reached (defaults to 'no').
</li>
<a name="internal-pullup"/>
<li>internal-pullup on|off<br>
enables/disables the internal pullup resistor of the Firmata pin (defaults to 'off'). Requires hardware
and firmware support.
</li>
<a name="IODev"/>
<li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
</li>
<li><a href="#attributes">global attributes</a></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
</ul><br>
<a name="FRM_INnotes"></a>
<a name="FRM_INnotes"/>
<b>Notes</b><br>
<ul>
<li>attribute <i>stateFormat</i><br>
@ -430,7 +505,7 @@ FRM_IN_Attr($$$$) {
=begin html_DE
<a name="FRM_IN"></a>
<a name="FRM_IN"/>
<h3>FRM_IN</h3>
<ul>
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$
#
# 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;
use strict;
@ -39,19 +44,24 @@ use warnings;
#add FHEM/lib to @INC if it's not already included. Should rather be in fhem.pl than here though...
BEGIN {
if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib";
};
};
if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib";
};
};
};
use Device::Firmata::Constants qw/ :all /;
use SetExtensions;
#####################################
sub
FRM_OUT_Initialize($)
# number of arguments
my %sets = (
"on:noArg" => 0,
"off:noArg" => 0,
);
sub FRM_OUT_Initialize
{
my ($hash) = @_;
@ -61,96 +71,127 @@ FRM_OUT_Initialize($)
$hash->{UndefFn} = "FRM_Client_Undef";
$hash->{AttrFn} = "FRM_OUT_Attr";
$hash->{StateFn} = "FRM_OUT_State";
$hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off activeLow:yes,no IODev valueMode:send,receive,bidirectional $main::readingFnAttributes";
main::LoadModule("FRM");
}
sub
FRM_OUT_Init($$)
sub FRM_OUT_Init
{
my ($hash,$args) = @_;
my $ret = FRM_Init_Pin_Client($hash,$args,PIN_OUTPUT);
return $ret if (defined $ret);
eval {
my $firmata = FRM_Client_FirmataDevice($hash);
my $pin = $hash->{PIN};
$firmata->observe_digital($pin,\&FRM_OUT_observer,$hash);
};
my $name = $hash->{NAME};
if (! (defined AttrVal($name,"stateFormat",undef))) {
$main::attr{$name}{"stateFormat"} = "value";
}
my $value = ReadingsVal($name,"value",undef);
if (!defined($value)) {
readingsSingleUpdate($hash,"value","off",0);
}
if (AttrVal($hash->{NAME},"restoreOnReconnect", "on") eq "on") {
FRM_OUT_Set($hash,$name,$value);
}
main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
my ($hash,$args) = @_;
my $name = $hash->{NAME};
if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
}
my $ret = FRM_Init_Pin_Client($hash, $args, Device::Firmata::Constants->PIN_OUTPUT);
if (defined($ret)) {
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
eval {
my $firmata = FRM_Client_FirmataDevice($hash);
my $pin = $hash->{PIN};
$firmata->observe_digital($pin,\&FRM_OUT_observer,$hash);
};
if ($@) {
$ret = FRM_Catch($@);
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
if (!(defined AttrVal($name,"stateFormat",undef))) {
$main::attr{$name}{"stateFormat"} = "value";
}
my $value = ReadingsVal($name,"value",undef);
if (!defined($value)) {
readingsSingleUpdate($hash,"value","off",0);
}
if (AttrVal($name, "restoreOnReconnect", "on") eq "on") {
FRM_OUT_Set($hash,$name,$value);
}
main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
}
sub
FRM_OUT_observer($$$$)
sub FRM_OUT_observer
{
my ($pin,$old,$new,$hash) = @_;
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") {
$old = $old == PIN_LOW ? PIN_HIGH : PIN_LOW if (defined $old);
$new = $new == PIN_LOW ? PIN_HIGH : PIN_LOW;
$old = $old == Device::Firmata::Constants->PIN_LOW ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW if (defined $old);
$new = $new == Device::Firmata::Constants->PIN_LOW ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW;
}
my $changed = !defined($old) || ($old != $new);
if ($changed && (AttrVal($hash->{NAME}, "valueMode", "send") ne "send")) {
main::readingsSingleUpdate($hash, "value", $new == PIN_HIGH? "on" : "off", 1);
main::readingsSingleUpdate($hash, "value", $new == Device::Firmata::Constants->PIN_HIGH? "on" : "off", 1);
}
}
sub
FRM_OUT_Set($$$)
sub FRM_OUT_Set
{
my ($hash, $name, $cmd, @a) = @_;
my $value;
my $invert = AttrVal($hash->{NAME},"activeLow", "no");
if (defined ($cmd)) {
if ($cmd eq "on") {
$value = $invert eq "yes" ? PIN_LOW : PIN_HIGH;
} elsif ($cmd eq "off") {
$value = $invert eq "yes" ? PIN_HIGH : PIN_LOW;
} else {
my $list = "on off";
return SetExtensions($hash, $list, $name, $cmd, @a);
}
eval {
FRM_Client_FirmataDevice($hash)->digital_write($hash->{PIN},$value);
if (AttrVal($hash->{NAME}, "valueMode", "send") ne "receive") {
main::readingsSingleUpdate($hash,"value",$cmd, 1);
}
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
return SetExtensions($hash, join(" ", keys %sets), $name, $cmd, @a) unless @match == 1;
return "$cmd requires $sets{$match[0]} arguments" unless (@a == $sets{$match[0]});
if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
}
my $value = Device::Firmata::Constants->PIN_LOW;
my $invert = AttrVal($hash->{NAME}, "activeLow", "no");
SETHANDLER: {
$cmd eq "on" and do {
$value = $invert eq "yes" ? Device::Firmata::Constants->PIN_LOW : Device::Firmata::Constants->PIN_HIGH;
last;
};
return $@;
} else {
return "no command specified";
$cmd eq "off" and do {
$value = $invert eq "yes" ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW;
last;
};
};
eval {
FRM_Client_FirmataDevice($hash)->digital_write($hash->{PIN},$value);
if (AttrVal($hash->{NAME}, "valueMode", "send") ne "receive") {
main::readingsSingleUpdate($hash,"value",$cmd, 1);
}
};
if ($@) {
my $ret = FRM_Catch($@);
$hash->{STATE} = "set $cmd error: " . $ret;
return $hash->{STATE};
}
return undef;
}
sub FRM_OUT_State
{
my ($hash, $tim, $sname, $sval) = @_;
STATEHANDLER: {
$sname eq "value" and do {
if (AttrVal($hash->{NAME},"restoreOnStartup", "on") eq "on") {
FRM_OUT_Set($hash,$hash->{NAME},$sval);
}
last;
}
}
}
sub FRM_OUT_State($$$$)
sub FRM_OUT_Attr
{
my ($hash, $tim, $sname, $sval) = @_;
STATEHANDLER: {
$sname eq "value" and do {
if (AttrVal($hash->{NAME},"restoreOnStartup", "on") eq "on") {
FRM_OUT_Set($hash,$hash->{NAME},$sval);
}
last;
}
}
}
sub
FRM_OUT_Attr($$$$) {
my ($command,$name,$attribute,$value) = @_;
my $hash = $main::defs{$name};
eval {
@ -163,6 +204,7 @@ FRM_OUT_Attr($$$$) {
}
last;
};
$attribute eq "activeLow" and do {
my $oldval = AttrVal($hash->{NAME},"activeLow", "no");
if ($oldval ne $value) {
@ -179,9 +221,9 @@ FRM_OUT_Attr($$$$) {
}
};
if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/;
$hash->{STATE} = "error setting $attribute to $value: ".$1;
return "cannot $command attribute $attribute to $value for $name: ".$1;
my $ret = FRM_Catch($@);
$hash->{STATE} = "$command $attribute error: " . $ret;
return $hash->{STATE};
}
}
@ -189,102 +231,148 @@ FRM_OUT_Attr($$$$) {
=pod
CHANGES
=head1 CHANGES
2016 jensb
o new sub FRM_OUT_observer, modified sub FRM_OUT_Init
to receive output state from Firmata device
o support attribute "activeLow"
01.01.2018 jensb
o create reading "value" in FRM_OUT_Init if missing
02.01.2018 jensb
o new attribute "valueMode" to control how "value" reading is updated
14.01.2018 jensb
o fix "uninitialised" when calling FRM_OUT_Set without command
23.08.2020 jensb
o check for IODev install error in Init
o prototypes removed
o set argument metadata added
o set argument verifier improved
22.10.2020 jensb
o annotaded module help of attributes for FHEMWEB
=cut
=pod
=head1 FHEM COMMANDREF METADATA
=over
=item device
=item summary Firmata: digital output
=item summary_DE Firmata: digitaler Ausang
=back
=head1 INSTALLATION AND CONFIGURATION
=begin html
<a name="FRM_OUT"></a>
<a name="FRM_OUT"/>
<h3>FRM_OUT</h3>
<ul>
This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a>
This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a>
that should be configured as a digital output.<br><br>
Requires a defined <a href="#FRM">FRM</a> device to work. The pin must be listed in
the internal reading "<a href="#FRMinternals">output_pins</a>"<br>
of the FRM device (after connecting to the Firmata device) to be used as digital output.<br><br>
<a name="FRM_OUTdefine"></a>
of the FRM device (after connecting to the Firmata device) to be used as digital output.<br><br>
<a name="FRM_OUTdefine"/>
<b>Define</b>
<ul>
<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.
<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.
</ul><br>
<a name="FRM_OUTset"></a>
<a name="FRM_OUTset"/>
<b>Set</b><br>
<ul>
<code>set &lt;name&gt; on|off</code><br><br>
<code>set &lt;name&gt; on|off</code><br><br>
</ul>
<ul>
<a href="#setExtensions">set extensions</a> are supported<br>
<a href="#setExtensions">set extensions</a> are supported<br>
</ul><br>
<a name="FRM_OUTget"></a>
<a name="FRM_OUTget"/>
<b>Get</b><br>
<ul>
N/A
N/A
</ul><br>
<a name="FRM_OUTattr"></a>
<a name="FRM_OUTattr"/>
<b>Attributes</b><br>
<ul>
<li>restoreOnStartup &lt;on|off&gt;, default: on<br>
Set output value in Firmata device on FHEM startup (if device is already connected) and
whenever the <em>setstate</em> command is used.
</li>
<li>restoreOnReconnect &lt;on|off&gt;, default: on<br>
Set output value in Firmata device after IODev is initialized.
</li>
<li>activeLow &lt;yes|no&gt;, default: no</li>
<li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
than one FRM-device defined.)
</li>
<li>valueMode &lt;send|receive|bidirectional&gt;, default: send<br>
Define how the reading <em>value</em> is updated:<br>
<a name="restoreOnStartup"/>
<li>restoreOnStartup &lt;on|off&gt;, default: on<br>
Set output value in Firmata device on FHEM startup (if device is already connected) and
whenever the <em>setstate</em> command is used.
</li>
<a name="restoreOnReconnect"/>
<li>restoreOnReconnect &lt;on|off&gt;, default: on<br>
Set output value in Firmata device after IODev is initialized.
</li>
<a name="activeLow"/>
<li>activeLow &lt;yes|no&gt;, default: no</li>
<a name="IODev"/>
<li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
</li>
<a name="valueMode"/>
<li>valueMode &lt;send|receive|bidirectional&gt;, default: send<br>
Define how the reading <em>value</em> is updated:<br>
<ul>
<li>send - after sending</li>
<li>receive - after receiving</li>
<li>bidirectional - after sending and receiving</li>
</ul>
</li>
<li><a href="#eventMap">eventMap</a><br></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
</li>
<li><a href="#attributes">global attributes</a></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
</ul><br>
<a name="FRM_OUTnotes"></a>
<a name="FRM_OUTnotes"/>
<b>Notes</b><br>
<ul>
<li>attribute <i>stateFormat</i><br>
In most cases it is a good idea to assign "value" to the attribute <i>stateFormat</i>. This will show the state
of the pin in the web interface.
</li>
<li>attribute <i>valueMode</i><br>
For modes "receive<" and "bidirectional" to work the default Firmata application code must
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>
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>
the state of an output or you want a feedback from the Firmata device after the output state was changed.
</li>
<li>attribute <i>stateFormat</i><br>
In most cases it is a good idea to assign "value" to the attribute <i>stateFormat</i>. This will show the state
of the pin in the web interface.
</li>
<li>attribute <i>valueMode</i><br>
For modes "receive" and "bidirectional" to work the default Firmata application code must
be modified in function "<code>setPinModeCallback</code>":<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 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.
</li>
</ul>
</ul><br>
=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

View File

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

View File

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

View File

@ -1,6 +1,42 @@
##############################################
########################################################################################
# $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;
use strict;
@ -8,37 +44,35 @@ use warnings;
#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
BEGIN {
if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib";
};
};
if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib";
};
};
};
use Device::Firmata::Constants qw/ :all /;
#####################################
# number of arguments
my %sets = (
"reset" => "noArg",
"offset"=> "",
"reset:noArg" => 0,
"offset" => 1,
);
my %gets = (
"position" => "noArg",
"offset" => "noArg",
"value" => "noArg",
"position" => "",
"offset" => "",
"value" => "",
);
sub
FRM_ROTENC_Initialize($)
sub FRM_ROTENC_Initialize
{
my ($hash) = @_;
$hash->{SetFn} = "FRM_ROTENC_Set";
$hash->{GetFn} = "FRM_ROTENC_Get";
$hash->{AttrFn} = "FRM_ROTENC_Attr";
$hash->{DefFn} = "FRM_Client_Define";
$hash->{DefFn} = "FRM_ROTENC_Define";
$hash->{InitFn} = "FRM_ROTENC_Init";
$hash->{UndefFn} = "FRM_ROTENC_Undef";
$hash->{StateFn} = "FRM_ROTENC_State";
@ -47,73 +81,87 @@ FRM_ROTENC_Initialize($)
main::LoadModule("FRM");
}
sub
FRM_ROTENC_Init($$)
sub FRM_ROTENC_Define
{
my ($hash,$args) = @_;
my ($hash, $def) = @_;
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};
$hash->{PINA} = $pinA;
$hash->{PINB} = $pinB;
$hash->{ENCODERNUM} = $encoder;
eval {
FRM_Client_AssignIOPort($hash);
my $firmata = FRM_Client_FirmataDevice($hash);
$firmata->encoder_attach($encoder,$pinA,$pinB);
$firmata->observe_encoder($encoder, \&FRM_ROTENC_observer, $hash );
};
if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/;
$hash->{STATE} = "error initializing: ".$1;
return "error initializing '$name': $1";
}
# verify define arguments
my $usage = "usage: define <name> FRM_ROTENC pinA pinB [id]";
if (! (defined AttrVal($name,"stateFormat",undef))) {
$main::attr{$name}{"stateFormat"} = "position";
}
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 $name = $hash->{NAME};
if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
}
eval {
FRM_Client_AssignIOPort($hash);
my $firmata = FRM_Client_FirmataDevice($hash);
$firmata->encoder_attach($hash->{ENCODERNUM}, $hash->{PINA}, $hash->{PINB});
$firmata->observe_encoder($hash->{ENCODERNUM}, \&FRM_ROTENC_observer, $hash );
};
if ($@) {
my $ret = FRM_Catch($@);
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
if (! (defined AttrVal($name,"stateFormat",undef))) {
$main::attr{$name}{"stateFormat"} = "position";
}
$hash->{offset} = ReadingsVal($name,"position",0);
main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
}
sub
FRM_ROTENC_observer
sub FRM_ROTENC_observer
{
my ( $encoder, $value, $hash ) = @_;
my $name = $hash->{NAME};
Log3 ($name,5,"onEncoderMessage for pins ".$hash->{PINA}.",".$hash->{PINB}." encoder: ".$encoder." position: ".$value."\n");
main::readingsBeginUpdate($hash);
main::readingsBulkUpdate($hash,"position",$value+$hash->{offset}, 1);
main::readingsBulkUpdate($hash,"value",$value, 1);
main::readingsEndUpdate($hash,1);
my ($encoder, $value, $hash) = @_;
my $name = $hash->{NAME};
Log3 ($name, 5, "$name: observer pins: ".$hash->{PINA}.", ".$hash->{PINB}." encoder: ".$encoder." position: ".$value."\n");
main::readingsBeginUpdate($hash);
main::readingsBulkUpdate($hash,"position",$value+$hash->{offset}, 1);
main::readingsBulkUpdate($hash,"value",$value, 1);
main::readingsEndUpdate($hash,1);
}
sub
FRM_ROTENC_Set
sub FRM_ROTENC_Set
{
my ($hash, @a) = @_;
return "Need at least one parameters" if(@a < 2);
my $command = $a[1];
my $value = $a[2];
if(!defined($sets{$command})) {
my @commands = ();
foreach my $key (sort keys %sets) {
push @commands, $sets{$key} ? $key.":".join(",",$sets{$key}) : $key;
}
return "Unknown argument $a[1], choose one of " . join(" ", @commands);
}
COMMAND_HANDLER: {
$command eq "reset" and do {
my ($hash, $name, $cmd, @a) = @_;
return "set command missing" if(!defined($cmd));
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
return "unknown set command '$cmd', choose one of " . join(" ", sort keys %sets) if ($cmd eq '?' || @match == 0);
return "$cmd requires $sets{$match[0]} argument(s)" unless (@a == $sets{$match[0]});
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';
}
eval {
FRM_Client_FirmataDevice($hash)->encoder_reset_position($hash->{ENCODERNUM});
};
@ -123,46 +171,37 @@ FRM_ROTENC_Set
main::readingsEndUpdate($hash,1);
last;
};
$command eq "offset" and do {
$cmd eq "offset" and do {
$hash->{offset} = $value;
readingsSingleUpdate($hash,"position",ReadingsVal($hash->{NAME},"value",0)+$value,1);
readingsSingleUpdate($hash,"position",ReadingsVal($name,"value",0)+$value,1);
last;
};
}
}
sub
FRM_ROTENC_Get($)
sub FRM_ROTENC_Get
{
my ($hash, @a) = @_;
return "Need at least one parameters" if(@a < 2);
my $command = $a[1];
my $value = $a[2];
if(!defined($gets{$command})) {
my @commands = ();
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: {
my ($hash, $name, $cmd, @a) = @_;
return "get command missing" if(!defined($cmd));
return "unknown get command '$cmd', choose one of " . join(":noArg ", sort keys %gets) . ":noArg" if(!defined($gets{$cmd}));
GETHANDLER: {
$cmd eq "position" and do {
return ReadingsVal($hash->{NAME},"position","0");
return ReadingsVal($name,"position","0");
};
$cmd eq "offset" and do {
return $hash->{offset};
};
$cmd eq "value" and do {
return ReadingsVal($hash->{NAME},"value","0");
return ReadingsVal($name,"value","0");
};
}
return undef;
}
sub
FRM_ROTENC_Attr($$$$) {
sub FRM_ROTENC_Attr
{
my ($command,$name,$attribute,$value) = @_;
my $hash = $main::defs{$name};
eval {
@ -179,38 +218,32 @@ FRM_ROTENC_Attr($$$$) {
}
};
if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/;
$hash->{STATE} = "error setting $attribute to $value: ".$1;
return "cannot $command attribute $attribute to $value for $name: ".$1;
my $ret = FRM_Catch($@);
$hash->{STATE} = "$command $attribute error: " . $ret;
return $hash->{STATE};
}
}
sub
FRM_ROTENC_Undef($$)
sub FRM_ROTENC_Undef
{
my ($hash, $name) = @_;
my $pinA = $hash->{PINA};
my $pinB = $hash->{PINB};
eval {
my $firmata = FRM_Client_FirmataDevice($hash);
$firmata->encoder_detach($hash->{ENCODERNUM});
$firmata->pin_mode($pinA,PIN_ANALOG);
$firmata->pin_mode($pinB,PIN_ANALOG);
};
if ($@) {
eval {
my $firmata = FRM_Client_FirmataDevice($hash);
$firmata->pin_mode($pinA,PIN_INPUT);
$firmata->digital_write($pinA,0);
$firmata->pin_mode($pinB,PIN_INPUT);
$firmata->digital_write($pinB,0);
};
}
$hash->{PIN} = $hash->{PINA};
FRM_Client_Undef($hash, $name);
$hash->{PIN} = $hash->{PINB};
FRM_Client_Undef($hash, $name);
return undef;
}
sub
FRM_ROTENC_State($$$$)
sub FRM_ROTENC_State
{
my ($hash, $tim, $sname, $sval) = @_;
if ($sname eq "position") {
@ -222,30 +255,64 @@ FRM_ROTENC_State($$$$)
1;
=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
<a name="FRM_ROTENC"></a>
<a name="FRM_ROTENC"/>
<h3>FRM_ROTENC</h3>
<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>
Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br>
<a name="FRM_ROTENCdefine"></a>
Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br>
<a name="FRM_ROTENCdefine"/>
<b>Define</b>
<ul>
<code>define &lt;name&gt; FRM_ROTENC &lt;pinA&gt; &lt;pinB&gt; [id]</code> <br>
Defines the FRM_ROTENC device. &lt;pinA&gt> and &lt;pinA&gt> are the arduino-pins to use.<br>
[id] is the instance-id of the encoder. Must be a unique number per FRM-device (rages from 0-4 depending on Firmata being used, optional if a single encoder is attached to the arduino).<br>
</ul>
<br>
<a name="FRM_ROTENCset"></a>
<a name="FRM_ROTENCset"/>
<b>Set</b><br>
<ul>
<li>reset<br>
resets to value of 'position' to 0<br></li>
<li>offset &lt;value&gt;<br>
set offset value of 'position'<br></li>
<a name="FRM_ROTENCget"></a>
</ul><br>
<a name="FRM_ROTENCget"/>
<b>Get</b>
<ul>
<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>
this value is reset to 0 whenever Arduino restarts or Firmata is reinitialized<br></li>
</ul><br>
<a name="FRM_ROTENCattr"></a>
<a name="FRM_ROTENCattr"/>
<b>Attributes</b><br>
<ul>
<li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
than one FRM-device defined.)
</li>
<li><a href="#eventMap">eventMap</a><br></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
</ul>
<a name="IODev"/>
<li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
</li>
<li><a href="#attributes">global attributes</a></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
</ul>
<br>
</ul><br>
=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

View File

@ -1,6 +1,42 @@
##############################################
########################################################################################
# $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;
use strict;
@ -8,23 +44,21 @@ use warnings;
#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
BEGIN {
if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib";
};
};
if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib";
};
};
};
use Device::Firmata::Constants qw/ :all /;
#####################################
# number of arguments
my %sets = (
"angle" => "",
"angle" => 1,
);
sub
FRM_SERVO_Initialize($)
sub FRM_SERVO_Initialize
{
my ($hash) = @_;
@ -33,29 +67,44 @@ FRM_SERVO_Initialize($)
$hash->{InitFn} = "FRM_SERVO_Init";
$hash->{UndefFn} = "FRM_Client_Undef";
$hash->{AttrFn} = "FRM_SERVO_Attr";
$hash->{AttrList} = "min-pulse max-pulse IODev $main::readingFnAttributes";
main::LoadModule("FRM");
}
sub
FRM_SERVO_Init($$)
sub FRM_SERVO_Init
{
my ($hash,$args) = @_;
my $ret = FRM_Init_Pin_Client($hash,$args,PIN_SERVO);
return $ret if (defined $ret);
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_SERVO);
if (defined($ret)) {
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
eval {
my $firmata = FRM_Client_FirmataDevice($hash);
$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
};
return FRM_Catch($@) if $@;
if ($@) {
$ret = FRM_Catch($@);
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
}
sub
FRM_SERVO_Attr($$$$) {
sub FRM_SERVO_Attr
{
my ($command,$name,$attribute,$value) = @_;
my $hash = $main::defs{$name};
eval {
@ -70,7 +119,10 @@ FRM_SERVO_Attr($$$$) {
};
($attribute eq "min-pulse" || $attribute eq "max-pulse") and do {
if ($main::init_done) {
$main::attr{$name}{$attribute}=$value;
if (defined($main::defs{$name}{IODev_ERROR})) {
die 'Perl module Device::Firmata not properly installed';
}
$main::attr{$name}{$attribute}=$value;
FRM_SERVO_apply_attribute($hash,$attribute);
}
last;
@ -78,15 +130,15 @@ FRM_SERVO_Attr($$$$) {
}
}
};
my $ret = FRM_Catch($@) if $@;
if ($ret) {
$hash->{STATE} = "error setting $attribute to $value: ".$ret;
return "cannot $command attribute $attribute to $value for $name: ".$ret;
if ($@) {
my $ret = FRM_Catch($@);
$hash->{STATE} = "$command $attribute error: " . $ret;
return $hash->{STATE};
}
return undef;
}
sub FRM_SERVO_apply_attribute {
sub FRM_SERVO_apply_attribute
{
my ($hash,$attribute) = @_;
if ( $attribute eq "min-pulse" || $attribute eq "max-pulse" ) {
my $name = $hash->{NAME};
@ -95,69 +147,130 @@ sub FRM_SERVO_apply_attribute {
}
}
sub
FRM_SERVO_Set($@)
sub FRM_SERVO_Set
{
my ($hash, @a) = @_;
return "Need at least one parameters" if(@a < 2);
return "Unknown argument $a[1], choose one of " . join(" ", sort keys %sets)
if(!defined($sets{$a[1]}));
my $command = $a[1];
my $value = $a[2];
my ($hash, $name, $cmd, @a) = @_;
return "set command missing" if(!defined($cmd));
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
return "unknown set command '$cmd', choose one of " . join(" ", sort keys %sets) if ($cmd eq '?' || @match == 0);
return "$cmd requires $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 {
FRM_Client_FirmataDevice($hash)->servo_write($hash->{PIN},$value);
main::readingsSingleUpdate($hash,"state",$value, 1);
};
return $@;
if ($@) {
my $ret = FRM_Catch($@);
$hash->{STATE} = "set $cmd error: " . $ret;
return $hash->{STATE};
}
return undef;
}
1;
=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
<a name="FRM_SERVO"></a>
<a name="FRM_SERVO"/>
<h3>FRM_SERVO</h3>
<ul>
represents a pin of an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a>
configured to drive a pwm-controlled servo-motor.<br>
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>
<a name="FRM_SERVOdefine"></a>
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>
<a name="FRM_SERVOdefine"/>
<b>Define</b>
<ul>
<code>define &lt;name&gt; FRM_SERVO &lt;pin&gt;</code> <br>
Defines the FRM_SERVO device. &lt;pin&gt> is the arduino-pin to use.
</ul>
<br>
<a name="FRM_SERVOset"></a>
<a name="FRM_SERVOset"/>
<b>Set</b><br>
<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>
</ul>
<a name="FRM_SERVOget"></a>
<a name="FRM_SERVOget"/>
<b>Get</b><br>
<ul>
N/A
</ul><br>
<a name="FRM_SERVOattr"></a>
<a name="FRM_SERVOattr"/>
<b>Attributes</b><br>
<ul>
<li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
than one FRM-device defined.)
</li>
<li>min-pulse<br>
sets the minimum puls-width to use. Defaults to 544. For most servos this translates into a rotation of 180° counterclockwise.</li>
<li>max-pulse<br>
sets the maximum puls-width to use. Defaults to 2400. For most servos this translates into a rotation of 180° clockwise</li>
<li><a href="#eventMap">eventMap</a><br></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
</ul>
<a name="IODev"/>
<li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
</li>
<a name="min-pulse"/>
<li>min-pulse<br>
sets the minimum puls-width to use. Defaults to 544. For most servos this translates into a rotation of 180° counterclockwise.
</li>
<a name="max-pulse"/>
<li>max-pulse<br>
sets the maximum puls-width to use. Defaults to 2400. For most servos this translates into a rotation of 180° clockwise.
</li>
<li><a href="#attributes">global attributes</a></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
</ul>
<br>
</ul><br>
=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

View File

@ -1,6 +1,42 @@
##############################################
########################################################################################
# $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;
use strict;
@ -8,136 +44,151 @@ use warnings;
#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
BEGIN {
if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib";
};
};
if (!grep(/FHEM\/lib$/,@INC)) {
foreach my $inc (grep(/FHEM$/,@INC)) {
push @INC,$inc."/lib";
};
};
};
use Device::Firmata::Constants qw/ :all /;
#####################################
# (min) number of arguments
my %sets = (
"reset" => "noArg",
"position" => "",
"step" => "",
"reset:noArg" => 0,
"position" => 1,
"step" => 1,
);
my %gets = (
"position" => "noArg",
"position" => "",
);
sub
FRM_STEPPER_Initialize($)
sub FRM_STEPPER_Initialize
{
my ($hash) = @_;
$hash->{SetFn} = "FRM_STEPPER_Set";
$hash->{GetFn} = "FRM_STEPPER_Get";
$hash->{DefFn} = "FRM_Client_Define";
$hash->{DefFn} = "FRM_STEPPER_Define";
$hash->{InitFn} = "FRM_STEPPER_Init";
$hash->{UndefFn} = "FRM_Client_Undef";
$hash->{AttrFn} = "FRM_STEPPER_Attr";
$hash->{StateFn} = "FRM_STEPPER_State";
$hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off speed acceleration deceleration IODev $main::readingFnAttributes";
main::LoadModule("FRM");
}
sub
FRM_STEPPER_Init($$)
sub FRM_STEPPER_Define
{
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]";
return $u unless defined $args;
my $driver = shift @$args;
return $u unless ( $driver eq 'DRIVER' or $driver eq 'TWO_WIRE' or $driver eq 'FOUR_WIRE' );
return $u if (($driver eq 'DRIVER' or $driver eq 'TWO_WIRE') and (scalar(@$args) < 3 or scalar(@$args) > 4));
return $u if (($driver eq 'FOUR_WIRE') and (scalar(@$args) < 5 or scalar(@$args) > 6));
$hash->{DRIVER} = $driver;
$hash->{PIN1} = shift @$args;
$hash->{PIN2} = shift @$args;
if ($driver eq 'FOUR_WIRE') {
$hash->{PIN3} = shift @$args;
$hash->{PIN4} = shift @$args;
}
$hash->{STEPSPERREV} = shift @$args;
$hash->{STEPPERNUM} = shift @$args;
eval {
FRM_Client_AssignIOPort($hash);
my $firmata = FRM_Client_FirmataDevice($hash);
$firmata->stepper_config(
$hash->{STEPPERNUM},
$driver,
$hash->{STEPSPERREV},
$hash->{PIN1},
$hash->{PIN2},
$hash->{PIN3},
$hash->{PIN4});
$firmata->observe_stepper(0, \&FRM_STEPPER_observer, $hash );
};
if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/;
$hash->{STATE} = "error initializing: ".$1;
return "error initializing '".$hash->{NAME}."': ".$1;
}
$hash->{POSITION} = 0;
$hash->{DIRECTION} = 0;
$hash->{STEPS} = 0;
if (! (defined AttrVal($hash->{NAME},"stateFormat",undef))) {
$main::attr{$hash->{NAME}}{"stateFormat"} = "position";
}
main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
}
# verify define arguments
my $usage = "usage: define <name> FRM_STEPPER [DRIVER|TWO_WIRE|FOUR_WIRE] directionPin stepPin [motorPin3 motorPin4] stepsPerRev [id]";
sub
FRM_STEPPER_observer
{
my ( $stepper, $hash ) = @_;
my $name = $hash->{NAME};
Log3 $name,5,"onStepperMessage for pins ".$hash->{PIN1}.",".$hash->{PIN2}.(defined ($hash->{PIN3}) ? ",".$hash->{PIN3} : ",-").(defined ($hash->{PIN4}) ? ",".$hash->{PIN4} : ",-")." stepper: ".$stepper;
my $position = $hash->{DIRECTION} ? $hash->{POSITION} - $hash->{STEPS} : $hash->{POSITION} + $hash->{STEPS};
$hash->{POSITION} = $position;
$hash->{DIRECTION} = 0;
$hash->{STEPS} = 0;
main::readingsSingleUpdate($hash,"position",$position,1);
}
my @a = split("[ \t][ \t]*", $def);
my $args = [@a[2..scalar(@a)-1]];
return $usage unless defined $args;
sub
FRM_STEPPER_Set
{
my ($hash, @a) = @_;
return "Need at least one parameters" if(@a < 2);
shift @a;
my $name = $hash->{NAME};
my $command = shift @a;
if(!defined($sets{$command})) {
my @commands = ();
foreach my $key (sort keys %sets) {
push @commands, $sets{$key} ? $key.":".join(",",$sets{$key}) : $key;
}
return "Unknown argument $command, choose one of " . join(" ", @commands);
my $driver = shift @$args;
return $usage unless ( $driver eq 'DRIVER' or $driver eq 'TWO_WIRE' or $driver eq 'FOUR_WIRE' );
return $usage if (($driver eq 'DRIVER' or $driver eq 'TWO_WIRE') and (scalar(@$args) < 3 or scalar(@$args) > 4));
return $usage if (($driver eq 'FOUR_WIRE') and (scalar(@$args) < 5 or scalar(@$args) > 6));
$hash->{DRIVER} = $driver;
$hash->{PIN1} = shift @$args;
$hash->{PIN2} = shift @$args;
if ($driver eq 'FOUR_WIRE') {
$hash->{PIN3} = shift @$args;
$hash->{PIN4} = shift @$args;
}
COMMAND_HANDLER: {
$command eq "reset" and do {
$hash->{STEPSPERREV} = shift @$args;
$hash->{STEPPERNUM} = shift @$args;
my $ret = FRM_Client_Define($hash, $def);
if ($ret) {
return $ret;
}
return undef;
}
sub FRM_STEPPER_Init
{
my ($hash,$args) = @_;
my $name = $hash->{NAME};
if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
}
eval {
FRM_Client_AssignIOPort($hash);
my $firmata = FRM_Client_FirmataDevice($hash);
$firmata->stepper_config(
$hash->{STEPPERNUM},
$hash->{DRIVER},
$hash->{STEPSPERREV},
$hash->{PIN1},
$hash->{PIN2},
$hash->{PIN3},
$hash->{PIN4});
$firmata->observe_stepper(0, \&FRM_STEPPER_observer, $hash );
};
if ($@) {
my $ret = FRM_Catch($@);
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
return $ret;
}
$hash->{POSITION} = 0;
$hash->{DIRECTION} = 0;
$hash->{STEPS} = 0;
if (! (defined AttrVal($name,"stateFormat",undef))) {
$main::attr{$name}{"stateFormat"} = "position";
}
main::readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
}
sub FRM_STEPPER_observer
{
my ( $stepper, $hash ) = @_;
my $name = $hash->{NAME};
Log3 $name, 5, "$name: observer pins: ".$hash->{PIN1}.",".$hash->{PIN2}.(defined ($hash->{PIN3}) ? ",".$hash->{PIN3} : ",-").(defined ($hash->{PIN4}) ? ",".$hash->{PIN4} : ",-")." stepper: ".$stepper;
my $position = $hash->{DIRECTION} ? $hash->{POSITION} - $hash->{STEPS} : $hash->{POSITION} + $hash->{STEPS};
$hash->{POSITION} = $position;
$hash->{DIRECTION} = 0;
$hash->{STEPS} = 0;
main::readingsSingleUpdate($hash,"position",$position,1);
}
sub FRM_STEPPER_Set
{
my ($hash, $name, $cmd, @a) = @_;
return "set command missing" if(!defined($cmd));
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
return "unknown set command '$cmd', choose one of " . join(" ", sort keys %sets) if ($cmd eq '?' || @match == 0);
return "$cmd requires (at least) $sets{$match[0]} argument(s)" unless (@a >= $sets{$match[0]});
my $value = shift @a;
SETHANDLER: {
$cmd eq "reset" and do {
$hash->{POSITION} = 0;
main::readingsSingleUpdate($hash,"position",0,1);
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 $value = shift @a;
my $direction = $value < $position ? 1 : 0;
my $steps = $direction ? $position - $value : $value - $position;
my $speed = shift @a;
@ -149,13 +200,19 @@ FRM_STEPPER_Set
$hash->{DIRECTION} = $direction;
$hash->{STEPS} = $steps;
eval {
# $stepperNum, $direction, $numSteps, $stepSpeed, $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;
};
$command eq "step" and do {
my $value = shift @a;
$cmd eq "step" and do {
if (defined($main::defs{$name}{IODev_ERROR})) {
return 'Perl module Device::Firmata not properly installed';
}
my $direction = $value < 0 ? 1 : 0;
my $steps = abs $value;
my $speed = shift @a;
@ -167,42 +224,53 @@ FRM_STEPPER_Set
$hash->{DIRECTION} = $direction;
$hash->{STEPS} = $steps;
eval {
# $stepperNum, $direction, $numSteps, $stepSpeed, $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;
};
}
return undef;
}
sub
FRM_STEPPER_Get
sub FRM_STEPPER_Get
{
my ($hash, @a) = @_;
return "Need at least one parameters" if(@a < 2);
shift @a;
my $name = $hash->{NAME};
my $command = shift @a;
return "Unknown argument $command, choose one of " . join(" ", sort keys %gets) unless defined($gets{$command});
my ($hash, $name, $cmd, @a) = @_;
return "get command missing" if(!defined($cmd));
return "unknown get command '$cmd', choose one of " . join(":noArg ", sort keys %gets) . ":noArg" if(!defined($gets{$cmd}));
GETHANDLER: {
$cmd eq 'position' and do {
return $hash->{POSITION};
};
}
return undef;
}
sub FRM_STEPPER_State($$$$)
sub FRM_STEPPER_State
{
my ($hash, $tim, $sname, $sval) = @_;
STATEHANDLER: {
$sname eq "value" and do {
if (AttrVal($hash->{NAME},"restoreOnStartup","on") eq "on") {
FRM_STEPPER_Set($hash,$hash->{NAME},$sval);
}
last;
}
}
my ($hash, $tim, $sname, $sval) = @_;
STATEHANDLER: {
$sname eq "value" and do {
if (AttrVal($hash->{NAME},"restoreOnStartup","on") eq "on") {
FRM_STEPPER_Set($hash,$hash->{NAME},$sval);
}
last;
}
}
}
sub
FRM_STEPPER_Attr($$$$) {
sub FRM_STEPPER_Attr
{
my ($command,$name,$attribute,$value) = @_;
my $hash = $main::defs{$name};
eval {
@ -219,115 +287,183 @@ FRM_STEPPER_Attr($$$$) {
}
};
if ($@) {
$@ =~ /^(.*)( at.*FHEM.*)$/;
$hash->{STATE} = "error setting $attribute to $value: ".$1;
return "cannot $command attribute $attribute to $value for $name: ".$1;
my $ret = FRM_Catch($@);
$hash->{STATE} = "$command $attribute error: " . $ret;
return $hash->{STATE};
}
}
1;
=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
<a name="FRM_STEPPER"></a>
<h3>FRM_STEPPER</h3>
<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>
Requires a defined <a href="#FRM">FRM</a>-device to work.<br><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>
<a name="FRM_STEPPERdefine"></a>
<b>Define</b>
<ul>
<code>define &lt;name&gt; FRM_STEPPER [DRIVER|TWO_WIRE|FOUR_WIRE] &lt;directionPin&gt &lt;stepPin&gt [motorPin3 motorPin4] stepsPerRev [stepper-id]</code><br>
Defines the FRM_STEPPER device.
<li>[DRIVER|TWO_WIRE|FOUR_WIRE] defines the control-sequence being used to drive the motor.
<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>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
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>
</li>
<li>
<ul>
<li>The sequence of control signals for 4 control wires is as follows:<br>
<br>
<code>
Step C0 C1 C2 C3<br>
1 1 0 1 0<br>
2 0 1 1 0<br>
3 0 1 0 1<br>
4 1 0 0 1<br>
</code>
</li>
<li>The sequence of controls signals for 2 control wires is as follows:<br>
(columns C1 and C2 from above):<br>
<br>
<code>
Step C0 C1<br>
1 0 1<br>
2 1 1<br>
3 1 0<br>
4 0 0<br>
</code>
</li>
</ul>
</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>
that can be archived either by rearranging the physical 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>
when being connected to the arduio with:<br>
<code>motor pin1 <-> arduino pin5<br>
motor pin2 <-> arduino pin6<br>
motor pin3 <-> arduino pin7<br>
motor pin4 <-> arduino pin8<br>
motor pin5 <-> ground</code><br>
</li>
</ul>
<br>
<code>define &lt;name&gt; FRM_STEPPER [DRIVER|TWO_WIRE|FOUR_WIRE] &lt;directionPin&gt &lt;stepPin&gt [motorPin3 motorPin4] stepsPerRev [stepper-id]</code><br>
Defines the FRM_STEPPER device.
<li>[DRIVER|TWO_WIRE|FOUR_WIRE] defines the control-sequence being used to drive the motor.
<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>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 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>
</li>
<li>
<ul>
<li>The sequence of control signals for 4 control wires is as follows:<br><br>
<code>
Step C0 C1 C2 C3<br>
1 1 0 1 0<br>
2 0 1 1 0<br>
3 0 1 0 1<br>
4 1 0 0 1<br>
</code>
</li>
<li>The sequence of controls signals for 2 control wires is as follows:<br>
(columns C1 and C2 from above):<br><br>
<code>
Step C0 C1<br>
1 0 1<br>
2 1 1<br>
3 1 0<br>
4 0 0<br>
</code>
</li>
</ul>
</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. That can be archived either by rearranging the physical
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>
when being connected to the arduio with:<br><br>
<code>
motor pin1 <-> arduino pin5<br>
motor pin2 <-> arduino pin6<br>
motor pin3 <-> arduino pin7<br>
motor pin4 <-> arduino pin8<br>
motor pin5 <-> ground
</code>
</li><br>
</ul><br>
<a name="FRM_STEPPERset"></a>
<b>Set</b><br>
<ul>
<code>set &lt;name&gt; reset</code>
<li>resets the reading 'position' to 0 without moving the motor</li>
<br>
<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>
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>
</li>
<br>
<code>set &lt;name&gt; step &lt;stepstomove&gt; [speed] [accel] [decel]</code>
<li>moves the motor the number of steps specified. positive or negative integer<br>
speed, accelleration and deceleration are optional.<br>
</li>
</ul>
<code>set &lt;name&gt; reset</code>
<li>resets the reading 'position' to 0 without moving the motor</li><br>
<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>
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>
</li><br>
<code>set &lt;name&gt; step &lt;stepstomove&gt; [speed] [accel] [decel]</code>
<li>moves the motor the number of steps specified. positive or negative integer<br>
speed, accelleration and deceleration are optional.<br>
</li>
</ul><br>
<a name="FRM_STEPPERget"></a>
<b>Get</b><br>
<ul>
N/A
<code>get &lt;position&gt;</code>
<li>returns the current position value</li>
</ul><br>
<a name="FRM_STEPPERattr"></a>
<b>Attributes</b><br>
<ul>
<li>restoreOnStartup &lt;on|off&gt;</li>
<li>restoreOnReconnect &lt;on|off&gt;</li>
<li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
than one FRM-device defined.)
</li>
<li>>speed (same meaning as in 'set position')</li>
<li>acceleration (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>
</ul>
<a name="restoreOnStartup"/>
<li>restoreOnStartup &lt;on|off&gt;</li>
<a name="restoreOnReconnect"/>
<li>restoreOnReconnect &lt;on|off&gt;</li>
<a name="IODev"/>
<li><a href="#IODev">IODev</a><br>
Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
</li>
<a name="speed"/>
<li>>speed (same meaning as in 'set position')</li>
<a name="acceleration"/>
<li>acceleration (same meaning as in 'set position')</li>
<a name="deceleration"/>
<li>deceleration (same meaning as in 'set position')</li>
<li><a href="#attributes">global attributes</a></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
</ul>
<br>
</ul><br>
=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

View File

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