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
ArduCounter
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
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.
The Arduino sketch that works with this module uses pin change interrupts so it can efficiently count pulses on all available input pins.
Prerequisites
-
This module requires an Arduino uno, nano, Jeenode or similar device running the ArduCounter sketch provided with this module
Define
define <name> ArduCounter <device>
<device> specifies the serial port to communicate with the Arduino.
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
Example:
define AC ArduCounter /dev/ttyUSB2@9600
Configuration of ArduCounter counters
Specify the pins where S0 interfaces are connected to as attr AC pinX rising pullup
The X in pinX can be an Arduino pin number with or without the letter D e.g. pin4, pin5, pinD4, pinD6 ...
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.
Example:
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
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.
Set-Commands
- raw
send the value to the Arduino board so you can directly talk to the sketch using its commands.
This is not needed for normal operation but might be useful sometimes for debugging
Get-Commands
- info
send the internal command show
to the Arduino board to get current counts.
This is not needed for normal operation but might be useful sometimes for debugging
Attributes
- do_not_notify
- readingFnAttributes
- pin.*
Define a pin of the Arduino board as input. This attribute expects either
rising
, falling
or change
as value, followed by
on optional pullup
.
- interval
Define the reporting interval after which the Arduino board should hand over the count and the time from first to last impulse per pin.
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.
- factor
Define a multiplicator for calculating the power from the impulse count and the time between the first and the last impulse
- readingNameCount[0-9]+
Change the name of the counter reading pinX to something more meaningful.
- readingNamePower[0-9]+
Change the name of the power reading powerX to something more meaningful.
Readings / Events
The module creates the following readings and events for each defined pin:
- pin.*
the current count at this pin
- power.*
the current calculated power at this pin
=end html
=cut