############################################## # $Id$ # See ZWDongle.pm for inspiration package main; use strict; use warnings; use SetExtensions; sub ZWave_Parse($$@); sub ZWave_Set($@); sub ZWave_Get($@); sub ZWave_Cmd($$@); sub ZWave_ParseMeter($); sub ZWave_SetClasses($$$$); use vars qw(%zw_func_id); use vars qw(%zw_type6); my @zwave_models = qw( Everspring_AN1582 Everspring_AN1583 ); my %zwave_id2class; my %zwave_class = ( NO_OPERATION => { id => '00', }, BASIC => { id => '20', set => { basicValue => "01%02x", }, get => { basicStatus => "02", }, parse => { "..200.(.*)"=> '"basicReport:$1"',}, }, CONTROLLER_REPLICATION => { id => '21', }, APPLICATION_STATUS => { id => '22', }, ZIP_SERVICES => { id => '23', }, ZIP_SERVER => { id => '24', }, SWITCH_BINARY => { id => '25', set => { off => "0100", on => "01FF", reportOn => "03FF", reportOff => "0300", }, get => { swbStatus => "02", }, parse => { "03250300" => "state:off", "032503ff" => "state:on", }, } , SWITCH_MULTILEVEL => { id => '26', set => { off => "0100", on => "01FF", dim => "01%02x", reportOn => "03FF", reportOff => "0300", }, get => { swmStatus => "02", }, #03260363 reported in http://forum.fhem.de/index.php?t=rview&th=10216 parse => { "032603(.*)"=> '($1 eq "00" ? "state:off" : ($1 eq "ff" ? "state:on" : "state:dim ".hex($1)))',}, }, SWITCH_ALL => { id => '27', }, SWITCH_TOGGLE_BINARY => { id => '28', }, SWITCH_TOGGLE_MULTILEVEL => { id => '29', }, CHIMNEY_FAN => { id => '2a', }, SCENE_ACTIVATION => { id => '2b', }, SCENE_ACTUATOR_CONF => { id => '2c', }, SCENE_CONTROLLER_CONF => { id => '2d', }, ZIP_CLIENT => { id => '2e', }, ZIP_ADV_SERVICES => { id => '2f', }, SENSOR_BINARY => { id => '30', get => { sbStatus => "02", }, parse => { "03300300" => "state:closed", "033003ff" => "state:open", "043003(..)0c" => '"motion:$1"', #Philio PHI_PSP01, PSM02-1 "043003(..)08" => '"tamper:$1"', #Philio PHI_PSP01, PSM02-1 "043003000a" => "state:closed", #Philio PSM02-1 "043003ff0a" => "state:open", #Philio PSM02-1 },}, SENSOR_MULTILEVEL => { id => '31', get => { smStatus => "04" }, parse => { "..3105(..)(..)(.*)" => 'ZWave_ParseMultilevel($1,$2,$3)'},}, METER => { id => '32', parse => { "..3202(.*)"=> 'ZWave_ParseMeter($1)' }, }, ZIP_ADV_SERVER => { id => '33', }, ZIP_ADV_CLIENT => { id => '34', }, METER_PULSE => { id => '35', }, HRV_STATUS => { id => '37', get => { hrvStatus => "01%02x", hrvStatusSupported => "03",}, parse => { "0637020042(....)" => 'sprintf("outdoorTemperature: %0.1f C", s2Hex($1)/100)', "0637020142(....)" => 'sprintf("supplyAirTemperature: %0.1f C", s2Hex($1)/100)', "0637020242(....)" => 'sprintf("exhaustAirTemperature: %0.1f C", s2Hex($1)/100)', "0637020342(....)" => 'sprintf("dischargeAirTemperature: %0.1f C",s2Hex($1)/100)', "0637020442(....)" => 'sprintf("indoorTemperature: %0.1f C", s2Hex($1)/100)', "0537020501(..)" => 'sprintf("indoorHumidity: %s %%", hex($1))', "0537020601(..)" => 'sprintf("remainingFilterLife: %s %%", hex($1))', "033704(..)" => 'sprintf("supportedStatus: %s", ZWave_HrvStatus($1))', },}, THERMOSTAT_HEATING => { id => '38', }, HRV_CONTROL => { id => '39', set => { bypassOff => "0400", bypassOn => "04FF", ventilationRate => "07%02x", }, get => { bypass => "05", ventilationRate => "08", }, parse => { "033906(..)"=> '($1 eq "00" ? "bypass:off" : '. '($1 eq "ff" ? "bypass:on" : '. '"bypass:dim ".hex($1)))', "033909(..)"=> 'sprintf("ventilationRate: %s",hex($1))', },}, METER_TBL_CONFIG => { id => '3c', }, METER_TBL_MONITOR => { id => '3d', }, METER_TBL_PUSH => { id => '3e', }, THERMOSTAT_MODE => { id => '40', set => { tmOff => "0100", tmHeating => "0101", tmCooling => "010b", tmManual => "011f", }, get => { thermostatMode => "02", }, parse => { "03400300" => "state:off", "0340030b" => "state:cooling", "03400301" => "state:heating", "0340031f" => "state:manual", }, } , THERMOSTAT_OPERATING_STATE=>{ id => '42', }, THERMOSTAT_SETPOINT => { id => '43', get => { setpoint => "02" }, parse => { "064303(..)(..)(....)" => 'sprintf("temperature:%0.1f %s %s", '. 'hex($3)/(10**int(hex($2)/32)), '. 'hex($2)&8 ? "F":"C", $1==1 ? "heating":"cooling")' }, }, THERMOSTAT_FAN_MODE => { id => '44', }, THERMOSTAT_FAN_STATE => { id => '45', }, CLIMATE_CONTROL_SCHEDULE => { id => '46', }, THERMOSTAT_SETBACK => { id => '47', }, DOOR_LOCK_LOGGING => { id => '4c', }, SCHEDULE_ENTRY_LOCK => { id => '4e', }, BASIC_WINDOW_COVERING => { id => '50', }, MTP_WINDOW_COVERING => { id => '51', }, MULTI_CHANNEL => { id => '60', # Version 2, aka MULTI_INSTANCE get => { mcEndpoints => "07", # Endpoints mcCapability=> "09%02x"}, parse => { "^046008(..)(..)" => '"mcEndpoints:total ".hex($2).'. '(hex($1)&0x80 ? ", dynamic":"").'. '(hex($1)&0x40 ? ", identical":", different")', "^..600a(.*)"=> 'ZWave_mcCapability($hash, $1)' }, }, DOOR_LOCK => { id => '62', }, USER_CODE => { id => '63', }, CONFIGURATION => { id => '70', set => { configDefault=>"04%02x80", configByte => "04%02x01%02x", configWord => "04%02x02%04x", configLong => "04%02x04%08x", }, get => { config => "05%02x", }, parse => { "..7006(..)..(.*)" => '"config_$1:".hex($2)',}, }, ALARM => { id => '71', get => { alarm => "04%02x", }, parse => { "..7105(..)(..)" => '"alarm_type_$1:level $2"',}, }, MANUFACTURER_SPECIFIC => { id => '72', }, POWERLEVEL => { id => '73', }, PROTECTION => { id => '75', }, LOCK => { id => '76', }, NODE_NAMING => { id => '77', }, FIRMWARE_UPDATE_MD => { id => '7a', }, GROUPING_NAME => { id => '7b', }, REMOTE_ASSOCIATION_ACTIVATE=>{id => '7c', }, REMOTE_ASSOCIATION => { id => '7d', }, BATTERY => { id => '80', get => { battery => "02" }, parse => { "038003(..)"=> '"battery:".hex($1)." %"' }, }, CLOCK => { id => '81', parse => { "028105"=> "clock:get" }, }, HAIL => { id => '82', }, WAKE_UP => { id => '84', set => { wakeupInterval => "04%06x%02x", wakeupNoMoreInformation => "08", }, get => { wakeupInterval => "05" }, parse => { "028407" => 'wakeup:notification', "..8406(......)(..)" => '"wakeupReport:interval ".hex($1)." target ".hex($2)',}, }, ASSOCIATION => { id => '85', set => { associationAdd => "01%02x%02x*", associationDel => "04%02x%02x*", }, get => { association => "02%02x", }, parse => { "..8503(..)(..)..(.*)" => '"assocGroup_$1:Max $2 Nodes $3"',}, }, VERSION => { id => '86', get => { version => "11", }, parse => { "078612(..)(..)(..)(..)(..)" => 'sprintf("version:Lib %d Prot %d.%d App %d.%d",'. 'hex($1),hex($2),hex($3),hex($4),hex($5))', } }, INDICATOR => { id => '87', }, PROPRIETARY => { id => '88', }, LANGUAGE => { id => '89', }, TIME => { id => '8a', }, TIME_PARAMETERS => { id => '8b', }, GEOGRAPHIC_LOCATION => { id => '8c', }, COMPOSITE => { id => '8d', }, MULTI_CHANNEL_ASSOCIATION=> { id => '8e', }, # aka MULTI_INSTANCE_ASSOCIATION MULTI_CMD => { id => '8f', }, # Handled in Parse ENERGY_PRODUCTION => { id => '90', }, MANUFACTURER_PROPRIETARY => { id => '91', }, SCREEN_MD => { id => '92', }, SCREEN_ATTRIBUTES => { id => '93', }, SIMPLE_AV_CONTROL => { id => '94', }, AV_CONTENT_DIRECTORY_MD => { id => '95', }, AV_RENDERER_STATUS => { id => '96', }, AV_CONTENT_SEARCH_MD => { id => '97', }, SECURITY => { id => '98', }, AV_TAGGING_MD => { id => '99', }, IP_CONFIGURATION => { id => '9a', }, ASSOCIATION_COMMAND_CONFIGURATION => { id => '9b', }, SENSOR_ALARM => { id => '9c', get => { alarm => "01%02x", }, parse => { "..9c02(..)(..)(..)(....)" => '"alarm_type_$2:level $3 node $1 seconds ".hex($4)',}, }, SILENCE_ALARM => { id => '9d', }, SENSOR_CONFIGURATION => { id => '9e', }, MARK => { id => 'ef', }, NON_INTEROPERABLE => { id => 'f0', }, ); my %zwave_cmdArgs = ( dim => "slider,0,1,99", ); sub ZWave_Initialize($) { my ($hash) = @_; $hash->{Match} = ".*"; $hash->{SetFn} = "ZWave_Set"; $hash->{GetFn} = "ZWave_Get"; $hash->{DefFn} = "ZWave_Define"; $hash->{UndefFn} = "ZWave_Undef"; $hash->{ParseFn} = "ZWave_Parse"; $hash->{AttrList} = "IODev do_not_notify:1,0 ". "ignore:1,0 dummy:1,0 showtime:1,0 classes ". "$readingFnAttributes " . "model:".join(",", sort @zwave_models); map { $zwave_id2class{lc($zwave_class{$_}{id})} = $_ } keys %zwave_class; } ############################# sub ZWave_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); my $name = shift @a; my $type = shift(@a); # always ZWave my $u = "wrong syntax for $name: define ZWave homeId id [classes]"; return $u if(int(@a) < 2 || int(@a) > 3); my $homeId = lc(shift @a); my $id = shift @a; return "define $name: wrong homeId ($homeId): need an 8 digit hex value" if( ($homeId !~ m/^[a-f0-9]{8}$/i) ); return "define $name: wrong id ($id): need a number" if( ($id !~ m/^\d+$/i) ); $id = sprintf("%0*x", ($id > 255 ? 4 : 2), $id); $hash->{homeId} = $homeId; $hash->{id} = $id; $modules{ZWave}{defptr}{"$homeId $id"} = $hash; AssignIoPort($hash); # FIXME: should take homeId into account if(@a) { ZWave_SetClasses($homeId, $id, undef, $a[0]); if($attr{$name}{classes} =~ m/ASSOCIATION/) { my $iodev = $hash->{IODev}; my $homeReading = ReadingsVal($iodev->{NAME}, "homeId", "") if($iodev); my $ctrlId = $1 if($homeReading && $homeReading =~ m/CtrlNodeId:(..)/); if($ctrlId) { Log3 $name, 1, "Adding the controller $ctrlId to association group 1"; IOWrite($hash, "00", "130a04850101${ctrlId}05"); } else { Log3 $name, 1, "Cannot associate $name, missing controller id"; } } } return undef; } ################################### sub ZWave_Cmd($$@) { my ($type, $hash, @a) = @_; my $ret = undef; return "no $type argument specified" if(int(@a) < 2); my $name = shift(@a); my $cmd = shift(@a); # Collect the commands from the distinct classes my %cmdList; my $classes = AttrVal($name, "classes", ""); foreach my $cl (split(" ", $classes)) { my $ptr = $zwave_class{$cl}{$type} if($zwave_class{$cl}{$type}); next if(!$ptr); foreach my $k (keys %{$ptr}) { if(!$cmdList{$k}) { $cmdList{$k}{fmt} = $ptr->{$k}; $cmdList{$k}{id} = $zwave_class{$cl}{id}; } } } if(!$cmdList{$cmd}) { my $list = join(" ",sort keys %cmdList); foreach my $cmd (keys %zwave_cmdArgs) { # add slider & co $list =~ s/\b$cmd\b/$cmd:$zwave_cmdArgs{$cmd}/; } if($type eq "set") { unshift @a, $name, $cmd; return SetExtensions($hash, $list, @a); } else { return "Unknown argument $cmd, choose one of $list"; } } Log3 $name, 2, "ZWave $type $name $cmd"; ################################ # ZW_SEND_DATA,nodeId,CMD,ACK|AUTO_ROUTE my $id = $hash->{id}; my $cmdFmt = $cmdList{$cmd}{fmt}; my $cmdId = $cmdList{$cmd}{id}; my $nArg = 0; if($cmdFmt =~ m/%/) { my @ca = split("%", $cmdFmt); $nArg = int(@ca)-1; } my $parTxt = ($nArg == 0 ? "no parameter" : ($nArg == 1 ? "one parameter" : "$nArg parameters")); if($cmdFmt =~ m/^(.*)\*$/) { $cmdFmt = $1; return "$type $cmd needs at least $parTxt" if($nArg > int(@a)); $cmdFmt .= ("%02x" x (int(@a)-$nArg)); } else { return "$type $cmd needs $parTxt" if($nArg != int(@a)); } $cmdFmt = sprintf($cmdFmt, @a) if($nArg); my ($baseClasses, $baseHash) = ($classes, $hash); if($id =~ m/(..)(..)/) { # Multi-Channel, encapsulate my ($baseId,$ch) = ($1, $2); $id = $baseId; $cmdFmt = "0d01$ch$cmdId$cmdFmt"; $cmdId = "60"; # MULTI_CHANNEL $baseHash = $modules{ZWave}{defptr}{"$hash->{homeId} $baseId"}; $baseClasses = AttrVal($baseHash->{NAME}, "classes", ""); } my $len = sprintf("%02x", length($cmdFmt)/2+1); my $data = "13$id$len$cmdId${cmdFmt}05"; # 13==SEND_DATA if($baseClasses =~ m/WAKE_UP/) { if(!$baseHash->{WakeUp}) { my @arr = (); $baseHash->{WakeUp} = \@arr; } my $awake = ($baseHash->{lastMsgTimestamp} && time() - $baseHash->{lastMsgTimestamp} < 2); if($awake && @{$baseHash->{WakeUp}} == 0) { push @{$baseHash->{WakeUp}}, ""; # Block the next } else { push @{$baseHash->{WakeUp}}, $data; return ($type eq "get" && AttrVal($name,"verbose",3) > 2 ? "Scheduled for sending after WAKEUP" : undef); } } IOWrite($hash, "00", $data); my $val; if($type eq "get") { no strict "refs"; my $iohash = $hash->{IODev}; my $fn = $modules{$iohash->{TYPE}}{ReadAnswerFn}; my ($err, $data) = &{$fn}($iohash, $cmd, "^000400$id"); use strict "refs"; return $err if($err); $val = ZWave_Parse($iohash, $data, 1); } else { $cmd .= " ".join(" ", @a) if(@a); } my $tn = TimeNow(); if($type eq "set") { readingsSingleUpdate($hash, "state", $cmd, 1); } else { my $mval = $val; ($cmd, $mval) = split(":", $val) if($val); if($cmd && $mval) { readingsSingleUpdate($hash, $cmd, $mval, 1); } } return $val; } sub ZWave_Set($@) { return ZWave_Cmd("set", shift, @_); } sub ZWave_Get($@) { return ZWave_Cmd("get", shift, @_); } # returns supported Parameters by hrvStatus sub ZWave_HrvStatus($) { my ($p) = @_; $p = hex($p); my @hrv_status = ( "outdoorTemperature", "supplyAirTemperature", "exhaustAirTemperature", "dischargeAirTemperature", "indoorTemperature", "indoorHumidity", "remainingFilterLife" ); my @l; for(my $i=0; $i < 7; $i++) { push @l, "$i = $hrv_status[$i]" if($p & (1<<$i)); } return join("\n", @l); } sub ZWave_ParseMeter($) { my ($val) = @_; return if($val !~ m/^(..)(..)(.*)$/); my ($v1, $v2, $v3) = (hex($1) & 0x1f, hex($2), $3); my @prectab = (1,10,100,1000,10000,100000,1000000, 10000000); my $prec = $prectab[($v2 >> 5) & 0x7]; my $scale = ($v2 >> 3) & 0x3; my $size = ($v2 >> 0) & 0x7; my @txt = ("undef", "energy", "gas", "water"); my $txt = ($v1 > $#txt ? "undef" : $txt[$v1]); my %unit = (energy => ["kWh", "kVAh", "W", "pulseCount"], gas => ["m3", "feet3", "undef", "pulseCount"], water => ["m3", "feet3", "USgallons", "pulseCount"]); my $unit = $txt eq "undef" ? "undef" : $unit{$txt}[$scale]; $txt = "power" if ($unit eq "W"); $v3 = hex(substr($v3, 0, 2*$size))/$prec; return "$txt:$v3 $unit"; } sub ZWave_ParseMultilevel($$$) { my ($type,$fl,$arg) = @_; my %ml_tbl = ( '01' => { n => 'temperature', st => ['C', 'F'] }, '02' => { n => 'generalPurpose', st => ['%', ''] }, '03' => { n => 'luminance', st => ['%', 'Lux'] }, '04' => { n => 'power', st => ['W', 'Btu/h'] }, '05' => { n => 'humidity', st => ['%'] }, '06' => { n => 'velocity', st => ['m/s', 'mph'] }, '07' => { n => 'direction', st => [] }, '08' => { n => 'atmosphericPressure', st => ['kPa', 'inchHg'] }, '09' => { n => 'barometricPressure', st => ['kPa', 'inchHg'] }, '0a' => { n => 'solarRadiation', st => ['W/m2'] }, '0b' => { n => 'dewpoint', st => ['C', 'F'] }, '0c' => { n => 'rain', st => ['mm/h', 'in/h'] }, '0d' => { n => 'tideLevel', st => ['m', 'feet'] }, '0e' => { n => 'weight', st => ['kg', 'pound'] }, '0f' => { n => 'voltage', st => ['V', 'mV'] }, '10' => { n => 'current', st => ['A', 'mA'] }, '11' => { n => 'CO2-level', st => ['ppm']}, '12' => { n => 'airFlow', st => ['m3/h', 'cfm'] }, '13' => { n => 'tankCapacity', st => ['l', 'cbm', 'usgal'] }, '14' => { n => 'distance', st => ['m', 'cm', 'feet'] }, '15' => { n => 'anglePosition', st => ['%', 'relN', 'relS'] }, ); my $pr = (hex($fl)>>5)&0x07; # precision my $sc = (hex($fl)>>3)&0x03; # scale my $bc = (hex($fl)>>0)&0x07; # bytecount my $msb = (hex($arg)>>8*$bc-1); # most significant bit ( 0 = pos, 1 = neg ) my $val = $msb ? -( 2 ** (8 * $bc) - hex($arg) ) : hex($arg); # 2's complement my $ml = $ml_tbl{$type}; return "UNKNOWN multilevel type: $type fl: $fl arg: $arg" if(!$ml); return sprintf("%s:%.*f %s", $ml->{n}, $pr, $val/(10**$pr), int(@{$ml->{st}}) > $sc ? $ml->{st}->[$sc] : ""); } sub ZWave_SetClasses($$$$) { my ($homeId, $id, $type6, $classes) = @_; my $def = $modules{ZWave}{defptr}{"$homeId $id"}; if(!$def) { $type6 = $zw_type6{$type6} if($type6 && $zw_type6{$type6}); $id = hex($id); return "UNDEFINED ZWave_${type6}_$id ZWave $homeId $id $classes" } my @classes; for my $classId (grep /../, split(/(..)/, lc($classes))) { push @classes, $zwave_id2class{lc($classId)} ? $zwave_id2class{lc($classId)} : "UNKNOWN_".lc($classId); } my $name = $def->{NAME}; $attr{$name}{classes} = join(" ", @classes) if(@classes); $def->{DEF} = "$homeId ".hex($id); return ""; } sub ZWave_mcCapability($$) { my ($hash, $caps) = @_; my $name = $hash->{NAME}; my $iodev = $hash->{IODev}; return "Missing IODev for $name" if(!$iodev); my $homeId = $iodev->{homeId}; my @l = grep /../, split(/(..)/, lc($caps)); my $chid = shift(@l); my $id = $hash->{id}; my @classes; for my $classId (@l) { push @classes, $zwave_id2class{lc($classId)} ? $zwave_id2class{lc($classId)} : "UNKNOWN_".uc($classId); } return "mcCapability_$chid:no classes" if(!@classes); if(!$modules{ZWave}{defptr}{"$homeId $id$chid"}) { my $lid = hex("$id$chid"); my $lcaps = substr($caps, 2); $id = hex($id); DoTrigger("global", "UNDEFINED ZWave_$classes[0]_$id.$chid ZWave $homeId $lid $caps", 1); } return "mcCapability_$chid:".join(" ", @classes); } ################################### # 0004000a03250300 (sensor binary off for id 11) # { ZWave_Parse($defs{zd}, "0004000c028407", 0) } sub ZWave_Parse($$@) { my ($iodev, $msg, $local) = @_; my $homeId = $iodev->{homeId}; my $ioName = $iodev->{NAME}; if(!$homeId) { Log3 $ioName, 1, "ERROR: $ioName homeId is not set!" if(!$iodev->{errReported}); $iodev->{errReported} = 1; return ""; } if($msg =~ m/^01(..)(..*)/) { # 01==ANSWER 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); return ""; } Log3 $ioName, 4, "$ioName: unhandled ANSWER: $cmd $arg"; return ""; } if($msg !~ m/^00(..)(..)(..)(.*)/) { # 00=REQUEST Log3 $ioName, 4, "$ioName: UNKNOWN msg $msg"; return ""; } my ($cmd, $callbackid, $id, $arg) = ($1, $2, $3, $4); $cmd = $zw_func_id{$cmd} if($zw_func_id{$cmd}); ##################################### # Controller commands my $evt; Log3 $ioName, 4, "$ioName CMD:$cmd ID:$id ARG:$arg"; if($cmd eq 'ZW_ADD_NODE_TO_NETWORK' || $cmd eq 'ZW_REMOVE_NODE_FROM_NETWORK') { my @vals = ("learnReady", "nodeFound", "slave", "controller", "", "done", "failed"); $evt = ($id eq "00" || hex($id)>@vals+1) ? "unknownArg" : $vals[hex($id)-1]; if($evt eq "slave" && $arg =~ m/(..)....(..)..(.*)$/) { my ($id,$type6,$classes) = ($1, $2, $3); return ZWave_SetClasses($homeId, $id, $type6, $classes) if($cmd eq 'ZW_ADD_NODE_TO_NETWORK'); } } elsif($cmd eq "ZW_APPLICATION_UPDATE" && $arg =~ m/....(..)..(.*)$/) { my ($type6,$classes) = ($1, $2, $3); my $ret = ZWave_SetClasses($homeId, $id, $type6, $classes); my $hash = $modules{ZWave}{defptr}{"$homeId $id"}; if($hash && $hash->{WakeUp} && @{$hash->{WakeUp}}) { # Always the base hash IOWrite($hash, "00", shift @{$hash->{WakeUp}}); } if(!$ret) { readingsSingleUpdate($hash, "CMD", $cmd, 1); # forum:20884 return $hash->{NAME}; } return $ret; } elsif($cmd eq "ZW_SEND_DATA") { Log3 $ioName, 2, "$ioName ERROR: SEND_DATA returned $id" if($id ne "00"); return ""; } if($evt) { return "$cmd $evt" if($local); DoTrigger($ioName, "$cmd $evt"); Log3 $ioName, 4, "$ioName $cmd $evt"; return ""; } ###################################### # device messages if($cmd ne "APPLICATION_COMMAND_HANDLER") { Log3 $ioName, 4, "$ioName unhandled command $cmd"; return "" } my $baseHash; if($arg =~ /^..600d(..)(..)(.*)/) { # MULTI_CHANNEL CMD_ENCAP $baseHash = $modules{ZWave}{defptr}{"$homeId $id"}; $id = "$id$1"; $arg = sprintf("%02x$3", length($3)/2); } my $hash = $modules{ZWave}{defptr}{"$homeId $id"}; $baseHash = $hash if(!$baseHash); if(!$hash) { $id = hex($id); Log3 $ioName, 3, "Unknown ZWave device $homeId $id, please define it"; return ""; } my @event; my @args = ($arg); # MULTI_CMD handling while(@args) { $arg = shift(@args); return if($arg !~ m/^..(..)/); my $class = $1; my $className = $zwave_id2class{lc($class)} ? $zwave_id2class{lc($class)} : "UNKNOWN_".uc($class); if($className eq "MULTI_CMD") { my ($ncmd, $off) = (0, 4); while(length($arg) > $off*2) { my $l = hex(substr($arg, $off*2, 2))+1; push @args, substr($arg, $off*2, $l*2); $off += $l; } next; } my $ptr = $zwave_class{$className}{parse} if($zwave_class{$className}{parse}); if(!$ptr) { Log3 $hash, 4, "$hash->{NAME}: Unknown message ($className $arg)"; next; } foreach my $k (keys %{$ptr}) { if($arg =~ m/$k/) { my $val = $ptr->{$k}; $val = eval $val if(index($val, '$') >= 0); push @event, $val; } } } my $wu = $baseHash->{WakeUp}; if($wu && @{$wu}) { shift @{$wu} if($wu->[0] eq ""); IOWrite($hash, "00", shift @{$wu}) if(@{$wu}); } $baseHash->{lastMsgTimestamp} = time(); return "" if(!@event); return join(" ", @event) if($local); readingsBeginUpdate($hash); for(my $i = 0; $i < int(@event); $i++) { next if($event[$i] eq ""); my ($vn, $vv) = split(":", $event[$i], 2); readingsBulkUpdate($hash, $vn, $vv); readingsBulkUpdate($hash, "reportedState", $vv) if($vn eq "state"); # different from set } readingsEndUpdate($hash, 1); return $hash->{NAME}; } ##################################### sub ZWave_Undef($$) { my ($hash, $arg) = @_; my $homeId = $hash->{homeId}; my $id = $hash->{id}; delete $modules{ZWave}{defptr}{"$homeId $id"}; return undef; } ##################################### # 2-byte signed hex sub s2Hex($) { my ($p) = @_; $p = hex($p); return ($p > 32767 ? -(65536-$p) : $p); } 1; =pod =begin html

ZWave

=end html =cut