From 31a723aa609b4f9afcee536f9456efc74899d911 Mon Sep 17 00:00:00 2001 From: ststrobel <> Date: Wed, 11 Feb 2015 18:13:09 +0000 Subject: [PATCH] =?UTF-8?q?98=5FArduCounter.pm:=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: https://svn.fhem.de/fhem/trunk@7939 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/contrib/98_ArduCounter.pm | 460 +++++++++++++++++++++++++++++++++ 1 file changed, 460 insertions(+) create mode 100755 fhem/contrib/98_ArduCounter.pm diff --git a/fhem/contrib/98_ArduCounter.pm b/fhem/contrib/98_ArduCounter.pm new file mode 100755 index 000000000..51507966c --- /dev/null +++ b/fhem/contrib/98_ArduCounter.pm @@ -0,0 +1,460 @@ +############################################################################ +# 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 +