mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-01-31 18:59:33 +00:00
dddd3ce266
git-svn-id: https://svn.fhem.de/fhem/trunk@10633 2b470e98-0d58-463d-a4d8-8e2adae1ed80
470 lines
16 KiB
Perl
Executable File
470 lines
16 KiB
Perl
Executable File
############################################################################
|
|
# $Id$
|
|
# 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 <http://www.gnu.org/licenses/>.
|
|
#
|
|
##############################################################################
|
|
# Changelog:
|
|
#
|
|
# 2014-2-4 initial version
|
|
# 2014-3-12 added documentation
|
|
# 2015-02-08 renamed ACNT to ArduCounter
|
|
# 2016-01-01 added attributes for reading names
|
|
#
|
|
|
|
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 " .
|
|
"readingNameCount[0-9]+ " .
|
|
"readingNamePower[0-9]+ " .
|
|
"do_not_notify:1,0 " . $readingFnAttributes;
|
|
}
|
|
|
|
#
|
|
# Define command
|
|
######################################################################### #
|
|
sub ArduCounter_Define($$)
|
|
{
|
|
my ( $hash, $def ) = @_;
|
|
my @a = split( "[ \t][ \t]*", $def );
|
|
|
|
return "wrong syntax: define <name> 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";
|
|
my $rcname = AttrVal($name, "readingNameCount$pin", "pin$pin");
|
|
my $rpname = AttrVal($name, "readingNamePower$pin", "power$pin");
|
|
readingsBulkUpdate($hash, $rcname, sprintf ("%.3f", $count) );
|
|
if ($time) {
|
|
readingsBulkUpdate($hash, $rpname, 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
|
|
|
|
<a name="ArduCounter"></a>
|
|
<h3>ArduCounter</h3>
|
|
|
|
<ul>
|
|
This module implements an Interface to an Arduino based counter for pulses on any input pin of an Arduino Uno, Nano or similar device like a Jeenode. The typical use case is an S0-Interface on an energy meter<br>
|
|
Counters are configured with attributes that define which Arduino pins should count pulses and in which intervals the Arduino board should report the current counts.<br>
|
|
The Arduino sketch that works with this module uses pin change interrupts so it can efficiently count pulses on all available input pins.
|
|
<br><br>
|
|
<b>Prerequisites</b>
|
|
<ul>
|
|
<br>
|
|
<li>
|
|
This module requires an Arduino uno, nano, Jeenode or similar device running the ArduCounter sketch provided with this module
|
|
</li>
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="ArduCounterdefine"></a>
|
|
<b>Define</b>
|
|
<ul>
|
|
<br>
|
|
<code>define <name> ArduCounter <device></code>
|
|
<br>
|
|
<device> specifies the serial port to communicate with the Arduino.<br>
|
|
|
|
The name of the serial-device depends on your distribution.
|
|
You can also specify a baudrate if the device name contains the @
|
|
character, e.g.: /dev/ttyUSB0@9600<br>
|
|
<br>
|
|
Example:<br>
|
|
<br>
|
|
<ul><code>define AC ArduCounter /dev/ttyUSB2@9600</code></ul>
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="ArduCounterconfiguration"></a>
|
|
<b>Configuration of ArduCounter counters</b><br><br>
|
|
<ul>
|
|
Specify the pins where S0 interfaces are connected to as <code>attr AC pinX rising pullup</code> <br>
|
|
The X in pinX can be an Arduino pin number with or without the letter D e.g. pin4, pin5, pinD4, pinD6 ...<br>
|
|
After the pin ypu can define if rising or falling edges of the signals should be counted. The optional keyword pullup
|
|
activates the pullup resistor for the given Arduino Pin.
|
|
<br><br>
|
|
Example:<br>
|
|
<pre>
|
|
define AC ArduCounter /dev/ttyUSB2@9600
|
|
attr AC factor 1000
|
|
attr AC interval 60 300
|
|
attr AC pinD4 rising pullup
|
|
attr AC pinD5 rising pullup
|
|
</pre>
|
|
this defines two counters connected to the pins D4 and D5, each with the pullup resistor activated.
|
|
Impulses will be counted when the signal changes from 0 to 1.
|
|
The ArduCounter sketch which must be loaded on the Arduino implements this using pin change interrupts,
|
|
so all avilable input pins can be used.
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="ArduCounterset"></a>
|
|
<b>Set-Commands</b><br>
|
|
<ul>
|
|
<li><b>raw</b></li>
|
|
send the value to the Arduino board so you can directly talk to the sketch using its commands.<br>
|
|
This is not needed for normal operation but might be useful sometimes for debugging<br>
|
|
</ul>
|
|
<br>
|
|
<a name="ArduCounterget"></a>
|
|
<b>Get-Commands</b><br>
|
|
<ul>
|
|
<li><b>info</b></li>
|
|
send the internal command <code>show</code> to the Arduino board to get current counts.<br>
|
|
This is not needed for normal operation but might be useful sometimes for debugging<br>
|
|
</ul>
|
|
<br>
|
|
<a name="ArduCounterattr"></a>
|
|
<b>Attributes</b><br><br>
|
|
<ul>
|
|
<li><a href="#do_not_notify">do_not_notify</a></li>
|
|
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
|
|
<br>
|
|
<li><b>pin.*</b></li>
|
|
Define a pin of the Arduino board as input. This attribute expects either
|
|
<code>rising</code>, <code>falling</code> or <code>change</code> as value, followed by
|
|
on optional <code>pullup</code>.<br>
|
|
<li><b>interval</b></li>
|
|
Define the reporting interval after which the Arduino board should hand over the count and the time from first to last impulse per pin.<br>
|
|
This Attribute expects two numbers as value. The first is the minimal interval, the second the maximal interval.
|
|
Nothing is reported during the minimal interval. The Arduino board just counts and reemembers the time between the first impulse and the last impulse for each pin.
|
|
After the minimal interval the Arduino board reports count and time for those pins where impulses were encountered.
|
|
If no impulses were encountered, the pin is not reported until the second interval is over.
|
|
The default intervals are 60 seconds as minimal time and 5 minutes as maximum interval.
|
|
<li><b>factor</b></li>
|
|
Define a multiplicator for calculating the power from the impulse count and the time between the first and the last impulse
|
|
|
|
<li><b>readingNameCount[0-9]+</b></li>
|
|
Change the name of the counter reading pinX to something more meaningful.
|
|
<li><b>readingNamePower[0-9]+</b></li>
|
|
Change the name of the power reading powerX to something more meaningful.
|
|
|
|
</ul>
|
|
<br>
|
|
<b>Readings / Events</b><br>
|
|
<ul>
|
|
The module creates the following readings and events for each defined pin:
|
|
<li><b>pin.*</b></li>
|
|
the current count at this pin
|
|
<li><b>power.*</b></li>
|
|
the current calculated power at this pin
|
|
</ul>
|
|
<br>
|
|
</ul>
|
|
|
|
=end html
|
|
=cut
|
|
|