######################################################################################## # $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; 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"; }; }; }; ##################################### # (min) number of arguments my %sets = ( "reset:noArg" => 0, "position" => 1, "step" => 1, ); my %gets = ( "position" => "", ); sub FRM_STEPPER_Initialize { my ($hash) = @_; $hash->{SetFn} = "FRM_STEPPER_Set"; $hash->{GetFn} = "FRM_STEPPER_Get"; $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_Define { my ($hash, $def) = @_; # verify define arguments my $usage = "usage: define FRM_STEPPER [DRIVER|TWO_WIRE|FOUR_WIRE] directionPin stepPin [motorPin3 motorPin4] stepsPerRev [id]"; my @a = split("[ \t][ \t]*", $def); my $args = [@a[2..scalar(@a)-1]]; return $usage unless defined $args; my $driver = shift @$args; 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; } $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; }; $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 $direction = $value < $position ? 1 : 0; my $steps = $direction ? $position - $value : $value - $position; my $speed = shift @a; $speed = AttrVal($name,"speed",30) unless (defined $speed); my $accel = shift @a; $accel = AttrVal($name,"acceleration",undef) unless (defined $accel); my $decel = shift @a; $decel = AttrVal($name,"deceleration",undef) unless (defined $decel); $hash->{DIRECTION} = $direction; $hash->{STEPS} = $steps; eval { 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; }; $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; $speed = AttrVal($name,"speed",100) unless (defined $speed); my $accel = shift @a; $accel = AttrVal($name,"acceleration",undef) unless (defined $accel); my $decel = shift @a; $decel = AttrVal($name,"deceleration",undef) unless (defined $decel); $hash->{DIRECTION} = $direction; $hash->{STEPS} = $steps; eval { 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 { 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 { 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 { my ($command,$name,$attribute,$value) = @_; my $hash = $main::defs{$name}; eval { if ($command eq "set") { ARGUMENT_HANDLER: { $attribute eq "IODev" and do { if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $value)) { FRM_Client_AssignIOPort($hash,$value); FRM_Init_Client($hash) if (defined ($hash->{IODev})); } last; }; } } }; if ($@) { my $ret = FRM_Catch($@); $hash->{STATE} = "$command $attribute error: " . $ret; return $hash->{STATE}; } } 1; =pod =head1 CHANGES 05.09.2020 jensb o check for IODev install error in Init and Set o prototypes removed o get position implemented o set argument verifier improved o module help updated o moved define argument verification and decoding from Init to Define 22.10.2020 jensb o annotaded module help of attributes for FHEMWEB =cut =pod =head1 FHEM COMMANDREF METADATA =over =item device =item summary Firmata: rotary encoder input =item summary_DE Firmata: Drehgeber Eingang =back =head1 INSTALLATION AND CONFIGURATION =begin html

FRM_STEPPER


=end html =begin html_DE

FRM_STEPPER


=end html_DE =cut