############################################################################ # fhem Modul für Impulszähler auf Basis von Arduino mit ArduCounter Sketch # # This file is part of fhem. # # Fhem 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. # # Fhem 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 fhem. If not, see . # ############################################################################## # Changelog: # # 2014-2-4 initial version # 2014-3-12 added documentation # 2015-02-08 renamed ACNT to ArduCounter # package main; use strict; use warnings; use Time::HiRes qw(gettimeofday); my %ArduCounter_sets = ( "raw" => "" ); my %ArduCounter_gets = ( "info" => "" ); # # FHEM module intitialisation # defines the functions to be called from FHEM ######################################################################### sub ArduCounter_Initialize($) { my ($hash) = @_; require "$attr{global}{modpath}/FHEM/DevIo.pm"; $hash->{ReadFn} = "ArduCounter_Read"; $hash->{ReadyFn} = "ArduCounter_Ready"; $hash->{DefFn} = "ArduCounter_Define"; $hash->{UndefFn} = "ArduCounter_Undef"; $hash->{GetFn} = "ArduCounter_Get"; $hash->{SetFn} = "ArduCounter_Set"; $hash->{AttrFn} = "ArduCounter_Attr"; $hash->{AttrList} = 'pin.* ' . "interval " . "factor " . "do_not_notify:1,0 " . $readingFnAttributes; } # # Define command ######################################################################### # sub ArduCounter_Define($$) { my ( $hash, $def ) = @_; my @a = split( "[ \t][ \t]*", $def ); return "wrong syntax: define ArduCounter devicename\@speed" if ( @a < 3 ); DevIo_CloseDev($hash); my $name = $a[0]; my $dev = $a[2]; $hash->{buffer} = ""; $hash->{DeviceName} = $dev; my $ret = DevIo_OpenDev( $hash, 0, 0); return $ret; } sub ArduCounter_InitDev($) { my ($hash) = @_; my $name = $hash->{NAME}; $hash->{STATE} = "Initialized"; # now talking to Arduino device is possible DevIo_SimpleWrite( $hash, "int 60 300\n", 0 ); # default 1 to 5 minutes # now that the Arduino device reported "setup done" # send attributes to arduino device. Just call ArduCounter_Attr again, # now with state "Initialized" while (my ($attr, $val) = each(%{$attr{$name}})) { if ($attr =~ "pin|del|interval") { Log3 $name, 3, "$name: InitDev calls Attr with $attr $val"; ArduCounter_Attr("set", $name, $attr, $val); } } } # # undefine command when device is deleted ######################################################################### sub ArduCounter_Undef($$) { my ( $hash, $arg ) = @_; DevIo_CloseDev($hash); return undef; } # Wrap write to IODEV in case device is not initialized yet ######################################################################### sub ArduCounter_Write($$) { my ( $hash, $line ) = @_; my $name = $hash->{NAME}; if ($line) { Log3 $name, 4, "$name: Write called with $line"; } else { Log3 $name, 5, "$name: Write called from timer, State = $hash->{STATE}"; delete $hash->{TimerSet}; } if ($hash->{STATE} eq "Initialized") { if ($hash->{WriteWaiting}) { DevIo_SimpleWrite( $hash, $hash->{WriteWaiting}, 0 ); Log3 $name, 4, "$name: Write: wrote waiting commands to device"; delete $hash->{WriteWaiting}; } DevIo_SimpleWrite( $hash, "$line\n", 0 ) if ($line); } else { # Device not initialized yet - add to WaitingBuffer if ($line) { if ($hash->{WriteWaiting}) { $hash->{WriteWaiting} .= "$line\n"; } else { $hash->{WriteWaiting} = "$line\n"; } } if (!$hash->{TimerSet}) { InternalTimer(gettimeofday()+1, "ArduCounter_Write", $hash, 0); } $hash->{TimerSet} = 1; } } # Attr command ######################################################################### sub ArduCounter_Attr(@) { my ($cmd,$name,$aName,$aVal) = @_; # $cmd can be "del" or "set" # $name is device name # aName and aVal are Attribute name and value my $hash = $defs{$name}; Log3 $name, 4, "$name: Attr called with @_"; if ($cmd eq "set") { if ($aName =~ 'pin.*') { if ($aName !~ 'pin([dD]?\d+)') { Log3 $name, 3, "$name: Invalid pin name in attr $name $aName $aVal"; return "Invalid pin name $aName"; } my $pin = $1; if ($aVal =~ '(rising|falling|change)( pullup)?') { my $opt = ""; if ($aVal =~ 'rising') {$opt = "r"} elsif ($aVal =~ 'falling') {$opt = "f"} elsif ($aVal =~ 'change') {$opt = "c"} if ($aVal =~ 'pull') {$opt .= " p"} ArduCounter_Write( $hash, "add $pin $opt") if ($hash->{STATE} eq "Initialized"); } else { Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal"; return "Invalid Value $aVal"; } } elsif ($aName eq "interval") { if ($aVal =~ '^(\d+) (\d+)$') { my $min = $1; my $max = $2; if ($min < 1 || $min > 3600 || $max < $min || $max > 3600) { Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal"; return "Invalid Value $aVal"; } ArduCounter_Write( $hash, "int $aVal") if ($hash->{STATE} eq "Initialized"); } else { Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal"; return "Invalid Value $aVal"; } } elsif ($aName eq "factor") { if ($aVal =~ '^(\d+)$') { } else { Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal"; return "Invalid Value $aVal"; } } } elsif ($cmd eq "del") { if ($aName =~ 'pin.*') { if ($aName !~ 'pin([dD]?\d+)') { Log3 $name, 3, "$name: Invalid pin name in attr $name $aName $aVal"; return "Invalid pin name $aName"; } my $pin = $1; ArduCounter_Write( $hash, "rem $pin") if ($hash->{STATE} eq "Initialized"); } } return undef; } # SET command ######################################################################### sub ArduCounter_Set($@) { my ( $hash, @a ) = @_; return "\"set ArduCounter\" needs at least one argument" if ( @a < 2 ); # @a is an array with DeviceName, SetName, Rest of Set Line my $name = shift @a; my $attr = shift @a; my $arg = join("", @a); if(!defined($ArduCounter_sets{$attr})) { my @cList = keys %ArduCounter_sets; return "Unknown argument $attr, choose one of " . join(" ", @cList); } if ($attr eq "raw") { DevIo_SimpleWrite( $hash, "$arg\n", 0 ); } return undef; } # GET command ######################################################################### sub ArduCounter_Get($@) { my ( $hash, @a ) = @_; return "\"set ArduCounter\" needs at least one argument" if ( @a < 2 ); # @a is an array with DeviceName, GetName my $name = shift @a; my $attr = shift @a; if(!defined($ArduCounter_gets{$attr})) { my @cList = keys %ArduCounter_gets; return "Unknown argument $attr, choose one of " . join(" ", @cList); } if ($attr eq "info") { DevIo_SimpleWrite( $hash, "show\n", 0 ); return "sent show command to get info - watch fhem log"; } return undef; } ######################################################################### # called from the global loop, when the select for hash->{FD} reports data sub ArduCounter_Read($) { my ($hash) = @_; my $name = $hash->{NAME}; my ($pin, $count, $diff, $power, $time, $factor); # read from serial device my $buf = DevIo_SimpleRead($hash); return "" if ( !defined($buf) ); $hash->{buffer} .= $buf; my $end = chop $buf; Log3 $name, 5, "$name: Current buffer content: " . $hash->{buffer}; # did we already get a full frame? return if ($end ne "\n"); readingsBeginUpdate($hash); my @lines = split /\n/, $hash->{buffer}; foreach my $line (@lines) { if ($line =~ 'R([\d]+) C([\d]+) D([\d]+) T([\d]+)') { $pin = $1; $count = $2; $diff = $3; $time = $4; if (defined ($attr{$name}{factor})) { $factor = $attr{$name}{factor}; } else { $factor = 1000; } Log3 $name, 4, "$name: Read match msg: Pin $pin count $count (diff $diff) in $time Millis"; readingsBulkUpdate($hash, "pin$pin", sprintf ("%.3f", $count) ); if ($time) { readingsBulkUpdate($hash, "power$pin", sprintf ("%.3f", $diff/$time/1000*3600*$factor) ); } } elsif ($line =~ '(ArduCounter V[\d\.]+.?) Setup done') { readingsBulkUpdate($hash, "version", $1); Log3 $name, 3, "$name: Arduino reported setup done - sending init cmds"; ArduCounter_InitDev($hash); } else { Log3 $name, 3, "$name: " . $line; } } readingsEndUpdate( $hash, 1 ); $hash->{buffer} = ""; return ""; } # # copied from other FHEM modules ######################################################################### sub ArduCounter_Ready($) { my ($hash) = @_; # try to reopen if state is disconnected return DevIo_OpenDev( $hash, 1, undef ) if ( $hash->{STATE} eq "disconnected" ); # This is relevant for windows/USB only my $po = $hash->{USBDev}; my ( $BlockingFlags, $InBytes, $OutBytes, $ErrorFlags ) = $po->status; return ( $InBytes > 0 ); } 1; =pod =begin html

ArduCounter

=end html =cut