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 +
      +
    • no get functionality
    • +
    +
    + + + 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 +
      +
    • no get functionality
    • +
    +
    + + + Get +
      +
    • no get functionality
    • +
    +
    + + + 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 movingAverageCount2samples,
      + 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 +
      +
    • no get functionality
    • +
    +
    + + + Attributes +
      +
    • no 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 +
      +
    • no set functionality
    • +
    +
    + + + Get +
      +
    • no get functionality
    • +
    +
    + + + Attributes +
      +
    • no 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 +
      +
    • no get functionality
    • +
    +
    + + + Get +
      +
    • no get functionality
    • +
    +
    + + + Attributes +
      +
    • no 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:
      +
        +
      • DS1820
      • +
      + Note: Esera 11131 temperature sensor is a DS1820. +
    +
    + + + Set +
      +
    • no set functionality
    • +
    +
    + + + Get +
      +
    • no get functionality
    • +
    +
    + + + Attributes +
      +
    • no 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