From 87d9bc437243f1a031758c29749d434ceebab128 Mon Sep 17 00:00:00 2001 From: rudolfkoenig <> Date: Sun, 6 Dec 2015 19:44:51 +0000 Subject: [PATCH] 00_ZWCUL.pm: first check in (Forum #44905) git-svn-id: https://svn.fhem.de/fhem/trunk@10116 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/00_ZWCUL.pm | 495 +++++++++++++++++++++++++++++++++++++++ fhem/FHEM/00_ZWDongle.pm | 178 +------------- fhem/FHEM/10_ZWave.pm | 13 +- fhem/FHEM/ZWLib.pm | 161 +++++++++++++ fhem/fhem.pl | 4 +- 5 files changed, 673 insertions(+), 178 deletions(-) create mode 100755 fhem/FHEM/00_ZWCUL.pm create mode 100644 fhem/FHEM/ZWLib.pm diff --git a/fhem/FHEM/00_ZWCUL.pm b/fhem/FHEM/00_ZWCUL.pm new file mode 100755 index 000000000..c5e6ab1eb --- /dev/null +++ b/fhem/FHEM/00_ZWCUL.pm @@ -0,0 +1,495 @@ +############################################## +# $Id$ +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); + +use ZWLib; +use DevIo; + +sub ZWCUL_Parse($$$$); +sub ZWCUL_Read($@); +sub ZWCUL_ReadAnswer($$$); +sub ZWCUL_Ready($); +sub ZWCUL_SimpleWrite($$); +sub ZWCUL_Write($$$); +sub ZWCUL_ProcessSendStack($); + +use vars qw(%zwave_id2class); + +my %sets = ( + "reopen" => "", + "raw" => { cmd=> "%s" }, +); + +my %gets = ( + "homeId" => { cmd=> "zi", regex => "^. [A-F0-9]{8} [A-F0-9]{2}\$" }, + "version" => { cmd=> "V", regex => "^V " }, + "raw" => { cmd=> "%s", regex => ".*" } +); + +sub +ZWCUL_Initialize($) +{ + my ($hash) = @_; + +# Provider + $hash->{ReadFn} = "ZWCUL_Read"; + $hash->{WriteFn} = "ZWCUL_Write"; + $hash->{ReadyFn} = "ZWCUL_Ready"; + $hash->{ReadAnswerFn} = "ZWCUL_ReadAnswer"; + +# Normal devices + $hash->{DefFn} = "ZWCUL_Define"; + $hash->{SetFn} = "ZWCUL_Set"; + $hash->{GetFn} = "ZWCUL_Get"; + $hash->{AttrFn} = "ZWCUL_Attr"; + $hash->{UndefFn} = "ZWCUL_Undef"; + $hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 model disable:0,1 "; + "networkKey"; +} + +##################################### +sub +ZWCUL_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + return "wrong syntax: define ZWCUL device homeId ctrlId" if(@a != 5); + return "wrong syntax: homeId is 8 digit hex" if($a[3] !~ m/^[0-9A-F]{8}$/); + return "wrong syntax: ctrlId is 2 digit hex" if($a[4] !~ m/^[0-9A-F]{2}$/); + + DevIo_CloseDev($hash); + + my $name = $a[0]; + my $dev = $a[2]; + $hash->{homeId} = $a[3]; + $hash->{homeIdSet} = $a[3]; + $hash->{nodeIdHex} = $a[4]; + + $hash->{Clients} = ":ZWave:STACKABLE_CC:"; + my %matchList = ( "1:ZWave" => ".*", + "2:STACKABLE_CC"=>"^\\*"); + $hash->{MatchList} = \%matchList; + + if($dev eq "none") { + Log3 $name, 1, "$name device is none, commands will be echoed only"; + $attr{$name}{dummy} = 1; + readingsSingleUpdate($hash, "state", "dummy", 1); + return undef; + + } elsif($dev !~ m/@/) { + $def .= "\@9600"; # default baudrate + + } + + $hash->{DeviceName} = $dev; + return DevIo_OpenDev($hash, 0, "ZWCUL_DoInit"); +} + +##################################### +sub +ZWCUL_DoInit($) +{ + my $hash = shift; + my $name = $hash->{NAME}; + + $hash->{PARTIAL} = ""; + + $hash->{RA_Timeout} = 0.5; # Clear the pipe + for(;;) { + my ($err, undef) = ZWCUL_ReadAnswer($hash, "Clear", "wontmatch"); + last if($err && ($err =~ m/^Timeout/ || $err =~ m/No FD/)); + } + delete($hash->{RA_Timeout}); + + my ($err, $ver, $try) = ("", "", 0); + while($try++ < 3 && $ver !~ m/^V/) { + ZWCUL_SimpleWrite($hash, "V"); + ($err, $ver) = ZWCUL_ReadAnswer($hash, "Version", "^V"); + return "$name: $err" if($err && ($err !~ m/Timeout/ || $try == 3)); + $ver = "" if(!$ver); + } + if($ver !~ m/^V/) { + $attr{$name}{dummy} = 1; + my $msg = "Not an CUL device, got for V: $ver"; + Log3 $name, 1, $msg; + return $msg; + } + $ver =~ s/[\r\n]//g; + $hash->{VERSION} = $ver; + + ZWCUL_SimpleWrite($hash, "zi".$hash->{homeIdSet}.$hash->{nodeIdHex}); + ZWCUL_SimpleWrite($hash, $hash->{homeIdSet} =~ m/^0*$/ ? "zm" : "zr"); + + readingsSingleUpdate($hash, "state", "Initialized", 1); + return undef; +} + + +##################################### +sub +ZWCUL_Undef($$) +{ + my ($hash,$arg) = @_; + ZWCUL_SimpleWrite($hash, "zx"); + DevIo_CloseDev($hash); + return undef; +} + +##################################### +sub +ZWCUL_cmd($$@) +{ + my ($type, $cmdList, $hash, @a) = @_; + my $name = shift @a; + + return "\"$type $name\" needs at least one parameter" if(@a < 1); + my $cmdName = shift @a; + + if(!defined($cmdList->{$cmdName})) { + return "Unknown argument $cmdName, choose one of " . + join(",",sort keys %{$cmdList}); + } + + Log3 $hash, 4, "ZWCUL $type $name $cmdName ".join(" ",@a); + if($cmdName eq "reopen") { + return if(AttrVal($name, "dummy",undef) || AttrVal($name, "disable",undef)); + delete $hash->{NEXT_OPEN}; + DevIo_CloseDev($hash); + sleep(1); + DevIo_OpenDev($hash, 0, "ZWCUL_DoInit"); + return; + } + + my $cmd = $cmdList->{$cmdName}{cmd}; + my @ca = split("%", $cmd, -1); + my $nargs = int(@ca)-1; + return "$type $name $cmdName needs $nargs arguments" if($nargs != int(@a)); + $cmd = sprintf($cmd, @a); + ZWCUL_SimpleWrite($hash, $cmd); + + return undef if($type eq "set"); + + my $re = $cmdList->{$cmdName}{regexp}; + my ($e, $d) = ZWCUL_ReadAnswer($hash, $cmdName, $cmdList->{$cmdName}{regexp}); + return $e if($e); + return $d; +} + +sub ZWCUL_Set() { return ZWCUL_cmd("set", \%sets, @_); }; +sub ZWCUL_Get() { return ZWCUL_cmd("get", \%gets, @_); }; + +##################################### +sub +ZWCUL_SimpleWrite($$) +{ + my ($hash, $msg) = @_; + return if(!$hash); + + my $name = $hash->{NAME}; + Log3 $name, 1, "SW: $msg"; + $msg .= "\n"; + + $hash->{USBDev}->write($msg) if($hash->{USBDev}); + syswrite($hash->{TCPDev}, $msg) if($hash->{TCPDev}); + syswrite($hash->{DIODev}, $msg) if($hash->{DIODev}); + select(undef, undef, undef, 0.001); +} + +##################################### +sub +ZWCUL_Write($$$) +{ + my ($hash,$fn,$msg) = @_; + + Log3 $hash, 5, "ZWCUL_Write $fn $msg"; + if($msg =~ m/0013(..)(..)(.*)(....)/) { + my ($t,$l,$p) = ($1,$2,$3); + my $th = $modules{ZWave}{defptr}{"$fn $t"}; + if(!$th) { + Log 1, "ZWCUL: no device found for $fn $t"; + return; + } + $th->{sentIdx} = ($th->{sentIdx}++ % 16); + $msg = sprintf("%s%s41%02x%02x%s%s", + $fn, $hash->{nodeIdHex}, $th->{sentIdx}, + length($p)/2+10, $th->{nodeIdHex}, $p); + $msg .= zwlib_checkSum($msg); + ZWCUL_SimpleWrite($hash, "zs".$msg); + } +} + +##################################### +# called from the global loop, when the select for hash->{FD} reports data +sub +ZWCUL_Read($@) +{ + my ($hash, $local, $regexp) = @_; + + my $buf = (defined($local) ? $local : DevIo_SimpleRead($hash)); + return "" if(!defined($buf)); + my $name = $hash->{NAME}; + + my $culdata = $hash->{PARTIAL}; + #Log3 $name, 5, "ZWCUL/RAW: $culdata/$buf"; + $culdata .= $buf; + + while($culdata =~ m/\n/) { + my $rmsg; + ($rmsg,$culdata) = split("\n", $culdata, 2); + $rmsg =~ s/\r//; + $hash->{PARTIAL} = $culdata; # for recursive calls + return $rmsg + if(defined($local) && (!defined($regexp) || ($rmsg =~ m/$regexp/))); + ZWCUL_Parse($hash, $hash, $name, $rmsg) if($rmsg); + $culdata = $hash->{PARTIAL}; + } + $hash->{PARTIAL} = $culdata; +} + +sub +ZWCUL_Parse($$$$) +{ + my ($hash, $iohash, $name, $rmsg) = @_; + + if($rmsg =~ m/^\*/) { # STACKABLE_CC + Dispatch($hash, $rmsg, undef); + return; + } + + $hash->{"${name}_MSGCNT"}++; + $hash->{"${name}_TIME"} = TimeNow(); + # showtime attribute + readingsSingleUpdate($hash, "state", $hash->{READINGS}{state}{VAL}, 0); + $hash->{RAWMSG} = $rmsg; + my %addvals = (RAWMSG => $rmsg); + + $rmsg = lc($rmsg) if($rmsg =~ m/^z/); + if($rmsg =~ m/^z(........)(..)(..)(..)(..)(..)(.*)(..)$/) { + + my ($H, $S, $F, $f, $L, $T, $P, $C) = ($1,$2,$3,$4,$5,$6,$7,$8); + Log3 $hash, 5, "$H S:$S F:$F f:$f L:$L T:$T P:$P C:$C"; + + $hash->{homeId} = $H; + + if(length($P)) { + $rmsg = sprintf("0004%s%s%02x%s", $S, $S, length($P)/2, $P); + my $ctrlIsTgt = ($T eq $hash->{nodeIdHex}); + my $th = $modules{ZWave}{defptr}{"$H $S"}; + + if(!$ctrlIsTgt && !$th) { + DoTrigger("global", "UNDEFINED ZWNode_$1_$2 ZWave $1 ".hex($2)); + $th = $modules{ZWave}{defptr}{"$1 $2"}; + } + + # Auto-Add classes + my $pcl = $zwave_id2class{substr($P, 0, 2)}; + if($th && $pcl) { + my $cl = AttrVal($th->{NAME},"classes",""); + if($cl !~ m/\b$pcl\b/) { + $attr{$th->{NAME}}{classes} = "$cl $pcl"; + } + } + + } else { + $rmsg = sprintf("0013%s00", $6); + + } + } + Dispatch($hash, $rmsg, \%addvals); +} + +##################################### +# This is a direct read for commands like get +sub +ZWCUL_ReadAnswer($$$) +{ + my ($hash, $arg, $regexp) = @_; + Log3 $hash, 4, "ZWCUL_ReadAnswer arg:$arg regexp:".($regexp ? $regexp:""); + return ("No FD (dummy device?)", undef) + if(!$hash || ($^O !~ /Win/ && !defined($hash->{FD}))); + my $to = ($hash->{RA_Timeout} ? $hash->{RA_Timeout} : 1); + + for(;;) { + + my $buf; + if($^O =~ m/Win/ && $hash->{USBDev}) { + $hash->{USBDev}->read_const_time($to*1000); # set timeout (ms) + # Read anstatt input sonst funzt read_const_time nicht. + $buf = $hash->{USBDev}->read(999); + return ("Timeout reading answer for get $arg", undef) + if(length($buf) == 0); + + } else { + if(!$hash->{FD}) { + Log3 $hash, 1, "ZWCUL_ReadAnswer: device lost"; + return ("Device lost when reading answer for get $arg", undef); + } + + my $rin = ''; + vec($rin, $hash->{FD}, 1) = 1; + my $nfound = select($rin, undef, undef, $to); + if($nfound < 0) { + my $err = $!; + Log3 $hash, 5, "ZWCUL_ReadAnswer: nfound < 0 / err:$err"; + next if ($err == EAGAIN() || $err == EINTR() || $err == 0); + DevIo_Disconnected($hash); + return("ZWCUL_ReadAnswer $arg: $err", undef); + } + + if($nfound == 0){ + Log3 $hash, 5, "ZWCUL_ReadAnswer: select timeout"; + return ("Timeout reading answer for get $arg", undef); + } + + $buf = DevIo_SimpleRead($hash); + if(!defined($buf)){ + Log3 $hash, 1,"ZWCUL_ReadAnswer: no data read"; + return ("No data", undef); + } + } + + my $ret = ZWCUL_Read($hash, $buf, $regexp); + if(defined($ret)){ + Log3 $hash, 4, "ZWCUL_ReadAnswer for $arg: $ret"; + return (undef, $ret); + } + } +} + +##################################### +sub +ZWCUL_Attr($$$$) +{ + my ($cmd, $name, $attr, $value) = @_; + my $hash = $defs{$name}; + + if($attr eq "disable") { + if($cmd eq "set" && ($value || !defined($value))) { + DevIo_CloseDev($hash) if(!AttrVal($name,"dummy",undef)); + readingsSingleUpdate($hash, "state", "disabled", 1); + + } else { + if(AttrVal($name,"dummy",undef)) { + readingsSingleUpdate($hash, "state", "dummy", 1); + return; + } + DevIo_OpenDev($hash, 0, "ZWCUL_DoInit"); + + } + + } elsif($attr eq "networkKey" && $cmd eq "set") { + if(!$value || $value !~ m/^[0-9A-F]{32}$/i) { + return "attr $name networkKey: not a hex string with a length of 32"; + } + return; + } + + return undef; + +} + +##################################### +sub +ZWCUL_Ready($) +{ + my ($hash) = @_; + + return undef if (IsDisabled($hash->{NAME})); + + return DevIo_OpenDev($hash, 1, "ZWCUL_DoInit") + if(ReadingsVal($hash->{NAME}, "state","") eq "disconnected"); + + # This is relevant for windows/USB only + my $po = $hash->{USBDev}; + if($po) { + my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status; + return ($InBytes>0); + } + return 0; +} + + +1; + +=pod +=begin html + + +

ZWCUL

+ + + +=end html +=cut diff --git a/fhem/FHEM/00_ZWDongle.pm b/fhem/FHEM/00_ZWDongle.pm index f2a0fd31e..225d74d9b 100755 --- a/fhem/FHEM/00_ZWDongle.pm +++ b/fhem/FHEM/00_ZWDongle.pm @@ -1,15 +1,11 @@ ############################################## # $Id$ -# TODO: -# - routing commands -# - one command to create a fhem device for all nodeList entries -# - inclusion mode active only for a given time (pairForSec) -# - use central readings functions package main; use strict; use warnings; use Time::HiRes qw(gettimeofday); +use ZWLib; sub ZWDongle_Parse($$$); sub ZWDongle_Read($@); @@ -55,124 +51,6 @@ my %gets = ( "raw" => "%s", # hex ); -# Known controller function. -# Note: Known != implemented, see %sets & %gets for the implemented ones. -use vars qw(%zw_func_id); -use vars qw(%zw_type6); - -%zw_func_id= ( - '02' => 'SERIAL_API_GET_INIT_DATA', - '03' => 'SERIAL_API_APPL_NODE_INFORMATION', - '04' => 'APPLICATION_COMMAND_HANDLER', - '05' => 'ZW_GET_CONTROLLER_CAPABILITIES', - '06' => 'SERIAL_API_SET_TIMEOUTS', - '07' => 'SERIAL_API_GET_CAPABILITIES', - '08' => 'SERIAL_API_SOFT_RESET', - '10' => 'ZW_SET_R_F_RECEIVE_MODE', - '11' => 'ZW_SET_SLEEP_MODE', - '12' => 'ZW_SEND_NODE_INFORMATION', - '13' => 'ZW_SEND_DATA', - '14' => 'ZW_SEND_DATA_MULTI', - '15' => 'ZW_GET_VERSION', - '16' => 'ZW_SEND_DATA_ABORT', - '17' => 'ZW_R_F_POWER_LEVEL_SET', - '18' => 'ZW_SEND_DATA_META', - '1c' => 'ZW_GET_RANDOM', - '20' => 'MEMORY_GET_ID', - '21' => 'MEMORY_GET_BYTE', - '22' => 'MEMORY_PUT_BYTE', - '23' => 'MEMORY_GET_BUFFER', - '24' => 'MEMORY_PUT_BUFFER', - '27' => 'FLASH_AUTO_PROG_SET', - '29' => 'NVM_GET_ID', - '2a' => 'NVM_EXT_READ_LONG_BUFFER', - '2b' => 'NVM_EXT_WRITE_LONG_BUFFER', - '2c' => 'NVM_EXT_READ_LONG_BYTE', - '2d' => 'NVM_EXT_WRITE_LONG_BYTE', - '30' => 'CLOCK_SET', - '31' => 'CLOCK_GET', - '32' => 'CLOCK_COMPARE', - '33' => 'RTC_TIMER_CREATE', - '34' => 'RTC_TIMER_READ', - '35' => 'RTC_TIMER_DELETE', - '36' => 'RTC_TIMER_CALL', - '40' => 'ZW_SET_LEARN_NODE_STATE', - '41' => 'ZW_GET_NODE_PROTOCOL_INFO', - '42' => 'ZW_SET_DEFAULT', - '43' => 'ZW_NEW_CONTROLLER', - '44' => 'ZW_REPLICATION_COMMAND_COMPLETE', - '45' => 'ZW_REPLICATION_SEND_DATA', - '46' => 'ZW_ASSIGN_RETURN_ROUTE', - '47' => 'ZW_DELETE_RETURN_ROUTE', - '48' => 'ZW_REQUEST_NODE_NEIGHBOR_UPDATE', - '49' => 'ZW_APPLICATION_UPDATE', - '4a' => 'ZW_ADD_NODE_TO_NETWORK', - '4b' => 'ZW_REMOVE_NODE_FROM_NETWORK', - '4c' => 'ZW_CREATE_NEW_PRIMARY', - '4d' => 'ZW_CONTROLLER_CHANGE', - '50' => 'ZW_SET_LEARN_MODE', - '51' => 'ZW_ASSIGN_SUC_RETURN_ROUTE', - '52' => 'ZW_ENABLE_SUC', - '53' => 'ZW_REQUEST_NETWORK_UPDATE', - '54' => 'ZW_SET_SUC_NODE_ID', - '55' => 'ZW_DELETE_SUC_RETURN_ROUTE', - '56' => 'ZW_GET_SUC_NODE_ID', - '57' => 'ZW_SEND_SUC_ID', - '59' => 'ZW_REDISCOVERY_NEEDED', - '5e' => 'ZW_EXPLORE_REQUEST_INCLUSION', - '60' => 'ZW_REQUEST_NODE_INFO', - '61' => 'ZW_REMOVE_FAILED_NODE_ID', - '62' => 'ZW_IS_FAILED_NODE', - '63' => 'ZW_REPLACE_FAILED_NODE', - '70' => 'TIMER_START', - '71' => 'TIMER_RESTART', - '72' => 'TIMER_CANCEL', - '73' => 'TIMER_CALL', - '80' => 'GET_ROUTING_TABLE_LINE', - '81' => 'GET_T_X_COUNTER', - '82' => 'RESET_T_X_COUNTER', - '83' => 'STORE_NODE_INFO', - '84' => 'STORE_HOME_ID', - '90' => 'LOCK_ROUTE_RESPONSE', - '91' => 'ZW_SEND_DATA_ROUTE_DEMO', - '95' => 'SERIAL_API_TEST', - 'a0' => 'SERIAL_API_SLAVE_NODE_INFO', - 'a1' => 'APPLICATION_SLAVE_COMMAND_HANDLER', - 'a2' => 'ZW_SEND_SLAVE_NODE_INFO', - 'a3' => 'ZW_SEND_SLAVE_DATA', - 'a4' => 'ZW_SET_SLAVE_LEARN_MODE', - 'a5' => 'ZW_GET_VIRTUAL_NODES', - 'a6' => 'ZW_IS_VIRTUAL_NODE', - 'b6' => 'ZW_WATCHDOG_ENABLE', - 'b7' => 'ZW_WATCHDOG_DISABLE', - 'b8' => 'ZW_WATCHDOG_CHECK', - 'b9' => 'ZW_SET_EXT_INT_LEVEL', - 'ba' => 'ZW_RF_POWERLEVEL_GET', - 'bb' => 'ZW_GET_NEIGHBOR_COUNT', - 'bc' => 'ZW_ARE_NODES_NEIGHBOURS', - 'bd' => 'ZW_TYPE_LIBRARY', - 'be' => 'ZW_SEND_TEST_FRAME', - 'bf' => 'ZW_GET_PROTOCOL_STATUS', - 'd0' => 'ZW_SET_PROMISCUOUS_MODE', - 'd2' => 'WATCHDOG_START', - 'd3' => 'WATCHDOG_STOP', - 'f2' => 'ZME_FREQ_CHANGE', - 'f4' => 'ZME_BOOTLOADER_FLASH', -); - -%zw_type6 = ( - '01' => 'GENERIC_CONTROLLER', '12' => 'SWITCH_REMOTE', - '02' => 'STATIC_CONTROLLER', '13' => 'SWITCH_TOGGLE', - '03' => 'AV_CONTROL_POINT', '20' => 'SENSOR_BINARY', - '06' => 'DISPLAY', '21' => 'SENSOR_MULTILEVEL', - '07' => 'GARAGE_DOOR', '22' => 'WATER_CONTROL', - '08' => 'THERMOSTAT', '30' => 'METER_PULSE', - '09' => 'WINDOW_COVERING', '40' => 'ENTRY_CONTROL', - '0F' => 'REPEATER_SLAVE', '50' => 'SEMI_INTEROPERABLE', - '10' => 'SWITCH_BINARY', 'ff' => 'NON_INTEROPERABLE', - '11' => 'SWITCH_MULTILEVEL', -); - sub ZWDongle_Initialize($) { @@ -314,7 +192,7 @@ ZWDongle_Set($@) my $nargs = int(@ca)-1; return "set $name $type needs $nargs arguments" if($nargs != int(@a)); - ZWDongle_Write($hash, "00", sprintf($cmd, @a)); + ZWDongle_Write($hash, "", "00".sprintf($cmd, @a)); return undef; } @@ -357,7 +235,7 @@ ZWDongle_Get($@) return "No $cmd for dummies" if(IsDummy($name)); my $out = sprintf($gets{$cmd}, @a); - ZWDongle_Write($hash, "00", $out); + ZWDongle_Write($hash, "", "00".$out); my $re = "^01".substr($out,0,2); # Start with <01><01> my ($err, $ret) = ZWDongle_ReadAnswer($hash, $cmd, $re); return $err if($err); @@ -449,7 +327,7 @@ ZWDongle_Get($@) } elsif($cmd eq "neighborList") { ############################ $msg =~ s/^....//; - $msg = ZWDongle_parseNeighborList($hash, $msg); + $msg = zwlib_parseNeighborList($hash, $msg); } @@ -499,29 +377,17 @@ ZWDongle_DoInit($) return undef; } -##################################### -sub -ZWDongle_CheckSum($) -{ - my ($data) = @_; - my $cs = 0xff; - map { $cs ^= ord($_) } split("", pack('H*', $data)); - return sprintf("%02x", $cs); -} - - ##################################### sub ZWDongle_Write($$$) { my ($hash,$fn,$msg) = @_; - Log3 $hash, 5, "ZWDongle_Write $fn $msg"; + Log3 $hash, 5, "ZWDongle_Write $msg ($fn)"; # assemble complete message - $msg = "$fn$msg"; $msg = sprintf("%02x%s", length($msg)/2+1, $msg); - $msg = "01$msg" . ZWDongle_CheckSum($msg); + $msg = "01$msg" . zwlib_checkSum($msg); push @{$hash->{SendStack}}, $msg; ZWDongle_ProcessSendStack($hash); @@ -667,7 +533,7 @@ ZWDongle_Read($@) my $rcs = substr($data, $l+2, 2); # Received Checksum $data = substr($data, $l+4); - my $ccs = ZWDongle_CheckSum("$len$msg"); # Computed Checksum + my $ccs = zwlib_checkSum("$len$msg"); # Computed Checksum if($rcs ne $ccs) { Log3 $name, 1, "$name: wrong checksum: received $rcs, computed $ccs for $len$msg"; @@ -835,32 +701,6 @@ ZWDongle_Ready($) return 0; } -sub -ZWDongle_parseNeighborList($$) -{ - my ($iodev, $data) = @_; - my $homeId = $iodev->{homeId}; - my $ioName = $iodev->{NAME}; - my @r = map { ord($_) } split("", pack('H*', $data)); - return "Bogus neightborList $data" if(int(@r) != 29); - - my @list; - my $ioId = ReadingsVal($ioName, "homeId", ""); - $ioId = $1 if($ioId =~ m/CtrlNodeId:(..)/); - for my $byte (0..28) { - my $bits = $r[$byte]; - for my $bit (0..7) { - if($bits & (1<<$bit)) { - my $dec = $byte*8+$bit+1; - my $hex = sprintf("%02x", $dec); - my $h = $modules{ZWave}{defptr}{"$homeId $hex"}; - push @list, ($hex eq $ioId ? $ioName : - ($h ? $h->{NAME} : "UNKNOWN_$dec")); - } - } - } - return @list ? join(" ", @list) : "empty"; -} 1; @@ -979,10 +819,10 @@ ZWDongle_parseNeighborList($$)
  • do_not_notify
  • model
  • disable
  • -
  • homeId
    +
  • homeId
    Stores the homeId of the dongle. Is a workaround for some buggy dongles, wich sometimes report a wrong/nonexisten homeId (Forum #35126)
  • -
  • networkKey
    +
  • networkKey
    Needed for secure inclusion, hex string with length of 32
  • diff --git a/fhem/FHEM/10_ZWave.pm b/fhem/FHEM/10_ZWave.pm index c49a86f0a..1ad47890e 100755 --- a/fhem/FHEM/10_ZWave.pm +++ b/fhem/FHEM/10_ZWave.pm @@ -9,6 +9,7 @@ use SetExtensions; use Compress::Zlib; use Time::HiRes qw( gettimeofday ); use HttpUtils; +use ZWLib; sub ZWave_Cmd($$@); sub ZWave_Get($@); @@ -20,11 +21,9 @@ sub ZWave_secStart($); sub ZWave_secEnd($); sub ZWave_configParseModel($;$); -use vars qw(%zw_func_id); -use vars qw(%zw_type6); use vars qw($FW_ME $FW_tp $FW_ss); +use vars qw(%zwave_id2class); -my %zwave_id2class; my %zwave_class = ( NO_OPERATION => { id => '00' }, BASIC => { id => '20', @@ -525,7 +524,7 @@ ZWave_Define($$) $modules{ZWave}{defptr}{"$homeId $id"} = $hash; my $proposed; if($init_done) { # Use the right device while inclusion is running - for my $p (devspec2array("TYPE=ZWDongle|FHEM2FHEM")) { + for my $p (devspec2array("TYPE=ZWDongle|ZWCUL|FHEM2FHEM")) { $proposed = $p if($defs{$p}{homeId} && $defs{$p}{homeId} eq $homeId); } } @@ -2597,7 +2596,7 @@ ZWave_wakeupTimer($$) my $nodeId = $hash->{nodeIdHex}; my $cmdEf = (AttrVal($hash->{NAME},"noExplorerFrames",0)==0 ? "25":"05"); # wakeupNoMoreInformation - IOWrite($hash, "00", "13${nodeId}028408${cmdEf}$nodeId"); + IOWrite($hash, $hash->{homeId}, "0013${nodeId}028408${cmdEf}$nodeId"); } delete $hash->{wakeupAlive}; @@ -2634,7 +2633,7 @@ ZWave_processSendStack($) return; } - IOWrite($hash, "00", $ss->[0]); + IOWrite($hash, $hash->{homeId}, "00".$ss->[0]); $ss->[0] = "sent:".$ss->[0]; $hash->{lastMsgSent} = gettimeofday(); @@ -2702,7 +2701,7 @@ ZWave_Parse($$@) my $hash = $modules{ZWave}{defptr}{"$homeId $id"}; my $name = ($hash ? $hash->{NAME} : "unknown"); - $msg = ZWDongle_parseNeighborList($iodev, $data); + $msg = zwlib_parseNeighborList($iodev, $data); readingsSingleUpdate($hash, "neighborList", $msg, 1) if($hash); return $msg if($srcCmd); diff --git a/fhem/FHEM/ZWLib.pm b/fhem/FHEM/ZWLib.pm new file mode 100644 index 000000000..3e344a84d --- /dev/null +++ b/fhem/FHEM/ZWLib.pm @@ -0,0 +1,161 @@ +############################################## +# $Id$ +package main; + +# Known controller function. +# Note: Known != implemented, see %sets & %gets for the implemented ones. +use vars qw(%zw_func_id); +use vars qw(%zw_type6); + +%zw_func_id= ( + '02' => 'SERIAL_API_GET_INIT_DATA', + '03' => 'SERIAL_API_APPL_NODE_INFORMATION', + '04' => 'APPLICATION_COMMAND_HANDLER', + '05' => 'ZW_GET_CONTROLLER_CAPABILITIES', + '06' => 'SERIAL_API_SET_TIMEOUTS', + '07' => 'SERIAL_API_GET_CAPABILITIES', + '08' => 'SERIAL_API_SOFT_RESET', + '10' => 'ZW_SET_R_F_RECEIVE_MODE', + '11' => 'ZW_SET_SLEEP_MODE', + '12' => 'ZW_SEND_NODE_INFORMATION', + '13' => 'ZW_SEND_DATA', + '14' => 'ZW_SEND_DATA_MULTI', + '15' => 'ZW_GET_VERSION', + '16' => 'ZW_SEND_DATA_ABORT', + '17' => 'ZW_R_F_POWER_LEVEL_SET', + '18' => 'ZW_SEND_DATA_META', + '1c' => 'ZW_GET_RANDOM', + '20' => 'MEMORY_GET_ID', + '21' => 'MEMORY_GET_BYTE', + '22' => 'MEMORY_PUT_BYTE', + '23' => 'MEMORY_GET_BUFFER', + '24' => 'MEMORY_PUT_BUFFER', + '27' => 'FLASH_AUTO_PROG_SET', + '29' => 'NVM_GET_ID', + '2a' => 'NVM_EXT_READ_LONG_BUFFER', + '2b' => 'NVM_EXT_WRITE_LONG_BUFFER', + '2c' => 'NVM_EXT_READ_LONG_BYTE', + '2d' => 'NVM_EXT_WRITE_LONG_BYTE', + '30' => 'CLOCK_SET', + '31' => 'CLOCK_GET', + '32' => 'CLOCK_COMPARE', + '33' => 'RTC_TIMER_CREATE', + '34' => 'RTC_TIMER_READ', + '35' => 'RTC_TIMER_DELETE', + '36' => 'RTC_TIMER_CALL', + '40' => 'ZW_SET_LEARN_NODE_STATE', + '41' => 'ZW_GET_NODE_PROTOCOL_INFO', + '42' => 'ZW_SET_DEFAULT', + '43' => 'ZW_NEW_CONTROLLER', + '44' => 'ZW_REPLICATION_COMMAND_COMPLETE', + '45' => 'ZW_REPLICATION_SEND_DATA', + '46' => 'ZW_ASSIGN_RETURN_ROUTE', + '47' => 'ZW_DELETE_RETURN_ROUTE', + '48' => 'ZW_REQUEST_NODE_NEIGHBOR_UPDATE', + '49' => 'ZW_APPLICATION_UPDATE', + '4a' => 'ZW_ADD_NODE_TO_NETWORK', + '4b' => 'ZW_REMOVE_NODE_FROM_NETWORK', + '4c' => 'ZW_CREATE_NEW_PRIMARY', + '4d' => 'ZW_CONTROLLER_CHANGE', + '50' => 'ZW_SET_LEARN_MODE', + '51' => 'ZW_ASSIGN_SUC_RETURN_ROUTE', + '52' => 'ZW_ENABLE_SUC', + '53' => 'ZW_REQUEST_NETWORK_UPDATE', + '54' => 'ZW_SET_SUC_NODE_ID', + '55' => 'ZW_DELETE_SUC_RETURN_ROUTE', + '56' => 'ZW_GET_SUC_NODE_ID', + '57' => 'ZW_SEND_SUC_ID', + '59' => 'ZW_REDISCOVERY_NEEDED', + '5e' => 'ZW_EXPLORE_REQUEST_INCLUSION', + '60' => 'ZW_REQUEST_NODE_INFO', + '61' => 'ZW_REMOVE_FAILED_NODE_ID', + '62' => 'ZW_IS_FAILED_NODE', + '63' => 'ZW_REPLACE_FAILED_NODE', + '70' => 'TIMER_START', + '71' => 'TIMER_RESTART', + '72' => 'TIMER_CANCEL', + '73' => 'TIMER_CALL', + '80' => 'GET_ROUTING_TABLE_LINE', + '81' => 'GET_T_X_COUNTER', + '82' => 'RESET_T_X_COUNTER', + '83' => 'STORE_NODE_INFO', + '84' => 'STORE_HOME_ID', + '90' => 'LOCK_ROUTE_RESPONSE', + '91' => 'ZW_SEND_DATA_ROUTE_DEMO', + '95' => 'SERIAL_API_TEST', + 'a0' => 'SERIAL_API_SLAVE_NODE_INFO', + 'a1' => 'APPLICATION_SLAVE_COMMAND_HANDLER', + 'a2' => 'ZW_SEND_SLAVE_NODE_INFO', + 'a3' => 'ZW_SEND_SLAVE_DATA', + 'a4' => 'ZW_SET_SLAVE_LEARN_MODE', + 'a5' => 'ZW_GET_VIRTUAL_NODES', + 'a6' => 'ZW_IS_VIRTUAL_NODE', + 'b6' => 'ZW_WATCHDOG_ENABLE', + 'b7' => 'ZW_WATCHDOG_DISABLE', + 'b8' => 'ZW_WATCHDOG_CHECK', + 'b9' => 'ZW_SET_EXT_INT_LEVEL', + 'ba' => 'ZW_RF_POWERLEVEL_GET', + 'bb' => 'ZW_GET_NEIGHBOR_COUNT', + 'bc' => 'ZW_ARE_NODES_NEIGHBOURS', + 'bd' => 'ZW_TYPE_LIBRARY', + 'be' => 'ZW_SEND_TEST_FRAME', + 'bf' => 'ZW_GET_PROTOCOL_STATUS', + 'd0' => 'ZW_SET_PROMISCUOUS_MODE', + 'd2' => 'WATCHDOG_START', + 'd3' => 'WATCHDOG_STOP', + 'f2' => 'ZME_FREQ_CHANGE', + 'f4' => 'ZME_BOOTLOADER_FLASH', +); + +%zw_type6 = ( + '01' => 'GENERIC_CONTROLLER', '12' => 'SWITCH_REMOTE', + '02' => 'STATIC_CONTROLLER', '13' => 'SWITCH_TOGGLE', + '03' => 'AV_CONTROL_POINT', '20' => 'SENSOR_BINARY', + '06' => 'DISPLAY', '21' => 'SENSOR_MULTILEVEL', + '07' => 'GARAGE_DOOR', '22' => 'WATER_CONTROL', + '08' => 'THERMOSTAT', '30' => 'METER_PULSE', + '09' => 'WINDOW_COVERING', '40' => 'ENTRY_CONTROL', + '0F' => 'REPEATER_SLAVE', '50' => 'SEMI_INTEROPERABLE', + '10' => 'SWITCH_BINARY', 'ff' => 'NON_INTEROPERABLE', + '11' => 'SWITCH_MULTILEVEL', +); + +sub +zwlib_parseNeighborList($$) +{ + my ($iodev, $data) = @_; + my $homeId = $iodev->{homeId}; + my $ioName = $iodev->{NAME}; + my @r = map { ord($_) } split("", pack('H*', $data)); + return "Bogus neightborList $data" if(int(@r) != 29); + + my @list; + my $ioId = ReadingsVal($ioName, "homeId", ""); + $ioId = $1 if($ioId =~ m/CtrlNodeId:(..)/); + for my $byte (0..28) { + my $bits = $r[$byte]; + for my $bit (0..7) { + if($bits & (1<<$bit)) { + my $dec = $byte*8+$bit+1; + my $hex = sprintf("%02x", $dec); + my $h = $modules{ZWave}{defptr}{"$homeId $hex"}; + push @list, ($hex eq $ioId ? $ioName : + ($h ? $h->{NAME} : "UNKNOWN_$dec")); + } + } + } + return @list ? join(" ", @list) : "empty"; +} + +##################################### +sub +zwlib_checkSum($) +{ + my ($data) = @_; + my $cs = 0xff; + map { $cs ^= ord($_) } split("", pack('H*', $data)); + return sprintf("%02x", $cs); +} + + +1; diff --git a/fhem/fhem.pl b/fhem/fhem.pl index 68e54974b..72f222ec7 100755 --- a/fhem/fhem.pl +++ b/fhem/fhem.pl @@ -1575,9 +1575,9 @@ CommandSet($$) foreach my $sdev (devspec2array($a[0], $cl)) { $a[0] = $sdev; - $defs{$sdev}->{CL} = $cl; + $defs{$sdev}->{CL} = $cl if($defs{$sdev}); my $ret = DoSet(@a); - delete $defs{$sdev}->{CL}; + delete $defs{$sdev}->{CL} if($defs{$sdev}); push @rets, $ret if($ret); }