diff --git a/fhem/FHEM/98_ComfoAir.pm b/fhem/FHEM/98_ComfoAir.pm index b9432a47a..7e3ead7bd 100755 --- a/fhem/FHEM/98_ComfoAir.pm +++ b/fhem/FHEM/98_ComfoAir.pm @@ -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 . # ############################################################################## -# 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 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; }