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
.
+ 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 "";
}