diff --git a/FHEM/00_ZWCUL.pm b/FHEM/00_ZWCUL.pm index 2fb16078a..06edbbf2e 100755 --- a/FHEM/00_ZWCUL.pm +++ b/FHEM/00_ZWCUL.pm @@ -3,18 +3,12 @@ package main; # TODO -# inclusion/exclusion # resend in firmware -# neighborUpdate Class: 010404010c0001 010604000c0040 010700 0105 # static routing +# neighborUpdate Class: 010404010c0001 010604000c0040 010700 0105 # explorer frames -# implement security +# check security # multicast -# NIF: -#zwcul: 0101 d3 9c01 10 01 5e25263370273281855972867a73ef5a82 (zwcul) -#zme reporting: 1404 10 01 5e25263370273281855972867a73ef5a82 -#zme nodeInfo: 0141 d3 9c01 041001 - use strict; use warnings; @@ -34,13 +28,18 @@ sub ZWCUL_ProcessSendStack($); use vars qw(%zwave_id2class); my %sets = ( - "reopen" => "", + "reopen" => { cmd=>"" }, + "led" => { cmd=>"l%02x", param=>{ on=>1, off=>0, blink=>2 } }, + "addNode" => { cmd=>"x%x", param => { on=>1, off=>0, onSec=>2 } }, + "addNodeId" => { cmd=>"x%x" }, + "removeNode" => { cmd=>"x%x", param => { on=>1, off=>0 } }, "raw" => { cmd=> "%s" }, ); my %gets = ( "homeId" => { cmd=> "zi", regex => "^. [A-F0-9]{8} [A-F0-9]{2}\$" }, "version" => { cmd=> "V", regex => "^V " }, + "nodeInfo" => { cmd=> "x%x" }, "raw" => { cmd=> "%s", regex => ".*" } ); @@ -83,8 +82,11 @@ ZWCUL_Define($$) $hash->{homeId} = lc($a[3]); $hash->{homeIdSet} = lc($a[3]); $hash->{nodeIdHex} = lc($a[4]); - $hash->{initString} = ($hash->{homeIdSet} =~ m/^0*$/ ? "zm":"zr"); + $hash->{initString} = ($hash->{homeIdSet} =~ m/^0*$/ ? "zm4":"zr4"); + $hash->{baudRate} = "40k"; $hash->{monitor} = 1 if($hash->{homeIdSet} eq "00000000"); + setReadingsVal($hash, "homeId", # ZWDongle compatibility + "HomeId:$hash->{homeId} CtrlNodeId:$hash->{nodeIdHex}", TimeNow()); $hash->{Clients} = ":ZWave:STACKABLE_CC:"; my %matchList = ( "1:ZWave" => ".*", @@ -156,6 +158,14 @@ ZWCUL_Undef($$) return undef; } +sub +ZWCUL_tmp9600($$) +{ + my ($hash, $on) = @_; + $hash->{baudRate} = ($on ? "9600" : AttrVal($hash->{NAME},"dataRate","40k")); + ZWCUL_SimpleWrite($hash, $on ? $on : $hash->{initString}); +} + ##################################### sub ZWCUL_cmd($$@) @@ -167,8 +177,11 @@ ZWCUL_cmd($$@) my $cmdName = shift @a; if(!defined($cmdList->{$cmdName})) { - return "Unknown argument $cmdName, choose one of " . - join(" ",sort keys %{$cmdList}); + my @r; + map { my $p = $cmdList->{$_}{param}; + push @r,($p ? "$_:".join(",",sort keys %{$p}) : $_) } + sort keys %{$cmdList}; + return "Unknown argument $cmdName, choose one of ".join(" ",@r); } Log3 $hash, 4, "ZWCUL $type $name $cmdName ".join(" ",@a); @@ -185,6 +198,45 @@ ZWCUL_cmd($$@) my @ca = split("%", $cmd, -1); my $nargs = int(@ca)-1; return "$type $name $cmdName needs $nargs arguments" if($nargs != int(@a)); + + my $param = $cmdList->{$cmdName}{param}; + if($param) { + return "invalid parameter $a[0] for $cmdName" if(!defined($param->{$a[0]})); + $a[0] = $param->{$a[0]}; + } + + if($cmdName =~ m/^addNode/) { + delete $hash->{removeNode}; + delete $hash->{addNode}; + if($cmdName eq "addNodeId") { + $hash->{addNode} = sprintf("%02x", $a[0]); + } else { + $hash->{addNode} = ZWCUL_getNextNodeId($hash) if($a[0]); + $hash->{addSecure} = 1 if($a[0] == 2); + } + Log3 $hash, 3, "ZWCUL going to assigning new node id $hash->{addNode}"; + ZWCUL_tmp9600($hash, $a[0] ? "zm9" : 0); # expect random homeId + return; + } + if($cmdName eq "removeNode") { + delete $hash->{addNode}; + delete $hash->{removeNode}; + $hash->{removeNode} = $a[0] if($a[0]); + ZWCUL_tmp9600($hash, $a[0] ? "zr9" : 0); + return; + } + if($cmdName eq "nodeInfo") { + my $node = ZWCUL_getNode($hash, sprintf("%02x", $a[0])); + return "Node with decimal id $a[0] not found" if(!$node); + my $ni = ReadingsVal($node->{NAME}, "nodeInfo", undef); + return "No nodeInfo present" if(!$ni); + $ni = "0141${ni}041001"; # TODO: Remove fixed values + my @r = map { ord($_) } split("", pack('H*', $ni)); + my $msg = zwlib_parseNodeInfo(@r); + setReadingsVal($hash, "nodeInfo_".$a[0], $msg, TimeNow()); + return $msg; + } + $cmd = sprintf($cmd, @a); ZWCUL_SimpleWrite($hash, $cmd); @@ -237,7 +289,7 @@ ZWCUL_Write($$$) $th->{sentIdx} = 0 if(!$th->{sentIdx} || $th->{sentIdx} == 15); $th->{sentIdx}++; - my $s100 = (AttrVal($hash->{NAME}, "dataRate", "40k") eq "100k"); + my $s100 = ($hash->{baudRate} eq "100k"); $msg = sprintf("%s%s41%02x%02x%s%s", $fn, $hash->{nodeIdHex}, $th->{sentIdx}, @@ -277,6 +329,46 @@ ZWCUL_Read($@) return undef; } +sub +ZWCUL_getNode($$) +{ + my ($hash, $id) = @_; + my @l = devspec2array(sprintf("TYPE=ZWave:". + "FILTER=homeId=%s:FILTER=nodeIdHex=%s", $hash->{homeId}, $id)); + return undef if(!int(@l)); + return $defs{$l[0]}; +} + +sub +ZWCUL_getNextNodeId($) +{ + my ($hash) = @_; + my @l = devspec2array(sprintf(".*:FILTER=homeId=%s",$hash->{homeId})); + my %h = map { $defs{$_}{nodeIdHex} => 1 } @l; + for(my $i = 1; $i <= 232; $i++) { + my $s = sprintf("%02x", $i); + return $s if(!$h{$s}); + } + Log 1, "NOTE: NO MORE nodeIDs available"; + return "ff"; +} + +sub +ZWCUL_assignId($$$$) +{ + my ($hash, $oldNodeId, $newHomeId, $newNodeId) = @_; + + my $myHash; + my $key = "$hash->{homeIdSet} $oldNodeId"; + if(!defined($modules{ZWave}{defptr}{$key})) { + $modules{ZWave}{defptr}{$key} = { nodeIdHex => $oldNodeId }; + $myHash = 1; + } + ZWCUL_Write($hash, $hash->{homeIdSet}, + sprintf("0013%s080103%s%s####", $oldNodeId, $newNodeId, $newHomeId)); + delete $modules{ZWave}{defptr}{$key} if($myHash); +} + sub ZWCUL_Parse($$$$$) { @@ -298,7 +390,7 @@ ZWCUL_Parse($$$$$) $rmsg = lc($rmsg); my $me = $hash->{NAME}; - my $s100 = (AttrVal($me, "dataRate", "40k") eq "100k"); + my $s100 = ($hash->{baudRate} eq "100k"); if($rmsg =~ m/^za(..)$/) { Log3 $hash, 5, "$me sent ACK to $1"; @@ -358,6 +450,20 @@ ZWCUL_Parse($$$$$) $hash->{homeId} = $H; # Fake homeId for monitor mode if(length($P)) { + + if($hash->{removeNode} && $T eq "ff") { + ZWCUL_assignId($hash, $S, "00000000", "00"); + $hash->{removeNode} = $S; + return; + } + + if($hash->{addNode} && $T eq "ff" && $S eq "00" && + $P =~ m/^0101(......)(..)..(.*)/) { + ZWCUL_assignId($hash, "00", $hash->{homeIdSet}, $hash->{addNode}); + $hash->{addNodeParam} = $P; + return; + } + $rmsg = sprintf("0004%s%s%02x%s", $S, $S, length($P)/2, $P); my $th = $modules{ZWave}{defptr}{"$H $S"}; @@ -366,7 +472,42 @@ ZWCUL_Parse($$$$$) $th = $modules{ZWave}{defptr}{"$H $S"}; } - } else { + + } else { # ACK + if($hash->{removeNode} && $hash->{removeNode} eq $S) { ############# + Log3 $hash, 3, "Node $S excluded from network"; + delete $hash->{removeNode}; + ZWCUL_tmp9600($hash, 0); + return; + } + + if($hash->{addNode} && $S eq "00") { ############# + $hash->{addNodeParam} =~ m/^0101(......)(..)..(.*)/; + my ($nodeInfo, $type6, $classes) = ($1, $2, $3); + + ZWCUL_tmp9600($hash, 0); + $hash->{homeId} = $hash->{homeIdSet}; + Dispatch($hash, sprintf("004a0003%s####%s##%s", + $hash->{addNode}, $type6, $classes), \%addvals); + + my $node = ZWCUL_getNode($hash, $hash->{addNode}); + if($node) { # autocreated a node + readingsSingleUpdate($node, "nodeInfo", $nodeInfo, 0); + Dispatch($hash, sprintf("004a0005%s##", $hash->{addNode}), \%addvals); + } + + delete $hash->{addNode}; + delete $hash->{addNodeParam}; + return; + } + + if($hash->{addNode} && $hash->{addNode} eq $S) { # Another hack + Log3 $hash, 3, "ZWCUL node ".hex($S)." excluded from network"; + delete $hash->{removeNode}; + ZWCUL_tmp9600($hash, 0); + return; + } + $rmsg = sprintf("0013%s00", $S); } @@ -472,7 +613,8 @@ ZWCUL_Attr($$$$) my $sfx = ($value eq "100k" ? "1" : ($value eq "9600" ? "9" : "4")); $hash->{initString} = ($hash->{homeIdSet} =~ m/^0*$/ ? "zm$sfx":"zr$sfx"); - ZWCUL_DoInit($hash); + $hash->{baudRate} = $value; + ZWCUL_SimpleWrite($hash, $hash->{initString}); } @@ -542,10 +684,31 @@ ZWCUL_Ready($) First close and then open the device. Used for debugging purposes. +
  • led [on|off|blink]
    + Set the LED on the CUL. +
  • +
  • raw
    send a raw string to culfw
  • +
  • addNode [on|onSec|off]
    + Activate (or deactivate) inclusion mode. The CUL will switch to dataRate + 9600 until terminating this mode with off, or a node is included. If onSec + is specified, the ZWCUL networkKey ist set, and the device supports the + SECURITY class, then a secure inclusion is attempted. +
  • + +
  • addNodeId <decimalNodeId>
    + Activate inclusion mode, and assign decimalNodeId to the next node. + To deactivate this mode, use addNode off. +
  • + +
  • removeNode [onNw|on|off]
    + Activate (or deactivate) exclusion mode. Like with addNode, the CUL will + switch temporarily to dataRate 9600, potentially missing some packets sent + on higher dataRates. Note: the corresponding fhem device have to be + deleted manually.

  • @@ -555,7 +718,8 @@ ZWCUL_Ready($) diff --git a/FHEM/00_ZWDongle.pm b/FHEM/00_ZWDongle.pm index 8ab2d583a..93596f087 100755 --- a/FHEM/00_ZWDongle.pm +++ b/FHEM/00_ZWDongle.pm @@ -308,18 +308,7 @@ ZWDongle_Get($@) if($id eq "00") { $msg = "node $a[0] is not present"; } else { - my @list; - my @type5 = qw( CONTROLLER STATIC_CONTROLLER SLAVE ROUTING_SLAVE); - push @list, $type5[$r[5]-1] if($r[5]>0 && $r[5] <= @type5); - push @list, $zw_type6{$id} if($zw_type6{$id}); - push @list, ($r[2] & 0x80) ? "listening" : "sleeping"; - push @list, "frequentListening:" . ($r[3] & ( 0x20 | 0x40 )); - push @list, "beaming:" . ($r[3] & 0x10); - push @list, "routing" if($r[2] & 0x40); - push @list, "40kBaud" if(($r[2] & 0x38) == 0x10); - push @list, "Vers:" . (($r[2]&0x7)+1); - push @list, "Security:" . ($r[3]&0x1); - $msg = join(" ", @list); + $msg = zwlib_parseNodeInfo(@r); } } elsif($cmd eq "random") { ############################ diff --git a/FHEM/ZWLib.pm b/FHEM/ZWLib.pm index 691cc72d8..445109ce7 100644 --- a/FHEM/ZWLib.pm +++ b/FHEM/ZWLib.pm @@ -187,4 +187,22 @@ zwlib_checkSum_16($) # CRC16-CCITT (Polynom: 1021) return sprintf("%04x", $crc); } +sub +zwlib_parseNodeInfo(@) +{ + my @r = @_; + my @list; + my @type5 = qw( CONTROLLER STATIC_CONTROLLER SLAVE ROUTING_SLAVE); + push @list, $type5[$r[5]-1] if($r[5]>0 && $r[5] <= @type5); + push @list, $zw_type6{$id} if($zw_type6{$id}); + push @list, ($r[2] & 0x80) ? "listening" : "sleeping"; + push @list, "frequentListening:" . ($r[3] & ( 0x20 | 0x40 )); + push @list, "beaming:" . ($r[3] & 0x10); + push @list, "routing" if($r[2] & 0x40); + push @list, "40kBaud" if(($r[2] & 0x38) == 0x10); + push @list, "Vers:" . (($r[2]&0x7)+1); + push @list, "Security:" . ($r[3]&0x1); + return join(" ", @list); +} + 1;