################################################################################
#
# $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
# 66_EseraShutter
#
# 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:EseraShutter:EseraDimmer:";
$hash->{MatchList} = { "1:EseraDigitalInOut" => "^DS2408|^11220|^11233|^11228|^11229|^11216|^SYS1|^SYS2",
"2:EseraTemp" => "^DS1820",
"3:EseraMulti" => "^DS2438|^11112|^11121|^11132|^11133|^11134|^11135",
"4:EseraAnalogInOut" => "^SYS3|^DS2450|^11202|^11203|^11208|^11219",
"5:EseraIButton" => "^DS2401",
"6:EseraCount" => "^DS2423",
"7:EseraShutter" => "^11231|^11209",
"8:EseraDimmer" => "^11221|^11222"};
}
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} = 12029;
$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 (3)";
}
}
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 (4)";
}
}
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 12029.
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