From 79a664abe5a9242e4995d2ca151e717b068b4140 Mon Sep 17 00:00:00 2001
From: jensb <>
Date: Fri, 30 Oct 2020 18:16:24 +0000
Subject: [PATCH] 20_FRM_*.pm: feature update, see forum #114552 msg #1087982 -
check for IODev install error in Init, Get, Set, Attr and Undef - missing
get/set argument metadata added - get/set argument verifier improved - moved
define argument verification and decoding from Init to Define - error
behaviour of Init, Get, Set and Attr standardized - annotaded module help of
attributes for FHEMWEB
git-svn-id: https://svn.fhem.de/fhem/trunk@23054 2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
fhem/CHANGED | 3 +
fhem/FHEM/20_FRM_AD.pm | 358 +++++++++++++----------
fhem/FHEM/20_FRM_IN.pm | 409 +++++++++++++++-----------
fhem/FHEM/20_FRM_OUT.pm | 398 ++++++++++++++++----------
fhem/FHEM/20_FRM_PWM.pm | 428 ++++++++++++++++------------
fhem/FHEM/20_FRM_RGB.pm | 316 +++++++++++++--------
fhem/FHEM/20_FRM_ROTENC.pm | 336 +++++++++++++---------
fhem/FHEM/20_FRM_SERVO.pm | 227 +++++++++++----
fhem/FHEM/20_FRM_STEPPER.pm | 552 ++++++++++++++++++++++--------------
fhem/MAINTAINER.txt | 12 +-
10 files changed, 1872 insertions(+), 1167 deletions(-)
diff --git a/fhem/CHANGED b/fhem/CHANGED
index 67877b8c3..974beda21 100644
--- a/fhem/CHANGED
+++ b/fhem/CHANGED
@@ -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
diff --git a/fhem/FHEM/20_FRM_AD.pm b/fhem/FHEM/20_FRM_AD.pm
index c36acf79c..34c799ebc 100755
--- a/fhem/FHEM/20_FRM_AD.pm
+++ b/fhem/FHEM/20_FRM_AD.pm
@@ -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
-
+
FRM_AD
- This module represents a pin of a Firmata device
+ This module represents a pin of a Firmata device
that should be configured as an analog input.
-
+
Requires a defined FRM device to work. The pin must be listed in the internal reading "analog_pins"
- of the FRM device (after connecting to the Firmata device) to be used as analog input.
-
-
+ of the FRM device (after connecting to the Firmata device) to be used as analog input.
+
+
Define
- define <name> FRM_AD <pin>
-
- Defines the FRM_AD device. <pin> is the arduino-pin to use.
+ define <name> FRM_AD <pin>
+
+ Defines the FRM_AD device. <pin> is the arduino-pin to use.
-
-
+
+
Set
-
-
+
+
Get
- reading
@@ -236,35 +277,52 @@ FRM_AD_Attr($$$$) {
- state
returns the 'state' reading
-
-
+
+
Attributes
- - upper-threshold
- sets the 'upper-threshold'. Whenever the 'reading' exceeds this value 'alarm-upper-threshold' is set to 'on'
- As soon 'reading' falls below the 'upper-threshold' 'alarm-upper-threshold' turns 'off' again
- Defaults to the max pin resolution plus one.
- - lower-threshold
- sets the 'lower-threshold'. Whenever the 'reading' falls below this value 'alarm-lower-threshold' is set to 'on'
- As soon 'reading' rises above the 'lower-threshold' 'alarm-lower-threshold' turns 'off' again
- Defaults to -1.
- - IODev
- Specify which FRM to use. (Optional, only required if there is more
- than one FRM-device defined.)
-
- - eventMap
- - readingFnAttributes
+
+ - upper-threshold
+ sets the 'upper-threshold'. Whenever the 'reading' exceeds this value 'alarm-upper-threshold' is set to 'on'
+ As soon 'reading' falls below the 'upper-threshold' 'alarm-upper-threshold' turns 'off' again
+ Defaults to the max pin resolution plus one.
+
+
+ - lower-threshold
+ sets the 'lower-threshold'. Whenever the 'reading' falls below this value 'alarm-lower-threshold' is set to 'on'
+ As soon 'reading' rises above the 'lower-threshold' 'alarm-lower-threshold' turns 'off' again
+ Defaults to -1.
+
+
+ - IODev
+ Specify which FRM to use. Only required if there is more than one FRM-device defined.
+
+
+ - global attributes
+
+ - readingFnAttributes
-
-
+
+
Notes
- - attribute stateFormat
- In most cases it is a good idea to assign "reading" to the attribute stateFormat. This will show the
- current value of the pin in the web interface.
-
-
+ - attribute stateFormat
+ In most cases it is a good idea to assign "reading" to the attribute stateFormat. This will show the
+ current value of the pin in the web interface.
+
+
=end html
+
+=begin html_DE
+
+
+FRM_AD
+
+ Die Modulbeschreibung von FRM_AD gibt es nur auf Englisch.
+
+
+=end html_DE
+
=cut
diff --git a/fhem/FHEM/20_FRM_IN.pm b/fhem/FHEM/20_FRM_IN.pm
index 33cf0b976..acca1bb22 100755
--- a/fhem/FHEM/20_FRM_IN.pm
+++ b/fhem/FHEM/20_FRM_IN.pm
@@ -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
-
+
FRM_IN
This module represents a pin of a Firmata device
@@ -354,7 +412,7 @@ FRM_IN_Attr($$$$) {
the internal reading "input_pins" or "pullup_pins"
of the FRM device (after connecting to the Firmata device) to be used as digital input with or without pullup.
-
+
Define
define <name> FRM_IN <pin>
@@ -362,7 +420,7 @@ FRM_IN_Attr($$$$) {
-
+
Set
- alarm on|off
@@ -373,47 +431,64 @@ FRM_IN_Attr($$$$) {
The counter is incremented depending on the attribute 'count-mode'.
-
+
Get
- reading
returns the logical state of the input pin last received from the Firmata device depending on the attribute 'activeLow'.
Values are 'on' and 'off'.
- count
- 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.
- alarm
- 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.
- state
returns the 'state' reading
-
+
Attributes
- - activeLow yes|no
- inverts the logical state of the pin reading if set to yes (defaults to 'no').
- - count-mode none|rising|falling|both
- Determines whether 'rising' (transitions from 'off' to 'on') of falling (transitions from 'on' to 'off')
- edges (or 'both') are counted (defaults to 'none').
- - count-threshold <number>
- 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.
- - reset-on-threshold-reached yes|no
- if set to 'yes' reset the counter to 0 when the threshold is reached (defaults to 'no').
-
- - internal-pullup on|off
- enables/disables the internal pullup resistor of the Firmata pin (defaults to 'off'). Requires hardware and firmware support.
-
- - IODev
- specify which FRM to use.
-
- - eventMap
- - readingFnAttributes
+
+ - activeLow yes|no
+ inverts the logical state of the pin reading if set to yes (defaults to 'no').
+
+
+
+ - count-mode none|rising|falling|both
+ Determines whether 'rising' (transitions from 'off' to 'on') of falling (transitions from 'on' to 'off')
+ edges (or 'both') are counted (defaults to 'none').
+
+
+
+ - count-threshold <number>
+ 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.
+
+
+
+ - reset-on-threshold-reached yes|no
+ if set to 'yes' reset the counter to 0 when the threshold is reached (defaults to 'no').
+
+
+
+ - internal-pullup on|off
+ enables/disables the internal pullup resistor of the Firmata pin (defaults to 'off'). Requires hardware
+ and firmware support.
+
+
+
+ - IODev
+ Specify which FRM to use. Only required if there is more than one FRM-device defined.
+
+
+ - global attributes
+
+ - readingFnAttributes
-
+
Notes
- attribute stateFormat
@@ -430,7 +505,7 @@ FRM_IN_Attr($$$$) {
=begin html_DE
-
+
FRM_IN
Die Modulbeschreibung von FRM_IN gibt es nur auf Englisch.
diff --git a/fhem/FHEM/20_FRM_OUT.pm b/fhem/FHEM/20_FRM_OUT.pm
index fc7e82e3a..ed5a936be 100755
--- a/fhem/FHEM/20_FRM_OUT.pm
+++ b/fhem/FHEM/20_FRM_OUT.pm
@@ -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
-
+
FRM_OUT
- This module represents a pin of a Firmata device
+ This module represents a pin of a Firmata device
that should be configured as a digital output.
-
+
Requires a defined FRM device to work. The pin must be listed in
the internal reading "output_pins"
- of the FRM device (after connecting to the Firmata device) to be used as digital output.
-
-
+ of the FRM device (after connecting to the Firmata device) to be used as digital output.
+
+
Define
- define <name> FRM_OUT <pin>
- Defines the FRM_OUT device. <pin>> is the arduino-pin to use.
+ define <name> FRM_OUT <pin>
+ Defines the FRM_OUT device. <pin>> is the arduino-pin to use.
-
-
+
+
Set
- set <name> on|off
+ set <name> on|off
-
-
+
+
Get
-
-
+
+
Attributes
- - restoreOnStartup <on|off>, default: on
- Set output value in Firmata device on FHEM startup (if device is already connected) and
- whenever the setstate command is used.
-
- - restoreOnReconnect <on|off>, default: on
- Set output value in Firmata device after IODev is initialized.
-
- - activeLow <yes|no>, default: no
- - IODev
- Specify which FRM to use. (Optional, only required if there is more
- than one FRM-device defined.)
-
- - valueMode <send|receive|bidirectional>, default: send
- Define how the reading value is updated:
+
+ - restoreOnStartup <on|off>, default: on
+ Set output value in Firmata device on FHEM startup (if device is already connected) and
+ whenever the setstate command is used.
+
+
+
+ - restoreOnReconnect <on|off>, default: on
+ Set output value in Firmata device after IODev is initialized.
+
+
+
+ - activeLow <yes|no>, default: no
+
+
+ - IODev
+ Specify which FRM to use. Only required if there is more than one FRM-device defined.
+
+
+
+ - valueMode <send|receive|bidirectional>, default: send
+ Define how the reading value is updated:
- send - after sending
- receive - after receiving
- bidirectional - after sending and receiving
-
- - eventMap
- - readingFnAttributes
+
+
+ - global attributes
+
+ - readingFnAttributes
-
-
+
+
Notes
- - attribute stateFormat
- In most cases it is a good idea to assign "value" to the attribute stateFormat. This will show the state
- of the pin in the web interface.
-
- - attribute valueMode
- For modes "receive<" and "bidirectional" to work the default Firmata application code must
- be modified in function "setPinModeCallback
":
- add " || mode == OUTPUT" to the if condition for "portConfigInputs[pin / 8] |= (1 << (pin & 7));
" to enable
- 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
- the state of an output or you want a feedback from the Firmata device after the output state was changed.
-
+ - attribute stateFormat
+ In most cases it is a good idea to assign "value" to the attribute stateFormat. This will show the state
+ of the pin in the web interface.
+
+ - attribute valueMode
+ For modes "receive" and "bidirectional" to work the default Firmata application code must
+ be modified in function "setPinModeCallback
":
+ add " || mode == OUTPUT
" to the if condition for "portConfigInputs[pin / 8] |= (1 << (pin & 7));
" to enable
+ 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.
+ the state of an output or you want a feedback from the Firmata device after the output state was changed.
+
=end html
+
+=begin html_DE
+
+
+FRM_OUT
+
+ Die Modulbeschreibung von FRM_OUT gibt es nur auf Englisch.
+
+
+=end html_DE
+
=cut
diff --git a/fhem/FHEM/20_FRM_PWM.pm b/fhem/FHEM/20_FRM_PWM.pm
index 137664fc4..91793579f 100755
--- a/fhem/FHEM/20_FRM_PWM.pm
+++ b/fhem/FHEM/20_FRM_PWM.pm
@@ -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
-
+
FRM_PWM
- This module represents a pin of a Firmata device
+ This module represents a pin of a Firmata device
that should be configured as a pulse width modulated output (PWM).
-
- Requires a defined FRM device to work. The pin must be listed in the internal reading "pwm_pins"
- of the FRM device (after connecting to the Firmata device) to be used as PWM output.
-
-
+
+ Requires a defined FRM device to work. The pin must be listed in the internal reading
+ "pwm_pins" of the FRM device (after connecting to the Firmata device) to be
+ used as PWM output.
+
+
Define
- define <name> FRM_PWM <pin>
-
- Defines the FRM_PWM device. <pin>> is the arduino-pin to use.
+ define <name> FRM_PWM <pin>
+
+ Defines the FRM_PWM device. <pin>> is the arduino-pin to use.
-
-
- Set
+
+
+ Set
- set <name> on
- sets the pulse-width to 100%
-
- -
-
set <name> off
- sets the pulse-width to 0%
-
- -
- set extensions are supported
-
- -
-
set <name> toggle
- toggles the pulse-width in between to the last value set by 'value' or 'dim' and 0 respectivly 100%
-
- -
-
set <name> value <value>
- sets the pulse-width to the value specified
- The min value is zero and the max value depends on the Firmata device (see internal reading
- "pwm_resolutions" of the FRM device). For 8 bits resolution the range
- is 0 to 255 (also see analogWrite() for details)
-
- -
-
set <name> dim <value>
- sets the pulse-width to the value specified in percent
- Range is from 0 to 100
-
- -
-
set <name> dimUp
- increases the pulse-width by 10%
-
- -
-
set <name> dimDown
- decreases the pulse-width by 10%
-
+ set <name> on
+ sets the pulse-width to 100%
+
+ -
+
set <name> off
+ sets the pulse-width to 0%
+
+ -
+ set extensions are supported
+
+ -
+
set <name> toggle
+ toggles the pulse-width in between to the last value set by 'value' or 'dim' and 0 respectivly 100%
+
+ -
+
set <name> value <value>
+ sets the pulse-width to the value specified
+ The min value is zero and the max value depends on the Firmata device (see internal reading
+ "pwm_resolutions" of the FRM device). For 8 bits resolution the range
+ is 0 to 255 (also see analogWrite() for details)
+
+ -
+
set <name> dim <value>
+ sets the pulse-width to the value specified in percent
+ Range is from 0 to 100
+
+ -
+
set <name> dimUp
+ increases the pulse-width by 10%
+
+ -
+
set <name> dimDown
+ decreases the pulse-width by 10%
+
-
-
+
+
Get
- N/A
+ -
+
get <dim>
+ returns current dim setting in percent, see description for set command for more details
+
+ -
+
get <value>
+ returns current dim setting, see description for set command for more details
+
-
-
+
+
Attributes
- - restoreOnStartup <on|off>
- - restoreOnReconnect <on|off>
- - IODev
- Specify which FRM to use. (Optional, only required if there is more
- than one FRM-device defined.)
-
- - eventMap
- - readingFnAttributes
+
+ - restoreOnStartup <on|off>
+
+
+ - restoreOnReconnect <on|off>
+
+
+ - IODev
+ Specify which FRM to use. Only required if there is more than one FRM-device defined.
+
+
+ - global attributes
+
+ - readingFnAttributes
-
-
+
+
Notes
- - attribute stateFormat
- In most cases it is a good idea to assign "value" to the attribute stateFormat. This will show the
- current value of the pin in the web interface.
-
-
+ - attribute stateFormat
+ In most cases it is a good idea to assign "value" to the attribute stateFormat. This will show the
+ current value of the pin in the web interface.
+
+
=end html
+
+=begin html_DE
+
+
+FRM_PWM
+
+ Die Modulbeschreibung von FRM_PWM gibt es nur auf Englisch.
+
+
+=end html_DE
+
=cut
diff --git a/fhem/FHEM/20_FRM_RGB.pm b/fhem/FHEM/20_FRM_RGB.pm
index fe76dc0af..e29884ccc 100644
--- a/fhem/FHEM/20_FRM_RGB.pm
+++ b/fhem/FHEM/20_FRM_RGB.pm
@@ -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
-
+
FRM_RGB
allows to drive LED-controllers and other multichannel-devices that use PWM as input by an Arduino running Firmata
- The value set will be output by the specified pins as pulse-width-modulated signals.
- Requires a defined FRM-device to work.
-
-
+ The value set will be output by the specified pins as pulse-width-modulated signals.
+ Requires a defined FRM-device to work.
+
+
Define
define <name> FRM_RGB <pin> <pin> <pin> [pin...]
Defines the FRM_RGB device. <pin>> are the arduino-pin to use.
For rgb-controlled devices first pin drives red, second pin green and third pin blue.
-
+
-
+
Set
- set <name> on
- sets the pulse-width of all configured pins to 100%
+ set <name> on
+ sets the pulse-width of all configured pins to 100%
- set <name> off
- sets the pulse-width of all configured pins to 0%
+ set <name> off
+ sets the pulse-width of all configured pins to 0%
+ set extensions are supported
- set <name> toggle
- toggles in between the last dimmed value, 0% and 100%. If no dimmed value was set before defaults to pulsewidth 50% on all channels
+ set <name> toggle
+ toggles in between the last dimmed value, 0% and 100%. If no dimmed value was set before defaults to pulsewidth 50% on all channels
- set <name> rgb <value>
- sets the pulse-width of all channels at once. Also sets the value toggle can switch to
- Value is encoded as hex-string, 2-digigs per channel (e.g. FFFFFF for reguler rgb)
+ set <name> rgb <value>
+ sets the pulse-width of all channels at once. Also sets the value toggle can switch to
+ Value is encoded as hex-string, 2-digigs per channel (e.g. FFFFFF for reguler rgb)
- set <name> pct <value>
- dims all channels at once while leving the ratio in between the channels unaltered.
- Range is 0-100 ('pct' stands for 'percent')
+ set <name> pct <value>
+ dims all channels at once while leving the ratio in between the channels unaltered.
+ Range is 0-100 ('pct' stands for 'percent')
- set <name> dimUp
- dims up by 10%
+ set <name> dimUp
+ dims up by 10%
- set <name> dimDown
- dims down by 10%
-
+ set <name> dimDown
+ dims down by 10%
+
+
+
Get
- get <name> rgb
- returns the values set for all channels. Format is hex, 2 nybbles per channel.
+ get <name> rgb
+ returns the values set for all channels. Format is hex, 2 nybbles per channel.
- get <name> RGB
- 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'.
+ get <name> RGB
+ 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'.
- get <name> pct
- returns the value of the channel with the highest value scaled to the range of 0-100 (percent).
+ get <name> pct
+ returns the value of the channel with the highest value scaled to the range of 0-100 (percent).
-
+
+
Attributes
- - restoreOnStartup <on|off>
- - restoreOnReconnect <on|off>
- - IODev
- Specify which FRM to use. (Optional, only required if there is more
- than one FRM-device defined.)
-
- - eventMap
- - readingFnAttributes
-
+
+ restoreOnStartup <on|off>
+
+
+ restoreOnReconnect <on|off>
+
+
+ IODev
+ Specify which FRM to use. Only required if there is more than one FRM-device defined.
+
+
+ global attributes
+
+ readingFnAttributes
-
+
=end html
+
+=begin html_DE
+
+
+FRM_RGB
+
+ Die Modulbeschreibung von FRM_RGB gibt es nur auf Englisch.
+
+
+=end html_DE
+
=cut
diff --git a/fhem/FHEM/20_FRM_ROTENC.pm b/fhem/FHEM/20_FRM_ROTENC.pm
index 753316185..83e5ad932 100755
--- a/fhem/FHEM/20_FRM_ROTENC.pm
+++ b/fhem/FHEM/20_FRM_ROTENC.pm
@@ -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 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 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
-
+
FRM_ROTENC
represents a rotary-encoder attached to two pins of an Arduino running Firmata
- Requires a defined FRM-device to work.
-
-
+ Requires a defined FRM-device to work.
+
+
Define
define <name> FRM_ROTENC <pinA> <pinB> [id]
Defines the FRM_ROTENC device. <pinA>> and <pinA>> are the arduino-pins to use.
[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).
-
+
-
+
Set
+
- reset
resets to value of 'position' to 0
- offset <value>
set offset value of 'position'
-
+
+
+
Get
- position
@@ -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
this value is reset to 0 whenever Arduino restarts or Firmata is reinitialized
-
+
+
Attributes
+
+ - IODev
+ Specify which FRM to use. Only required if there is more than one FRM-device defined.
+
+
+ - global attributes
+
+ - readingFnAttributes
-
+
=end html
+
+=begin html_DE
+
+
+FRM_ROTENC
+
+ Die Modulbeschreibung von FRM_ROTENC gibt es nur auf Englisch.
+
+
+=end html_DE
+
=cut
diff --git a/fhem/FHEM/20_FRM_SERVO.pm b/fhem/FHEM/20_FRM_SERVO.pm
index bedb4486f..720bfb8fb 100755
--- a/fhem/FHEM/20_FRM_SERVO.pm
+++ b/fhem/FHEM/20_FRM_SERVO.pm
@@ -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
-
+
FRM_SERVO
represents a pin of an Arduino running Firmata
configured to drive a pwm-controlled servo-motor.
- The value set will be drive the shaft of the servo to the specified angle. see Servo.write for values and range
- Requires a defined FRM-device to work.
-
-
+ The value set will be drive the shaft of the servo to the specified angle. see Servo.write for values and range
+ Requires a defined FRM-device to work.
+
+
Define
define <name> FRM_SERVO <pin>
Defines the FRM_SERVO device. <pin>> is the arduino-pin to use.
-
+
-
+
Set
set <name> angle <value>
sets the angle of the servo-motors shaft to the value specified (in degrees).
-
+
+
Get
-
+
+
Attributes
- - IODev
- Specify which FRM to use. (Optional, only required if there is more
- than one FRM-device defined.)
-
- - min-pulse
- sets the minimum puls-width to use. Defaults to 544. For most servos this translates into a rotation of 180° counterclockwise.
- - max-pulse
- sets the maximum puls-width to use. Defaults to 2400. For most servos this translates into a rotation of 180° clockwise
- - eventMap
- - readingFnAttributes
-
+
+ - IODev
+ Specify which FRM to use. Only required if there is more than one FRM-device defined.
+
+
+
+ - min-pulse
+ sets the minimum puls-width to use. Defaults to 544. For most servos this translates into a rotation of 180° counterclockwise.
+
+
+
+ - max-pulse
+ sets the maximum puls-width to use. Defaults to 2400. For most servos this translates into a rotation of 180° clockwise.
+
+
+ - global attributes
+
+ - readingFnAttributes
-
+
=end html
+
+=begin html_DE
+
+
+FRM_SERVO
+
+ Die Modulbeschreibung von FRM_SERVO gibt es nur auf Englisch.
+
+
+=end html_DE
+
=cut
diff --git a/fhem/FHEM/20_FRM_STEPPER.pm b/fhem/FHEM/20_FRM_STEPPER.pm
index 30dbb942d..bdbaed7df 100755
--- a/fhem/FHEM/20_FRM_STEPPER.pm
+++ b/fhem/FHEM/20_FRM_STEPPER.pm
@@ -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 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 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
FRM_STEPPER
- represents a stepper-motor attached to digital-i/o pins of an Arduino running Firmata
- Requires a defined FRM-device to work.
-
+ represents a stepper-motor attached to digital-i/o pins of an Arduino
+ running Firmata
+ Requires a defined FRM-device to work.
+
Define
- define <name> FRM_STEPPER [DRIVER|TWO_WIRE|FOUR_WIRE] <directionPin> <stepPin> [motorPin3 motorPin4] stepsPerRev [stepper-id]
- Defines the FRM_STEPPER device.
- - [DRIVER|TWO_WIRE|FOUR_WIRE] defines the control-sequence being used to drive the motor.
-
- - 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.
- - FOUR_WIRE: motor is attached via four wires each driving one coil individually.
- - 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.
-
-
- -
-
- - The sequence of control signals for 4 control wires is as follows:
-
-
-Step C0 C1 C2 C3
- 1 1 0 1 0
- 2 0 1 1 0
- 3 0 1 0 1
- 4 1 0 0 1
-
-
- - The sequence of controls signals for 2 control wires is as follows:
-(columns C1 and C2 from above):
-
-
-Step C0 C1
- 1 0 1
- 2 1 1
- 3 1 0
- 4 0 0
-
-
-
-
- -
- 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:
- 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
- define stepper FRM_STEPPER FOUR_WIRE 7 5 6 8 64 0
- when being connected to the arduio with:
- motor pin1 <-> arduino pin5
- motor pin2 <-> arduino pin6
- motor pin3 <-> arduino pin7
- motor pin4 <-> arduino pin8
- motor pin5 <-> ground
-
-
-
-
+ define <name> FRM_STEPPER [DRIVER|TWO_WIRE|FOUR_WIRE] <directionPin> <stepPin> [motorPin3 motorPin4] stepsPerRev [stepper-id]
+ Defines the FRM_STEPPER device.
+ - [DRIVER|TWO_WIRE|FOUR_WIRE] defines the control-sequence being used to drive the motor.
+
+ - 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.
+
+ - FOUR_WIRE: motor is attached via four wires each driving one coil individually.
+ - 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.
+
+
+
+ -
+
+ - The sequence of control signals for 4 control wires is as follows:
+
+
+ Step C0 C1 C2 C3
+ 1 1 0 1 0
+ 2 0 1 1 0
+ 3 0 1 0 1
+ 4 1 0 0 1
+
+
+ - The sequence of controls signals for 2 control wires is as follows:
+ (columns C1 and C2 from above):
+
+
+ Step C0 C1
+ 1 0 1
+ 2 1 1
+ 3 1 0
+ 4 0 0
+
+
+
+
+ -
+ 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:
+ 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
+ define stepper FRM_STEPPER FOUR_WIRE 7 5 6 8 64 0
+ when being connected to the arduio with:
+
+
+ motor pin1 <-> arduino pin5
+ motor pin2 <-> arduino pin6
+ motor pin3 <-> arduino pin7
+ motor pin4 <-> arduino pin8
+ motor pin5 <-> ground
+
+
+
+
Set
- set <name> reset
- - resets the reading 'position' to 0 without moving the motor
-
- set <name> position <position> [speed] [acceleration] [deceleration]
- - moves the motor to the absolute position specified. positive or negative integer
- 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
- acceleration and deceleration are optional.
-
-
- set <name> step <stepstomove> [speed] [accel] [decel]
- - moves the motor the number of steps specified. positive or negative integer
- speed, accelleration and deceleration are optional.
-
-
+ set <name> reset
+ resets the reading 'position' to 0 without moving the motor
+
+ set <name> position <position> [speed] [acceleration] [deceleration]
+ moves the motor to the absolute position specified. positive or negative integer
+ 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
+ acceleration and deceleration are optional.
+
+
+ set <name> step <stepstomove> [speed] [accel] [decel]
+ moves the motor the number of steps specified. positive or negative integer
+ speed, accelleration and deceleration are optional.
+
+
+
Get
- N/A
+ get <position>
+ - returns the current position value
+
Attributes
- - restoreOnStartup <on|off>
- - restoreOnReconnect <on|off>
- - IODev
- Specify which FRM to use. (Optional, only required if there is more
- than one FRM-device defined.)
-
- - >speed (same meaning as in 'set position')
- - acceleration (same meaning as in 'set position')
- - deceleration (same meaning as in 'set position')
- - eventMap
- - readingFnAttributes
-
+
+ restoreOnStartup <on|off>
+
+
+ restoreOnReconnect <on|off>
+
+
+ IODev
+ Specify which FRM to use. Only required if there is more than one FRM-device defined.
+
+
+
+ >speed (same meaning as in 'set position')
+
+
+ acceleration (same meaning as in 'set position')
+
+
+ deceleration (same meaning as in 'set position')
+
+ global attributes
+
+ readingFnAttributes
-
+
=end html
+
+=begin html_DE
+
+
+FRM_STEPPER
+
+ Die Modulbeschreibung von FRM_STEPPER gibt es nur auf Englisch.
+
+
+=end html_DE
+
=cut
diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt
index e8b4bb87b..5cf898768 100644
--- a/fhem/MAINTAINER.txt
+++ b/fhem/MAINTAINER.txt
@@ -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