From 69169f2bc4e65701bf277fbdb6880962c04b0f76 Mon Sep 17 00:00:00 2001
From: rudolfkoenig <>
Date: Sat, 5 Sep 2015 20:09:56 +0000
Subject: [PATCH] 10_ZWave.pm: rewrite command queueing (Forum #40594)
git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@9204 2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
FHEM/00_ZWDongle.pm | 55 ++++++----------
FHEM/10_ZWave.pm | 155 +++++++++++++++++++++++++++++---------------
2 files changed, 123 insertions(+), 87 deletions(-)
diff --git a/FHEM/00_ZWDongle.pm b/FHEM/00_ZWDongle.pm
index 0928b9230..bf02540ed 100755
--- a/FHEM/00_ZWDongle.pm
+++ b/FHEM/00_ZWDongle.pm
@@ -16,7 +16,7 @@ sub ZWDongle_Read($@);
sub ZWDongle_ReadAnswer($$$);
sub ZWDongle_Ready($);
sub ZWDongle_Write($$$);
-sub ZWave_ProcessSendStack($);
+sub ZWDongle_ProcessSendStack($);
# See also:
@@ -193,8 +193,8 @@ ZWDongle_Initialize($)
$hash->{GetFn} = "ZWDongle_Get";
$hash->{AttrFn} = "ZWDongle_Attr";
$hash->{UndefFn} = "ZWDongle_Undef";
- $hash->{AttrList}=
- "do_not_notify:1,0 dummy:1,0 model:ZWDongle disable:0,1 homeId networkKey";
+ $hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 model:ZWDongle disable:0,1 ".
+ "homeId networkKey delayNeeded:1,0";
}
#####################################
@@ -513,32 +513,9 @@ ZWDongle_Write($$$)
$msg = sprintf("%02x%s", length($msg)/2+1, $msg);
$msg = "01$msg" . ZWDongle_CheckSum($msg);
-
- # push message on stack
- my $ss = $hash->{SendStack};
+ push @{$hash->{SendStack}}, $msg;
- my $wNMIre = '01....13..028408';
- if(@{$ss} && $ss->[0] =~ m/$wNMIre/) {
- Log3 $hash, 2,
- "ZWDongle_Write: command after wakeupNoMoreInformation dropped";
- return;
- }
-
- push @{$ss}, $msg;
-
- # assure that wakeupNoMoreInformation is the last message on the sendStack
- if($msg =~ m/^01....13(..)/) {
- my $wNMI;
- my @s = grep { /^$wNMIre/ ? ($wNMI=$_,0):1 } @{$ss};
- if($wNMI) {
- Log3 $hash, 5, "ZWDongle_Write wakeupNoMoreInformation moved to the end"
- if($wNMI ne $msg);
- push @s, $wNMI;
- $hash->{SendStack} = \@s;
- }
- }
-
- ZWave_ProcessSendStack($hash);
+ ZWDongle_ProcessSendStack($hash);
}
sub
@@ -547,7 +524,8 @@ ZWDongle_shiftSendStack($$$)
my ($hash, $level, $txt) = @_;
my $ss = $hash->{SendStack};
my $cmd = shift @{$ss};
- Log3 $hash, $level, "$txt, removing $cmd from sendstack" if($txt && $cmd);
+ Log3 $hash, $level, "$txt, removing $cmd from dongle sendstack"
+ if($txt && $cmd);
$hash->{WaitForAck}=0;
$hash->{SendRetries}=0;
@@ -555,11 +533,11 @@ ZWDongle_shiftSendStack($$$)
}
sub
-ZWave_ProcessSendStack($)
+ZWDongle_ProcessSendStack($)
{
my ($hash) = @_;
- #Log3 $hash, 1, "ZWave_ProcessSendStack: ".@{$hash->{SendStack}}.
+ #Log3 $hash, 1, "ZWDongle_ProcessSendStack: ".@{$hash->{SendStack}}.
# " items on stack, waitForAck ".$hash->{WaitForAck};
RemoveInternalTimer($hash);
@@ -568,12 +546,12 @@ ZWave_ProcessSendStack($)
if($hash->{WaitForAck}){
if($ts-$hash->{SendTime} >= 1){
- Log3 $hash, 2, "ZWave_ProcessSendStack: no ACK, resending message";
+ Log3 $hash, 2, "ZWDongle_ProcessSendStack: no ACK, resending message";
$hash->{SendRetries}++;
$hash->{WaitForAck} = 0;
} else {
- InternalTimer($ts+1, "ZWave_ProcessSendStack", $hash, 0);
+ InternalTimer($ts+1, "ZWDongle_ProcessSendStack", $hash, 0);
return;
}
@@ -593,7 +571,7 @@ ZWave_ProcessSendStack($)
$hash->{WaitForAck} = 1;
$hash->{SendTime} = $ts;
- InternalTimer($ts+1, "ZWave_ProcessSendStack", $hash, 0);
+ InternalTimer($ts+1, "ZWDongle_ProcessSendStack", $hash, 0);
}
#####################################
@@ -693,7 +671,7 @@ ZWDongle_Read($@)
$hash->{PARTIAL} = $data;
# trigger sending of next message
- ZWave_ProcessSendStack($hash) if(length($data) == 0);
+ ZWDongle_ProcessSendStack($hash) if(length($data) == 0);
return $msg if(defined($local));
return undef;
@@ -944,6 +922,13 @@ ZWDongle_Ready($)
homeId
Stores the homeId of the dongle. Is a workaround for some buggy dongles,
wich sometimes report a wrong/nonexisten homeId (Forum #35126)
+ networkKey
+ Needed for secure inclusion, hex string with length of 32
+
+ delayNeeded
+ If set to 0, no delay is needed between sending consecutive commands to
+ the same receiver. Default is 1 (delay is needed).
+
diff --git a/FHEM/10_ZWave.pm b/FHEM/10_ZWave.pm
index 22845a799..9071f1302 100755
--- a/FHEM/10_ZWave.pm
+++ b/FHEM/10_ZWave.pm
@@ -9,11 +9,12 @@ use SetExtensions;
use Compress::Zlib;
use Time::HiRes qw( gettimeofday );
+sub ZWave_Cmd($$@);
+sub ZWave_Get($@);
sub ZWave_Parse($$@);
sub ZWave_Set($@);
-sub ZWave_Get($@);
-sub ZWave_Cmd($$@);
sub ZWave_SetClasses($$$$);
+sub ZWave_addToSendStack($$);
use vars qw(%zw_func_id);
use vars qw(%zw_type6);
@@ -425,7 +426,8 @@ use vars qw(%zwave_deviceSpecial);
init => { ORDER=>50, CMD => '"get $NAME zwavePlusInfo"' } } }
);
-my $crypt_Rijndael = 0;
+my $zwave_cryptRijndael = 0;
+my $zwave_lastHashSent;
sub
ZWave_Initialize($)
@@ -448,7 +450,7 @@ ZWave_Initialize($)
if($@) {
Log 3, "ZWave: cannot load Crypt::Rijndael, SECURITY class disabled";
} else {
- $crypt_Rijndael = 1;
+ $zwave_cryptRijndael = 1;
}
}
@@ -481,7 +483,6 @@ ZWave_Define($$)
AssignIoPort($hash); # FIXME: should take homeId into account
if(@a) { # Autocreate: set the classes, execute the init calls
- $hash->{lastMsgTimestamp} = gettimeofday(); # device is awake.
ZWave_SetClasses($homeId, $id, undef, $a[0]);
}
return undef;
@@ -617,8 +618,6 @@ ZWave_Cmd($$@)
my $cmdFmt = $cmdList{$cmd}{fmt};
my $cmdId = $cmdList{$cmd}{id};
# 0x05=AUTO_ROUTE+ACK, 0x20: ExplorerFrames
- my $cmdEf = (AttrVal($name, "noExplorerFrames", 0) == 0 ? "25" : "05");
-
my $nArg = 0;
if($cmdFmt =~ m/%/) {
@@ -679,7 +678,9 @@ ZWave_Cmd($$@)
} else {
my $len = sprintf("%02x", length($cmdFmt)/2+1);
+ my $cmdEf = (AttrVal($name, "noExplorerFrames", 0) == 0 ? "25" : "05");
$data = "13$id$len$cmdId${cmdFmt}$cmdEf"; # 13==SEND_DATA
+
}
$data .= $id; # callback=>id
@@ -697,25 +698,11 @@ ZWave_Cmd($$@)
}
}
- if($baseClasses =~ m/WAKE_UP/) {
- if(!$baseHash->{WakeUp}) {
- my @arr = ();
- $baseHash->{WakeUp} = \@arr;
- }
- my $tdiff = gettimeofday() - $baseHash->{lastMsgTimestamp};
- my $awake = ($baseHash->{lastMsgTimestamp} && $tdiff < 3);
-
- if(!$awake && ($data !~ m/......9880.*/) ) {
- Log3 $name, 5, "Device not awake (tdiff= $tdiff),".
- " command ($data) stored in sendstack";
- push @{$baseHash->{WakeUp}}, $data;
- return (AttrVal($name,"verbose",3) > 2 ?
- "Scheduled for sending after WAKEUP" : undef);
- }
+ my $r = ZWave_addToSendStack($baseHash, $data);
+ if($r) {
+ return (AttrVal($name,"verbose",3) > 2 ? $r : undef);
}
- IOWrite($hash, "00", $data);
-
my $val;
if($type eq "get") {
no strict "refs";
@@ -1345,7 +1332,7 @@ ZWave_clockAdjust($$)
my ($hash, $d) = @_;
return $d if($d !~ m/^13(..)048104....$/);
my ($err, $nd) = ZWave_clockSet();
- my $cmdEf = (AttrVal($hash->{NAME}, "noExplorerFrames", 0) == 0 ? "25" : "05");
+ my $cmdEf = (AttrVal($hash->{NAME},"noExplorerFrames",0) == 0 ? "25" : "05");
return "13${1}0481${nd}${cmdEf}${1}";
}
@@ -2386,12 +2373,20 @@ ZWave_wakeupTimer($)
{
my ($hash) = @_;
my $now = gettimeofday();
- if($now - $hash->{lastMsgTimestamp} > 1) { # wakeupNoMoreInformation
- if($hash->{STATE} ne "TRANSMIT_NO_ACK") {
+
+ if(!$hash->{wakeupAlive}) {
+ $hash->{wakeupAlive} = 1;
+ $hash->{lastMsgSent} = $now;
+ InternalTimer($now+0.1, "ZWave_wakeupTimer", $hash, 0);
+
+ } elsif($now - $hash->{lastMsgSent} > 1) {
+ if(!$hash->{SendStack}) {
my $nodeId = $hash->{id};
my $cmdEf = (AttrVal($hash->{NAME},"noExplorerFrames",0)==0 ? "25":"05");
+ # wakeupNoMoreInformation
IOWrite($hash, "00", "13${nodeId}028408${cmdEf}$nodeId");
}
+ delete $hash->{wakeupAlive};
} else {
InternalTimer($now+0.1, "ZWave_wakeupTimer", $hash, 0);
@@ -2400,19 +2395,58 @@ ZWave_wakeupTimer($)
}
sub
-ZWave_sendWakeup($)
+ZWave_processSendStack($$$)
{
- my ($hash) = @_;
+ my ($hash, $now, $withDelay) = @_;
+ my $ss = $hash->{SendStack};
+ return if(!$ss);
- my $wu = $hash->{WakeUp};
- if($wu && @{$wu}) {
- foreach my $wuCmd (@{$wu}) {
- IOWrite($hash, "00", ZWave_clockAdjust($hash, $wuCmd));
- Log3 $hash, 4, "Sending stored command: $wuCmd";
- }
- @{$hash->{WakeUp}}=();
- InternalTimer(gettimeofday()+0.1, "ZWave_wakeupTimer", $hash, 0);
+ if($withDelay && AttrVal($hash->{IODev}{NAME}, "delayNeeded",1)) {
+ InternalTimer(gettimeofday()+0.3, sub {
+ ZWave_processSendStack($hash, $now, 0);
+ }, undef, 0);
+ return;
}
+
+ shift @{$ss} if(index($ss->[0],"sent") == 0);
+ if(@{$ss} == 0) {
+ delete $hash->{SendStack};
+ return;
+ }
+
+ IOWrite($hash, "00", $ss->[0]);
+ $ss->[0] = "sent:".$ss->[0];
+
+ $hash->{lastMsgSent} = ($now ? $now : gettimeofday());
+ $zwave_lastHashSent = $hash;
+}
+
+sub
+ZWave_addToSendStack($$)
+{
+ my ($hash, $cmd) = @_;
+ if(!$hash->{SendStack}) {
+ my @empty;
+ $hash->{SendStack} = \@empty;
+ }
+ my $ss = $hash->{SendStack};
+ push @{$ss}, $cmd;
+
+ my $now;
+ if(index(AttrVal($hash->{NAME}, "classes", ""), "WAKE_UP") >= 0) {
+ return "Scheduled for sending after WAKEUP" if(!$hash->{wakeupAlive});
+
+ } else { # clear commands without 0113 and 0013
+ $now = gettimeofday();
+ if(@{$ss} > 1 && $now-$hash->{lastMsgSent} > 10) {
+ Log3 $hash, 2,
+ "ERROR: $hash->{NAME}: cleaning commands without ack after 10s";
+ delete $hash->{SendStack};
+ return ZWave_addToSendStack($hash, $cmd);
+ }
+ }
+ ZWave_processSendStack($hash, $now, 0) if(@{$ss} == 1);
+ return undef;
}
@@ -2432,11 +2466,21 @@ ZWave_Parse($$@)
return "";
}
- if($msg =~ m/^01(..)(..*)/) { # 01==ANSWER
+ if($msg =~ m/^01(..)(..*)/) { # 01==ANSWER from the ZWDongle
my ($cmd, $arg) = ($1, $2);
$cmd = $zw_func_id{$cmd} if($zw_func_id{$cmd});
- if($cmd eq "ZW_SEND_DATA") {
- Log3 $ioName, 2, "ERROR: cannot SEND_DATA: $arg" if($arg != 1);
+ if($cmd eq "ZW_SEND_DATA") { # 011301: data was sent.
+ if($arg != 1) {
+ if($zwave_lastHashSent) {
+ my $hash = $zwave_lastHashSent;
+ readingsSingleUpdate($hash, "SEND_DATA", "failed:$arg", 1);
+ Log3 $ioName, 2, "ERROR: cannot SEND_DATA to $hash->{NAME}: $arg";
+ ZWave_processSendStack($hash, undef, 1);
+
+ } else {
+ Log3 $ioName, 2, "ERROR: cannot SEND_DATA: $arg (unknown device)";
+ }
+ }
return "";
}
if($cmd eq "SERIAL_API_SET_TIMEOUTS" && $arg =~ m/(..)(..)/) {
@@ -2476,14 +2520,12 @@ ZWave_Parse($$@)
my $dh = $modules{ZWave}{defptr}{"$homeId $1"};
return "" if(!$dh);
- $dh->{lastMsgTimestamp} = gettimeofday();
-
if($iodev->{addSecure}) {
readingsSingleUpdate($dh, "SECURITY",
"INITIALIZING (starting secure inclusion)", 0);
my $classes = AttrVal($dh->{NAME}, "classes", "");
if($classes =~ m/SECURITY/) {
- if ($crypt_Rijndael == 1) {
+ if ($zwave_cryptRijndael == 1) {
my $key = AttrVal($ioName, "networkKey", "");
if($key) {
$iodev->{secInitName} = $dh->{NAME};
@@ -2498,9 +2540,9 @@ ZWave_Parse($$@)
}
} else {
readingsSingleUpdate($dh, "SECURITY",
- 'DISABLED (Module Crypt_Rijndael not found)', 0);
+ 'DISABLED (Module Crypt::Rijndael not found)', 0);
Log3 $dh->{NAME}, 1, "$dh->{NAME}: SECURITY disabled, module ".
- "Crypt_Rijndael not found";
+ "Crypt::Rijndael not found";
}
} else {
readingsSingleUpdate($dh, "SECURITY",
@@ -2519,8 +2561,11 @@ ZWave_Parse($$@)
my $hash = $modules{ZWave}{defptr}{"$homeId $id"};
if($hash) {
- ZWave_sendWakeup($hash) if($hash);
- $hash->{lastMsgTimestamp} = gettimeofday();
+ if(index(AttrVal($hash->{NAME}, "classes", ""), "WAKE_UP") >= 0) {
+ ZWave_wakeupTimer($hash);
+ ZWave_processSendStack($hash, undef, 0);
+ }
+
if(!$ret) {
readingsSingleUpdate($hash, "CMD", $cmd, 1); # forum:20884
return $hash->{NAME};
@@ -2528,14 +2573,18 @@ ZWave_Parse($$@)
}
return $ret;
- } elsif($cmd eq "ZW_SEND_DATA") {
+ } elsif($cmd eq "ZW_SEND_DATA") { # 0013cb00....
my $hash = $modules{ZWave}{defptr}{"$homeId $callbackid"};
my %msg = ('00'=>'OK', '01'=>'NO_ACK', '02'=>'FAIL',
'03'=>'NOT_IDLE', '04'=>'NOROUTE' );
my $msg = ($msg{$id} ? $msg{$id} : "UNKNOWN_ERROR");
- if ($id eq "00") {
+
+ if($id eq "00") {
Log3 $ioName, 4, "$ioName transmit $msg for $callbackid";
- readingsSingleUpdate($hash, "transmit", $msg, 0) if($hash);
+ if($hash) {
+ readingsSingleUpdate($hash, "transmit", $msg, 0);
+ ZWave_processSendStack($hash, undef, 1);
+ }
return "";
} else {
@@ -2612,7 +2661,6 @@ ZWave_Parse($$@)
}
- $baseHash->{lastMsgTimestamp} = gettimeofday();
my $name = $hash->{NAME};
my @event;
my @args = ($arg); # MULTI_CMD handling
@@ -2667,7 +2715,10 @@ ZWave_Parse($$@)
push @event, "UNPARSED:$className $arg" if(!$matched);
}
- ZWave_sendWakeup($baseHash) if($arg =~ m/^028407/);
+ if($arg =~ m/^028407/) { # wakeup:notification
+ ZWave_wakeupTimer($hash);
+ ZWave_processSendStack($hash, undef, 0);
+ }
return "" if(!@event);