98_ComfoAir.pm: new version using package

git-svn-id: https://svn.fhem.de/fhem/trunk@24145 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
StefanStrobel
2021-04-04 10:26:29 +00:00
parent baef0b5469
commit ff4ce542cd

View File

@@ -1,10 +1,9 @@
##############################################
##########################################################################
# $Id$
#
# fhem Modul für ComfoAir Lüftungsanlagen von Zehnder mit
# serieller Schnittstelle (RS232) sowie dazu kompatible Anlagen wie
# Storkair WHR 930, Storkair 950
# Paul Santos 370 DC, Paul Santos 570 DC
# Storkair WHR 930, Storkair 950, Paul Santos 370 DC, Paul Santos 570 DC
# Wernig G90-380, Wernig G90-550
#
# Dieses Modul basiert auf der Protokollanalyse von SeeSolutions:
@@ -28,51 +27,88 @@
# along with fhem. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# Changelog:
#
# 2014-04-18 initial version
# 2014-05-17 added more protocol commands, changed logging settings
# 2014-05-25 added hide- attributes
# 2014-07-07 corrected handling of 0xD2 Message (protocol is different than documented)
# 2014-07-24 added max queue length checking and attribute
# 2016-01-25 Reading Namen mit Umlauten korrigiert
# 2016-04-06 Testmode Protokollbefehle hinzugefügt
# 2016-11-13 korrektur bei set / get / readanswer. Set liefert bei Erfolg undef statt Text
# 2017-02-12 Doku ergänzt
# 2017-05-09 Text-Kodierung für summary korrigiert
# 2020-05-07 fixed log level of debug massages in get function
# 2020-05-15 fix uppercase hex strings in %parseInfo, use DevIo
#
#
# Todo / Ideas:
#
package main;
package ComfoAir;
use strict;
use DevIo;
use warnings;
use Time::HiRes qw( time );
use GPUtils qw(:all);
use Time::HiRes qw(gettimeofday time);
use DevIo;
use FHEM::HTTPMOD::Utils qw(:all);
sub ComfoAir_Initialize($);
sub ComfoAir_Define($$);
sub ComfoAir_Undef($$);
sub ComfoAir_Set($@);
sub ComfoAir_Get($@);
sub ComfoAir_Read($);
sub ComfoAir_Ready($);
sub ComfoAir_ReadAnswer($$$);
sub ComfoAir_GetUpdate($$);
sub ComfoAir_Send($$$;$$);
sub ComfoAir_ParseFrames($);
sub ComfoAir_InterpretFrame($$);
sub ComfoAir_HandleSendQueue($);
sub ComfoAir_SendAck($);
sub ComfoAir_TimeoutSend($);
use Exporter ('import');
our @EXPORT_OK = qw();
our %EXPORT_TAGS = (all => [@EXPORT_OK]);
my $ComfoAir_Version = '1.52 - 15.5.2020';
BEGIN {
GP_Import( qw(
fhem
CommandAttr
CommandDeleteAttr
addToDevAttrList
AttrVal
ReadingsVal
ReadingsTimestamp
readingsSingleUpdate
readingsBeginUpdate
readingsBulkUpdate
readingsEndUpdate
InternalVal
makeReadingName
Log3
RemoveInternalTimer
InternalTimer
deviceEvents
EvalSpecials
AnalyzePerlCommand
CheckRegexp
IsDisabled
gettimeofday
FmtDateTime
GetTimeSpec
fhemTimeLocal
time_str2num
min
max
minNum
maxNum
abstime2rel
defInfo
trim
ltrim
rtrim
UntoggleDirect
UntoggleIndirect
IsInt
fhemNc
round
sortTopicNum
Svn_GetFile
WriteFile
DevIo_OpenDev
DevIo_SimpleWrite
DevIo_SimpleRead
DevIo_CloseDev
SetExtensions
HttpUtils_NonblockingGet
featurelevel
defs
modules
attr
init_done
));
GP_Export( qw(
Initialize
));
};
my $Module_Version = '2.01 - 31.3.2021';
# %parseInfo:
# replyCode => msgHashRef
@@ -244,23 +280,21 @@ my %getHash; # helper to reference the msgHash in parseInfo for each name
my %requestHash; # helper to reference each msgHash for each request Set
my %cmdHash; # helper to map from send cmd code to msgHash of Reply
my %ComfoAir_AddSets = (
my %AddSets = (
"SendRawData" => ""
);
#####################################
sub
ComfoAir_Initialize($)
{
my ($hash) = @_;
sub Initialize {
my $hash = shift;
$hash->{ReadFn} = "ComfoAir_Read";
$hash->{ReadyFn} = "ComfoAir_Ready";
$hash->{DefFn} = "ComfoAir_Define";
$hash->{UndefFn} = "ComfoAir_Undef";
$hash->{SetFn} = "ComfoAir_Set";
$hash->{GetFn} = "ComfoAir_Get";
$hash->{ReadFn} = \&ComfoAir::ReadFn;
$hash->{ReadyFn} = \&ComfoAir::ReadyFn;
$hash->{DefFn} = \&ComfoAir::DefineFn;
$hash->{UndefFn} = \&ComfoAir::UndefFn;
$hash->{SetFn} = \&ComfoAir::SetFn;
$hash->{GetFn} = \&ComfoAir::GetFn;
@setList = ();
@getList = ();
@@ -310,7 +344,8 @@ ComfoAir_Initialize($)
my $hl = $readingHashRef->{map}; # create hint list from map
$hl =~ s/([^ ,\$]+):([^ ,\$]+,?) ?/$2/g;
$readingHashRef->{setopt} = $reading . ":$hl";
} else {
}
else {
$readingHashRef->{setopt} = $reading; # keine besonderen Optionen, nur den Namen für setopt verwenden.
}
if (defined($readingHashRef->{hint})){ # hints explizit definiert? (überschreibt evt. schon abgeleitete hints)
@@ -328,96 +363,81 @@ ComfoAir_Initialize($)
"queueMax " .
#"minSendDelay " .
join (" ", @pollList) . " " . # Def der zyklisch abzufragenden Nachrichten
$readingFnAttributes;
$main::readingFnAttributes;
return;
}
#####################################
sub
ComfoAir_Define($$)
{
my ($hash, $def) = @_;
my @a = split("[ \t][ \t]*", $def);
sub DefineFn {
my $hash = shift; # reference to the Fhem device hash
my $def = shift; # definition string
my @a = split( /[ \t]+/, $def ); # the above string split at space or tab
my ($name, $ComfoAir, $dev, $interval) = @a;
return "wrong syntax: define <name> ComfoAir [devicename|none] [interval]"
if(@a < 3);
$hash->{BUSY} = 0;
$hash->{EXPECT} = "";
$hash->{ModuleVersion} = $ComfoAir_Version;
$hash->{EXPECT} = '';
$hash->{ModuleVersion} = $Module_Version;
if (!defined($interval)) {
$hash->{INTERVAL} = 0;
Log 1, "$name: interval is 0 or not defined - not sending requests - just listening!";
} else {
$hash->{INTERVAL} = $interval;
}
DevIo_CloseDev($hash);
if($dev eq "none") {
Log 1, "$name: device is none, commands will be echoed only";
return undef;
}
$hash->{DeviceName} = $dev;
my $ret = DevIo_OpenDev($hash, 0, 0);
InternalTimer(gettimeofday()+1, "ComfoAir_GetUpdate", $hash, 0) # erste Abfrage von Werten nach 1 Sekunde (zumindest in Queue stellen)
if ($hash->{INTERVAL});
return $ret;
if($dev ne "none") {
DevIo_OpenDev($hash, 0, 0);
}
if (!$interval) {
$hash->{INTERVAL} = 0;
Log3 $name, 3, "$name: interval is 0 or not specified - not sending requests - just listening!";
}
else {
$hash->{INTERVAL} = $interval;
InternalTimer(gettimeofday()+1, "GetUpdate", $hash, 0);
}
Log3 $name, 3, "$name: Defined with device $dev" . ($interval ? ", interval $interval" : '');
return;
}
#####################################
sub
ComfoAir_Undef($$)
{
sub UndefFn {
my ($hash, $arg) = @_;
my $name = $hash->{NAME};
DevIo_CloseDev($hash);
RemoveInternalTimer ("timeout:".$name);
RemoveInternalTimer ("queue:".$name);
RemoveInternalTimer ($hash);
return undef;
return;
}
#####################################
sub
ComfoAir_Get($@)
{
my ($hash, @a) = @_;
return "\"get ComfoAir\" needs at least one argument" if(@a < 2);
sub GetFn {
my @getValArr = @_; # rest is optional values
my $hash = shift @getValArr; # reference to device hash
my $name = shift @getValArr; # device name
my $getName = shift @getValArr; # get option name
my $getVal = join(' ', @getValArr); # optional value after get name
my $name = $hash->{NAME};
my $getName = $a[1];
if (defined($getHash{$getName})) {
# get Option für Reading aus parseInfo -> generische Verarbeitung
my $msgHash = $getHash{$getName}{msgHash}; # Hash für die Nachricht aus parseInfo
Log3 $name, 5, "$name: Request found in getHash created from parseInfo data";
if ($msgHash->{request}) {
ComfoAir_Send($hash, $msgHash->{request}, "", $msgHash->{replyCode}, 1);
my ($err, $result) = ComfoAir_ReadAnswer($hash, $getName, $msgHash->{replyCode});
return $err if ($err);
return $result;
} else {
return "Protocol doesn't provide a command to get $getName";
}
} else {
# undefiniertes Get
return "\"get ComfoAir\" needs at least one argument" if(!$getName);
if (!defined($getHash{$getName})) { # undefined Get
Log3 $name, 5, "$name: Get $getName not found, return list @getList ";
return "Unknown argument $a[1], choose one of @getList ";
return "Unknown argument $getName, choose one of @getList ";
}
return undef;
my $msgHash = $getHash{$getName}{msgHash}; # Hash für die Nachricht aus parseInfo
Log3 $name, 5, "$name: Request found in getHash created from parseInfo data";
if (!$msgHash->{request}) {
return "Protocol doesn't provide a command to get $getName";
}
Send($hash, $msgHash->{request}, '', $msgHash->{replyCode}, 1);
my ($err, $result) = ReadAnswer($hash, $getName, $msgHash->{replyCode});
return $err if ($err);
return $result;
}
#####################################
sub
ComfoAir_Set($@)
{
sub SetFn {
my ($hash, @a) = @_;
return "\"set ComfoAir\" needs at least an argument" if(@a < 2);
@@ -431,7 +451,7 @@ ComfoAir_Set($@)
if (defined($requestHash{$setName})) {
# set Option ist Daten-Abfrage-Request aus parseInfo
Log3 $name, 5, "$name: Request found in requestHash created from parseInfo data";
ComfoAir_Send($hash, $requestHash{$setName}{request}, "", $requestHash{$setName}{replyCode});
Send($hash, $requestHash{$setName}{request}, "", $requestHash{$setName}{replyCode});
return "";
}
if (defined($setHash{$setName})) {
@@ -475,46 +495,44 @@ ComfoAir_Set($@)
# 3. Schritt: Konvertiere mit setexpr falls definiert
if (defined($setHash{$setName}{setexpr})) {
my $val = $rawVal;
$rawVal = eval($setHash{$setName}{setexpr});
$rawVal = eval($setHash{$setName}{setexpr}); ## no critic - expression needs to come from variable
Log3 $name, 5, "$name: converted Value $val to $rawVal using expr $setHash{$setName}{setexpr}";
}
# 4. Schritt: mit sprintf umwandeln und senden.
$data = sprintf($fmt, $rawVal); # in parseInfo angegebenes Format bei set=> - meist Konvert in Hex
ComfoAir_Send($hash, $cmd, $data, 0);
Send($hash, $cmd, $data, 0);
# Nach dem Set gleich den passenden Datenblock nochmals anfordern, damit die Readings de neuen Wert haben
if ($setHash{$setName}{msgHash}{request}) {
ComfoAir_Send($hash, $setHash{$setName}{msgHash}{request}, "",
Send($hash, $setHash{$setName}{msgHash}{request}, "",
$setHash{$setName}{msgHash}{replyCode},1);
# falls ein minDelay bei Send implementiert wäre, müsste ReadAnswer optimiert werden, sonst wird der 2. send ggf nicht vor einem Timeout gesendet ...
my ($err, $result) = ComfoAir_ReadAnswer($hash, $setName, $setHash{$setName}{msgHash}{replyCode});
my ($err, $result) = ReadAnswer($hash, $setName, $setHash{$setName}{msgHash}{replyCode});
#return "$setName -> $result";
return $err if ($err);
}
return undef;
return;
} elsif (defined($ComfoAir_AddSets{$setName})) {
# Additional set option not defined in parseInfo but ComfoAir_AddSets
} elsif (defined($AddSets{$setName})) {
# Additional set option not defined in parseInfo but AddSets
if($setName eq "SendRawData") {
return "please specify data as cmd or cmd -> data in hex"
if (!defined($setVal));
($cmd, $data) = split("->",$setVal); # eingegebener Wert ist HexCmd -> HexData
$data="" if(!defined($data));
}
ComfoAir_Send($hash, $cmd, $data, 0);
Send($hash, $cmd, $data, 0);
} else {
# undefiniertes Set
Log3 $name, 5, "$name: Set $setName not found, return list @setList " . join (" ", keys %ComfoAir_AddSets);
return "Unknown argument $a[1], choose one of @setList " . join (" ", keys %ComfoAir_AddSets);
Log3 $name, 5, "$name: Set $setName not found, return list @setList " . join (" ", keys %AddSets);
return "Unknown argument $a[1], choose one of @setList " . join (" ", keys %AddSets);
}
return undef;
return;
}
#####################################
# Called from the read functions
sub
ComfoAir_ParseFrames($)
{
sub ParseFrames {
my $hash = shift;
my $name = $hash->{NAME};
my $frame = $hash->{helper}{buffer};
@@ -532,7 +550,9 @@ ComfoAir_ParseFrames($)
Log3 $name, 5, "$name: ParseFrames got frame: $hash->{RAWBUFFER}" .
" data $hash->{LASTFRAMEDATA} Rest " . unpack ('H*', $hash->{helper}{buffer});
return $framedata;
} elsif ($frame =~ /\x07\xf3(.*)/s) {
}
# ACK?
elsif ($frame =~ /\x07\xf3(.*)/s) {
my $level = ($hash->{INTERVAL} ? 4 : 5);
Log3 $name, $level, "$name: read got Ack";
$hash->{helper}{buffer} = $1; # only keep the rest after the frame
@@ -541,56 +561,48 @@ ComfoAir_ParseFrames($)
# es wird keine weitere Antwort erwartet -> gleich weiter Send Queue abarbeiten und nicht auf alten Timer warten
RemoveInternalTimer ("timeout:".$name);
RemoveInternalTimer ("queue:".$name);
ComfoAir_HandleSendQueue ("direct:".$name); # don't wait for next regular handle queue slot
HandleSendQueue ("direct:".$name); # don't wait for next regular handle queue slot
}
return undef;
} else {
return undef; # continue reading, probably frame not fully received yet
}
}
return; # continue reading, probably frame not fully received yet
}
#####################################
# Called from the read functions
sub
ComfoAir_InterpretFrame($$)
{
sub InterpretFrame {
my $hash = shift;
my $framedata = shift;
my $name = $hash->{NAME};
my ($cmd, $hexcmd, $hexdata, $len, $data, $chk);
if (defined($framedata)) {
if ($framedata =~ /(.{2})(.)(.*)(.)/s) {
$cmd = $1;
$len = $2;
$data = $3;
$chk = unpack ('C', $4);
$hexcmd = unpack ('H*', $cmd);
$hexdata = unpack ('H*', $data);
Log3 $name, 5, "$name: read split frame into cmd $hexcmd, len " . unpack ('C', $len) .
", data $hexdata chk $chk";
} else {
if ($framedata !~ /(.{2})(.)(.*)(.)/s) {
Log3 $name, 3, "$name: read: error splitting frame into fields: $hash->{LASTFRAMEDATA}";
return;
}
}
$cmd = $1;
$len = $2;
$data = $3;
$chk = unpack ('C', $4);
$hexcmd = unpack ('H*', $cmd);
$hexdata = unpack ('H*', $data);
Log3 $name, 5, "$name: read split frame into cmd $hexcmd, len " . unpack ('C', $len) .
", data $hexdata chk $chk";
}
# Länge prüfen
if (unpack ('C', $len) != length($data)) {
Log3 $name, 4, "$name: read: wrong length: " . length($data) .
" (calculated) != " . unpack ('C', $len) . " (header)" .
" cmd=$hexcmd, data=$hexdata, chk=$chk";
#return;
return;
}
# Checksum prüfen
my $csum = unpack ('%8C*', $cmd . $len . $data . "\xad"); # berechne csum
if($csum != $chk) {
Log3 $name, 4, "$name: read: wrong checksum: $csum (calculated) != $chk (frame) cmd $hexcmd, data $hexdata";
return;
};
};
# Parse Data
if ($parseInfo{$hexcmd}) {
if (!AttrVal($name, "hide-$parseInfo{$hexcmd}{name}", 0)) {
@@ -601,9 +613,9 @@ ComfoAir_InterpretFrame($$)
my @fields = unpack($p{"unpack"}, $data);
my $filter = 0;
if ($p{check}) {
Log3 $name, 5, "$name: cmd $hexcmd check is " . eval($p{check}) .
', $fields[5] = ' . $fields[5] if ($fields[5] > 15);
if (!eval($p{check})) {
my $result = eval($p{check}); ## no critic - expression needs to come from variable
Log3 $name, 5, "$name: cmd $hexcmd check is " . $result . ', $fields[5] = ' . $fields[5] if ($fields[5] > 15);
if (!$result) {
Log3 $name, 5, "$name: filter data for failed check: @fields";
$filter = 1;
}
@@ -617,7 +629,7 @@ ComfoAir_InterpretFrame($$)
# Exp zur Nachbearbeitung der Werte?
if ($p{"readings"}[$i]{"expr"}) {
Log3 $name, 5, "$name: read evaluate $val with expr " . $p{"readings"}[$i]{"expr"};
$val = eval($p{"readings"}[$i]{"expr"});
$val = eval($p{"readings"}[$i]{"expr"}); ## no critic - expr needs tocome from variable
}
# Map zur Nachbereitung der Werte?
if ($p{"readings"}[$i]{"map"}) {
@@ -646,43 +658,53 @@ ComfoAir_InterpretFrame($$)
Log3 $name, 3, "$name: read did not get expected reply (" . $hash->{EXPECT} . ") but $hexcmd";
}
}
ComfoAir_SendAck($hash) if ($hash->{INTERVAL});
SendAck($hash) if ($hash->{INTERVAL});
if (!$hash->{EXPECT}) {
# es wird keine Antwort mehr erwartet -> gleich weiter Send Queue abarbeiten und nicht auf Timer warten
$hash->{BUSY} = 0; # zur Sicherheit falls ein Ack versäumt wurde
RemoveInternalTimer ("timeout:".$name);
RemoveInternalTimer ("queue:".$name);
ComfoAir_HandleSendQueue ("direct:".$name); # don't wait for next regular handle queue slot
HandleSendQueue ("direct:".$name); # don't wait for next regular handle queue slot
}
return;
}
#####################################
# Called from the global loop, when the select for hash->{FD} reports data
sub
ComfoAir_Read($)
{
sub ReadFn {
my $hash = shift;
my $name = $hash->{NAME};
my $buf = DevIo_SimpleRead($hash);
return if(!defined($buf));
my $buf;
if ($hash->{DeviceName} eq 'none') { # simulate receiving
if ($hash->{TestInput}) {
$buf = $hash->{TestInput};
delete $hash->{TestInput};
}
}
else {
$buf = DevIo_SimpleRead($hash);
return if(!defined($buf));
}
$hash->{helper}{buffer} .= $buf;
# todo: does this loop really make sense?
for (my $i = 0;$i < 2;$i++) {
my $framedata = ComfoAir_ParseFrames($hash);
my $framedata = ParseFrames($hash);
return if (!$framedata);
ComfoAir_InterpretFrame($hash, $framedata);
InterpretFrame($hash, $framedata);
}
return;
}
#####################################
# Called from get / set to get a direct answer
sub
ComfoAir_ReadAnswer($$$)
{
# todo: restructure this function (see Modbus)
# handle BUSY when timeout here!
sub ReadAnswer {
my ($hash, $arg, $expectReply) = @_;
my $name = $hash->{NAME};
@@ -696,7 +718,17 @@ ComfoAir_ReadAnswer($$$)
Log3 $name, 5, "$name: ReadAnswer called for get $arg";
for(;;) {
if($^O =~ m/Win/ && $hash->{USBDev}) {
if ($hash->{DeviceName} eq 'none') { # simulate receiving
if ($hash->{TestInput}) {
$buf = $hash->{TestInput};
delete $hash->{TestInput};
} else {
#$hash->{BUSY} = 0;
#$hash->{EXPECT} = "";
return ("Timeout reading answer for $arg", undef);
}
}
elsif($^O =~ m/Win/ && $hash->{USBDev}) {
$hash->{USBDev}->read_const_time($to*1000); # set timeout (ms)
$buf = $hash->{USBDev}->read(999);
if(length($buf) == 0) {
@@ -716,7 +748,7 @@ ComfoAir_ReadAnswer($$$)
my $err = $!;
DevIo_Disconnected($hash);
Log3 $name, 3, "$name: ReadAnswer $arg: error $err";
return("ComfoAir_ReadAnswer $arg: $err", undef);
return("ReadAnswer $arg: $err", undef);
}
if($nfound == 0) {
Log3 $name, 3, "$name: Timeout2 in ReadAnswer for $arg";
@@ -735,9 +767,9 @@ ComfoAir_ReadAnswer($$$)
Log3 $name, 5, "$name: ReadAnswer got: " . unpack ("H*", $hash->{helper}{buffer});
}
$framedata = ComfoAir_ParseFrames($hash);
$framedata = ParseFrames($hash);
if ($framedata) {
ComfoAir_InterpretFrame($hash, $framedata);
InterpretFrame($hash, $framedata);
$cmd = unpack ('H4x*', $framedata);
if ($cmd eq $expectReply) {
# das war's worauf wir gewartet haben
@@ -745,15 +777,14 @@ ComfoAir_ReadAnswer($$$)
return (undef, ReadingsVal($name, $arg, ""));
}
}
ComfoAir_HandleSendQueue("direct:".$name);
HandleSendQueue("direct:".$name);
}
return;
}
#####################################
sub
ComfoAir_Ready($)
{
sub ReadyFn {
my ($hash) = @_;
return DevIo_OpenDev($hash, 1, undef)
if($hash->{STATE} eq "disconnected");
@@ -767,11 +798,10 @@ ComfoAir_Ready($)
#####################################
sub
ComfoAir_GetUpdate($$) {
sub GetUpdate {
my ($hash) = @_;
my $name = $hash->{NAME};
InternalTimer(gettimeofday()+$hash->{INTERVAL}, "ComfoAir_GetUpdate", $hash, 0)
InternalTimer(gettimeofday()+$hash->{INTERVAL}, "GetUpdate", $hash, 0)
if ($hash->{INTERVAL});
foreach my $msgHashRef (values %parseInfo) {
@@ -779,16 +809,16 @@ ComfoAir_GetUpdate($$) {
my $default = ($msgHashRef->{defaultpoll} ? 1 : 0); # verwende als Defaultwert für Attribut, falls gesetzt in %parseInfo
if (AttrVal($name, "poll-$msgHashRef->{name}", $default)) {
Log3 $name, 5, "$name: GetUpdate requests $msgHashRef->{name}, default is $default";
ComfoAir_Send($hash, $msgHashRef->{request}, "", $msgHashRef->{replyCode});
Send($hash, $msgHashRef->{request}, "", $msgHashRef->{replyCode});
}
}
}
return;
}
#####################################
sub
ComfoAir_Send($$$;$$){
sub Send {
my ($hash, $hexcmd, $hexdata, $expectReply, $first) = @_;
my $name = $hash->{NAME};
@@ -826,14 +856,13 @@ ComfoAir_Send($$$;$$){
}
}
}
ComfoAir_HandleSendQueue("direct:".$name);
HandleSendQueue("direct:".$name);
return;
}
#######################################
sub
ComfoAir_TimeoutSend($)
{
sub TimeoutSend {
my $param = shift;
my (undef,$name) = split(':',$param);
my $hash = $defs{$name};
@@ -842,14 +871,13 @@ ComfoAir_TimeoutSend($)
($hash->{EXPECT} ? " expecting " . $hash->{EXPECT} : "") .
" Request was " . $hash->{LASTREQUEST};
$hash->{BUSY} = 0;
$hash->{EXPECT} = "";
};
$hash->{EXPECT} = "";
return;
}
#######################################
sub
ComfoAir_HandleSendQueue($)
{
sub HandleSendQueue {
my $param = shift;
my (undef,$name) = split(':',$param);
my $hash = $defs{$name};
@@ -860,13 +888,13 @@ ComfoAir_HandleSendQueue($)
if(defined($arr) && @{$arr} > 0) {
if (!$init_done) { # fhem not initialized, wait with IO
RemoveInternalTimer ("queue:".$name);
InternalTimer($now+$queueDelay, "ComfoAir_HandleSendQueue", "queue:".$name, 0);
InternalTimer($now+$queueDelay, "ComfoAir::HandleSendQueue", "queue:".$name, 0);
Log3 $name, 3, "$name: init not done, delay writing from queue";
return;
}
if ($hash->{BUSY}) { # still waiting for reply to last request
RemoveInternalTimer ("queue:".$name);
InternalTimer($now+$queueDelay, "ComfoAir_HandleSendQueue", "queue:".$name, 0);
InternalTimer($now+$queueDelay, "ComfoAir::HandleSendQueue", "queue:".$name, 0);
Log3 $name, 5, "$name: send busy, delay writing from queue";
return;
}
@@ -885,7 +913,12 @@ ComfoAir_HandleSendQueue($)
($entry->{EXPECT} ? " and wait for " . $entry->{EXPECT} : "") .
", V " . $hash->{ModuleVersion};
DevIo_SimpleWrite($hash, $bstring, 0);
if ($hash->{DeviceName} eq 'none') {
Log3 $name, 4, "$name: Simulate sending to none: " . unpack ('H*', $bstring);
}
else {
DevIo_SimpleWrite($hash, $bstring, 0);
}
if ($entry->{EXPECT}) {
# we expect a reply
@@ -893,27 +926,27 @@ ComfoAir_HandleSendQueue($)
}
my $to = AttrVal($name, "timeout", 2); # default is 2 seconds timeout
RemoveInternalTimer ("timeout:".$name);
InternalTimer($now+$to, "ComfoAir_TimeoutSend", "timeout:".$name, 0);
InternalTimer($now+$to, "ComfoAir::TimeoutSend", "timeout:".$name, 0);
}
shift(@{$arr});
if(@{$arr} == 0) { # last item was sent -> delete queue
delete($hash->{QUEUE});
} else { # more items in queue -> schedule next handle invocation
RemoveInternalTimer ("queue:".$name);
InternalTimer($now+$queueDelay, "ComfoAir_HandleSendQueue", "queue:".$name, 0);
InternalTimer($now+$queueDelay, "ComfoAir::HandleSendQueue", "queue:".$name, 0);
}
}
return;
}
#######################################
sub
ComfoAir_SendAck($)
{
sub SendAck {
my $hash = shift;
my $name = $hash->{NAME};
Log3 $name, 4, "$name: sending Ack";
DevIo_SimpleWrite($hash, "\x07\xf3", 0);
return;
}