mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-04-12 02:39:57 +00:00
- check for IODev install error in Init, Get, Set, Attr and Undef - missing get/set argument metadata added - get/set argument verifier improved - moved define argument verification and decoding from Init to Define - error behaviour of Init, Get, Set and Attr standardized - annotaded module help of attributes for FHEMWEB git-svn-id: https://svn.fhem.de/fhem/trunk@23054 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
b94eb42175
commit
79a664abe5
@ -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
|
||||
|
@ -1,37 +1,42 @@
|
||||
########################################################################################
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
# FHEM module for one Firmata analog input pin
|
||||
#
|
||||
########################################################################################
|
||||
#
|
||||
# LICENSE AND COPYRIGHT
|
||||
#
|
||||
# Copyright (C) 2013 ntruchess
|
||||
# Copyright (C) 2016 jensb
|
||||
#
|
||||
# All rights reserved
|
||||
#
|
||||
# This script is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# The GNU General Public License can be found at
|
||||
# http://www.gnu.org/copyleft/gpl.html.
|
||||
# A copy is found in the textfile GPL.txt and important notices to the license
|
||||
# from the author is found in LICENSE.txt distributed with these scripts.
|
||||
#
|
||||
# This script is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# This copyright notice MUST APPEAR in all copies of the script!
|
||||
#
|
||||
########################################################################################
|
||||
|
||||
=encoding UTF-8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
FHEM module for one Firmata analog input pin
|
||||
|
||||
=head1 LICENSE AND COPYRIGHT
|
||||
|
||||
Copyright (C) 2013 ntruchess
|
||||
Copyright (C) 2016 jensb
|
||||
|
||||
All rights reserved
|
||||
|
||||
This script is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This script is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this script; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
A copy of the GNU General Public License, Version 2 can also be found at
|
||||
|
||||
http://www.gnu.org/licenses/old-licenses/gpl-2.0.
|
||||
|
||||
This copyright notice MUST APPEAR in all copies of the script!
|
||||
|
||||
=cut
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
@ -39,26 +44,24 @@ use warnings;
|
||||
|
||||
#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
|
||||
BEGIN {
|
||||
if (!grep(/FHEM\/lib$/,@INC)) {
|
||||
foreach my $inc (grep(/FHEM$/,@INC)) {
|
||||
push @INC,$inc."/lib";
|
||||
};
|
||||
};
|
||||
if (!grep(/FHEM\/lib$/,@INC)) {
|
||||
foreach my $inc (grep(/FHEM$/,@INC)) {
|
||||
push @INC,$inc."/lib";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
use Device::Firmata::Constants qw/ :all /;
|
||||
|
||||
#####################################
|
||||
|
||||
# get command default return values
|
||||
my %gets = (
|
||||
"reading" => "",
|
||||
"state" => "",
|
||||
"alarm-upper-threshold" => "off",
|
||||
"alarm-lower-threshold" => "off",
|
||||
"reading" => "",
|
||||
"state" => "",
|
||||
"alarm-upper-threshold" => "off",
|
||||
"alarm-lower-threshold" => "off",
|
||||
);
|
||||
|
||||
sub
|
||||
FRM_AD_Initialize($)
|
||||
sub FRM_AD_Initialize
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
@ -66,83 +69,102 @@ FRM_AD_Initialize($)
|
||||
$hash->{GetFn} = "FRM_AD_Get";
|
||||
$hash->{DefFn} = "FRM_Client_Define";
|
||||
$hash->{InitFn} = "FRM_AD_Init";
|
||||
|
||||
|
||||
$hash->{AttrList} = "IODev upper-threshold lower-threshold $main::readingFnAttributes";
|
||||
main::LoadModule("FRM");
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_AD_Init($$)
|
||||
sub FRM_AD_Init
|
||||
{
|
||||
my ($hash,$args) = @_;
|
||||
my $ret = FRM_Init_Pin_Client($hash,$args,PIN_ANALOG);
|
||||
return $ret if (defined $ret);
|
||||
my $firmata = $hash->{IODev}->{FirmataDevice};
|
||||
my $name = $hash->{NAME};
|
||||
my $resolution = 10;
|
||||
if (defined $firmata->{metadata}{analog_resolutions}) {
|
||||
$resolution = $firmata->{metadata}{analog_resolutions}{$hash->{PIN}}
|
||||
}
|
||||
$hash->{resolution} = $resolution;
|
||||
$hash->{".max"} = defined $resolution ? (1<<$resolution)-1 : 1024;
|
||||
eval {
|
||||
$firmata->observe_analog($hash->{PIN},\&FRM_AD_observer,$hash);
|
||||
};
|
||||
return FRM_Catch($@) if $@;
|
||||
if (! (defined AttrVal($name,"stateFormat",undef))) {
|
||||
$main::attr{$name}{"stateFormat"} = "reading";
|
||||
}
|
||||
if (! (defined AttrVal($name,"event-min-interval",undef))) {
|
||||
$main::attr{$name}{"event-min-interval"} = 5;
|
||||
}
|
||||
main::readingsSingleUpdate($hash,"state","Initialized",1);
|
||||
return undef;
|
||||
my ($hash,$args) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
return 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
|
||||
my $ret = FRM_Init_Pin_Client($hash, $args, Device::Firmata::Constants->PIN_ANALOG);
|
||||
if (defined($ret)) {
|
||||
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
eval {
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
my $resolution = 10;
|
||||
if (defined $firmata->{metadata}{analog_resolutions}) {
|
||||
$resolution = $firmata->{metadata}{analog_resolutions}{$hash->{PIN}}
|
||||
}
|
||||
$hash->{resolution} = $resolution;
|
||||
$hash->{".max"} = defined $resolution ? (1<<$resolution)-1 : 1024;
|
||||
$firmata->observe_analog($hash->{PIN},\&FRM_AD_observer,$hash);
|
||||
};
|
||||
if ($@) {
|
||||
$ret = FRM_Catch($@);
|
||||
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
if (!(defined AttrVal($name,"stateFormat",undef))) {
|
||||
$main::attr{$name}{"stateFormat"} = "reading";
|
||||
}
|
||||
|
||||
if (!(defined AttrVal($name,"event-min-interval",undef))) {
|
||||
$main::attr{$name}{"event-min-interval"} = 5;
|
||||
}
|
||||
|
||||
main::readingsSingleUpdate($hash,"state","Initialized",1);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_AD_observer
|
||||
sub FRM_AD_observer
|
||||
{
|
||||
my ($pin,$old,$new,$hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
Log3 $name,5,"onAnalogMessage for pin ".$pin.", old: ".(defined $old ? $old : "--").", new: ".(defined $new ? $new : "--");
|
||||
main::readingsBeginUpdate($hash);
|
||||
main::readingsBulkUpdate($hash,"reading",$new,1);
|
||||
my $upperthresholdalarm = ReadingsVal($name,"alarm-upper-threshold","off");
|
||||
if ( $new < AttrVal($name,"upper-threshold",$hash->{".max"}) ) {
|
||||
if ( $upperthresholdalarm eq "on" ) {
|
||||
main::readingsBulkUpdate($hash,"alarm-upper-threshold","off",1);
|
||||
}
|
||||
my $lowerthresholdalarm = ReadingsVal($name,"alarm-lower-threshold","off");
|
||||
if ( $new > AttrVal($name,"lower-threshold",-1) ) {
|
||||
if ( $lowerthresholdalarm eq "on" ) {
|
||||
main::readingsBulkUpdate($hash,"alarm-lower-threshold","off",1);
|
||||
}
|
||||
} else {
|
||||
if ( $lowerthresholdalarm eq "off" ) {
|
||||
main::readingsBulkUpdate($hash,"alarm-lower-threshold","on",1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ( $upperthresholdalarm eq "off" ) {
|
||||
main::readingsBulkUpdate($hash,"alarm-upper-threshold","on",1);
|
||||
}
|
||||
};
|
||||
main::readingsEndUpdate($hash,1);
|
||||
my ($pin,$old,$new,$hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
Log3 $name, 5, "$name: observer pin: ".$pin.", old: ".(defined $old ? $old : "--").", new: ".(defined $new ? $new : "--");
|
||||
main::readingsBeginUpdate($hash);
|
||||
main::readingsBulkUpdate($hash,"reading",$new,1);
|
||||
my $upperthresholdalarm = ReadingsVal($name,"alarm-upper-threshold","off");
|
||||
if ( $new < AttrVal($name,"upper-threshold",$hash->{".max"}) ) {
|
||||
if ( $upperthresholdalarm eq "on" ) {
|
||||
main::readingsBulkUpdate($hash,"alarm-upper-threshold","off",1);
|
||||
}
|
||||
my $lowerthresholdalarm = ReadingsVal($name,"alarm-lower-threshold","off");
|
||||
if ( $new > AttrVal($name,"lower-threshold",-1) ) {
|
||||
if ( $lowerthresholdalarm eq "on" ) {
|
||||
main::readingsBulkUpdate($hash,"alarm-lower-threshold","off",1);
|
||||
}
|
||||
} else {
|
||||
if ( $lowerthresholdalarm eq "off" ) {
|
||||
main::readingsBulkUpdate($hash,"alarm-lower-threshold","on",1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ( $upperthresholdalarm eq "off" ) {
|
||||
main::readingsBulkUpdate($hash,"alarm-upper-threshold","on",1);
|
||||
}
|
||||
};
|
||||
main::readingsEndUpdate($hash,1);
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_AD_Get($)
|
||||
sub FRM_AD_Get
|
||||
{
|
||||
my ($hash,@a) = @_;
|
||||
my $name = shift @a;
|
||||
my $cmd = shift @a;
|
||||
my $ret;
|
||||
my ($hash, $name, $cmd, @a) = @_;
|
||||
|
||||
return "get command missing" if(!defined($cmd));
|
||||
return "unknown get command '$cmd', choose one of " . join(":noArg ", sort keys %gets) . ":noArg" if(!defined($gets{$cmd}));
|
||||
|
||||
ARGUMENT_HANDLER: {
|
||||
$cmd eq "reading" and do {
|
||||
eval {
|
||||
my $result = eval {
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
return 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
return FRM_Client_FirmataDevice($hash)->analog_read($hash->{PIN});
|
||||
};
|
||||
return $@;
|
||||
return FRM_Catch($@) if ($@);
|
||||
return $result;
|
||||
};
|
||||
( $cmd eq "alarm-upper-threshold" or $cmd eq "alarm-lower-threshold" or $cmd eq "state" ) and do {
|
||||
return main::ReadingsVal($name,"count",$gets{$cmd});
|
||||
@ -151,8 +173,8 @@ FRM_AD_Get($)
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_AD_Attr($$$$) {
|
||||
sub FRM_AD_Attr
|
||||
{
|
||||
my ($command,$name,$attribute,$value) = @_;
|
||||
my $hash = $main::defs{$name};
|
||||
eval {
|
||||
@ -169,9 +191,9 @@ FRM_AD_Attr($$$$) {
|
||||
}
|
||||
};
|
||||
if ($@) {
|
||||
$@ =~ /^(.*)( at.*FHEM.*)$/;
|
||||
$hash->{STATE} = "error setting $attribute to $value: ".$1;
|
||||
return "cannot $command attribute $attribute to $value for $name: ".$1;
|
||||
my $ret = FRM_Catch($@);
|
||||
$hash->{STATE} = "$command $attribute error: " . $ret;
|
||||
return $hash->{STATE};
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,46 +201,65 @@ FRM_AD_Attr($$$$) {
|
||||
|
||||
=pod
|
||||
|
||||
CHANGES
|
||||
=head1 CHANGES
|
||||
|
||||
2016 jensb
|
||||
o modified sub FRM_AD_Init to catch exceptions and return error message
|
||||
|
||||
|
||||
19.01.2018 jensb
|
||||
o support analog resolution depending on device capability
|
||||
|
||||
24.08.2020 jensb
|
||||
o check for IODev install error in Init and Get
|
||||
o prototypes removed
|
||||
o set argument verifier added
|
||||
|
||||
22.10.2020 jensb
|
||||
o annotaded module help of attributes for FHEMWEB
|
||||
|
||||
=cut
|
||||
|
||||
=pod
|
||||
|
||||
=head1 FHEM COMMANDREF METADATA
|
||||
|
||||
=over
|
||||
|
||||
=item device
|
||||
|
||||
=item summary Firmata: analog input
|
||||
=item summary_DE Firmata: analog Eingang
|
||||
|
||||
=item summary_DE Firmata: analoger Eingang
|
||||
|
||||
=back
|
||||
|
||||
=head1 INSTALLATION AND CONFIGURATION
|
||||
|
||||
=begin html
|
||||
|
||||
<a name="FRM_AD"></a>
|
||||
<a name="FRM_AD"/>
|
||||
<h3>FRM_AD</h3>
|
||||
<ul>
|
||||
This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a>
|
||||
This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a>
|
||||
that should be configured as an analog input.<br><br>
|
||||
|
||||
|
||||
Requires a defined <a href="#FRM">FRM</a> device to work. The pin must be listed in the internal reading "<a href="#FRMinternals">analog_pins</a>"<br>
|
||||
of the FRM device (after connecting to the Firmata device) to be used as analog input.<br><br>
|
||||
|
||||
<a name="FRM_ADdefine"></a>
|
||||
of the FRM device (after connecting to the Firmata device) to be used as analog input.<br><br>
|
||||
|
||||
<a name="FRM_ADdefine"/>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> FRM_AD <pin></code><br><br>
|
||||
|
||||
Defines the FRM_AD device. <pin> is the arduino-pin to use.
|
||||
<code>define <name> FRM_AD <pin></code><br><br>
|
||||
|
||||
Defines the FRM_AD device. <pin> is the arduino-pin to use.
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_ADset"></a>
|
||||
|
||||
<a name="FRM_ADset"/>
|
||||
<b>Set</b><br>
|
||||
<ul>
|
||||
N/A<br>
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_ADget"></a>
|
||||
|
||||
<a name="FRM_ADget"/>
|
||||
<b>Get</b><br>
|
||||
<ul>
|
||||
<li>reading<br>
|
||||
@ -236,35 +277,52 @@ FRM_AD_Attr($$$$) {
|
||||
<li>state<br>
|
||||
returns the 'state' reading</li>
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_ADattr"></a>
|
||||
|
||||
<a name="FRM_ADattr"/>
|
||||
<b>Attributes</b><br>
|
||||
<ul>
|
||||
<li>upper-threshold<br>
|
||||
sets the 'upper-threshold'. Whenever the 'reading' exceeds this value 'alarm-upper-threshold' is set to 'on'<br>
|
||||
As soon 'reading' falls below the 'upper-threshold' 'alarm-upper-threshold' turns 'off' again<br>
|
||||
Defaults to the max pin resolution plus one.</li>
|
||||
<li>lower-threshold<br>
|
||||
sets the 'lower-threshold'. Whenever the 'reading' falls below this value 'alarm-lower-threshold' is set to 'on'<br>
|
||||
As soon 'reading' rises above the 'lower-threshold' 'alarm-lower-threshold' turns 'off' again<br>
|
||||
Defaults to -1.</li>
|
||||
<li><a href="#IODev">IODev</a><br>
|
||||
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
|
||||
than one FRM-device defined.)
|
||||
</li>
|
||||
<li><a href="#eventMap">eventMap</a><br></li>
|
||||
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
|
||||
<a name="upper-threshold"/>
|
||||
<li>upper-threshold<br>
|
||||
sets the 'upper-threshold'. Whenever the 'reading' exceeds this value 'alarm-upper-threshold' is set to 'on'<br>
|
||||
As soon 'reading' falls below the 'upper-threshold' 'alarm-upper-threshold' turns 'off' again<br>
|
||||
Defaults to the max pin resolution plus one.</li>
|
||||
|
||||
<a name="lower-threshold"/>
|
||||
<li>lower-threshold<br>
|
||||
sets the 'lower-threshold'. Whenever the 'reading' falls below this value 'alarm-lower-threshold' is set to 'on'<br>
|
||||
As soon 'reading' rises above the 'lower-threshold' 'alarm-lower-threshold' turns 'off' again<br>
|
||||
Defaults to -1.</li>
|
||||
|
||||
<a name="IODev"/>
|
||||
<li><a href="#IODev">IODev</a><br>
|
||||
Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
|
||||
</li>
|
||||
|
||||
<li><a href="#attributes">global attributes</a></li>
|
||||
|
||||
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_ADnotes"></a>
|
||||
|
||||
<a name="FRM_ADnotes"/>
|
||||
<b>Notes</b><br>
|
||||
<ul>
|
||||
<li>attribute <i>stateFormat</i><br>
|
||||
In most cases it is a good idea to assign "reading" to the attribute <i>stateFormat</i>. This will show the
|
||||
current value of the pin in the web interface.
|
||||
</li>
|
||||
</ul>
|
||||
<li>attribute <i>stateFormat</i><br>
|
||||
In most cases it is a good idea to assign "reading" to the attribute <i>stateFormat</i>. This will show the
|
||||
current value of the pin in the web interface.
|
||||
</li>
|
||||
</ul>
|
||||
</ul><br>
|
||||
|
||||
=end html
|
||||
|
||||
=begin html_DE
|
||||
|
||||
<a name="FRM_AD"/>
|
||||
<h3>FRM_AD</h3>
|
||||
<ul>
|
||||
Die Modulbeschreibung von FRM_AD gibt es nur auf <a href="commandref.html#FRM_AD">Englisch</a>. <br>
|
||||
</ul> <br>
|
||||
|
||||
=end html_DE
|
||||
|
||||
=cut
|
||||
|
@ -1,37 +1,42 @@
|
||||
########################################################################################
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
# FHEM module for one Firmata digial input pin
|
||||
#
|
||||
########################################################################################
|
||||
#
|
||||
# LICENSE AND COPYRIGHT
|
||||
#
|
||||
# Copyright (C) 2013 ntruchess
|
||||
# Copyright (C) 2018 jensb
|
||||
#
|
||||
# All rights reserved
|
||||
#
|
||||
# This script is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# The GNU General Public License can be found at
|
||||
# http://www.gnu.org/copyleft/gpl.html.
|
||||
# A copy is found in the textfile GPL.txt and important notices to the license
|
||||
# from the author is found in LICENSE.txt distributed with these scripts.
|
||||
#
|
||||
# This script is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# This copyright notice MUST APPEAR in all copies of the script!
|
||||
#
|
||||
########################################################################################
|
||||
|
||||
=encoding UTF-8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
FHEM module for one Firmata digial input pin
|
||||
|
||||
=head1 LICENSE AND COPYRIGHT
|
||||
|
||||
Copyright (C) 2013 ntruchess
|
||||
Copyright (C) 2018 jensb
|
||||
|
||||
All rights reserved
|
||||
|
||||
This script is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This script is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this script; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
A copy of the GNU General Public License, Version 2 can also be found at
|
||||
|
||||
http://www.gnu.org/licenses/old-licenses/gpl-2.0.
|
||||
|
||||
This copyright notice MUST APPEAR in all copies of the script!
|
||||
|
||||
=cut
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
@ -39,22 +44,22 @@ use warnings;
|
||||
|
||||
#add FHEM/lib to @INC if it's not already included. Should rather be in fhem.pl than here though...
|
||||
BEGIN {
|
||||
if (!grep(/FHEM\/lib$/,@INC)) {
|
||||
foreach my $inc (grep(/FHEM$/,@INC)) {
|
||||
push @INC,$inc."/lib";
|
||||
};
|
||||
};
|
||||
if (!grep(/FHEM\/lib$/,@INC)) {
|
||||
foreach my $inc (grep(/FHEM$/,@INC)) {
|
||||
push @INC,$inc."/lib";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
use Device::Firmata::Constants qw/ :all /;
|
||||
|
||||
#####################################
|
||||
|
||||
# default values for Attr
|
||||
my %sets = (
|
||||
"alarm" => "",
|
||||
"count" => 0,
|
||||
);
|
||||
|
||||
# default values for Get
|
||||
my %gets = (
|
||||
"reading" => "",
|
||||
"state" => "",
|
||||
@ -62,8 +67,7 @@ my %gets = (
|
||||
"alarm" => "off"
|
||||
);
|
||||
|
||||
sub
|
||||
FRM_IN_Initialize($)
|
||||
sub FRM_IN_Initialize
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
@ -78,8 +82,7 @@ FRM_IN_Initialize($)
|
||||
main::LoadModule("FRM");
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_IN_PinModePullupSupported($)
|
||||
sub FRM_IN_PinModePullupSupported
|
||||
{
|
||||
my ($hash) = @_;
|
||||
my $iodev = $hash->{IODev};
|
||||
@ -88,133 +91,160 @@ FRM_IN_PinModePullupSupported($)
|
||||
return defined($pullupPins);
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_IN_Init($$)
|
||||
sub FRM_IN_Init
|
||||
{
|
||||
my ($hash,$args) = @_;
|
||||
if (FRM_IN_PinModePullupSupported($hash)) {
|
||||
my $pullup = AttrVal($hash->{NAME},"internal-pullup","off");
|
||||
my $ret = FRM_Init_Pin_Client($hash,$args,defined($pullup) && ($pullup eq "on")? PIN_PULLUP : PIN_INPUT);
|
||||
return $ret if (defined $ret);
|
||||
eval {
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
my $pin = $hash->{PIN};
|
||||
$firmata->observe_digital($pin,\&FRM_IN_observer,$hash);
|
||||
};
|
||||
return FRM_Catch($@) if $@;
|
||||
} else {
|
||||
my $ret = FRM_Init_Pin_Client($hash,$args,PIN_INPUT);
|
||||
return $ret if (defined $ret);
|
||||
eval {
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
my $pin = $hash->{PIN};
|
||||
if (defined (my $pullup = AttrVal($hash->{NAME},"internal-pullup",undef))) {
|
||||
$firmata->digital_write($pin,$pullup eq "on" ? 1 : 0);
|
||||
}
|
||||
$firmata->observe_digital($pin,\&FRM_IN_observer,$hash);
|
||||
};
|
||||
return FRM_Catch($@) if $@;
|
||||
}
|
||||
if (! (defined AttrVal($hash->{NAME},"stateFormat",undef))) {
|
||||
$main::attr{$hash->{NAME}}{"stateFormat"} = "reading";
|
||||
}
|
||||
main::readingsSingleUpdate($hash,"state","Initialized",1);
|
||||
return undef;
|
||||
my ($hash,$args) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
return 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
|
||||
if (FRM_IN_PinModePullupSupported($hash)) {
|
||||
my $pullup = AttrVal($name, "internal-pullup", "off");
|
||||
my $ret = FRM_Init_Pin_Client($hash,$args,defined($pullup) && ($pullup eq "on")? Device::Firmata::Constants->PIN_PULLUP : Device::Firmata::Constants->PIN_INPUT);
|
||||
return $ret if (defined $ret);
|
||||
eval {
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
my $pin = $hash->{PIN};
|
||||
$firmata->observe_digital($pin,\&FRM_IN_observer,$hash);
|
||||
};
|
||||
if ($@) {
|
||||
my $ret = FRM_Catch($@);
|
||||
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
|
||||
return $ret;
|
||||
}
|
||||
} else {
|
||||
my $ret = FRM_Init_Pin_Client($hash, $args, Device::Firmata::Constants->PIN_INPUT);
|
||||
return $ret if (defined $ret);
|
||||
eval {
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
my $pin = $hash->{PIN};
|
||||
if (defined(my $pullup = AttrVal($name, "internal-pullup", undef))) {
|
||||
$firmata->digital_write($pin,$pullup eq "on" ? 1 : 0);
|
||||
}
|
||||
$firmata->observe_digital($pin,\&FRM_IN_observer,$hash);
|
||||
};
|
||||
if ($@) {
|
||||
my $ret = FRM_Catch($@);
|
||||
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(defined AttrVal($name, "stateFormat", undef))) {
|
||||
$main::attr{$name}{"stateFormat"} = "reading";
|
||||
}
|
||||
|
||||
main::readingsSingleUpdate($hash,"state","Initialized",1);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_IN_observer($$$$)
|
||||
sub FRM_IN_observer
|
||||
{
|
||||
my ($pin,$last,$new,$hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $old = ReadingsVal($name, "reading", undef);
|
||||
if (defined($old)) {
|
||||
$old = $old eq "on" ? PIN_HIGH : PIN_LOW;
|
||||
}
|
||||
if (AttrVal($hash->{NAME},"activeLow","no") eq "yes") {
|
||||
$new = $new == PIN_LOW ? PIN_HIGH : PIN_LOW;
|
||||
}
|
||||
Log3 $name, 5, "$name observer pin: $pin, old: ".(defined $old ? $old : "--").", new: ".(defined $new ? $new : "--");
|
||||
my $changed = !defined($old) || $old != $new;
|
||||
if ($changed) {
|
||||
main::readingsBeginUpdate($hash);
|
||||
if (defined (my $mode = main::AttrVal($name,"count-mode",undef))) {
|
||||
if (($mode eq "both")
|
||||
or (($mode eq "rising") and ($new == PIN_HIGH))
|
||||
or (($mode eq "falling") and ($new == PIN_LOW))) {
|
||||
my $count = main::ReadingsVal($name,"count",0);
|
||||
$count++;
|
||||
if (defined (my $threshold = main::AttrVal($name,"count-threshold",undef))) {
|
||||
if ( $count > $threshold ) {
|
||||
if (AttrVal($name,"reset-on-threshold-reached","no") eq "yes") {
|
||||
$count=0;
|
||||
main::readingsBulkUpdate($hash,"alarm","on",1);
|
||||
} elsif ( main::ReadingsVal($name,"alarm","off") ne "on" ) {
|
||||
main::readingsBulkUpdate($hash,"alarm","on",1);
|
||||
}
|
||||
}
|
||||
}
|
||||
main::readingsBulkUpdate($hash,"count",$count,1);
|
||||
}
|
||||
};
|
||||
main::readingsBulkUpdate($hash,"reading",$new == PIN_HIGH ? "on" : "off", 1);
|
||||
main::readingsEndUpdate($hash,1);
|
||||
}
|
||||
my ($pin,$last,$new,$hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $old = ReadingsVal($name, "reading", undef);
|
||||
if (defined($old)) {
|
||||
$old = $old eq "on" ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW;
|
||||
}
|
||||
if (AttrVal($hash->{NAME},"activeLow","no") eq "yes") {
|
||||
$new = $new == Device::Firmata::Constants->PIN_LOW ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW;
|
||||
}
|
||||
Log3 $name, 5, "$name: observer pin: $pin, old: ".(defined $old ? $old : "--").", new: ".(defined $new ? $new : "--");
|
||||
|
||||
my $changed = !defined($old) || $old != $new;
|
||||
if ($changed) {
|
||||
main::readingsBeginUpdate($hash);
|
||||
if (defined (my $mode = main::AttrVal($name,"count-mode",undef))) {
|
||||
if (($mode eq "both")
|
||||
or (($mode eq "rising") and ($new == Device::Firmata::Constants->PIN_HIGH))
|
||||
or (($mode eq "falling") and ($new == Device::Firmata::Constants->PIN_LOW))) {
|
||||
my $count = main::ReadingsVal($name,"count",0);
|
||||
$count++;
|
||||
if (defined (my $threshold = main::AttrVal($name,"count-threshold",undef))) {
|
||||
if ( $count > $threshold ) {
|
||||
if (AttrVal($name,"reset-on-threshold-reached","no") eq "yes") {
|
||||
$count=0;
|
||||
main::readingsBulkUpdate($hash,"alarm","on",1);
|
||||
} elsif ( main::ReadingsVal($name,"alarm","off") ne "on" ) {
|
||||
main::readingsBulkUpdate($hash,"alarm","on",1);
|
||||
}
|
||||
}
|
||||
}
|
||||
main::readingsBulkUpdate($hash,"count",$count,1);
|
||||
}
|
||||
};
|
||||
main::readingsBulkUpdate($hash, "reading", $new == Device::Firmata::Constants->PIN_HIGH ? "on" : "off", 1);
|
||||
main::readingsEndUpdate($hash,1);
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_IN_Set($@)
|
||||
sub FRM_IN_Set
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
return "set command missing" if(@a < 2 || !defined($a[1]));
|
||||
return "unknown set command '$a[1]', choose one of " . join(" ", sort keys %sets) if(!defined($sets{$a[1]}));
|
||||
my $command = $a[1];
|
||||
my $value = $a[2];
|
||||
my ($hash, $name, $cmd, @a) = @_;
|
||||
|
||||
return "set command missing" if(!defined($cmd));
|
||||
return "unknown set command '$cmd', choose one of " . join(" ", sort keys %sets) if(!defined($sets{$cmd}));
|
||||
return "$cmd requires 1 argument" unless (@a == 1);
|
||||
|
||||
my $value = shift @a;
|
||||
COMMAND_HANDLER: {
|
||||
$command eq "alarm" and do {
|
||||
$cmd eq "alarm" and do {
|
||||
return undef if (!($value eq "off" or $value eq "on"));
|
||||
main::readingsSingleUpdate($hash,"alarm",$value,1);
|
||||
last;
|
||||
};
|
||||
$command eq "count" and do {
|
||||
$cmd eq "count" and do {
|
||||
main::readingsSingleUpdate($hash,"count",$value,1);
|
||||
last;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_IN_Get($@)
|
||||
sub FRM_IN_Get
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
return "get command missing" if(@a < 2 || !defined($a[1]));
|
||||
return "unknown get command '$a[1]', choose one of " . join(":noArg ", sort keys %gets) . ":noArg" if(!defined($gets{$a[1]}));
|
||||
my $name = shift @a;
|
||||
my $cmd = shift @a;
|
||||
my ($hash, $name, $cmd, @a) = @_;
|
||||
|
||||
return "get command missing" if(!defined($cmd));
|
||||
return "unknown get command '$cmd', choose one of " . join(":noArg ", sort keys %gets) . ":noArg" if(!defined($gets{$cmd}));
|
||||
|
||||
ARGUMENT_HANDLER: {
|
||||
( $cmd eq "reading" ) and do {
|
||||
$cmd eq "reading" and do {
|
||||
my $last;
|
||||
eval {
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
die 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
$last = FRM_Client_FirmataDevice($hash)->digital_read($hash->{PIN});
|
||||
if (AttrVal($hash->{NAME},"activeLow","no") eq "yes") {
|
||||
$last = $last == PIN_LOW ? PIN_HIGH : PIN_LOW;
|
||||
$last = $last == Device::Firmata::Constants->PIN_LOW ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW;
|
||||
}
|
||||
};
|
||||
return FRM_Catch($@) if $@;
|
||||
return $last == PIN_HIGH ? "on" : "off";
|
||||
if ($@) {
|
||||
my $ret = FRM_Catch($@);
|
||||
$hash->{STATE} = "get $cmd error: " . $ret;
|
||||
return $hash->{STATE};
|
||||
}
|
||||
return $last == Device::Firmata::Constants->PIN_HIGH ? "on" : "off";
|
||||
};
|
||||
( $cmd eq "count" or $cmd eq "alarm" or $cmd eq "state" ) and do {
|
||||
|
||||
($cmd eq "count" or $cmd eq "alarm" or $cmd eq "state") and do {
|
||||
return main::ReadingsVal($name,$cmd,$gets{$cmd});
|
||||
};
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_IN_Attr($$$$) {
|
||||
sub FRM_IN_Attr
|
||||
{
|
||||
my ($command,$name,$attribute,$value) = @_;
|
||||
my $hash = $main::defs{$name};
|
||||
my $pin = $hash->{PIN};
|
||||
|
||||
eval {
|
||||
if ($command eq "set") {
|
||||
ARGUMENT_HANDLER: {
|
||||
@ -225,12 +255,14 @@ FRM_IN_Attr($$$$) {
|
||||
}
|
||||
last;
|
||||
};
|
||||
|
||||
$attribute eq "count-mode" and do {
|
||||
if ($value ne "none" and !defined main::ReadingsVal($name,"count",undef)) {
|
||||
main::readingsSingleUpdate($main::defs{$name},"count",$sets{count},1);
|
||||
}
|
||||
last;
|
||||
};
|
||||
|
||||
$attribute eq "reset-on-threshold-reached" and do {
|
||||
if ($value eq "yes"
|
||||
and defined (my $threshold = main::AttrVal($name,"count-threshold",undef))) {
|
||||
@ -240,6 +272,7 @@ FRM_IN_Attr($$$$) {
|
||||
}
|
||||
last;
|
||||
};
|
||||
|
||||
$attribute eq "count-threshold" and do {
|
||||
if (main::ReadingsVal($name,"count",0) > $value) {
|
||||
main::readingsBeginUpdate($hash);
|
||||
@ -253,11 +286,15 @@ FRM_IN_Attr($$$$) {
|
||||
}
|
||||
last;
|
||||
};
|
||||
|
||||
$attribute eq "internal-pullup" and do {
|
||||
if ($main::init_done) {
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
die 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
if (FRM_IN_PinModePullupSupported($hash)) {
|
||||
$firmata->pin_mode($pin,$value eq "on"? PIN_PULLUP : PIN_INPUT);
|
||||
$firmata->pin_mode($pin, $value eq "on"? Device::Firmata::Constants->PIN_PULLUP : Device::Firmata::Constants->PIN_INPUT);
|
||||
} else {
|
||||
$firmata->digital_write($pin,$value eq "on" ? 1 : 0);
|
||||
#ignore any errors here, the attribute-value will be applied next time FRM_IN_init() is called.
|
||||
@ -265,11 +302,15 @@ FRM_IN_Attr($$$$) {
|
||||
}
|
||||
last;
|
||||
};
|
||||
|
||||
$attribute eq "activeLow" and do {
|
||||
my $oldval = AttrVal($hash->{NAME},"activeLow","no");
|
||||
if ($oldval ne $value) {
|
||||
$main::attr{$hash->{NAME}}{activeLow} = $value;
|
||||
if ($main::init_done) {
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
die 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
FRM_IN_observer($pin,undef,$firmata->digital_read($pin),$hash);
|
||||
}
|
||||
@ -280,16 +321,23 @@ FRM_IN_Attr($$$$) {
|
||||
} elsif ($command eq "del") {
|
||||
ARGUMENT_HANDLER: {
|
||||
$attribute eq "internal-pullup" and do {
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
die 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
if (FRM_IN_PinModePullupSupported($hash)) {
|
||||
$firmata->pin_mode($pin,PIN_INPUT);
|
||||
$firmata->pin_mode($pin, Device::Firmata::Constants->PIN_INPUT);
|
||||
} else {
|
||||
$firmata->digital_write($pin,0);
|
||||
}
|
||||
last;
|
||||
};
|
||||
|
||||
$attribute eq "activeLow" and do {
|
||||
if (AttrVal($hash->{NAME},"activeLow","no") eq "yes") {
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
die 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
delete $main::attr{$hash->{NAME}}{activeLow};
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
FRM_IN_observer($pin,undef,$firmata->digital_read($pin),$hash);
|
||||
@ -299,9 +347,10 @@ FRM_IN_Attr($$$$) {
|
||||
}
|
||||
}
|
||||
};
|
||||
if (my $error = FRM_Catch($@)) {
|
||||
$hash->{STATE} = "error setting $attribute to $value: ".$error;
|
||||
return "cannot $command attribute $attribute to $value for $name: ".$error;
|
||||
if ($@) {
|
||||
my $ret = FRM_Catch($@);
|
||||
$hash->{STATE} = "$command $attribute error: " . $ret;
|
||||
return $hash->{STATE};
|
||||
}
|
||||
}
|
||||
|
||||
@ -309,11 +358,11 @@ FRM_IN_Attr($$$$) {
|
||||
|
||||
=pod
|
||||
|
||||
CHANGES
|
||||
=head1 CHANGES
|
||||
|
||||
15.02.2019 jensb
|
||||
o bugfix: change detection no longer assumes that reading "reading" is defined
|
||||
|
||||
|
||||
04.11.2018 jensb
|
||||
o bugfix: get alarm/reading/state
|
||||
o feature: remove unused FHEMWEB input field from all get commands
|
||||
@ -324,8 +373,17 @@ FRM_IN_Attr($$$$) {
|
||||
03.01.2018 jensb
|
||||
o implemented Firmata 2.5 feature PIN_MODE_PULLUP (requires perl-firmata 0.64 or higher)
|
||||
|
||||
24.08.2020 jensb
|
||||
o check for IODev install error in Init, Get and Attr
|
||||
o prototypes removed
|
||||
o set argument verifier improved
|
||||
|
||||
19.10.2020 jensb
|
||||
o annotaded module help of attributes for FHEMWEB
|
||||
|
||||
=cut
|
||||
|
||||
|
||||
=pod
|
||||
|
||||
=head1 FHEM COMMANDREF METADATA
|
||||
@ -344,7 +402,7 @@ FRM_IN_Attr($$$$) {
|
||||
|
||||
=begin html
|
||||
|
||||
<a name="FRM_IN"></a>
|
||||
<a name="FRM_IN"/>
|
||||
<h3>FRM_IN</h3>
|
||||
<ul>
|
||||
This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a>
|
||||
@ -354,7 +412,7 @@ FRM_IN_Attr($$$$) {
|
||||
the internal reading <a href="#FRMinternals">"input_pins" or "pullup_pins"</a>
|
||||
of the FRM device (after connecting to the Firmata device) to be used as digital input with or without pullup.<br><br>
|
||||
|
||||
<a name="FRM_INdefine"></a>
|
||||
<a name="FRM_INdefine"/>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> FRM_IN <pin></code> <br>
|
||||
@ -362,7 +420,7 @@ FRM_IN_Attr($$$$) {
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
<a name="FRM_INset"></a>
|
||||
<a name="FRM_INset"/>
|
||||
<b>Set</b><br>
|
||||
<ul>
|
||||
<li>alarm on|off<br>
|
||||
@ -373,47 +431,64 @@ FRM_IN_Attr($$$$) {
|
||||
The counter is incremented depending on the attribute 'count-mode'.</li>
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_INget"></a>
|
||||
<a name="FRM_INget"/>
|
||||
<b>Get</b>
|
||||
<ul>
|
||||
<li>reading<br>
|
||||
returns the logical state of the input pin last received from the Firmata device depending on the attribute 'activeLow'.
|
||||
Values are 'on' and 'off'.<br></li>
|
||||
<li>count<br>
|
||||
returns the current counter value. Contains the number of toggles reported by the Fimata device on this input pin.
|
||||
returns the current counter value. Contains the number of toggles reported by the Fimata device on this input pin.
|
||||
Depending on the attribute 'count-mode' every rising or falling edge (or both) is counted.</li>
|
||||
<li>alarm<br>
|
||||
returns the 'alarm' reading. Values are 'on' and 'off' (Defaults to 'off').
|
||||
returns the 'alarm' reading. Values are 'on' and 'off' (Defaults to 'off').
|
||||
The 'alarm' reading doesn't clear itself, it has to be set to 'off' explicitly.</li>
|
||||
<li>state<br>
|
||||
returns the 'state' reading</li>
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_INattr"></a>
|
||||
<a name="FRM_INattr"/>
|
||||
<b>Attributes</b><br>
|
||||
<ul>
|
||||
<li>activeLow yes|no<br>
|
||||
inverts the logical state of the pin reading if set to yes (defaults to 'no').</li>
|
||||
<li>count-mode none|rising|falling|both<br>
|
||||
Determines whether 'rising' (transitions from 'off' to 'on') of falling (transitions from 'on' to 'off')
|
||||
edges (or 'both') are counted (defaults to 'none').</li>
|
||||
<li>count-threshold <number><br>
|
||||
sets the threshold-value for the counter - if defined whenever 'count' exceeds the 'count-threshold' the 'alarm' reading is
|
||||
set to 'on' (defaults to undefined). Use 'set alarm off' to clear the alarm.</li>
|
||||
<li>reset-on-threshold-reached yes|no<br>
|
||||
if set to 'yes' reset the counter to 0 when the threshold is reached (defaults to 'no').
|
||||
</li>
|
||||
<li>internal-pullup on|off<br>
|
||||
enables/disables the internal pullup resistor of the Firmata pin (defaults to 'off'). Requires hardware and firmware support.
|
||||
</li>
|
||||
<li><a href="#IODev">IODev</a><br>
|
||||
specify which <a href="#FRM">FRM</a> to use.
|
||||
</li>
|
||||
<li><a href="#eventMap">eventMap</a><br></li>
|
||||
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
|
||||
<a name="activeLow"/>
|
||||
<li>activeLow yes|no<br>
|
||||
inverts the logical state of the pin reading if set to yes (defaults to 'no').
|
||||
</li>
|
||||
|
||||
<a name="count-mode"/>
|
||||
<li>count-mode none|rising|falling|both<br>
|
||||
Determines whether 'rising' (transitions from 'off' to 'on') of falling (transitions from 'on' to 'off')
|
||||
edges (or 'both') are counted (defaults to 'none').
|
||||
</li>
|
||||
|
||||
<a name="count-threshold"/>
|
||||
<li>count-threshold <number><br>
|
||||
sets the threshold-value for the counter - if defined whenever 'count' exceeds the 'count-threshold'
|
||||
the 'alarm' reading is set to 'on' (defaults to undefined). Use 'set alarm off' to clear the alarm.
|
||||
</li>
|
||||
|
||||
<a name="reset-on-threshold-reached"/>
|
||||
<li>reset-on-threshold-reached yes|no<br>
|
||||
if set to 'yes' reset the counter to 0 when the threshold is reached (defaults to 'no').
|
||||
</li>
|
||||
|
||||
<a name="internal-pullup"/>
|
||||
<li>internal-pullup on|off<br>
|
||||
enables/disables the internal pullup resistor of the Firmata pin (defaults to 'off'). Requires hardware
|
||||
and firmware support.
|
||||
</li>
|
||||
|
||||
<a name="IODev"/>
|
||||
<li><a href="#IODev">IODev</a><br>
|
||||
Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
|
||||
</li>
|
||||
|
||||
<li><a href="#attributes">global attributes</a></li>
|
||||
|
||||
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_INnotes"></a>
|
||||
<a name="FRM_INnotes"/>
|
||||
<b>Notes</b><br>
|
||||
<ul>
|
||||
<li>attribute <i>stateFormat</i><br>
|
||||
@ -430,7 +505,7 @@ FRM_IN_Attr($$$$) {
|
||||
|
||||
=begin html_DE
|
||||
|
||||
<a name="FRM_IN"></a>
|
||||
<a name="FRM_IN"/>
|
||||
<h3>FRM_IN</h3>
|
||||
<ul>
|
||||
Die Modulbeschreibung von FRM_IN gibt es nur auf <a href="commandref.html#FRM_IN">Englisch</a>. <br>
|
||||
|
@ -1,37 +1,42 @@
|
||||
########################################################################################
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
# FHEM module for one Firmata digial output pin
|
||||
#
|
||||
########################################################################################
|
||||
#
|
||||
# LICENSE AND COPYRIGHT
|
||||
#
|
||||
# Copyright (C) 2013 ntruchess
|
||||
# Copyright (C) 2016 jensb
|
||||
#
|
||||
# All rights reserved
|
||||
#
|
||||
# This script is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# The GNU General Public License can be found at
|
||||
# http://www.gnu.org/copyleft/gpl.html.
|
||||
# A copy is found in the textfile GPL.txt and important notices to the license
|
||||
# from the author is found in LICENSE.txt distributed with these scripts.
|
||||
#
|
||||
# This script is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# This copyright notice MUST APPEAR in all copies of the script!
|
||||
#
|
||||
########################################################################################
|
||||
|
||||
=encoding UTF-8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
FHEM module for one Firmata digial output pin
|
||||
|
||||
=head1 LICENSE AND COPYRIGHT
|
||||
|
||||
Copyright (C) 2013 ntruchess
|
||||
Copyright (C) 2016 jensb
|
||||
|
||||
All rights reserved
|
||||
|
||||
This script is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This script is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this script; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
A copy of the GNU General Public License, Version 2 can also be found at
|
||||
|
||||
http://www.gnu.org/licenses/old-licenses/gpl-2.0.
|
||||
|
||||
This copyright notice MUST APPEAR in all copies of the script!
|
||||
|
||||
=cut
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
@ -39,19 +44,24 @@ use warnings;
|
||||
|
||||
#add FHEM/lib to @INC if it's not already included. Should rather be in fhem.pl than here though...
|
||||
BEGIN {
|
||||
if (!grep(/FHEM\/lib$/,@INC)) {
|
||||
foreach my $inc (grep(/FHEM$/,@INC)) {
|
||||
push @INC,$inc."/lib";
|
||||
};
|
||||
};
|
||||
if (!grep(/FHEM\/lib$/,@INC)) {
|
||||
foreach my $inc (grep(/FHEM$/,@INC)) {
|
||||
push @INC,$inc."/lib";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
use Device::Firmata::Constants qw/ :all /;
|
||||
use SetExtensions;
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FRM_OUT_Initialize($)
|
||||
|
||||
# number of arguments
|
||||
my %sets = (
|
||||
"on:noArg" => 0,
|
||||
"off:noArg" => 0,
|
||||
);
|
||||
|
||||
sub FRM_OUT_Initialize
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
@ -61,96 +71,127 @@ FRM_OUT_Initialize($)
|
||||
$hash->{UndefFn} = "FRM_Client_Undef";
|
||||
$hash->{AttrFn} = "FRM_OUT_Attr";
|
||||
$hash->{StateFn} = "FRM_OUT_State";
|
||||
|
||||
|
||||
$hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off activeLow:yes,no IODev valueMode:send,receive,bidirectional $main::readingFnAttributes";
|
||||
|
||||
main::LoadModule("FRM");
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_OUT_Init($$)
|
||||
sub FRM_OUT_Init
|
||||
{
|
||||
my ($hash,$args) = @_;
|
||||
my $ret = FRM_Init_Pin_Client($hash,$args,PIN_OUTPUT);
|
||||
return $ret if (defined $ret);
|
||||
eval {
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
my $pin = $hash->{PIN};
|
||||
$firmata->observe_digital($pin,\&FRM_OUT_observer,$hash);
|
||||
};
|
||||
my $name = $hash->{NAME};
|
||||
if (! (defined AttrVal($name,"stateFormat",undef))) {
|
||||
$main::attr{$name}{"stateFormat"} = "value";
|
||||
}
|
||||
my $value = ReadingsVal($name,"value",undef);
|
||||
if (!defined($value)) {
|
||||
readingsSingleUpdate($hash,"value","off",0);
|
||||
}
|
||||
if (AttrVal($hash->{NAME},"restoreOnReconnect", "on") eq "on") {
|
||||
FRM_OUT_Set($hash,$name,$value);
|
||||
}
|
||||
main::readingsSingleUpdate($hash,"state","Initialized",1);
|
||||
return undef;
|
||||
my ($hash,$args) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
return 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
|
||||
my $ret = FRM_Init_Pin_Client($hash, $args, Device::Firmata::Constants->PIN_OUTPUT);
|
||||
if (defined($ret)) {
|
||||
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
eval {
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
my $pin = $hash->{PIN};
|
||||
$firmata->observe_digital($pin,\&FRM_OUT_observer,$hash);
|
||||
};
|
||||
if ($@) {
|
||||
$ret = FRM_Catch($@);
|
||||
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
if (!(defined AttrVal($name,"stateFormat",undef))) {
|
||||
$main::attr{$name}{"stateFormat"} = "value";
|
||||
}
|
||||
|
||||
my $value = ReadingsVal($name,"value",undef);
|
||||
if (!defined($value)) {
|
||||
readingsSingleUpdate($hash,"value","off",0);
|
||||
}
|
||||
|
||||
if (AttrVal($name, "restoreOnReconnect", "on") eq "on") {
|
||||
FRM_OUT_Set($hash,$name,$value);
|
||||
}
|
||||
|
||||
main::readingsSingleUpdate($hash,"state","Initialized",1);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_OUT_observer($$$$)
|
||||
sub FRM_OUT_observer
|
||||
{
|
||||
my ($pin,$old,$new,$hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
Log3 $name, 5, "onDigitalMessage for pin ".$pin.", old: ".(defined $old? $old : "--").", new: ".(defined $new? $new : "--");
|
||||
Log3 $name, 5, "$name: observer pin: ".$pin.", old: ".(defined $old? $old : "--").", new: ".(defined $new? $new : "--");
|
||||
if (AttrVal($hash->{NAME}, "activeLow", "no") eq "yes") {
|
||||
$old = $old == PIN_LOW ? PIN_HIGH : PIN_LOW if (defined $old);
|
||||
$new = $new == PIN_LOW ? PIN_HIGH : PIN_LOW;
|
||||
$old = $old == Device::Firmata::Constants->PIN_LOW ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW if (defined $old);
|
||||
$new = $new == Device::Firmata::Constants->PIN_LOW ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW;
|
||||
}
|
||||
my $changed = !defined($old) || ($old != $new);
|
||||
if ($changed && (AttrVal($hash->{NAME}, "valueMode", "send") ne "send")) {
|
||||
main::readingsSingleUpdate($hash, "value", $new == PIN_HIGH? "on" : "off", 1);
|
||||
main::readingsSingleUpdate($hash, "value", $new == Device::Firmata::Constants->PIN_HIGH? "on" : "off", 1);
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_OUT_Set($$$)
|
||||
sub FRM_OUT_Set
|
||||
{
|
||||
my ($hash, $name, $cmd, @a) = @_;
|
||||
my $value;
|
||||
my $invert = AttrVal($hash->{NAME},"activeLow", "no");
|
||||
if (defined ($cmd)) {
|
||||
if ($cmd eq "on") {
|
||||
$value = $invert eq "yes" ? PIN_LOW : PIN_HIGH;
|
||||
} elsif ($cmd eq "off") {
|
||||
$value = $invert eq "yes" ? PIN_HIGH : PIN_LOW;
|
||||
} else {
|
||||
my $list = "on off";
|
||||
return SetExtensions($hash, $list, $name, $cmd, @a);
|
||||
}
|
||||
eval {
|
||||
FRM_Client_FirmataDevice($hash)->digital_write($hash->{PIN},$value);
|
||||
if (AttrVal($hash->{NAME}, "valueMode", "send") ne "receive") {
|
||||
main::readingsSingleUpdate($hash,"value",$cmd, 1);
|
||||
}
|
||||
|
||||
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
|
||||
return SetExtensions($hash, join(" ", keys %sets), $name, $cmd, @a) unless @match == 1;
|
||||
return "$cmd requires $sets{$match[0]} arguments" unless (@a == $sets{$match[0]});
|
||||
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
return 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
|
||||
my $value = Device::Firmata::Constants->PIN_LOW;
|
||||
my $invert = AttrVal($hash->{NAME}, "activeLow", "no");
|
||||
SETHANDLER: {
|
||||
$cmd eq "on" and do {
|
||||
$value = $invert eq "yes" ? Device::Firmata::Constants->PIN_LOW : Device::Firmata::Constants->PIN_HIGH;
|
||||
last;
|
||||
};
|
||||
return $@;
|
||||
} else {
|
||||
return "no command specified";
|
||||
$cmd eq "off" and do {
|
||||
$value = $invert eq "yes" ? Device::Firmata::Constants->PIN_HIGH : Device::Firmata::Constants->PIN_LOW;
|
||||
last;
|
||||
};
|
||||
};
|
||||
|
||||
eval {
|
||||
FRM_Client_FirmataDevice($hash)->digital_write($hash->{PIN},$value);
|
||||
if (AttrVal($hash->{NAME}, "valueMode", "send") ne "receive") {
|
||||
main::readingsSingleUpdate($hash,"value",$cmd, 1);
|
||||
}
|
||||
};
|
||||
if ($@) {
|
||||
my $ret = FRM_Catch($@);
|
||||
$hash->{STATE} = "set $cmd error: " . $ret;
|
||||
return $hash->{STATE};
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub FRM_OUT_State
|
||||
{
|
||||
my ($hash, $tim, $sname, $sval) = @_;
|
||||
|
||||
STATEHANDLER: {
|
||||
$sname eq "value" and do {
|
||||
if (AttrVal($hash->{NAME},"restoreOnStartup", "on") eq "on") {
|
||||
FRM_OUT_Set($hash,$hash->{NAME},$sval);
|
||||
}
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub FRM_OUT_State($$$$)
|
||||
sub FRM_OUT_Attr
|
||||
{
|
||||
my ($hash, $tim, $sname, $sval) = @_;
|
||||
|
||||
STATEHANDLER: {
|
||||
$sname eq "value" and do {
|
||||
if (AttrVal($hash->{NAME},"restoreOnStartup", "on") eq "on") {
|
||||
FRM_OUT_Set($hash,$hash->{NAME},$sval);
|
||||
}
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_OUT_Attr($$$$) {
|
||||
my ($command,$name,$attribute,$value) = @_;
|
||||
my $hash = $main::defs{$name};
|
||||
eval {
|
||||
@ -163,6 +204,7 @@ FRM_OUT_Attr($$$$) {
|
||||
}
|
||||
last;
|
||||
};
|
||||
|
||||
$attribute eq "activeLow" and do {
|
||||
my $oldval = AttrVal($hash->{NAME},"activeLow", "no");
|
||||
if ($oldval ne $value) {
|
||||
@ -179,9 +221,9 @@ FRM_OUT_Attr($$$$) {
|
||||
}
|
||||
};
|
||||
if ($@) {
|
||||
$@ =~ /^(.*)( at.*FHEM.*)$/;
|
||||
$hash->{STATE} = "error setting $attribute to $value: ".$1;
|
||||
return "cannot $command attribute $attribute to $value for $name: ".$1;
|
||||
my $ret = FRM_Catch($@);
|
||||
$hash->{STATE} = "$command $attribute error: " . $ret;
|
||||
return $hash->{STATE};
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,102 +231,148 @@ FRM_OUT_Attr($$$$) {
|
||||
|
||||
=pod
|
||||
|
||||
CHANGES
|
||||
=head1 CHANGES
|
||||
|
||||
2016 jensb
|
||||
o new sub FRM_OUT_observer, modified sub FRM_OUT_Init
|
||||
to receive output state from Firmata device
|
||||
o support attribute "activeLow"
|
||||
|
||||
01.01.2018 jensb
|
||||
o create reading "value" in FRM_OUT_Init if missing
|
||||
|
||||
02.01.2018 jensb
|
||||
o new attribute "valueMode" to control how "value" reading is updated
|
||||
|
||||
14.01.2018 jensb
|
||||
o fix "uninitialised" when calling FRM_OUT_Set without command
|
||||
|
||||
23.08.2020 jensb
|
||||
o check for IODev install error in Init
|
||||
o prototypes removed
|
||||
o set argument metadata added
|
||||
o set argument verifier improved
|
||||
|
||||
22.10.2020 jensb
|
||||
o annotaded module help of attributes for FHEMWEB
|
||||
|
||||
=cut
|
||||
|
||||
|
||||
=pod
|
||||
|
||||
=head1 FHEM COMMANDREF METADATA
|
||||
|
||||
=over
|
||||
|
||||
=item device
|
||||
|
||||
=item summary Firmata: digital output
|
||||
|
||||
=item summary_DE Firmata: digitaler Ausang
|
||||
|
||||
=back
|
||||
|
||||
=head1 INSTALLATION AND CONFIGURATION
|
||||
|
||||
=begin html
|
||||
|
||||
<a name="FRM_OUT"></a>
|
||||
<a name="FRM_OUT"/>
|
||||
<h3>FRM_OUT</h3>
|
||||
<ul>
|
||||
This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a>
|
||||
This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a>
|
||||
that should be configured as a digital output.<br><br>
|
||||
|
||||
|
||||
Requires a defined <a href="#FRM">FRM</a> device to work. The pin must be listed in
|
||||
the internal reading "<a href="#FRMinternals">output_pins</a>"<br>
|
||||
of the FRM device (after connecting to the Firmata device) to be used as digital output.<br><br>
|
||||
|
||||
<a name="FRM_OUTdefine"></a>
|
||||
of the FRM device (after connecting to the Firmata device) to be used as digital output.<br><br>
|
||||
|
||||
<a name="FRM_OUTdefine"/>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> FRM_OUT <pin></code> <br>
|
||||
Defines the FRM_OUT device. <pin>> is the arduino-pin to use.
|
||||
<code>define <name> FRM_OUT <pin></code> <br>
|
||||
Defines the FRM_OUT device. <pin>> is the arduino-pin to use.
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_OUTset"></a>
|
||||
|
||||
<a name="FRM_OUTset"/>
|
||||
<b>Set</b><br>
|
||||
<ul>
|
||||
<code>set <name> on|off</code><br><br>
|
||||
<code>set <name> on|off</code><br><br>
|
||||
</ul>
|
||||
<ul>
|
||||
<a href="#setExtensions">set extensions</a> are supported<br>
|
||||
<a href="#setExtensions">set extensions</a> are supported<br>
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_OUTget"></a>
|
||||
|
||||
<a name="FRM_OUTget"/>
|
||||
<b>Get</b><br>
|
||||
<ul>
|
||||
N/A
|
||||
N/A
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_OUTattr"></a>
|
||||
|
||||
<a name="FRM_OUTattr"/>
|
||||
<b>Attributes</b><br>
|
||||
<ul>
|
||||
<li>restoreOnStartup <on|off>, default: on<br>
|
||||
Set output value in Firmata device on FHEM startup (if device is already connected) and
|
||||
whenever the <em>setstate</em> command is used.
|
||||
</li>
|
||||
<li>restoreOnReconnect <on|off>, default: on<br>
|
||||
Set output value in Firmata device after IODev is initialized.
|
||||
</li>
|
||||
<li>activeLow <yes|no>, default: no</li>
|
||||
<li><a href="#IODev">IODev</a><br>
|
||||
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
|
||||
than one FRM-device defined.)
|
||||
</li>
|
||||
<li>valueMode <send|receive|bidirectional>, default: send<br>
|
||||
Define how the reading <em>value</em> is updated:<br>
|
||||
<a name="restoreOnStartup"/>
|
||||
<li>restoreOnStartup <on|off>, default: on<br>
|
||||
Set output value in Firmata device on FHEM startup (if device is already connected) and
|
||||
whenever the <em>setstate</em> command is used.
|
||||
</li>
|
||||
|
||||
<a name="restoreOnReconnect"/>
|
||||
<li>restoreOnReconnect <on|off>, default: on<br>
|
||||
Set output value in Firmata device after IODev is initialized.
|
||||
</li>
|
||||
|
||||
<a name="activeLow"/>
|
||||
<li>activeLow <yes|no>, default: no</li>
|
||||
|
||||
<a name="IODev"/>
|
||||
<li><a href="#IODev">IODev</a><br>
|
||||
Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
|
||||
</li>
|
||||
|
||||
<a name="valueMode"/>
|
||||
<li>valueMode <send|receive|bidirectional>, default: send<br>
|
||||
Define how the reading <em>value</em> is updated:<br>
|
||||
<ul>
|
||||
<li>send - after sending</li>
|
||||
<li>receive - after receiving</li>
|
||||
<li>bidirectional - after sending and receiving</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#eventMap">eventMap</a><br></li>
|
||||
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
|
||||
</li>
|
||||
|
||||
<li><a href="#attributes">global attributes</a></li>
|
||||
|
||||
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_OUTnotes"></a>
|
||||
|
||||
<a name="FRM_OUTnotes"/>
|
||||
<b>Notes</b><br>
|
||||
<ul>
|
||||
<li>attribute <i>stateFormat</i><br>
|
||||
In most cases it is a good idea to assign "value" to the attribute <i>stateFormat</i>. This will show the state
|
||||
of the pin in the web interface.
|
||||
</li>
|
||||
<li>attribute <i>valueMode</i><br>
|
||||
For modes "receive<" and "bidirectional" to work the default Firmata application code must
|
||||
be modified in function "<code>setPinModeCallback</code>":<br>
|
||||
add "<ins> || mode == OUTPUT</ins>" to the if condition for "<code>portConfigInputs[pin / 8] |= (1 << (pin & 7));</code>" to enable<br>
|
||||
reporting the output state (as if the pin were an input). This is of interest if you have custom code in your Firmata device that can change<br>
|
||||
the state of an output or you want a feedback from the Firmata device after the output state was changed.
|
||||
</li>
|
||||
<li>attribute <i>stateFormat</i><br>
|
||||
In most cases it is a good idea to assign "value" to the attribute <i>stateFormat</i>. This will show the state
|
||||
of the pin in the web interface.
|
||||
</li>
|
||||
<li>attribute <i>valueMode</i><br>
|
||||
For modes "receive" and "bidirectional" to work the default Firmata application code must
|
||||
be modified in function "<code>setPinModeCallback</code>":<br>
|
||||
add "<code> || mode == OUTPUT</code>" to the if condition for "<code>portConfigInputs[pin / 8] |= (1 << (pin & 7));</code>" to enable<br>
|
||||
reporting the output state (as if the pin were an input). This is of interest if you have custom code in your Firmata device that my change to pin state.<br>
|
||||
the state of an output or you want a feedback from the Firmata device after the output state was changed.
|
||||
</li>
|
||||
</ul>
|
||||
</ul><br>
|
||||
|
||||
=end html
|
||||
|
||||
=begin html_DE
|
||||
|
||||
<a name="FRM_OUT"/>
|
||||
<h3>FRM_OUT</h3>
|
||||
<ul>
|
||||
Die Modulbeschreibung von FRM_OUT gibt es nur auf <a href="commandref.html#FRM_OUT">Englisch</a>. <br>
|
||||
</ul><br>
|
||||
|
||||
=end html_DE
|
||||
|
||||
=cut
|
||||
|
@ -1,37 +1,42 @@
|
||||
########################################################################################
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
# FHEM module for one Firmata PWM output pin
|
||||
#
|
||||
########################################################################################
|
||||
#
|
||||
# LICENSE AND COPYRIGHT
|
||||
#
|
||||
# Copyright (C) 2013 ntruchess
|
||||
# Copyright (C) 2016 jensb
|
||||
#
|
||||
# All rights reserved
|
||||
#
|
||||
# This script is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# The GNU General Public License can be found at
|
||||
# http://www.gnu.org/copyleft/gpl.html.
|
||||
# A copy is found in the textfile GPL.txt and important notices to the license
|
||||
# from the author is found in LICENSE.txt distributed with these scripts.
|
||||
#
|
||||
# This script is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# This copyright notice MUST APPEAR in all copies of the script!
|
||||
#
|
||||
########################################################################################
|
||||
|
||||
=encoding UTF-8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
FHEM module for one Firmata PWM output pin
|
||||
|
||||
=head1 LICENSE AND COPYRIGHT
|
||||
|
||||
Copyright (C) 2013 ntruchess
|
||||
Copyright (C) 2016 jensb
|
||||
|
||||
All rights reserved
|
||||
|
||||
This script is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This script is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this script; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
A copy of the GNU General Public License, Version 2 can also be found at
|
||||
|
||||
http://www.gnu.org/licenses/old-licenses/gpl-2.0.
|
||||
|
||||
This copyright notice MUST APPEAR in all copies of the script!
|
||||
|
||||
=cut
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
@ -39,37 +44,34 @@ use warnings;
|
||||
|
||||
#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
|
||||
BEGIN {
|
||||
if (!grep(/FHEM\/lib$/,@INC)) {
|
||||
foreach my $inc (grep(/FHEM$/,@INC)) {
|
||||
push @INC,$inc."/lib";
|
||||
};
|
||||
};
|
||||
if (!grep(/FHEM\/lib$/,@INC)) {
|
||||
foreach my $inc (grep(/FHEM$/,@INC)) {
|
||||
push @INC,$inc."/lib";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
use Device::Firmata::Constants qw/ :all /;
|
||||
use SetExtensions qw/ :all /;
|
||||
|
||||
#####################################
|
||||
|
||||
my %gets = (
|
||||
"dim" => 0,
|
||||
"value" => 0,
|
||||
"devStateIcon" => 0,
|
||||
"dim" => "",
|
||||
"value" => "",
|
||||
);
|
||||
|
||||
# number of arguments
|
||||
my %sets = (
|
||||
"on" => 0,
|
||||
"off" => 0,
|
||||
"toggle" => 0,
|
||||
"value" => 1,
|
||||
"dim:slider,0,1,100" => 1,
|
||||
"fadeTo" => 2,
|
||||
"dimUp" => 0,
|
||||
"dimDown" => 0,
|
||||
"on:noArg" => 0,
|
||||
"off:noArg" => 0,
|
||||
"toggle:noArg" => 0,
|
||||
"value" => 1,
|
||||
"dim:slider,0,1,100" => 1,
|
||||
"dimUp:noArg" => 0,
|
||||
"dimDown:noArg" => 0,
|
||||
);
|
||||
|
||||
sub
|
||||
FRM_PWM_Initialize($)
|
||||
sub FRM_PWM_Initialize
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
@ -80,51 +82,73 @@ FRM_PWM_Initialize($)
|
||||
$hash->{UndefFn} = "FRM_Client_Undef";
|
||||
$hash->{AttrFn} = "FRM_PWM_Attr";
|
||||
$hash->{StateFn} = "FRM_PWM_State";
|
||||
|
||||
|
||||
$hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off IODev $main::readingFnAttributes";
|
||||
main::LoadModule("FRM");
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_PWM_Init($$)
|
||||
sub FRM_PWM_Init
|
||||
{
|
||||
my ($hash,$args) = @_;
|
||||
my $ret = FRM_Init_Pin_Client($hash,$args,PIN_PWM);
|
||||
return $ret if (defined $ret);
|
||||
my $firmata = $hash->{IODev}->{FirmataDevice};
|
||||
my $name = $hash->{NAME};
|
||||
my $resolution = 8;
|
||||
if (defined $firmata->{metadata}{pwm_resolutions}) {
|
||||
$resolution = $firmata->{metadata}{pwm_resolutions}{$hash->{PIN}}
|
||||
}
|
||||
$hash->{resolution} = $resolution;
|
||||
$hash->{".max"} = defined $resolution ? (1<<$resolution)-1 : 255;
|
||||
$hash->{".dim"} = 0;
|
||||
$hash->{".toggle"} = "off";
|
||||
if (! (defined AttrVal($name,"stateFormat",undef))) {
|
||||
$main::attr{$name}{"stateFormat"} = "value";
|
||||
}
|
||||
my $value = ReadingsVal($name,"value",undef);
|
||||
if (defined $value and AttrVal($hash->{NAME},"restoreOnReconnect","on") eq "on") {
|
||||
FRM_PWM_Set($hash,$name,"value",$value);
|
||||
}
|
||||
main::readingsSingleUpdate($hash,"state","Initialized",1);
|
||||
return undef;
|
||||
}
|
||||
my ($hash,$args) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
sub
|
||||
FRM_PWM_Set($@)
|
||||
{
|
||||
my ($hash, $name, $cmd, @a) = @_;
|
||||
|
||||
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
|
||||
#-- check argument
|
||||
return SetExtensions($hash, join(" ", keys %sets), $name, $cmd, @a) unless @match == 1;
|
||||
return "$cmd expects $sets{$match[0]} parameters" unless (@a eq $sets{$match[0]});
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
return 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
|
||||
my $ret = FRM_Init_Pin_Client($hash, $args, Device::Firmata::Constants->PIN_PWM);
|
||||
if (defined($ret)) {
|
||||
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
eval {
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
my $resolution = 8;
|
||||
if (defined $firmata->{metadata}{pwm_resolutions}) {
|
||||
$resolution = $firmata->{metadata}{pwm_resolutions}{$hash->{PIN}}
|
||||
}
|
||||
$hash->{resolution} = $resolution;
|
||||
$hash->{".max"} = defined $resolution ? (1<<$resolution)-1 : 255;
|
||||
$hash->{".dim"} = 0;
|
||||
$hash->{".toggle"} = "off";
|
||||
};
|
||||
if ($@) {
|
||||
my $ret = FRM_Catch($@);
|
||||
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
if (!(defined AttrVal($name,"stateFormat",undef))) {
|
||||
$main::attr{$name}{"stateFormat"} = "value";
|
||||
}
|
||||
|
||||
my $value = ReadingsVal($name,"value",undef);
|
||||
if (defined $value and AttrVal($hash->{NAME},"restoreOnReconnect","on") eq "on") {
|
||||
FRM_PWM_Set($hash,$name,"value",$value);
|
||||
}
|
||||
|
||||
main::readingsSingleUpdate($hash,"state","Initialized",1);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub FRM_PWM_Set
|
||||
{
|
||||
my ($hash, $name, $cmd, @a) = @_;
|
||||
|
||||
return "set command missing" if(!defined($cmd));
|
||||
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
|
||||
return SetExtensions($hash, join(" ", keys %sets), $name, $cmd, @a) unless @match == 1;
|
||||
return "$cmd requires $sets{$match[0]} argument(s)" unless (@a == $sets{$match[0]});
|
||||
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
return 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
|
||||
my $value = shift @a;
|
||||
eval {
|
||||
SETHANDLER: {
|
||||
my $value = $a[0] if @a;
|
||||
$cmd eq "on" and do {
|
||||
FRM_PWM_writeOut($hash,$hash->{".max"});
|
||||
$hash->{".toggle"} = "on";
|
||||
@ -141,7 +165,7 @@ FRM_PWM_Set($@)
|
||||
$toggle eq "off" and do {
|
||||
FRM_PWM_writeOut($hash,$hash->{".dim"});
|
||||
$hash->{".toggle"} = "up";
|
||||
last;
|
||||
last;
|
||||
};
|
||||
$toggle eq "up" and do {
|
||||
FRM_PWM_writeOut($hash,$hash->{".max"});
|
||||
@ -151,7 +175,7 @@ FRM_PWM_Set($@)
|
||||
$toggle eq "on" and do {
|
||||
FRM_PWM_writeOut($hash,$hash->{".dim"});
|
||||
$hash->{".toggle"} = "down";
|
||||
last;
|
||||
last;
|
||||
};
|
||||
$toggle eq "down" and do {
|
||||
FRM_PWM_writeOut($hash,0);
|
||||
@ -197,9 +221,6 @@ FRM_PWM_Set($@)
|
||||
};
|
||||
last;
|
||||
};
|
||||
$cmd eq "fadeTo" and do {
|
||||
die "fadeTo not implemented yet";
|
||||
};
|
||||
$cmd eq "dimUp" and do {
|
||||
my $dim = $hash->{".dim"};
|
||||
my $max = $hash->{".max"};
|
||||
@ -230,18 +251,19 @@ FRM_PWM_Set($@)
|
||||
};
|
||||
}
|
||||
};
|
||||
if ($@) {
|
||||
$@ =~ /^(.*)( at.*FHEM.*)$/;
|
||||
$hash->{STATE} = "error setting '$cmd': ".(defined $1 ? $1 : $@);
|
||||
return "error setting '$hash->{NAME} $cmd': ".(defined $1 ? $1 : $@);
|
||||
}
|
||||
return undef;
|
||||
if ($@) {
|
||||
my $ret = FRM_Catch($@);
|
||||
$hash->{STATE} = "set $cmd error: " . $ret;
|
||||
return $hash->{STATE};
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_PWM_writeOut($$)
|
||||
sub FRM_PWM_writeOut
|
||||
{
|
||||
my ($hash,$value) = @_;
|
||||
|
||||
FRM_Client_FirmataDevice($hash)->analog_write($hash->{PIN},$value);
|
||||
readingsBeginUpdate($hash);
|
||||
readingsBulkUpdate($hash,"value",$value, 1);
|
||||
@ -249,14 +271,13 @@ FRM_PWM_writeOut($$)
|
||||
readingsEndUpdate($hash, 1);
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_PWM_Get($@)
|
||||
sub FRM_PWM_Get
|
||||
{
|
||||
my ($hash, $name, $cmd, @a) = @_;
|
||||
|
||||
return "FRM_PWM: Get with unknown argument $cmd, choose one of ".join(" ", sort keys %gets)
|
||||
unless defined($gets{$cmd});
|
||||
|
||||
|
||||
return "get command missing" if(!defined($cmd));
|
||||
return "unknown get command '$cmd', choose one of " . join(":noArg ", sort keys %gets) . ":noArg" if(!defined($gets{$cmd}));
|
||||
|
||||
GETHANDLER: {
|
||||
$cmd eq 'dim' and do {
|
||||
return ReadingsVal($name,"dim",undef);
|
||||
@ -264,14 +285,10 @@ FRM_PWM_Get($@)
|
||||
$cmd eq 'value' and do {
|
||||
return ReadingsVal($name,"value",undef);
|
||||
};
|
||||
$cmd eq 'devStateIcon' and do {
|
||||
return return "not implemented yet";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_PWM_State($$$$)
|
||||
sub FRM_PWM_State
|
||||
{
|
||||
my ($hash, $tim, $sname, $sval) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
@ -291,8 +308,7 @@ FRM_PWM_State($$$$)
|
||||
return 0; # default processing by fhem.pl
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_PWM_Attr($$$$)
|
||||
sub FRM_PWM_Attr
|
||||
{
|
||||
my ($command,$name,$attribute,$value) = @_;
|
||||
my $hash = $main::defs{$name};
|
||||
@ -310,9 +326,9 @@ FRM_PWM_Attr($$$$)
|
||||
}
|
||||
};
|
||||
if ($@) {
|
||||
$@ =~ /^(.*)( at.*FHEM.*)$/;
|
||||
$hash->{STATE} = "error setting $attribute to $value: ".$1;
|
||||
return "cannot $command attribute $attribute to $value for $name: ".$1;
|
||||
my $ret = FRM_Catch($@);
|
||||
$hash->{STATE} = "$command $attribute error: " . $ret;
|
||||
return $hash->{STATE};
|
||||
}
|
||||
}
|
||||
|
||||
@ -320,103 +336,151 @@ FRM_PWM_Attr($$$$)
|
||||
|
||||
=pod
|
||||
|
||||
CHANGES
|
||||
=head1 CHANGES
|
||||
|
||||
2016 jensb
|
||||
o modified subs FRM_PWM_Init and FRM_PWM_State to support attribute "restoreOnStartup"
|
||||
|
||||
24.08.2020 jensb
|
||||
o check for IODev install error in Init and Set
|
||||
o prototypes removed
|
||||
o set argument metadata added
|
||||
o get/set argument verifier improved
|
||||
o module help updated
|
||||
|
||||
22.10.2020 jensb
|
||||
o annotaded module help of attributes for FHEMWEB
|
||||
|
||||
=cut
|
||||
|
||||
|
||||
=pod
|
||||
|
||||
=head1 FHEM COMMANDREF METADATA
|
||||
|
||||
=over
|
||||
|
||||
=item device
|
||||
|
||||
=item summary Firmata: PWM output
|
||||
|
||||
=item summary_DE Firmata: PWM Ausgang
|
||||
|
||||
=back
|
||||
|
||||
=head1 INSTALLATION AND CONFIGURATION
|
||||
|
||||
=begin html
|
||||
|
||||
<a name="FRM_PWM"></a>
|
||||
<a name="FRM_PWM"/>
|
||||
<h3>FRM_PWM</h3>
|
||||
<ul>
|
||||
This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a>
|
||||
This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a>
|
||||
that should be configured as a pulse width modulated output (PWM).<br><br>
|
||||
|
||||
Requires a defined <a href="#FRM">FRM</a> device to work. The pin must be listed in the internal reading "<a href="#FRMinternals">pwm_pins</a>"<br>
|
||||
of the FRM device (after connecting to the Firmata device) to be used as PWM output.<br><br>
|
||||
|
||||
<a name="FRM_PWMdefine"></a>
|
||||
|
||||
Requires a defined <a href="#FRM">FRM</a> device to work. The pin must be listed in the internal reading
|
||||
"<a href="#FRMinternals">pwm_pins</a>" of the FRM device (after connecting to the Firmata device) to be
|
||||
used as PWM output.<br><br>
|
||||
|
||||
<a name="FRM_PWMdefine"/>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> FRM_PWM <pin></code><br><br>
|
||||
|
||||
Defines the FRM_PWM device. <pin>> is the arduino-pin to use.
|
||||
<code>define <name> FRM_PWM <pin></code><br><br>
|
||||
|
||||
Defines the FRM_PWM device. <pin>> is the arduino-pin to use.
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_PWMset"></a>
|
||||
<b>Set</b><br>
|
||||
|
||||
<a name="FRM_PWMset"/>
|
||||
<b>Set</b>
|
||||
<ul>
|
||||
<li><code>set <name> on</code><br>
|
||||
sets the pulse-width to 100%<br>
|
||||
</li>
|
||||
<li>
|
||||
<code>set <name> off</code><br>
|
||||
sets the pulse-width to 0%<br>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#setExtensions">set extensions</a> are supported<br>
|
||||
</li>
|
||||
<li>
|
||||
<code>set <name> toggle</code><br>
|
||||
toggles the pulse-width in between to the last value set by 'value' or 'dim' and 0 respectivly 100%<br>
|
||||
</li>
|
||||
<li>
|
||||
<code>set <name> value <value></code><br>
|
||||
sets the pulse-width to the value specified<br>
|
||||
The min value is zero and the max value depends on the Firmata device (see internal reading<br>
|
||||
"<a href="#FRMinternals">pwm_resolutions</a>" of the FRM device). For 8 bits resolution the range
|
||||
is 0 to 255 (also see <a href="http://arduino.cc/en/Reference/AnalogWrite">analogWrite()</a> for details)<br>
|
||||
</li>
|
||||
<li>
|
||||
<code>set <name> dim <value></code><br>
|
||||
sets the pulse-width to the value specified in percent<br>
|
||||
Range is from 0 to 100<br>
|
||||
</li>
|
||||
<li>
|
||||
<code>set <name> dimUp</code><br>
|
||||
increases the pulse-width by 10%<br>
|
||||
</li>
|
||||
<li>
|
||||
<code>set <name> dimDown</code><br>
|
||||
decreases the pulse-width by 10%<br>
|
||||
</li>
|
||||
<li><code>set <name> on</code><br>
|
||||
sets the pulse-width to 100%<br>
|
||||
</li>
|
||||
<li>
|
||||
<code>set <name> off</code><br>
|
||||
sets the pulse-width to 0%<br>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#setExtensions">set extensions</a> are supported<br>
|
||||
</li>
|
||||
<li>
|
||||
<code>set <name> toggle</code><br>
|
||||
toggles the pulse-width in between to the last value set by 'value' or 'dim' and 0 respectivly 100%<br>
|
||||
</li>
|
||||
<li>
|
||||
<code>set <name> value <value></code><br>
|
||||
sets the pulse-width to the value specified<br>
|
||||
The min value is zero and the max value depends on the Firmata device (see internal reading<br>
|
||||
"<a href="#FRMinternals">pwm_resolutions</a>" of the FRM device). For 8 bits resolution the range
|
||||
is 0 to 255 (also see <a href="http://arduino.cc/en/Reference/AnalogWrite">analogWrite()</a> for details)<br>
|
||||
</li>
|
||||
<li>
|
||||
<code>set <name> dim <value></code><br>
|
||||
sets the pulse-width to the value specified in percent<br>
|
||||
Range is from 0 to 100<br>
|
||||
</li>
|
||||
<li>
|
||||
<code>set <name> dimUp</code><br>
|
||||
increases the pulse-width by 10%<br>
|
||||
</li>
|
||||
<li>
|
||||
<code>set <name> dimDown</code><br>
|
||||
decreases the pulse-width by 10%<br>
|
||||
</li>
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_PWMget"></a>
|
||||
|
||||
<a name="FRM_PWMget"/>
|
||||
<b>Get</b><br>
|
||||
<ul>
|
||||
N/A
|
||||
<li>
|
||||
<code>get <dim></code><br>
|
||||
returns current dim setting in percent, see description for set command for more details<br>
|
||||
</li>
|
||||
<li>
|
||||
<code>get <value></code><br>
|
||||
returns current dim setting, see description for set command for more details<br>
|
||||
</li>
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_PWMattr"></a>
|
||||
|
||||
<a name="FRM_PWMattr"/>
|
||||
<b>Attributes</b><br>
|
||||
<ul>
|
||||
<li>restoreOnStartup <on|off></li>
|
||||
<li>restoreOnReconnect <on|off></li>
|
||||
<li><a href="#IODev">IODev</a><br>
|
||||
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
|
||||
than one FRM-device defined.)
|
||||
</li>
|
||||
<li><a href="#eventMap">eventMap</a><br></li>
|
||||
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
|
||||
<a name="restoreOnStartup"/>
|
||||
<li>restoreOnStartup <on|off></li>
|
||||
|
||||
<a name="restoreOnReconnect"/>
|
||||
<li>restoreOnReconnect <on|off></li>
|
||||
|
||||
<a name="IODev"/>
|
||||
<li><a href="#IODev">IODev</a><br>
|
||||
Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
|
||||
</li>
|
||||
|
||||
<li><a href="#attributes">global attributes</a></li>
|
||||
|
||||
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_PWMnotes"></a>
|
||||
|
||||
<a name="FRM_PWMnotes"/>
|
||||
<b>Notes</b><br>
|
||||
<ul>
|
||||
<li>attribute <i>stateFormat</i><br>
|
||||
In most cases it is a good idea to assign "value" to the attribute <i>stateFormat</i>. This will show the
|
||||
current value of the pin in the web interface.
|
||||
</li>
|
||||
</ul>
|
||||
<li>attribute <i>stateFormat</i><br>
|
||||
In most cases it is a good idea to assign "value" to the attribute <i>stateFormat</i>. This will show the
|
||||
current value of the pin in the web interface.
|
||||
</li>
|
||||
</ul>
|
||||
</ul><br>
|
||||
|
||||
=end html
|
||||
|
||||
=begin html_DE
|
||||
|
||||
<a name="FRM_PWM"/>
|
||||
<h3>FRM_PWM</h3>
|
||||
<ul>
|
||||
Die Modulbeschreibung von FRM_PWM gibt es nur auf <a href="commandref.html#FRM_PWM">Englisch</a>. <br>
|
||||
</ul><br>
|
||||
|
||||
=end html_DE
|
||||
|
||||
=cut
|
||||
|
@ -1,6 +1,42 @@
|
||||
##############################################
|
||||
########################################################################################
|
||||
# $Id$
|
||||
##############################################
|
||||
########################################################################################
|
||||
|
||||
=encoding UTF-8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
FHEM module for one Firmata PWM output pin for controlling RGB LEDs
|
||||
|
||||
=head1 LICENSE AND COPYRIGHT
|
||||
|
||||
Copyright (C) 2013 ntruchess
|
||||
Copyright (C) 2020 jensb
|
||||
|
||||
All rights reserved
|
||||
|
||||
This script is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This script is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this script; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
A copy of the GNU General Public License, Version 2 can also be found at
|
||||
|
||||
http://www.gnu.org/licenses/old-licenses/gpl-2.0.
|
||||
|
||||
This copyright notice MUST APPEAR in all copies of the script!
|
||||
|
||||
=cut
|
||||
|
||||
package main;
|
||||
|
||||
use vars qw{%attr %defs $readingFnAttributes};
|
||||
@ -16,32 +52,29 @@ BEGIN {
|
||||
};
|
||||
};
|
||||
|
||||
use Device::Firmata::Constants qw/ :all /;
|
||||
use Color qw/ :all /;
|
||||
use SetExtensions qw/ :all /;
|
||||
|
||||
#####################################
|
||||
|
||||
my %gets = (
|
||||
"rgb" => 0,
|
||||
"RGB" => 0,
|
||||
"pct" => 0,
|
||||
"devStateIcon" => 0,
|
||||
"rgb" => "",
|
||||
"RGB" => "",
|
||||
"pct" => "",
|
||||
);
|
||||
|
||||
# number of arguments
|
||||
my %sets = (
|
||||
"on" => 0,
|
||||
"off" => 0,
|
||||
"toggle" => 0,
|
||||
"on:noArg" => 0,
|
||||
"off:noArg" => 0,
|
||||
"toggle:noArg" => 0,
|
||||
"rgb:colorpicker,RGB" => 1,
|
||||
"pct:slider,0,1,100" => 1,
|
||||
"fadeTo" => 2,
|
||||
"dimUp" => 0,
|
||||
"dimDown" => 0,
|
||||
"dimUp:noArg" => 0,
|
||||
"dimDown:noArg" => 0,
|
||||
);
|
||||
|
||||
sub
|
||||
FRM_RGB_Initialize($)
|
||||
sub FRM_RGB_Initialize
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
@ -52,34 +85,40 @@ FRM_RGB_Initialize($)
|
||||
$hash->{UndefFn} = "FRM_Client_Undef";
|
||||
$hash->{AttrFn} = "FRM_RGB_Attr";
|
||||
$hash->{StateFn} = "FRM_RGB_State";
|
||||
|
||||
|
||||
$hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off IODev loglevel:0,1,2,3,4,5 $readingFnAttributes";
|
||||
|
||||
|
||||
LoadModule("FRM");
|
||||
FHEM_colorpickerInit();
|
||||
FHEM_colorpickerInit();
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_RGB_Define($$)
|
||||
sub FRM_RGB_Define
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
$attr{$hash->{NAME}}{webCmd} = "rgb:rgb ff0000:rgb 00ff00:rgb 0000ff:toggle:on:off";
|
||||
return FRM_Client_Define($hash,$def);
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_RGB_Init($$)
|
||||
sub FRM_RGB_Init
|
||||
{
|
||||
my ($hash,$args) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $ret = FRM_Init_Pin_Client($hash,$args,PIN_PWM);
|
||||
return $ret if (defined $ret);
|
||||
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
return 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
|
||||
my $ret = FRM_Init_Pin_Client($hash, $args, Device::Firmata::Constants->PIN_PWM);
|
||||
if (defined($ret)) {
|
||||
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
|
||||
return $ret;
|
||||
}
|
||||
my @pins = ();
|
||||
eval {
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
$hash->{PIN} = "";
|
||||
foreach my $pin (@{$args}) {
|
||||
$firmata->pin_mode($pin,PIN_PWM);
|
||||
$firmata->pin_mode($pin, Device::Firmata::Constants->PIN_PWM);
|
||||
push @pins,{
|
||||
pin => $pin,
|
||||
"shift" => defined $firmata->{metadata}{pwm_resolutions} ? $firmata->{metadata}{pwm_resolutions}{$pin}-8 : 0,
|
||||
@ -89,11 +128,11 @@ FRM_RGB_Init($$)
|
||||
$hash->{PINS} = \@pins;
|
||||
};
|
||||
if ($@) {
|
||||
$@ =~ /^(.*)( at.*FHEM.*)$/;
|
||||
$hash->{STATE} = "error initializing: ".$1;
|
||||
return "error initializing '".$hash->{NAME}."': ".$1;
|
||||
$ret = FRM_Catch($@);
|
||||
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
|
||||
return $ret;
|
||||
}
|
||||
if (! (defined AttrVal($name,"stateFormat",undef))) {
|
||||
if (!(defined AttrVal($name,"stateFormat",undef))) {
|
||||
$attr{$name}{"stateFormat"} = "rgb";
|
||||
}
|
||||
my $value = ReadingsVal($name,"rgb",undef);
|
||||
@ -103,22 +142,26 @@ FRM_RGB_Init($$)
|
||||
$hash->{toggle} = "off";
|
||||
$hash->{".dim"} = {
|
||||
bri => 50,
|
||||
channels => [(255) x @{$hash->{PINS}}],
|
||||
channels => [(255) x @{$hash->{PINS}}],
|
||||
};
|
||||
readingsSingleUpdate($hash,"state","Initialized",1);
|
||||
return undef;
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_RGB_Set($@)
|
||||
sub FRM_RGB_Set
|
||||
{
|
||||
my ($hash, $name, $cmd, @a) = @_;
|
||||
|
||||
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
|
||||
#-- check argument
|
||||
return SetExtensions($hash, join(" ", keys %sets), $name, $cmd, @a) unless @match == 1;
|
||||
return "$cmd expects $sets{$match[0]} parameters" unless (@a eq $sets{$match[0]});
|
||||
|
||||
return "set command missing" if(!defined($cmd));
|
||||
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
|
||||
return SetExtensions($hash, join(" ", keys %sets), $name, $cmd, @a) unless @match == 1;
|
||||
return "$cmd requires $sets{$match[0]} argument(s)" unless (@a == $sets{$match[0]});
|
||||
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
return 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
|
||||
my $value = shift @a;
|
||||
eval {
|
||||
SETHANDLER: {
|
||||
$cmd eq "on" and do {
|
||||
@ -137,7 +180,7 @@ FRM_RGB_Set($@)
|
||||
$toggle eq "off" and do {
|
||||
$hash->{toggle} = "up";
|
||||
FRM_RGB_SetChannels($hash,BrightnessToChannels($hash->{".dim"}));
|
||||
last;
|
||||
last;
|
||||
};
|
||||
$toggle eq "up" and do {
|
||||
FRM_RGB_SetChannels($hash,(0xFF) x @{$hash->{PINS}});
|
||||
@ -147,7 +190,7 @@ FRM_RGB_Set($@)
|
||||
$toggle eq "on" and do {
|
||||
$hash->{toggle} = "down";
|
||||
FRM_RGB_SetChannels($hash,BrightnessToChannels($hash->{".dim"}));
|
||||
last;
|
||||
last;
|
||||
};
|
||||
$toggle eq "down" and do {
|
||||
FRM_RGB_SetChannels($hash,(0x0) x @{$hash->{PINS}});
|
||||
@ -158,18 +201,17 @@ FRM_RGB_Set($@)
|
||||
last;
|
||||
};
|
||||
$cmd eq "rgb" and do {
|
||||
my $arg = $a[0];
|
||||
my $numPins = scalar(@{$hash->{PINS}});
|
||||
my $nybles = $numPins << 1;
|
||||
die "$arg is not the right format" unless( $arg =~ /^[\da-f]{$nybles}$/i );
|
||||
my @channels = RgbToChannels($arg,$numPins);
|
||||
die "$value is not the right format" unless( $value =~ /^[\da-f]{$nybles}$/i );
|
||||
my @channels = RgbToChannels($value,$numPins);
|
||||
FRM_RGB_SetChannels($hash,@channels);
|
||||
RGBHANDLER: {
|
||||
$arg =~ /^0{$nybles}$/ and do {
|
||||
$value =~ /^0{$nybles}$/ and do {
|
||||
$hash->{toggle} = "off";
|
||||
last;
|
||||
};
|
||||
$arg =~ /^f{$nybles}$/i and do {
|
||||
$value =~ /^f{$nybles}$/i and do {
|
||||
$hash->{toggle} = "on";
|
||||
last;
|
||||
};
|
||||
@ -179,14 +221,14 @@ FRM_RGB_Set($@)
|
||||
last;
|
||||
};
|
||||
$cmd eq "pct" and do {
|
||||
$hash->{".dim"}->{bri} = $a[0];
|
||||
$hash->{".dim"}->{bri} = $value;
|
||||
FRM_RGB_SetChannels($hash,BrightnessToChannels($hash->{".dim"}));
|
||||
last;
|
||||
};
|
||||
$cmd eq "dimUp" and do {
|
||||
$hash->{".dim"}->{bri} = $hash->{".dim"}->{bri} > 90 ? 100 : $hash->{".dim"}->{bri}+10;
|
||||
FRM_RGB_SetChannels($hash,BrightnessToChannels($hash->{".dim"}));
|
||||
last;
|
||||
FRM_RGB_SetChannels($hash,BrightnessToChannels($hash->{".dim"}));
|
||||
last;
|
||||
};
|
||||
$cmd eq "dimDown" and do {
|
||||
$hash->{".dim"}->{bri} = $hash->{".dim"}->{bri} < 10 ? 0 : $hash->{".dim"}->{bri}-10;
|
||||
@ -195,22 +237,22 @@ FRM_RGB_Set($@)
|
||||
};
|
||||
}
|
||||
};
|
||||
if ($@) {
|
||||
$@ =~ /^(.*)( at.*FHEM.*)$/;
|
||||
$hash->{STATE} = "error setting '$cmd': ".(defined $1 ? $1 : $@);
|
||||
return "error setting '$hash->{NAME} $cmd': ".(defined $1 ? $1 : $@);
|
||||
}
|
||||
if ($@) {
|
||||
my $ret = FRM_Catch($@);
|
||||
$hash->{STATE} = "set $cmd error: " . $ret;
|
||||
return $hash->{STATE};
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_RGB_Get($@)
|
||||
sub FRM_RGB_Get
|
||||
{
|
||||
my ($hash, $name, $cmd, @a) = @_;
|
||||
|
||||
return "FRM_RGB: Get with unknown argument $cmd, choose one of ".join(" ", sort keys %gets)
|
||||
unless defined($gets{$cmd});
|
||||
|
||||
|
||||
return "get command missing" if(!defined($cmd));
|
||||
return "unknown get command '$cmd', choose one of " . join(":noArg ", sort keys %gets) . ":noArg" if(!defined($gets{$cmd}));
|
||||
|
||||
GETHANDLER: {
|
||||
$cmd eq 'rgb' and do {
|
||||
return ReadingsVal($name,"rgb",undef);
|
||||
@ -220,20 +262,18 @@ FRM_RGB_Get($@)
|
||||
};
|
||||
$cmd eq 'pct' and do {
|
||||
return $hash->{".dim"}->{bri};
|
||||
return undef;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_RGB_SetChannels($$)
|
||||
sub FRM_RGB_SetChannels
|
||||
{
|
||||
my ($hash,@channels) = @_;
|
||||
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
my @pins = @{$hash->{PINS}};
|
||||
my @values = @channels;
|
||||
|
||||
|
||||
while(@values) {
|
||||
my $pin = shift @pins;
|
||||
my $value = shift @values;
|
||||
@ -250,8 +290,7 @@ FRM_RGB_SetChannels($$)
|
||||
readingsEndUpdate($hash, 1);
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_RGB_State($$$$)
|
||||
sub FRM_RGB_State
|
||||
{
|
||||
my ($hash, $tim, $sname, $sval) = @_;
|
||||
if ($sname eq "rgb") {
|
||||
@ -259,8 +298,7 @@ FRM_RGB_State($$$$)
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_RGB_Attr($$$$)
|
||||
sub FRM_RGB_Attr
|
||||
{
|
||||
my ($command,$name,$attribute,$value) = @_;
|
||||
my $hash = $main::defs{$name};
|
||||
@ -278,91 +316,141 @@ FRM_RGB_Attr($$$$)
|
||||
}
|
||||
};
|
||||
if ($@) {
|
||||
$@ =~ /^(.*)( at.*FHEM.*)$/;
|
||||
$hash->{STATE} = "error setting $attribute to $value: ".$1;
|
||||
return "cannot $command attribute $attribute to $value for $name: ".$1;
|
||||
my $ret = FRM_Catch($@);
|
||||
$hash->{STATE} = "$command $attribute error: " . $ret;
|
||||
return $hash->{STATE};
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=pod
|
||||
|
||||
=head1 CHANGES
|
||||
|
||||
30.08.2020 jensb
|
||||
o check for IODev install error in Init and Set
|
||||
o prototypes removed
|
||||
o set argument metadata added
|
||||
o get/set argument verifier improved
|
||||
|
||||
19.10.2020 jensb
|
||||
o annotaded module help of attributes for FHEMWEB
|
||||
|
||||
=cut
|
||||
|
||||
|
||||
=pod
|
||||
|
||||
=head1 FHEM COMMANDREF METADATA
|
||||
|
||||
=over
|
||||
|
||||
=item device
|
||||
|
||||
=item summary Firmata: PWM output for RGB-LED
|
||||
|
||||
=item summary_DE Firmata: PWM Ausgang für RGB-LED
|
||||
|
||||
=back
|
||||
|
||||
=head1 INSTALLATION AND CONFIGURATION
|
||||
|
||||
=begin html
|
||||
|
||||
<a name="FRM_RGB"></a>
|
||||
<a name="FRM_RGB"/>
|
||||
<h3>FRM_RGB</h3>
|
||||
<ul>
|
||||
allows to drive LED-controllers and other multichannel-devices that use PWM as input by an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a>
|
||||
<br>
|
||||
The value set will be output by the specified pins as pulse-width-modulated signals.<br>
|
||||
Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br>
|
||||
|
||||
<a name="FRM_RGBdefine"></a>
|
||||
The value set will be output by the specified pins as pulse-width-modulated signals.<br>
|
||||
Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br>
|
||||
|
||||
<a name="FRM_RGBdefine"/>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> FRM_RGB <pin> <pin> <pin> [pin...]</code> <br>
|
||||
Defines the FRM_RGB device. <pin>> are the arduino-pin to use.<br>
|
||||
For rgb-controlled devices first pin drives red, second pin green and third pin blue.
|
||||
</ul>
|
||||
|
||||
|
||||
<br>
|
||||
<a name="FRM_RGBset"></a>
|
||||
<a name="FRM_RGBset"/>
|
||||
<b>Set</b><br>
|
||||
<ul>
|
||||
<code>set <name> on</code><br>
|
||||
sets the pulse-width of all configured pins to 100%</ul><br>
|
||||
<code>set <name> on</code><br>
|
||||
sets the pulse-width of all configured pins to 100%</ul><br>
|
||||
<ul>
|
||||
<code>set <name> off</code><br>
|
||||
sets the pulse-width of all configured pins to 0%</ul><br>
|
||||
<code>set <name> off</code><br>
|
||||
sets the pulse-width of all configured pins to 0%</ul><br>
|
||||
<ul>
|
||||
<a href="#setExtensions">set extensions</a> are supported</ul><br>
|
||||
<a href="#setExtensions">set extensions</a> are supported</ul><br>
|
||||
<ul>
|
||||
<code>set <name> toggle</code><br>
|
||||
toggles in between the last dimmed value, 0% and 100%. If no dimmed value was set before defaults to pulsewidth 50% on all channels</ul><br>
|
||||
<code>set <name> toggle</code><br>
|
||||
toggles in between the last dimmed value, 0% and 100%. If no dimmed value was set before defaults to pulsewidth 50% on all channels</ul><br>
|
||||
<ul>
|
||||
<code>set <name> rgb <value></code><br>
|
||||
sets the pulse-width of all channels at once. Also sets the value toggle can switch to<br>
|
||||
Value is encoded as hex-string, 2-digigs per channel (e.g. FFFFFF for reguler rgb)</ul><br>
|
||||
<code>set <name> rgb <value></code><br>
|
||||
sets the pulse-width of all channels at once. Also sets the value toggle can switch to<br>
|
||||
Value is encoded as hex-string, 2-digigs per channel (e.g. FFFFFF for reguler rgb)</ul><br>
|
||||
<ul>
|
||||
<code>set <name> pct <value></code><br>
|
||||
dims all channels at once while leving the ratio in between the channels unaltered.<br>
|
||||
Range is 0-100 ('pct' stands for 'percent')</ul><br>
|
||||
<code>set <name> pct <value></code><br>
|
||||
dims all channels at once while leving the ratio in between the channels unaltered.<br>
|
||||
Range is 0-100 ('pct' stands for 'percent')</ul><br>
|
||||
<ul>
|
||||
<code>set <name> dimUp</code><br>
|
||||
dims up by 10%</ul><br>
|
||||
<code>set <name> dimUp</code><br>
|
||||
dims up by 10%</ul><br>
|
||||
<ul>
|
||||
<code>set <name> dimDown</code><br>
|
||||
dims down by 10%</ul><br>
|
||||
<a name="FRM_RGBget"></a>
|
||||
<code>set <name> dimDown</code><br>
|
||||
dims down by 10%
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_RGBget"/>
|
||||
<b>Get</b><br>
|
||||
<ul>
|
||||
<code>get <name> rgb</code><br>
|
||||
returns the values set for all channels. Format is hex, 2 nybbles per channel.
|
||||
<code>get <name> rgb</code><br>
|
||||
returns the values set for all channels. Format is hex, 2 nybbles per channel.
|
||||
</ul><br>
|
||||
<ul>
|
||||
<code>get <name> RGB</code><br>
|
||||
returns the values set for all channels in normalized format. Format is hex, 2 nybbles per channel.
|
||||
Values are scaled such that the channel with the highest value is set to FF. The real values are calculated
|
||||
by multipying each byte with the value of 'pct'.
|
||||
<code>get <name> RGB</code><br>
|
||||
returns the values set for all channels in normalized format. Format is hex, 2 nybbles per channel.
|
||||
Values are scaled such that the channel with the highest value is set to FF. The real values are calculated
|
||||
by multipying each byte with the value of 'pct'.
|
||||
</ul><br>
|
||||
<ul>
|
||||
<code>get <name> pct</code><br>
|
||||
returns the value of the channel with the highest value scaled to the range of 0-100 (percent).
|
||||
<code>get <name> pct</code><br>
|
||||
returns the value of the channel with the highest value scaled to the range of 0-100 (percent).
|
||||
</ul><br>
|
||||
<a name="FRM_RGBattr"></a>
|
||||
|
||||
<a name="FRM_RGBattr"/>
|
||||
<b>Attributes</b><br>
|
||||
<ul>
|
||||
<li>restoreOnStartup <on|off></li>
|
||||
<li>restoreOnReconnect <on|off></li>
|
||||
<li><a href="#IODev">IODev</a><br>
|
||||
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
|
||||
than one FRM-device defined.)
|
||||
</li>
|
||||
<li><a href="#eventMap">eventMap</a><br></li>
|
||||
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
|
||||
</ul>
|
||||
<a name="restoreOnStartup"/>
|
||||
<li>restoreOnStartup <on|off></li>
|
||||
|
||||
<a name="restoreOnReconnect"/>
|
||||
<li>restoreOnReconnect <on|off></li>
|
||||
|
||||
<a name="IODev"/>
|
||||
<li><a href="#IODev">IODev</a><br>
|
||||
Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
|
||||
</li>
|
||||
|
||||
<li><a href="#attributes">global attributes</a></li>
|
||||
|
||||
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
|
||||
</ul>
|
||||
<br>
|
||||
</ul><br>
|
||||
|
||||
=end html
|
||||
|
||||
=begin html_DE
|
||||
|
||||
<a name="FRM_RGB"/>
|
||||
<h3>FRM_RGB</h3>
|
||||
<ul>
|
||||
Die Modulbeschreibung von FRM_RGB gibt es nur auf <a href="commandref.html#FRM_RGB">Englisch</a>. <br>
|
||||
</ul> <br>
|
||||
|
||||
=end html_DE
|
||||
|
||||
=cut
|
||||
|
@ -1,6 +1,42 @@
|
||||
##############################################
|
||||
########################################################################################
|
||||
# $Id$
|
||||
##############################################
|
||||
########################################################################################
|
||||
|
||||
=encoding UTF-8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
FHEM module for two Firmata rotary encoder input pins
|
||||
|
||||
=head1 LICENSE AND COPYRIGHT
|
||||
|
||||
Copyright (C) 2013 ntruchess
|
||||
Copyright (C) 2020 jensb
|
||||
|
||||
All rights reserved
|
||||
|
||||
This script is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This script is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this script; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
A copy of the GNU General Public License, Version 2 can also be found at
|
||||
|
||||
http://www.gnu.org/licenses/old-licenses/gpl-2.0.
|
||||
|
||||
This copyright notice MUST APPEAR in all copies of the script!
|
||||
|
||||
=cut
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
@ -8,37 +44,35 @@ use warnings;
|
||||
|
||||
#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
|
||||
BEGIN {
|
||||
if (!grep(/FHEM\/lib$/,@INC)) {
|
||||
foreach my $inc (grep(/FHEM$/,@INC)) {
|
||||
push @INC,$inc."/lib";
|
||||
};
|
||||
};
|
||||
if (!grep(/FHEM\/lib$/,@INC)) {
|
||||
foreach my $inc (grep(/FHEM$/,@INC)) {
|
||||
push @INC,$inc."/lib";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
use Device::Firmata::Constants qw/ :all /;
|
||||
|
||||
#####################################
|
||||
|
||||
# number of arguments
|
||||
my %sets = (
|
||||
"reset" => "noArg",
|
||||
"offset"=> "",
|
||||
"reset:noArg" => 0,
|
||||
"offset" => 1,
|
||||
);
|
||||
|
||||
my %gets = (
|
||||
"position" => "noArg",
|
||||
"offset" => "noArg",
|
||||
"value" => "noArg",
|
||||
"position" => "",
|
||||
"offset" => "",
|
||||
"value" => "",
|
||||
);
|
||||
|
||||
sub
|
||||
FRM_ROTENC_Initialize($)
|
||||
sub FRM_ROTENC_Initialize
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{SetFn} = "FRM_ROTENC_Set";
|
||||
$hash->{GetFn} = "FRM_ROTENC_Get";
|
||||
$hash->{AttrFn} = "FRM_ROTENC_Attr";
|
||||
$hash->{DefFn} = "FRM_Client_Define";
|
||||
$hash->{DefFn} = "FRM_ROTENC_Define";
|
||||
$hash->{InitFn} = "FRM_ROTENC_Init";
|
||||
$hash->{UndefFn} = "FRM_ROTENC_Undef";
|
||||
$hash->{StateFn} = "FRM_ROTENC_State";
|
||||
@ -47,73 +81,87 @@ FRM_ROTENC_Initialize($)
|
||||
main::LoadModule("FRM");
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_ROTENC_Init($$)
|
||||
sub FRM_ROTENC_Define
|
||||
{
|
||||
my ($hash,$args) = @_;
|
||||
my ($hash, $def) = @_;
|
||||
|
||||
my $u = "wrong syntax: define <name> FRM_ROTENC pinA pinB [id]";
|
||||
return $u unless defined $args and int(@$args) > 1;
|
||||
my $pinA = @$args[0];
|
||||
my $pinB = @$args[1];
|
||||
my $encoder = defined @$args[2] ? @$args[2] : 0;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
$hash->{PINA} = $pinA;
|
||||
$hash->{PINB} = $pinB;
|
||||
|
||||
$hash->{ENCODERNUM} = $encoder;
|
||||
|
||||
eval {
|
||||
FRM_Client_AssignIOPort($hash);
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
$firmata->encoder_attach($encoder,$pinA,$pinB);
|
||||
$firmata->observe_encoder($encoder, \&FRM_ROTENC_observer, $hash );
|
||||
};
|
||||
if ($@) {
|
||||
$@ =~ /^(.*)( at.*FHEM.*)$/;
|
||||
$hash->{STATE} = "error initializing: ".$1;
|
||||
return "error initializing '$name': $1";
|
||||
}
|
||||
# verify define arguments
|
||||
my $usage = "usage: define <name> FRM_ROTENC pinA pinB [id]";
|
||||
|
||||
if (! (defined AttrVal($name,"stateFormat",undef))) {
|
||||
$main::attr{$name}{"stateFormat"} = "position";
|
||||
}
|
||||
my @a = split("[ \t]+", $def);
|
||||
return $usage if (scalar(@a) < 4);
|
||||
my $args = [@a[2..scalar(@a)-1]];
|
||||
|
||||
$hash->{PINA} = @$args[0];
|
||||
$hash->{PINB} = @$args[1];
|
||||
|
||||
$hash->{ENCODERNUM} = defined @$args[2] ? @$args[2] : 0;
|
||||
|
||||
my $ret = FRM_Client_Define($hash, $def);
|
||||
if ($ret) {
|
||||
return $ret;
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub FRM_ROTENC_Init
|
||||
{
|
||||
my ($hash,$args) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
return 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
|
||||
eval {
|
||||
FRM_Client_AssignIOPort($hash);
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
$firmata->encoder_attach($hash->{ENCODERNUM}, $hash->{PINA}, $hash->{PINB});
|
||||
$firmata->observe_encoder($hash->{ENCODERNUM}, \&FRM_ROTENC_observer, $hash );
|
||||
};
|
||||
if ($@) {
|
||||
my $ret = FRM_Catch($@);
|
||||
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
if (! (defined AttrVal($name,"stateFormat",undef))) {
|
||||
$main::attr{$name}{"stateFormat"} = "position";
|
||||
}
|
||||
|
||||
$hash->{offset} = ReadingsVal($name,"position",0);
|
||||
|
||||
main::readingsSingleUpdate($hash,"state","Initialized",1);
|
||||
return undef;
|
||||
main::readingsSingleUpdate($hash,"state","Initialized",1);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_ROTENC_observer
|
||||
sub FRM_ROTENC_observer
|
||||
{
|
||||
my ( $encoder, $value, $hash ) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
Log3 ($name,5,"onEncoderMessage for pins ".$hash->{PINA}.",".$hash->{PINB}." encoder: ".$encoder." position: ".$value."\n");
|
||||
main::readingsBeginUpdate($hash);
|
||||
main::readingsBulkUpdate($hash,"position",$value+$hash->{offset}, 1);
|
||||
main::readingsBulkUpdate($hash,"value",$value, 1);
|
||||
main::readingsEndUpdate($hash,1);
|
||||
my ($encoder, $value, $hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
Log3 ($name, 5, "$name: observer pins: ".$hash->{PINA}.", ".$hash->{PINB}." encoder: ".$encoder." position: ".$value."\n");
|
||||
main::readingsBeginUpdate($hash);
|
||||
main::readingsBulkUpdate($hash,"position",$value+$hash->{offset}, 1);
|
||||
main::readingsBulkUpdate($hash,"value",$value, 1);
|
||||
main::readingsEndUpdate($hash,1);
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_ROTENC_Set
|
||||
sub FRM_ROTENC_Set
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
return "Need at least one parameters" if(@a < 2);
|
||||
my $command = $a[1];
|
||||
my $value = $a[2];
|
||||
if(!defined($sets{$command})) {
|
||||
my @commands = ();
|
||||
foreach my $key (sort keys %sets) {
|
||||
push @commands, $sets{$key} ? $key.":".join(",",$sets{$key}) : $key;
|
||||
}
|
||||
return "Unknown argument $a[1], choose one of " . join(" ", @commands);
|
||||
}
|
||||
COMMAND_HANDLER: {
|
||||
$command eq "reset" and do {
|
||||
my ($hash, $name, $cmd, @a) = @_;
|
||||
|
||||
return "set command missing" if(!defined($cmd));
|
||||
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
|
||||
return "unknown set command '$cmd', choose one of " . join(" ", sort keys %sets) if ($cmd eq '?' || @match == 0);
|
||||
return "$cmd requires $sets{$match[0]} argument(s)" unless (@a == $sets{$match[0]});
|
||||
|
||||
my $value = shift @a;
|
||||
SETHANDLER: {
|
||||
$cmd eq "reset" and do {
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
return 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
eval {
|
||||
FRM_Client_FirmataDevice($hash)->encoder_reset_position($hash->{ENCODERNUM});
|
||||
};
|
||||
@ -123,46 +171,37 @@ FRM_ROTENC_Set
|
||||
main::readingsEndUpdate($hash,1);
|
||||
last;
|
||||
};
|
||||
$command eq "offset" and do {
|
||||
$cmd eq "offset" and do {
|
||||
$hash->{offset} = $value;
|
||||
readingsSingleUpdate($hash,"position",ReadingsVal($hash->{NAME},"value",0)+$value,1);
|
||||
readingsSingleUpdate($hash,"position",ReadingsVal($name,"value",0)+$value,1);
|
||||
last;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_ROTENC_Get($)
|
||||
sub FRM_ROTENC_Get
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
return "Need at least one parameters" if(@a < 2);
|
||||
my $command = $a[1];
|
||||
my $value = $a[2];
|
||||
if(!defined($gets{$command})) {
|
||||
my @commands = ();
|
||||
foreach my $key (sort keys %gets) {
|
||||
push @commands, $gets{$key} ? $key.":".join(",",$gets{$key}) : $key;
|
||||
}
|
||||
return "Unknown argument $a[1], choose one of " . join(" ", @commands);
|
||||
}
|
||||
my $name = shift @a;
|
||||
my $cmd = shift @a;
|
||||
ARGUMENT_HANDLER: {
|
||||
my ($hash, $name, $cmd, @a) = @_;
|
||||
|
||||
return "get command missing" if(!defined($cmd));
|
||||
return "unknown get command '$cmd', choose one of " . join(":noArg ", sort keys %gets) . ":noArg" if(!defined($gets{$cmd}));
|
||||
|
||||
GETHANDLER: {
|
||||
$cmd eq "position" and do {
|
||||
return ReadingsVal($hash->{NAME},"position","0");
|
||||
return ReadingsVal($name,"position","0");
|
||||
};
|
||||
$cmd eq "offset" and do {
|
||||
return $hash->{offset};
|
||||
};
|
||||
$cmd eq "value" and do {
|
||||
return ReadingsVal($hash->{NAME},"value","0");
|
||||
return ReadingsVal($name,"value","0");
|
||||
};
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_ROTENC_Attr($$$$) {
|
||||
sub FRM_ROTENC_Attr
|
||||
{
|
||||
my ($command,$name,$attribute,$value) = @_;
|
||||
my $hash = $main::defs{$name};
|
||||
eval {
|
||||
@ -179,38 +218,32 @@ FRM_ROTENC_Attr($$$$) {
|
||||
}
|
||||
};
|
||||
if ($@) {
|
||||
$@ =~ /^(.*)( at.*FHEM.*)$/;
|
||||
$hash->{STATE} = "error setting $attribute to $value: ".$1;
|
||||
return "cannot $command attribute $attribute to $value for $name: ".$1;
|
||||
my $ret = FRM_Catch($@);
|
||||
$hash->{STATE} = "$command $attribute error: " . $ret;
|
||||
return $hash->{STATE};
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_ROTENC_Undef($$)
|
||||
sub FRM_ROTENC_Undef
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
|
||||
my $pinA = $hash->{PINA};
|
||||
my $pinB = $hash->{PINB};
|
||||
eval {
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
$firmata->encoder_detach($hash->{ENCODERNUM});
|
||||
$firmata->pin_mode($pinA,PIN_ANALOG);
|
||||
$firmata->pin_mode($pinB,PIN_ANALOG);
|
||||
};
|
||||
if ($@) {
|
||||
eval {
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
$firmata->pin_mode($pinA,PIN_INPUT);
|
||||
$firmata->digital_write($pinA,0);
|
||||
$firmata->pin_mode($pinB,PIN_INPUT);
|
||||
$firmata->digital_write($pinB,0);
|
||||
};
|
||||
}
|
||||
|
||||
$hash->{PIN} = $hash->{PINA};
|
||||
FRM_Client_Undef($hash, $name);
|
||||
$hash->{PIN} = $hash->{PINB};
|
||||
FRM_Client_Undef($hash, $name);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_ROTENC_State($$$$)
|
||||
sub FRM_ROTENC_State
|
||||
{
|
||||
my ($hash, $tim, $sname, $sval) = @_;
|
||||
if ($sname eq "position") {
|
||||
@ -222,30 +255,64 @@ FRM_ROTENC_State($$$$)
|
||||
1;
|
||||
|
||||
=pod
|
||||
|
||||
=head1 CHANGES
|
||||
|
||||
05.09.2020 jensb
|
||||
o check for IODev install error in Init, Set and Undef
|
||||
o prototypes removed
|
||||
o set argument verifier improved
|
||||
o moved define argument verification and decoding from Init to Define
|
||||
|
||||
19.10.2020 jensb
|
||||
o annotaded module help of attributes for FHEMWEB
|
||||
|
||||
=cut
|
||||
|
||||
|
||||
=pod
|
||||
|
||||
=head1 FHEM COMMANDREF METADATA
|
||||
|
||||
=over
|
||||
|
||||
=item device
|
||||
|
||||
=item summary Firmata: rotary encoder input
|
||||
|
||||
=item summary_DE Firmata: Drehgeber Eingang
|
||||
|
||||
=back
|
||||
|
||||
=head1 INSTALLATION AND CONFIGURATION
|
||||
|
||||
=begin html
|
||||
|
||||
<a name="FRM_ROTENC"></a>
|
||||
<a name="FRM_ROTENC"/>
|
||||
<h3>FRM_ROTENC</h3>
|
||||
<ul>
|
||||
represents a rotary-encoder attached to two pins of an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a><br>
|
||||
Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br>
|
||||
|
||||
<a name="FRM_ROTENCdefine"></a>
|
||||
Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br>
|
||||
|
||||
<a name="FRM_ROTENCdefine"/>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> FRM_ROTENC <pinA> <pinB> [id]</code> <br>
|
||||
Defines the FRM_ROTENC device. <pinA>> and <pinA>> are the arduino-pins to use.<br>
|
||||
[id] is the instance-id of the encoder. Must be a unique number per FRM-device (rages from 0-4 depending on Firmata being used, optional if a single encoder is attached to the arduino).<br>
|
||||
</ul>
|
||||
|
||||
|
||||
<br>
|
||||
<a name="FRM_ROTENCset"></a>
|
||||
<a name="FRM_ROTENCset"/>
|
||||
<b>Set</b><br>
|
||||
<ul>
|
||||
<li>reset<br>
|
||||
resets to value of 'position' to 0<br></li>
|
||||
<li>offset <value><br>
|
||||
set offset value of 'position'<br></li>
|
||||
<a name="FRM_ROTENCget"></a>
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_ROTENCget"/>
|
||||
<b>Get</b>
|
||||
<ul>
|
||||
<li>position<br>
|
||||
@ -258,18 +325,31 @@ FRM_ROTENC_State($$$$)
|
||||
returns the raw position value as it's reported by the rotary-encoder attached to pinA and pinB of the arduino<br>
|
||||
this value is reset to 0 whenever Arduino restarts or Firmata is reinitialized<br></li>
|
||||
</ul><br>
|
||||
<a name="FRM_ROTENCattr"></a>
|
||||
|
||||
<a name="FRM_ROTENCattr"/>
|
||||
<b>Attributes</b><br>
|
||||
<ul>
|
||||
<li><a href="#IODev">IODev</a><br>
|
||||
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
|
||||
than one FRM-device defined.)
|
||||
</li>
|
||||
<li><a href="#eventMap">eventMap</a><br></li>
|
||||
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
|
||||
</ul>
|
||||
<a name="IODev"/>
|
||||
<li><a href="#IODev">IODev</a><br>
|
||||
Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
|
||||
</li>
|
||||
|
||||
<li><a href="#attributes">global attributes</a></li>
|
||||
|
||||
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
|
||||
</ul>
|
||||
<br>
|
||||
</ul><br>
|
||||
|
||||
=end html
|
||||
|
||||
=begin html_DE
|
||||
|
||||
<a name="FRM_ROTENC"/>
|
||||
<h3>FRM_ROTENC</h3>
|
||||
<ul>
|
||||
Die Modulbeschreibung von FRM_ROTENC gibt es nur auf <a href="commandref.html#FRM_ROTENC">Englisch</a>. <br>
|
||||
</ul><br>
|
||||
|
||||
=end html_DE
|
||||
|
||||
=cut
|
||||
|
@ -1,6 +1,42 @@
|
||||
##############################################
|
||||
########################################################################################
|
||||
# $Id$
|
||||
##############################################
|
||||
########################################################################################
|
||||
|
||||
=encoding UTF-8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
FHEM module for one Firmata PMW controlled servo output
|
||||
|
||||
=head1 LICENSE AND COPYRIGHT
|
||||
|
||||
Copyright (C) 2013 ntruchess
|
||||
Copyright (C) 2020 jensb
|
||||
|
||||
All rights reserved
|
||||
|
||||
This script is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This script is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this script; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
A copy of the GNU General Public License, Version 2 can also be found at
|
||||
|
||||
http://www.gnu.org/licenses/old-licenses/gpl-2.0.
|
||||
|
||||
This copyright notice MUST APPEAR in all copies of the script!
|
||||
|
||||
=cut
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
@ -8,23 +44,21 @@ use warnings;
|
||||
|
||||
#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
|
||||
BEGIN {
|
||||
if (!grep(/FHEM\/lib$/,@INC)) {
|
||||
foreach my $inc (grep(/FHEM$/,@INC)) {
|
||||
push @INC,$inc."/lib";
|
||||
};
|
||||
};
|
||||
if (!grep(/FHEM\/lib$/,@INC)) {
|
||||
foreach my $inc (grep(/FHEM$/,@INC)) {
|
||||
push @INC,$inc."/lib";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
use Device::Firmata::Constants qw/ :all /;
|
||||
|
||||
#####################################
|
||||
|
||||
# number of arguments
|
||||
my %sets = (
|
||||
"angle" => "",
|
||||
"angle" => 1,
|
||||
);
|
||||
|
||||
sub
|
||||
FRM_SERVO_Initialize($)
|
||||
sub FRM_SERVO_Initialize
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
@ -33,29 +67,44 @@ FRM_SERVO_Initialize($)
|
||||
$hash->{InitFn} = "FRM_SERVO_Init";
|
||||
$hash->{UndefFn} = "FRM_Client_Undef";
|
||||
$hash->{AttrFn} = "FRM_SERVO_Attr";
|
||||
|
||||
|
||||
$hash->{AttrList} = "min-pulse max-pulse IODev $main::readingFnAttributes";
|
||||
main::LoadModule("FRM");
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_SERVO_Init($$)
|
||||
sub FRM_SERVO_Init
|
||||
{
|
||||
my ($hash,$args) = @_;
|
||||
my $ret = FRM_Init_Pin_Client($hash,$args,PIN_SERVO);
|
||||
return $ret if (defined $ret);
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
return 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
|
||||
my $ret = FRM_Init_Pin_Client($hash,$args,Device::Firmata::Constants->PIN_SERVO);
|
||||
if (defined($ret)) {
|
||||
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
eval {
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
$hash->{resolution}=$firmata->{metadata}{servo_resolutions}{$hash->{PIN}} if (defined $firmata->{metadata}{servo_resolutions});
|
||||
FRM_SERVO_apply_attribute($hash,"max-pulse"); #sets min-pulse as well
|
||||
};
|
||||
return FRM_Catch($@) if $@;
|
||||
if ($@) {
|
||||
$ret = FRM_Catch($@);
|
||||
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
main::readingsSingleUpdate($hash,"state","Initialized",1);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_SERVO_Attr($$$$) {
|
||||
sub FRM_SERVO_Attr
|
||||
{
|
||||
my ($command,$name,$attribute,$value) = @_;
|
||||
my $hash = $main::defs{$name};
|
||||
eval {
|
||||
@ -70,7 +119,10 @@ FRM_SERVO_Attr($$$$) {
|
||||
};
|
||||
($attribute eq "min-pulse" || $attribute eq "max-pulse") and do {
|
||||
if ($main::init_done) {
|
||||
$main::attr{$name}{$attribute}=$value;
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
die 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
$main::attr{$name}{$attribute}=$value;
|
||||
FRM_SERVO_apply_attribute($hash,$attribute);
|
||||
}
|
||||
last;
|
||||
@ -78,15 +130,15 @@ FRM_SERVO_Attr($$$$) {
|
||||
}
|
||||
}
|
||||
};
|
||||
my $ret = FRM_Catch($@) if $@;
|
||||
if ($ret) {
|
||||
$hash->{STATE} = "error setting $attribute to $value: ".$ret;
|
||||
return "cannot $command attribute $attribute to $value for $name: ".$ret;
|
||||
if ($@) {
|
||||
my $ret = FRM_Catch($@);
|
||||
$hash->{STATE} = "$command $attribute error: " . $ret;
|
||||
return $hash->{STATE};
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub FRM_SERVO_apply_attribute {
|
||||
sub FRM_SERVO_apply_attribute
|
||||
{
|
||||
my ($hash,$attribute) = @_;
|
||||
if ( $attribute eq "min-pulse" || $attribute eq "max-pulse" ) {
|
||||
my $name = $hash->{NAME};
|
||||
@ -95,69 +147,130 @@ sub FRM_SERVO_apply_attribute {
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_SERVO_Set($@)
|
||||
sub FRM_SERVO_Set
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
return "Need at least one parameters" if(@a < 2);
|
||||
return "Unknown argument $a[1], choose one of " . join(" ", sort keys %sets)
|
||||
if(!defined($sets{$a[1]}));
|
||||
my $command = $a[1];
|
||||
my $value = $a[2];
|
||||
my ($hash, $name, $cmd, @a) = @_;
|
||||
|
||||
return "set command missing" if(!defined($cmd));
|
||||
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
|
||||
return "unknown set command '$cmd', choose one of " . join(" ", sort keys %sets) if ($cmd eq '?' || @match == 0);
|
||||
return "$cmd requires $sets{$match[0]} argument" unless (@a == $sets{$match[0]});
|
||||
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
return 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
|
||||
my $value = shift @a;
|
||||
eval {
|
||||
FRM_Client_FirmataDevice($hash)->servo_write($hash->{PIN},$value);
|
||||
main::readingsSingleUpdate($hash,"state",$value, 1);
|
||||
};
|
||||
return $@;
|
||||
if ($@) {
|
||||
my $ret = FRM_Catch($@);
|
||||
$hash->{STATE} = "set $cmd error: " . $ret;
|
||||
return $hash->{STATE};
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=pod
|
||||
|
||||
=head1 CHANGES
|
||||
|
||||
05.09.2020 jensb
|
||||
o check for IODev install error in Init and Set
|
||||
o prototypes removed
|
||||
o set argument verifier improved
|
||||
|
||||
19.10.2020 jensb
|
||||
o annotaded module help of attributes for FHEMWEB
|
||||
|
||||
=cut
|
||||
|
||||
|
||||
=pod
|
||||
|
||||
=head1 FHEM COMMANDREF METADATA
|
||||
|
||||
=over
|
||||
|
||||
=item device
|
||||
|
||||
=item summary Firmata: PWM controlled servo output
|
||||
|
||||
=item summary_DE Firmata: PWM gesteuerter Servo Ausgang
|
||||
|
||||
=back
|
||||
|
||||
=head1 INSTALLATION AND CONFIGURATION
|
||||
|
||||
=begin html
|
||||
|
||||
<a name="FRM_SERVO"></a>
|
||||
<a name="FRM_SERVO"/>
|
||||
<h3>FRM_SERVO</h3>
|
||||
<ul>
|
||||
represents a pin of an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a>
|
||||
configured to drive a pwm-controlled servo-motor.<br>
|
||||
The value set will be drive the shaft of the servo to the specified angle. see <a href="http://arduino.cc/en/Reference/ServoWrite">Servo.write</a> for values and range<br>
|
||||
Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br>
|
||||
|
||||
<a name="FRM_SERVOdefine"></a>
|
||||
The value set will be drive the shaft of the servo to the specified angle. see <a href="http://arduino.cc/en/Reference/ServoWrite">Servo.write</a> for values and range<br>
|
||||
Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br>
|
||||
|
||||
<a name="FRM_SERVOdefine"/>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> FRM_SERVO <pin></code> <br>
|
||||
Defines the FRM_SERVO device. <pin>> is the arduino-pin to use.
|
||||
</ul>
|
||||
|
||||
|
||||
<br>
|
||||
<a name="FRM_SERVOset"></a>
|
||||
<a name="FRM_SERVOset"/>
|
||||
<b>Set</b><br>
|
||||
<ul>
|
||||
<code>set <name> angle <value></code><br>sets the angle of the servo-motors shaft to the value specified (in degrees).<br>
|
||||
</ul>
|
||||
<a name="FRM_SERVOget"></a>
|
||||
|
||||
<a name="FRM_SERVOget"/>
|
||||
<b>Get</b><br>
|
||||
<ul>
|
||||
N/A
|
||||
</ul><br>
|
||||
<a name="FRM_SERVOattr"></a>
|
||||
|
||||
<a name="FRM_SERVOattr"/>
|
||||
<b>Attributes</b><br>
|
||||
<ul>
|
||||
<li><a href="#IODev">IODev</a><br>
|
||||
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
|
||||
than one FRM-device defined.)
|
||||
</li>
|
||||
<li>min-pulse<br>
|
||||
sets the minimum puls-width to use. Defaults to 544. For most servos this translates into a rotation of 180° counterclockwise.</li>
|
||||
<li>max-pulse<br>
|
||||
sets the maximum puls-width to use. Defaults to 2400. For most servos this translates into a rotation of 180° clockwise</li>
|
||||
<li><a href="#eventMap">eventMap</a><br></li>
|
||||
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
|
||||
</ul>
|
||||
<a name="IODev"/>
|
||||
<li><a href="#IODev">IODev</a><br>
|
||||
Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
|
||||
</li>
|
||||
|
||||
<a name="min-pulse"/>
|
||||
<li>min-pulse<br>
|
||||
sets the minimum puls-width to use. Defaults to 544. For most servos this translates into a rotation of 180° counterclockwise.
|
||||
</li>
|
||||
|
||||
<a name="max-pulse"/>
|
||||
<li>max-pulse<br>
|
||||
sets the maximum puls-width to use. Defaults to 2400. For most servos this translates into a rotation of 180° clockwise.
|
||||
</li>
|
||||
|
||||
<li><a href="#attributes">global attributes</a></li>
|
||||
|
||||
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
|
||||
</ul>
|
||||
<br>
|
||||
</ul><br>
|
||||
|
||||
=end html
|
||||
|
||||
=begin html_DE
|
||||
|
||||
<a name="FRM_SERVO"/>
|
||||
<h3>FRM_SERVO</h3>
|
||||
<ul>
|
||||
Die Modulbeschreibung von FRM_SERVO gibt es nur auf <a href="commandref.html#FRM_SERVO">Englisch</a>. <br>
|
||||
</ul><br>
|
||||
|
||||
=end html_DE
|
||||
|
||||
=cut
|
||||
|
@ -1,6 +1,42 @@
|
||||
##############################################
|
||||
########################################################################################
|
||||
# $Id$
|
||||
##############################################
|
||||
########################################################################################
|
||||
|
||||
=encoding UTF-8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
FHEM module for two/four Firmata stepper motor output pins
|
||||
|
||||
=head1 LICENSE AND COPYRIGHT
|
||||
|
||||
Copyright (C) 2013 ntruchess
|
||||
Copyright (C) 2020 jensb
|
||||
|
||||
All rights reserved
|
||||
|
||||
This script is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This script is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this script; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
A copy of the GNU General Public License, Version 2 can also be found at
|
||||
|
||||
http://www.gnu.org/licenses/old-licenses/gpl-2.0.
|
||||
|
||||
This copyright notice MUST APPEAR in all copies of the script!
|
||||
|
||||
=cut
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
@ -8,136 +44,151 @@ use warnings;
|
||||
|
||||
#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
|
||||
BEGIN {
|
||||
if (!grep(/FHEM\/lib$/,@INC)) {
|
||||
foreach my $inc (grep(/FHEM$/,@INC)) {
|
||||
push @INC,$inc."/lib";
|
||||
};
|
||||
};
|
||||
if (!grep(/FHEM\/lib$/,@INC)) {
|
||||
foreach my $inc (grep(/FHEM$/,@INC)) {
|
||||
push @INC,$inc."/lib";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
use Device::Firmata::Constants qw/ :all /;
|
||||
|
||||
#####################################
|
||||
|
||||
# (min) number of arguments
|
||||
my %sets = (
|
||||
"reset" => "noArg",
|
||||
"position" => "",
|
||||
"step" => "",
|
||||
"reset:noArg" => 0,
|
||||
"position" => 1,
|
||||
"step" => 1,
|
||||
);
|
||||
|
||||
my %gets = (
|
||||
"position" => "noArg",
|
||||
"position" => "",
|
||||
);
|
||||
|
||||
sub
|
||||
FRM_STEPPER_Initialize($)
|
||||
sub FRM_STEPPER_Initialize
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{SetFn} = "FRM_STEPPER_Set";
|
||||
$hash->{GetFn} = "FRM_STEPPER_Get";
|
||||
$hash->{DefFn} = "FRM_Client_Define";
|
||||
$hash->{DefFn} = "FRM_STEPPER_Define";
|
||||
$hash->{InitFn} = "FRM_STEPPER_Init";
|
||||
$hash->{UndefFn} = "FRM_Client_Undef";
|
||||
$hash->{AttrFn} = "FRM_STEPPER_Attr";
|
||||
$hash->{StateFn} = "FRM_STEPPER_State";
|
||||
|
||||
|
||||
$hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off speed acceleration deceleration IODev $main::readingFnAttributes";
|
||||
main::LoadModule("FRM");
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_STEPPER_Init($$)
|
||||
sub FRM_STEPPER_Define
|
||||
{
|
||||
my ($hash,$args) = @_;
|
||||
my ($hash, $def) = @_;
|
||||
|
||||
my $u = "wrong syntax: define <name> FRM_STEPPER [DRIVER|TWO_WIRE|FOUR_WIRE] directionPin stepPin [motorPin3 motorPin4] stepsPerRev [id]";
|
||||
return $u unless defined $args;
|
||||
|
||||
my $driver = shift @$args;
|
||||
|
||||
return $u unless ( $driver eq 'DRIVER' or $driver eq 'TWO_WIRE' or $driver eq 'FOUR_WIRE' );
|
||||
return $u if (($driver eq 'DRIVER' or $driver eq 'TWO_WIRE') and (scalar(@$args) < 3 or scalar(@$args) > 4));
|
||||
return $u if (($driver eq 'FOUR_WIRE') and (scalar(@$args) < 5 or scalar(@$args) > 6));
|
||||
|
||||
$hash->{DRIVER} = $driver;
|
||||
|
||||
$hash->{PIN1} = shift @$args;
|
||||
$hash->{PIN2} = shift @$args;
|
||||
|
||||
if ($driver eq 'FOUR_WIRE') {
|
||||
$hash->{PIN3} = shift @$args;
|
||||
$hash->{PIN4} = shift @$args;
|
||||
}
|
||||
|
||||
$hash->{STEPSPERREV} = shift @$args;
|
||||
$hash->{STEPPERNUM} = shift @$args;
|
||||
|
||||
eval {
|
||||
FRM_Client_AssignIOPort($hash);
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
$firmata->stepper_config(
|
||||
$hash->{STEPPERNUM},
|
||||
$driver,
|
||||
$hash->{STEPSPERREV},
|
||||
$hash->{PIN1},
|
||||
$hash->{PIN2},
|
||||
$hash->{PIN3},
|
||||
$hash->{PIN4});
|
||||
$firmata->observe_stepper(0, \&FRM_STEPPER_observer, $hash );
|
||||
};
|
||||
if ($@) {
|
||||
$@ =~ /^(.*)( at.*FHEM.*)$/;
|
||||
$hash->{STATE} = "error initializing: ".$1;
|
||||
return "error initializing '".$hash->{NAME}."': ".$1;
|
||||
}
|
||||
$hash->{POSITION} = 0;
|
||||
$hash->{DIRECTION} = 0;
|
||||
$hash->{STEPS} = 0;
|
||||
if (! (defined AttrVal($hash->{NAME},"stateFormat",undef))) {
|
||||
$main::attr{$hash->{NAME}}{"stateFormat"} = "position";
|
||||
}
|
||||
main::readingsSingleUpdate($hash,"state","Initialized",1);
|
||||
return undef;
|
||||
}
|
||||
# verify define arguments
|
||||
my $usage = "usage: define <name> FRM_STEPPER [DRIVER|TWO_WIRE|FOUR_WIRE] directionPin stepPin [motorPin3 motorPin4] stepsPerRev [id]";
|
||||
|
||||
sub
|
||||
FRM_STEPPER_observer
|
||||
{
|
||||
my ( $stepper, $hash ) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
Log3 $name,5,"onStepperMessage for pins ".$hash->{PIN1}.",".$hash->{PIN2}.(defined ($hash->{PIN3}) ? ",".$hash->{PIN3} : ",-").(defined ($hash->{PIN4}) ? ",".$hash->{PIN4} : ",-")." stepper: ".$stepper;
|
||||
my $position = $hash->{DIRECTION} ? $hash->{POSITION} - $hash->{STEPS} : $hash->{POSITION} + $hash->{STEPS};
|
||||
$hash->{POSITION} = $position;
|
||||
$hash->{DIRECTION} = 0;
|
||||
$hash->{STEPS} = 0;
|
||||
main::readingsSingleUpdate($hash,"position",$position,1);
|
||||
}
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
my $args = [@a[2..scalar(@a)-1]];
|
||||
return $usage unless defined $args;
|
||||
|
||||
sub
|
||||
FRM_STEPPER_Set
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
return "Need at least one parameters" if(@a < 2);
|
||||
shift @a;
|
||||
my $name = $hash->{NAME};
|
||||
my $command = shift @a;
|
||||
if(!defined($sets{$command})) {
|
||||
my @commands = ();
|
||||
foreach my $key (sort keys %sets) {
|
||||
push @commands, $sets{$key} ? $key.":".join(",",$sets{$key}) : $key;
|
||||
}
|
||||
return "Unknown argument $command, choose one of " . join(" ", @commands);
|
||||
my $driver = shift @$args;
|
||||
return $usage unless ( $driver eq 'DRIVER' or $driver eq 'TWO_WIRE' or $driver eq 'FOUR_WIRE' );
|
||||
return $usage if (($driver eq 'DRIVER' or $driver eq 'TWO_WIRE') and (scalar(@$args) < 3 or scalar(@$args) > 4));
|
||||
return $usage if (($driver eq 'FOUR_WIRE') and (scalar(@$args) < 5 or scalar(@$args) > 6));
|
||||
|
||||
$hash->{DRIVER} = $driver;
|
||||
|
||||
$hash->{PIN1} = shift @$args;
|
||||
$hash->{PIN2} = shift @$args;
|
||||
|
||||
if ($driver eq 'FOUR_WIRE') {
|
||||
$hash->{PIN3} = shift @$args;
|
||||
$hash->{PIN4} = shift @$args;
|
||||
}
|
||||
COMMAND_HANDLER: {
|
||||
$command eq "reset" and do {
|
||||
|
||||
$hash->{STEPSPERREV} = shift @$args;
|
||||
$hash->{STEPPERNUM} = shift @$args;
|
||||
|
||||
my $ret = FRM_Client_Define($hash, $def);
|
||||
if ($ret) {
|
||||
return $ret;
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub FRM_STEPPER_Init
|
||||
{
|
||||
my ($hash,$args) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
return 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
|
||||
eval {
|
||||
FRM_Client_AssignIOPort($hash);
|
||||
my $firmata = FRM_Client_FirmataDevice($hash);
|
||||
$firmata->stepper_config(
|
||||
$hash->{STEPPERNUM},
|
||||
$hash->{DRIVER},
|
||||
$hash->{STEPSPERREV},
|
||||
$hash->{PIN1},
|
||||
$hash->{PIN2},
|
||||
$hash->{PIN3},
|
||||
$hash->{PIN4});
|
||||
$firmata->observe_stepper(0, \&FRM_STEPPER_observer, $hash );
|
||||
};
|
||||
if ($@) {
|
||||
my $ret = FRM_Catch($@);
|
||||
readingsSingleUpdate($hash, 'state', "error initializing: $ret", 1);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
$hash->{POSITION} = 0;
|
||||
$hash->{DIRECTION} = 0;
|
||||
$hash->{STEPS} = 0;
|
||||
if (! (defined AttrVal($name,"stateFormat",undef))) {
|
||||
$main::attr{$name}{"stateFormat"} = "position";
|
||||
}
|
||||
|
||||
main::readingsSingleUpdate($hash,"state","Initialized",1);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub FRM_STEPPER_observer
|
||||
{
|
||||
my ( $stepper, $hash ) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
Log3 $name, 5, "$name: observer pins: ".$hash->{PIN1}.",".$hash->{PIN2}.(defined ($hash->{PIN3}) ? ",".$hash->{PIN3} : ",-").(defined ($hash->{PIN4}) ? ",".$hash->{PIN4} : ",-")." stepper: ".$stepper;
|
||||
my $position = $hash->{DIRECTION} ? $hash->{POSITION} - $hash->{STEPS} : $hash->{POSITION} + $hash->{STEPS};
|
||||
$hash->{POSITION} = $position;
|
||||
$hash->{DIRECTION} = 0;
|
||||
$hash->{STEPS} = 0;
|
||||
main::readingsSingleUpdate($hash,"position",$position,1);
|
||||
}
|
||||
|
||||
sub FRM_STEPPER_Set
|
||||
{
|
||||
my ($hash, $name, $cmd, @a) = @_;
|
||||
|
||||
return "set command missing" if(!defined($cmd));
|
||||
my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
|
||||
return "unknown set command '$cmd', choose one of " . join(" ", sort keys %sets) if ($cmd eq '?' || @match == 0);
|
||||
return "$cmd requires (at least) $sets{$match[0]} argument(s)" unless (@a >= $sets{$match[0]});
|
||||
|
||||
my $value = shift @a;
|
||||
SETHANDLER: {
|
||||
$cmd eq "reset" and do {
|
||||
$hash->{POSITION} = 0;
|
||||
main::readingsSingleUpdate($hash,"position",0,1);
|
||||
last;
|
||||
};
|
||||
$command eq "position" and do {
|
||||
$cmd eq "position" and do {
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
return 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
my $position = $hash->{POSITION};
|
||||
my $value = shift @a;
|
||||
my $direction = $value < $position ? 1 : 0;
|
||||
my $steps = $direction ? $position - $value : $value - $position;
|
||||
my $speed = shift @a;
|
||||
@ -149,13 +200,19 @@ FRM_STEPPER_Set
|
||||
$hash->{DIRECTION} = $direction;
|
||||
$hash->{STEPS} = $steps;
|
||||
eval {
|
||||
# $stepperNum, $direction, $numSteps, $stepSpeed, $accel, $decel
|
||||
FRM_Client_FirmataDevice($hash)->stepper_step($hash->{STEPPERNUM},$direction,$steps,$speed,$accel,$decel);
|
||||
};
|
||||
if ($@) {
|
||||
my $ret = FRM_Catch($@);
|
||||
$hash->{STATE} = "set $cmd error: " . $ret;
|
||||
return $hash->{STATE};
|
||||
}
|
||||
last;
|
||||
};
|
||||
$command eq "step" and do {
|
||||
my $value = shift @a;
|
||||
$cmd eq "step" and do {
|
||||
if (defined($main::defs{$name}{IODev_ERROR})) {
|
||||
return 'Perl module Device::Firmata not properly installed';
|
||||
}
|
||||
my $direction = $value < 0 ? 1 : 0;
|
||||
my $steps = abs $value;
|
||||
my $speed = shift @a;
|
||||
@ -167,42 +224,53 @@ FRM_STEPPER_Set
|
||||
$hash->{DIRECTION} = $direction;
|
||||
$hash->{STEPS} = $steps;
|
||||
eval {
|
||||
# $stepperNum, $direction, $numSteps, $stepSpeed, $accel, $decel
|
||||
FRM_Client_FirmataDevice($hash)->stepper_step($hash->{STEPPERNUM},$direction,$steps,$speed,$accel,$decel);
|
||||
};
|
||||
if ($@) {
|
||||
my $ret = FRM_Catch($@);
|
||||
$hash->{STATE} = "set $cmd error: " . $ret;
|
||||
return $hash->{STATE};
|
||||
}
|
||||
last;
|
||||
};
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_STEPPER_Get
|
||||
sub FRM_STEPPER_Get
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
return "Need at least one parameters" if(@a < 2);
|
||||
shift @a;
|
||||
my $name = $hash->{NAME};
|
||||
my $command = shift @a;
|
||||
return "Unknown argument $command, choose one of " . join(" ", sort keys %gets) unless defined($gets{$command});
|
||||
my ($hash, $name, $cmd, @a) = @_;
|
||||
|
||||
return "get command missing" if(!defined($cmd));
|
||||
return "unknown get command '$cmd', choose one of " . join(":noArg ", sort keys %gets) . ":noArg" if(!defined($gets{$cmd}));
|
||||
|
||||
GETHANDLER: {
|
||||
$cmd eq 'position' and do {
|
||||
return $hash->{POSITION};
|
||||
};
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
sub FRM_STEPPER_State($$$$)
|
||||
sub FRM_STEPPER_State
|
||||
{
|
||||
my ($hash, $tim, $sname, $sval) = @_;
|
||||
|
||||
STATEHANDLER: {
|
||||
$sname eq "value" and do {
|
||||
if (AttrVal($hash->{NAME},"restoreOnStartup","on") eq "on") {
|
||||
FRM_STEPPER_Set($hash,$hash->{NAME},$sval);
|
||||
}
|
||||
last;
|
||||
}
|
||||
}
|
||||
my ($hash, $tim, $sname, $sval) = @_;
|
||||
|
||||
STATEHANDLER: {
|
||||
$sname eq "value" and do {
|
||||
if (AttrVal($hash->{NAME},"restoreOnStartup","on") eq "on") {
|
||||
FRM_STEPPER_Set($hash,$hash->{NAME},$sval);
|
||||
}
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_STEPPER_Attr($$$$) {
|
||||
sub FRM_STEPPER_Attr
|
||||
{
|
||||
my ($command,$name,$attribute,$value) = @_;
|
||||
my $hash = $main::defs{$name};
|
||||
eval {
|
||||
@ -219,115 +287,183 @@ FRM_STEPPER_Attr($$$$) {
|
||||
}
|
||||
};
|
||||
if ($@) {
|
||||
$@ =~ /^(.*)( at.*FHEM.*)$/;
|
||||
$hash->{STATE} = "error setting $attribute to $value: ".$1;
|
||||
return "cannot $command attribute $attribute to $value for $name: ".$1;
|
||||
my $ret = FRM_Catch($@);
|
||||
$hash->{STATE} = "$command $attribute error: " . $ret;
|
||||
return $hash->{STATE};
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=pod
|
||||
|
||||
=head1 CHANGES
|
||||
|
||||
05.09.2020 jensb
|
||||
o check for IODev install error in Init and Set
|
||||
o prototypes removed
|
||||
o get position implemented
|
||||
o set argument verifier improved
|
||||
o module help updated
|
||||
o moved define argument verification and decoding from Init to Define
|
||||
|
||||
22.10.2020 jensb
|
||||
o annotaded module help of attributes for FHEMWEB
|
||||
|
||||
=cut
|
||||
|
||||
|
||||
=pod
|
||||
|
||||
=head1 FHEM COMMANDREF METADATA
|
||||
|
||||
=over
|
||||
|
||||
=item device
|
||||
|
||||
=item summary Firmata: rotary encoder input
|
||||
|
||||
=item summary_DE Firmata: Drehgeber Eingang
|
||||
|
||||
=back
|
||||
|
||||
=head1 INSTALLATION AND CONFIGURATION
|
||||
|
||||
=begin html
|
||||
|
||||
<a name="FRM_STEPPER"></a>
|
||||
<h3>FRM_STEPPER</h3>
|
||||
<ul>
|
||||
represents a stepper-motor attached to digital-i/o pins of an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a><br>
|
||||
Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br>
|
||||
|
||||
represents a stepper-motor attached to digital-i/o pins of an <a href="http://www.arduino.cc">Arduino</a>
|
||||
running <a href="http://www.firmata.org">Firmata</a><br>
|
||||
Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br>
|
||||
|
||||
<a name="FRM_STEPPERdefine"></a>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> FRM_STEPPER [DRIVER|TWO_WIRE|FOUR_WIRE] <directionPin> <stepPin> [motorPin3 motorPin4] stepsPerRev [stepper-id]</code><br>
|
||||
Defines the FRM_STEPPER device.
|
||||
<li>[DRIVER|TWO_WIRE|FOUR_WIRE] defines the control-sequence being used to drive the motor.
|
||||
<ul>
|
||||
<li>DRIVER: motor is attached via a smart circuit that is controlled via two lines: 1 line defines the direction to turn, the other triggers one step per impluse.</li>
|
||||
<li>FOUR_WIRE: motor is attached via four wires each driving one coil individually.</li>
|
||||
<li>TWO_WIRE: motor is attached via two wires. This mode makes use of the fact that at any time two of the four motor
|
||||
coils are the inverse of the other two so by using an inverting circuit to drive the motor the number of control connections can be reduced from 4 to 2.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<ul>
|
||||
<li>The sequence of control signals for 4 control wires is as follows:<br>
|
||||
<br>
|
||||
<code>
|
||||
Step C0 C1 C2 C3<br>
|
||||
1 1 0 1 0<br>
|
||||
2 0 1 1 0<br>
|
||||
3 0 1 0 1<br>
|
||||
4 1 0 0 1<br>
|
||||
</code>
|
||||
</li>
|
||||
<li>The sequence of controls signals for 2 control wires is as follows:<br>
|
||||
(columns C1 and C2 from above):<br>
|
||||
<br>
|
||||
<code>
|
||||
Step C0 C1<br>
|
||||
1 0 1<br>
|
||||
2 1 1<br>
|
||||
3 1 0<br>
|
||||
4 0 0<br>
|
||||
</code>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
If your stepper-motor does not move or does move but only in a single direction you will have to rearrage the pin-numbers to match the control sequence.<br>
|
||||
that can be archived either by rearranging the physical connections, or by mapping the connection to the pin-definitions in FRM_STEPPERS define:<br>
|
||||
e.g. the widely used cheap 28byj-48 you can get for few EUR on eBay including a simple ULN2003 driver interface may be defined by<br>
|
||||
<code>define stepper FRM_STEPPER FOUR_WIRE 7 5 6 8 64 0</code><br>
|
||||
when being connected to the arduio with:<br>
|
||||
<code>motor pin1 <-> arduino pin5<br>
|
||||
motor pin2 <-> arduino pin6<br>
|
||||
motor pin3 <-> arduino pin7<br>
|
||||
motor pin4 <-> arduino pin8<br>
|
||||
motor pin5 <-> ground</code><br>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
<code>define <name> FRM_STEPPER [DRIVER|TWO_WIRE|FOUR_WIRE] <directionPin> <stepPin> [motorPin3 motorPin4] stepsPerRev [stepper-id]</code><br>
|
||||
Defines the FRM_STEPPER device.
|
||||
<li>[DRIVER|TWO_WIRE|FOUR_WIRE] defines the control-sequence being used to drive the motor.
|
||||
<ul>
|
||||
<li>DRIVER: motor is attached via a smart circuit that is controlled via two lines: 1 line defines the
|
||||
direction to turn, the other triggers one step per impluse.
|
||||
</li>
|
||||
<li>FOUR_WIRE: motor is attached via four wires each driving one coil individually.</li>
|
||||
<li>TWO_WIRE: motor is attached via two wires. This mode makes use of the fact that at any time two of
|
||||
the four motor coils are the inverse of the other two so by using an inverting circuit to drive the motor
|
||||
the number of control connections can be reduced from 4 to 2.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<ul>
|
||||
<li>The sequence of control signals for 4 control wires is as follows:<br><br>
|
||||
|
||||
<code>
|
||||
Step C0 C1 C2 C3<br>
|
||||
1 1 0 1 0<br>
|
||||
2 0 1 1 0<br>
|
||||
3 0 1 0 1<br>
|
||||
4 1 0 0 1<br>
|
||||
</code>
|
||||
</li>
|
||||
<li>The sequence of controls signals for 2 control wires is as follows:<br>
|
||||
(columns C1 and C2 from above):<br><br>
|
||||
|
||||
<code>
|
||||
Step C0 C1<br>
|
||||
1 0 1<br>
|
||||
2 1 1<br>
|
||||
3 1 0<br>
|
||||
4 0 0<br>
|
||||
</code>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
If your stepper-motor does not move or does move but only in a single direction you will have to rearrage
|
||||
the pin-numbers to match the control sequence. That can be archived either by rearranging the physical
|
||||
connections, or by mapping the connection to the pin-definitions in FRM_STEPPERS define:<br>
|
||||
e.g. the widely used cheap 28byj-48 you can get for few EUR on eBay including a simple ULN2003 driver
|
||||
interface may be defined by<br>
|
||||
<code>define stepper FRM_STEPPER FOUR_WIRE 7 5 6 8 64 0</code><br>
|
||||
when being connected to the arduio with:<br><br>
|
||||
|
||||
<code>
|
||||
motor pin1 <-> arduino pin5<br>
|
||||
motor pin2 <-> arduino pin6<br>
|
||||
motor pin3 <-> arduino pin7<br>
|
||||
motor pin4 <-> arduino pin8<br>
|
||||
motor pin5 <-> ground
|
||||
</code>
|
||||
</li><br>
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_STEPPERset"></a>
|
||||
<b>Set</b><br>
|
||||
<ul>
|
||||
<code>set <name> reset</code>
|
||||
<li>resets the reading 'position' to 0 without moving the motor</li>
|
||||
<br>
|
||||
<code>set <name> position <position> [speed] [acceleration] [deceleration]</code>
|
||||
<li>moves the motor to the absolute position specified. positive or negative integer<br>
|
||||
speed (10 * revolutions per minute, optional), defaults to 30, higher numbers are faster) At 2048 steps per revolution (28byj-48) a speed of 30 results in 3 rev/min<br>
|
||||
acceleration and deceleration are optional.<br>
|
||||
</li>
|
||||
<br>
|
||||
<code>set <name> step <stepstomove> [speed] [accel] [decel]</code>
|
||||
<li>moves the motor the number of steps specified. positive or negative integer<br>
|
||||
speed, accelleration and deceleration are optional.<br>
|
||||
</li>
|
||||
</ul>
|
||||
<code>set <name> reset</code>
|
||||
<li>resets the reading 'position' to 0 without moving the motor</li><br>
|
||||
|
||||
<code>set <name> position <position> [speed] [acceleration] [deceleration]</code>
|
||||
<li>moves the motor to the absolute position specified. positive or negative integer<br>
|
||||
speed (10 * revolutions per minute, optional), defaults to 30, higher numbers are faster.
|
||||
At 2048 steps per revolution (28byj-48) a speed of 30 results in 3 rev/min<br>
|
||||
acceleration and deceleration are optional.<br>
|
||||
</li><br>
|
||||
|
||||
<code>set <name> step <stepstomove> [speed] [accel] [decel]</code>
|
||||
<li>moves the motor the number of steps specified. positive or negative integer<br>
|
||||
speed, accelleration and deceleration are optional.<br>
|
||||
</li>
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_STEPPERget"></a>
|
||||
<b>Get</b><br>
|
||||
<ul>
|
||||
N/A
|
||||
<code>get <position></code>
|
||||
<li>returns the current position value</li>
|
||||
</ul><br>
|
||||
|
||||
<a name="FRM_STEPPERattr"></a>
|
||||
<b>Attributes</b><br>
|
||||
<ul>
|
||||
<li>restoreOnStartup <on|off></li>
|
||||
<li>restoreOnReconnect <on|off></li>
|
||||
<li><a href="#IODev">IODev</a><br>
|
||||
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
|
||||
than one FRM-device defined.)
|
||||
</li>
|
||||
<li>>speed (same meaning as in 'set position')</li>
|
||||
<li>acceleration (same meaning as in 'set position')</li>
|
||||
<li>deceleration (same meaning as in 'set position')</li>
|
||||
<li><a href="#eventMap">eventMap</a><br></li>
|
||||
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
|
||||
</ul>
|
||||
<a name="restoreOnStartup"/>
|
||||
<li>restoreOnStartup <on|off></li>
|
||||
|
||||
<a name="restoreOnReconnect"/>
|
||||
<li>restoreOnReconnect <on|off></li>
|
||||
|
||||
<a name="IODev"/>
|
||||
<li><a href="#IODev">IODev</a><br>
|
||||
Specify which <a href="#FRM">FRM</a> to use. Only required if there is more than one FRM-device defined.
|
||||
</li>
|
||||
|
||||
<a name="speed"/>
|
||||
<li>>speed (same meaning as in 'set position')</li>
|
||||
|
||||
<a name="acceleration"/>
|
||||
<li>acceleration (same meaning as in 'set position')</li>
|
||||
|
||||
<a name="deceleration"/>
|
||||
<li>deceleration (same meaning as in 'set position')</li>
|
||||
|
||||
<li><a href="#attributes">global attributes</a></li>
|
||||
|
||||
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
|
||||
</ul>
|
||||
<br>
|
||||
</ul><br>
|
||||
|
||||
=end html
|
||||
|
||||
=begin html_DE
|
||||
|
||||
<a name="FRM_STEPPER"></a>
|
||||
<h3>FRM_STEPPER</h3>
|
||||
<ul>
|
||||
Die Modulbeschreibung von FRM_STEPPER gibt es nur auf <a href="commandref.html#FRM_STEPPER">Englisch</a>. <br>
|
||||
</ul><br>
|
||||
|
||||
=end html_DE
|
||||
|
||||
=cut
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user