From 848efb5d425b4f1cbbfea0cc70c7895ae49a8b2f Mon Sep 17 00:00:00 2001
From: pizmus <>
Date: Mon, 25 Nov 2019 20:56:33 +0000
Subject: [PATCH] EseraOneWire: initial commit with logical modules
EseraAnalogInOut EseraDigitalInOut EseraMulti EseraTemp EseraCount
EseraIButton
git-svn-id: https://svn.fhem.de/fhem/trunk@20592 2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
fhem/CHANGED | 7 +
fhem/FHEM/66_EseraAnalogInOut.pm | 596 ++++++++
fhem/FHEM/66_EseraCount.pm | 460 ++++++
fhem/FHEM/66_EseraDigitalInOut.pm | 726 ++++++++++
fhem/FHEM/66_EseraIButton.pm | 294 ++++
fhem/FHEM/66_EseraMulti.pm | 351 +++++
fhem/FHEM/66_EseraOneWire.pm | 2221 +++++++++++++++++++++++++++++
fhem/FHEM/66_EseraTemp.pm | 260 ++++
fhem/MAINTAINER.txt | 7 +
9 files changed, 4922 insertions(+)
create mode 100644 fhem/FHEM/66_EseraAnalogInOut.pm
create mode 100644 fhem/FHEM/66_EseraCount.pm
create mode 100644 fhem/FHEM/66_EseraDigitalInOut.pm
create mode 100644 fhem/FHEM/66_EseraIButton.pm
create mode 100644 fhem/FHEM/66_EseraMulti.pm
create mode 100644 fhem/FHEM/66_EseraOneWire.pm
create mode 100644 fhem/FHEM/66_EseraTemp.pm
diff --git a/fhem/CHANGED b/fhem/CHANGED
index 797e9ced8..b438b9a63 100644
--- a/fhem/CHANGED
+++ b/fhem/CHANGED
@@ -1,6 +1,13 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
# Do not insert empty lines here, update check depends on it.
+ - new: 66_EseraAnalogInOut.pm: new modul
+ - new: 66_EseraDigitalInOut.pm: new modul
+ - new: 66_EseraMulti.pm: new modul
+ - new: 66_EseraTemp.pm: new modul
+ - new: 66_EseraCount.pm: new modul
+ - new: 66_EseraIButton.pm: new modul
+ - new: 66_EseraOneWire.pm: new modul
- feature: 70_SolarEdgeAPI: new readings from storageData API, overview API
- bugfix: 73_ElectricityCalculator.pm: floating number flutter corrected
- bugfix: 73_GasCalculator.pm: floating number flutter corrected
diff --git a/fhem/FHEM/66_EseraAnalogInOut.pm b/fhem/FHEM/66_EseraAnalogInOut.pm
new file mode 100644
index 000000000..312f3ca34
--- /dev/null
+++ b/fhem/FHEM/66_EseraAnalogInOut.pm
@@ -0,0 +1,596 @@
+################################################################################
+#
+# $Id$
+#
+# 66_EseraAnalogInOut.pm
+#
+# Copyright (C) 2018 pizmus
+#
+# This program 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 program. If not, see .
+#
+################################################################################
+#
+# This FHEM module controls analog input/output devices connected via
+# an Esera 1-wire controller and the 66_EseraOneWire module.
+#
+################################################################################
+
+package main;
+
+use strict;
+use warnings;
+use SetExtensions;
+
+my %SYS3Specs = (
+ factor => 0.01, # factor to get from raw reading to value in given unit
+ unit => "V",
+ lowLimit => 0.0,
+ highLimit => 10.0,
+ defaultValue => 0.0
+);
+
+my %DS2450Specs = (
+ factor => 0.01, # factor to get from raw reading to value in given unit
+ unit => "V",
+ lowLimit => 0.0,
+ highLimit => 0.0,
+ defaultValue => 0.0
+);
+
+my %Esera11202Specs = (
+ factor => 0.01, # factor to get from raw reading to value in given unit
+ unit => "V",
+ lowLimit => 0.0,
+ highLimit => 0.0,
+ defaultValue => 0.0
+);
+
+my %Esera11203Specs = (
+ factor => 0.01, # factor to get from raw reading to value in given unit
+ unit => "V",
+ lowLimit => 0.0,
+ highLimit => 0.0,
+ defaultValue => 0.0
+);
+
+my %Esera11208Specs = (
+ factor => 0.01, # factor to get from raw reading to value in given unit
+ unit => "V",
+ lowLimit => 0.0,
+ highLimit => 10.0,
+ defaultValue => 0.0
+);
+
+my %Esera11219Specs = (
+ factor => 0.01, # factor to get from raw reading to value in given unit
+ unit => "mA",
+ lowLimit => 0.0,
+ highLimit => 20.0,
+ defaultValue => 0.0
+);
+
+my %EseraAnalogInOutSpecs = (
+ "SYS3" => \%SYS3Specs,
+ "DS2450" => \%DS2450Specs,
+ "11202" => \%Esera11202Specs,
+ "11203" => \%Esera11203Specs,
+ "11208" => \%Esera11208Specs,
+ "11219" => \%Esera11219Specs
+ );
+
+sub
+EseraAnalogInOut_Initialize($)
+{
+ my ($hash) = @_;
+ $hash->{Match} = "SYS3|DS2450|11202|11203|11208|11219";
+ $hash->{DefFn} = "EseraAnalogInOut_Define";
+ $hash->{UndefFn} = "EseraAnalogInOut_Undef";
+ $hash->{ParseFn} = "EseraAnalogInOut_Parse";
+ $hash->{SetFn} = "EseraAnalogInOut_Set";
+ $hash->{GetFn} = "EseraAnalogInOut_Get";
+ $hash->{AttrFn} = "EseraAnalogInOut_Attr";
+ $hash->{AttrList} = "LowValue HighValue ".$readingFnAttributes;
+}
+
+sub
+EseraAnalogInOut_Define($$)
+{
+ my ($hash, $def) = @_;
+ my @a = split( "[ \t][ \t]*", $def);
+
+ my $usage = "Usage: define EseraAnalogInOut <1-wire-ID> ";
+
+ return $usage if(@a < 7);
+
+ my $devName = $a[0];
+ my $type = $a[1];
+ my $physicalDevice = $a[2];
+ my $oneWireId = $a[3];
+ my $deviceType = uc($a[4]);
+ my $lowLimit = $a[5];
+ my $highLimit = $a[6];
+
+ $hash->{STATE} = 'Initialized';
+ $hash->{NAME} = $devName;
+ $hash->{TYPE} = $type;
+ $hash->{ONEWIREID} = $oneWireId;
+ $hash->{ESERAID} = undef; # We will get this from the first reading.
+ $hash->{DEVICE_TYPE} = $deviceType;
+
+ $modules{EseraAnalogInOut}{defptr}{$oneWireId} = $hash;
+
+ AssignIoPort($hash, $physicalDevice);
+
+ if (defined($hash->{IODev}->{NAME}))
+ {
+ Log3 $devName, 4, "EseraAnalogInOut ($devName) - I/O device is " . $hash->{IODev}->{NAME};
+ }
+ else
+ {
+ Log3 $devName, 1, "EseraAnalogInOut ($devName) - no I/O device";
+ return $usage;
+ }
+
+ # check and use LowLimit and HighLimit
+ if (!defined($EseraAnalogInOutSpecs{$deviceType}))
+ {
+ Log3 $devName, 1, "EseraAnalogInOut ($devName) - unknown device type".$deviceType;
+ return $usage;
+ }
+
+ if (($lowLimit eq "-") || ($lowLimit < $EseraAnalogInOutSpecs{$deviceType}->{lowLimit}))
+ {
+ $lowLimit = $EseraAnalogInOutSpecs{$deviceType}->{lowLimit};
+ }
+ if (($highLimit eq "-") || ($highLimit > $EseraAnalogInOutSpecs{$deviceType}->{highLimit}))
+ {
+ $highLimit = $EseraAnalogInOutSpecs{$deviceType}->{highLimit};
+ }
+ $hash->{LOW_LIMIT} = $lowLimit;
+ $hash->{HIGH_LIMIT} = $highLimit;
+
+ # program the the device type into the controller via the physical module
+ if ($deviceType =~ m/^DS([0-9A-F]+)/)
+ {
+ # for the DS* devices types the "DS" has to be omitted
+ IOWrite($hash, "assign;$oneWireId;$1");
+ }
+ elsif (!($deviceType =~ m/^SYS3/))
+ {
+ IOWrite($hash, "assign;$oneWireId;$deviceType");
+ }
+
+ return undef;
+}
+
+sub
+EseraAnalogInOut_Undef($$)
+{
+ my ($hash, $arg) = @_;
+ my $oneWireId = $hash->{ONEWIREID};
+
+ RemoveInternalTimer($hash);
+ delete( $modules{EseraAnalogInOut}{defptr}{$oneWireId} );
+
+ return undef;
+}
+
+sub
+EseraAnalogInOut_Get($@)
+{
+ return undef;
+}
+
+sub
+EseraAnalogInOut_setSysDigout($$$)
+{
+ my ($hash, $owId, $value) = @_;
+ my $name = $hash->{NAME};
+
+ if (($value < $hash->{LOW_LIMIT}) || ($value > $hash->{HIGH_LIMIT}))
+ {
+ my $message = "error: value out of range ".$value." ".$hash->{LOW_LIMIT}." ".$hash->{HIGH_LIMIT};
+ Log3 $name, 1, "EseraAnalogInOut ($name) - ".$message;
+ return $message;
+ }
+
+ # look up the ESERA ID
+ my $eseraId = $hash->{ESERAID};
+ if (!defined $eseraId)
+ {
+ my $message = "error: ESERA ID not known";
+ Log3 $name, 1, "EseraAnalogInOut ($name) - ".$message;
+ return $message;
+ }
+
+ # set value
+ my $command = "set,sys,outa,".int($value / $EseraAnalogInOutSpecs{SYS3}->{factor});
+ IOWrite($hash, "set;$owId;$command");
+
+ return undef;
+}
+
+sub
+EseraAnalogInOut_set11208Digout($$$)
+{
+ my ($hash, $owId, $value) = @_;
+ my $name = $hash->{NAME};
+
+ if (($value < $hash->{LOW_LIMIT}) || ($value > $hash->{HIGH_LIMIT}))
+ {
+ my $message = "error: value out of range ".$value." ".$hash->{LOW_LIMIT}." ".$hash->{HIGH_LIMIT};
+ Log3 $name, 1, "EseraAnalogInOut ($name) - ".$message;
+ return $message;
+ }
+
+ # look up the ESERA ID
+ my $eseraId = $hash->{ESERAID};
+ if (!defined $eseraId)
+ {
+ my $message = "error: ESERA ID not known";
+ Log3 $name, 1, "EseraAnalogInOut ($name) - ".$message;
+ return $message;
+ }
+
+ # set value
+ # SET,OWD,OUTA,OWD-Nummer,Ausgangsspannung
+ my $command = "set,owd,outa,".$eseraId.",".int($value / $EseraAnalogInOutSpecs{$hash->{DEVICE_TYPE}}->{factor});
+ IOWrite($hash, "set;$owId;$command");
+
+ return undef;
+}
+
+sub
+EseraAnalogInOut_set11219Digout($$$)
+{
+ my ($hash, $owId, $value) = @_;
+ my $name = $hash->{NAME};
+
+ if (($value < $hash->{LOW_LIMIT}) || ($value > $hash->{HIGH_LIMIT}))
+ {
+ my $message = "error: value out of range ".$value." ".$hash->{LOW_LIMIT}." ".$hash->{HIGH_LIMIT};
+ Log3 $name, 1, "EseraAnalogInOut ($name) - ".$message;
+ return $message;
+ }
+
+ # look up the ESERA ID
+ my $eseraId = $hash->{ESERAID};
+ if (!defined $eseraId)
+ {
+ my $message = "error: ESERA ID not known";
+ Log3 $name, 1, "EseraAnalogInOut ($name) - ".$message;
+ return $message;
+ }
+
+ # set value
+ # SET,OWD,OUTAMA,OWD-Nummer,Ausgangsstrom
+ my $command = "set,owd,outama,".$eseraId.",".int($value / $EseraAnalogInOutSpecs{$hash->{DEVICE_TYPE}}->{factor});
+ IOWrite($hash, "set;$owId;$command");
+
+ return undef;
+}
+
+sub
+EseraAnalogInOut_setOutput($$$)
+{
+ my ($hash, $oneWireId, $value) = @_;
+ my $name = $hash->{NAME};
+
+ if (!defined $hash->{DEVICE_TYPE})
+ {
+ my $message = "error: device type not known";
+ Log3 $name, 1, "EseraAnalogInOut ($name) - ".$message;
+ return $message;
+ }
+
+ if ($hash->{DEVICE_TYPE} eq "SYS3")
+ {
+ Log3 $name, 5, "EseraAnalogInOut ($name) - EseraAnalogInOut_setOutput SYS3 value: $value";
+ EseraAnalogInOut_setSysDigout($hash, $oneWireId, $value);
+ }
+ elsif ($hash->{DEVICE_TYPE} eq "11208")
+ {
+ Log3 $name, 5, "EseraAnalogInOut ($name) - EseraAnalogInOut_setOutput value: $value";
+ EseraAnalogInOut_set11208Digout($hash, $oneWireId, $value);
+ }
+ elsif ($hash->{DEVICE_TYPE} eq "11219")
+ {
+ Log3 $name, 5, "EseraAnalogInOut ($name) - EseraAnalogInOut_setOutput value: $value";
+ EseraAnalogInOut_set11219Digout($hash, $oneWireId, $value);
+ }
+ else
+ {
+ my $message = "error: device type not supported as analog output: ".$hash->{DEVICE_TYPE};
+ Log3 $name, 1, "EseraAnalogInOut ($name) - ".$message;
+ return $message;
+ }
+
+ return undef;
+}
+
+sub
+EseraAnalogInOut_Set($$)
+{
+ my ( $hash, @parameters ) = @_;
+ my $name = $parameters[0];
+ my $what = lc($parameters[1]);
+
+ my $oneWireId = $hash->{ONEWIREID};
+ my $iodev = $hash->{IODev}->{NAME};
+
+ my $commands = ("on off out");
+
+ if ($what eq "out")
+ {
+ if ((scalar(@parameters) != 3))
+ {
+ my $message = "error: unexpected number of parameters (".scalar(@parameters).")";
+ Log3 $name, 1, "EseraAnalogInOut ($name) - ".$message;
+ return $message;
+ }
+ my $value = $parameters[2];
+ EseraAnalogInOut_setOutput($hash, $oneWireId, $value);
+ $hash->{LAST_OUT} = undef;
+ }
+ elsif ($what eq "on")
+ {
+ if ((scalar(@parameters) != 2))
+ {
+ my $message = "error: unexpected number of parameters (".scalar(@parameters).")";
+ Log3 $name, 1, "EseraAnalogInOut ($name) - ".$message;
+ return $message;
+ }
+ my $value = AttrVal($name, "HighValue", $EseraAnalogInOutSpecs{$hash->{DEVICE_TYPE}}->{defaultValue});
+ EseraAnalogInOut_setOutput($hash, $oneWireId, $value);
+ $hash->{LAST_OUT} = 1;
+ }
+ elsif ($what eq "off")
+ {
+ if ((scalar(@parameters) != 2))
+ {
+ my $message = "error: unexpected number of parameters (".scalar(@parameters).")";
+ Log3 $name, 1, "EseraAnalogInOut ($name) - ".$message;
+ return $message;
+ }
+ my $value = AttrVal($name, "LowValue", $EseraAnalogInOutSpecs{$hash->{DEVICE_TYPE}}->{defaultValue});
+ EseraAnalogInOut_setOutput($hash, $oneWireId, $value);
+ $hash->{LAST_OUT} = 0;
+ }
+ elsif ($what eq "?")
+ {
+ my $message = "unknown argument $what, choose one of $commands";
+ return $message;
+ }
+ else
+ {
+ shift @parameters;
+ shift @parameters;
+ return SetExtensions($hash, $commands, $name, $what, @parameters);
+ }
+ return undef;
+}
+
+sub
+EseraAnalogInOut_ParseForOneDevice($$$$$$)
+{
+ my ($rhash, $deviceType, $oneWireId, $eseraId, $readingId, $value) = @_;
+ my $rname = $rhash->{NAME};
+ Log3 $rname, 4, "EseraAnalogInOut ($rname) - ParseForOneDevice: ".$rname;
+
+ # capture the Esera ID for later use
+ $rhash->{ESERAID} = $eseraId;
+
+ # consistency check of device type
+ if (!($rhash->{DEVICE_TYPE} eq uc($deviceType)))
+ {
+ Log3 $rname, 1, "EseraAnalogInOut ($rname) - unexpected device type ".$deviceType;
+
+ # program the the device type into the controller via the physical module
+ if ($rhash->{DEVICE_TYPE} =~ m/^DS([0-9A-F]+)/)
+ {
+ # for the DS* devices types the "DS" has to be omitted
+ IOWrite($rhash, "assign;$oneWireId;$1");
+ }
+ elsif (!($rhash->{DEVICE_TYPE} =~ m/^SYS3/))
+ {
+ IOWrite($rhash, "assign;$oneWireId;".$rhash->{DEVICE_TYPE});
+ }
+ }
+
+ if ($readingId eq "ERROR")
+ {
+ Log3 $rname, 1, "EseraAnalogInOut ($rname) - error message from physical device: ".$value;
+ }
+ elsif ($readingId eq "STATISTIC")
+ {
+ Log3 $rname, 1, "EseraAnalogInOut ($rname) - statistics message not supported yet: ".$value;
+ }
+ else
+ {
+ my $nameOfReading = "";
+ if (($deviceType eq "SYS3") || ($deviceType eq "11208") || ($deviceType eq "11219"))
+ {
+ if ($readingId == 0)
+ {
+ $nameOfReading .= "out";
+ my $readingValue = $value * $EseraAnalogInOutSpecs{$deviceType}->{factor};
+ my $reading = $readingValue." ".$EseraAnalogInOutSpecs{$deviceType}->{unit};
+ readingsSingleUpdate($rhash, $nameOfReading, $reading, 1);
+ }
+ }
+ elsif (($deviceType eq "DS2450") || ($deviceType eq "11202") || ($deviceType eq "11203"))
+ {
+ if (($readingId >=1) && ($readingId <=4))
+ {
+ $nameOfReading .= "in".$readingId;
+ my $readingValue = $value * $EseraAnalogInOutSpecs{$deviceType}->{factor};
+ my $reading = $readingValue." ".$EseraAnalogInOutSpecs{$deviceType}->{unit};
+ readingsSingleUpdate($rhash, $nameOfReading, $reading, 1);
+ }
+ }
+ }
+ return $rname;
+}
+
+sub
+EseraAnalogInOut_Parse($$)
+{
+ my ($ioHash, $msg) = @_;
+ my $ioName = $ioHash->{NAME};
+ my $buffer = $msg;
+
+ # expected message format: $deviceType."_".$oneWireId."_".$eseraId."_".$readingId."_".$value
+ my @fields = split(/_/, $buffer);
+ if (scalar(@fields) != 5)
+ {
+ return undef;
+ }
+ my $deviceType = uc($fields[0]);
+ my $oneWireId = $fields[1];
+ my $eseraId = $fields[2];
+ my $readingId = $fields[3];
+ my $value = $fields[4];
+
+ # search for logical device
+ my $rhash = undef;
+ my @list;
+ foreach my $d (keys %defs)
+ {
+ my $h = $defs{$d};
+ my $type = $h->{TYPE};
+
+ if($type eq "EseraAnalogInOut")
+ {
+ if (defined($h->{IODev}->{NAME}))
+ {
+ my $ioDev = $h->{IODev}->{NAME};
+ my $def = $h->{DEF};
+
+ # $def has the whole definition, extract the oneWireId (which is expected as 2nd parameter)
+ my @parts = split(/ /, $def);
+ my $oneWireIdFromDef = $parts[1];
+
+ if (($ioDev eq $ioName) && ($oneWireIdFromDef eq $oneWireId))
+ {
+ $rhash = $h;
+ my $rname = EseraAnalogInOut_ParseForOneDevice($rhash, $deviceType, $oneWireId, $eseraId, $readingId, $value);
+ push(@list, $rname);
+ }
+ }
+ }
+ }
+
+ if ((scalar @list) > 0)
+ {
+ return @list;
+ }
+ elsif (exists($EseraAnalogInOutSpecs{$deviceType}))
+ {
+ return "UNDEFINED EseraAnalogInOut_".$ioName."_".$oneWireId." EseraAnalogInOut ".$ioName." ".$oneWireId." ".$deviceType." - -";
+ }
+
+ return undef;
+}
+
+sub
+EseraAnalogInOut_Attr(@)
+{
+ return undef;
+}
+
+1;
+
+=pod
+=item summary Represents a 1-wire analog input/output.
+=item summary_DE Repraesentiert einen 1-wire analogen Eingang/Ausgang.
+=begin html
+
+
+EseraAnalogInOut
+
+
+ This module implements a 1-wire analog input/output. It uses 66_EseraOneWire
+ as I/O device.
+
+
+
+ Define
+
+ define <name> EseraAnalogInOut <ioDevice> <oneWireId> <deviceType> <lowLimit> <highLimit>
+ <oneWireId> specifies the 1-wire ID of the analog input/output chip.
+ Use the "get devices" query of EseraOneWire to get a list of 1-wire IDs,
+ or simply rely on autocreate.
+ Supported values for deviceType:
+
+ - SYS3 (analog output build into the Esera Controller 2, Note: This does not show up in the "get devices" response.)
+ - DS2450 (4x analog input)
+ - 11202 (4x analog input)
+ - 11203 (4x analog input)
+ - 11208 (analog output, voltage)
+ - 11219 (analog output, current)
+
+ This module knows the high and low limits of the supported devices. You might
+ want to further reduce the output range, e.g. to protect hardware connected to the
+ output from user errors. You can use the parameters <lowLimit> and <highLimit> to do
+ so. You can also give "-" for <lowLimit> and <highLimit>. In this case the module uses
+ the maximum possible output range.
+
+
+
+
+ Set
+
+ This applies to analog outputs only.
+ -
+
set <name> out <value>
+ Controls the analog output. <value> specifies the new value.
+
+ -
+
set <name> on
+ Switch output to "high" value. The on value has to be specified as attribute HighValue.
+
+ -
+
set <name> off
+ Switch output to "low" value. The on value has to be specified as attribute LowValue.
+
+
+
+
+
+ Get
+
+
+
+
+ Attributes
+
+ - LowValue (see above)
+ - HighValue (see above)
+
+
+
+
+ Readings
+
+ - in – analog input value
+ - out – analog output value
+
+
+
+
+
+=end html
+=cut
diff --git a/fhem/FHEM/66_EseraCount.pm b/fhem/FHEM/66_EseraCount.pm
new file mode 100644
index 000000000..fcaf39420
--- /dev/null
+++ b/fhem/FHEM/66_EseraCount.pm
@@ -0,0 +1,460 @@
+################################################################################
+#
+# $Id$
+#
+# 66_EseraCount.pm
+#
+# Copyright (C) 2019 pizmus
+#
+# This program 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 program. If not, see .
+#
+################################################################################
+#
+# This FHEM module supports DS2423 counters.
+#
+################################################################################
+
+package main;
+
+use strict;
+use warnings;
+use SetExtensions;
+
+sub
+EseraCount_Initialize($)
+{
+ my ($hash) = @_;
+ $hash->{Match} = "DS2423";
+ $hash->{DefFn} = "EseraCount_Define";
+ $hash->{UndefFn} = "EseraCount_Undef";
+ $hash->{ParseFn} = "EseraCount_Parse";
+ $hash->{SetFn} = "EseraCount_Set";
+ $hash->{GetFn} = "EseraCount_Get";
+ $hash->{AttrFn} = "EseraCount_Attr";
+ $hash->{AttrList} = "ticksPerUnit1 ticksPerUnit2 movingAverageFactor1 movingAverageFactor2 movingAverageCount1 movingAverageCount2 $readingFnAttributes";
+}
+
+sub
+EseraCount_Define($$)
+{
+ my ($hash, $def) = @_;
+ my @a = split( "[ \t][ \t]*", $def);
+
+ return "Usage: define EseraCount <1-wire-ID> " if(@a < 5);
+
+ my $devName = $a[0];
+ my $type = $a[1];
+ my $physicalDevice = $a[2];
+ my $oneWireId = $a[3];
+ my $deviceType = uc($a[4]);
+
+ $hash->{STATE} = 'Initialized';
+ $hash->{NAME} = $devName;
+ $hash->{TYPE} = $type;
+ $hash->{ONEWIREID} = $oneWireId;
+ $hash->{ESERAID} = undef; # We will get this from the first reading.
+ $hash->{DEVICE_TYPE} = $deviceType;
+ $hash->{DATE_OF_LAST_SAMPLE} = undef;
+ $hash->{START_VALUE_OF_DAY_1} = 0;
+ $hash->{START_VALUE_OF_DAY_2} = 0;
+ $hash->{LAST_VALUE_1} = 0;
+ $hash->{LAST_VALUE_2} = 0;
+
+ $modules{EseraCount}{defptr}{$oneWireId} = $hash;
+
+ AssignIoPort($hash, $physicalDevice);
+
+ if (defined($hash->{IODev}->{NAME}))
+ {
+ Log3 $devName, 4, "$devName: I/O device is " . $hash->{IODev}->{NAME};
+ }
+ else
+ {
+ Log3 $devName, 1, "$devName: no I/O device";
+ }
+
+ return undef;
+}
+
+sub
+EseraCount_Undef($$)
+{
+ my ($hash, $arg) = @_;
+ my $oneWireId = $hash->{ONEWIREID};
+
+ RemoveInternalTimer($hash);
+ delete( $modules{EseraCount}{defptr}{$oneWireId} );
+
+ return undef;
+}
+
+sub
+EseraCount_Get($@)
+{
+ return undef;
+}
+
+sub
+EseraCount_Set($$)
+{
+ return undef;
+}
+
+sub
+EseraCount_IsNewDay($)
+{
+ my ($hash) = @_;
+
+ my $timestamp = FmtDateTime(gettimeofday());
+ # example: 2016-02-16 19:34:24
+
+ if ($timestamp =~ m/^([0-9\-]+)\s/)
+ {
+ my $dateString = $1;
+
+ if (defined $hash->{DATE_OF_LAST_SAMPLE})
+ {
+ if (!($hash->{DATE_OF_LAST_SAMPLE} eq $dateString))
+ {
+ $hash->{DATE_OF_LAST_SAMPLE} = $dateString;
+ return 1;
+ }
+ }
+ else
+ {
+ $hash->{DATE_OF_LAST_SAMPLE} = $dateString;
+ }
+ }
+
+ return undef;
+}
+
+sub
+EseraCount_MovingAverage($$$$)
+{
+ my ($hash, $newValue, $averageCount, $channel) = @_;
+ my $name = $hash->{NAME};
+
+ Log3 $name, 5, "EseraCount ($name): averageCount $averageCount newValue $newValue";
+
+ # get array with the last samples
+ my @lastSamples;
+ my $ref;
+ if ($channel == 1)
+ {
+ $ref = $hash->{LAST_VALUES_1};
+ }
+ else
+ {
+ $ref = $hash->{LAST_VALUES_2};
+ }
+ if (defined $ref)
+ {
+ @lastSamples = @$ref;
+ }
+ else
+ {
+ @lastSamples = ();
+ }
+
+ # add new sample to front of the list
+ unshift(@lastSamples, $newValue);
+
+ # remove oldest sample if needed
+ while ((scalar @lastSamples) > $averageCount)
+ {
+ pop @lastSamples;
+ Log3 $name, 5, "EseraCount ($name): pop once";
+ }
+
+ # store new array in $hash
+ if ($channel == 1)
+ {
+ $hash->{LAST_VALUES_1} = \@lastSamples;
+ }
+ else
+ {
+ $hash->{LAST_VALUES_2} = \@lastSamples;
+ }
+
+ # calculate the average across the array
+ my $count = 0;
+ my $sum = 0;
+ foreach (@lastSamples)
+ {
+ $count += 1;
+ $sum += $_;
+ Log3 $name, 5, "EseraCount ($name): count $count sum $sum value $_";
+ }
+
+ return $sum / $count;
+}
+
+sub
+EseraCount_Parse($$)
+{
+ my ($ioHash, $msg) = @_;
+ my $ioName = $ioHash->{NAME};
+ my $buffer = $msg;
+
+ # expected message format: $deviceType."_".$oneWireId."_".$eseraId."_".$readingId."_".$value
+ my @fields = split(/_/, $buffer);
+ if (scalar(@fields) != 5)
+ {
+ return undef;
+ }
+ my $deviceType = uc($fields[0]);
+ my $oneWireId = $fields[1];
+ my $eseraId = $fields[2];
+ my $readingId = $fields[3];
+ my $value = $fields[4];
+
+ # search for logical device
+ my $rhash = undef;
+ foreach my $d (keys %defs) {
+ my $h = $defs{$d};
+ my $type = $h->{TYPE};
+
+ if($type eq "EseraCount")
+ {
+ if (defined($h->{IODev}->{NAME}))
+ {
+ my $ioDev = $h->{IODev}->{NAME};
+ my $def = $h->{DEF};
+
+ # $def has the whole definition, extract the oneWireId (which is expected as 2nd parameter)
+ my @parts = split(/ /, $def);
+ my $oneWireIdFromDef = $parts[1];
+
+ if (($ioDev eq $ioName) && ($oneWireIdFromDef eq $oneWireId))
+ {
+ $rhash = $h;
+ last;
+ }
+ }
+ }
+ }
+
+ if($rhash) {
+ my $rname = $rhash->{NAME};
+ Log3 $rname, 4, "EseraCount ($rname) - parse - device found: ".$rname;
+
+ # capture the Esera ID for later use
+ $rhash->{ESERAID} = $eseraId;
+
+ # consistency check of device type
+ if (!($rhash->{DEVICE_TYPE} eq uc($deviceType)))
+ {
+ Log3 $rname, 1, "EseraCount ($rname) - unexpected device type ".$deviceType;
+ }
+
+ if ($readingId eq "ERROR")
+ {
+ Log3 $rname, 1, "EseraCount ($rname) - error message from physical device: ".$value;
+ }
+ elsif ($readingId eq "STATISTIC")
+ {
+ Log3 $rname, 1, "EseraCount ($rname) - statistics message not supported yet: ".$value;
+ }
+ else
+ {
+ if ($deviceType eq "DS2423")
+ {
+ if (EseraCount_IsNewDay($rhash))
+ {
+ $rhash->{START_VALUE_OF_DAY_1} = $rhash->{LAST_VALUE_1};
+ $rhash->{START_VALUE_OF_DAY_2} = $rhash->{LAST_VALUE_2};
+ }
+
+ if ($readingId == 1)
+ {
+ my $ticksPerUnit = AttrVal($rname, "ticksPerUnit1", 1.0);
+ readingsSingleUpdate($rhash, "count1", ($value / $ticksPerUnit), 1);
+ readingsSingleUpdate($rhash, "count1Today", ($value - $rhash->{START_VALUE_OF_DAY_1}) / $ticksPerUnit, 1);
+ if (defined $rhash->{LAST_VALUE_1})
+ {
+ my $movingAverageFactor = AttrVal($rname, "movingAverageFactor1", 1.0);
+ my $averageCount = AttrVal($rname, "movingAverageCount1", 1);
+ my $movingAverage = ($value - $rhash->{LAST_VALUE_1});
+ my $processedMovingAverage = EseraCount_MovingAverage($rhash, $movingAverage * $movingAverageFactor, $averageCount, 1);
+ readingsSingleUpdate($rhash, "count1MovingAverage", $processedMovingAverage, 1);
+ }
+ $rhash->{LAST_VALUE_1} = $value;
+ }
+ elsif ($readingId == 2)
+ {
+ my $ticksPerUnit = AttrVal($rname, "ticksPerUnit2", 1.0);
+ readingsSingleUpdate($rhash, "count2", ($value / $ticksPerUnit), 1);
+ readingsSingleUpdate($rhash, "count2Today", ($value - $rhash->{START_VALUE_OF_DAY_2}) / $ticksPerUnit, 1);
+ if (defined $rhash->{LAST_VALUE_2})
+ {
+ my $movingAverageFactor = AttrVal($rname, "movingAverageFactor2", 1.0);
+ my $averageCount = AttrVal($rname, "movingAverageCount2", 1);
+ my $movingAverage = ($value - $rhash->{LAST_VALUE_2});
+ my $processedMovingAverage = EseraCount_MovingAverage($rhash, $movingAverage * $movingAverageFactor, $averageCount, 2);
+ readingsSingleUpdate($rhash, "count2MovingAverage", $processedMovingAverage, 1);
+ }
+ $rhash->{LAST_VALUE_2} = $value;
+ }
+ }
+ }
+
+ my @list;
+ push(@list, $rname);
+ return @list;
+ }
+ elsif ($deviceType eq "DS2423")
+ {
+ return "UNDEFINED EseraCount_".$ioName."_".$oneWireId." EseraCount ".$ioName." ".$oneWireId." ".$deviceType;
+ }
+
+ return undef;
+}
+
+sub
+EseraCount_Attr($$$$)
+{
+ my ($cmd, $name, $attrName, $attrValue) = @_;
+ # $cmd - "del" or "set"
+ # $name - device name
+ # $attrName/$attrValue
+
+ if ($cmd eq "set") {
+ if (($attrName eq "ticksPerUnit1") || ($attrName eq "ticksPerUnit2"))
+ {
+ if ($attrValue <= 0)
+ {
+ my $message = "illegal value for ticksPerUnit";
+ Log3 $name, 3, "EseraCount ($name) - ".$message;
+ return $message;
+ }
+ }
+ if (($attrName eq "movingAverageFactor1") || ($attrName eq "movingAverageFactor2"))
+ {
+ if ($attrValue <= 0)
+ {
+ my $message = "illegal value for movingAverageFactor";
+ Log3 $name, 3, "EseraCount ($name) - ".$message;
+ return $message;
+ }
+ }
+ if (($attrName eq "movingAverageCount1") || ($attrName eq "movingAverageCount2"))
+ {
+ if ($attrValue < 1)
+ {
+ my $message = "illegal value for movingAverageCount";
+ Log3 $name, 3, "EseraCount ($name) - ".$message;
+ return $message;
+ }
+ }
+ }
+
+ return undef;
+}
+
+1;
+
+=pod
+=item summary Represents a DS2423 1-wire dual counter.
+=item summary_DE Repraesentiert einen DS2423 1-wire 2-fach Zaehler.
+=begin html
+
+
+EseraCount
+
+
+ This module supports DS2423 1-wire dual counters.
+ It uses 66_EseraOneWire as I/O device.
+
+
+
+ Define
+
+ define <name> EseraCount <ioDevice> <oneWireId> <deviceType>
+ <oneWireId> specifies the 1-wire ID of the sensor. Use the "get devices"
+ query of EseraOneWire to get a list of 1-wire IDs, or simply rely on autocreate.
+ The only supported <deviceType> is DS2423.
+
+
+
+ Set
+
+
+
+
+ Get
+
+
+
+
+ Attributes
+
+ -
+
ticksPerUnit1
+ ticksPerUnit2
+ These attribute are applied to readings count1
/ count2
and
+ count1Today
/ count2Today
.
+ The default value is 1. The attribute is used to convert the raw
+ tick count to meaningful value with a unit.
+
+ -
+
movingAverageCount1
+ movingAverageCount2
+ see description of reading count1MovingAverage
and count2MovingAverage
+ default: 1
+
+ -
+
movingAverageFactor1
+ movingAverageFactor2
+ see description of reading count1MovingAverage
and count2MovingAverage
+ default: 1
+
+
+
+
+
+ Readings
+
+ -
+
count1
+ count2
+ The counter values for channel 1 and 2. These are the counter values with
+ attributes ticksPerUnit1
and ticksPerUnit2
applied.
+
+ -
+
count1Today
+ count2Today
+ Similar to count1
and count2
but with a reset at midnight.
+
+ -
+
count1MovingAverage
+ count2MovingAverage
+ Moving average of the last movingAverageCount1
and movingAverageCount2
samples,
+ multiplied with movingAverageFactor1
or movingAverageFactor2
. This reading and
+ the related attributes are used to derive a power value value from the S0 count of an
+ energy meter. Samples must have a fixed and known period. This is the case with the Esera 1-wire
+ controller. When selecting a value for movingAverageFactor1
and movingAverageFactor2
the sample
+ period has to be considered.
+
+
+
+
+
+
+=end html
+=cut
diff --git a/fhem/FHEM/66_EseraDigitalInOut.pm b/fhem/FHEM/66_EseraDigitalInOut.pm
new file mode 100644
index 000000000..bdbd6901b
--- /dev/null
+++ b/fhem/FHEM/66_EseraDigitalInOut.pm
@@ -0,0 +1,726 @@
+################################################################################
+#
+# $Id$
+#
+# 66_EseraDigitalInOut.pm
+#
+# Copyright (C) 2018 pizmus
+#
+# This program 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 program. If not, see .
+#
+################################################################################
+#
+# This FHEM module controls a digital input and/or output device connected via
+# an Esera "1-wire Controller 1" with LAN interface and the 66_EseraOneWire
+# module.
+#
+################################################################################
+
+package main;
+
+use strict;
+use warnings;
+use SetExtensions;
+
+my %deviceSpecs = ("DS2408" => 8, "11220" => 8, "11228" => 8, "11229" => 8, "11216" => 8, "SYS1" => 4, "SYS2" => 5);
+
+sub
+EseraDigitalInOut_Initialize($)
+{
+ my ($hash) = @_;
+ $hash->{Match} = "DS2408";
+ $hash->{DefFn} = "EseraDigitalInOut_Define";
+ $hash->{UndefFn} = "EseraDigitalInOut_Undef";
+ $hash->{ParseFn} = "EseraDigitalInOut_Parse";
+ $hash->{SetFn} = "EseraDigitalInOut_Set";
+ $hash->{GetFn} = "EseraDigitalInOut_Get";
+ $hash->{AttrFn} = "EseraDigitalInOut_Attr";
+ $hash->{AttrList} = "$readingFnAttributes";
+}
+
+sub
+EseraDigitalInOut_calculateBits($$$$)
+{
+ my ($hash, $deviceType, $rawBitPos, $rawBitCount) = @_;
+ my $name = $hash->{NAME};
+
+ my $maxBitCount = $deviceSpecs{$deviceType};
+
+ if (!defined $maxBitCount)
+ {
+ Log3 $name, 1, "EseraDigitalInOut ($name) - error looking up maximum bit width";
+ return undef;
+ }
+
+ my $bitPos = 0;
+
+ if (!($rawBitPos eq "-"))
+ {
+ if (($rawBitPos >= 0) && ($rawBitPos < $maxBitCount))
+ {
+ $bitPos = $rawBitPos;
+ }
+ else
+ {
+ Log3 $name, 1, "EseraDigitalInOut ($name) - specified bit field position is out of range";
+ }
+ }
+ $hash->{BITPOS} = $bitPos;
+
+ my $bitCount = $maxBitCount - $bitPos;
+ if (!($rawBitCount eq "-"))
+ {
+ if ($rawBitCount > $bitCount)
+ {
+ Log3 $name, 1, "EseraDigitalInOut ($name) - specified bit field size is out of range";
+ }
+ else
+ {
+ $bitCount = $rawBitCount;
+ }
+ }
+ $hash->{BITCOUNT} = $bitCount;
+
+ return 1;
+}
+
+sub
+EseraDigitalInOut_Define($$)
+{
+ my ($hash, $def) = @_;
+ my @a = split( "[ \t][ \t]*", $def);
+
+ return "Usage: define EseraDigitalInOut <1-wire-ID> (|-) (|-)" if(@a < 7);
+
+ my $devName = $a[0];
+ my $type = $a[1];
+ my $physicalDevice = $a[2];
+ my $oneWireId = $a[3];
+ my $deviceType = uc($a[4]);
+ my $bitPos = $a[5];
+ my $bitCount = $a[6];
+
+ $hash->{STATE} = 'Initialized';
+ $hash->{NAME} = $devName;
+ $hash->{TYPE} = $type;
+ $hash->{ONEWIREID} = $oneWireId;
+ $hash->{ESERAID} = undef; # We will get this from the first reading.
+ $hash->{DEVICE_TYPE} = $deviceType;
+
+ my $success = EseraDigitalInOut_calculateBits($hash, $deviceType, $bitPos, $bitCount);
+ if (!$success)
+ {
+ Log3 $devName, 1, "EseraDigitalInOut ($devName) - definition failed";
+ return undef;
+ }
+
+ $modules{EseraDigitalInOut}{defptr}{$oneWireId} = $hash;
+
+ AssignIoPort($hash, $physicalDevice);
+
+ if (defined($hash->{IODev}->{NAME}))
+ {
+ Log3 $devName, 4, "EseraDigitalInOut ($devName) - I/O device is " . $hash->{IODev}->{NAME};
+ }
+ else
+ {
+ Log3 $devName, 1, "EseraDigitalInOut ($devName) - no I/O device";
+ }
+
+ # program the the device type into the controller via the physical module
+ if ($deviceType =~ m/^DS([0-9A-F]+)/)
+ {
+ # for the DS* devices types the "DS" has to be omitted
+ IOWrite($hash, "assign;$oneWireId;$1");
+ }
+ elsif (!($deviceType =~ m/^SYS[12]/))
+ {
+ IOWrite($hash, "assign;$oneWireId;$deviceType");
+ }
+
+ return undef;
+}
+
+sub
+EseraDigitalInOut_Undef($$)
+{
+ my ($hash, $arg) = @_;
+ my $oneWireId = $hash->{ONEWIREID};
+
+ RemoveInternalTimer($hash);
+ delete( $modules{EseraDigitalInOut}{defptr}{$oneWireId} );
+
+ return undef;
+}
+
+sub
+EseraDigitalInOut_Get($@)
+{
+ return undef;
+}
+
+sub
+EseraDigitalInOut_setDS2408digout($$$$)
+{
+ my ($hash, $owId, $mask, $value) = @_;
+ my $name = $hash->{NAME};
+
+ if ($mask < 1)
+ {
+ my $message = "error: at least one mask bit must be set, mask ".$mask.", value ".$value;
+ Log3 $name, 1, "EseraDigitalInOut ($name) - ".$message;
+ return $message;
+ }
+
+ if ($mask > 255)
+ {
+ my $message = "error: mask is out of range";
+ Log3 $name, 1, "EseraDigitalInOut ($name) - ".$message;
+ return $message;
+ }
+
+ if (($value < 0) || ($value > 255))
+ {
+ my $message = "error: value out of range";
+ Log3 $name, 1, "EseraDigitalInOut ($name) - ".$message;
+ return $message;
+ }
+
+ # look up the ESERA ID
+ my $eseraId = $hash->{ESERAID};
+ if (!defined $eseraId)
+ {
+ my $message = "error: ESERA ID not known";
+ Log3 $name, 1, "EseraDigitalInOut ($name) - ".$message;
+ return $message;
+ }
+
+ # set values as given by mask and value
+ if ($mask == 255)
+ {
+ # all bits are selected, use command to set all bits
+ my $command = "set,owd,outh,".$eseraId.",".$value;
+ IOWrite($hash, "set;$owId;$command");
+ return undef;
+ }
+ else
+ {
+ # a subset of bits is selected, iterate over selected bits
+ my $i;
+ for ($i=0; $i<8; $i++)
+ {
+ if ($mask & 0x1)
+ {
+ my $bitValue = $value & 0x1;
+ my $command = "set,owd,out,".$eseraId.",".$i.",".$bitValue;
+ IOWrite($hash, "set;$owId;$command");
+ }
+ $mask = $mask >> 1;
+ $value = $value >> 1;
+ }
+ return undef;
+ }
+
+ return undef;
+}
+
+sub
+EseraDigitalInOut_setSysDigout($$$$)
+{
+ my ($hash, $owId, $mask, $value) = @_;
+ my $name = $hash->{NAME};
+
+ if ($mask < 1)
+ {
+ my $message = "error: at least one mask bit must be set, mask ".$mask.", value ".$value;
+ Log3 $name, 1, "EseraDigitalInOut ($name) - ".$message;
+ return $message;
+ }
+
+ if ($mask > 31)
+ {
+ my $message = "error: mask is out of range";
+ Log3 $name, 1, "EseraDigitalInOut ($name) - ".$message;
+ return $message;
+ }
+
+ if (($value < 0) || ($value > 31))
+ {
+ my $message = "error: value out of range";
+ Log3 $name, 1, "EseraDigitalInOut ($name) - ".$message;
+ return $message;
+ }
+
+ # look up the ESERA ID
+ my $eseraId = $hash->{ESERAID};
+ if (!defined $eseraId)
+ {
+ my $message = "error: ESERA ID not known";
+ Log3 $name, 1, "EseraDigitalInOut ($name) - ".$message;
+ return $message;
+ }
+
+ # set values as given by mask and value
+ if ($mask == 31)
+ {
+ # all bits are selected, use command to set all bits
+ my $command = "set,sys,outh,".$value;
+ IOWrite($hash, "set;$owId;$command");
+ return undef;
+ }
+ else
+ {
+ # a subset of bits is selected, iterate over selected bits
+ my $i;
+ for ($i=0; $i<8; $i++)
+ {
+ if ($mask & 0x1)
+ {
+ my $bitValue = $value & 0x1;
+ my $command = "set,sys,out,".($i+1).",".$bitValue;
+ IOWrite($hash, "set;$owId;$command");
+ }
+ $mask = $mask >> 1;
+ $value = $value >> 1;
+ }
+ return undef;
+ }
+
+ return undef;
+}
+
+sub
+EseraDigitalInOut_calculateBitMasksForSet($$$)
+{
+ my ($hash, $mask, $value) = @_;
+ my $name = $hash->{NAME};
+
+ my $maxMask = (2**($hash->{BITCOUNT})) - 1;
+
+ my $adjustedMask = ($mask & $maxMask) << $hash->{BITPOS};
+ my $adjustedValue = ($value & $maxMask) << $hash->{BITPOS};
+
+ return ($adjustedMask, $adjustedValue);
+}
+
+sub
+EseraDigitalInOut_setOutput($$$$)
+{
+ my ($hash, $oneWireId, $mask, $value) = @_;
+ my $name = $hash->{NAME};
+
+ Log3 $name, 5, "EseraDigitalInOut ($name) - EseraDigitalInOut_setOutput inputs: $oneWireId,$mask,$value";
+
+ if (!defined $hash->{DEVICE_TYPE})
+ {
+ my $message = "error: device type not known";
+ Log3 $name, 1, "EseraDigitalInOut ($name) - ".$message;
+ return $message;
+ }
+
+ if (($hash->{DEVICE_TYPE} eq "DS2408") ||
+ ($hash->{DEVICE_TYPE} eq "11220") ||
+ ($hash->{DEVICE_TYPE} eq "11228") ||
+ ($hash->{DEVICE_TYPE} eq "11229"))
+ {
+ my ($adjustedMask, $adjustedValue) = EseraDigitalInOut_calculateBitMasksForSet($hash, $mask, $value);
+
+ Log3 $name, 5, "EseraDigitalInOut ($name) - EseraDigitalInOut_setOutput DS2408 adjustedMask: $adjustedMask, adjustedValue: $adjustedValue";
+ EseraDigitalInOut_setDS2408digout($hash, $oneWireId, $adjustedMask, $adjustedValue);
+ }
+ elsif ($hash->{DEVICE_TYPE} eq "SYS2")
+ {
+ my ($adjustedMask, $adjustedValue) = EseraDigitalInOut_calculateBitMasksForSet($hash, $mask, $value);
+
+ Log3 $name, 5, "EseraDigitalInOut ($name) - EseraDigitalInOut_setOutput SYS2 adjustedMask: $adjustedMask, adjustedValue: $adjustedValue";
+ EseraDigitalInOut_setSysDigout($hash, $oneWireId, $adjustedMask, $adjustedValue);
+ }
+ elsif (($hash->{DEVICE_TYPE} eq "11216") || ($hash->{DEVICE_TYPE} eq "SYS1"))
+ {
+ Log3 $name, 1, "EseraDigitalInOut ($name) - error: trying to set digital output but this device only has inputs";
+ }
+ else
+ {
+ my $message = "error: device type not supported: ".$hash->{DEVICE_TYPE};
+ Log3 $name, 1, "EseraDigitalInOut ($name) - ".$message;
+ return $message;
+ }
+
+ return undef;
+}
+
+# interpret a string entered by the user as a number
+sub
+EseraDigitalInOut_convertNumber($$)
+{
+ my ($hash, $numberString) = @_;
+ $numberString = lc($numberString);
+ my $number = undef;
+ if ($numberString =~ m/^(\d+)$/)
+ {
+ $number = $1;
+ }
+ elsif (($numberString =~ m/^0b([01]+)$/) || ($numberString =~ m/^0x([a-f0-9]+)$/))
+ {
+ $number = oct($numberString);
+ }
+ return $number;
+}
+
+sub
+EseraDigitalInOut_Set($$)
+{
+ my ( $hash, @parameters ) = @_;
+ my $name = $parameters[0];
+ my $what = lc($parameters[1]);
+
+ my $oneWireId = $hash->{ONEWIREID};
+ my $iodev = $hash->{IODev}->{NAME};
+
+ my $commands = ("on:noArg off:noArg out");
+
+ if ($what eq "out")
+ {
+ if ((scalar(@parameters) != 4))
+ {
+ my $message = "error: unexpected number of parameters (".scalar(@parameters).")";
+ Log3 $name, 1, "EseraDigitalInOut ($name) - ".$message;
+ return $message;
+ }
+ my $mask = EseraDigitalInOut_convertNumber($hash, $parameters[2]);
+ my $value = EseraDigitalInOut_convertNumber($hash, $parameters[3]);
+ EseraDigitalInOut_setOutput($hash, $oneWireId, $mask, $value);
+ $hash->{LAST_OUT} = undef;
+ }
+ elsif ($what eq "on")
+ {
+ if ((scalar(@parameters) != 2))
+ {
+ my $message = "error: unexpected number of parameters (".scalar(@parameters).")";
+ Log3 $name, 1, "EseraDigitalInOut ($name) - ".$message;
+ return $message;
+ }
+ EseraDigitalInOut_setOutput($hash, $oneWireId, 0xFFFFFFFF, 0xFFFFFFFF);
+ $hash->{LAST_OUT} = 1;
+ }
+ elsif ($what eq "off")
+ {
+ if ((scalar(@parameters) != 2))
+ {
+ my $message = "error: unexpected number of parameters (".scalar(@parameters).")";
+ Log3 $name, 1, "EseraDigitalInOut ($name) - ".$message;
+ return $message;
+ }
+ EseraDigitalInOut_setOutput($hash, $oneWireId, 0xFFFFFFFF, 0x00000000);
+ $hash->{LAST_OUT} = 0;
+ }
+ elsif ($what eq "?")
+ {
+ my $message = "unknown argument $what, choose one of $commands";
+ return $message;
+ }
+ else
+ {
+ shift @parameters;
+ shift @parameters;
+ return SetExtensions($hash, $commands, $name, $what, @parameters);
+ }
+ return undef;
+}
+
+sub
+EseraDigitalInOut_getReadingValue($$$)
+{
+ my ($value, $bitPos, $bitCount) = @_;
+
+ # The controller sends digital output state as binary mask (without leading 0b)
+ my ($decimalValue) = oct("0b".$value);
+
+ return ($decimalValue >> $bitPos) & ((2**$bitCount)-1);
+}
+
+sub
+EseraDigitalInOut_ParseForOneDevice($$$$$$)
+{
+ my ($rhash, $deviceType, $oneWireId, $eseraId, $readingId, $value) = @_;
+ my $rname = $rhash->{NAME};
+ Log3 $rname, 4, "EseraDigitalInOut ($rname) - ParseForOneDevice: ".$rname;
+
+ # capture the Esera ID for later use
+ $rhash->{ESERAID} = $eseraId;
+
+ # consistency check of device type
+ if (!($rhash->{DEVICE_TYPE} eq uc($deviceType)))
+ {
+ Log3 $rname, 1, "EseraDigitalInOut ($rname) - unexpected device type ".$deviceType;
+
+ # program the the device type into the controller via the physical module
+ if ($rhash->{DEVICE_TYPE} =~ m/^DS([0-9A-F]+)/)
+ {
+ # for the DS* devices types the "DS" has to be omitted
+ IOWrite($rhash, "assign;$oneWireId;$1");
+ }
+ elsif (!($deviceType =~ m/^SYS[12]/))
+ {
+ IOWrite($rhash, "assign;$oneWireId;".$rhash->{DEVICE_TYPE});
+ }
+ }
+
+ if ($readingId eq "ERROR")
+ {
+ Log3 $rname, 1, "EseraDigitalInOut ($rname) - error message from physical device: ".$value;
+ }
+ elsif ($readingId eq "STATISTIC")
+ {
+ Log3 $rname, 1, "EseraDigitalInOut ($rname) - statistics message not supported yet: ".$value;
+ }
+ else
+ {
+ my $nameOfReading;
+ if ($deviceType eq "DS2408")
+ {
+ if ($readingId == 2)
+ {
+ $nameOfReading = "in";
+ my $readingValue = EseraDigitalInOut_getReadingValue($value, $rhash->{BITPOS}, $rhash->{BITCOUNT});
+ readingsSingleUpdate($rhash, $nameOfReading, $readingValue, 1);
+ }
+ elsif ($readingId == 4)
+ {
+ $nameOfReading = "out";
+ my $readingValue = EseraDigitalInOut_getReadingValue($value, $rhash->{BITPOS}, $rhash->{BITCOUNT});
+ readingsSingleUpdate($rhash, $nameOfReading, $readingValue, 1);
+ }
+ }
+ elsif (($deviceType eq "11220") || ($deviceType eq "11228")) # 8 channel digital output with push buttons
+ {
+ if ($readingId == 2)
+ {
+ $nameOfReading = "in";
+ my $readingValue = EseraDigitalInOut_getReadingValue($value, $rhash->{BITPOS}, $rhash->{BITCOUNT});
+ readingsSingleUpdate($rhash, $nameOfReading, $readingValue, 1);
+ }
+ if ($readingId == 4)
+ {
+ $nameOfReading = "out";
+ my $readingValue = EseraDigitalInOut_getReadingValue($value, $rhash->{BITPOS}, $rhash->{BITCOUNT});
+ readingsSingleUpdate($rhash, $nameOfReading, $readingValue, 1);
+ }
+ }
+ elsif ($deviceType eq "11229") # 8 channel digital output
+ {
+ if ($readingId == 4)
+ {
+ $nameOfReading = "out";
+ my $readingValue = EseraDigitalInOut_getReadingValue($value, $rhash->{BITPOS}, $rhash->{BITCOUNT});
+ readingsSingleUpdate($rhash, $nameOfReading, $readingValue, 1);
+ }
+ }
+ elsif ($deviceType eq "11216") # 8 channel digital input
+ {
+ if ($readingId == 2)
+ {
+ $nameOfReading = "in";
+ my $readingValue = EseraDigitalInOut_getReadingValue($value, $rhash->{BITPOS}, $rhash->{BITCOUNT});
+ readingsSingleUpdate($rhash, $nameOfReading, $readingValue, 1);
+ }
+ }
+ elsif ($deviceType eq "SYS2") # Controller 2 digital output
+ {
+ if ($readingId == 2)
+ {
+ $nameOfReading = "out";
+ my $readingValue = EseraDigitalInOut_getReadingValue($value, $rhash->{BITPOS}, $rhash->{BITCOUNT});
+ readingsSingleUpdate($rhash, $nameOfReading, $readingValue, 1);
+ }
+ }
+ elsif ($deviceType eq "SYS1") # Controller 2 digital input
+ {
+ if ($readingId == 2)
+ {
+ $nameOfReading = "in";
+ my $readingValue = EseraDigitalInOut_getReadingValue($value, $rhash->{BITPOS}, $rhash->{BITCOUNT});
+ readingsSingleUpdate($rhash, $nameOfReading, $readingValue, 1);
+ }
+ }
+ }
+ return $rname;
+}
+
+sub
+EseraDigitalInOut_Parse($$)
+{
+ my ($ioHash, $msg) = @_;
+ my $ioName = $ioHash->{NAME};
+ my $buffer = $msg;
+
+ # expected message format: $deviceType."_".$oneWireId."_".$eseraId."_".$readingId."_".$value
+ my @fields = split(/_/, $buffer);
+ if (scalar(@fields) != 5)
+ {
+ return undef;
+ }
+ my $deviceType = uc($fields[0]);
+ my $oneWireId = $fields[1];
+ my $eseraId = $fields[2];
+ my $readingId = $fields[3];
+ my $value = $fields[4];
+
+ # search for logical device
+ my $rhash = undef;
+ my @list;
+ foreach my $d (keys %defs)
+ {
+ my $h = $defs{$d};
+ my $type = $h->{TYPE};
+
+ if($type eq "EseraDigitalInOut")
+ {
+ if (defined($h->{IODev}->{NAME}))
+ {
+ my $ioDev = $h->{IODev}->{NAME};
+ my $def = $h->{DEF};
+
+ # $def has the whole definition, extract the oneWireId (which is expected as 2nd parameter)
+ my @parts = split(/ /, $def);
+ my $oneWireIdFromDef = $parts[1];
+
+ if (($ioDev eq $ioName) && ($oneWireIdFromDef eq $oneWireId))
+ {
+ $rhash = $h;
+ my $rname = EseraDigitalInOut_ParseForOneDevice($rhash, $deviceType, $oneWireId, $eseraId, $readingId, $value);
+ push(@list, $rname);
+ }
+ }
+ }
+ }
+
+ if ((scalar @list) > 0)
+ {
+ return @list;
+ }
+ elsif (($deviceType eq "DS2408") or ($deviceType eq "11216") or
+ ($deviceType eq "11220") or
+ ($deviceType eq "11228") or ($deviceType eq "11229") or
+ ($deviceType eq "SYS1") or ($deviceType eq "SYS2"))
+ {
+ return "UNDEFINED EseraDigitalInOut_".$ioName."_".$oneWireId." EseraDigitalInOut ".$ioName." ".$oneWireId." ".$deviceType." - -";
+ }
+
+ return undef;
+}
+
+sub
+EseraDigitalInOut_Attr(@)
+{
+}
+
+
+
+1;
+
+=pod
+=item summary Represents a 1-wire digital input/output.
+=item summary_DE Repraesentiert einen 1-wire digitalen Eingang/Ausgang.
+=begin html
+
+
+EseraDigitalInOut
+
+
+ This module implements a 1-wire digital input/output. It uses 66_EseraOneWire
+ as I/O device.
+
+
+
+ Define
+
+ define <name> EseraDigitalInOut <ioDevice> <oneWireId> <deviceType> <bitPos> <bitCount>
+ <oneWireId> specifies the 1-wire ID of the digital input/output chip.
+ Use the "get devices" query of EseraOneWire to get a list of 1-wire IDs,
+ or simply rely on autocreate.
+ Supported values for deviceType:
+
+ - DS2408
+ - 11220/11228 (Esera "Digital Out 8-Channel with push-button interface")
+ - 11229 (Esera "Digital Out 8-Channel")
+ - 11216 (Esera "8-Channel Digital Input DC")
+ - SYS1 (Esera Controller 2, digital input, not listed by "get devices")
+ - SYS2 (Esera Controller 2, digital output, not listed by "get devices")
+
+ The bitPos and bitCount parameters is used to specify a subset of bits only.
+ For example, the DS2408 has 8 inputs, and you can define a EseraDigitalInOut
+ that uses bits 4..7 (in range 0..7). In this case you specify bitPos = 4 and
+ bitWidth = 4.
+ You can also give "-" for bitPos and bitWidth. In this case the module uses
+ the maximum possible bit range, which is bitPos = 0 and bitWidth = 8 for DS2408.
+ In typical use cases the n bits of a digital input device are used to control
+ or observe n different things, e.g. 8 motion sensors connected to 8 digital inputs.
+ In this case you would define 8 EseraDigitalInOut devices, one for each motion sensor.
+
+
+
+
+ Set
+
+ -
+
set <name> out <bitMask> <bitValue>
+ Controls digital outputs. The bitMask selects bits that are programmed,
+ and bitValue specifies the new value.
+ Examples: set myEseraDigitalInOut out 0xf 0x3
+ In this example the four lower outputs are selected by the mask,
+ and they get the new value 0x3 = 0b0011.
+ bitMask and bitValue can be specified as hex number (0x...), binary
+ number (0b....) or decimal number.
+ Note: If all bits are selected by mask the outputs are set by a single
+ access to the controller. If subset of bits is selected the bits are set
+ by individual accesses, one after the other, as fast as the controller allows.
+
+ -
+
set <name> on
+ Switch on all outputs.
+
+ -
+
set <name> off
+ Switch off all outputs.
+
+
+
+
+
+ Get
+
+
+
+
+ Attributes
+
+
+
+
+ Readings
+
+ - in – digital input state
+ - out – digital output state
+
+
+
+
+
+=end html
+=cut
diff --git a/fhem/FHEM/66_EseraIButton.pm b/fhem/FHEM/66_EseraIButton.pm
new file mode 100644
index 000000000..543f25b21
--- /dev/null
+++ b/fhem/FHEM/66_EseraIButton.pm
@@ -0,0 +1,294 @@
+################################################################################
+#
+# $Id$
+#
+# 66_EseraIButton.pm
+#
+# Copyright (C) 2018 pizmus
+#
+# This program 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 program. If not, see .
+#
+################################################################################
+#
+# This FHEM module supports iButton devices connected via an Esera 1-wire Controller
+# and the 66_EseraOneWire module.
+# For more details please read the device specific help / commandref.
+#
+################################################################################
+
+package main;
+
+use strict;
+use warnings;
+use SetExtensions;
+
+sub
+EseraIButton_Initialize($)
+{
+ my ($hash) = @_;
+ $hash->{Match} = "DS2401";
+ $hash->{DefFn} = "EseraIButton_Define";
+ $hash->{UndefFn} = "EseraIButton_Undef";
+ $hash->{ParseFn} = "EseraIButton_Parse";
+ $hash->{SetFn} = "EseraIButton_Set";
+ $hash->{GetFn} = "EseraIButton_Get";
+ $hash->{AttrFn} = "EseraIButton_Attr";
+ $hash->{AttrList} = "$readingFnAttributes";
+}
+
+sub
+EseraIButton_Define($$)
+{
+ my ($hash, $def) = @_;
+ my @a = split( "[ \t][ \t]*", $def);
+
+ return "Usage: define EseraIButton <1-wire-ID> " if(@a < 5);
+
+ my $devName = $a[0];
+ my $type = $a[1];
+ my $physicalDevice = $a[2];
+ my $oneWireId = $a[3];
+ my $deviceType = uc($a[4]);
+
+ $hash->{STATE} = 'Initialized';
+ $hash->{NAME} = $devName;
+ $hash->{TYPE} = $type;
+ $hash->{ONEWIREID} = $oneWireId;
+ $hash->{ESERAID} = undef; # We will get this from the first reading.
+ $hash->{DEVICE_TYPE} = $deviceType;
+
+ $modules{EseraIButton}{defptr}{$oneWireId} = $hash;
+
+ AssignIoPort($hash, $physicalDevice);
+
+ if (defined($hash->{IODev}->{NAME}))
+ {
+ Log3 $devName, 4, "$devName: I/O device is " . $hash->{IODev}->{NAME};
+ }
+ else
+ {
+ Log3 $devName, 1, "$devName: no I/O device";
+ }
+
+ return undef;
+}
+
+sub
+EseraIButton_Undef($$)
+{
+ my ($hash, $arg) = @_;
+ my $oneWireId = $hash->{ONEWIREID};
+
+ RemoveInternalTimer($hash);
+ delete( $modules{EseraIButton}{defptr}{$oneWireId} );
+
+ return undef;
+}
+
+sub
+EseraIButton_Get($@)
+{
+ return undef;
+}
+
+sub
+EseraIButton_Set($$)
+{
+ my ( $hash, @parameters ) = @_;
+ my $name = $parameters[0];
+ my $what = lc($parameters[1]);
+
+ my $oneWireId = $hash->{ONEWIREID};
+ my $iodev = $hash->{IODev}->{NAME};
+
+ my $commands = ("statusRequest");
+
+ if ($what eq "statusRequest")
+ {
+ IOWrite($hash, "status;$oneWireId");
+ }
+ elsif ($what eq "?")
+ {
+ # TODO use the :noArg info
+ my $message = "unknown argument $what, choose one of $commands";
+ return $message;
+ }
+ else
+ {
+ my $message = "unknown argument $what, choose one of $commands";
+ Log3 $name, 1, "EseraIButton ($name) - ".$message;
+ return $message;
+ }
+ return undef;
+}
+
+sub
+EseraIButton_Parse($$)
+{
+ my ($ioHash, $msg) = @_;
+ my $ioName = $ioHash->{NAME};
+ my $buffer = $msg;
+
+ # expected message format: $deviceType."_".$oneWireId."_".$eseraId."_".$readingId."_".$value
+ my @fields = split(/_/, $buffer);
+ if (scalar(@fields) != 5)
+ {
+ return undef;
+ }
+ my $deviceType = uc($fields[0]);
+ my $oneWireId = $fields[1];
+ my $eseraId = $fields[2];
+ my $readingId = $fields[3];
+ my $value = $fields[4];
+
+ # search for logical device
+ my $rhash = undef;
+ foreach my $d (keys %defs) {
+ my $h = $defs{$d};
+ my $type = $h->{TYPE};
+
+ if($type eq "EseraIButton")
+ {
+ if (defined($h->{IODev}->{NAME}))
+ {
+ my $ioDev = $h->{IODev}->{NAME};
+ my $def = $h->{DEF};
+
+ # $def has the whole definition, extract the oneWireId (which is expected as 2nd parameter)
+ my @parts = split(/ /, $def);
+ my $oneWireIdFromDef = $parts[1];
+
+ if (($ioDev eq $ioName) && ($oneWireIdFromDef eq $oneWireId))
+ {
+ $rhash = $h;
+ last;
+ }
+ }
+ }
+ }
+
+ if($rhash) {
+ my $rname = $rhash->{NAME};
+ Log3 $rname, 4, "EseraIButton ($rname) - parse - device found: ".$rname;
+
+ # capture the Esera ID for later use
+ $rhash->{ESERAID} = $eseraId;
+
+ # consistency check of device type
+ if (!($rhash->{DEVICE_TYPE} eq uc($deviceType)))
+ {
+ Log3 $rname, 1, "EseraIButton ($rname) - unexpected device type ".$deviceType;
+ }
+
+ if ($readingId eq "ERROR")
+ {
+ Log3 $rname, 1, "EseraIButton ($rname) - error message from physical device: ".$value;
+ }
+ elsif ($readingId eq "STATISTIC")
+ {
+ Log3 $rname, 1, "EseraIButton ($rname) - statistics message not supported yet: ".$value;
+ }
+ else
+ {
+ my $nameOfReading = "status";
+ readingsSingleUpdate($rhash, $nameOfReading, $value, 1);
+ }
+
+ my @list;
+ push(@list, $rname);
+ return @list;
+ }
+ elsif ($deviceType eq "DS2401") # TODO
+ {
+ return "UNDEFINED EseraIButton_".$ioName."_".$oneWireId." EseraIButton ".$ioName." ".$oneWireId." ".$deviceType;
+ }
+
+ return undef;
+}
+
+sub
+EseraIButton_Attr(@)
+{
+}
+
+1;
+
+=pod
+=item summary Represents a 1-wire iButton device.
+=item summary_DE Repraesentiert einen 1-wire iButton.
+=begin html
+
+
+EseraIButton
+
+
+ This module supports 1-wire iButton devices. It uses 66_EseraOneWire as I/O device.
+ Events are generated for connecting and disconnecting an iButton.
+
+ The Esera Controller needs to know the iButton so that it can detect it quickly when it
+ is connected. The iButton needs to be in the list of devices which is stored in a non-volatile
+ memory in the controller. Initially, you need to connect a new iButton for ~10 seconds. Use the
+ "get devices" query of EseraOneWire to check whether the device has been detected. When it has
+ been detected use "set savelist" to store the current list in the controller. Repeat the same
+ procedure with additional iButtons. Alternatively, you can use the "Config Tool 3" software from
+ Esera to store iButton devices in the controller.
+
+ It is stronly recommended to use the additional license "iButton Fast Mode" from Esera (product
+ number 40202). With this license the controller detects iButton devices quickly. Without that
+ license the controller sometimes needs quite long to detect an iButton.
+
+ See the "Programmierhandbuch" from Esera for details.
+
+
+
+ Define
+
+ define <name> EseraIButton <ioDevice> <oneWireId> <deviceType>
+ <oneWireId> specifies the 1-wire ID of the iButton.
+ Supported values for deviceType: DS2401
+
+
+
+
+ Set
+
+
+
+
+ Get
+
+
+
+
+ Attributes
+
+
+
+
+ Readings
+
+ - status – connection status 0 or 1
+
+
+
+
+
+=end html
+=cut
diff --git a/fhem/FHEM/66_EseraMulti.pm b/fhem/FHEM/66_EseraMulti.pm
new file mode 100644
index 000000000..7b7dccdf0
--- /dev/null
+++ b/fhem/FHEM/66_EseraMulti.pm
@@ -0,0 +1,351 @@
+################################################################################
+#
+# $Id$
+#
+# 66_EseraMulti.pm
+#
+# Copyright (C) 2018 pizmus
+#
+# This program 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 program. If not, see .
+#
+################################################################################
+#
+# This FHEM module supports an Esera multi sensor connected via
+# an Esera 1-wire Controller and the 66_EseraOneWire module.
+#
+################################################################################
+
+package main;
+
+use strict;
+use warnings;
+use SetExtensions;
+
+sub
+EseraMulti_Initialize($)
+{
+ my ($hash) = @_;
+ $hash->{Match} = "DS2438";
+ $hash->{DefFn} = "EseraMulti_Define";
+ $hash->{UndefFn} = "EseraMulti_Undef";
+ $hash->{ParseFn} = "EseraMulti_Parse";
+ $hash->{SetFn} = "EseraMulti_Set";
+ $hash->{GetFn} = "EseraMulti_Get";
+ $hash->{AttrFn} = "EseraMulti_Attr";
+ $hash->{AttrList} = "$readingFnAttributes";
+}
+
+sub
+EseraMulti_Define($$)
+{
+ my ($hash, $def) = @_;
+ my @a = split( "[ \t][ \t]*", $def);
+
+ return "Usage: define EseraMulti <1-wire-ID> " if(@a < 5);
+
+ my $devName = $a[0];
+ my $type = $a[1];
+ my $physicalDevice = $a[2];
+ my $oneWireId = $a[3];
+ my $deviceType = uc($a[4]);
+
+ $hash->{STATE} = 'Initialized';
+ $hash->{NAME} = $devName;
+ $hash->{TYPE} = $type;
+ $hash->{ONEWIREID} = $oneWireId;
+ $hash->{ESERAID} = undef; # We will get this from the first reading.
+ $hash->{DEVICE_TYPE} = $deviceType;
+
+ $modules{EseraMulti}{defptr}{$oneWireId} = $hash;
+
+ AssignIoPort($hash, $physicalDevice);
+
+ if (defined($hash->{IODev}->{NAME}))
+ {
+ Log3 $devName, 4, "$devName: I/O device is " . $hash->{IODev}->{NAME};
+ }
+ else
+ {
+ Log3 $devName, 1, "$devName: no I/O device";
+ }
+
+ # program the the device type into the controller via the physical module
+ if ($deviceType =~ m/^DS([0-9A-F]+)/)
+ {
+ # for the DS* devices types the "DS" has to be omitted
+ IOWrite($hash, "assign;$oneWireId;$1");
+ }
+ else
+ {
+ IOWrite($hash, "assign;$oneWireId;$deviceType");
+ }
+
+ return undef;
+}
+
+sub
+EseraMulti_Undef($$)
+{
+ my ($hash, $arg) = @_;
+ my $oneWireId = $hash->{ONEWIREID};
+
+ RemoveInternalTimer($hash);
+ delete( $modules{EseraMulti}{defptr}{$oneWireId} );
+
+ return undef;
+}
+
+sub
+EseraMulti_Get($@)
+{
+ return undef;
+}
+
+sub
+EseraMulti_Set($$)
+{
+ return undef;
+}
+
+sub
+EseraMulti_Parse($$)
+{
+ my ($ioHash, $msg) = @_;
+ my $ioName = $ioHash->{NAME};
+ my $buffer = $msg;
+
+ # expected message format: $deviceType."_".$oneWireId."_".$eseraId."_".$readingId."_".$value
+ my @fields = split(/_/, $buffer);
+ if (scalar(@fields) != 5)
+ {
+ return undef;
+ }
+ my $deviceType = uc($fields[0]);
+ my $oneWireId = $fields[1];
+ my $eseraId = $fields[2];
+ my $readingId = $fields[3];
+ my $value = $fields[4];
+
+ # search for logical device
+ my $rhash = undef;
+ foreach my $d (keys %defs) {
+ my $h = $defs{$d};
+ my $type = $h->{TYPE};
+
+ if($type eq "EseraMulti")
+ {
+ if (defined($h->{IODev}->{NAME}))
+ {
+ my $ioDev = $h->{IODev}->{NAME};
+ my $def = $h->{DEF};
+
+ # $def has the whole definition, extract the oneWireId (which is expected as 2nd parameter)
+ my @parts = split(/ /, $def);
+ my $oneWireIdFromDef = $parts[1];
+
+ if (($ioDev eq $ioName) && ($oneWireIdFromDef eq $oneWireId))
+ {
+ $rhash = $h;
+ last;
+ }
+ }
+ }
+ }
+
+ if($rhash) {
+ my $rname = $rhash->{NAME};
+ Log3 $rname, 4, "EseraMulti ($rname) - parse - device found: ".$rname;
+
+ # capture the Esera ID for later use
+ $rhash->{ESERAID} = $eseraId;
+
+ # consistency check of device type
+ if (!($rhash->{DEVICE_TYPE} eq uc($deviceType)))
+ {
+ Log3 $rname, 1, "EseraMulti ($rname) - unexpected device type ".$deviceType;
+
+ # program the the device type into the controller via the physical module
+ if ($rhash->{DEVICE_TYPE} =~ m/^DS([0-9A-F]+)/)
+ {
+ # for the DS* devices types the "DS" has to be omitted
+ IOWrite($rhash, "assign;$oneWireId;$1");
+ }
+ else
+ {
+ IOWrite($rhash, "assign;$oneWireId;".$rhash->{DEVICE_TYPE});
+ }
+ }
+
+ if ($readingId eq "ERROR")
+ {
+ Log3 $rname, 1, "EseraMulti ($rname) - error message from physical device: ".$value;
+ }
+ elsif ($readingId eq "STATISTIC")
+ {
+ Log3 $rname, 1, "EseraMulti ($rname) - statistics message not supported yet: ".$value;
+ }
+ else
+ {
+ my $nameOfReading;
+ if ($deviceType eq "DS2438")
+ {
+ if ($readingId == 1)
+ {
+ $nameOfReading = "temperature";
+ readingsSingleUpdate($rhash, $nameOfReading, $value / 100.0, 1);
+ }
+ elsif ($readingId == 2)
+ {
+ $nameOfReading = "VCC";
+ readingsSingleUpdate($rhash, $nameOfReading, $value / 100.0, 1);
+ }
+ elsif ($readingId == 3)
+ {
+ $nameOfReading = "VAD";
+ readingsSingleUpdate($rhash, $nameOfReading, $value / 100.0, 1);
+ }
+ elsif ($readingId == 4)
+ {
+ $nameOfReading = "VSense";
+ readingsSingleUpdate($rhash, $nameOfReading, $value / 100000.0, 1);
+ }
+ }
+ elsif (($deviceType eq "11121") || ($deviceType eq "11132") || ($deviceType eq "11134") || ($deviceType eq "11135"))
+ {
+ if ($readingId == 1)
+ {
+ $nameOfReading = "temperature";
+ readingsSingleUpdate($rhash, $nameOfReading, $value / 100.0, 1);
+ }
+ elsif ($readingId == 2)
+ {
+ $nameOfReading = "voltage";
+ readingsSingleUpdate($rhash, $nameOfReading, $value / 100.0, 1);
+ }
+ elsif ($readingId == 3)
+ {
+ $nameOfReading = "humidity";
+ readingsSingleUpdate($rhash, $nameOfReading, $value / 100.0, 1);
+ }
+ elsif ($readingId == 4)
+ {
+ $nameOfReading = "dewpoint";
+ readingsSingleUpdate($rhash, $nameOfReading, $value / 100.0, 1);
+ }
+ elsif ($readingId == 5)
+ {
+ $nameOfReading = "brightness";
+ readingsSingleUpdate($rhash, $nameOfReading, $value / 100.0, 1);
+ }
+ }
+ }
+
+ my @list;
+ push(@list, $rname);
+ return @list;
+ }
+ elsif (($deviceType eq "DS2438") || ($deviceType eq "11121") || ($deviceType eq "11132") || ($deviceType eq "11134") || ($deviceType eq "11135"))
+ {
+ return "UNDEFINED EseraMulti_".$ioName."_".$oneWireId." EseraMulti ".$ioName." ".$oneWireId." ".$deviceType;
+ }
+
+ return undef;
+}
+
+sub
+EseraMulti_Attr(@)
+{
+}
+
+1;
+
+=pod
+=item summary Represents an Esera 1-wire multi sensor.
+=item summary_DE Repraesentiert einen Esera 1-wire Multi-Sensor.
+=begin html
+
+
+EseraMulti
+
+
+ This module supports an Esera 1-wire multi sensor or a DS2438 1-wire IC.
+ It uses 66_EseraOneWire as I/O device.
+
+
+
+ Define
+
+ define <name> EseraMulti <ioDevice> <oneWireId> <deviceType>
+ <oneWireId> specifies the 1-wire ID of the sensor. Use the "get devices"
+ query of EseraOneWire to get a list of 1-wire IDs, or simply rely on autocreate.
+ Supported values for deviceType:
+
+ - DS2438
+ - 11121 (Esera product number)
+ - 11132 (Esera product number, multi sensor Unterputz)
+ - 11134 (Esera product number, multi sensor Aufputz)
+ - 11135 (Esera product number, multi sensor Outdoor)
+
+ With deviceType DS2438 this device generates readings with un-interpreted data
+ from DS2438. This can be used with any DS2438 device, independent of an Esera
+ product. With deviceType 11121/11132/11134/11135 this module provides interpreted
+ readings like humidity or dew point.
+
+
+
+ Set
+
+
+
+
+ Get
+
+
+
+
+ Attributes
+
+
+
+
+ Readings
+
+ readings for DS2438:
+
+ - VAD
+ - VCC
+ - VSense
+ - temperature
+
+ readings for Esera 11121/11132/11134/11135:
+
+ - temperature
+ - humidity
+ - dewpoint
+ - brightness
+ - voltage
+
+
+
+
+
+
+=end html
+=cut
diff --git a/fhem/FHEM/66_EseraOneWire.pm b/fhem/FHEM/66_EseraOneWire.pm
new file mode 100644
index 000000000..952c60776
--- /dev/null
+++ b/fhem/FHEM/66_EseraOneWire.pm
@@ -0,0 +1,2221 @@
+################################################################################
+#
+# $Id$
+#
+# 66_EseraOneWire.pm
+#
+# Copyright (C) 2018 pizmus
+#
+# This program 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 program. If not, see .
+#
+################################################################################
+#
+# This FHEM module controls the Esera "1-wire Controller 1" and the
+# "1-wire Controller 2" with LAN interface.
+# It works together with the following client modules:
+# 66_EseraAnalogInOut
+# 66_EseraDigitalInOut
+# 66_EseraMulti
+# 66_EseraTemp
+# 66_EseraCount
+# 66_EseraIButton
+#
+# For Esera 1-wire controllers with serial/USB interface please check the
+# commandref.
+#
+################################################################################
+#
+# Data stored in device hash:
+# - Various information read from the controller e.g. during EseraOneWire_refreshControllerInfo().
+# In most cases the hash key is named like the controller command.
+# - Hashes storing information per 1-wire device reported by the controller:
+# - ESERA ID of the device (index into a list of devices kept by the controller)
+# - device type
+# - status
+# The key used with these hashes is always a 1-wire ID.
+#
+################################################################################
+#
+# Known issues and potential enhancements:
+#
+# - Let the user control certain settings, like DATATIME, SEACHTIME and wait time
+# used after posted writes, e.g. as parameters to the define command.
+# - Implement support for all devices listed in the Programmierhandbuch.
+#
+################################################################################
+
+package main;
+
+use HttpUtils;
+use strict;
+use warnings;
+use vars qw {%attr %defs};
+use DevIo;
+
+sub
+EseraOneWire_Initialize($)
+{
+ my ($hash) = @_;
+ $hash->{ReadFn} = "EseraOneWire_Read";
+ $hash->{WriteFn} = "EseraOneWire_Write";
+ $hash->{ReadyFn} = "EseraOneWire_Ready";
+ $hash->{DefFn} = "EseraOneWire_Define";
+ $hash->{UndefFn} = "EseraOneWire_Undef";
+ $hash->{DeleteFn} = "EseraOneWire_Delete";
+ $hash->{GetFn} = "EseraOneWire_Get";
+ $hash->{SetFn} = "EseraOneWire_Set";
+ $hash->{AttrFn} = "EseraOneWire_Attr";
+ $hash->{AttrList} = $readingFnAttributes;
+ $hash->{AttrList} = "pollTime ".
+ "dataTime ".
+ $readingFnAttributes;
+
+
+ $hash->{Clients} = ":EseraDigitalInOut:EseraTemp:EseraMulti:EseraAnalogInOut:EseraIButton:EseraCount:";
+ $hash->{MatchList} = { "1:EseraDigitalInOut" => "^DS2408|^11220|^11228|^11229|^11216|^SYS1|^SYS2",
+ "2:EseraTemp" => "^DS1820",
+ "3:EseraMulti" => "^DS2438|^11121|^11132|^11134|^11135",
+ "4:EseraAnalogInOut" => "^SYS3",
+ "5:EseraIButton" => "^DS2401",
+ "6:EseraCount" => "^DS2423"};
+}
+
+sub
+EseraOneWire_Define($$)
+{
+ my ($hash, $def) = @_;
+ my @a = split("[ \t]+", $def);
+
+ my $name = $a[0];
+
+ # $a[1] always equals the module name
+
+ # first argument is the hostname or IP address of the device (e.g. "192.168.1.120")
+ # or the serial port (e.g. /dev/ttyUSB0)
+ my $dev = $a[2];
+
+ return "no device given" unless($dev);
+
+ my $isSerialDevice = ($dev =~ m/^COM|^\/dev\//);
+ if (not $isSerialDevice)
+ {
+ # add the default port
+ $dev .= ':5000' if (not $dev =~ m/:\d+$/);
+ Log3 $name, 3, "EseraOneWire ($name) - define: $dev (TCP/IP)";
+ }
+ else
+ {
+ Log3 $name, 3, "EseraOneWire ($name) - define: $dev (serial)";
+ }
+
+ $hash->{DeviceName} = $dev;
+
+ $hash->{KAL_PERIOD} = 60;
+ $hash->{RECOMMENDED_FW} = 12027;
+ $hash->{DEFAULT_POLLTIME} = 5;
+ $hash->{DEFAULT_DATATIME} = 10;
+
+ # close connection if maybe open (on definition modify)
+ DevIo_CloseDev($hash) if(DevIo_IsOpen($hash));
+
+ # open connection with custom init and error callback function (non-blocking connection establishment)
+ DevIo_OpenDev($hash, 0, "EseraOneWire_Init", "EseraOneWire_Callback");
+
+ EseraOneWire_SetStatus($hash, "defined");
+
+ return undef;
+}
+
+sub
+EseraOneWire_Undef($$)
+{
+ my ($hash, $name) = @_;
+
+ # close the connection
+ DevIo_CloseDev($hash);
+
+ RemoveInternalTimer($hash);
+
+ return undef;
+}
+
+sub
+EseraOneWire_Delete($$)
+{
+ my ($hash, $name) = @_;
+ #delete all dev-spec temp-files
+ unlink($attr{global}{modpath}. "/FHEM/FhemUtils/$name.tmp");
+ return undef;
+}
+
+sub
+EseraOneWire_Ready($)
+{
+ my ($hash) = @_;
+
+ # try to reopen the connection in case the connection is lost
+ return DevIo_OpenDev($hash, 1, "EseraOneWire_Init", "EseraOneWire_Callback");
+}
+
+sub
+EseraOneWire_Attr($$$$)
+{
+ my ($cmd, $name, $attrName, $attrValue) = @_;
+ # $cmd - "del" or "set"
+ # $name - device name
+ # $attrName/$attrValue
+ my $hash = $defs{$name};
+
+ if ($attrName eq "pollTime")
+ {
+ if ($cmd eq "set")
+ {
+ if (not ($attrValue =~ m/^[0-9]+$/))
+ {
+ my $message = "illegal value for pollTime, not an integer number";
+ Log3 $name, 3, "EseraOneWire ($name) - ".$message;
+ return $message;
+ }
+
+ if (($attrValue < 1) or ($attrValue > 240))
+ {
+ my $message = "illegal value for pollTime, out of range";
+ Log3 $name, 3, "EseraOneWire ($name) - ".$message;
+ return $message;
+ }
+
+ if ($attrValue >= AttrVal($name, "dataTime", $hash->{DEFAULT_DATATIME}))
+ {
+ my $message = "illegal value for pollTime, compared to dataTime";
+ Log3 $name, 3, "EseraOneWire ($name) - ".$message;
+ return $message;
+ }
+
+ Log3 $name, 3, "EseraOneWire ($name) - attribute pollTime = $attrValue, re-initializing the controller";
+ InternalTimer(gettimeofday()+1, "EseraOneWire_baseSettings", $hash);
+ }
+ if ($cmd eq "del")
+ {
+ Log3 $name, 3, "EseraOneWire ($name) - attribute pollTime deleted, re-initializing the controller";
+ InternalTimer(gettimeofday()+1, "EseraOneWire_baseSettings", $hash);
+ }
+ }
+
+ if ($attrName eq "dataTime")
+ {
+ if ($cmd eq "set")
+ {
+ if (not ($attrValue =~ m/^[0-9]+$/))
+ {
+ my $message = "illegal value for dataTime, not an integer number";
+ Log3 $name, 3, "EseraOneWire ($name) - ".$message;
+ return $message;
+ }
+ if (($attrValue < 10) or ($attrValue > 240))
+ {
+ my $message = "illegal value for dataTime, out of range";
+ Log3 $name, 3, "EseraOneWire ($name) - ".$message;
+ return $message;
+ }
+ if ($attrValue <= AttrVal($name, "pollTime", $hash->{DEFAULT_POLLTIME}))
+ {
+ my $message = "illegal value for dataTime, compared to pollTime";
+ Log3 $name, 3, "EseraOneWire ($name) - ".$message;
+ return $message;
+ }
+
+ Log3 $name, 3, "EseraOneWire ($name) - attribute dataTime = $attrValue, re-initializing the controller";
+
+ InternalTimer(gettimeofday()+1, "EseraOneWire_baseSettings", $hash);
+ }
+ if ($cmd eq "del")
+ {
+ Log3 $name, 3, "EseraOneWire ($name) - attribute dataTime deleted, re-initializing the controller";
+ InternalTimer(gettimeofday()+1, "EseraOneWire_baseSettings", $hash);
+ }
+ }
+
+ return undef;
+}
+
+sub
+EseraOneWire_Init($)
+{
+ my ($hash) = @_;
+ EseraOneWire_SetStatus($hash, "connected");
+ EseraOneWire_baseSettings($hash);
+ return undef;
+}
+
+sub
+EseraOneWire_Callback($$)
+{
+ my ($hash, $error) = @_;
+ my $name = $hash->{NAME};
+
+ if ($error)
+ {
+ EseraOneWire_SetStatus($hash, "disconnected");
+ Log3 $name, 1, "EseraOneWire ($name) - error while connecting: >>".$error."<<";
+ }
+
+ return undef;
+}
+
+sub
+EseraOneWire_initTimeoutHandler($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ Log3 $name, 3, "EseraOneWire ($name) - info: initialization timeout detected, trying again";
+
+ EseraOneWire_baseSettings($hash);
+}
+
+################################################################################
+# controller info, settings and status
+################################################################################
+
+sub
+EseraOneWire_baseSettings($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ if (not DevIo_IsOpen($hash))
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - initialization aborted due to missing connection";
+ return undef;
+ }
+
+ EseraOneWire_SetStatus($hash, "initializing");
+ $hash->{".READ_PENDING"} = 0;
+
+ # start a timeout counter for initialization
+ RemoveInternalTimer($hash, "EseraOneWire_initTimeoutHandler");
+ InternalTimer(gettimeofday()+60, "EseraOneWire_initTimeoutHandler", $hash);
+
+ # clear task list before reset
+ undef $hash->{TASK_LIST} unless (!defined $hash->{TASK_LIST});
+
+ # send reset command (posted)
+ EseraOneWire_taskListAddPostedWrite($hash, "set,sys,rst,1", 5.0);
+
+ # Sending this request as a dummy, because the first access seems to get an 1_ERR always, followed
+ # by the correct response. Wait time of 3s between "set,sys,rst,1" and "set,sys,dataprint,1" does not help.
+ EseraOneWire_taskListAddSync($hash, "set,sys,dataprint,1", "1_DATAPRINT", \&EseraOneWire_query_response_handler);
+
+ # commands below here are expected to receive a "good" response
+
+ # ensure factory default settings
+ # This command does not clear the list of devices, only the settings. If you want to clear the list
+ # as well use "set,sys,facreset,1" with expected response "1_FACRESET|1".
+ EseraOneWire_taskListAddSimple($hash, "set,sys,loaddefault,1", "1_LOADDEFAULT", \&EseraOneWire_query_response_handler);
+
+ # wait some time, 0.2s works -> adding some safety buffer
+ EseraOneWire_taskListAddPostedWrite($hash, "", 0.4);
+
+ # events must contain the 1-wire ID, not the ESERA ID
+ EseraOneWire_taskListAddSimple($hash, "set,owb,owdid,1", "1_OWDID", \&EseraOneWire_query_response_handler);
+
+ # get iButton events for connect and disconnect
+ EseraOneWire_taskListAddSimple($hash, "set,key,data,2", "1_DATA", \&EseraOneWire_query_response_handler);
+
+ # Due to a bug in FW version 12027 there is no response to this command if the license is not available.
+ # Therefore, do a posted write only.
+ # EseraOneWire_taskListAddSimple($hash, "set,key,fast,1", "1_FAST", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddPostedWrite($hash, "set,key,fast,1", 1.0);
+
+ # load list of devices so that iButton devices which are stored in the controller are handled quickly
+ EseraOneWire_taskListAddSimple($hash, "set,owb,load", "1_LOAD", \&EseraOneWire_query_response_handler);
+
+ # more explicit default settings...
+ EseraOneWire_taskListAddSimple($hash, "set,sys,datatime,10", "1_DATATIME", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "set,sys,kalsend,1", "1_KALSEND", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "set,sys,kalsendtime,".$hash->{KAL_PERIOD}, "1_KALSENDTIME", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "set,sys,kalrec,0", "1_KALREC", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "set,owb,search,2", "1_SEARCH", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "set,owb,searchtime,30", "1_SEARCHTIME", \&EseraOneWire_query_response_handler);
+
+ # settings from attributes
+ my $dataTime = AttrVal($name, "dataTime", $hash->{DEFAULT_DATATIME});
+ EseraOneWire_taskListAddSimple($hash, "set,sys,datatime,$dataTime", "1_DATATIME", \&EseraOneWire_query_response_handler);
+ my $pollTime = AttrVal($name, "pollTime", $hash->{DEFAULT_POLLTIME});
+ EseraOneWire_taskListAddSimple($hash, "set,owb,polltime,$pollTime", "1_POLLTIME", \&EseraOneWire_query_response_handler);
+
+ # wait some time to give the controller time to detect the devices
+ EseraOneWire_taskListAddPostedWrite($hash, "", 4);
+
+ # read settings, ... from the controller and store the info in the hash
+ EseraOneWire_refreshControllerInfo($hash);
+
+ # wait some more time before readings can be forwarded to clients
+ EseraOneWire_taskListAddPostedWrite($hash, "", 2);
+
+ # This must be the last one.
+ EseraOneWire_taskListAddSimple($hash, "get,sys,run", "1_RUN", \&EseraOneWire_init_complete_handler);
+}
+
+sub
+EseraOneWire_removeDeviceTypeFromList($$)
+{
+ my ($hash, $oneWireId) = @_;
+ my $name = $hash->{NAME};
+
+ if (defined $hash->{DEVICE_TYPES})
+ {
+ # list is not empty; get it and delete entry
+ my $deviceTypesRef = $hash->{DEVICE_TYPES};
+ my %deviceTypes = %$deviceTypesRef;
+ if (defined $deviceTypes{$oneWireId})
+ {
+ delete($deviceTypes{$oneWireId});
+ }
+ $hash->{DEVICE_TYPES} = \%deviceTypes;
+ }
+}
+
+# incremental update of device list
+sub
+EseraOneWire_updateDeviceList($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ # do nothing if a refresh is already in process
+ return undef if ((defined $hash->{".READ_PENDING"}) && ($hash->{".READ_PENDING"} == 1));
+
+ Log3 $name, 4, "EseraOneWire ($name) - info: reading device list from controller";
+
+ # do not clear old device information
+
+ # Enqueue a query to retrieve the device list.
+ # The LST query gets multiple responses. We read all devices, not just the currently active ones,
+ # so that iButton devices which are known to the controller can be handled.
+ # Read the list with a posted write. The wait time has to chosen so that all LST responses are received
+ # before the next command is sent. LST responses are handled generically in the EseraOneWire_Read().
+ EseraOneWire_taskListAddPostedWrite($hash, "get,owb,listall", 2);
+
+ # This must be the last one.
+ EseraOneWire_taskListAddSimple($hash, "get,sys,run", "1_RUN", \&EseraOneWire_read_complete_handler);
+ $hash->{".READ_PENDING"} = 1;
+
+ return undef;
+}
+
+sub
+EseraOneWire_refreshControllerInfo($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ # do nothing if a refresh is already in process
+ return undef if ((defined $hash->{".READ_PENDING"}) && ($hash->{".READ_PENDING"} == 1));
+
+ # clear old information
+ undef $hash->{ESERA_IDS} unless (!defined $hash->{ESERA_IDS});
+ undef $hash->{DEVICE_TYPES} unless (!defined $hash->{DEVICE_TYPES});
+
+ # queue queries to retrieve updated information
+
+ # The LST query gets multiple responses. We read all devices, not just the currently active ones,
+ # so that iButton devices which are known to the controller can be handled.
+ # Read the list with a posted write. The wait time has to chosen so that all LST responses are received
+ # before the next command is sent. LST responses are handled generically in the EseraOneWire_Read().
+ EseraOneWire_taskListAddPostedWrite($hash, "get,owb,listall", 2);
+
+ EseraOneWire_taskListAddSimple($hash, "get,sys,fw", "1_FW", \&EseraOneWire_fw_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,sys,hw", "1_HW", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,sys,serial", "1_SERIAL", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,sys,id", "1_ID", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,sys,dom", "1_DOM", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,sys,run", "1_RUN", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,sys,contno", "1_CONTNO", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,sys,kalrec", "1_KALREC", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,sys,kalrectime", "1_KALRECTIME", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,sys,dataprint", "1_DATAPRINT", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,sys,datatime", "1_DATATIME", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,sys,kalsend", "1_KALSEND", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,sys,kalsendtime", "1_KALSENDTIME", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,owb,owdid", "1_OWDID", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,owb,owdidformat", "1_OWDIDFORMAT", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,owb,search", "1_SEARCH", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,owb,searchtime", "1_SEARCHTIME", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,owb,polltime", "1_POLLTIME", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,owd,ds2408inv", "1_DS2408INV", \&EseraOneWire_query_response_handler);
+
+ EseraOneWire_taskListAddSimple($hash, "get,key,data", "1_DATA", \&EseraOneWire_query_response_handler);
+ # Due to a bug in FW version 12027 this command does not get a response. Skip it for now.
+ # EseraOneWire_taskListAddSimple($hash, "get,key,fast", "1_FAST", \&EseraOneWire_query_response_handler);
+ EseraOneWire_taskListAddSimple($hash, "get,sys,liz,2", "1_LIZ", \&EseraOneWire_LIZ2_handler);
+
+ # TODO This does not work. The command is documented like this but it causes an
+ # ERR response. What does the last parameter mean anyway? Ask Esera.
+ # 2018.09.23 15:36:04 1: EseraOneWire (owc) - COMM sending: get,sys,owdid,0
+ # 2018.09.23 15:36:05 1: EseraOneWire (owc) - COMM Read: 1_INF|18:17:21
+ # 2018.09.23 15:36:05 1: EseraOneWire (owc) - COMM Read: 1_ERR|3
+ # 2018.09.23 15:36:05 1: EseraOneWire (owc) - COMM error response received, expected: 1_OWDID
+ # 2018.09.23 15:36:05 1: EseraOneWire (owc) - error response, command: get,sys,owdid,0 , response: 1_ERR|3 , ignoring the response
+ #EseraOneWire_taskListAddSimple($hash, "get,sys,owdid,0", "1_OWDID", \&EseraOneWire_query_response_handler);
+
+ # TODO This does not work as documented. It returns an ERR message. Ask Esera.
+ # 2018.09.23 15:36:05 1: EseraOneWire (owc) - COMM sending: get,sys,echo
+ # 2018.09.23 15:36:05 1: EseraOneWire (owc) - COMM Read: 1_INF|18:17:21
+ # 2018.09.23 15:36:05 1: EseraOneWire (owc) - COMM Read: 1_ERR|3
+ # 2018.09.23 15:36:05 1: EseraOneWire (owc) - COMM error response received, expected: 1_ECHO
+ #2018.09.23 15:36:05 1: EseraOneWire (owc) - error response, command: get,sys,echo , response: 1_ERR|3 , ignoring the response
+ #EseraOneWire_taskListAddSimple($hash, "get,sys,echo", "1_ECHO", \&EseraOneWire_query_response_handler);
+
+ # This must be the last one.
+ EseraOneWire_taskListAddSimple($hash, "get,sys,run", "1_RUN", \&EseraOneWire_read_complete_handler);
+ $hash->{".READ_PENDING"} = 1;
+
+ return undef;
+}
+
+sub
+EseraOneWire_refreshStatus($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ # get access to list of active devices
+ my $eseraIdsRef = $hash->{ESERA_IDS};
+ if (!defined $eseraIdsRef)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - no devices known";
+ return undef;
+ }
+
+ # clear old information
+ undef $hash->{DEVICE_STATUS} unless (!defined $hash->{DEVICE_STATUS});
+ undef $hash->{DEVICE_ERRORS} unless (!defined $hash->{DEVICE_ERRORS});
+
+ # iterate over known devices
+ my %eseraIds = %$eseraIdsRef;
+ my @keys = keys %eseraIds;
+ foreach (@keys)
+ {
+ my $eseraId = $eseraIds{$_};
+
+ # query the status
+ EseraOneWire_taskListAdd($hash, "get,owd,status,".$eseraId,
+ "1_OWD_", \&EseraOneWire_DEVICE_STATUS_handler, "ERR", \&EseraOneWire_error_handler, \&EseraOneWire_unexpected_handler, 0);
+
+ # sample response: "1_ERROWD2|0"
+ EseraOneWire_taskListAddSimple($hash, "get,owb,errowd,".$eseraId,
+ "1_ERROWD", \&EseraOneWire_ERROWD_handler);
+ }
+}
+
+sub
+EseraOneWire_eseraIdToOneWireId($$)
+{
+ my ($hash, $eseraId) = @_;
+ my $name = $hash->{NAME};
+
+ my $eseraIdsRef = $hash->{ESERA_IDS};
+ if (defined $eseraIdsRef)
+ {
+ my %eseraIds = %$eseraIdsRef;
+ my @keys = keys %eseraIds;
+ foreach (@keys)
+ {
+ if ($eseraIds{$_} eq $eseraId)
+ {
+ return $_;
+ }
+ }
+ }
+ return undef;
+}
+
+sub
+EseraOneWire_oneWireIdToEseraId($$)
+{
+ my ($hash, $oneWireId) = @_;
+ my $name = $hash->{NAME};
+ my $eseraIdsRef = $hash->{ESERA_IDS};
+ if (defined $eseraIdsRef)
+ {
+ my %eseraIds = %$eseraIdsRef;
+ my $eseraId = $eseraIds{$oneWireId};
+ if (defined $eseraId)
+ {
+ return $eseraId;
+ }
+ else
+ {
+ Log3 $name, 4, "EseraOneWire ($name) - EseraOneWire_oneWireIdToEseraId, not found, ".$oneWireId;
+ }
+ }
+ else
+ {
+ Log3 $name, 4, "EseraOneWire ($name) - EseraOneWire_oneWireIdToEseraId, hash does not exist";
+ }
+ return undef;
+}
+
+################################################################################
+# Set command
+################################################################################
+
+sub
+EseraOneWire_Set($$)
+{
+ my ($hash, @parameters) = @_;
+ my $name = $parameters[0];
+ my $what = lc($parameters[1]);
+
+ if ($what eq "raw")
+ {
+ if (scalar(@parameters) != 3)
+ {
+ my $message = "error: unexpected number of parameters (".scalar(@parameters).") to raw";
+ Log3 $name, 1, "EseraOneWire ($name) - ".$message;
+ return $message;
+ }
+ my $rawCommand = lc($parameters[2]);
+ my $message = "command to ESERA controller: ".$rawCommand;
+ Log3 $name, 4, "EseraOneWire ($name) - ".$message;
+
+ $hash->{RAW_COMMAND} = $rawCommand;
+ $hash->{RAW_RESPONSE} = ".";
+
+ EseraOneWire_taskListAdd($hash, $rawCommand,
+ "1_", \&EseraOneWire_raw_command_handler, "ZZZ", \&EseraOneWire_error_handler,
+ \&EseraOneWire_unexpected_handler, 0.5);
+
+ return undef;
+ }
+ elsif ($what eq "refresh")
+ {
+ EseraOneWire_refreshStatus($hash);
+ EseraOneWire_refreshControllerInfo($hash);
+ }
+ elsif ($what eq "clearlist")
+ {
+ EseraOneWire_taskListAddSimple($hash, "set,owb,delmem", "1_DELMEM", \&EseraOneWire_query_response_handler);
+ }
+ elsif ($what eq "savelist")
+ {
+ EseraOneWire_taskListAddSimple($hash, "set,owb,save", "1_SAVE", \&EseraOneWire_query_response_handler);
+ }
+ elsif ($what eq "reset")
+ {
+ if (scalar(@parameters) != 3)
+ {
+ my $message = "error: unexpected number of parameters (".scalar(@parameters).") to reset";
+ Log3 $name, 1, "EseraOneWire ($name) - ".$message;
+ return $message;
+ }
+ my $resetSpecifier = lc($parameters[2]);
+ my $message;
+ if ($resetSpecifier eq "tasks")
+ {
+ undef $hash->{TASK_LIST} unless (!defined $hash->{TASK_LIST});
+ }
+ elsif ($resetSpecifier eq "controller")
+ {
+ EseraOneWire_baseSettings($hash);
+ $message = "reset controller";
+ Log3 $name, 4, "EseraOneWire ($name) - ".$message;
+ }
+ else
+ {
+ $message = "error: unknown reset specifier ".$resetSpecifier;
+ Log3 $name, 1, "EseraOneWire ($name) - ".$message;
+ }
+ return undef;
+ }
+ elsif ($what eq "close")
+ {
+ # close connection if open
+ if (DevIo_IsOpen($hash))
+ {
+ DevIo_CloseDev($hash);
+
+ RemoveInternalTimer($hash, "EseraOneWire_initTimeoutHandler");
+ RemoveInternalTimer($hash, "EseraOneWire_KalTimeoutHandler");
+
+ EseraOneWire_SetStatus($hash, "disconnected");
+ if (DevIo_IsOpen($hash))
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - set close: failed";
+ }
+ else
+ {
+ Log3 $name, 3, "EseraOneWire ($name) - set close: success";
+ }
+ }
+ else
+ {
+ Log3 $name, 3, "EseraOneWire ($name) - set close: is not open";
+ }
+ }
+ elsif ($what eq "open")
+ {
+ Log3 $name, 3, "EseraOneWire ($name) - set open";
+ # open connection with custom init and error callback function (non-blocking connection establishment)
+ DevIo_OpenDev($hash, 0, "EseraOneWire_Init", "EseraOneWire_Callback") if(!DevIo_IsOpen($hash));
+ }
+ elsif ($what eq "?")
+ {
+ my $message = "unknown argument $what, choose one of clearlist:noArg savelist:noArg refresh:noArg reset:controller,tasks raw close:noArg open:noArg";
+ return $message;
+ }
+ else
+ {
+ my $message = "unknown argument $what, choose one of clearlist savelist reset refresh raw close open";
+ Log3 $name, 1, "EseraOneWire ($name) - ".$message;
+ return $message;
+ }
+ return undef;
+}
+
+################################################################################
+# Get command
+################################################################################
+
+sub
+EseraOneWire_getDevices($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ my $eseraIdsRef = $hash->{ESERA_IDS};
+ my $deviceTypesRef = $hash->{DEVICE_TYPES};
+ my $list = "";
+ my $isEmpty = 1;
+ if (defined $eseraIdsRef && defined $deviceTypesRef)
+ {
+ my %eseraIds = %$eseraIdsRef;
+ my %deviceTypes = %$deviceTypesRef;
+ my @keys = keys %eseraIds;
+ my $updateNeeded = 0;
+ foreach (@keys)
+ {
+ $isEmpty = 0;
+ if (defined $deviceTypes{$_})
+ {
+ $list .= $_.",".$eseraIds{$_}.",".$deviceTypes{$_}.";\n";
+ }
+ else
+ {
+ $list .= $_.",".$eseraIds{$_}.",pending;\n";
+ $updateNeeded = 1;
+ }
+ }
+ if ($updateNeeded)
+ {
+ EseraOneWire_updateDeviceList($hash);
+ }
+ }
+
+ if ($isEmpty)
+ {
+ $list .= "device list is empty\n";
+ }
+
+ return $list;
+}
+
+sub
+EseraOneWire_getInfo($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+ my $list = "";
+
+ my $fwVersion = $hash->{".FW"};
+ my $hwVersion = $hash->{".HW"};
+ my $serialNumber = $hash->{".SERIAL"};
+ my $productNumber = $hash->{".ID"};
+ my $dom = $hash->{".DOM"};
+
+ $fwVersion = "UNKNOWN" if (!defined $fwVersion);
+ $hwVersion = "UNKNOWN" if (!defined $hwVersion);
+ $serialNumber = "UNKNOWN" if (!defined $serialNumber);
+ $productNumber = "UNKNOWN" if (!defined $productNumber);
+ $dom = "UNKNOWN" if (!defined $dom);
+
+ $list .= "FW version: ".$fwVersion."\n";
+ $list .= "HW version: ".$hwVersion."\n";
+ $list .= "serial number: ".$serialNumber."\n";
+ $list .= "ESERA product number: ".$productNumber."\n";
+ $list .= "date of manufacturing: ".$dom."\n";
+ return $list;
+}
+
+sub
+EseraOneWire_getSettings($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+ my $list = "";
+
+ my $run = $hash->{".RUN"};
+ my $contno = $hash->{".CONTNO"};
+ my $kalrec = $hash->{".KALREC"};
+ my $kalrectime = $hash->{".KALRECTIME"};
+ my $kalsend = $hash->{".KALSEND"};
+ my $kalsendtime = $hash->{".KALSENDTIME"};
+ my $dataprint = $hash->{".DATAPRINT"};
+ my $datatime = $hash->{".DATATIME"};
+ my $useOwdid = $hash->{".OWDID"};
+ my $owdidFormat = $hash->{".OWDIDFORMAT"};
+ my $searchMode = $hash->{".SEARCH"};
+ my $searchTime = $hash->{".SEARCHTIME"};
+ my $polltime = $hash->{".POLLTIME"};
+ my $ds2408inv = $hash->{".DS2408INV"};
+ my $data = $hash->{".DATA"};
+ my $fast = $hash->{".FAST"};
+
+ my $license = $hash->{".LIZ2"};
+
+ $run = "UNKNOWN" if (!defined $run);
+ $contno = "UNKNOWN" if (!defined $contno);
+ $kalrec = "UNKNOWN" if (!defined $kalrec);
+ $kalrectime = "UNKNOWN" if (!defined $kalrectime);
+ $kalsend = "UNKNOWN" if (!defined $kalsend);
+ $kalsendtime = "UNKNOWN" if (!defined $kalsendtime);
+ $dataprint = "UNKNOWN" if (!defined $dataprint);
+ $datatime = "UNKNOWN" if (!defined $datatime);
+ $useOwdid = "UNKNOWN" if (!defined $useOwdid);
+ $owdidFormat = "UNKNOWN" if (!defined $owdidFormat);
+ $searchMode = "UNKNOWN" if (!defined $searchMode);
+ $searchTime = "UNKNOWN" if (!defined $searchTime);
+ $polltime = "UNKNOWN" if (!defined $polltime);
+ $ds2408inv = "UNKNOWN" if (!defined $ds2408inv);
+ $data = "UNKNOWN" if (!defined $data);
+ $fast = "UNKNOWN" if (!defined $fast);
+ $license = "UNKNOWN" if (!defined $license);
+
+ $list .= "RUN: ".$run." (1=controller sending to FHEM)\n";
+ $list .= "CONTNO: ".$contno." (ESERA controller number)\n";
+ $list .= "KALREC: ".$kalrec." (1=keep-alive signal expected by controller)\n";
+ $list .= "KALRECTIME: ".$kalrectime." (time period in seconds used for expected keep-alive messages)\n";
+ $list .= "KALSEND: ".$kalsend." (1=controller sending keep-alive messages)\n";
+ $list .= "KALSENDTIME: ".$kalsendtime." (time period in seconds used for sending keep-alive messages)\n";
+ $list .= "DATAPRINT: ".$dataprint." (0=list responses are returned in a single line)\n";
+ $list .= "DATATIME: ".$datatime." (time period used for data delivery to FHEM)\n";
+ $list .= "OWDID: ".$useOwdid." (1=return readings with 1-wire ID instead of Esera ID)\n";
+ $list .= "OWDIDFORMAT: ".$owdidFormat." (selects format of 1-wire ID)\n";
+ $list .= "SEARCH: ".$searchMode." (2=cyclic search for new devices)\n";
+ $list .= "SEARCHTIME: ".$searchTime." (time period in seconds used to search for new devices)\n";
+ $list .= "POLLTIME: ".$polltime." (time period in seconds used with periodic reads from devices)\n";
+ $list .= "DS2408INV: ".$ds2408inv." (1=invert readings from DS2408 devices)\n";
+ $list .= "DATA: ".$data." (2=get events for iButton connect and disconnect)\n";
+ $list .= "FAST: ".$fast." (1=fast polling is enabled, requires special license)\n";
+ $list .= "LIZ2: ".$license." (1=iButton fast mode license found)\n";
+
+ return $list;
+}
+
+sub
+EseraOneWire_getStatus($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ # get access to stored device information
+ my $eseraIdsRef = $hash->{ESERA_IDS};
+ my $deviceTypesRef = $hash->{DEVICE_TYPES};
+ if (!defined $eseraIdsRef || !defined $deviceTypesRef)
+ {
+ EseraOneWire_refreshControllerInfo($hash);
+ my $message = "No device information found. Refreshing list. Please try again.";
+ Log3 $name, 3, "EseraOneWire ($name) - ".$message;
+ return $message;
+ }
+
+ # get access to stored device status
+ my $deviceStatusRef = $hash->{DEVICE_STATUS};
+ if (!defined $deviceStatusRef)
+ {
+ EseraOneWire_refreshStatus($hash);
+ my $message = "No status information found. Triggering refresh. Please try again.";
+ Log3 $name, 3, "EseraOneWire ($name) - ".$message;
+ return $message;
+ }
+
+ # get access to stored device errors
+ my $deviceErrorsRef = $hash->{DEVICE_ERRORS};
+ if (!defined $deviceErrorsRef)
+ {
+ EseraOneWire_refreshStatus($hash);
+ my $message = "No error information found. Triggering refresh. Please try again.";
+ Log3 $name, 3, "EseraOneWire ($name) - ".$message;
+ return $message;
+ }
+
+ # iterate over detected devices
+ my $list = "";
+ my %eseraIds = %$eseraIdsRef;
+ my %deviceTypes = %$deviceTypesRef;
+ my %deviceStatus = %$deviceStatusRef;
+ my %deviceErrors = %$deviceErrorsRef;
+ my @keys = keys %eseraIds;
+ foreach (@keys)
+ {
+ if (defined $deviceTypes{$_})
+ {
+ $list .= $_.",".$eseraIds{$_}.",".$deviceTypes{$_}.",";
+ }
+ else
+ {
+ $list .= $_.",".$eseraIds{$_}.",pending,";
+ EseraOneWire_updateDeviceList($hash);
+ }
+
+ my $status = $deviceStatus{$_};
+ if (!defined $status)
+ {
+ $list .= "unknown";
+ }
+ else
+ {
+ $list .= $status;
+ }
+ $list .= ",".$deviceErrors{$_};
+ $list .= ";\n";
+ }
+
+ # trigger next read of status info from controller
+ EseraOneWire_refreshStatus($hash);
+
+ return $list;
+}
+
+sub
+EseraOneWire_Get($$)
+{
+ my ($hash, @parameters) = @_;
+ my $name = $hash->{NAME};
+ my $what = lc($parameters[1]);
+
+ if ($what eq "devices")
+ {
+ return EseraOneWire_getDevices($hash);
+ }
+ elsif ($what eq "info")
+ {
+ return EseraOneWire_getInfo($hash);
+ }
+ elsif ($what eq "settings")
+ {
+ return EseraOneWire_getSettings($hash);
+ }
+ elsif ($what eq "status")
+ {
+ return EseraOneWire_getStatus($hash);
+ }
+ elsif ($what eq "?")
+ {
+ my $message = "unknown argument $what, choose one of devices:noArg info:noArg settings:noArg status:noArg";
+ return $message;
+ }
+
+ my $message = "unknown argument $what, choose one of devices:noArg info:noArg settings:noArg status:noArg";
+ Log3 $name, 3, "EseraOneWire ($name) - ".$message;
+ return $message;
+}
+
+################################################################################
+# Read command - process data coming from the device
+################################################################################
+
+sub
+EseraOneWire_Read($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ my $data = DevIo_SimpleRead($hash);
+ return if (!defined($data)); # connection lost
+
+ my $buffer = $hash->{PARTIAL};
+
+ $data =~ s/\r//g;
+
+ # concat received data to $buffer
+ $buffer .= $data;
+
+ while ($buffer =~ m/\n/)
+ {
+ my $msg;
+
+ # extract the complete message ($msg), everything else is assigned to $buffer
+ ($msg, $buffer) = split("\n", $buffer, 2);
+
+ # remove trailing whitespaces
+ chomp $msg;
+
+ Log3 $name, 4, "EseraOneWire ($name) - COMM Read: $msg";
+
+ my $ascii = $msg;
+
+ if ((length $ascii) == 0)
+ {
+ Log3 $name, 4, "EseraOneWire ($name) - COMM - error: empty response detected";
+ next;
+ }
+
+ my @fields = split(/\|/, $ascii);
+ my $type = $fields[0];
+
+ if ($type =~ m/1_EVT/)
+ {
+ Log3 $name, 5, "EseraOneWire ($name) - EVT received";
+ }
+ elsif ($type =~ m/1_CSE/)
+ {
+ Log3 $name, 5, "EseraOneWire ($name) - CSE received";
+ }
+ elsif ($type =~ m/1_CSI/)
+ {
+ Log3 $name, 5, "EseraOneWire ($name) - CSI received";
+ }
+ elsif ($type =~ m/1_INF/)
+ {
+ Log3 $name, 5, "EseraOneWire ($name) - COMM - INF received";
+ }
+ elsif ($type =~ m/1_KAL$/)
+ {
+ EseraOneWire_processKalMessage($hash);
+ }
+ elsif ($type =~ m/^1_LST/)
+ {
+ Log3 $name, 4, "EseraOneWire ($name) - COMM - 1_LST received";
+ }
+ elsif ($type eq "LST")
+ {
+ EseraOneWire_LIST_handler($hash, $ascii);
+ }
+ elsif ($type =~ m/1_OWD(\d+)_(\d+)/)
+ {
+ if (EseraOneWire_IsReadyToAcceptHardwareReadings($hash))
+ {
+ Log3 $name, 4, "EseraOneWire ($name) - readings data from controller has incorrect format (1_OWD*_* instead of using the 1-wire ID)";
+ }
+ }
+ elsif ($ascii =~ m/^1_([0-9A-F]+)_(\d+)/)
+ {
+ if (EseraOneWire_IsReadyToAcceptHardwareReadings($hash))
+ {
+ EseraOneWire_parseReading($hash, $ascii);
+ }
+ else
+ {
+ Log3 $name, 5, "EseraOneWire ($name) - readings ignored because controller is not initialized (1)";
+ }
+ }
+ elsif ($ascii =~ m/^1_([0-9A-F]+)\|/)
+ {
+ if (EseraOneWire_IsReadyToAcceptHardwareReadings($hash))
+ {
+ EseraOneWire_parseReading($hash, $ascii);
+ }
+ else
+ {
+ Log3 $name, 5, "EseraOneWire ($name) - readings ignored because controller is not initialized (2)";
+ }
+ }
+ elsif ($ascii =~ m/^1_OWD([0-9A-F]+)\|/)
+ {
+ if (EseraOneWire_IsReadyToAcceptHardwareReadings($hash))
+ {
+ EseraOneWire_parseReading($hash, $ascii);
+ }
+ else
+ {
+ Log3 $name, 5, "EseraOneWire ($name) - readings ignored because controller is not initialized (2)";
+ }
+ }
+ elsif ($ascii =~ m/^1_SYS(\d+)/)
+ {
+ if (EseraOneWire_IsReadyToAcceptHardwareReadings($hash))
+ {
+ EseraOneWire_parseReading($hash, $ascii);
+ }
+ else
+ {
+ Log3 $name, 5, "EseraOneWire ($name) - readings ignored because controller is not initialized (2)";
+ }
+ }
+ else
+ {
+ # everything else is considered a response to latest command
+ EseraOneWire_taskListHandleResponse($hash, $ascii);
+ }
+ }
+
+ $hash->{PARTIAL} = $buffer;
+}
+
+sub
+EseraOneWire_processListEntry($$)
+{
+ my ($hash, $fieldsRef) = @_;
+ my $name = $hash->{NAME};
+ my @fields = @$fieldsRef;
+ if (scalar(@fields) != 5)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error: unexpected number of response fields for list entry";
+ }
+
+ # extract OWD number from response
+ my $longEseraOwd = $fields[1];
+ my $eseraOwd;
+ if ($longEseraOwd =~ m/1_OWD(\d+)$/)
+ {
+ $eseraOwd = $1;
+ }
+ else
+ {
+ $eseraOwd = "_".$longEseraOwd."_";
+ Log3 $name, 1, "EseraOneWire ($name) - error: unexpected OWD number format in LST response: ".$longEseraOwd;
+ }
+
+ # extract 1-wire ID
+ my $oneWireId = $fields[2];
+
+ my $status = $fields[3];
+
+ # extract device type
+ my $oneWireDeviceType = $fields[4];
+
+ Log3 $name, 5, "EseraOneWire ($name) - new list entry: Esera OWD ".$eseraOwd." 1-wire ID ".$oneWireId." device type ".$oneWireDeviceType;
+
+ if ($oneWireId =~ m/^FFFFFFFFFFFFFFFF/)
+ {
+ Log3 $name, 5, "EseraOneWire ($name) - list entry ".$eseraOwd." is not in use";
+ return;
+ }
+
+ # store ESERA ID in hash
+ if (defined $hash->{ESERA_IDS})
+ {
+ # list is not empty; get it and add new entry
+ my $eseraIdsRef = $hash->{ESERA_IDS};
+ my %eseraIds = %$eseraIdsRef;
+ $eseraIds{$oneWireId} = $eseraOwd;
+ $hash->{ESERA_IDS} = \%eseraIds;
+ }
+ else
+ {
+ # list is empty; create new list and store in hash
+ my %eseraIds;
+ $eseraIds{$oneWireId} = $eseraOwd;
+ $hash->{ESERA_IDS} = \%eseraIds;
+ }
+
+ # store device type in hash
+ if (defined $hash->{DEVICE_TYPES})
+ {
+ # list is not empty; get it and add new entry
+ my $deviceTypesRef = $hash->{DEVICE_TYPES};
+ my %deviceTypes = %$deviceTypesRef;
+ $deviceTypes{$oneWireId} = $oneWireDeviceType;
+ $hash->{DEVICE_TYPES} = \%deviceTypes;
+ }
+ else
+ {
+ # list is empty; create new list and store in hash
+ my %deviceTypes;
+ $deviceTypes{$oneWireId} = $oneWireDeviceType;
+ $hash->{DEVICE_TYPES} = \%deviceTypes;
+ }
+}
+
+sub
+EseraOneWire_LIST_handler($$)
+{
+ my ($hash, $response) = @_;
+ my $name = $hash->{NAME};
+
+ my @fields = split(/\|/, $response);
+ my $numberOfFields = scalar(@fields);
+ if ($numberOfFields > 0)
+ {
+ my $type = $fields[0];
+ if ($type eq "LST")
+ {
+ EseraOneWire_processListEntry($hash, \@fields);
+ }
+ else
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - unexpected content in LST response: ".$response;
+ }
+ }
+}
+
+################################################################################
+# Write command - process requests from client modules
+################################################################################
+
+sub
+EseraOneWire_Write($$)
+{
+ my ($hash, $msg) = @_;
+ my $name = $hash->{NAME};
+
+ Log3 $name, 4, "EseraOneWire ($name) - received command from client: ".$msg;
+
+ my @fields = split(/;/, $msg);
+ if ($fields[0] eq "set")
+ {
+ # set;;
+ if (scalar(@fields) != 3)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - syntax error in command from client: ".$msg;
+ return undef;
+ }
+ EseraOneWire_taskListAddPostedWrite($hash, $fields[2], 0.1);
+ }
+ elsif ($fields[0] eq "assign")
+ {
+ # assign;; -> send command to controller, used to apply an ESERA product number
+ # This uses a specific call response handler which sends an error message to the client if something goes wrong.
+ if (scalar(@fields) != 3)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - syntax error in command from client: ".$msg;
+ return undef;
+ }
+
+ my $oneWireId = $fields[1];
+ my $eseraId = EseraOneWire_oneWireIdToEseraId($hash, $oneWireId);
+ if (!defined $eseraId)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error looking up eseraId for assign request, oneWireId $oneWireId";
+ return undef;
+ }
+
+ my $productNumber = $fields[2];
+ my $command = "set,owd,art,".$eseraId.",".$productNumber;
+
+ Log3 $name, 4, "EseraOneWire ($name) - info: assigning product number $productNumber to $oneWireId";
+
+ EseraOneWire_taskListAdd($hash, $command, "1_ART", \&EseraOneWire_clientArtHandler,
+ "1_ERR", \&EseraOneWire_clientArtErrorHandler, \&EseraOneWire_unexpected_handler, undef);
+
+ # trigger an update of the device type in the device list
+ EseraOneWire_removeDeviceTypeFromList($hash, $oneWireId);
+ EseraOneWire_updateDeviceList($hash)
+ }
+ elsif ($fields[0] eq "status")
+ {
+ # status; -> retrieve status for given device from controller and return it via Dispatch
+ # $deviceType.";".$owId.";".$readingId.";".$value (and readingId==STATISTIC)
+ # This requires a response handler that calls Dispatch after receiving and processing the response from the
+ # controller.
+ if (scalar(@fields) != 4)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - syntax error in command from client: ".$msg;
+ return undef;
+ }
+ Log3 $name, 4, "EseraOneWire ($name) - status command from client not supported yet";
+ }
+ else
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - syntax error in command from client: ".$msg;
+ return undef;
+ }
+}
+
+################################################################################
+# Readings
+################################################################################
+
+sub
+EseraOneWire_forwardReadingToClient($$$$$)
+{
+ my ($hash, $fieldsCount, $owId, $readingId, $value) = @_;
+ my $name = $hash->{NAME};
+ if ($fieldsCount != 2)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error: unexpected number of fields for reading";
+ }
+
+ my $eseraIdsRef = $hash->{ESERA_IDS};
+ my $deviceTypesRef = $hash->{DEVICE_TYPES};
+ if (!defined $eseraIdsRef || !defined $deviceTypesRef)
+ {
+ EseraOneWire_updateDeviceList($hash);
+ return undef;
+ }
+
+ my %eseraIds = %$eseraIdsRef;
+ my %deviceTypes = %$deviceTypesRef;
+ if (!defined $eseraIds{$owId} || !defined $deviceTypes{$owId})
+ {
+ EseraOneWire_updateDeviceList($hash);
+ return undef;
+ }
+
+ my $deviceType = $deviceTypes{$owId};
+ my $eseraId = $eseraIds{$owId};
+ my $message = $deviceType."_".$owId."_".$eseraId."_".$readingId."_".$value;
+ Log3 $name, 4, "EseraOneWire ($name) - forwarding reading to clients: ".$message;
+ Dispatch($hash, $message, "");
+ EseraOneWire_SetStatus($hash, "ready");
+
+ return undef;
+}
+
+sub
+EseraOneWire_parseSysReadings($$$$$)
+{
+ my ($hash, $fieldsCount, $owId, $readingId, $value) = @_;
+ my $name = $hash->{NAME};
+ if ($fieldsCount != 2)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error: unexpected number of fields for reading";
+ }
+
+ my $deviceType = $owId;
+ my $eseraId = $owId;
+ my $message = $deviceType."_".$owId."_".$eseraId."_".$readingId."_".$value;
+ Log3 $name, 4, "EseraOneWire ($name) - passing reading to clients: ".$message;
+ Dispatch($hash, $message, "");
+
+ return undef;
+}
+
+# examples of reading messages from controller:
+#2019.02.13 23:43:17 4: EseraOneWire (owc2) - COMM Read: 1_SYS1_1|0
+#2019.02.13 23:43:17 4: EseraOneWire (owc2) - COMM Read: 1_SYS1_2|00000000
+#2019.02.13 23:43:18 4: EseraOneWire (owc2) - COMM Read: 1_SYS2_1|0
+#2019.02.13 23:43:18 4: EseraOneWire (owc2) - COMM Read: 1_SYS2_2|00000000
+#2019.02.13 23:43:18 4: EseraOneWire (owc2) - COMM Read: 1_SYS3|0
+#2019.02.13 23:43:18 4: EseraOneWire (owc2) - COMM Read: 1_0400000763496828|2262
+#2019.02.13 23:43:18 4: EseraOneWire (owc2) - COMM Read: 1_E6000001C3748301|1
+#2019.02.13 23:43:20 4: EseraOneWire (owc2) - COMM Read: 1_OWD2|0
+#2019.02.13 23:43:22 4: EseraOneWire (owc2) - COMM Read: 1_OWD2|1
+sub
+EseraOneWire_parseReading($$)
+{
+ my ($hash, $line) = @_;
+ my $name = $hash->{NAME};
+
+ Log3 $name, 4, "EseraOneWire ($name) - line: ".$line;
+
+ my @fields = split(/\|/, $line);
+ my $numberOfFields = scalar(@fields);
+ if ($numberOfFields == 2)
+ {
+ if ($fields[0] =~ m/^1_([0-9A-F]+)_([0-9]+)$/)
+ {
+ my $owId = $1;
+ my $readingId = $2;
+ my $value = $fields[1];
+
+ EseraOneWire_forwardReadingToClient($hash, 2, $owId, $readingId, $value);
+ }
+ elsif ($fields[0] =~ m/^1_([0-9A-F]+)$/)
+ {
+ my $owId = $1;
+ my $readingId = 0;
+ my $value = $fields[1];
+
+ EseraOneWire_forwardReadingToClient($hash, 2, $owId, $readingId, $value);
+ }
+ elsif ($fields[0] =~ m/1_([0-9:]+)_(\d+)/) # TODO still needed after controller update?
+ {
+ my $owId = $2;
+ my $readingId = 0;
+ my $value = $fields[1];
+
+ EseraOneWire_forwardReadingToClient($hash, 2, $owId, $readingId, $value);
+ }
+ elsif ($fields[0] =~ m/^1_SYS(\d)_(\d)/)
+ {
+ my $owId = "SYS".$1;
+ my $readingId = $2;
+ my $value = $fields[1];
+
+ EseraOneWire_parseSysReadings($hash, 2, $owId, $readingId, $value);
+ }
+ elsif ($fields[0] =~ m/^1_SYS3/)
+ {
+ my $owId = "SYS3";
+ my $readingId = 0;
+ my $value = $fields[1];
+
+ EseraOneWire_parseSysReadings($hash, 2, $owId, $readingId, $value);
+ }
+ elsif ($fields[0] =~ m/^1_OWD(\d)/)
+ {
+ my $eseraId = $1;
+ my $owId = EseraOneWire_eseraIdToOneWireId($hash, $eseraId);
+ if (not $owId)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - unexpected readings format (1) $fields[0]";
+ return undef;
+ }
+ my $readingId = 0;
+ my $value = $fields[1];
+ EseraOneWire_forwardReadingToClient($hash, 2, $owId, $readingId, $value);
+ }
+ else
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - unexpected readings format (2) $fields[0]";
+ }
+ }
+ else
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - unexpected readings format (3)";
+ }
+ return undef;
+}
+
+################################################################################
+# response handlers
+################################################################################
+
+sub
+EseraOneWire_query_response_handler($$)
+{
+ my ($hash, $response) = @_;
+ my $name = $hash->{NAME};
+ $response =~ s/;//g;
+ my @fields = split(/\|/, $response);
+
+ if (scalar(@fields) != 2)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error: unexpected number of response fields for generic query response: ".$response;
+ return;
+ }
+
+ if ($fields[0] =~ m/1_([A-Z0-9]+)$/)
+ {
+ my $key = ".".$1;
+ my $value = $fields[1];
+ $hash->{$key} = $value;
+ }
+}
+
+sub
+EseraOneWire_RST_handler($$)
+{
+ my ($hash, $response) = @_;
+ my $name = $hash->{NAME};
+ $response =~ s/;//g;
+ my @fields = split(/\|/, $response);
+ if (scalar(@fields) != 2)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error: unexpected number of response fields for RST";
+ }
+ # ignore the response value
+}
+
+sub
+EseraOneWire_fw_handler($$)
+{
+ my ($hash, $response) = @_;
+ my $name = $hash->{NAME};
+ $response =~ s/;//g;
+ my @fields = split(/\|/, $response);
+
+ if (scalar(@fields) != 2)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error: unexpected number of response fields for fw query response: ".$response;
+ return;
+ }
+
+ if ($fields[0] =~ m/1_([A-Z0-9]+)$/)
+ {
+ my $key = ".".$1;
+ my $value = $fields[1];
+ $hash->{$key} = $value;
+
+ if ($value != $hash->{RECOMMENDED_FW})
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - warning: actual FW version is $value, recommended FW version is ".$hash->{RECOMMENDED_FW};
+ }
+ }
+}
+
+
+sub
+EseraOneWire_LIZ2_handler($$)
+{
+ my ($hash, $response) = @_;
+ my $name = $hash->{NAME};
+ $response =~ s/;//g;
+ my @fields = split(/\|/, $response);
+ if (scalar(@fields) != 3)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error: unexpected number of response fields for LIZ,2 query";
+ }
+ if ($fields[1] != 2)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error: unexpected LIZ response";
+ }
+ $hash->{".LIZ2"} = $fields[2];
+}
+
+sub
+EseraOneWire_error_handler($$$)
+{
+ my ($hash, $command, $response) = @_;
+ my $name = $hash->{NAME};
+ $response =~ s/;//g;
+
+ Log3 $name, 1, "EseraOneWire ($name) - error response, command: ".$command." , response: ".$response." , ignoring the response";
+}
+
+sub
+EseraOneWire_unexpected_handler($$$)
+{
+ my ($hash, $command, $response) = @_;
+ my $name = $hash->{NAME};
+ $response =~ s/;//g;
+
+ Log3 $name, 1, "EseraOneWire ($name) - error: unexpected response, command: ".$command.", response: ".$response.", ignoring the response";
+}
+
+sub
+EseraOneWire_raw_command_handler($$$)
+{
+ my ($hash, $response) = @_;
+ my $name = $hash->{NAME};
+ Log3 $name, 3, "EseraOneWire ($name) - response to raw command: ".$response;
+ $hash->{RAW_RESPONSE} = $response;
+}
+
+sub
+EseraOneWire_init_complete_handler($$$)
+{
+ my ($hash, $command, $response) = @_;
+ my $name = $hash->{NAME};
+
+ # stop the timeout counter
+ RemoveInternalTimer($hash, "EseraOneWire_initTimeoutHandler");
+
+ EseraOneWire_SetStatus($hash, "initialized");
+}
+
+sub
+EseraOneWire_read_complete_handler($$$)
+{
+ my ($hash, $command, $response) = @_;
+ $hash->{".READ_PENDING"} = 0;
+}
+
+sub
+EseraOneWire_DEVICE_STATUS_handler($$)
+{
+ my ($hash, $response) = @_;
+ my $name = $hash->{NAME};
+ $response =~ s/;//g;
+ my @fields = split(/\|/, $response);
+ if (scalar(@fields) != 2)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error: unexpected number of response fields for STATUS query";
+ }
+ my $owdId;
+ my $status;
+ if ($fields[0] =~ m/1_OWD_(\d+)/)
+ {
+ my $eseraId = $1;
+ my $oneWireId = EseraOneWire_eseraIdToOneWireId($hash, $eseraId);
+
+ if (!defined $oneWireId)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error: could not map ESERA ID to 1-wire ID: ".$eseraId;
+ return;
+ }
+
+ $status = $fields[1];
+ my $statusText;
+ if (!defined $status)
+ {
+ $statusText = "unknown";
+ }
+ elsif ($status == 0)
+ {
+ $statusText = "ok";
+ }
+ else
+ {
+ # TODO question to Esera: What is the meaning of status values 1..3?
+
+ $statusText = "error (".$status .")";
+ }
+
+ if (defined $hash->{DEVICE_STATUS})
+ {
+ # list is not empty; get it and add new entry
+ my $deviceStatusRef = $hash->{DEVICE_STATUS};
+ my %deviceStatus = %$deviceStatusRef;
+ $deviceStatus{$oneWireId} = $statusText;
+ $hash->{DEVICE_STATUS} = \%deviceStatus;
+ }
+ else
+ {
+ # list is empty; create new list and store in hash
+ my %deviceStatus;
+ $deviceStatus{$oneWireId} = $statusText;
+ $hash->{DEVICE_STATUS} = \%deviceStatus;
+ }
+ }
+ else
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error: could not extract OWD ID";
+ }
+}
+
+sub
+EseraOneWire_ERROWD_handler($$)
+{
+ my ($hash, $response) = @_;
+ my $name = $hash->{NAME};
+ $response =~ s/;//g;
+ my @fields = split(/\|/, $response);
+ if (scalar(@fields) != 2)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error: unexpected number of response fields for ERROWD query";
+ }
+ my $owdId;
+ my $status;
+ if ($fields[0] =~ m/1_ERROWD(\d+)/)
+ {
+ my $eseraId = $1;
+ my $oneWireId = EseraOneWire_eseraIdToOneWireId($hash, $eseraId);
+
+ if (!defined $oneWireId)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error: could not map ESERA ID to 1-wire ID: ".$eseraId;
+ return;
+ }
+
+ my $errorCount = $fields[1];
+
+ if (defined $hash->{DEVICE_ERRORS})
+ {
+ # list is not empty; get it and add new entry
+ my $deviceErrorsRef = $hash->{DEVICE_ERRORS};
+ my %deviceErrors = %$deviceErrorsRef;
+ $deviceErrors{$oneWireId} = $errorCount;
+ $hash->{DEVICE_ERRORS} = \%deviceErrors;
+ }
+ else
+ {
+ # list is empty; create new list and store in hash
+ my %deviceErrors;
+ $deviceErrors{$oneWireId} = $errorCount;
+ $hash->{DEVICE_ERRORS} = \%deviceErrors;
+ }
+ }
+ else
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error: could not extract OWD ID";
+ }
+}
+
+sub
+EseraOneWire_clientArtHandler($$)
+{
+ my ($hash, $response) = @_;
+ my $name = $hash->{NAME};
+ $response =~ s/;//g;
+ my @fields = split(/\|/, $response);
+
+ if (scalar(@fields) != 3)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error: unexpected number of response fields for ART response ".$response;
+ return;
+ }
+}
+
+sub
+EseraOneWire_clientArtErrorHandler($$)
+{
+ my ($hash, $response) = @_;
+ my $name = $hash->{NAME};
+ $response =~ s/;//g;
+ my @fields = split(/\|/, $response);
+
+ if (scalar(@fields) != 2)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error: unexpected number of response fields for ART error response";
+ return;
+ }
+}
+
+################################################################################
+# task list
+################################################################################
+#
+# The purpose of the task list is to provide queue for requests to the controller,
+# to map responses to requests and to call handlers for incoming data from the
+# controller. It is used to avoid blocking accesses to the controller.
+#
+# different kinds of tasks:
+#
+# 1) normal task: Command with expected response and error handling; waitTime is ignored
+# purpose: normal get/set commands that provide one single deterministic response
+#
+# 2) posted write: Command is sent. It is removed from the queue after waitTime.
+# Responses coming in during waitTime are recognized, but no handler function is called.
+# A zero waitTime is not allowed.
+#
+# 3) wait: Start a timer. Until the timer expires just do basic generic processing but
+# since no handler function is specified, do no special handling.
+# This is a special case of posted write, with no command and no handlers specified, but with
+# a specified waitTime.
+#
+# 4) sync: Send a command and expect a given response. Ignore responses which are not expected.
+# waitTime is undefined.
+
+sub
+EseraOneWire_taskListStartNext($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ # retrieve the task list
+ my $taskListRef = $hash->{TASK_LIST};
+ if (!defined $taskListRef)
+ {
+ Log3 $name, 4, "EseraOneWire ($name) - task list does not exist";
+ return undef;
+ }
+ my @taskList = @$taskListRef;
+ if ((scalar @taskList) < 1)
+ {
+ Log3 $name, 4, "EseraOneWire ($name) - task list is empty";
+ return undef;
+ }
+
+ # get the next task
+ my $taskRef = $taskList[0];
+ my @task = @$taskRef;
+ my $command = $task[0];
+ my $expectedPattern = $task[1];
+ my $waitTime = $task[6];
+
+ # if a command is specified: send it to the controller
+ if ($command)
+ {
+ Log3 $name, 4, "EseraOneWire ($name) - COMM sending: $command";
+ DevIo_SimpleWrite($hash, $command."\r", 2);
+ }
+
+ # if the current command does not have an expected response: start
+ # a timer to start the next command later
+ if (!defined $expectedPattern)
+ {
+ Log3 $name, 5, "EseraOneWire ($name) - starting timer";
+ InternalTimer(gettimeofday()+$waitTime, "EseraOneWire_popPostedWriteFromTaskList", $hash);
+ }
+
+ return undef;
+}
+
+sub
+EseraOneWire_taskListGetCurrentCommand($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ # retrieve the task list
+ my $taskListRef = $hash->{TASK_LIST};
+ if (!defined $taskListRef)
+ {
+ Log3 $name, 4, "EseraOneWire ($name) - task list does not exist";
+ return undef;
+ }
+ my @taskList = @$taskListRef;
+ if ((scalar @taskList) < 1)
+ {
+ Log3 $name, 4, "EseraOneWire ($name) - task list is empty";
+ return undef;
+ }
+
+ # get the task
+ my $taskRef = $taskList[0];
+ my @task = @$taskRef;
+ my $command = $task[0];
+
+ return $command;
+}
+
+sub
+EseraOneWire_removeCurrentTaskFromList($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ # retrieve the task list
+ my $taskListRef = $hash->{TASK_LIST};
+ if (!defined $taskListRef)
+ {
+ Log3 $name, 4, "EseraOneWire ($name) - task list does not exist";
+ return undef;
+ }
+ my @taskList = @$taskListRef;
+ if ((scalar @taskList) < 1)
+ {
+ Log3 $name, 4, "EseraOneWire ($name) - task list is empty";
+ return undef;
+ }
+
+ # remove current task from list
+ my $length = scalar @taskList;
+ Log3 $name, 4, "EseraOneWire ($name) - old length of task list: ".$length;
+ shift @taskList; # pop
+ $length = scalar @taskList;
+ Log3 $name, 4, "EseraOneWire ($name) - new length of task list: ".$length;
+ $hash->{TASK_LIST} = \@taskList;
+
+ return undef;
+}
+
+# Add a new task to the list. A task is the container for a request (read or write) to the controller,
+# plus information about what to do with the response. When adding a new task to an empty
+# task list that task will be started.
+sub
+EseraOneWire_taskListAdd($$$$$$$$)
+{
+ my ($hash, $command, $expectedPattern, $handler, $errorPattern, $errorHandler, $unexpectedHandler, $waitTime) = @_;
+ my $name = $hash->{NAME};
+
+ # combine task info in one array
+ my @task = ($command, $expectedPattern, $handler, $errorPattern, $errorHandler, $unexpectedHandler, $waitTime);
+
+ # retrieve the task list
+ my $taskListRef = $hash->{TASK_LIST};
+ my @taskList;
+ if (defined $taskListRef)
+ {
+ @taskList = @$taskListRef;
+ }
+
+ # add task to tasklist
+ push @taskList, \@task;
+ $hash->{TASK_LIST} = \@taskList;
+
+ # trigger the new task if it is the first one in the list
+ my $length = scalar @taskList;
+ Log3 $name, 4, "EseraOneWire ($name) - new length of task list: ".$length;
+ if ($length == 1)
+ {
+ EseraOneWire_taskListStartNext($hash);
+ }
+
+ return undef;
+}
+
+# Add a new task to the list. Use this function for accesses that do not cause a
+# response.
+sub
+EseraOneWire_taskListAddPostedWrite($$$)
+{
+ my ($hash, $command, $waitTime) = @_;
+ my $name = $hash->{NAME};
+
+ EseraOneWire_taskListAdd($hash, $command, undef, undef, undef, undef, undef, $waitTime);
+
+ return undef;
+}
+
+sub
+EseraOneWire_taskListAddSync($$$$)
+{
+ my ($hash, $command, $expectedPattern, $handler) = @_;
+ my $name = $hash->{NAME};
+
+ EseraOneWire_taskListAdd($hash, $command, $expectedPattern, $handler, undef, undef, undef, undef);
+
+ return undef;
+}
+
+# Add a new task to the list. Use this function if no special handing for
+# error responses and unexpected response is required..
+sub
+EseraOneWire_taskListAddSimple($$$$)
+{
+ my ($hash, $command, $expectedPattern, $handler) = @_;
+ my $name = $hash->{NAME};
+
+ EseraOneWire_taskListAdd($hash, $command, $expectedPattern, $handler,
+ "1_ERR", \&EseraOneWire_error_handler, \&EseraOneWire_unexpected_handler, undef);
+
+ return undef;
+}
+
+sub
+EseraOneWire_popPostedWriteFromTaskList($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ Log3 $name, 5, "EseraOneWire ($name) - EseraOneWire_popPostedWriteFromTaskList";
+
+ EseraOneWire_removeCurrentTaskFromList($hash);
+ EseraOneWire_taskListStartNext($hash);
+
+ return undef;
+}
+
+sub
+EseraOneWire_taskListHandleResponse($$)
+{
+ my ($hash, $response) = @_;
+ my $name = $hash->{NAME};
+
+ # retrieve the task list
+ my $taskListRef = $hash->{TASK_LIST};
+ if (!defined $taskListRef)
+ {
+ Log3 $name, 4, "EseraOneWire ($name) - task list does not exist";
+ return undef;
+ }
+ my @taskList = @$taskListRef;
+ if ((scalar @taskList) < 1)
+ {
+ Log3 $name, 4, "EseraOneWire ($name) - task list is empty";
+ return undef;
+ }
+
+ my $taskRef = $taskList[0];
+ my @task = @$taskRef;
+ my $expectedPattern = $task[1];
+ my $errorPattern = $task[3];
+ my $command = $task[0];
+
+ if (!defined $expectedPattern)
+ {
+ # response during waitTime after posted write
+ Log3 $name, 5, "EseraOneWire ($name) - receiving response while waiting after a posted write, ignoring: $response";
+ }
+ elsif ((defined $expectedPattern) and (!defined $errorPattern))
+ {
+ # sync task
+ if ($response =~ m/$expectedPattern/)
+ {
+ # sync found, call registered function
+ Log3 $name, 4, "EseraOneWire ($name) - COMM expected response for sync task received: ".$response;
+ my $functionRef = $task[2];
+ &$functionRef($hash, $response);
+
+ EseraOneWire_removeCurrentTaskFromList($hash);
+ EseraOneWire_taskListStartNext($hash);
+ }
+ else
+ {
+ # continue waiting for sync
+ Log3 $name, 4, "EseraOneWire ($name) - COMM ignoring response while waiting for sync: ".$response;
+ }
+ }
+ else
+ {
+ # normal task with expected response
+ if ($response =~ m/$expectedPattern/)
+ {
+ Log3 $name, 4, "EseraOneWire ($name) - COMM expected response received: ".$response;
+ # call registered handler
+ my $functionRef = $task[2];
+ &$functionRef($hash, $response);
+ }
+ elsif ($response =~ m/$errorPattern/)
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error response received, expected: $expectedPattern";
+ # call registered error handler
+ my $functionRef = $task[4];
+ &$functionRef($hash, $command, $response);
+ }
+ else
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - unexpected response received, expected: $expectedPattern";
+ # call registered handler for unepected responses
+ my $functionRef = $task[5];
+ &$functionRef($hash, $command, $response);
+ }
+ EseraOneWire_removeCurrentTaskFromList($hash);
+ EseraOneWire_taskListStartNext($hash);
+ }
+
+ return undef;
+}
+
+################################################################################
+# status tracking and reporting
+################################################################################
+
+sub
+EseraOneWire_SetStatus($$)
+{
+ my ($hash, $status) = @_;
+ my $name = $hash->{NAME};
+
+ # check whether given status is an expected value
+ if (!(($status eq "defined") ||
+ ($status eq "connected") ||
+ ($status eq "disconnected") ||
+ ($status eq "initializing") ||
+ ($status eq "initialized") ||
+ ($status eq "ready")))
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - Error: SetStatus with unexpected status: $status";
+ return;
+ }
+
+ # report status if it has changed
+ if ((!defined $hash->{STATUS}) || (!($hash->{STATUS} eq $status)))
+ {
+ Log3 $name, 5, "EseraOneWire ($name) - $status";
+ $hash->{STATUS} = $status;
+
+ # Generate event consistently with events generated by DevIo.
+ # Some other events are generated by DevIo.
+ if ($status eq "ready")
+ {
+ DoTrigger($name, "READY");
+ }
+
+ # Update "state" reading consistently with DevIo. Some other
+ # values are set by DevIo. Do not create event for the "state"
+ # reading.
+ if (($status eq "initializing") ||
+ ($status eq "initialized") ||
+ ($status eq "ready"))
+ {
+ setReadingsVal($hash, "state", $status, TimeNow());
+ }
+ }
+}
+
+sub
+EseraOneWire_GetStatus($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ if (defined $hash->{STATUS})
+ {
+ return $hash->{STATUS};
+ }
+ else
+ {
+ return "unknown";
+ }
+}
+
+sub
+EseraOneWire_IsReadyToAcceptHardwareReadings($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ my $status = EseraOneWire_GetStatus($hash);
+
+ if (($status eq "initialized") || ($status eq "ready"))
+ {
+ return 1;
+ }
+
+ return undef;
+}
+
+sub
+EseraOneWire_KalTimeoutHandler($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+ Log3 $name, 5, "EseraOneWire ($name) - EseraOneWire_KalTimeoutHandler";
+ if (EseraOneWire_IsReadyToAcceptHardwareReadings($hash))
+ {
+ Log3 $name, 1, "EseraOneWire ($name) - error: KAL timeout";
+ # We expect that the controller is up and running, but we do not
+ # receive KAL messages. -> Try to reconnect.
+ DevIo_CloseDev($hash) if(DevIo_IsOpen($hash));
+ DevIo_OpenDev($hash, 0, "EseraOneWire_Init", "EseraOneWire_Callback");
+ }
+}
+
+sub
+EseraOneWire_processKalMessage($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ Log3 $name, 5, "EseraOneWire ($name) - COMM - 1_KAL message received";
+
+ RemoveInternalTimer($hash, "EseraOneWire_KalTimeoutHandler");
+ InternalTimer(gettimeofday()+int($hash->{KAL_PERIOD}*2.5), "EseraOneWire_KalTimeoutHandler", $hash);
+}
+
+################################################################################
+1;
+################################################################################
+
+=pod
+=item device
+=item summary Provides an interface between FHEM and Esera 1-wire controllers.
+=item summary_DE Stellt eine Verbindung zu Esera 1-wire Controllern zur Verfuegung.
+=begin html
+
+
+EseraOneWire
+
+ This module provides an interface to Esera 1-wire controllers.
+ The module works in conjunction with 66_Esera* modules which support
+ various 1-wire devices. See these modules for more information
+ about supported 1-wire devices. This module supports autocreate.
+
+ The module is tested with "Controller 1" and "Controller 2" with
+ TCP/IP interface, implementing the Esera ASCII protocol as described
+ in the Esera Controller Programmierhandbuch. The module
+ supports serial connections as well, for controllers with serial/USB
+ interface. It is tested with EseraStation 200.
+
+ Tested with Esera controller firmware version 12027.
+
+
+ Define
+
+ define <name> EseraOneWire <ip-address>
+ Example: define myEseraOneWireController EseraOneWire 192.168.0.15
+ Example: define myEseraOneWireController EseraOneWire /dev/ttyUSB0
+
+
+
+ Set
+
+ -
+
set <name> clearlist
+ Clear the list of devices which is persistently stored in the controller.
+
+ -
+
set <name> savelist
+ Save the current list of devices persistently in the controller. This is only
+ required when using iButton devices. See module EseraIButton for more information.
+
+ -
+
set <name> reset controller
+ Sends a reset command to the controller.
+
+ -
+
set <name> reset tasks
+ The module internally queues tasks. Tasks are requests to the controller
+ which need to executed one after the other. For debug purposes only the
+ module provides a way to clear the queue.
+
+ -
+
set <name> refresh
+ Triggers a read of updated information from the controller, including
+ settings, list of devices and controller info. In normal operation it
+ is not needed to call thi when the 1-wire devices are added. The module
+ triggers a re-read automatically if readings are received from unknown
+ devices.
+
+ -
+
set <name> raw <command>
+ Debug only: Allows the user to send an arbitrary low level command to the
+ controller. The command is a command as described in the Esera Controller
+ Programmierhandbuch, without spaces, with commas as seperators, and without
+ trailing new line character.
+ The raw command and the received response are stored as internals
+ RAW_COMMAND and RAW_RESPONSE.
+
+ Examples:
+
+ set myEseraOneWireController raw get,sys,datatime
+ set myEseraOneWire raw set,sys,datatime,0
+
+
+ -
+
set <name> close
+ Close the TCP/IP connection to the controller.
+
+ -
+
set <name> open
+ Open the TCP/IP connection to the controller.
+
+
+
+
+ Get
+
+ -
+
get <name> info
+ Prints various information about the controller like part number,
+ serial number, date of manufacturing.
+
+ -
+
get <name> settings
+ Returns various controller settings for debug purposes. New information
+ is retrieved when set refresh or get settings is called. New data
+ can then be printed with the following call of get settings. It is
+ implemented like this because it is a simple way to avoid blocking reads.
+ For more information regarding the individual settings please refer to the
+ Esera Controller Programmierhandbuch which can be downloaded from
+ www.esera.de .
+
+ -
+
get <name> devices
+ Returns the list of currently connected 1-wire devices.
+ The format of each list entry is
+ <oneWireId>,<eseraId>,<deviceType>;
+
+ -
+
get <name> status
+ Reports currently known status of all connected 1-wire devices.
+ The format of each list entry is
+ <oneWireId>,<eseraId>,<deviceType>,<status>,<errors>;
+ New status information is retrieved from the known devices when
+ get status is called. New data can then be printed with
+ the following call of get status. It is implemented like
+ this because it is a simple way to avoid blocking reads.
+
+
+
+
+ Readings
+
+ - state – possible values are:
+
+ opened
– connection to controller established (from DevIo)
+ disconnected
– disconnected from controller (from DevIo)
+ failed
– connection to controller failed (from DevIo)
+ initializing
– controller initialization in progress
+ initialized
– controller initialization done
+ ready
– fully functional
+
+
+
+
+
+ Attributes
+
+ - pollTime – POLLTIME setting of the controller, see controller manual, default: 5
+ - dataTime – DATATIME setting of the controller, see controller manual, default: 10
+
+
+
+ Events
+
+ CONNECTED
– from DevIo
+ DISCONNECTED
– from DevIo
+ FAILED
– from DevIo
+ READY
– fully functional
+
+
+
+=end html
+=cut
diff --git a/fhem/FHEM/66_EseraTemp.pm b/fhem/FHEM/66_EseraTemp.pm
new file mode 100644
index 000000000..eafe831e6
--- /dev/null
+++ b/fhem/FHEM/66_EseraTemp.pm
@@ -0,0 +1,260 @@
+################################################################################
+#
+# $Id$
+#
+# 66_EseraTemp.pm
+#
+# Copyright (C) 2018 pizmus
+#
+# This program 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 program. If not, see .
+#
+################################################################################
+#
+# This FHEM module supports a temperature sensor connected via
+# an Esera "1-wire Controller 1" with LAN interface and the 66_EseraOneWire
+# module.
+#
+################################################################################
+
+package main;
+
+use strict;
+use warnings;
+use SetExtensions;
+
+sub
+EseraTemp_Initialize($)
+{
+ my ($hash) = @_;
+ $hash->{Match} = "DS1820";
+ $hash->{DefFn} = "EseraTemp_Define";
+ $hash->{UndefFn} = "EseraTemp_Undef";
+ $hash->{ParseFn} = "EseraTemp_Parse";
+ $hash->{SetFn} = "EseraTemp_Set";
+ $hash->{GetFn} = "EseraTemp_Get";
+ $hash->{AttrFn} = "EseraTemp_Attr";
+ $hash->{AttrList} = "$readingFnAttributes";
+}
+
+sub
+EseraTemp_Define($$)
+{
+ my ($hash, $def) = @_;
+ my @a = split( "[ \t][ \t]*", $def);
+
+ return "Usage: define EseraTemp <1-wire-ID> " if(@a < 5);
+
+ my $devName = $a[0];
+ my $type = $a[1];
+ my $physicalDevice = $a[2];
+ my $oneWireId = $a[3];
+ my $deviceType = uc($a[4]);
+
+ $hash->{STATE} = 'Initialized';
+ $hash->{NAME} = $devName;
+ $hash->{TYPE} = $type;
+ $hash->{ONEWIREID} = $oneWireId;
+ $hash->{ESERAID} = undef; # We will get this from the first reading.
+ $hash->{DEVICE_TYPE} = $deviceType;
+
+ $modules{EseraTemp}{defptr}{$oneWireId} = $hash;
+
+ AssignIoPort($hash, $physicalDevice);
+
+ if (defined($hash->{IODev}->{NAME}))
+ {
+ Log3 $devName, 4, "$devName: I/O device is " . $hash->{IODev}->{NAME};
+ }
+ else
+ {
+ Log3 $devName, 1, "$devName: no I/O device";
+ }
+
+ return undef;
+}
+
+sub
+EseraTemp_Undef($$)
+{
+ my ($hash, $arg) = @_;
+ my $oneWireId = $hash->{ONEWIREID};
+
+ RemoveInternalTimer($hash);
+ delete( $modules{EseraTemp}{defptr}{$oneWireId} );
+
+ return undef;
+}
+
+sub
+EseraTemp_Get($@)
+{
+ return undef;
+}
+
+sub
+EseraTemp_Set($$)
+{
+ return undef;
+}
+
+sub
+EseraTemp_Parse($$)
+{
+ my ($ioHash, $msg) = @_;
+ my $ioName = $ioHash->{NAME};
+ my $buffer = $msg;
+
+ # expected message format: $deviceType."_".$oneWireId."_".$eseraId."_".$readingId."_".$value
+ my @fields = split(/_/, $buffer);
+ if (scalar(@fields) != 5)
+ {
+ return undef;
+ }
+ my $deviceType = uc($fields[0]);
+ my $oneWireId = $fields[1];
+ my $eseraId = $fields[2];
+ my $readingId = $fields[3];
+ my $value = $fields[4];
+
+ # search for logical device
+ my $rhash = undef;
+ foreach my $d (keys %defs) {
+ my $h = $defs{$d};
+ my $type = $h->{TYPE};
+
+ if($type eq "EseraTemp")
+ {
+ if (defined($h->{IODev}->{NAME}))
+ {
+ my $ioDev = $h->{IODev}->{NAME};
+ my $def = $h->{DEF};
+
+ # $def has the whole definition, extract the oneWireId (which is expected as 2nd parameter)
+ my @parts = split(/ /, $def);
+ my $oneWireIdFromDef = $parts[1];
+
+ if (($ioDev eq $ioName) && ($oneWireIdFromDef eq $oneWireId))
+ {
+ $rhash = $h;
+ last;
+ }
+ }
+ }
+ }
+
+ if($rhash) {
+ my $rname = $rhash->{NAME};
+ Log3 $rname, 4, "EseraTemp ($rname) - parse - device found: ".$rname;
+
+ # capture the Esera ID for later use
+ $rhash->{ESERAID} = $eseraId;
+
+ # consistency check of device type
+ if (!($rhash->{DEVICE_TYPE} eq uc($deviceType)))
+ {
+ Log3 $rname, 1, "EseraTemp ($rname) - unexpected device type ".$deviceType;
+ }
+
+ if ($readingId eq "ERROR")
+ {
+ Log3 $rname, 1, "EseraTemp ($rname) - error message from physical device: ".$value;
+ }
+ elsif ($readingId eq "STATISTIC")
+ {
+ Log3 $rname, 1, "EseraTemp ($rname) - statistics message not supported yet: ".$value;
+ }
+ else
+ {
+ my $nameOfReading = "temperature";
+ readingsSingleUpdate($rhash, $nameOfReading, $value / 100.0, 1);
+ }
+
+ my @list;
+ push(@list, $rname);
+ return @list;
+ }
+ elsif ($deviceType eq "DS1820")
+ {
+ return "UNDEFINED EseraTemp_".$ioName."_".$oneWireId." EseraTemp ".$ioName." ".$oneWireId." ".$deviceType;
+ }
+
+ return undef;
+}
+
+sub
+EseraTemp_Attr(@)
+{
+}
+
+1;
+
+=pod
+=item summary Represents a 1-wire temperature sensor.
+=item summary_DE Repraesentiert einen 1-wire Temperatursensor.
+=begin html
+
+
+EseraTemp
+
+
+ This module implements a 1-wire temperature sensor. It uses 66_EseraOneWire
+ as I/O device.
+
+
+
+ Define
+
+ define <name> EseraTemp <ioDevice> <oneWireId> <deviceType>
+ <oneWireId> specifies the 1-wire ID of the sensor. Use the "get devices"
+ query of EseraOneWire to get a list of 1-wire IDs, or simply rely on autocreate.
+ Supported values for deviceType:
+
+ Note: Esera 11131 temperature sensor is a DS1820.
+
+
+
+
+ Set
+
+
+
+
+ Get
+
+
+
+
+ Attributes
+
+
+
+
+ Readings
+
+ - temperature – temperature in degrees Celsius
+
+
+
+
+
+=end html
+=cut
diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt
index bb79dad53..082b44df5 100644
--- a/fhem/MAINTAINER.txt
+++ b/fhem/MAINTAINER.txt
@@ -314,6 +314,13 @@ FHEM/62_EMEM.pm rudolfkoenig SlowRF
FHEM/63_EMGZ.pm rudolfkoenig SlowRF
FHEM/64_ESA2000.pm stromer-12 SlowRF
FHEM/66_ECMD.pm neubert Sonstige Systeme
+FHEM/66_EseraAnalogInIout.pm pizmus 1Wire
+FHEM/66_EseraCount.pm pizmus 1Wire
+FHEM/66_EseraDigitalInOut.pm pizmus 1Wire
+FHEM/66_EseraIButton.pm pizmus 1Wire
+FHEM/66_EseraMulti.pm pizmus 1Wire
+FHEM/66_EseraOneWire.pm pizmus 1Wire
+FHEM/66_EseraTemp.pm pizmus 1Wire
FHEM/67_ECMDDevice.pm neubert Sonstige Systeme
FHEM/70_BOTVAC.pm vuffiraa Sonstige Systeme
FHEM/70_BRAVIA.pm vuffiraa Multimedia