From 6ae941f04d16e0dd66dbd3a785ce14fa4da264c3 Mon Sep 17 00:00:00 2001 From: borisneubert <> Date: Thu, 11 Sep 2014 15:09:30 +0000 Subject: [PATCH] option to cope with partial messages in ECMD/ECMDDevice always match complete messages git-svn-id: https://svn.fhem.de/fhem/trunk@6536 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/66_ECMD.pm | 44 ++++++++++++++++++-------- fhem/FHEM/67_ECMDDevice.pm | 65 +++++++++++++++++++++++++++++++++++--- 3 files changed, 92 insertions(+), 18 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 2d6ebddc6..32673c894 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: option to cope with partial messages in ECMD/ECMDDevice - bugfix: SOMFY: add module to CUL client list, to set IODev automatically - feature: sequence: triggerPartial Attribute added - feature: 36_JeeLink: changed flash command to use fhem firmware diff --git a/fhem/FHEM/66_ECMD.pm b/fhem/FHEM/66_ECMD.pm index 8784dc3ef..23df467b2 100644 --- a/fhem/FHEM/66_ECMD.pm +++ b/fhem/FHEM/66_ECMD.pm @@ -64,7 +64,7 @@ ECMD_Initialize($) $hash->{GetFn} = "ECMD_Get"; $hash->{SetFn} = "ECMD_Set"; $hash->{AttrFn} = "ECMD_Attr"; - $hash->{AttrList}= "classdefs split logTraffic:0,1,2,3,4,5 timeout"; + $hash->{AttrList}= "classdefs split logTraffic:0,1,2,3,4,5 timeout partial"; } ##################################### @@ -172,9 +172,30 @@ sub ECMD_SimpleExpect($$$) { my ($hash, $msg, $expect) = @_; + + my $name= $hash->{NAME}; + my $timeout= AttrVal($name, "timeout", 3.0); + my $partialTimeout= AttrVal($name, "partial", 0.0); ECMD_Log $hash, undef, "write " . dq($msg) . ", expect $expect"; - my $answer= DevIo_Expect($hash, $msg, AttrVal($hash->{NAME}, "timeout", 3.0)); + my $answer= DevIo_Expect($hash, $msg, $timeout ); + + #Debug "$name: Expect got \"" . escapeLogLine($answer) . "\"."; + + # complete partial answers + if($partialTimeout> 0) { + my $t0= gettimeofday(); + while(!defined($answer) || ($answer !~ /^$expect$/)) { + #Debug "$name: waiting for a match..."; + my $a= DevIo_SimpleReadWithTimeout($hash, $partialTimeout); # we deliberately use partialTimeout here! + #Debug "$name: SimpleReadWithTimeout got \"" . escapeLogLine($a) . "\"."; + if(defined($a)) { + $answer= ( defined($answer) ? $answer . $a : $a ); + } + #Debug "$name: SimpleExpect has now answer \"" . escapeLogLine($answer) . "\"."; + last if(gettimeofday()-$t0> $partialTimeout); + } + } if(defined($answer)) { ECMD_Log $hash, undef, "read " . dq($answer); @@ -226,9 +247,7 @@ ECMD_Get($@) if($cmd eq "raw") { return "get raw needs an argument" if(@a< 3); - #my $nonl= AttrVal($name, "nonl", 0); my $ecmd= join " ", @args; - #ecmd .= "\n" unless($nonl); $ecmd= AnalyzePerlCommand(undef, $ecmd); $answer= ECMD_SimpleExpect($hash, $ecmd, ".*"); } else { @@ -511,8 +530,6 @@ ECMD_Write($$$) my $requestSeparator= "\000"; # AttrVal($hash, "requestSeparator", "\000"); my $responseSeparator= ""; # AttrVal($hash, "responseSeparator", ""); my @ecmds= split $requestSeparator, $msg; - #my @ecmds= split "\n", $msg; - #my $nonl= AttrVal($name, "nonl", 0); ECMD_Log $hash, 5, "command split into " . ($#ecmds+1) . " parts." if($#ecmds>0); foreach my $ecmd (@ecmds) { ECMD_Log $hash, 5, "sending command " . dq($ecmd); @@ -647,11 +664,7 @@ ECMD_Write($$$) foo 12 and bar off.
  • logTraffic <loglevel>
    Enables logging of sent and received datagrams with the given loglevel. Control characters in the logged datagrams are escaped, i.e. a double backslash is shown for a single backslash, \n is shown for a line feed character, etc.
  • timeout <seconds>
    Time in seconds to wait for a reply from the physical ECMD device before FHEM assumes that something has gone wrong. The default is 3 seconds if this attribute is not set.
  • - +
  • partial <seconds>
    Some physical ECMD devices split readings and replies into several transmissions. If the partial attribute is set, this behavior is accounted for as follows: (a) If a reply is expected for a get or set command, FHEM collects transmissions from the physical ECMD device until either the reply matches the expected reply or the time in seconds given with the partial attribute has expired. (b) If a spontaneous transmission does not match the regular expression for any reading, the transmission is recorded and prepended to the next transmission. If the line is quiet for longer than the time in seconds given with the partial attribute, the recorded transmission is discarded. Use regular expressions that produce exact matches.
  • verbose


  • @@ -660,11 +673,11 @@ ECMD_Write($$$) Datagram monitoring and matching

    - Data to and from the physical device is processed as is. In particular, if you need to send a line feed you have to include send a \n control character. On the other hand, control characters like line feeds are not stripped from the data received. This needs to be considered when defining a class definition.

    + Data to and from the physical device is processed as is. In particular, if you need to send a line feed you have to explicitely send a \n control character. On the other hand, control characters like line feeds are not stripped from the data received. This needs to be considered when defining a class definition.

    For debugging purposes, especially when designing a class definition, it is advisable to turn traffic logging on. Use attr myECMD logTraffic 3 to log all data to and from the physical device at level 3. A typical response might look like 21.2\n, i.e. a floating point number followed by a newline.

    - Data received from the physical device is processed as it comes in chunks. If for some reason a datagram from the device is split in transit, pattern matching and processing will most likely fail. + Data received from the physical device is processed as it comes in chunks. If for some reason a datagram from the device is split in transit, pattern matching and processing will most likely fail. You can use the partial attribute to make FHEM collect and recombine the chunks.

    @@ -714,7 +727,9 @@ ECMD_Write($$$) set <commandname> expect "<regex>"
    get <commandname> expect "<regex>"

    - Declares what FHEM expects to receive after the execution of the get or set command <commandname>. <regex> is a Perl regular expression. The double quotes around the regular expression are mandatory and they are not part of the regular expression itself. Particularly, broken connections can only be detected if something is expected (see Connection error handling). + Declares what FHEM expects to receive after the execution of the get or set command <commandname>. <regex> is a Perl regular expression. The double quotes around the regular expression are mandatory and they are not part of the regular expression itself. + <regex> must match the entire reply, as in m/^<regex>$/. + Particularly, broken connections can only be detected if something is expected (see Connection error handling).

    @@ -740,6 +755,7 @@ ECMD_Write($$$) reading <reading> match "<regex>"

    Declares a new reading named <reading>. A spontaneous data transmission from the physical device that matches the Perl regular expression <regex> is evaluated to become the value of the named reading. All ECMDDevice devices belonging to the ECMD device with readings with matching regular expressions will receive an update of the said readings. + <regex> must match the entire reply, as in m/^<regex>$/.

    diff --git a/fhem/FHEM/67_ECMDDevice.pm b/fhem/FHEM/67_ECMDDevice.pm index cab51c05f..90815b135 100644 --- a/fhem/FHEM/67_ECMDDevice.pm +++ b/fhem/FHEM/67_ECMDDevice.pm @@ -264,6 +264,41 @@ ECMDDevice_Parse($$) my @matches; my $name= $IOhash->{NAME}; + my $ts= gettimeofday(); + + if(defined(AttrVal($name, "partial", undef))) { + + if(!defined($IOhash->{fhem}{partial})) { + $IOhash->{fhem}{partial}{ts}= $ts; + $IOhash->{fhem}{partial}{msg}= ""; + } + + #Debug "$name: partial message \"" . escapeLogLine($IOhash->{fhem}{partial}{msg}) . "\" recorded at $ts"; + if($IOhash->{fhem}{partial}{msg} ne "") { + # clear partial message if expired + my $timeout= AttrVal($name, "partial", 1); + my $t0= $IOhash->{fhem}{partial}{ts}; + if($ts-$t0> $timeout) { + $IOhash->{fhem}{partial}{msg}= ""; + #Debug "$name: partial message expired."; + } + } + + # prepend to recently received message + $IOhash->{fhem}{partial}{ts}= $ts; + $message= $IOhash->{fhem}{partial}{msg} . $message; + $IOhash->{fhem}{partial}{msg}= ""; + + } else { + + # clean the partial stuff for clarity + if(defined($IOhash->{fhem}{partial})) { + delete($IOhash->{fhem}{partial}) + } + } + + #Debug "$name: analyzing \"" . escapeLogLine($message) . "\"."; + my @msgs; if(defined(AttrVal($name, "split", undef))) { @msgs= split(AttrVal($name, "split", undef), $message); @@ -271,8 +306,13 @@ ECMDDevice_Parse($$) push @msgs, $message; } + #my @unmatchedMsgs; # future use + my $lastMsg= ""; + my $msgMatched; + foreach my $msg (@msgs) { - #Debug "Trying to find a match for \"" . escapeLogLine($msg) ."\""; + #Debug "$name: trying to find a match for \"" . escapeLogLine($msg) ."\""; + $msgMatched= 0; # walk over all clients foreach my $d (keys %defs) { my $hash= $defs{$d}; @@ -288,9 +328,10 @@ ECMDDevice_Parse($$) foreach my $r (keys %{$classDef->{readings}}) { my $regex= ECMDDevice_ReplaceSpecials($classDef->{readings}{$r}{match}, %specials); #Debug " Trying to match reading $r with regular expressing \"$regex\"."; - if($msg =~ m/$regex/) { + if($msg =~ m/^$regex$/) { # we found a match Log3 $IOhash, 5, "$name: match regex $regex for reading $r of device $d with class $classname"; + $msgMatched++; push @matches, $d; my $postproc= $classDef->{readings}{$r}{postproc}; my $value= ECMDDevice_PostProc($hash, $postproc, $msg); @@ -300,12 +341,28 @@ ECMDDevice_Parse($$) } } } - + #push @unmatchedMsgs, $msg unless($msgMatched); # future use } + $lastMsg= $msgs[$#msgs] unless($msgMatched); # contains the last message if the last message is unmatched + if(defined(AttrVal($name, "partial", undef)) && $lastMsg ne "") { + # we come here if the last message was unmatched and we want partial messages + if($#msgs>= 0) { + # we had more messages; therefore the partial message belonged to the first message and needs + # to be cleared + $IOhash->{fhem}{partial}{msg}= ""; + } + $IOhash->{fhem}{partial}{msg}.= $lastMsg; # append unmatched message + #Debug "$name: partial message \"" . escapeLogLine($IOhash->{fhem}{partial}{msg}) . "\" kept."; + } + return @matches if(@matches); + + # we come here if no match is found + # NOTE: In a split message, undefined messages are not reported if there was at least one match. - return "UNDEFINED ECMDDevice message $message"; + # return "UNDEFINED ECMDDevice message $message"; + return ""; }