############################################## # CUL HomeMatic handler # $Id$ package main; use strict; use warnings; #use Time::HiRes qw(gettimeofday); sub CUL_HM_Initialize($); sub CUL_HM_Define($$); sub CUL_HM_Undef($$); sub CUL_HM_Parse($$); sub CUL_HM_Get($@); sub CUL_HM_fltCvT($); sub CUL_HM_Set($@); sub CUL_HM_infoUpdtDevData($$$); sub CUL_HM_Pair(@); sub CUL_HM_getConfig($$$$$); sub CUL_HM_SendCmd($$$$); sub CUL_HM_responseSetup($$$); sub CUL_HM_eventP($$); sub CUL_HM_respPendRm($); sub CUL_HM_respPendTout($); sub CUL_HM_PushCmdStack($$); sub CUL_HM_ProcessCmdStack($); sub CUL_HM_Resend($); sub CUL_HM_Id($); sub CUL_HM_name2hash($); sub CUL_HM_Name2Id(@); sub CUL_HM_id2Name($); sub CUL_HM_getDeviceHash($); sub CUL_HM_DumpProtocol($$@); sub CUL_HM_parseCommon(@); sub CUL_HM_encodeTime8($); sub CUL_HM_decodeTime8($); sub CUL_HM_encodeTime16($); sub CUL_HM_convTemp($); sub CUL_HM_decodeTime16($); sub CUL_HM_pushConfig($$$$$$$$); sub CUL_HM_maticFn($$$$$); sub CUL_HM_secSince2000(); my %culHmDevProps=( "01" => { st => "AlarmControl", cl => "controller" }, # by peterp "12" => { st => "outputUnit", cl => "receiver" }, # Test Pending "10" => { st => "switch", cl => "receiver" }, # Parse,Set "20" => { st => "dimmer", cl => "receiver" }, # Parse,Set "30" => { st => "blindActuator", cl => "receiver" }, # Parse,Set "39" => { st => "ClimateControl", cl => "sender" }, "40" => { st => "remote", cl => "sender" }, # Parse "41" => { st => "sensor", cl => "sender" }, "42" => { st => "swi", cl => "sender" }, # e.g. HM-SwI-3-FM "43" => { st => "pushButton", cl => "sender" }, "58" => { st => "thermostat", cl => "receiver" }, "60" => { st => "KFM100", cl => "sender" }, # Parse,unfinished "70" => { st => "THSensor", cl => "sender" }, # Parse,unfinished "80" => { st => "threeStateSensor",cl => "sender" }, # e.g.HM-SEC-RHS "81" => { st => "motionDetector", cl => "sender" }, "C0" => { st => "keyMatic", cl => "receiver" }, "C1" => { st => "winMatic", cl => "receiver" }, "CD" => { st => "smokeDetector", cl => "receiver" }, # Parse,set unfinished ); # chan supports autocreate of channels for the device # Syntax :: # chn=>{btn:1:3,disp:4,aux:5:7} wil create # _btn1,_btn2,_btn3 as channel 1 to 3 # _disp as channel 4 # _aux1,_aux2,_aux7 as channel 5 to 7 # autocreate for single channel devices is possible not recommended #rxt - receivetype of the device------ # c: receive on config # w: receive in wakeup # b: receive on burst #register list definition - identifies valid register lists # 1,3,5:3p.4.5 => list 1 valid for all channel # => list 3 for all channel # => list 5 only for channel 3 but assotiated with peers # => list 5 for channel 4 and 5 with peer=00000000 # my %culHmModel=( "0001" => {name=>"HM-LC-SW1-PL-OM54" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"",}, "0002" => {name=>"HM-LC-SW1-SM" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"",}, "0003" => {name=>"HM-LC-SW4-SM" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"Sw:1:4",}, "0004" => {name=>"HM-LC-SW1-FM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, "0005" => {name=>"HM-LC-BL1-FM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, "0006" => {name=>"HM-LC-BL1-SM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, "0007" => {name=>"KS550" ,cyc=>'00:10' ,rxt=>'' ,lst=>'1' ,chn=>"",}, "0008" => {name=>"HM-RC-4" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:4",}, "0009" => {name=>"HM-LC-SW2-FM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Sw:1:2",}, "000A" => {name=>"HM-LC-SW2-SM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Sw:1:2",}, "000B" => {name=>"HM-WDC7000" ,cyc=>'' ,rxt=>'' ,lst=>'' ,chn=>"",}, "000D" => {name=>"ASH550" ,cyc=>'' ,rxt=>'c:w' ,lst=>'' ,chn=>"",}, "000E" => {name=>"ASH550I" ,cyc=>'' ,rxt=>'c:w' ,lst=>'' ,chn=>"",}, "000F" => {name=>"S550IA" ,cyc=>'00:10' ,rxt=>'c:w' ,lst=>'' ,chn=>"",}, "0011" => {name=>"HM-LC-SW1-PL" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"",}, "0012" => {name=>"HM-LC-DIM1L-CV" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Vtr:2:3",}, "0013" => {name=>"HM-LC-DIM1L-PL" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, "0014" => {name=>"HM-LC-SW1-SM-ATMEGA168" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"",}, "0015" => {name=>"HM-LC-SW4-SM-ATMEGA168" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Sw:1:4",}, "0016" => {name=>"HM-LC-DIM2L-CV" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Sw:1:2,Vtr:3:6",}, "0018" => {name=>"CMM" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"",}, "0019" => {name=>"HM-SEC-KEY" ,cyc=>'' ,rxt=>'b' ,lst=>'3' ,chn=>"",}, "001A" => {name=>"HM-RC-P1" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"",}, "001B" => {name=>"HM-RC-SEC3" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:3",}, "001C" => {name=>"HM-RC-SEC3-B" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:3",}, "001D" => {name=>"HM-RC-KEY3" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:3",}, "001E" => {name=>"HM-RC-KEY3-B" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:3",}, "0022" => {name=>"WS888" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, "0026" => {name=>"HM-SEC-KEY-S" ,cyc=>'' ,rxt=>'b' ,lst=>'3' ,chn=>"",}, "0027" => {name=>"HM-SEC-KEY-O" ,cyc=>'' ,rxt=>'b' ,lst=>'3' ,chn=>"",}, "0028" => {name=>"HM-SEC-WIN" ,cyc=>'' ,rxt=>'b' ,lst=>'1,3' ,chn=>"",}, "0029" => {name=>"HM-RC-12" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:12",}, "002A" => {name=>"HM-RC-12-B" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:12",}, "002D" => {name=>"HM-LC-SW4-PCB" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"Sw:1:4",}, "002E" => {name=>"HM-LC-DIM2L-SM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Sw:1:2,Vtr:3:6",}, "002F" => {name=>"HM-SEC-SC" ,cyc=>'28:00' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",}, "0030" => {name=>"HM-SEC-RHS" ,cyc=>'28:00' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",}, "0034" => {name=>"HM-PBI-4-FM" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:4",}, "0035" => {name=>"HM-PB-4-WM" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:4",}, "0036" => {name=>"HM-PB-2-WM" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:2",}, "0037" => {name=>"HM-RC-19" ,cyc=>'' ,rxt=>'c:b' ,lst=>'1,4' ,chn=>"Btn:1:17,Disp:18",}, "0038" => {name=>"HM-RC-19-B" ,cyc=>'' ,rxt=>'c:b' ,lst=>'1,4' ,chn=>"Btn:1:17,Disp:18",}, "0039" => {name=>"HM-CC-TC" ,cyc=>'00:10' ,rxt=>'c:w' ,lst=>'5:2.3p,6:2' ,chn=>"Weather:1:1,Climate:2:2,WindowRec:3:3",}, "003A" => {name=>"HM-CC-VD" ,cyc=>'28:00' ,rxt=>'c:w' ,lst=>'5' ,chn=>"",}, "003B" => {name=>"HM-RC-4-B" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:4",}, "003C" => {name=>"HM-WDS20-TH-O" ,cyc=>'' ,rxt=>'c:w' ,lst=>'' ,chn=>"",}, "003D" => {name=>"HM-WDS10-TH-O" ,cyc=>'' ,rxt=>'c:w' ,lst=>'' ,chn=>"",}, "003E" => {name=>"HM-WDS30-T-O" ,cyc=>'00:10' ,rxt=>'c:w' ,lst=>'' ,chn=>"",}, "003F" => {name=>"HM-WDS40-TH-I" ,cyc=>'' ,rxt=>'c:w' ,lst=>'' ,chn=>"",}, "0040" => {name=>"HM-WDS100-C6-O" ,cyc=>'00:10' ,rxt=>'c:w' ,lst=>'1' ,chn=>"",}, "0041" => {name=>"HM-WDC7000" ,cyc=>'' ,rxt=>'' ,lst=>'1,4' ,chn=>"",}, "0042" => {name=>"HM-SEC-SD" ,cyc=>'90:00' ,rxt=>'b' ,lst=>'' ,chn=>"",}, "0043" => {name=>"HM-SEC-TIS" ,cyc=>'28:00' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",}, "0044" => {name=>"HM-SEN-EP" ,cyc=>'' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",}, "0045" => {name=>"HM-SEC-WDS" ,cyc=>'28:00' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",}, "0047" => {name=>"KFM-Sensor" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, "0046" => {name=>"HM-SWI-3-FM" ,cyc=>'' ,rxt=>'c' ,lst=>'4' ,chn=>"Sw:1:3",}, "0048" => {name=>"IS-WDS-TH-OD-S-R3" ,cyc=>'' ,rxt=>'c:w' ,lst=>'1,3' ,chn=>"",}, "0049" => {name=>"KFM-Display" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, "004A" => {name=>"HM-SEC-MDIR" ,cyc=>'00:20' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",}, "004B" => {name=>"HM-Sec-Cen" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, "004C" => {name=>"HM-RC-12-SW" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:12",}, "004D" => {name=>"HM-RC-19-SW" ,cyc=>'' ,rxt=>'c:b' ,lst=>'1,4' ,chn=>"Btn:1:17,Disp:18",}, "004E" => {name=>"HM-LC-DDC1-PCB" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, "004F" => {name=>"HM-SEN-MDIR-SM" ,cyc=>'' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",}, "0050" => {name=>"HM-SEC-SFA-SM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, "0051" => {name=>"HM-LC-SW1-PB-FM" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"",}, "0052" => {name=>"HM-LC-SW2-PB-FM" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"Sw:1:2",}, "0053" => {name=>"HM-LC-BL1-PB-FM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, "0054" => {name=>"DORMA_RC-H" ,cyc=>'' ,rxt=>'c' ,lst=>'1,3' ,chn=>"",}, "0056" => {name=>"HM-CC-SCD" ,cyc=>'28:00' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",}, "0057" => {name=>"HM-LC-DIM1T-PL" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Vtr:2:3",}, "0058" => {name=>"HM-LC-DIM1T-CV" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Vtr:2:3",}, "0059" => {name=>"HM-LC-DIM1T-FM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Vtr:2:3",}, "005A" => {name=>"HM-LC-DIM2T-SM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Sw:1:2,Vtr:3:6",}, "005C" => {name=>"HM-OU-CF-PL" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"Led:1:1,Sound:2:2",}, "005D" => {name=>"HM-Sen-MDIR-O" ,cyc=>'' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",}, "005F" => {name=>"HM-SCI-3-FM" ,cyc=>'28:00' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",}, "0060" => {name=>"HM-PB-4DIS-WM" ,cyc=>'00:10' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:20",}, "0061" => {name=>"HM-LC-SW4-DR" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"Sw:1:4",}, "0062" => {name=>"HM-LC-SW2-DR" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Sw:1:2",}, "0064" => {name=>"DORMA_atent" ,cyc=>'' ,rxt=>'c' ,lst=>'1,3' ,chn=>"",}, "0065" => {name=>"DORMA_BRC-H" ,cyc=>'' ,rxt=>'c' ,lst=>'1,3' ,chn=>"",}, "0066" => {name=>"HM-LC-SW4-WM" ,cyc=>'' ,rxt=>'b' ,lst=>'3' ,chn=>"Sw:1:4",}, "0067" => {name=>"HM-LC-Dim1PWM-CV" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, "0068" => {name=>"HM-LC-Dim1TPBU-FM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, "0069" => {name=>"HM-LC-Sw1PBU-FM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, "006A" => {name=>"HM-LC-Bl1PBU-FM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, "006B" => {name=>"HM-PB-2-WM55" ,cyc=>'' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",}, "006C" => {name=>"HM-LC-SW1-BA-PCB" ,cyc=>'' ,rxt=>'b' ,lst=>'3' ,chn=>"",}, "006D" => {name=>"HM-OU-LED16" ,cyc=>'' ,rxt=>'' ,lst=>'' ,chn=>"Led:1:16",}, "0075" => {name=>"HM-OU-CFM-PL" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"Led:1:1,Mp3:2:2",}, "0078" => {name=>"HM-Dis-TD-T" ,cyc=>'' ,rxt=>'b' ,lst=>'3' ,chn=>"",}, "0079" => {name=>"ROTO_ZEL-STG-RM-FWT" ,cyc=>'' ,rxt=>'c:w' ,lst=>'1,3' ,chn=>"",}, "0x7A" => {name=>"ROTO_ZEL-STG-RM-FSA" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, "007B" => {name=>"ROTO_ZEL-STG-RM-FEP-230V",cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, "007D" => {name=>"ROTO_ZEL-STG-RM-WT-2" ,cyc=>'' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",}, "007E" => {name=>"ROTO_ZEL-STG-RM-DWT-10" ,cyc=>'00:10' ,rxt=>'c' ,lst=>'1,4' ,chn=>"",}, "007F" => {name=>"ROTO_ZEL-STG-RM-FST-UP4" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"",}, "0080" => {name=>"ROTO_ZEL-STG-RM-HS-4" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"",}, "0081" => {name=>"ROTO_ZEL-STG-RM-FDK" ,cyc=>'28:00' ,rxt=>'c:w' ,lst=>'1,3' ,chn=>"",}, "0082" => {name=>"Roto_ZEL-STG-RM-FFK" ,cyc=>'28:00' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",}, "0083" => {name=>"Roto_ZEL-STG-RM-FSS-UP3" ,cyc=>'' ,rxt=>'c' ,lst=>'4' ,chn=>"",}, "0084" => {name=>"Schueco_263-160" ,cyc=>'' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",}, "0086" => {name=>"Schueco_263-146" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, "008D" => {name=>"Schueco_263-1350" ,cyc=>'' ,rxt=>'c:w' ,lst=>'1,3' ,chn=>"",}, "008E" => {name=>"Schueco_263-155" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"",}, "008F" => {name=>"Schueco_263-145" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"",}, "0090" => {name=>"Schueco_263-162" ,cyc=>'00:30' ,rxt=>'c:w' ,lst=>'1,3' ,chn=>"",}, "0092" => {name=>"Schueco_263-144" ,cyc=>'' ,rxt=>'c' ,lst=>'4' ,chn=>"",}, "0093" => {name=>"Schueco_263-158" ,cyc=>'' ,rxt=>'c:w' ,lst=>'' ,chn=>"",}, "0094" => {name=>"Schueco_263-157" ,cyc=>'' ,rxt=>'c:w' ,lst=>'' ,chn=>"",}, ); sub CUL_HM_Initialize($) { my ($hash) = @_; $hash->{Match} = "^A...................."; $hash->{DefFn} = "CUL_HM_Define"; $hash->{UndefFn} = "CUL_HM_Undef"; $hash->{ParseFn} = "CUL_HM_Parse"; $hash->{SetFn} = "CUL_HM_Set"; $hash->{GetFn} = "CUL_HM_Get"; $hash->{RenameFn} = "CUL_HM_Rename"; $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:1,0 dummy:1,0 ". "showtime:1,0 loglevel:0,1,2,3,4,5,6 ". "hmClass:receiver,sender serialNr firmware devInfo ". "rawToReadable unit ". "chanNo device peerList peerIDs ". "actCycle actStatus ". "protCmdPend protLastRcv protSndCnt protSndLast protCmdDel protNackCnt protNackLast ". "protResndFailLast protResndLast protResndFailCnt protResndCnt protToutRespLast protToutRespCnt ". "channel_01 channel_02 channel_03 channel_04 channel_05 channel_06 ". "channel_07 channel_08 channel_09 channel_0A channel_0B channel_0C ". "channel_0D channel_0E channel_0F channel_10 channel_11 channel_12 ". "channel_13 channel_14 channel_15 channel_16 channel_17 channel_18 "; my @modellist; foreach my $model (keys %culHmModel){ push @modellist,$culHmModel{$model}{name}; } $hash->{AttrList} .= " model:" .join(",", sort @modellist); $hash->{AttrList} .= " subType:".join(",", sort map { $culHmDevProps{$_}{st} } keys %culHmDevProps); } ############################# sub CUL_HM_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); my $name = $hash->{NAME}; return "wrong syntax: define CUL_HM 6-digit-hex-code [Raw-Message]" if(!(int(@a)==3 || int(@a)==4) || $a[2] !~ m/^[A-F0-9]{6,8}$/i); my $HMid = uc($a[2]); return "HMid DEF already used by " . CUL_HM_id2Name($HMid) if ($modules{CUL_HM}{defptr}{$HMid}); if(length($a[2]) == 8) { my $devHmId = uc(substr($a[2], 0, 6)); my $chn = substr($a[2], 6, 2); my $devHash = $modules{CUL_HM}{defptr}{$devHmId}; if($devHash) {# define a channel $modules{CUL_HM}{defptr}{$HMid} = $hash; AssignIoPort($hash); my $devName = $devHash->{NAME}; $attr{$name}{device} = $devName; $attr{$name}{chanNo} = $chn; $attr{$name}{model} = $attr{$devName}{model} if ($attr{$devName}{model}); $attr{$devName}{"channel_$chn"} = $name; } else{ return "please define a device with hmId:".$devHmId." first"; } } else{# define a device $modules{CUL_HM}{defptr}{$HMid} = $hash; AssignIoPort($hash); } CUL_HM_ActGetCreateHash() if($HMid eq '000000');#startTimer if(int(@a) == 4) { $hash->{DEF} = $a[2]; CUL_HM_Parse($hash, $a[3]); } return undef; } ############################# sub CUL_HM_Undef($$) { my ($hash, $name) = @_; my $devName = $attr{$name}{device}; my $HMid = $hash->{DEF}; my $chn = substr($HMid,6,2); if ($chn){# delete a channel delete $attr{$devName}{"channel_$chn"} if ($devName); } else{# delete a device foreach my $channel (keys %{$attr{$name}}){ CommandDelete(undef,$attr{$name}{$channel}) if ($channel =~ m/^channel_/); } } delete($modules{CUL_HM}{defptr}{$HMid}); return undef; } ############################# sub CUL_HM_Rename($$$) { #my ($hash, $name,$newName) = @_; my ($name, $oldName) = @_; my $HMid = CUL_HM_Name2Id($name); if (length($HMid) == 8){# we are channel, inform the device $attr{$name}{chanNo} = substr($HMid,6,2); my $device = AttrVal($name, "device", ""); $attr{$device}{"channel_".$attr{$name}{chanNo}} = $name if ($device); } else{# we are a device - inform channels if exist for (my$chn = 1; $chn <25;$chn++){ my $chnName = AttrVal($name, sprintf("channel_%02X",$chn), ""); $attr{$chnName}{device} = $name if ($chnName); } } return; } ############################# sub CUL_HM_Parse($$) { my ($iohash, $msg) = @_; my $id = CUL_HM_Id($iohash); # Msg format: Allnnffttssssssddddddpp... $msg =~ m/A(..)(..)(..)(..)(......)(......)(.*)/; my @msgarr = ($1,$2,$3,$4,$5,$6,$7); my ($len,$msgcnt,$msgFlag,$msgType,$src,$dst,$p) = @msgarr; $p = "" if(!defined($p)); my $cmd = "$msgFlag$msgType"; #still necessary to maintain old style my $lcm = "$len$cmd"; # $shash will be replaced for multichannel commands my $shash = $modules{CUL_HM}{defptr}{$src}; my $dhash = $modules{CUL_HM}{defptr}{$dst}; my $dname = $dhash ? $dhash->{NAME} : ($dst eq "000000" ? "broadcast" : ($dst eq $id ? $iohash->{NAME} : $dst)); my $target = " (to $dname)"; return "" if($p =~ m/NACK$/);#discard TCP errors from HMlan. Resend will cover it return "" if($src eq $id);#discard mirrored messages if(!$shash) { # Unknown source # Generate an UNKNOWN event for pairing requests, ignore everything else if($msgType eq "00") { my $sname = "CUL_HM_$src"; # prefer subType over model to make autocreate easier # model names are quite cryptic anyway my $model = substr($p, 2, 4); my $stc = substr($p, 26, 2); # subTypeCode if($culHmDevProps{$stc}) { $sname = "CUL_HM_".$culHmDevProps{$stc}{st} . "_" . $src; } elsif($culHmModel{$model}{name}) { $sname = "CUL_HM_".$culHmModel{$model}{name} . "_" . $src; $sname =~ s/-/_/g; } Log 3, "CUL_HM Unknown device $sname, please define it"; return "UNDEFINED $sname CUL_HM $src $msg"; } return ""; } CUL_HM_eventP($shash,"Rcv"); my $name = $shash->{NAME}; my @event; my $st = AttrVal($name, "subType", ""); my $model = AttrVal($name, "model", ""); my $tn = TimeNow(); # return if duplicate my $msgX = "No:$msgcnt - t:$msgType s:$src d:$dst $p"; if($shash->{lastMsg} && $shash->{lastMsg} eq $msgX) { Log GetLogLevel($name,4), "CUL_HM $name dup mesg"; return ""; #return something to please dispatcher } $shash->{lastMsg} = $msgX; $iohash->{HM_CMDNR} = hex($msgcnt) if($dst eq $id);# update messag counter to receiver CUL_HM_DumpProtocol("RCV",$iohash,$len,$msgcnt,$msgFlag,$msgType,$src,$dst,$p); #----------start valid messages parsing --------- my $parse = CUL_HM_parseCommon($msgcnt,$msgType,$src,$dst,$p); push @event, "powerOn" if($parse eq "powerOn"); my $sendAck = "yes";# if yes Ack will be determined automatically $sendAck = "" if ($parse eq "STATresp"); if ($parse eq "ACK"){# remember - ACKinfo will be passed on push @event, ""; } elsif($parse eq "NACK"){ push @event, "state:NACK"; } elsif($parse eq "done"){ push @event, ""; $sendAck = ""; } elsif($lcm eq "09A112") { #### Another fhem wants to talk (HAVE_DATA) ; } elsif($msgType eq "00" ){ #### DEVICE_INFO, Pairing-Request CUL_HM_infoUpdtDevData($name, $shash,$p);#update data if($shash->{cmdStack} && (CUL_HM_getRxType($shash) & 0x04)) { CUL_HM_ProcessCmdStack($shash);# sender devices may have msgs stacked push @event,""; } else { push @event, CUL_HM_Pair($name, $shash,$cmd,$src,$dst,$p); } $sendAck = ""; #todo why is this special? } elsif(($cmd =~ m/^A0[01]{2}$/ && $dst eq $id) && $st ne "keyMatic") {#### Pairing-Request-Convers. push @event, ""; #todo why end here? } elsif($model eq "KS550" || $model eq "HM-WDS100-C6-O") { ############ if($cmd eq "8670" && $p =~ m/^(....)(..)(....)(....)(..)(..)(..)/) { my ( $t, $h, $r, $w, $wd, $s, $b ) = (hex($1), hex($2), hex($3), hex($4), hex($5), hex($6), hex($7)); my $tsgn = ($t & 0x4000); $t = ($t & 0x3fff)/10; $t = sprintf("%0.1f", $t-1638.4) if($tsgn); my $ir = $r & 0x8000; $r = ($r & 0x7fff) * 0.295; my $wdr = ($w>>14)*22.5; $w = ($w & 0x3fff)/10; $wd = $wd * 5; push @event, "state:T: $t H: $h W: $w R: $r IR: $ir WD: $wd WDR: $wdr S: $s B: $b"; push @event, "temperature:$t"; push @event, "humidity:$h"; push @event, "windSpeed:$w"; push @event, "windDirection:$wd"; push @event, "windDirRange:$wdr"; push @event, "rain:$r"; push @event, "isRaining:$ir"; push @event, "sunshine:$s"; push @event, "brightness:$b"; } else { push @event, "unknown:$p"; } $sendAck = ""; #todo why is this special? } elsif($model eq "HM-CC-TC") { #################################### my ($sType,$chn) = ($1,$2) if($p =~ m/^(..)(..)/); if($msgType eq "70" && $p =~ m/^(....)(..)/) {# weather event $chn = '01'; # fix definition my ( $t, $h) = (hex($1), hex($2));# temp is 15 bit signed $t = ($t & 0x3fff)/10*(($t & 0x4000)?-1:1); push @event, "state:T: $t H: $h"; push @event, "measured-temp:$t"; push @event, "humidity:$h"; } elsif($msgType eq "58" && $p =~ m/^(..)(..)/) {#climate event $chn = '02'; # fix definition my ( $d1, $vp) = # adjust_command[0..4] adj_data[0..250] ( $1, hex($2)); $vp = int($vp/2.56+0.5); # valve position in % push @event, "actuator:$vp %"; # Set the valve state too, without an extra trigger if($dhash) { DoTrigger($dname,'ValvePosition:set_'.$vp.'%'); $dhash->{STATE} = "$vp %"; CUL_HM_setRd($dhash,"state","set_$vp %",$tn); } } elsif(($msgType eq '02' &&$sType eq '01')|| # ackStatus ($msgType eq '10' &&$sType eq '06')){ #infoStatus push @event, "desired-temp:" .sprintf("%0.1f", hex(substr($p,4,2))/2); } elsif($msgType eq "10"){ if( $p =~ m/^0403(......)(..)0505(..)0000/) { # change of chn 3(window) list 5 register 5 - a peer window changed! my ( $tdev, $tchan, $v1) = (($1), hex($2), hex($3)); push @event, sprintf("windowopen-temp-%d: %.1f (sensor:%s)" ,$tchan, $v1/2, $tdev); } elsif($p =~ m/^0402000000000(.)(..)(..)(..)(..)(..)(..)(..)(..)/) { # param list 5 or 6, 4 value pairs. my ($plist, $o1, $v1, $o2, $v2, $o3, $v3, $o4, $v4) = (hex($1),hex($2),hex($3),hex($4),hex($5),hex($6),hex($7),hex($8),hex($9)); my ($dayoff, $maxdays, $basevalue); my @days = ("Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri"); if($plist == 5 || $plist == 6) { if($plist == 5) { $dayoff = 0; $maxdays = 5; $basevalue = hex("0B"); } else { $dayoff = 5; $maxdays = 2; $basevalue = hex("01"); } my $idx = ($o1-$basevalue); my $dayidx = int($idx/48); if($idx % 4 == 0 && $dayidx < $maxdays) { $idx -= 48*$dayidx; $idx /= 2; my $ptr = $shash->{TEMPLIST}{$days[$dayidx+$dayoff]}; $ptr->{$idx}{HOUR} = int($v1/6); $ptr->{$idx}{MINUTE} = ($v1%6)*10; $ptr->{$idx}{TEMP} = $v2/2; $ptr->{$idx+1}{HOUR} = int($v3/6); $ptr->{$idx+1}{MINUTE} = ($v3%6)*10; $ptr->{$idx+1}{TEMP} = $v4/2; } } foreach my $wd (@days) { my $twentyfour = 0; my $msg = 'tempList'.$wd.':'; foreach(my $idx=0; $idx<24; $idx++) { my $ptr = $shash->{TEMPLIST}{$wd}{$idx}; if(defined ($ptr->{TEMP}) && $ptr->{TEMP} ne "") { if($twentyfour == 0) { $msg .= sprintf(" %02d:%02d %.1f", $ptr->{HOUR}, $ptr->{MINUTE}, $ptr->{TEMP}); } else { $ptr->{HOUR} = $ptr->{MINUTE} = $ptr->{TEMP} = ""; } } if(defined ($ptr->{HOUR}) && 0+$ptr->{HOUR} == 24) { $twentyfour = 1; # next value uninteresting, only first counts. } } push @event, $msg; # generate one event per day entry } } elsif($p =~ m/^04020000000005(..)(..)/) { my ( $o1, $v1) = (hex($1),hex($2));# only parse list 5 for chn 2 my $msg; my @days = ("Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri"); if($o1 == 1) { ### bitfield containing multiple values... my %mode = (0 => "manual",1 => "auto",2 => "central",3 => "party"); push @event,'displayMode:temperature '.(($v1 & 1)?" and humidity":" only"); push @event,'displayTemp:' .(($v1 & 2)?"setpoint" :"actual"); push @event,'displayTempUnit:' .(($v1 & 4)?"fahrenheit" :"celsius"); push @event,'controlMode:' .($mode{(($v1 & 0x18)>>3)}); push @event,'decalcDay:' .$days[($v1 & 0xE0)>>5]; } elsif($o1 == 2) { my %pos = (0=>"Auto",1=>"Closed",2=>"Open",3=>"unknown"); push @event,"tempValveMode:".$pos{(($v1 & 0xC0)>>6)}; } else{ push @event,'param-change: offset='.$o1.', value='.$v1; } } elsif($p =~ m/^0[23]/){#param response push @event,'';#cannot be handled here as request missing } } elsif($msgType eq "01"){ if($p =~ m/^010809(..)0A(..)/) { # TC set valve for VD => post events to VD my ( $of, $vep) = (hex($1), hex($2)); push @event, "ValveErrorPosition for $dname: $vep %"; push @event, "ValveOffset for $dname: $of %"; DoTrigger($dname,'ValveErrorPosition:set_'.$vep.'%'); DoTrigger($dname,'ValveOffset:set_'.$of.'%'); push @event,""; # nothing to report for TC } elsif($p =~ m/^010[56]/){ # 'prepare to set' or 'end set' push @event,""; # } } # ($cmd eq "A112" && $p =~ m/^0202(..)$/)) { # Set desired temp elsif($cmd eq "A03F" && $id eq $dst) { # Timestamp request my $s2000 = sprintf("%02X", CUL_HM_secSince2000()); CUL_HM_SendCmd($shash, "++803F$id${src}0204$s2000",1,0); push @event, "time-request"; $sendAck = ""; } if($cmd ne "8002" && $cmd ne "A03F" && $id eq $dst) { CUL_HM_SendCmd($shash, $msgcnt."8002$id${src}00",1,0) # Send Ack } $sendAck = ""; #todo why is this special? } elsif($model eq "HM-CC-VD") { ################### # CMD:8202 SRC:13F251 DST:15B50D 010100002A # status ACK to controlling HM-CC-TC if($msgType eq "02" && $p =~ m/^(..)(..)(..)(..)/) {#subtype+chn+value+err my ($chn,$vp, $err) = ($2,hex($3), hex($4)); $vp = int($vp)/2; # valve position in % push @event, "ValvePosition:$vp%"; $shash = $modules{CUL_HM}{defptr}{"$src$chn"} if($modules{CUL_HM}{defptr}{"$src$chn"}); my $cmpVal = defined($shash->{helper}{addVal})?$shash->{helper}{addVal}:0xff; $cmpVal = (($cmpVal ^ $err)|$err); # all error,only one goto normal $shash->{helper}{addVal} = $err; #store to handle changes # Status-Byte Auswertung my $stErr = ($err >>1) & 0x7; if ($cmpVal&0x0E){# report bad always, good only once if (!$stErr){#remove both conditions push @event, "battery:ok"; push @event, "motorErr:ok"; } else{ push @event, "motorErr:blocked" if($stErr == 1); push @event, "motorErr:loose" if($stErr == 2); push @event, "motorErr:adjusting range too small" if($stErr == 3); push @event, "battery:low" if($stErr == 4); } } push @event, "motor:opening" if(($err&0x30) == 0x10); push @event, "motor:closing" if(($err&0x30) == 0x20); push @event, "motor:stop" if(($err&0x30) == 0x00); } # CMD:A010 SRC:13F251 DST:5D24C9 0401 00000000 05 09:00 0A:07 00:00 # status change report to paired central unit #read List5 reg 09 (offset) and 0A (err-pos) #list 5 is channel-dependant not link dependant # => Link discriminator (00000000) is fixed elsif($msgType eq "10" && $p =~ m/^04..........0509(..)0A(..)/) { my ( $of, $vep) = (hex($1), hex($2)); push @event, "ValveErrorPosition:$vep%"; push @event, "ValveOffset:$of%"; } } elsif($st eq "KFM100" && $model eq "KFM-Sensor") { ################### if($p =~ m/.14(.)0200(..)(..)(..)/) {# todo very risky - no start... my ($k_cnt, $k_v1, $k_v2, $k_v3) = ($1,$2,$3,$4); my $v = 128-hex($k_v2); # FIXME: calibrate # $v = 256+$v if($v < 0); $v += 256 if(!($k_v3 & 1)); push @event, "rawValue:$v"; my $seq = hex($k_cnt); push @event, "Sequence:$seq"; my $r2r = AttrVal($name, "rawToReadable", undef); if($r2r) { my @r2r = split("[ :]", $r2r); foreach(my $idx = 0; $idx < @r2r-2; $idx+=2) { if($v >= $r2r[$idx] && $v <= $r2r[$idx+2]) { my $f = (($v-$r2r[$idx])/($r2r[$idx+2]-$r2r[$idx])); my $cv = ($r2r[$idx+3]-$r2r[$idx+1])*$f + $r2r[$idx+1]; my $unit = AttrVal($name, "unit", ""); $unit = " $unit" if($unit); push @event, sprintf("state:%.1f %s",$cv,$unit); push @event, sprintf("content:%.1f %s",$cv,$unit); last; } } } else { push @event, "state:$v"; } $sendAck = ""; #todo why no ack? } } elsif($st eq "switch" || ############################################ $st eq "dimmer" || $st eq "blindActuator") { if (($msgType eq "02" && $p =~ m/^01/) || # handle Ack_Status ($msgType eq "10" && $p =~ m/^06/)) { # or Info_Status message here my ($subType,$chn,$level,$err) = ($1,$2,$3,hex($4)) if($p =~ m/^(..)(..)(..)(..)/); # Multi-channel device: Use channel if defined $shash = $modules{CUL_HM}{defptr}{"$src$chn"} if($modules{CUL_HM}{defptr}{"$src$chn"}); my $cmpVal = defined($shash->{helper}{addVal})?$shash->{helper}{addVal}:0xff; $cmpVal = (($cmpVal ^ $err)|$err); # all error,only one goto normal $shash->{helper}{addVal} = $err; #store to handle changes my $val = hex($level)/2; $val = ($val == 100 ? "on" : ($val == 0 ? "off" : "$val %")); push @event, "deviceMsg:$val$target" if($chn ne "00"); #hack for blind - other then behaved devices blind does not send # a status info fo rchan 0 at power on # chn3 (virtual chan) and not used up to now # info from it is likely a power on! push @event, "powerOn" if($chn eq "03"&&$st eq "dimmer"); my $eventName = "unknown"; # different names for events $eventName = "switch" if($st eq "switch"); $eventName = "motor" if($st eq "blindActuator"); $eventName = "dim" if($st eq "dimmer"); my $action; #determine action if ($st ne "switch"){ push @event, "$eventName:up:$val" if(($err&0x30) == 0x10); push @event, "$eventName:down:$val" if(($err&0x30) == 0x20); push @event, "$eventName:stop:$val" if(($err&0x30) == 0x00); } push @event, "battery:" . (($err&0x80) ? "low" : "ok" ) if(($model eq "HM-LC-SW1-BA-PCB")&&($cmpVal&0x80)); push @event, "state:$val"; } } elsif($st eq "remote" || $st eq "pushButton" || $st eq "swi") { ############# if($msgType =~ m/^4./ && $p =~ m/^(..)(..)$/) { my ($buttonField, $bno) = (hex($1), hex($2));# button number/event count my $buttonID = $buttonField&0x3f;# only 6 bit are valid my $btnName; my $state = ""; my $chnHash = $modules{CUL_HM}{defptr}{$src.uc(sprintf("%02x",$buttonID))}; if ($chnHash){# use userdefined name - ignore this irritating on-off naming $btnName = $chnHash->{NAME}; } else{# Button not defined, use default naming $chnHash = $shash; if ($st eq "swi"){#maintain history for event naming $btnName = "Btn$buttonField"; }else{ my $btn = int((($buttonField&0x3f)+1)/2); $btnName = "Btn$btn"; $state = ($buttonField&1 ? "off" : "on") } } if($buttonField & 0x40){ if(!$shash->{BNO} || $shash->{BNO} ne $bno){#bno = event counter $shash->{BNO}=$bno; $shash->{BNOCNT}=1; # message counter reest } $shash->{BNOCNT}+=1; $state .= "Long" .($msgFlag eq "A0" ? "Release" : ""). " ".$shash->{BNOCNT}."-".$cmd."-"; } else{ $state .= ($st eq "swi")?"toggle":"Short";#swi only support toggle } my $cmpVal = defined($shash->{helper}{addVal})?$shash->{helper}{addVal}:0xff; $cmpVal = (($cmpVal ^ $buttonField)|$buttonField); # all error,only one goto normal $shash->{helper}{addVal} = $buttonField; #store to handle changes push @event,"battery:". (($buttonField&0x80)?"low":"ok")if($cmpVal&0x80); push @event, "state:$btnName $state$target"; $chnHash->{STATE} = $state.$target; #handle channel manually, others to device DoTrigger($btnName,"$state$target"); if($id eq $dst) { # Send Ack CUL_HM_SendCmd($shash, $msgcnt."8002".$id.$src."0101". ($state =~ m/on/?"C8":"00")."00", 1, 0);#Actor simulation } $sendAck = ""; #todo why is this special? } } elsif($st eq "virtual"){##################################################### # possibly add code to count all acks that are paired. if($msgType eq "02") { push @event, "ackFrom ".$name; } } elsif($st eq "outputUnit"){################################################## if($msgType eq "40" && $p =~ m/^(..)(..)$/){ my ($button, $bno) = (hex($1), hex($2)); if(!(exists($shash->{BNO})) || $shash->{BNO} ne $bno){ $shash->{BNO}=$bno; $shash->{BNOCNT}=1; } else{ $shash->{BNOCNT}+=1; } my $btn = int($button&0x3f); push @event, "state:Btn$btn on$target"; } elsif(($msgType eq "02" && $p =~ m/^01/) || # handle Ack_Status ($msgType eq "10" && $p =~ m/^06/)){ # or Info_Status message my ($msgChn,$msgState) = ($1,$2) if ($p =~ m/..(..)(..)/); my $chnHash = $modules{CUL_HM}{defptr}{$src.$msgChn}; if ($model eq "HM-OU-LED16") { #special: all LEDs map to device state my $devState = ReadingsVal($name,"color","00000000"); if($parse eq "powerOn"){# reset LEDs after power on CUL_HM_PushCmdStack($shash,'++A011'.$id.$src."8100".$devState); CUL_HM_ProcessCmdStack($shash); } else {# just update datafields in storage my $bitLoc = ((hex($msgChn)-1)*2);#calculate bit location my $mask = 3<<$bitLoc; my $value = (hex($devState) &~$mask)|($msgState<<$bitLoc); push @event,"color:".sprintf("%08X",$value); if ($chnHash){ $shash = $chnHash; my %colorTable=("00"=>"off","01"=>"red","02"=>"green","03"=>"orange"); my $actColor = $colorTable{$msgState}; $actColor = "unknown" if(!$actColor); CUL_HM_setRd($chnHash,"color",$actColor,$tn); push @event, "state:$actColor"; } } } elsif ($model eq "HM-OU-CFM-PL"){ if ($chnHash){ $shash = $chnHash; my $val = hex($msgState)/2; $val = ($val == 100 ? "on" : ($val == 0 ? "off" : "$val %")); push @event, "state:$val"; } $sendAck = ""; ##no ack for those messages! } } } elsif($st eq "motionDetector") { ##################################### # Code with help of Bassem my $state; if(($msgType eq "10" ||$msgType eq "02") && $p =~ m/^0601(..)(..)/) { my $err; ($state, $err) = ($1, hex($2)); my $cmpVal = defined($shash->{helper}{addVal})? $shash->{helper}{addVal}:0xff; $cmpVal = (($cmpVal ^ $err)|$err); # all error,only one goto normal $shash->{helper}{addVal} = $err;#store to handle changes my $bright = hex($state); push @event, "brightness:".$bright if (ReadingsVal($name,"brightness","") != $bright);# post if changed push @event, "cover:". (($err&0x0E)?"open" :"closed") if ($cmpVal&0x0E); push @event, "battery:". (($err&0x80)?"low" :"ok" ) if ($cmpVal&0x80); } elsif($msgType eq "41" && $p =~ m/^..(..)(..)(..)/) { my($cnt, $bright,$nextTr) = (hex($1), hex($2),(hex($3)>>4)); push @event, "state:motion"; push @event, "motion:on$target"; #added peterp push @event, "motionCount:".$cnt."_next:".$nextTr; push @event, "brightness:".$bright if (ReadingsVal($name,"brightness","") != $bright);# post if changed } elsif($msgType eq "70" && $p =~ m/^7F(..)(.*)/) { my($d1, $d2) = ($1, $2); push @event, 'devState_raw'.$d1.':'.$d2; } CUL_HM_SendCmd($shash, $msgcnt."8002".$id.$src."0101${state}00",1,0) if($id eq $dst && $cmd ne "8002"); # Send AckStatus $sendAck = ""; #todo why is this special? } elsif($st eq "smokeDetector") { ##################################### #todo: check for correct msgType, see below #AckStatus : msgType=0x02 p(..)(..)(..) subtype=01, channel, state (1 byte) #Info Level: msgType=0x10 p(..)(..)(..) subtype=06, channel, state (1 byte) #Event: msgType=0x41 p(..)(..)(..) channel , unknown, state (1 byte) my $level = $1 if($p =~ m/01..(..)/);# todo: fancy incomplete way to parse an AckStatus if ($level) { if ($level eq "C8"){ push @event, "state:on"; push @event, "smoke_detect:on$target"; }elsif($level eq "01"){ push @event, "state:all-clear"; }else{ push @event, "state:$level";#todo - maybe calculate the level in % } } if ($msgType eq "10"){ #todo: why is the information in InfoLevel ignored? push @event, "state:alive"; } if($p =~ m/^00(..)$/) { push @event, "test:$1"; } push @event, "SDunknownMsg:$p" if(!@event); CUL_HM_SendCmd($shash, $msgcnt."8002".$id.$src.($cmd eq "A001" ? "80":"00"),1,0) if($id eq $dst && $cmd ne "8002"); # Send Ack/Nack $sendAck = ""; #todo why is this special? } elsif($st eq "threeStateSensor") { ##################################### #todo: check for correct msgType, see below #Event: msgType=0x41 p(..)(..)(..) channel , unknown, state #Info Level: msgType=0x10 p(..)(..)(..)(..) subty=06, chn, state,err (3bit) #AckStatus: msgType=0x02 p(..)(..)(..)(..) subty=01, chn, state,err (3bit) if($p =~ m/^(..)(..)(..)(..)?$/) { my ($b12, $b34, $state) = ($1, $2, $3); my $err; $err = hex($4) if(defined($4)); my $chn = ($msgType eq "41")?$b12:$b34; # Multi-channel device: Switch to channel hash $shash = $modules{CUL_HM}{defptr}{"$src$chn"} if($modules{CUL_HM}{defptr}{"$src$chn"}); if ($msgType eq "02"||$msgType eq "10"){ my $cmpVal = defined($shash->{helper}{addVal})?$shash->{helper}{addVal}:0xff; $cmpVal = (defined($err))?(($cmpVal ^ $err)|$err):0; # all error,one normal $shash->{helper}{addVal} = $err;#store to handle changes push @event, "alive:yes"; push @event, "battery:". (($err&0x80)?"low" :"ok" ) if($cmpVal&0x80); if ($model ne "HM-SEC-WDS"){ push @event, "cover:". (($err&0x0E)?"open" :"closed")if($cmpVal&0x0E); } } my %txt; %txt = ("C8"=>"open", "64"=>"tilted", "00"=>"closed"); %txt = ("C8"=>"wet", "64"=>"damp", "00"=>"dry") # by peterp if($model eq "HM-SEC-WDS"); my $txt = $txt{$state}; $txt = "unknown:$state" if(!$txt); push @event, "state:$txt"; push @event, "contact:$txt$target"; CUL_HM_SendCmd($shash, $msgcnt."8002$id$src${chn}00",1,0) # Send Ack if($id eq $dst); $sendAck = ""; #todo why is this special? } push @event, "3SSunknownMsg:$p" if(!@event); } elsif($model eq "HM-WDC7000" ||$st eq "THSensor") { #################### my $t = hex(substr($p,0,4)); $t -= 32768 if($t > 1638.4); $t = sprintf("%0.1f", $t/10); my $h = hex(substr($p,4,2)); my $ap = hex(substr($p,6,4)); my $statemsg = "state:T: $t"; $statemsg .= " H: $h" if ($h); $statemsg .= " AP: $ap" if ($ap); push @event, $statemsg; push @event, "temperature:$t";#temp is always there push @event, "humidity:$h" if ($h); push @event, "airpress:$ap" if ($ap); $sendAck = ""; } elsif($st eq "winMatic") { #################################### if($msgType eq "10"){ if ($p =~ m/^0601(..)(..)/) { my ($lst, $flg) = ($1, $2); if($lst eq "C8" && $flg eq "00") { push @event, "contact:tilted"; } elsif($lst eq "FF" && $flg eq "00") { push @event, "contact:closed"; } elsif($lst eq "FF" && $flg eq "10") { push @event, "contact:lock_on"; } elsif($lst eq "00" && $flg eq "10") { push @event, "contact:movement_tilted"; } elsif($lst eq "00" && $flg eq "20") { push @event, "contact:movement_closed"; } elsif($lst eq "00" && $flg eq "30") { push @event, "contact:open"; } CUL_HM_SendCmd($shash, $msgcnt."8002".$id.$src."0101".$lst."00",1,0) if($id eq $dst);# Send AckStatus $sendAck = ""; } elsif ($p =~ m/^0287(..)89(..)8B(..)/) { my ($air, undef, $course) = ($1, $2, $3); push @event, "airing:".($air eq "FF" ? "inactiv" : CUL_HM_decodeTime8($air)); push @event, "course:".($course eq "FF" ? "tilt" : "close"); } elsif($p =~ m/^0201(..)03(..)04(..)05(..)07(..)09(..)0B(..)0D(..)/) { my ($flg1, $flg2, $flg3, $flg4, $flg5, $flg6, $flg7, $flg8) = ($1, $2, $3, $4, $5, $6, $7, $8); push @event, "airing:".($flg5 eq "FF" ? "inactiv" : CUL_HM_decodeTime8($flg5)); push @event, "contact:tesed"; } } } elsif($st eq "keyMatic") { #################################### #Info Level: msgType=0x10 p(..)(..)(..)(..) subty=06, chn, state,err (3bit) #AckStatus: msgType=0x02 p(..)(..)(..)(..) subty=01, chn, state,err (3bit) if(($msgType eq "10" && $p =~ m/^06/) || ($msgType eq "02" && $p =~ m/^01/)) { $p =~ m/^..(..)(..)(..)/; my ($chn,$val, $err) = ($1,hex($2), hex($3)); $shash = $modules{CUL_HM}{defptr}{"$src$chn"} if($modules{CUL_HM}{defptr}{"$src$chn"}); my $cmpVal = defined($shash->{helper}{addVal})?$shash->{helper}{addVal}:0xff; $cmpVal = (($cmpVal ^ $err)|$err); # all error,only one goto normal $shash->{helper}{addVal} = $err; #store to handle changes my $stErr = ($err >>1) & 0x7; my $error = 'unknown_'.$stErr; $error = 'motor aborted' if ($stErr == 2); $error = 'clutch failure' if ($stErr == 1); $error = 'none' if ($stErr == 0); push @event, "unknown:" . (($err&0x40) ? "40" :"") if($cmpVal&0x40); push @event, "battery:". (($err&0x80) ? "low":"ok") if($cmpVal&0x80); push @event, "uncertain:" .(($err&0x30) ? "yes":"no") if($cmpVal&0x30); push @event, "error:" . ($error) if($cmpVal&0x0E); my $state = ($err & 0x30) ? " (uncertain)" : ""; push @event, "lock:" . (($val == 1) ? "unlocked" : "locked"); push @event, "state:" . (($val == 1) ? "unlocked" : "locked") . $state; } } else{##################################### ; # no one wants the message } #------------ parse for virtual destination ------------------ if (AttrVal($dname, "subType", "none") eq "virtual"){# see if need for answer if($msgType =~ m/^4./ && $p =~ m/^(..)/) { my ($recChn) = ($1);# button number/event count my $recId = $src.$recChn; for (my $cnt=1;$cnt<25;$cnt++) {#need to check each channel my $dChNo = sprintf("%02X",$cnt); my $dChName = AttrVal($dname,"channel_".$dChNo,""); if (!$dChName){next;} # not channel provisioned my @peerIDs = split(',',AttrVal($dChName,"peerIDs","")); foreach my $pId (@peerIDs){ if ($pId eq $recId){ #match: we have to ack my $dChHash = CUL_HM_name2hash($dChName); my $state = ReadingsVal($dChName,"virtActState","C8"); $state = ($state eq "00")?"C8":"00"; setReadingsVal($dChHash,"virtActState",$state,$tn); setReadingsVal($dChHash,"virtActTrigger",$name,$tn); CUL_HM_SendCmd($dChHash,$msgcnt."8002".$dst.$src.'01'.$dChNo. $state."00", 1, 0); } } } } } #------------ send default ACK if not applicable------------------ # ack if we are destination, anyone did accept the message (@event) # parser did not supress CUL_HM_SendCmd($shash, $msgcnt."8002".$id.$src."00",1,0) # Send Ack if( ($id eq $dst) #are we adressee && ($msgType ne "02") #no ack for ack && @event #only ack of we identified it && ($sendAck eq "yes")); #sender requested ACK #------------ process events ------------------ push @event, "noReceiver:src:$src ($cmd) $p" if(!@event); my @changed; for(my $i = 0; $i < int(@event); $i++) { next if($event[$i] eq ""); my ($vn, $vv) = split(":", $event[$i], 2); if($vn eq "state") { if($shash->{cmdSent} && $shash->{cmdSent} eq $vv) { delete($shash->{cmdSent}); # Skip second "on/off" after our own command } else { $shash->{STATE} = $vv; push @changed, $vv; } } else { push @changed, ($vn.": ".(($vv)?$vv:"-")); } CUL_HM_setRd($shash,$vn,$vv,$tn); } $shash->{CHANGED} = \@changed; return $shash->{NAME} ;# shash could have changed to support channel } ##----------definitions for register settings----------------- # definition of Register for all devices # a: address, incl bits 13.4 4th bit in reg 13 # s: size 2.0 = 2 byte, 0.5 = 5 bit. Max is 4.0!! # l: list number. List0 will be for channel 0 # List 1 will set peer to 00000000 # list 3 will need the input of a peer! # min: minimal input value # max: maximal input value # c: conversion, will point to a routine for calculation # f: factor to be used if c = 'factor' # u: unit for description # t: txt description # caution: !!! bitfield setting will zero the rest of the register # if less then a byte !!!!!!!!!!! my %culHmRegDefine = ( intKeyVisib =>{a=> 2.7,s=>0.1,l=>0,min=>0 ,max=>1 ,c=>"" ,f=>"" ,u=>'bool',t=>'visibility of internal keys'}, pairCentral =>{a=> 10.0,s=>3.0,l=>0,min=>0 ,max=>16777215,c=>'' ,f=>"" ,u=>'dec' ,t=>'pairing to central'}, #blindActuator mainly driveUp =>{a=> 13.0,s=>2.0,l=>1,min=>0 ,max=>6000.0,c=>'factor' ,f=>10 ,u=>'s' ,t=>"drive time up"}, driveDown =>{a=> 11.0,s=>2.0,l=>1,min=>0 ,max=>6000.0,c=>'factor' ,f=>10 ,u=>'s' ,t=>"drive time up"}, driveTurn =>{a=> 15.0,s=>1.0,l=>1,min=>0 ,max=>6000.0,c=>'factor' ,f=>10 ,u=>'s' ,t=>"fliptime up <=>down"}, maxTimeFSh =>{a=> 29.0,s=>1.0,l=>3,min=>0 ,max=>25.4 ,c=>'factor' ,f=>10 ,u=>'s' ,t=>"Short:max time first direction"}, maxTimeFLg =>{a=>157.0,s=>1.0,l=>3,min=>0 ,max=>25.4 ,c=>'factor' ,f=>10 ,u=>'s' ,t=>"Long:max time first direction"}, #remote mainly language =>{a=> 7.0,s=>1.0,l=>0,min=>0 ,max=>1 ,c=>"" ,f=>"" ,u=>'' ,t=>"Language 0:English, 1:German"}, stbyTime =>{a=> 14.0,s=>1.0,l=>0,min=>1 ,max=>99 ,c=>"" ,f=>"" ,u=>'s' ,t=>"Standby Time"}, backOnTime =>{a=> 14.0,s=>1.0,l=>0,min=>0 ,max=>255 ,c=>"" ,f=>"" ,u=>'s' ,t=>"Backlight On Time"}, backAtEvnt =>{a=> 13.5,s=>0.3,l=>0,min=>0 ,max=>8 ,c=>"" ,f=>"" ,u=>'' ,t=>"Backlight at key=4,motion=2,charge=1"}, longPress =>{a=> 4.4,s=>0.4,l=>1,min=>0.3,max=>1.8 ,c=>'m10s3' ,f=>"" ,u=>'s' ,t=>"time to detect key long press"}, msgShowTime =>{a=> 45.0,s=>1.0,l=>1,min=>0.0,max=>120 ,c=>'factor' ,f=>2 ,u=>'s' ,t=>"Message show time(RC19). 0=always on"}, #dimmer mainly ovrTempLvl =>{a=> 50.0,s=>1.0,l=>1,min=>30 ,max=>100 ,c=>"" ,f=>"" ,u=>"degC",t=>"overtemperatur level"}, redTempLvl =>{a=> 52.0,s=>1.0,l=>1,min=>30 ,max=>100 ,c=>"" ,f=>"" ,u=>"degC",t=>"reduced temperatur recover"}, redLvl =>{a=> 53.0,s=>1.0,l=>1,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>"%" ,t=>"reduced power level"}, OnDlySh =>{a=> 6.0,s=>1.0,l=>3,min=>0 ,max=>111600,c=>'fltCvT' ,f=>'' ,u=>'s' ,t=>"Short:on delay "}, OnTimeSh =>{a=> 7.0,s=>1.0,l=>3,min=>0 ,max=>111600,c=>'fltCvT' ,f=>'' ,u=>'s' ,t=>"Short:on time"}, OffDlySh =>{a=> 8.0,s=>1.0,l=>3,min=>0 ,max=>111600,c=>'fltCvT' ,f=>'' ,u=>'s' ,t=>"Short:off delay"}, OffTimeSh =>{a=> 9.0,s=>1.0,l=>3,min=>0 ,max=>111600,c=>'fltCvT' ,f=>'' ,u=>'s' ,t=>"Short:off time"}, OffLevelSh =>{a=> 15.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,t=>"Short:PowerLevel Off"}, OnMinLevelSh =>{a=> 16.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,t=>"Short:minimum PowerLevel"}, OnLevelSh =>{a=> 17.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,t=>"Short:PowerLevel on"}, OffLevelKmSh =>{a=> 15.0,s=>1.0,l=>3,min=>0 ,max=>127.5 ,c=>'factor' ,f=>2 ,u=>'%' ,t=>"Short:OnLevel 127.5=locked"}, OnLevelKmSh =>{a=> 17.0,s=>1.0,l=>3,min=>0 ,max=>127.5 ,c=>'factor' ,f=>2 ,u=>'%' ,t=>"Short:OnLevel 127.5=locked"}, OnRampOnSpSh =>{a=> 34.0,s=>1.0,l=>3,min=>0 ,max=>1 ,c=>'factor' ,f=>200 ,u=>'s' ,t=>"Short:Ramp On speed"}, OnRampOffSpSh=>{a=> 35.0,s=>1.0,l=>3,min=>0 ,max=>1 ,c=>'factor' ,f=>200 ,u=>'s' ,t=>"Short:Ramp Off speed"}, rampSstepSh =>{a=> 18.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,t=>"Short:rampStartStep"}, rampOnTimeSh =>{a=> 19.0,s=>1.0,l=>3,min=>0 ,max=>111600,c=>'fltCvT' ,f=>'' ,u=>'s' ,t=>"Short:rampOnTime"}, rampOffTimeSh=>{a=> 20.0,s=>1.0,l=>3,min=>0 ,max=>111600,c=>'fltCvT' ,f=>'' ,u=>'s' ,t=>"Short:rampOffTime"}, dimMinLvlSh =>{a=> 21.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,t=>"Short:dimMinLevel"}, dimMaxLvlSh =>{a=> 22.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,t=>"Short:dimMaxLevel"}, dimStepSh =>{a=> 23.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,t=>"Short:dimStep"}, OnDlyLg =>{a=>134.0,s=>1.0,l=>3,min=>0 ,max=>111600,c=>'fltCvT' ,f=>'' ,u=>'s' ,t=>"Long:on delay"}, OnTimeLg =>{a=>135.0,s=>1.0,l=>3,min=>0 ,max=>111600,c=>'fltCvT' ,f=>'' ,u=>'s' ,t=>"Long:on time"}, OffDlyLg =>{a=>136.0,s=>1.0,l=>3,min=>0 ,max=>111600,c=>'fltCvT' ,f=>'' ,u=>'s' ,t=>"Long:off delay"}, OffTimeLg =>{a=>137.0,s=>1.0,l=>3,min=>0 ,max=>111600,c=>'fltCvT' ,f=>'' ,u=>'s' ,t=>"Long:off time"}, OffLevelLg =>{a=>143.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,t=>"Long:PowerLevel Off"}, OnMinLevelLg =>{a=>144.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,t=>"Long:minimum PowerLevel"}, OnLevelLg =>{a=>145.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,t=>"Long:PowerLevel on"}, rampSstepLg =>{a=>146.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,t=>"Long:rampStartStep"}, rampOnTimeLg =>{a=>147.0,s=>1.0,l=>3,min=>0 ,max=>111600,c=>'fltCvT' ,f=>'' ,u=>'s' ,t=>"Long:off delay"}, rampOffTimeLg=>{a=>148.0,s=>1.0,l=>3,min=>0 ,max=>111600,c=>'fltCvT' ,f=>'' ,u=>'s' ,t=>"Long:off delay"}, dimMinLvlLg =>{a=>149.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,t=>"Long:dimMinLevel"}, dimMaxLvlLg =>{a=>150.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,t=>"Long:dimMaxLevel"}, dimStepLg =>{a=>151.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,t=>"Long:dimStep"}, OffLevelKmLg =>{a=>143.0,s=>1.0,l=>3,min=>0 ,max=>127.5 ,c=>'factor' ,f=>2 ,u=>'%' ,t=>"Long:OnLevel 127.5=locked"}, OnLevelKmLg =>{a=>145.0,s=>1.0,l=>3,min=>0 ,max=>127.5 ,c=>'factor' ,f=>2 ,u=>'%' ,t=>"Long:OnLevel 127.5=locked"}, OnRampOnSpLg =>{a=>162.0,s=>1.0,l=>3,min=>0 ,max=>1 ,c=>'factor' ,f=>200 ,u=>'s' ,t=>"Long:Ramp On speed"}, OnRampOffSpLg=>{a=>163.0,s=>1.0,l=>3,min=>0 ,max=>1 ,c=>'factor' ,f=>200 ,u=>'s' ,t=>"Long:Ramp Off speed"}, #tc BacklOnTime =>{a=>5.0 ,s=>0.6,l=>0,min=>1 ,max=>25 ,c=>"" ,f=>'' ,u=>'s' ,t=>"Backlight ontime"}, BacklOnMode =>{a=>5.6 ,s=>0.2,l=>0,min=>0 ,max=>1 ,c=>'factor' ,f=>2 ,u=>'bool',t=>"Backlight mode 0=OFF, 1=AUTO"}, BtnLock =>{a=>15 ,s=>1 ,l=>0,min=>0 ,max=>1 ,c=>'' ,f=>'' ,u=>'bool',t=>"Button Lock 0=OFF, 1=Lock"}, DispTempHum =>{a=>1.0 ,s=>0.1,l=>5,min=>0 ,max=>1 ,c=>'' ,f=>'' ,u=>'bool',t=>"0=temp ,1=temp-humidity"}, DispTempInfo =>{a=>1.1 ,s=>0.1,l=>5,min=>0 ,max=>1 ,c=>'' ,f=>'' ,u=>'bool',t=>"0=actual ,1=setPoint"}, DispTempUnit =>{a=>1.2 ,s=>0.1,l=>5,min=>0 ,max=>1 ,c=>'' ,f=>'' ,u=>'bool',t=>"0=Celsius ,1=Fahrenheit"}, MdTempReg =>{a=>1.3 ,s=>0.2,l=>5,min=>0 ,max=>3 ,c=>'' ,f=>'' ,u=>'' ,t=>"0=MANUAL ,1=AUTO ,2=CENTRAL ,3=PARTY"}, MdTempValve =>{a=>2.6 ,s=>0.2,l=>5,min=>0 ,max=>2 ,c=>'' ,f=>'' ,u=>'' ,t=>"0=auto ,1=close ,2=open"}, TempComfort =>{a=>3 ,s=>0.6,l=>5,min=>6 ,max=>30 ,c=>'factor' ,f=>2 ,u=>'C' ,t=>"confort temp value"}, TempLower =>{a=>4 ,s=>0.6,l=>5,min=>6 ,max=>30 ,c=>'factor' ,f=>2 ,u=>'C' ,t=>"confort temp value"}, PartyEndDay =>{a=>98 ,s=>1 ,l=>6,min=>0 ,max=>200 ,c=>'' ,f=>'' ,u=>'d' ,t=>"Party end Day"}, PartyEndMin =>{a=>97.7 ,s=>1 ,l=>6,min=>0 ,max=>1 ,c=>'' ,f=>'' ,u=>'min' ,t=>"Party end 0=:00, 1=:30"}, PartyEndHr =>{a=>97 ,s=>0.6,l=>6,min=>0 ,max=>23 ,c=>'' ,f=>'' ,u=>'h' ,t=>"Party end Hour"}, TempParty =>{a=>6 ,s=>0.6,l=>5,min=>6 ,max=>30 ,c=>'factor' ,f=>2 ,u=>'C' ,t=>"Temperature for Party"}, TempWinOpen =>{a=>5 ,s=>0.6,l=>5,min=>6 ,max=>30 ,c=>'factor' ,f=>2 ,u=>'C' ,t=>"Temperature for Win open !chan 3 only!"}, DecalDay =>{a=>1.5 ,s=>0.3,l=>5,min=>0 ,max=>7 ,c=>'' ,f=>'' ,u=>'d' ,t=>"Decalc weekday 0=Sat...6=Fri"}, DecalHr =>{a=>8.3 ,s=>0.5,l=>5,min=>0 ,max=>23 ,c=>'' ,f=>'' ,u=>'h' ,t=>"Decalc hour"}, DecalMin =>{a=>8 ,s=>0.3,l=>5,min=>0 ,max=>50 ,c=>'factor' ,f=>0.1 ,u=>'min' ,t=>"Decalc min"}, #Thermal-cc-VD ValveOffset =>{a=>9 ,s=>0.5,l=>5,min=>0 ,max=>25 ,c=>'' ,f=>'' ,u=>'%' ,t=>"Valve offset"}, # size actually 0.5 ValveError =>{a=>10 ,s=>1 ,l=>5,min=>0 ,max=>99 ,c=>'' ,f=>'' ,u=>'%' ,t=>"Valve position when error"},# size actually 0.7 #output Unit ActTypeSh =>{a=>36 ,s=>1 ,l=>3,min=>0 ,max=>255 ,c=>'' ,f=>'' ,u=>'' ,t=>"Short:Action type(LED or Tone)"}, ActNumSh =>{a=>37 ,s=>1 ,l=>3,min=>1 ,max=>255 ,c=>'' ,f=>'' ,u=>'' ,t=>"Short:Action Number"}, IntenseSh =>{a=>47 ,s=>1 ,l=>3,min=>10 ,max=>255 ,c=>'' ,f=>'' ,u=>'' ,t=>"Short:Volume - Tone channel only!"}, ActTypeLg =>{a=>164 ,s=>1 ,l=>3,min=>0 ,max=>255 ,c=>'' ,f=>'' ,u=>'' ,t=>"Long:Action type(LED or Tone)"}, ActNumLg =>{a=>165 ,s=>1 ,l=>3,min=>1 ,max=>255 ,c=>'' ,f=>'' ,u=>'' ,t=>"Long:Action Number"}, IntenseLg =>{a=>175 ,s=>1 ,l=>3,min=>10 ,max=>255 ,c=>'' ,f=>'' ,u=>'' ,t=>"Long:Volume - Tone channel only!"}, # keymatic secific register signal =>{a=>3.4 ,s=>0.1,l=>0,min=>0 ,max=>1 ,c=>'' ,f=>'' ,u=>'bool',t=>"Confirmation beep 0=OFF, 1=On"}, signalTone =>{a=>3.6 ,s=>0.2,l=>0,min=>0 ,max=>3 ,c=>'' ,f=>'' ,u=>'%' ,t=>"0=low 1=mid 2=high 3=very high"}, keypressSignal=>{a=>3.0 ,s=>0.1,l=>0,min=>0 ,max=>1 ,c=>'' ,f=>'' ,u=>'bool',t=>"Keypress beep 0=OFF, 1=On"}, holdTime =>{a=>20 ,s=>1, l=>1,min=>0 ,max=>8.16 ,c=>'factor' ,f=>31.25 ,u=>'s' ,t=>"Holdtime for door opening"}, setupDir =>{a=>22 ,s=>0.1,l=>1,min=>0 ,max=>1 ,c=>'' ,f=>'' ,u=>'bool',t=>"Rotation direction for locking. ,0=right, 1=left"}, setupPosition =>{a=>23 ,s=>1 ,l=>1,min=>0 ,max=>3000 ,c=>'factor' ,f=>15 ,u=>'%' ,t=>"Rotation angle neutral position"}, angelOpen =>{a=>24 ,s=>1 ,l=>1,min=>0 ,max=>3000 ,c=>'factor' ,f=>15 ,u=>'%' ,t=>"Door opening angle"}, angelMax =>{a=>25 ,s=>1 ,l=>1,min=>0 ,max=>3000 ,c=>'factor' ,f=>15 ,u=>'%' ,t=>"Angle locked"}, angelLocked =>{a=>26 ,s=>1 ,l=>1,min=>0 ,max=>3000 ,c=>'factor' ,f=>15 ,u=>'%' ,t=>"Angle Locked position"}, ledFlashUnlocked=>{a=>31.3,s=>0.1,l=>1,min=>0,max=>1 ,c=>'' ,f=>'' ,u=>'bool',t=>"1=LED blinks when not locked"}, ledFlashLocked=>{a=>31.6,s=>0.1,l=>1,min=>0 ,max=>1 ,c=>'' ,f=>'' ,u=>'bool',t=>"1=LED blinks when locked"}, # sec_mdir evtFltrPeriod =>{a=>1.0 ,s=>0.4,l=>1,min=>0.5,max=>7.5 ,c=>'factor' ,f=>2 ,u=>'s' ,t=>"event filter period"}, evtFltrNum =>{a=>1.4 ,s=>0.4,l=>1,min=>1 ,max=>15 ,c=>'' ,f=>'' ,u=>'' ,t=>"sensitivity - read sach n-th puls"}, minInterval =>{a=>2.0 ,s=>0.3,l=>1,min=>0 ,max=>4 ,c=>'' ,f=>'' ,u=>'' ,t=>"minimum interval 0,15,20,60,120s"}, captInInterval=>{a=>2.3 ,s=>0.1,l=>1,min=>0 ,max=>1 ,c=>'' ,f=>'' ,u=>'bool',t=>"capture within interval"}, brightFilter =>{a=>2.4 ,s=>0.4,l=>1,min=>0 ,max=>7 ,c=>'' ,f=>'' ,u=>'' ,t=>"brightness filter"}, ledOnTime =>{a=>34 ,s=>1 ,l=>1,min=>0 ,max=>1.275 ,c=>'factor' ,f=>200 ,u=>'s' ,t=>"LED ontime"}, ); my %culHmRegGeneral = ( intKeyVisib=>1,pairCentral=>1, ); my %culHmRegSupported = ( remote=> {backOnTime=>1,backAtEvnt=>1,longPress=>1,msgShowTime=>1,}, blindActuator=> {driveUp=>1, driveDown=>1 , driveTurn=>1, maxTimeFSh =>1, maxTimeFLg =>1, OnDlySh=>1, OnTimeSh=>1, OffDlySh =>1, OffTimeSh=>1, OnDlyLg=>1, OnTimeLg=>1, OffDlyLg =>1, OffTimeLg=>1, OffLevelSh =>1, OnLevelSh =>1, OffLevelLg =>1, OnLevelLg =>1, }, dimmer=> {ovrTempLvl =>1,redTempLvl =>1,redLvl =>1, OnDlySh =>1,OnTimeSh =>1,OffDlySh =>1,OffTimeSh =>1, OnDlyLg =>1,OnTimeLg =>1,OffDlyLg =>1,OffTimeLg =>1, OffLevelSh =>1,OnMinLevelSh=>1,OnLevelSh =>1, OffLevelLg =>1,OnMinLevelLg=>1,OnLevelLg =>1, rampSstepSh=>1,rampOnTimeSh=>1,rampOffTimeSh=>1,dimMinLvlSh=>1, dimMaxLvlSh=>1,dimStepSh =>1, rampSstepLg=>1,rampOnTimeLg=>1,rampOffTimeLg=>1,dimMinLvlLg=>1, dimMaxLvlLg=>1,dimStepLg =>1, }, switch=> {OnTimeSh =>1,OnTimeLg =>1,OffTimeSh =>1,OffTimeLg =>1, OnDlySh =>1,OnDlyLg =>1,OffDlySh =>1,OffDlyLg =>1, }, thermostat=>{ DispTempHum =>1,DispTempInfo =>1,DispTempUnit =>1,MdTempReg =>1, MdTempValve =>1,TempComfort =>1,TempLower =>1,PartyEndDay =>1, PartyEndMin =>1,PartyEndHr =>1,TempParty =>1,DecalDay =>1, TempWinOpen =>1, DecalHr =>1,DecalMin =>1, BacklOnTime =>1,BacklOnMode =>1,BtnLock =>1, ValveOffset =>1,ValveError =>1, }, outputUnit=>{ OnDlySh =>1,OnTimeSh =>1,OffDlySh =>1,OffTimeSh =>1, OnDlyLg =>1,OnTimeLg =>1,OffDlyLg =>1,OffTimeLg =>1, ActTypeSh =>1,ActNumSh =>1,IntenseSh =>1, ActTypeLg =>1,ActNumLg =>1,IntenseLg =>1, }, winMatic=>{ OnTimeSh =>1,OffTimeSh =>1,OffLevelKmSh =>1, OnLevelKmSh =>1,OnRampOnSpSh =>1,OnRampOffSpSh =>1, OnTimeLg =>1,OffTimeLg =>1,OffLevelKmLg =>1, OnLevelKmLg =>1,OnRampOnSpLg =>1,OnRampOffSpLg =>1, }, keyMatic=>{ signal =>1,signalTone=>1,keypressSignal=>1, holdTime =>1,setupDir =>1,setupPosition =>1, angelOpen =>1,angelMax =>1,angelLocked =>1, ledFlashUnlocked=>1,ledFlashLocked=>1, }, dis4=> {language => 1,stbyTime => 1, #todo insert correct name }, motionDetector=>{ evtFltrPeriod =>1,evtFltrNum =>1,minInterval =>1, captInInterval=>1,brightFilter =>1,ledOnTime =>1, }, ); ##--------------- Conversion routines for register settings my %fltCvT = (0.1=>3.1,1=>31,5=>155,10=>310,60=>1860,300=>9300, 600=>18600,3600=>111600); sub CUL_HM_fltCvT($) # float -> config time { my ($inValue) = @_; my $exp = 0; my $div2; foreach my $div(sort{$a <=> $b} keys %fltCvT){ $div2 = $div; last if ($inValue < $fltCvT{$div}); $exp++; } return ($exp << 5)+int($inValue/$div2); } sub CUL_HM_CvTflt($) # config time -> float { my ($inValue) = @_; return ($inValue & 0x1f)*((sort {$a <=> $b} keys(%fltCvT))[$inValue >> 5]); } #define gets - try use same names as for set my %culHmGlobalGets = ( param => "", reg => " ... ", regList => "", ); my %culHmSubTypeGets = ( none4Type => { "test"=>"" }, ); my %culHmModelGets = ( none4Mod=> { "none" => "", }, ); ################################### sub CUL_HM_Get($@) { my ($hash, @a) = @_; return "no get value specified" if(@a < 2); my $name = $hash->{NAME}; my $devName = $attr{$name}{device};# get devName as protocol entity $devName = $name if (!$devName); # we control ourself if no chief available my $st = AttrVal($devName, "subType", ""); my $md = AttrVal($devName, "model", ""); my $mId = CUL_HM_getMId($hash); my $rxType = CUL_HM_getRxType($hash); my $class = AttrVal($devName, "hmClass", "");#relevant is the chief my $cmd = $a[1]; my $dst = $hash->{DEF}; my $isChannel = (length($dst) == 8)?"true":""; my $chn = ($isChannel)?substr($dst,6,2):"01"; $dst = substr($dst,0,6); my $devHash = CUL_HM_getDeviceHash($hash); my $h = $culHmGlobalGets{$cmd}; $h = $culHmSubTypeGets{$st}{$cmd} if(!defined($h) && $culHmSubTypeGets{$st}); $h = $culHmModelGets{$md}{$cmd} if(!defined($h) && $culHmModelGets{$md}); my @h; @h = split(" ", $h) if($h); if(!defined($h)) { my @arr = keys %culHmGlobalGets; push @arr, keys %{$culHmSubTypeGets{$st}} if($culHmSubTypeGets{$st}); push @arr, keys %{$culHmModelGets{$md}} if($culHmModelGets{$md}); my $usg = "Unknown argument $cmd, choose one of ".join(" ",sort @arr); return $usg; }elsif($h eq "" && @a != 2) { return "$cmd requires no parameters"; } elsif($h !~ m/\.\.\./ && @h != @a-2) { return "$cmd requires parameter: $h"; } my $id = CUL_HM_Id($hash->{IODev}); #----------- now start processing -------------- if($cmd eq "param") { ###################################################### my $val; $val = AttrVal($name, $a[2], ""); $val = $hash->{READINGS}{$a[2]}{VAL} if (!$val && $hash->{READINGS}{$a[2]}); $val = AttrVal($devName, $a[2], "") if (!$val); $val = $devHash->{READINGS}{$a[2]}{VAL} if (!$val && $devHash->{READINGS}{$a[2]}); $val = $hash->{$a[2]} if (!$val && $hash->{$a[2]}); $val = $devHash->{$a[2]} if (!$val && $devHash->{$a[2]}); $val = $hash->{helper}{$a[2]} if((!$val)&& (ref($hash->{helper}{$a[2]}) ne "HASH")); $val = $devHash->{helper}{$a[2]} if (!$val); return (defined ($val))?$val:"undefined"; } elsif($cmd eq "reg") { ##################################################### my (undef,undef,$regReq,$list,$peerId) = @a; if ($regReq eq 'all'){ my @regArr = keys %culHmRegGeneral; push @regArr, keys %{$culHmRegSupported{$st}} if($culHmRegSupported{$st}); my @peers; # get all peers we have a reglist my @listWp; # list that require peers foreach my $readEntry (keys %{$hash->{READINGS}}){ my $regs = ReadingsVal($hash->{NAME},$readEntry,""); if ($readEntry =~m /^RegL_/){ #this is a reg Reading "RegL_:peerN my $peer = substr($readEntry,8); my $listP = substr($readEntry,6,1); push(@peers,$peer) if ($peer); push(@listWp,$listP) if ($peer); } } my @regValList; #storage of results foreach my $regName (@regArr){ my $regL = $culHmRegDefine{$regName}->{l}; my @peerExe = (grep (/$regL/,@listWp))?@peers:("00000000"); foreach my $peer(@peerExe){ next if($peer eq ""); my $regVal = CUL_HM_getRegFromStore($name,$regName,0,$peer); #determine peerID push @regValList,"List:".$regL. " Peer:".$peer. "\t".$regName. ":\tvalue:". $regVal."\n" if ($regVal ne 'unknown') ; } } return $name." type:".$st." - \n".join("",sort(@regValList)); } else{ my $regVal = CUL_HM_getRegFromStore($name,$regReq,$list,$peerId); return ($regVal eq "invalid")? "Value not captured" : "0x".sprintf("%X",$regVal)." dec:".$regVal; } } elsif($cmd eq "regList") { ################################################# my @arr = keys %culHmRegGeneral ; push @arr, keys %{$culHmRegSupported{$st}} if($culHmRegSupported{$st}); my $info = $st." - \n"; foreach my $regName (@arr){ my $reg = $culHmRegDefine{$regName}; $info .= $regName."\trange:". $reg->{min}." to ".$reg->{max}.$reg->{u}. ((($reg->{l} == 3)||($reg->{l} == 4))?"\tpeer required":"") ."\t: ".$reg->{t}."\n"; } return $info; } Log GetLogLevel($name,4), "CUL_HM get $name " . join(" ", @a[1..$#a]); CUL_HM_ProcessCmdStack($devHash) if ($rxType & 0x03);#burst/all return ""; } ################################### my %culHmGlobalSets = ( raw => "data ...", reset => "", pair => "", unpair => "", sign => "[on|off]", regRaw =>"[List0|List1|List2|List3|List4|List5|List6] ... ", statusRequest => "", getpair => "", getdevicepair => "", getRegRaw =>"[List0|List1|List2|List3|List4|List5|List6] ... ", getConfig => "", regSet =>" ... ", virtual =>"", actiondetect =>"", ); my %culHmSubTypeSets = ( switch => { "on-for-timer"=>"sec", "on-till"=>"time", on=>"", off=>"", toggle=>"" }, dimmer => { "on-for-timer"=>"sec", , "on-till"=>"time", on=>"", off=>"", toggle=>"", pct=>"", stop=>""}, blindActuator=> { on=>"", off=>"", toggle=>"", pct=>"", stop=>""}, remote => { devicepair => " device ... [single|dual] [set|unset] [actor|remote|both]",}, pushButton => { devicepair => " device ... [single|dual] [set|unset] [actor|remote|both]",}, virtual => { raw => "data ...", devicepair => " device ... [single|dual] [set|unset] [actor|remote|both]", press => "[long|short]...", virtual =>"",}, #redef necessary for virtual smokeDetector => { test => "", "alarmOn"=>"", "alarmOff"=>"", }, winMatic =>{matic => "", read => "", keydef => " ", create => "" }, keyMatic =>{lock =>"", unlock =>"[sec] ...", open =>"[sec] ...", inhibit=>"[on|off]", }, ); my %culHmModelSets = ( "HM-CC-TC"=>{ devicepair => " device ... [single|dual] [set|unset] [actor|remote|both]", "day-temp" => "temp", "night-temp" => "temp", "party-temp" => "temp", "desired-temp" => "temp", # does not work - only in manual mode?? tempListSat => "HH:MM temp ...", tempListSun => "HH:MM temp ...", tempListMon => "HH:MM temp ...", tempListTue => "HH:MM temp ...", tempListThu => "HH:MM temp ...", tempListWed => "HH:MM temp ...", tempListFri => "HH:MM temp ...", displayMode => "[temp-only|temp-hum]", displayTemp => "[actual|setpoint]", displayTempUnit => "[celsius|fahrenheit]", controlMode => "[manual|auto|central|party]", decalcDay => "day", }, "HM-CC-VD"=>{ valvePos => "position",}, "HM-RC-19"=> { service => "", alarm => "", display => " [comma,no] [unit] [off|1|2|3] [off|on|slow|fast] ",}, "HM-RC-19-B"=> { service => "", alarm => "", display => " [comma,no] [unit] [off|1|2|3] [off|on|slow|fast] ",}, "HM-RC-19-SW"=> { service => "", alarm => "", display => " [comma,no] [unit] [off|1|2|3] [off|on|slow|fast] ",}, "HM-PB-4DIS-WM"=>{ text => " [on|off] ",}, "HM-OU-LED16" =>{ led =>"[off|red|green|orange]" , ilum =>"[0-15] [0-127]" }, "HM-OU-CFM-PL"=>{ led => "[,..]", playTone => "[,..]",}, ); ############################################## sub CUL_HM_getMId($) {#in: hash(chn or dev) out:model key (key for %culHmModel). # Will store result in device helper my ($hash) = @_; $hash = CUL_HM_getDeviceHash($hash); my $mId = $hash->{helper}{mId}; if (!$mId){ my $model = AttrVal($hash->{NAME}, "model", ""); foreach my $mIdKey(keys%culHmModel){ if ($culHmModel{$mIdKey}{name} && $culHmModel{$mIdKey}{name} eq $model){ $mId = $hash->{helper}{mId} = $mIdKey; return $mIdKey; } } } return $mId; } ############################################## sub CUL_HM_getRxType($) { #in:hash(chn or dev) out:binary coded Rx type # Will store result in device helper my ($hash) = @_; $hash = CUL_HM_getDeviceHash($hash); no warnings; #convert regardless of content my $rxtEntity = int($hash->{helper}{rxType}); use warnings; if (!$rxtEntity){ #at least one bit must be set my $MId = CUL_HM_getMId($hash); my $rxtOfModel = $culHmModel{$MId}{rxt} if ($MId && $culHmModel{$MId}{rxt}); if ($rxtOfModel){ $rxtEntity |= ($rxtOfModel =~ m/b/)?0x02:0;#burst $rxtEntity |= ($rxtOfModel =~ m/c/)?0x04:0;#config $rxtEntity |= ($rxtOfModel =~ m/w/)?0x08:0;#wakeup } $rxtEntity = 1 if (!$rxtEntity);#always $hash->{helper}{rxType} = $rxtEntity; } return $rxtEntity; } ############################################## sub CUL_HM_getFlag($) {#msgFlag set to 'A0' for normal and 'B0' for burst devices # currently not supported is the wakeupflag since it is hardly used my ($hash) = @_; return (CUL_HM_getRxType($hash) & 0x02)?"B0":"A0"; #set burst flag } sub CUL_HM_Set($@) { my ($hash, @a) = @_; my ($ret, $tval, $rval); #added rval for ramptime by unimatrix return "no set value specified" if(@a < 2); my $name = $hash->{NAME}; my $devName = AttrVal($name, "device" , $name);# devName as protocol entity my $st = AttrVal($devName, "subType", ""); my $md = AttrVal($devName, "model" , ""); my $class = AttrVal($devName, "hmClass", "");#relevant is the device my $rxType = CUL_HM_getRxType($hash); my $flag = CUL_HM_getFlag($hash); #set burst flag my $cmd = $a[1]; my $dst = $hash->{DEF}; my $isChannel = (length($dst) == 8)?"true":""; my $chn = ($isChannel)?substr($dst,6,2):"01"; $dst = substr($dst,0,6); my $devHash = CUL_HM_getDeviceHash($hash); my $h = $culHmGlobalSets{$cmd} if($st ne "virtual"); $h = $culHmSubTypeSets{$st}{$cmd} if(!defined($h) && $culHmSubTypeSets{$st}); $h = $culHmModelSets{$md}{$cmd} if(!defined($h) && $culHmModelSets{$md}); my @h; @h = split(" ", $h) if($h); if(!defined($h) && defined($culHmSubTypeSets{$st}{pct}) && $cmd =~ m/^\d+/) { $cmd = "pct"; } elsif(!defined($h)) { my @arr; @arr = keys %culHmGlobalSets if($st ne "virtual"); push @arr, keys %{$culHmSubTypeSets{$st}} if($culHmSubTypeSets{$st}); push @arr, keys %{$culHmModelSets{$md}} if($culHmModelSets{$md}); my $usg = "Unknown argument $cmd, choose one of ".join(" ",sort @arr); if($usg =~ m/ pct/) { $usg =~ s/ pct/ pct:slider,0,1,100/; } elsif($md eq "HM-CC-TC") { my @list = map { ($_.".0", $_+0.5) } (6..30); pop @list; my $list = "on,off," . join(",",@list); $usg =~ s/-temp/-temp:$list/g; } return $usg; } elsif($cmd eq "pct") { splice @a, 1, 1; } elsif($h eq "" && @a != 2) { return "$cmd requires no parameters"; } elsif($h !~ m/\.\.\./ && @h != @a-2) { return "$cmd requires parameter: $h"; } my $id = CUL_HM_Id($hash->{IODev}); my $state = "set_".join(" ", @a[1..(int(@a)-1)]); if($cmd eq "raw") { ################################################## return "Usage: set $a[0] $cmd data [data ...]" if(@a < 3); $state = ""; for (my $i = 2; $i < @a; $i++) { CUL_HM_PushCmdStack($hash, $a[$i]); } } elsif($cmd eq "reset") { ############################################ CUL_HM_PushCmdStack($hash,"++".$flag."11".$id.$dst."0400"); } elsif($cmd eq "pair") { ############################################# return "pair is not enabled for this type of device, ". "use set hmPairForSec" if($class eq "sender"); $state = ""; my $serialNr = AttrVal($name, "serialNr", undef); return "serialNr is not set" if(!$serialNr); CUL_HM_PushCmdStack($hash,"++A401".$id."000000010A".unpack("H*",$serialNr)); $hash->{hmPairSerial} = $serialNr; } elsif($cmd eq "unpair") { ########################################### CUL_HM_pushConfig($hash, $id, $dst, 0,0,0,0, "02010A000B000C00"); $state = ""; } elsif($cmd eq "sign") { ############################################ CUL_HM_pushConfig($hash, $id, $dst, $chn,0,0,$chn, "08" . ($a[2] eq "on" ? "01":"02")); $state = ""; } elsif($cmd eq "statusRequest") { ############################################ my @chnIdList = CUL_HM_getAssChnId($name); foreach my $channel (@chnIdList){ my $chnNo = substr($channel,6,2); CUL_HM_PushCmdStack($hash,"++".$flag.'01'.$id.$dst.$chnNo.'0E'); } $state = ""; } elsif($cmd eq "getpair") { ################################################## CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.'00040000000000'); $state = ""; } elsif($cmd eq "getdevicepair") { ############################################ CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.$chn.'03'); $state = ""; } elsif($cmd eq "getConfig") { ################################################ my $chFound = 0; CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.'00040000000000') if (!$isChannel); my @chnIdList = CUL_HM_getAssChnId($name); foreach my $channel (@chnIdList){ my $chnHash = CUL_HM_id2hash($channel); CUL_HM_getConfig($hash,$chnHash,$id,$dst,substr($channel,6,2)); } $state = ""; } elsif($cmd eq "regRaw" ||$cmd eq "getRegRaw") { ############################# my ($list,$addr,$data,$peerID); $state = ""; ($list,$addr,$data,$peerID) = ($a[2],hex($a[3]),hex($a[4]),$a[5]) if ($cmd eq "regRaw"); ($list,$peerID) = ($a[2],$a[3])if ($cmd eq "getRegRaw"); $list =~ s/List/0/;# convert Listy to 0y # as of now only hex value allowed check range and convert $chn = "00" if ($list eq "00"); my $pSc = substr($peerID,0,4); #helper for shortcut spread if ($pSc eq 'self'){$peerID=$dst.sprintf("%02X",'0'.substr($peerID,4)); }elsif ($pSc eq 'fhem'){$peerID=$id .sprintf("%02X",'0'.substr($peerID,4)); }elsif($peerID eq 'all'){;# keep all }else {$peerID = CUL_HM_Name2Id($peerID); } $peerID = $peerID.((length($peerID) == 6)?"01":"");# default chn 1, if none $peerID = "00000000" if (length($peerID) != 8 && $peerID ne 'all');# none? my $peerChn = substr($peerID,6,2);# have to split chan and id $peerID = substr($peerID,0,6); if($cmd eq "getRegRaw"){ if ($list eq "00"){ CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.'00040000000000'); } else{# other lists are per channel my @chnIdList = CUL_HM_getAssChnId($name); foreach my $channel (@chnIdList){ my $chnNo = substr($channel,6,2); if ($list =~m /0[34]/){#getPeers to see if list3 is available CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.$chnNo.'03'); my $chnHash = CUL_HM_id2hash($channel); $chnHash->{helper}{getCfgList} = $peerID.$peerChn;#list3 regs $chnHash->{helper}{getCfgListNo} = int($list); } else{ CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.$chnNo.'04' .$peerID.$peerChn.$list); } } } } else{ # as of now only hex value allowed check range and convert return "invalid address or data" if ($addr > 255 || $data > 255); my $addrData = uc(sprintf("%02x%02x",$addr,$data)); CUL_HM_pushConfig($hash,$id,$dst,$chn,$peerID,$peerChn,$list,$addrData); } } elsif($cmd eq "regSet") { ################################################### #set regSet my ($regName,$data,$peerChnIn) = ($a[2],$a[3],$a[4]); $state = ""; if (!$culHmRegSupported{$st}{$regName} && !$culHmRegGeneral{$regName} ){ my @arr = keys %culHmRegGeneral ; push @arr, keys %{$culHmRegSupported{$st}} if($culHmRegSupported{$st}); return "supported register are ".join(" ",sort @arr); } my $reg = $culHmRegDefine{$regName}; return $st." - ".$regName # give some help ." range:". $reg->{min}." to ".$reg->{max}.$reg->{u} .(($reg->{l} == 3)?" peer required":"")." : ".$reg->{t}."\n" if ($data eq "?"); return "value:".$data." out of range for Reg \"".$regName."\"" if ($data < $reg->{min} ||$data > $reg->{max}); my $conversion = $reg->{c}; if (!$conversion){;# do nothing }elsif($conversion eq "factor"){$data *= $reg->{f};# use factor }elsif($conversion eq "fltCvT"){$data = CUL_HM_fltCvT($data); }elsif($conversion eq "m10s3") {$data = $data*10-3; }else{return " conversion undefined - please contact admin"; } my $addr = int($reg->{a}); # bit location later my $list = $reg->{l}; my $bit = ($reg->{a}*10)%10; # get fraction my $dLen = $reg->{s}; # datalength in bit $dLen = int($dLen)*8+(($dLen*10)%10); # only allow it level if length less then one byte!! return "partial Word error: ".$dLen if($dLen != 8*int($dLen/8) && $dLen>7); no warnings qw(overflow portable); my $mask = (0xffffffff>>(32-$dLen)); use warnings qw(overflow portable); my $dataStr = substr(sprintf("%08X",($data & $mask) << $bit), 8-int($reg->{s}+0.99)*2,); my ($lChn,$peerID,$peerChn) = ($chn,"000000","00"); if (($list == 3) ||($list == 4)){ # peer is necessary for list 3/4 return "Peer not specified" if (!$peerChnIn); my $pSc = substr($peerID,0,4); #helper for shortcut spread if ($pSc eq 'self'){$peerID=$dst.sprintf("%02X",'0'.substr($peerID,4)); }elsif ($pSc eq 'fhem'){$peerID=$id .sprintf("%02X",'0'.substr($peerID,4)); }else {$peerID = CUL_HM_Name2Id($peerID); } $peerChn = ((length($peerID) == 8)?substr($peerID,6,2):"01"); $peerID = substr($peerID,0,6); return "Peer not specified" if (!$peerID); } elsif($list == 0){ $lChn = "00"; } else{ #if($list == 1/5/6){ $lChn = "01" if ($chn eq "00"); #by default select chan 01 for device } my $addrData; if ($dLen < 8){# fractional byte see whether we have stored the register #read full 8 bit!!! my $curVal = CUL_HM_getRegFromStore(CUL_HM_id2Name($dst.$lChn), $addr,$list,$peerID.$peerChn); return "cannot read current value for Bitfield - retrieve Data first" if (!$curVal); $data = ($curVal & (~($mask<<$bit)))|($data<<$bit); $addrData.=sprintf("%02X%02X",$addr,$data); } else{ for (my $cnt = 0;$cnt{s}+0.99);$cnt++){ $addrData.=sprintf("%02X",$addr+$cnt).substr($dataStr,$cnt*2,2); } } CUL_HM_pushConfig($hash,$id,$dst,$lChn,$peerID,$peerChn,$list,$addrData); } elsif($cmd eq "on") { ############################################### CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'02'.$chn.'C80000'); } elsif($cmd eq "off") { ############################################## CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'02'.$chn.'000000'); } elsif($cmd eq "on-for-timer"||$cmd eq "on-till") { ########################## my (undef,undef,$duration,$edate) = @a; #date prepared extention to entdate if ($cmd eq "on-till"){ # to be extended to handle end date as well my ($eH,$eM,$eSec) = split(':',$duration); $eSec += $eH*3600 + $eM*60; my @lt = localtime; my $ltSec = $lt[2]*3600+$lt[1]*60+$lt[0];# actually strip of date $eSec += 3600*24 if ($ltSec > $eSec); # go for the next day $duration = $eSec - $ltSec; } return "please enter the duration in seconds" if (!defined ($duration)); $tval = CUL_HM_encodeTime16($duration);# onTime 0.0..85825945.6, 0=forever CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'02'.$chn.'C80000'.$tval); } elsif($cmd eq "toggle") { ################################################### $hash->{toggleIndex} = 1 if(!$hash->{toggleIndex}); $hash->{toggleIndex} = (($hash->{toggleIndex}+1) % 128); CUL_HM_PushCmdStack($hash, sprintf("++%s3E%s%s%s40%s%02X",$flag,$id, $dst, $dst, $chn, $hash->{toggleIndex})); } elsif($cmd eq "lock") { ################################################### CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'800100FF'); # LEVEL_SET } elsif($cmd eq "unlock") { ################################################### $tval = (@a > 2) ? int($a[2]) : 0; my $delay = ($tval > 0) ? CUL_HM_encodeTime8($tval) : "FF"; # RELOCK_DELAY (FF=never) CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'800101'.$delay);# LEVEL_SET } elsif($cmd eq "open") { ################################################### $tval = (@a > 2) ? int($a[2]) : 0; my $delay = ($tval > 0) ? CUL_HM_encodeTime8($tval) : "FF"; # RELOCK_DELAY (FF=never) CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'8001C8'.$delay);# OPEN $state = ""; } elsif($cmd eq "inhibit") { ############################################### return "$a[2] is not on or off" if($a[2] !~ m/^(on|off)$/); my $val = ($a[2] eq "on") ? "01" : "00"; CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.$val.'01'); # SET_LOCK $state = ""; } elsif($cmd eq "pct") { ###################################################### $a[1] = 100 if ($a[1] > 100); $tval = CUL_HM_encodeTime16(((@a > 2)&&$a[2]!=0)?$a[2]:85825945);# onTime 0.0..85825945.6, 0=forever $rval = CUL_HM_encodeTime16((@a > 3)?$a[3]:2.5); # rampTime 0.0..85825945.6, 0=immediate CUL_HM_PushCmdStack($hash, sprintf("++%s11%s%s02%s%02X%s%s",$flag,$id,$dst,$chn,$a[1]*2,$rval,$tval)); } elsif($cmd eq "stop") { ##################################### CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'03'.$chn); } elsif($cmd eq "text") { ############################################# $state = ""; return "$a[2] is not a button number" if($a[2] !~ m/^\d$/ || $a[2] < 1); return "$a[3] is not on or off" if($a[3] !~ m/^(on|off)$/); my $bn = $a[2]*2-($a[3] eq "on" ? 0 : 1); my ($l1, $l2, $s); # Create CONFIG_WRITE_INDEX string $l1 = $a[4] . "\x00"; $l1 = substr($l1, 0, 13); $s = 54; $l1 =~ s/(.)/sprintf("%02X%02X",$s++,ord($1))/ge; $l2 = $a[5] . "\x00"; $l2 = substr($l2, 0, 13); $s = 70; $l2 =~ s/(.)/sprintf("%02X%02X",$s++,ord($1))/ge; $l1 .= $l2; CUL_HM_pushConfig($hash, $id, $dst, $bn,0,0,1, $l1); } elsif($cmd eq "display") { ################################################## my (undef,undef,undef,$t,$c,$u,$snd,$blk,$symb) = @_; return "cmd only possible for device or its display channel" if (length($hash->{DEF}) ne 6 && $chn ne 18); my %symbol=(off => 0x0000, bulb =>0x0100,switch =>0x0200,window =>0x0400,door=>0x0800, blind=>0x1000,scene =>0x2000,phone =>0x4000,bell=>0x8000, clock=>0x0001,arrowUp=>0x0002,arrowDown=>0x0004); my %light=(off=>0,on=>1,slow=>2,fast=>3); my %unit=(off =>0,Proz=>1,Watt=>2,x3=>3,C=>4,x5=>5,x6=>6,x7=>7, F=>8,x9=>9,x10=>10,x11=>11,x12=>12,x13=>13,x14=>14,x15=>15); my @symbList = split(',',$symb); my $symbAdd = ""; foreach my $symb (@symbList){ if (!defined($symbol{$symb})){# wrong parameter return "'$symb ' unknown. Select one of ".join(" ",sort keys(%symbol)); } $symbAdd |= $symbol{$symb}; } return "$c not specified. Select one of [comma|no]" if ($c ne "comma" && $c ne "no"); return "'$u' unknown. Select one of ".join(" ",sort keys(%unit)) if (!defined($unit{$u})); return "'$snd' unknown. Select one of [off|1|2|3]" if ($snd ne "off" && $snd > 3); return "'$blk' unknown. Select one of ".join(" ",sort keys(%light)) if (!defined($light{$blk})); my $beepBack = $snd | $light{$blk}*4; $symbAdd |= 0x0004 if ($c eq "comma"); $symbAdd |= $unit{$u}; my $text = sprintf("%5.5s",$t);#pad left with space $text = uc(unpack("H*",$text)); CUL_HM_PushCmdStack($hash,sprintf("++%s11%s%s8012%s%04X%02X", $flag,$id,$dst,$text,$symbAdd,$beepBack)); } elsif($cmd eq "alarm"||$cmd eq "service") { ################################# return "$a[2] must be below 255" if ($a[2] >255 ); $chn = 18 if ($chn eq "01"); my $subtype = ($cmd eq "alarm")?"81":"82"; CUL_HM_PushCmdStack($hash, sprintf("++%s11%s%s%s%s%02X",$flag,$id,$dst,$subtype,$chn, $a[2])); } elsif($cmd eq "led") { ###################################################### if ($md eq "HM-OU-LED16"){ my %color=(off=>0,red=>1,green=>2,orange=>3); if (length($hash->{DEF}) == 6){# command called for a device, not a channel my $col4all; if (defined($color{$a[2]})){ $col4all = sprintf("%02X",$color{$a[2]}*85);#Color for 4 LEDS $col4all = $col4all.$col4all.$col4all.$col4all;#and now for 16 } elsif ($a[2] =~ m/^[A-Fa-f0-9]{1,8}$/i){ $col4all = sprintf("%08X",hex($a[2])); } else{ return "$a[2] unknown. use hex or: ".join(" ",sort keys(%color)); } CUL_HM_PushCmdStack($hash,sprintf("++%s11%s%s8100%s", $flag,$id,$dst,$col4all)); }else{# operating on a channel return "$a[2] unknown. use: ".join(" ",sort keys(%color)) if (!defined($color{$a[2]}) ); CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'80'.$chn.'0'.$color{$a[2]}); } } elsif($md eq "HM-OU-CFM-PL"){ return "use channel 1 of the device for LED" if ($chn != 1); my %color = (redL =>18,greenL =>34,orangeL =>50, redS =>17,greenS =>33,orangeS =>49); my @ledList = split(',',$a[2]); my $ledBytes; foreach my $led (@ledList){ if (!$color{$led} ){# wrong parameter return "'$led' unknown. use: ".join(" ",sort keys(%color)); } $ledBytes .= sprintf("%02X",$color{$led}); } CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'80'.$chn.'0101'.$ledBytes); } else{ return "device for command cannot be identified"; } } elsif($cmd eq "playTone") { ################################################# $chn = "02" if (length($hash->{DEF}) == 6);# be nice, select implicite return "use channel 2 of the device to play MP3" if ($chn != 2); my @mp3List = split(',',$a[2]); my $mp3Bytes; foreach my $mp3 (@mp3List){ $mp3Bytes .= sprintf("%02X",$mp3); } CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'80'.$chn.'0202'.$mp3Bytes); } elsif($cmd eq "ilum") { ##################################################### return "$a[2] not specified. choose 0-15 for brightness" if ($a[2]>15); return "$a[3] not specified. choose 0-127 for duration" if ($a[3]>127); return "unsupported for HMid:".$hash->{DEF}.", use HMId:".substr($hash->{DEF},0,6) if (length($hash->{DEF}) != 6); my $addrData = sprintf("04%02X08%02X",$a[2],$a[3]*2); # write list0, CUL_HM_pushConfig($hash,$id,$dst,0,0,0,0,$addrData); } elsif(($cmd eq "displayMode")||($cmd eq "displayTemp")|| ($cmd eq "controlMode")||($cmd eq "decalcDay") || ($cmd eq "displayTempUnit") ){ ######################################## my %regs = (displayTemp =>{actual=>0,setpoint=>2}, displayMode =>{"temp-only"=>0,"temp-hum"=>1}, displayTempUnit =>{celsius=>0,fahrenheit=>4}, controlMode =>{manual=>0,auto=>8,central=>16,party=>24}, decalcDay =>{Sat=>0 ,Sun=>32 ,Mon=>64,Tue=>96, Wed=>128,Thu=>160,Fri=>192}); return $a[2]."invalid for ".$cmd." select one of ". join (" ",sort keys %{$regs{$cmd}}) if(!defined($regs{$cmd}{$a[2]})); CUL_HM_setRd($hash,$cmd,$a[2],""); my $tcnf = 0; my $missingEntries; foreach my $entry (keys %regs){ if (!$hash->{READINGS}{$entry}){ $missingEntries .= $entry." "; } else{ $tcnf |= $regs{$entry}{$hash->{READINGS}{$entry}{VAL}}; } } return "please complete settings for ".$missingEntries if($missingEntries); CUL_HM_pushConfig($hash, $id, $dst, 2,0,0,5, "01".sprintf("%02X",$tcnf)); } elsif($cmd eq "desired-temp") { ################## my $temp = CUL_HM_convTemp($a[2]); return $temp if(length($temp) > 2); CUL_HM_PushCmdStack($hash, sprintf("++%s11%s%s0202%s",$flag,$id,$dst,$temp)); } elsif($cmd =~ m/^(day|night|party)-temp$/) { ################## my %tt = (day=>"03", night=>"04", party=>"06"); my $tt = $tt{$1}; my $temp = CUL_HM_convTemp($a[2]); return $temp if(length($temp) > 2); CUL_HM_pushConfig($hash, $id, $dst, 2,0,0,5, "$tt$temp"); # List 5 } elsif($cmd =~ m/^tempList(...)/) { ################################## my %day2off = ( "Sat"=>"5 0B", "Sun"=>"5 3B", "Mon"=>"5 6B", "Tue"=>"5 9B", "Wed"=>"5 CB", "Thu"=>"6 01", "Fri"=>"6 31"); my $wd = $1; my ($list,$addr) = split(" ", $day2off{$wd}); $addr = hex($addr); return "To few arguments" if(@a < 4); return "To many arguments, max is 24 pairs" if(@a > 50); return "Bad format, use HH:MM TEMP ..." if(@a % 2); return "Last time spec must be 24:00" if($a[@a-2] ne "24:00"); my $data = ""; my $msg = ""; for(my $idx = 2; $idx < @a; $idx += 2) { return "$a[$idx] is not in HH:MM format" if($a[$idx] !~ m/^([0-2]\d):([0-5]\d)/); my ($h, $m) = ($1, $2); my $temp = CUL_HM_convTemp($a[$idx+1]); return $temp if(length($temp) > 2); $data .= sprintf("%02X%02X%02X%s", $addr, $h*6+($m/10), $addr+1, $temp); $addr += 2; $hash->{TEMPLIST}{$wd}{($idx-2)/2}{HOUR} = $h; $hash->{TEMPLIST}{$wd}{($idx-2)/2}{MINUTE} = $m; $hash->{TEMPLIST}{$wd}{($idx-2)/2}{TEMP} = $a[$idx+1]; $msg .= sprintf(" %02d:%02d %.1f", $h, $m, $a[$idx+1]); } CUL_HM_pushConfig($hash, $id, $dst, 2,0,0,$list, $data); my $vn = "tempList$wd"; CUL_HM_setRd($hash,$vn,$msg,''); } elsif($cmd eq "valvePos") { ################## my $vp = ($a[2]+0.5)*2.56; my $d1 = 0; CUL_HM_PushCmdStack($hash,sprintf("++A258%s%s%02X%02X",$id,$dst,$d1,$vp)); } elsif($cmd eq "matic") { ##################################### # Trigger pre-programmed action in the winmatic. These actions must be # programmed via the original software. CUL_HM_PushCmdStack($hash, sprintf("++%s3E%s%s%s40%02X%s", $flag,$id, $dst, $id, $a[2], $chn)); } elsif($cmd eq "create") { ################################### CUL_HM_PushCmdStack($hash, sprintf("++%s01%s%s0101%s%02X%s",$flag,$id, $dst, $id, $a[2], $chn)); CUL_HM_PushCmdStack($hash, sprintf("++A001%s%s0104%s%02X%s", $id, $dst, $id, $a[2], $chn)); } elsif($cmd eq "read") { ################################### return "read is discontinued since duplicate.\n". "please use getRegRaw instead. Syntax getRegRaw List3 fhem \n". "or getConfig for a complete configuratin list"; } elsif($cmd eq "keydef") { ##################################### if ( $a[3] eq "tilt") {CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,"0B220D838B228D83");#JT_ON/OFF/RAMPON/RAMPOFF short and long } elsif ($a[3] eq "close") {CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,"0B550D838B558D83");#JT_ON/OFF/RAMPON/RAMPOFF short and long } elsif ($a[3] eq "closed") {CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,"0F008F00"); #offLevel (also thru register) } elsif ($a[3] eq "bolt") {CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,"0FFF8FFF"); #offLevel (also thru register) } elsif ($a[3] eq "speedclose"){CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,sprintf("23%02XA3%02X",$a[4]*2,$a[4]*2));#RAMPOFFspeed (also in reg) } elsif ($a[3] eq "speedtilt") {CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,sprintf("22%02XA2%02X",$a[4]*2,$a[4]*2));#RAMPOFFspeed (also in reg) } elsif ($a[3] eq "delete") {CUL_HM_PushCmdStack($hash,sprintf("++%s01%s%s0102%s%02X%s",$flag,$id, $dst, $id, $a[2], $chn));#unlearn key } else { return 'unknown argument '.$a[3]; } } elsif($cmd eq "test") { ##################################################### my $testnr = $hash->{TESTNR} ? ($hash->{TESTNR} +1) : 1; $hash->{TESTNR} = $testnr; CUL_HM_SendCmd($hash, sprintf("++9440%s%s00%02X",$dst,$dst,$testnr), 1, 0); } elsif($cmd =~ m/alarm(.*)/) { ############################################### CUL_HM_SendCmd($hash, sprintf("++9441%s%s01%s", $dst,$dst, $1 eq "On" ? "0BC8" : "0C01"), 1, 0); } elsif($cmd eq "virtual") { ################################################## $state = ""; my (undef,undef,$maxBtnNo) = @a; return "please give a number between 1 and 255" if ($maxBtnNo < 1 ||$maxBtnNo > 255);# arbitrary - 255 should be max return $name." already defines as ".$attr{$name}{subType} if ($attr{$name}{subType} && $attr{$name}{subType} ne "virtual"); $attr{$name}{subType} = "virtual"; $attr{$name}{hmClass} = "sender"; $attr{$name}{model} = "virtual_".$maxBtnNo; my $devId = $hash->{DEF}; for (my $btn=1;$btn <= $maxBtnNo;$btn++){ my $chnName = $name."_Btn".$btn; my $chnId = $devId.sprintf("%02X",$btn); DoTrigger("global", "UNDEFINED $chnName CUL_HM $chnId") if (!$modules{CUL_HM}{defptr}{$chnId}); } foreach my $channel (keys %{$attr{$name}}){# remove higher numbers my $chNo = $1 if($channel =~ m/^channel_(.*)/); CommandDelete(undef,$attr{$name}{$channel}) if (hex($chNo) > $maxBtnNo); } } elsif($cmd eq "actiondetect"){ $state = ""; my (undef,undef,$cyctime) = @a; return ($cyctime eq 'off')?CUL_HM_ActDel($dst):CUL_HM_ActAdd($dst,$cyctime); } elsif($cmd eq "press") { #################################################### my (undef,undef,$mode) = @a; my ($srcId,$srcChn) = ($1,$2) if ($hash->{DEF} =~ m/(......)(..)/); return "invalid channel:".$srcId.$srcChn if (!$srcChn); my $rcvId = "000000"; #may have to change my $btn = sprintf("%02X",$srcChn+(($mode && $mode eq "long")?64:0)); my $pressCnt = (!$hash->{helper}{count})?1:$hash->{helper}{count}+1; $pressCnt %= 256; my @peerList; foreach my $peer (sort(split(',',AttrVal($name,"peerList","")))) { $peer =~ s/ .*//; push (@peerList,substr(CUL_HM_Name2Id($peer),0,6)); } my $oldPeer; # only once to device, not channel! foreach my $peer (sort @peerList){ next if ($oldPeer eq $peer); my $peerHash = $modules{CUL_HM}{defptr}{$peer}; my $peerSt = AttrVal($peerHash->{NAME}, "subType", ""); my $peerFlag = ($peerSt ne "keyMatic") ? "A4" : "B4"; CUL_HM_PushCmdStack($hash, sprintf("++%s40%s%s%s%02X", $peerFlag,$srcId,$peer,$btn,$pressCnt)); $oldPeer = $peer; } CUL_HM_PushCmdStack($hash, sprintf("++%s40%s000000%s%02X", $flag,$srcId,$btn,$pressCnt))if (!@peerList); $hash->{helper}{count}=$pressCnt; } elsif($cmd eq "devicepair") { ############################################### #devicepair => " device ... [single|dual] [set|unset] [actor|remote|both]" my ($bNo,$peerN,$single,$set,$target) = ($a[2],$a[3],$a[4],$a[5],$a[6]); $state = ""; return "$bNo is not a button number" if(($bNo < 1) && !$chn); my $peerHash = $defs{$peerN} if ($peerN); return "$peerN not a CUL_HM device" if(!$peerHash ||$peerHash->{TYPE} ne "CUL_HM"); return "$single must be single or dual" if(defined($single) && (($single ne"single") &&($single ne"dual"))); return "$set must be set or unset" if(defined($set) && (($set ne"set") &&($set ne"unset"))); return "$target must be [actor|remote|both]" if(defined($target) && (($target ne"actor") && ($target ne"remote")&&($target ne"both"))); return "use climate chan to pair TC" if($md eq "HM-CC-TC" &&$chn ne "02"); $single = ($single eq "single")?1:"";#default to dual $set = ($set eq "unset")?0:1; my ($b1,$b2,$nrCh2Pair); $b1 = ($isChannel) ? hex($chn):sprintf("%02X",$bNo); $b1 = $b1*2 - 1 if(!$single && !$isChannel); if ($single){ $b2 = $b1; $nrCh2Pair = 1; } else{ $b2 = $b1 + 1; $nrCh2Pair = 2; } my $cmd = ($set)?"01":"02";# do we set or remove? my $peerDst = $peerHash->{DEF}; my $peerChn = "01"; if(length($peerDst) == 8) { # shadow switch device for multi-channel switch ($peerDst,$peerChn) = ($1,$2) if($peerDst =~ m/(......)(..)/); $peerHash = $modules{CUL_HM}{defptr}{$peerDst}; } # First the remote (one loop for on, one for off) if (!$target || $target eq "remote" || $target eq "both"){ for(my $i = 1; $i <= $nrCh2Pair; $i++) { my $b = ($i==1 ? $b1 : $b2); if ($st eq "virtual"){ my $btnName = CUL_HM_id2Name($dst.sprintf("%02X",$b)); return "button ".$b." not defined for virtual remote ".$name if (!defined $attr{$btnName}); my $peerlist = $attr{$btnName}{peerList}; $peerlist = "" if (!$peerlist); my $repl = CUL_HM_id2Name($peerDst.$peerChn).","; $peerlist =~ s/$repl//;#avoid duplicate $peerlist.= $repl if($set == 1); $attr{$btnName}{peerList} = $peerlist; delete $attr{$btnName}{peerList} if (!$peerlist); } else{ my $bStr = sprintf("%02X",$b); CUL_HM_PushCmdStack($hash, "++".$flag."01${id}${dst}${bStr}$cmd${peerDst}${peerChn}00"); CUL_HM_pushConfig($hash,$id, $dst,$b,$peerDst,hex($peerChn),4,"0100") if($md ne "HM-CC-TC"); } } } if (!$target || $target eq "actor" || $target eq "both"){ if (AttrVal( CUL_HM_id2Name($peerDst), "subType", "") eq "virtual"){ my $peerIDs = AttrVal($peerN,"peerIDs",""); my $pId = $dst.sprintf("%02X",$b1); $peerIDs .= $pId."," if($peerIDs !~ m/$pId,/); $attr{$peerN}{peerIDs} = $peerIDs; my $peerList = ""; foreach my$tmpId (split(",",$peerIDs)){ $peerList .= CUL_HM_id2Name($tmpId); } $attr{$peerN}{peerList} = $peerList; } else{ my $peerFlag = CUL_HM_getFlag($peerHash); CUL_HM_PushCmdStack($peerHash, sprintf("++%s01%s%s%s%s%s%02X%02X", $peerFlag,$id,$peerDst,$peerChn,$cmd,$dst,$b2,$b1 )); } } $devHash = $peerHash; # Exchange the hash, as the switch is always alive. $rxType = CUL_HM_getRxType($devHash); } $hash->{STATE} = $state if($state); Log GetLogLevel($name,3), "CUL_HM set $name " . join(" ", @a[1..$#a]); CUL_HM_ProcessCmdStack($devHash) if($rxType & 0x03);#all/burst return ""; } ################################### sub CUL_HM_infoUpdtDevData($$$){ my($name,$hash,$p) = @_; my($fw,$mId,$serNo,$stc,$devInfo) = ($1,$2,$3,$4,$5) if($p =~ m/(..)(.{4})(.{20})(.{2})(.*)/); my $model = $culHmModel{$mId}{name} ? $culHmModel{$mId}{name}:"unknown"; $attr{$name}{model} = $model; my $dp = $culHmDevProps{$stc}; $attr{$name}{subType} = $dp ? $dp->{st} : "unknown"; $attr{$name}{hmClass} = $dp ? $dp->{cl} : "unknown"; $attr{$name}{serialNr} = pack('H*',$serNo); $attr{$name}{firmware} = sprintf("%d.%d", hex(substr($p,0,1)),hex(substr($p,1,1))); $attr{$name}{devInfo} = $devInfo; delete $hash->{helper}{rxType}; CUL_HM_getRxType($hash); #will update rxType $mId = CUL_HM_getMId($hash);# set helper valiable and use result # autocreate undefined channels my @chanTypesList = split(',',$culHmModel{$mId}{chn}); foreach my $chantype (@chanTypesList){ my ($chnTpName,$chnStart,$chnEnd) = split(':',$chantype); my $chnNoTyp = 1; for (my $chnNoAbs = $chnStart; $chnNoAbs <= $chnEnd;$chnNoAbs++){ my $chnId = $hash->{DEF}.sprintf("%02X",$chnNoAbs); if (!$modules{CUL_HM}{defptr}{$chnId}){ my $chnName = $name."_".$chnTpName.(($chnStart == $chnEnd)? '':'_'.sprintf("%02d",$chnNoTyp)); DoTrigger("global", 'UNDEFINED '.$chnName.' CUL_HM '.$chnId); } $attr{CUL_HM_id2Name($chnId)}{model} = $model; $chnNoTyp++; } } if ($culHmModel{$mId}{cyc}){ CUL_HM_ActAdd($hash->{DEF},$culHmModel{$mId}{cyc}); } } ################################### sub CUL_HM_Pair(@) { my ($name, $hash,$cmd,$src,$dst,$p) = @_; my $iohash = $hash->{IODev}; my $id = CUL_HM_Id($iohash); my $serNo = $attr{$name}{serialNr}; Log GetLogLevel($name,3), "CUL_HM pair: $name $attr{$name}{subType}, model $attr{$name}{model} serialNr $serNo"; # Abort if we are not authorized if($dst eq "000000") { if(!$iohash->{hmPair} && (!$iohash->{hmPairSerial} || $iohash->{hmPairSerial} ne $serNo)) { Log GetLogLevel($name,3), $iohash->{NAME}. " pairing (hmPairForSec) not enabled"; return ""; } } elsif($dst ne $id) { return "" ; } elsif($cmd eq "0400") { # WDC7000 return "" ; } elsif($iohash->{hmPairSerial}) { delete($iohash->{hmPairSerial}); } my ($idstr, $s) = ($id, 0xA); $idstr =~ s/(..)/sprintf("%02X%s",$s++,$1)/ge; CUL_HM_pushConfig($hash, $id, $src,0,0,0,0, "0201$idstr"); CUL_HM_SendCmd($hash, shift @{$hash->{cmdStack}}, 1, 1); return ""; } ################################### sub CUL_HM_getConfig($$$$$){ my ($hash,$chnhash,$id,$dst,$chn) = @_; my $flag = CUL_HM_getFlag($hash); foreach my $readEntry (keys %{$chnhash->{READINGS}}){ if ($readEntry =~ m/^RegL_/){#remove old lists, no longer valid delete $chnhash->{READINGS}{$readEntry}; } } #get Peer-list in any case - it is part of the config CUL_HM_PushCmdStack($hash,sprintf("++%s01%s%s%s03",$flag,$id,$dst,$chn)); my $lstAr = $culHmModel{CUL_HM_getMId($hash)}{lst}; my @list = split(",",$lstAr); #get valid lists e.g."1, 5:2:3.p ,6:2" foreach my$listEntry (@list){# each list that is define for this channel my ($peerReq,$chnValid)= (0,0); my ($listNo,$chnLst1) = split(":",$listEntry); if (!$chnLst1){ $chnValid = 1; #if no entry channel is valid $peerReq = 1 if($listNo==3 ||$listNo==4); #default } else{ my @chnLst = split('\.',$chnLst1); foreach my $lchn (@chnLst){ $peerReq = 1 if ($lchn =~ m/p/); no warnings;# know that lchan may be followed by a 'p' causing a warning $chnValid = 1 if (int($lchn) == hex($chn)); use warnings; last if ($chnValid); } } #$listNo,$chnValid $peerReq if ($chnValid){# yes, we will go for a list if ($peerReq){# need to get the peers first # CUL_HM_PushCmdStack($hash,sprintf("++%s01%s%s%s03",$flag,$id,$dst,$chn)); $chnhash->{helper}{getCfgList} = "all";# peers first $chnhash->{helper}{getCfgListNo} = $listNo; } else{ CUL_HM_PushCmdStack($hash,sprintf("++%s01%s%s%s0400000000%02X",$flag,$id,$dst,$chn,$listNo)); } } } } ###################-------send related --------################ sub CUL_HM_SendCmd($$$$) { my ($hash, $cmd, $sleep, $waitforack) = @_; my $io = $hash->{IODev}; select(undef, undef, undef, 0.1) if($io->{TYPE} ne 'HMLAN'); $cmd =~ m/^(..)(.*)$/; my ($mn, $cmd2) = ($1, $2); if($mn eq "++") { $mn = $io->{HM_CMDNR} ? (($io->{HM_CMDNR} +1)&0xff) : 1; } elsif($cmd =~ m/^[+-]/){; #continue pure IOWrite($hash, "", $cmd); return; } else { $mn = hex($mn); } $io->{HM_CMDNR} = $mn; $cmd = sprintf("As%02X%02X%s", length($cmd2)/2+1, $mn, $cmd2); IOWrite($hash, "", $cmd); $cmd =~ m/As(..)(..)(..)(..)(......)(......)(.*)/; CUL_HM_DumpProtocol("SND", $io, ($1,$2,$3,$4,$5,$6,$7)); CUL_HM_responseSetup($hash,$cmd,$waitforack); } ################################### sub CUL_HM_responseSetup($$$) {#store all we need to handle the response #setup repeatTimer and cmdStackControll my ($hash,$cmd,$waitForAck) = @_; my ($msgId, $msgType,$dst,$p) = ($2,$4,$6,$7) if ($cmd =~ m/As(..)(..)(..)(..)(......)(......)(.*)/); my ($chn,$subType) = ($1,$2) if($p =~ m/^(..)(..)/); my $rTo = 2; #default rsponse timeout if ($msgType eq "01" && $subType){ if ($subType eq "03"){ #PeerList------------- #--- remember request params in device level $hash->{helper}{respWait}{Pending} = "PeerList"; $hash->{helper}{respWait}{forChn} = substr($p,0,2);#channel info we await # define timeout - holdup cmdStack until response complete or timeout InternalTimer(gettimeofday()+$rTo, "CUL_HM_respPendTout", "respPend:$dst", 0); #--- remove readings in channel my $chnhash = $modules{CUL_HM}{defptr}{"$dst$chn"}; $chnhash = $hash if (!$chnhash); $chnhash->{READINGS}{peerList}{VAL}="";#empty old list return; } elsif($subType eq "04"){ #RegisterRead------- my ($peerID, $list) = ($1,$2) if ($p =~ m/..04(........)(..)/); $peerID = ($peerID ne "00000000")?CUL_HM_id2Name($peerID):""; $peerID =~ s/ /_/g;#subs blanks #--- set messaging items $hash->{helper}{respWait}{Pending} = "RegisterRead"; $hash->{helper}{respWait}{forChn} = $chn; $hash->{helper}{respWait}{forList}= $list; $hash->{helper}{respWait}{forPeer}= $peerID;# this is the HMid + channel # define timeout - holdup cmdStack until response complete or timeout InternalTimer(gettimeofday()+$rTo,"CUL_HM_respPendTout","respPend:$dst", 0); #--- remove channel entries that will be replaced my $chnhash = $modules{CUL_HM}{defptr}{"$dst$chn"}; $chnhash = $hash if(!$chnhash); $peerID ="" if($list !~ m/^0[34]$/); #empty val since reading will be cumulative $chnhash->{READINGS}{"RegL_".$list.":".$peerID}{VAL}=""; delete ($chnhash->{READINGS}{"RegL_".$list.":".$peerID}{TIME}); return; } elsif($subType eq "0E"){ #StatusReq---------- #--- set messaging items $hash->{helper}{respWait}{Pending} = "StatusReq"; $hash->{helper}{respWait}{forChn} = $chn; # define timeout - holdup cmdStack until response complete or timeout InternalTimer(gettimeofday()+$rTo, "CUL_HM_respPendTout", "respPend:$dst", 0); return; } } if ($waitForAck){ my $iohash = $hash->{IODev}; #$hash->{helper}{respWait}{Pending}= "Ack"; $hash->{helper}{respWait}{cmd} = $cmd; $hash->{helper}{respWait}{msgId} = $msgId; #msgId we wait to ack $hash->{helper}{respWait}{reSent} = 1; my $off = 2; #$off += 0.15*int(@{$iohash->{QUEUE}}) if($iohash->{QUEUE}); InternalTimer(gettimeofday()+$off, "CUL_HM_Resend", $hash, 0); } } ################################### sub CUL_HM_eventP($$) { # handle protocol events #todo: add severity, counter, history and acknowledge my ($hash, $evntType) = @_; my $name = $hash->{NAME}; return if (!$name); if ($evntType eq "Rcv"){ $attr{$name}{"protLastRcv"} = TimeNow(); return; } $attr{$name}{"prot".$evntType."Cnt"} = 0 if (!$attr{$name}{"prot".$evntType."Cnt"}); $attr{$name}{"prot".$evntType."Cnt"}++; $attr{$name}{"prot".$evntType."Last"} = TimeNow(); if ($evntType eq "Nack" ||$evntType eq "ResndFail"){ my $delMsgSum; $attr{$name}{protCmdDel} = 0 if(!$attr{$name}{protCmdDel}); $attr{$name}{protCmdDel} += scalar @{$hash->{cmdStack}} if ($hash->{cmdStack}); } } ################################### sub CUL_HM_respPendRm($) { # delete all response related entries in messageing entity my ($hash) = @_; delete ($hash->{helper}{respWait}); RemoveInternalTimer($hash); # remove resend-timer RemoveInternalTimer("respPend:$hash->{DEF}");# remove responsePending timer CUL_HM_ProcessCmdStack($hash); # continue processing commands } ################################### sub CUL_HM_respPendTout($) { my ($HMid) = @_; $HMid =~ s/.*://; #remove timer identifier my $hash = $modules{CUL_HM}{defptr}{$HMid}; if ($hash){ CUL_HM_eventP($hash,"Tout") if ($hash->{helper}{respWait}{cmd}); CUL_HM_eventP($hash,"ToutResp") if ($hash->{helper}{respWait}{Pending}); CUL_HM_respPendRm($hash); DoTrigger($hash->{NAME}, "RESPONSE TIMEOUT"); } } ################################### sub CUL_HM_respPendToutProlong($) {#used when device sends part responses my ($hash) = @_; RemoveInternalTimer("respPend:$hash->{DEF}");# remove responsePending timer? InternalTimer(gettimeofday()+1, "CUL_HM_respPendTout", "respPend:$hash->{DEF}", 0); } ################################### sub CUL_HM_PushCmdStack($$) { my ($chnhash, $cmd) = @_; my @arr = (); my $hash = CUL_HM_getDeviceHash($chnhash); $hash->{cmdStack} = \@arr if(!$hash->{cmdStack}); push(@{$hash->{cmdStack}}, $cmd); my $entries = scalar @{$hash->{cmdStack}}; $attr{$hash->{NAME}}{protCmdPend} = $entries." CMDs pending"; } ################################### sub CUL_HM_ProcessCmdStack($) { my ($chnhash) = @_; my $hash = CUL_HM_getDeviceHash($chnhash); my $sent; if($hash->{cmdStack} && !$hash->{helper}{respWait}{Pending} &&!$hash->{helper}{respWait}{cmd}){ if(@{$hash->{cmdStack}}) { CUL_HM_SendCmd($hash, shift @{$hash->{cmdStack}}, 1, 1); $sent = 1; $attr{$hash->{NAME}}{protCmdPend} = scalar @{$hash->{cmdStack}} ." CMDs pending"; CUL_HM_eventP($hash,"Snd"); } if(!@{$hash->{cmdStack}}) { delete($hash->{cmdStack}); delete($attr{$hash->{NAME}}{protCmdPend}); } } return $sent; } ################################### sub CUL_HM_Resend($) {#resend a message if there is no answer my $hash = shift; my $name = $hash->{NAME}; return if(!$hash->{helper}{respWait}{reSent}); # Double timer? if($hash->{helper}{respWait}{reSent} >= 3) { CUL_HM_eventP($hash,"ResndFail"); delete($hash->{cmdStack}); delete($attr{$hash->{NAME}}{protCmdPend}); CUL_HM_respPendRm($hash); $hash->{STATE} = "MISSING ACK"; DoTrigger($name, "MISSING ACK"); } else { CUL_HM_eventP($hash,"Resnd"); IOWrite($hash, "", $hash->{helper}{respWait}{cmd}); $hash->{helper}{respWait}{reSent}++; Log GetLogLevel($name,4),"CUL_HM_Resend: ".$name. " nr ".$hash->{helper}{respWait}{reSent}; InternalTimer(gettimeofday()+1, "CUL_HM_Resend", $hash, 0); } } ###################-----------helper and shortcuts--------################ sub CUL_HM_getAssChnId($) { # will return the list of assotiated channel of a device # if it is a channel only return itself # if device and no channel my ($name) = @_; my @chnIdList; foreach my $channel (keys %{$attr{$name}}){ next if ($channel !~ m/^channel_/); my $chnHash = CUL_HM_name2hash($attr{$name}{$channel}); push @chnIdList,$chnHash->{DEF} if ($chnHash); } my $dId = CUL_HM_Name2Id($name); push @chnIdList,$dId."01" if (length($dId) == 6 && !$attr{$name}{channel_01}); push @chnIdList,$dId if (length($dId) == 8); return sort(@chnIdList); } ################################### sub CUL_HM_Id($) {#in ioHash out ioHMid my ($io) = @_; my $fhtid = defined($io->{FHTID}) ? $io->{FHTID} : "0000"; return AttrVal($io->{NAME}, "hmId", "F1$fhtid"); } ################################### sub CUL_HM_id2hash($) {# in: id, out:hash my ($id) = @_; return $modules{CUL_HM}{defptr}{$id} if ($modules{CUL_HM}{defptr}{$id}); return $modules{CUL_HM}{defptr}{substr($id,0,6)}; # could be chn 01 of dev } ################################### sub CUL_HM_name2hash($) {# in: name, out:hash my ($name) = @_; return $defs{$name}; } ################################### sub CUL_HM_Name2Id(@) { # in: name or HMid out: HMid, undef if no match my ($idName,$idHash) = @_; my $hash = $defs{$idName}; return $hash->{DEF} if ($hash); #idName is entity return "000000" if($idName eq "broadcast"); #broadcast return $defs{$1}.$2 if($idName =~ m/(.*)_chn:(.*)/); # chn:xx return $idName if($idName =~ m/^[A-F0-9]{6,8}$/i); #was already HMid return $idHash->{DEF}.sprintf("%02X",$1) if($idHash && $idName =~ m/self(.*)/); return; } ################################### sub CUL_HM_id2Name($) { # in: name or HMid out: name my ($p) = @_; return $p if($attr{$p}); # is already name my $devId= substr($p, 0, 6); return "broadcast" if($devId eq "000000"); my ($chn,$chnId); if (length($p) == 8){ $chn = substr($p, 6, 2);; $chnId = $p; } my $defPtr = $modules{CUL_HM}{defptr}; return $defPtr->{$chnId}{NAME} if($chnId && $defPtr->{$chnId}); return $defPtr->{$devId}{NAME} if($defPtr->{$devId}); return $devId. ($chn ? ("_chn:".$chn):""); } ################################### sub CUL_HM_getDeviceHash($) {#in: hash (chn or dev) out: hash of the device (used e.g. for send messages) my ($hash) = @_; return $hash if(!$hash->{DEF}); my $devHash = $modules{CUL_HM}{defptr}{substr($hash->{DEF},0,6)}; return ($devHash)?$devHash:$hash; } ############################# my %culHmBits = ( "00" => { txt => "DEVICE_INFO", params => { FIRMWARE => '00,2', TYPE => "02,4", SERIALNO => '06,20,$val=pack("H*",$val)', CLASS => "26,2", PEER_CHANNEL_A => "28,2", PEER_CHANNEL_B => "30,2", UNKNOWN => "32,2", }}, "01;p11=01" => { txt => "CONFIG_PEER_ADD", params => { CHANNEL => "00,2", PEER_ADDRESS => "04,6", PEER_CHANNEL_A => "10,2", PEER_CHANNEL_B => "12,2", }}, "01;p11=02" => { txt => "CONFIG_PEER_REMOVE", params => { CHANNEL => "00,2", PEER_ADDRESS => '04,6,$val=CUL_HM_id2Name($val)', PEER_CHANNEL_A => "10,2", PEER_CHANNEL_B => "12,2", } }, "01;p11=03" => { txt => "CONFIG_PEER_LIST_REQ", params => { CHANNEL => "0,2", },}, "01;p11=04" => { txt => "CONFIG_PARAM_REQ", params => { CHANNEL => "00,2", PEER_ADDRESS => "04,6", PEER_CHANNEL => "10,2", PARAM_LIST => "12,2", },}, "01;p11=05" => { txt => "CONFIG_START", params => { CHANNEL => "00,2", PEER_ADDRESS => "04,6", PEER_CHANNEL => "10,2", PARAM_LIST => "12,2", } }, "01;p11=06" => { txt => "CONFIG_END", params => { CHANNEL => "0,2", } }, "01;p11=08" => { txt => "CONFIG_WRITE_INDEX", params => { CHANNEL => "0,2", DATA => '4,,$val =~ s/(..)(..)/ $1:$2/g', } }, "01;p11=0A" => { txt => "PAIR_SERIAL", params => { SERIALNO => '04,,$val=pack("H*",$val)', } }, "01;p11=0E" => { txt => "CONFIG_STATUS_REQUEST", params => { CHANNEL => "0,2", } }, "02;p01=00" => { txt => "ACK"}, "02;p01=01" => { txt => "ACK_STATUS", params => { CHANNEL => "02,2", STATUS => "04,2", DOWN => '06,02,$val=(hex($val)&0x20)?1:0', UP => '06,02,$val=(hex($val)&0x10)?1:0', LOWBAT => '06,02,$val=(hex($val)&0x80)?1:0', RSSI => '08,02,$val=(-1)*(hex($val))', }}, "02;p01=02" => { txt => "ACK2"}, # smokeDetector pairing only? "02;p01=80" => { txt => "NACK"}, "02;p01=84" => { txt => "NACK_TARGET_INVALID"}, "02" => { txt => "ACK/NACK_UNKNOWN "}, "02" => { txt => "Request AES", params => { #todo check data DATA => "0," } }, "03" => { txt => "AES reply", params => { DATA => "0," } }, "10;p01=01" => { txt => "INFO_PEER_LIST", params => { PEER1 => '02,8,$val=CUL_HM_id2Name($val)', PEER2 => '10,8,$val=CUL_HM_id2Name($val)', PEER3 => '18,8,$val=CUL_HM_id2Name($val)', PEER4 => '26,8,$val=CUL_HM_id2Name($val)'},}, "10;p01=02" => { txt => "INFO_PARAM_RESPONSE_PAIRS", params => { DATA => "2,", },}, "10;p01=03" => { txt => "INFO_PARAM_RESPONSE_SEQ", params => { OFFSET => "2,2", DATA => "4,", },}, "10;p01=04" => { txt => "INFO_PARAMETER_CHANGE", params => { CHANNEL => "2,2", PEER => '4,8,$val=CUL_HM_id2Name($val)', PARAM_LIST => "12,2", DATA => '14,,$val =~ s/(..)(..)/ $1:$2/g', } }, "10;p01=06" => { txt => "INFO_ACTUATOR_STATUS", params => { CHANNEL => "2,2", STATUS => '4,2', UNKNOWN => "6,2", RSSI => '08,02,$val=(-1)*(hex($val))' } }, "11;p02=0400" => { txt => "RESET" }, "11;p01=02" => { txt => "SET" , params => { CHANNEL => "02,2", VALUE => "04,2", RAMPTIME => '06,4,$val=CUL_HM_decodeTime16($val)', DURATION => '10,4,$val=CUL_HM_decodeTime16($val)', } }, "11;p01=80" => { txt => "LED" , params => { CHANNEL => "02,2", COLOR => "04,2", } }, "11;p01=81" => { txt => "LEDall" , params => { Led1To16 => '04,8,$val= join(":",sprintf("%b",hex($val))=~ /(.{2})/g)', } }, "12" => { txt => "HAVE_DATA"}, "3E" => { txt => "SWITCH", params => { DST => "00,6", UNKNOWN => "06,2", CHANNEL => "08,2", COUNTER => "10,2", } }, "3F" => { txt => "TimeStamp", params => { UNKNOWN => "00,4", TIME => "04,2", } }, "40" => { txt => "REMOTE", params => { BUTTON => '00,2,$val=(hex($val)&0x3F)', LONG => '00,2,$val=(hex($val)&0x40)?1:0', LOWBAT => '00,2,$val=(hex($val)&0x80)?1:0', COUNTER => "02,2", } }, "58" => { txt => "ClimateEvent", params => { CMD => "00,2", ValvePos => '02,2,$val=(hex($val))', } }, "70" => { txt => "WeatherEvent", params => { TEMP => '00,4,$val=((hex($val)&0x3FFF)/10)*((hex($val)&0x4000)?-1:1)', HUM => '04,2,$val=(hex($val))', } }, ); # RC send BCAST to specific address. Is the meaning understood? my @culHmCmdFlags = ("WAKEUP", "WAKEMEUP", "BCAST", "Bit3", "BURST", "BIDI", "RPTED", "RPTEN"); sub CUL_HM_DumpProtocol($$@) { my ($prefix, $iohash, $len,$cnt,$msgFlags,$msgType,$src,$dst,$p) = @_; my $iname = $iohash->{NAME}; no warnings;# conv 2 number would cause a warning - which is ok my $hmProtocolEvents = int(AttrVal($iname, "hmProtocolEvents", 0)); use warnings; return if(!$hmProtocolEvents); my $p01 = substr($p,0,2); my $p02 = substr($p,0,4); my $p11 = (length($p) > 2 ? substr($p,2,2) : ""); # decode message flags for printing my $msgFlLong=""; my $msgFlagsHex = hex($msgFlags); for(my $i = 0; $i < @culHmCmdFlags; $i++) { $msgFlLong .= ",$culHmCmdFlags[$i]" if($msgFlagsHex & (1<<$i)); } my $ps; $ps = $culHmBits{"$msgType;p11=$p11"} if(!$ps); $ps = $culHmBits{"$msgType;p01=$p01"} if(!$ps); $ps = $culHmBits{"$msgType;p02=$p02"} if(!$ps); $ps = $culHmBits{"$msgType"} if(!$ps); my $txt = ""; if($ps) { $txt = $ps->{txt}; if($ps->{params}) { $ps = $ps->{params}; foreach my $k (sort {$ps->{$a} cmp $ps->{$b} } keys %{$ps}) { my ($o,$l,$expr) = split(",", $ps->{$k}, 3); last if(length($p) <= $o); my $val = $l ? substr($p,$o,$l) : substr($p,$o); eval $expr if($hmProtocolEvents > 1 && $expr); $txt .= " $k:".(($hmProtocolEvents > 1 && $expr)?"":"0x")."$val"; } } $txt = " ($txt)" if($txt); } $src=CUL_HM_id2Name($src); $dst=CUL_HM_id2Name($dst); my $msg ="$prefix L:$len N:$cnt F:$msgFlags CMD:$msgType SRC:$src DST:$dst $p$txt ($msgFlLong)"; Log GetLogLevel($iname, 4), $msg; DoTrigger($iname, $msg) if($hmProtocolEvents > 2); } ############################# sub CUL_HM_parseCommon(@){ # parsing commands that are device independant my ($msgId,$msgType,$src,$dst,$p) = @_; my $shash = $modules{CUL_HM}{defptr}{$src}; my $dhash = $modules{CUL_HM}{defptr}{$dst}; return "" if(!$shash->{DEF});# this should be from ourself my $pendType = $shash->{helper}{respWait}{Pending}? $shash->{helper}{respWait}{Pending}:""; if ($msgType eq "02"){# Ack/Nack ####################################### if ($shash->{helper}{respWait}{msgId} && $shash->{helper}{respWait}{msgId} eq $msgId ){ #ack we waited for - stop Waiting CUL_HM_respPendRm($shash); } #see if the channel is defined separate - otherwise go for chief my $subType = substr($p,0,2); my $chn = substr($p,2,2); #mark timing on the channel, not the device my $HMid = $chn?$src.$chn:$src; my $chnhash = $modules{CUL_HM}{defptr}{$HMid}; $chnhash = $shash if(!$chnhash); my $reply; my $success; if ($subType =~ m/^8/){ #NACK $success = "no"; CUL_HM_eventP($shash,"Nack"); delete($shash->{cmdStack}); delete($attr{$shash->{NAME}}{protCmdPend}); CUL_HM_respPendRm($shash); $reply = "NACK"; } else{ #ACK $reply = ($subType eq "01")?"ACKStatus":"ACK"; $success = "yes"; } CUL_HM_setRd($chnhash,"CommandAccepted",$success,""); CUL_HM_ProcessCmdStack($shash); # see if there is something left return $reply; } elsif($msgType eq "10"){ my $subtype = substr($p,0,2); if($subtype eq "01"){ #storePeerList################# if ($pendType eq "PeerList"){ my $chn = $shash->{helper}{respWait}{forChn}; my $chnhash = $modules{CUL_HM}{defptr}{$src.$chn}; $chnhash = $shash if (!$chnhash); my @peers = substr($p,2,) =~ /(.{8})/g; my @peerList; my @peerID; foreach my $peer(@peers){ push(@peerList,CUL_HM_id2Name($peer)); push(@peerID,$peer); } my $peerFound = join (',',@peerList); $peerFound =~ s/broadcast//; # remove end indication, not a peer my $pl = ReadingsVal($chnhash->{NAME},"peerList","").",".$peerFound; CUL_HM_setRd($chnhash,"peerList",$pl,''); $peerFound = join (',',@peerID); $peerFound =~ s/00000000//; $chnhash->{helper}{peerList}.= ",".$peerFound; if ($p =~ m/000000..$/) {# last entry, peerList is complete CUL_HM_respPendRm($shash); # check for request to get List3 data my $reqPeer = $chnhash->{helper}{getCfgList}; if ($reqPeer){ my $flag = CUL_HM_getFlag($shash); my $id = CUL_HM_Id($shash->{IODev}); @peerID = split(",", $chnhash->{helper}{peerList}); my $class = AttrVal(CUL_HM_id2Name($src), "hmClass", ""); my $listNo = "0".$chnhash->{helper}{getCfgListNo}; foreach my $peer (@peerID){ $peer .="01" if (length($peer) == 6); # add the default if ($peer &&($peer eq $reqPeer || $reqPeer eq "all")){ CUL_HM_PushCmdStack($shash,sprintf("++%s01%s%s%s04%s%s", $flag,$id,$src,$chn,$peer,$listNo));# List3 or 4 } } CUL_HM_ProcessCmdStack($shash); } delete $chnhash->{helper}{getCfgList}; delete $chnhash->{helper}{getCfgListNo}; delete $chnhash->{helper}{peerList}; } else{ CUL_HM_respPendToutProlong($shash);#wasn't last - reschedule timer } return "done"; } } elsif($subtype eq "02" ||$subtype eq "03"){ #ParamResp################## if ($pendType eq "RegisterRead"){ my $chnSrc = $src.$shash->{helper}{respWait}{forChn}; my $chnhash = $modules{CUL_HM}{defptr}{$chnSrc}; $chnhash = $shash if (!$chnhash); my $chnName = $chnhash->{NAME}; my ($format,$data) = ($1,$2) if ($p =~ m/^(..)(.*)/); my $list = $shash->{helper}{respWait}{forList}; $list = "00" if (!$list); #use the default if ($format eq "02"){ # list 2: format aa:dd aa:dd ... $data =~ s/(..)(..)/ $1:$2/g; } elsif ($format eq "03"){ # list 3: format aa:dddd my $addr; my @dataList; ($addr,$data) = (hex($1),$2) if ($data =~ m/(..)(.*)/); if ($addr == 0){ $data = "00:00"; } else{ $data =~s/(..)/$1:/g; foreach my $d1 (split(":",$data)){ push (@dataList,sprintf("%02X:%s",$addr++,$d1)); } $data = join(" ",@dataList); } } my $regLN = "RegL_".$list.":".$shash->{helper}{respWait}{forPeer}; CUL_HM_setRd($chnhash,$regLN, ReadingsVal($chnName,$regLN,"")." ".$data,''); if ($data =~m/00:00$/){ # this was the last message in the block if($list eq "00"){ my $name = CUL_HM_id2Name($src); CUL_HM_setRd($shash,"PairedTo", sprintf("%02X%02X%02X", CUL_HM_getRegFromStore($name,10,0,"00000000"), CUL_HM_getRegFromStore($name,11,0,"00000000"), CUL_HM_getRegFromStore($name,12,0,"00000000")),""); } CUL_HM_respPendRm($shash); delete $chnhash->{helper}{shadowReg}{$regLN};#remove shadowhash } else{ CUL_HM_respPendToutProlong($shash);#wasn't last - reschedule timer } return "done"; } } elsif($subtype eq "04"){ #ParamChange################### my($chn,$peerID,$list,$data) = @_ if($p =~ m/^04(..)(........)(..)(.*)/); my $chnHash = $modules{CUL_HM}{defptr}{$src.$chn}; $chnHash = $shash if(!$chnHash); # will add param to dev if no chan my $listName = "RegL_".$list.":".CUL_HM_id2Name($peerID); $listName =~ s/ /_/g; #remove blanks $data =~ s/(..)(..)/ $1:$2/g; my $lN = ReadingsVal($chnHash->{NAME},$listName,""); $lN = "" if($lN =~m/00:00$/);#clear data if it was finished before $lN .= " ".$data; CUL_HM_setRdIfCh($chnHash,$listName,$lN,""); # todo: this is likely a set of messages. Postpone command stack processing # until end of transmission. Verify whether there is a conflict with a # current operation and use timer supervision to abort } elsif($subtype eq "06"){ #reply to status request####### #todo = what is the answer to a status request if ($pendType eq "StatusReq"){#it is the answer to our request my $chnSrc = $src.$shash->{helper}{respWait}{forChn}; my $chnhash = $modules{CUL_HM}{defptr}{$chnSrc}; $chnhash = $shash if (!$chnhash); CUL_HM_respPendRm($shash); return "STATresp";# todo dont send ACK - check what others do } else{ my ($chn) = ($1) if($p =~ m/^..(..)/); return "powerOn" if ($chn eq "00");# check dst eq "000000" as well? } } } elsif($msgType eq "70"){ #wakeup ####################################### #CUL_HM_Id($hash->{IODev}) if((CUL_HM_getRxType($shash) & 0x08) && $shash->{cmdStack}){ #send wakeup and process command stack if applicable CUL_HM_SendCmd($shash, '++A112'.CUL_HM_Id($shash->{IODev}).$src, 1, 1); CUL_HM_ProcessCmdStack($shash); } } return ""; } ############################# sub CUL_HM_getRegFromStore($$$$) {#read a register from backup data my($name,$regName,$list,$peerId)=@_; my $hash = CUL_HM_name2hash($name); my ($size,$pos,$conversion,$factor,$unit) = (8,0,"",1,""); # default my $addr = $regName; if ($culHmRegDefine{$regName}) { # get the register's information $addr = $culHmRegDefine{$regName}{a}; $pos = ($addr*10)%10; $addr = int($addr); $list = $culHmRegDefine{$regName}{l}; $size = $culHmRegDefine{$regName}{s}; $size = int($size)*8 + ($size*10)%10; $conversion = $culHmRegDefine{$regName}{c}; #unconvert formula $factor = $culHmRegDefine{$regName}{f}; $unit = $culHmRegDefine{$regName}{u}; } $peerId = substr(CUL_HM_Name2Id($name),0,6).sprintf("%02X",$1) if($peerId =~ m/^self(.*)/); # plus channel my $regLN = "RegL_".sprintf("%02X",$list).":".CUL_HM_id2Name($peerId); $regLN =~ s/broadcast//; $regLN =~ s/ /_/g; my $data=0; for (my $size2go = $size;$size2go>0;$size2go -=8){ my $addrS = sprintf("%02X",$addr); my $dRead; if ($hash->{helper}{shadowReg}&&$hash->{helper}{shadowReg}{$regLN}){ $dRead = $1 if($hash->{helper}{shadowReg}{$regLN} =~ m/$addrS:(..)/); } if (!$dRead && $hash->{READINGS}{$regLN}) { $dRead = $1 if($hash->{READINGS}{$regLN}{VAL} =~ m/$addrS:(..)/); } return "unknown" if (!$dRead); $data = ($data<< 8)+hex($dRead); $addr++; } $data = ($data>>$pos) & (0xffffffff>>(32-$size)); if (!$conversion){ ;# do nothing } elsif($conversion eq "factor"){ $data /= $factor; } elsif($conversion eq "fltCvT"){ $data = CUL_HM_CvTflt($data); } elsif($conversion eq "m10s3") { $data = ($data+3)/10; } else { return " conversion undefined - please contact admin"; } return $data.$unit; } ############################# my @culHmTimes8 = ( 0.1, 1, 5, 10, 60, 300, 600, 3600 ); sub CUL_HM_encodeTime8($) { my $v = shift; return "00" if($v < 0.1); for(my $i = 0; $i < @culHmTimes8; $i++) { if($culHmTimes8[$i] * 32 > $v) { for(my $j = 0; $j < 32; $j++) { if($j*$culHmTimes8[$i] >= $v) { return sprintf("%X", $i*32+$j); } } } } return "FF"; } ############################# sub CUL_HM_decodeTime8($) { my $v = hex(shift); return "undef" if($v > 255); my $v1 = int($v/32); my $v2 = $v%32; return $v2 * $culHmTimes8[$v1]; } ############################# sub CUL_HM_encodeTime16($) { my $v = shift; my $ret = "FFFF"; my $mul = 20; return "0000" if($v < 0.05); for(my $i = 0; $i < 16; $i++) { if($v*$mul < 0xfff) { $ret=sprintf("%03X%X", $v*$mul, $i); last; } $mul /= 2; } my $v2 = CUL_HM_decodeTime16($ret); Log 2, "Timeout $v rounded to $v2" if($v != $v2); return ($ret); } sub CUL_HM_convTemp($) { my ($val) = @_; if(!($val eq "on" || $val eq "off" || ($val =~ m/^\d*\.?\d+$/ && $val >= 6 && $val <= 30))) { my @list = map { ($_.".0", $_+0.5) } (6..30); pop @list; return "Invalid temperature $val, choose one of on off " . join(" ",@list); } $val = 100 if($val eq "on"); $val = 0 if($val eq "off"); return sprintf("%02X", $val*2); } ############################# sub CUL_HM_decodeTime16($) { my $v = hex(shift); my $m = int($v/16); my $e = $v % 16; my $mul = 0.05; while($e--) { $mul *= 2; } return $mul*$m; } ############################# sub CUL_HM_pushConfig($$$$$$$$) {#routine will generate messages to write cnfig data to register my ($hash,$src,$dst,$chn,$peerAddr,$peerChn,$list,$content) = @_; my $flag = CUL_HM_getFlag($hash); $peerAddr = "000000" if(!$peerAddr); my $tl = length($content); ($chn,$peerChn,$list) = split(':',sprintf("%02X:%02X:%02X",$chn,$peerChn,$list)); # --store pending changes in shadow to handle bit manipulations cululativ-- my $peerN = ($peerAddr eq "000000")?CUL_HM_id2Name($peerAddr.$peerChn):""; $peerN =~ s/broadcast//; $peerN =~ s/ /_/g;#remote blanks my $regLN = "RegL_".$list.":".$peerN; #--- copy data from readings to shadow my $chnhash = $modules{CUL_HM}{defptr}{$dst.$chn}; $chnhash = $hash if (!$chnhash); if (!$chnhash->{helper}{shadowReg} || !$chnhash->{helper}{shadowReg}{$regLN}){ $chnhash->{helper}{shadowReg}{$regLN} = ReadingsVal($chnhash->{NAME},$regLN,""); } #--- update with ne value my $regs = $chnhash->{helper}{shadowReg}{$regLN}; for(my $l = 0; $l < $tl; $l+=4) { #substitute changed bytes in shadow my $addr = substr($content,$l,2); my $data = substr($content,$l+2,2); if(!$regs || !($regs =~ s/$addr:../$addr:$data/)){ $regs .= " ".$addr.":".$data; } } $chnhash->{helper}{shadowReg}{$regLN} = $regs; CUL_HM_PushCmdStack($hash, "++".$flag.'01'.$src.$dst.$chn.'05'. $peerAddr.$peerChn.$list); for(my $l = 0; $l < $tl; $l+=28) { my $ml = $tl-$l < 28 ? $tl-$l : 28; CUL_HM_PushCmdStack($hash, "++A001".$src.$dst.$chn."08". substr($content,$l,$ml)); } CUL_HM_PushCmdStack($hash,"++A001".$src.$dst.$chn."06"); } sub CUL_HM_secSince2000() { # Calculate the local time in seconds from 2000. my $t = time(); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($t); $t -= 946684800; # seconds between 01.01.2000, 00:00 and THE EPOCH (1970) $t -= 7200; # HM Special $t += fhemTzOffset($t); return $t; } ############### Activity supervision section ################ # verify that devices are seen in a certain period of time # It will generate events if no message is seen sourced by the device during # that period. # ActionDetector will use the fixed HMid 000000 sub CUL_HM_ActGetCreateHash() {# return hash of ActionDetector - create one if not existant if (!$modules{CUL_HM}{defptr}{"000000"}){ DoTrigger("global", "UNDEFINED ActionDetector CUL_HM 000000"); $attr{ActionDetector}{actCycle} = 600; } my $defPtr = $modules{CUL_HM}{defptr}; my $actName = $defPtr->{"000000"}{NAME} if($defPtr->{"000000"}); my $actHash = $modules{CUL_HM}{defptr}{"000000"}; if (!$actHash->{helper}{first}){ # if called first time arrributes are no yet #recovered InternalTimer(gettimeofday()+3, "CUL_HM_ActGetCreateHash", "ActionDetector", 0); $actHash->{helper}{first} = 1; return; } if (!$actHash->{helper}{actCycle} ){ #This is the first call my $peerList = $attr{$actName}{peerList}; $peerList = "" if (!$peerList); my $tn = TimeNow(); foreach my $devId (split(",",$peerList)){ $actHash->{helper}{$devId}{start} = $tn; my $devName = CUL_HM_id2Name($devId); setReadingsVal($actHash,"status_".$devName,"unknown",$tn); $attr{$devName}{actStatus}=""; # force trigger CUL_HM_setAttrIfCh($devName,"actStatus","unknown","Activity"); } } if (!$actHash->{helper}{actCycle} || $actHash->{helper}{actCycle} != $attr{$actName}{actCycle}){ $attr{$actName}{actCycle} = 30 if(!$attr{$actName}{actCycle} || $attr{$actName}{actCycle}<30); $actHash->{helper}{actCycle} = $attr{$actName}{actCycle}; RemoveInternalTimer("ActionDetector"); $actHash->{STATE} = "active"; InternalTimer(gettimeofday()+$attr{$actName}{actCycle}, "CUL_HM_ActCheck", "ActionDetector", 0); } return $actHash; } sub CUL_HM_time2sec($) { my ($timeout) = @_; my ($h,$m) = split(":",$timeout); no warnings; $h = int($h); $m = int($m); use warnings; return ((sprintf("%03s:%02d",$h,$m)),((int($h)*60+int($m))*60)); } sub CUL_HM_ActAdd($$) {# add an HMid to list for activity supervision my ($devId,$timeout) = @_; #timeout format [hh]h:mm return $devId." is not an HM device - action detection cannot be added" if (length($devId) != 6); my ($cycleString,undef)=CUL_HM_time2sec($timeout); my $devName = CUL_HM_id2Name($devId); $attr{$devName}{actCycle} = $cycleString; $attr{$devName}{actStatus}=""; # force trigger CUL_HM_setAttrIfCh($devName,"actStatus","unknown","Activity"); my $actHash = CUL_HM_ActGetCreateHash(); my $actName = $actHash->{NAME}; # could have been renamed my $peerList = (!defined($attr{$actName}{peerList}))?"":$attr{$actName}{peerList}; $peerList .= $devId."," if($peerList !~ m/$devId,/);#add if not in $attr{$actName}{peerList} = $peerList; my $tn = TimeNow(); $actHash->{helper}{$devId}{start} = $tn; setReadingsVal($actHash,"status_".$devName,"unknown",$tn); Log GetLogLevel($actName,3),"Device ".$devName." added to ActionDetector with " .$cycleString." time"; } sub CUL_HM_ActDel($) {# delete HMid for activity supervision my ($devId) = @_; return $devId." is not an HM device - action detection cannot be added" if (length($devId) != 6); my $devName = CUL_HM_id2Name($devId); delete ($attr{$devName}{actCycle}); CUL_HM_setAttrIfCh($devName,"actStatus","deleted","Activity");#post trigger delete ($attr{$devName}{actStatus}); my $acthash = CUL_HM_ActGetCreateHash(); my $actName = $acthash->{NAME}; delete ($acthash->{helper}{$devId}); $attr{$actName}{peerList} = "" if (!defined($attr{$actName}{peerList})); $attr{$actName}{peerList} =~ s/$devId,//g; Log GetLogLevel($actName,3),"Device ".$devName." removed from ActionDetector"; } sub CUL_HM_ActCheck() {# perform supervision my $actHash = CUL_HM_ActGetCreateHash(); my $tn = TimeNow(); my $tod = int(gettimeofday()); my $actName = $actHash->{NAME}; delete ($actHash->{READINGS}); #cleansweep CUL_HM_setRd($actHash,"status","check performed",$tn); foreach my $devId (split(",",AttrVal($actName,"peerList","none"))){ my $devName = CUL_HM_id2Name($devId); if(!$devName || !defined($attr{$devName}{actCycle})){ CUL_HM_ActDel($devId); next; } my $rdName = "status_".$devName; my (undef,$tSec)=CUL_HM_time2sec($attr{$devName}{actCycle}); if ($tSec == 0){# detection switched off CUL_HM_setRdIfCh($actHash,$rdName,"switchedOff",$tn); CUL_HM_setAttrIfCh($devName,"actStatus","switchedOff","Activity"); next; } my $tLast = $attr{$devName}{"protLastRcv"}; my @t = localtime($tod - $tSec); #time since when a trigger is expected my $tSince = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]); if ((!$tLast || $tSince gt $tLast)){ #no message received in timeframe if ($tSince gt $actHash->{helper}{$devId}{start}){ CUL_HM_setRdIfCh($actHash,$rdName,"timedOut - last: ".$tLast,$tn); CUL_HM_setAttrIfCh($devName,"actStatus","dead","Activity"); Log GetLogLevel($actName,2),"Device ".$devName." is dead"; } # no action otherwise } else{ CUL_HM_setRdIfCh($actHash,$rdName,"alive",$tn); CUL_HM_setAttrIfCh($devName,"actStatus","alive","Activity"); Log GetLogLevel($actName,5),"Device ".$devName." is alive"; } } $attr{$actName}{actCycle} = 600 if($attr{$actName}{actCycle}<30); $actHash->{helper}{actCycle} = $attr{$actName}{actCycle}; InternalTimer(gettimeofday()+$attr{$actName}{actCycle}, "CUL_HM_ActCheck", "ActionDetector", 0); } sub CUL_HM_setRd($$$$) #$hash,$rd,$val,$ts {#change all readings from here - till fhem.pl provides solution my ($hash,$rd,$val,$ts) = @_; $ts = TimeNow() if (!$ts); setReadingsVal($hash,$rd,$val,$ts); } sub CUL_HM_setRdIfCh($$$$) { my ($hash,$rd,$val,$ts) = @_; $ts = TimeNow() if (!$ts); setReadingsVal($hash,$rd,$val,$ts) if(ReadingsVal($hash->{NAME},$rd,"") ne $val); } sub CUL_HM_setAttrIfCh($$$$) { my ($name,$att,$val,$trig) = @_; if($attr{$name}{$att} ne $val){ DoTrigger($name,$trig.":".$val) if($trig); $attr{$name}{$att} = $val; } } 1; =pod =begin html

CUL_HM

    Support for eQ-3 HomeMatic devices via the CUL or the HMLAN.

    Define
      define <name> CUL_HM <6-digit-hex-code|8-digit-hex-code>

      Correct device definition is the key for HM environment simple maintenance.
      Background to define entities:
      HM devices has a 3 byte (6 digit hex value) HMid - which is key for addressing. Each device hosts one or more channels. HMid for a channel is the device's HMid plus the channel number (1 byte, 2 digit) in hex. Channels should be defined for all multi-channel devices. Channel entities cannot be defined if the hosting device does not exist
      Note: FHEM mappes channel 1 to the device if it is not defined explicitely. Therefore it does not need to be defined for single channel devices.
      Note: if a device is deleted all assotiated channels will be removed as well.
      An example for a full definition of a 2 channel switch is given below:
        define livingRoomSwitch CUL_HM 123456
        define LivingroomMainLight CUL_HM 12345601
        define LivingroomBackLight CUL_HM 12345602

      livingRoomSwitch is the device managing communication. This device is defined prior to channels to be able to setup references.
      LivingroomMainLight is channel 01 dealing with status of light, channel peers and channel assotiated register. If not defined channel 01 is covered by the device entity.
      LivingRoomBackLight is the second 'channel', channel 02. Its definition is mandatory to operate this function.

      Sender specials: HM threats each button of remotes, push buttons and similar as channels. It is possible (not necessary) to define a channel per button. If all channels are defined access to pairing informatin is possible as well as access to channel related register. Furthermore names make the traces better readable.

      define may also be invoked by the autocreate module, together with the necessary hmClass and subType attributes. Usually you issue a hmPairForSec and press the corresponding button on the device to be paired, or issue a hmPairSerial set command if the device is a receiver and you know its serial number. Autocreate will then create a fhem device and set all necessary attributes. Without pairing the device will not accept messages from fhem. fhem may create the device even if the pairing is not successful. Upon a successful pairing you'll see a CommandAccepted entry in the details section of the CUL_HM device.

      If you cannot use autocreate, then you have to specify:
      • the <6-digit-hex-code>or HMid+ch <8-digit-hex-code>
        It is the unique, hardcoded device-address and cannot be changed (no, you cannot choose it arbitrarily like for FS20 devices). You may detect it by inspecting the fhem log.
      • the hmClass attribute
        which is either sender or receiver
      • the subType attribute
        which is one of switch dimmer blindActuator remote sensor swi pushButton threeStateSensor motionDetector keyMatic winMatic smokeDetector
      Without these attributes fhem won't be able to decode device messages appropriately.

      Notes
      • If the interface is a CUL device, the rfmode attribute of the corresponding CUL/CUN device must be set to HomeMatic. Note: this mode is BidCos/Homematic only, you will not receive FS20/HMS/EM/S300 messages via this device. Previously defined FS20/HMS etc devices must be assigned to a different input device (CUL/FHZ/etc).
      • Currently supported device families: remote, switch, dimmer, blindActuator, motionDetector, smokeDetector, threeStateSensor, THSensor, winmatic. Special devices: KS550, HM-CC-TC and the KFM100.
      • Device messages can only be interpreted correctly if the device type is known. fhem will extract the device type from a "pairing request" message, even if it won't respond to it (see hmPairSerial and hmPairForSec to enable pairing). As an alternative, set the correct subType and model attributes, for a list of possible subType values see "attr hmdevice ?".
      • The so called "AES-Encryption" is in reality a signing request: if it is enabled, an actor device will only execute a received command, if a correct answer to a request generated by the actor is received. This means:
        • Reaction to commands is noticably slower, as 3 messages are sent instead of one before the action is processed by the actor.
        • Every command and its final ack from the device is sent in clear, so an outside observer will know the status of each device.
        • The firmware implementation is buggy: the "toggle" event is executed before the answer for the signing request is received, at least by some switches (HM-LC-Sw1-Pl and HM-LC-SW2-PB-FM).
        • The HMLAN configurator will answer signing requests by itself, and if it is configured with the 3-byte address of a foreign CCU which is still configurerd with the default password, it is able to answer signing requests correctly.
        • AES-Encryption is not useable with a CUL device as the interface, but it is supported with a HMLAN. Due to the issues above I do not recommend using Homematic encryption at all.

    Set
      Note: devices which are normally send-only (remote/sensor/etc) must be set into pairing/learning mode in order to receive the following commands.

      General commands (available to most hm devices):
      • actiondetect <[hhh:mm]|off>
        Supports 'alive' or better 'not alive' detection for devices. [hhh:mm] is the maxumin silent time for the device. Upon no message received in this period an event will be raised "<device> is dead". If the device sends again another notification is posted "<device> is alive".
        This actiondetect will be autocreated for each device with build in cyclic status report.
        Controlling entity is a pseudo device "ActionDetector" with HMId "000000".
        Due to performance considerations the report latency is set to 600sec (10min). It can be controlled by the attribute "actCycle" of "ActionDetector".
        Once entered to the supervision the HM device has 2 attributes:
          actStatus: activity status of the device
          actCycle: detection period [hhh.mm]
        Furthermore the overall function can be viewed checking out the "ActionDetector" entity. Here the status of all entities is present in the READING section.
        Note: This function can be enabled for devices with non-cyclic messages as well. It is up to the user to enter a reasonable cycletime.
      • getConfig
        Will read major configuration items stored in the HM device. Executed on a channel it will read pair Inforamtion, List0, List1 and List3 of the 1st internal peer. Furthermore the peerlist will be retrieved for teh given channel. If executed on a device the command will get the above info or all assotated channels. Not included will be the configuration for additional peers.
        The command is a shortcut for a selection of other commands.
      • getdevicepair
        will read the peers (see devicepair) that are assigned to a channel. This command needs to be executed per channel. Information will be stored in the field Peers of the channel (see devicepair for specials about single-channel deivces).
        For sender the same procedure as described in devicepair is necessary to get a reading. Also note that a proper diaplay will only be possible if define per channel (button) was done - see define.
      • getpair
        read pair information of the device. See also pair
      • getRegRaw [List0|List1|List2|List3|List4|List5|List6] <peerChannel>
        Read registerset in raw format. Description of the registers is beyond the scope of this documentation.
        Registers are structured in so called lists each containing a set of registers.
        List0: device-level settings e.g. CUL-pairing or dimmer thermal limit settings.
        List1: per channel settings e.g. time to drive the blind up and down.
        List3: per 'link' settings - means per peer-channel. This is a lot of data!. It controlls actions taken upon receive of a trigger from the peer.
        List4: settings for channel (button) of a remote

        <PeerChannel> paired HMid+ch, i.e. 4 byte (8 digit) value like '12345601'. It is mendatory for List 3 and 4 and can be left out for List 0 and 1.
        'all' can be used to get data of each paired link of the channel.
        'selfxx' can be used to address data for internal channels (associated with the build-in switches if any). xx is the number of the channel in decimal.
        Note1: execution depends on the entity. If List1 is requested on a device rather then a channel the command will retrieve List1 for all channels assotiated. List3 with peerChannel = all will get all link for all channel if executed on a device.
        Note2: for 'sender' see remote
        Note3: the information retrieval may take a while - especially for devices with a lot of channels and links. It may be necessary to refresh the web interface manually to view the results
        Note4: the direct buttons on a HM device are hidden by default. Nevertheless those are implemented as links as well. To get access to the 'internal links' it is necessary to issue 'set <name> setReg intKeyVisib 1' or 'set <name> setRegRaw List0 2 81'. Reset it by replacing '81' with '01'
        example:
          set mydimmer getRegRaw List1
          set mydimmer getRegRaw List3 all
      • pair
        Pair the device again with its known serialNumber (e.g. after a device reset) to the CUL. If paired, devices will report status information to the CUL. If not paired, the device wont respond to requests, and certain status information is also not reported. Paring is on device level and is common for all channels. See also getPair and unpair.
      • regRaw [List0|List1|List2|List3|List4] <addr> <data> <peerChannel>
        Will set register for device or channel. See also getRegRaw.
        <addr> and <data> are 1 byte values that need to be given in hex.
        Example:
          set mydimmer regRaw List1 0B 10 00000000
          set mydimmer regRaw List1 0C 00 00000000
        will set the max drive time up for a blind actor to 25,6sec
      • regSet <regName> <value> <peerChannel>
        For some major register a readable version is implemented supporting register names <regName> and value conversionsing. Only a subset of register can be supproted.
        <value> is the data in human readable manner that will be written to the register.
        <peerChannel> is required if this register is defined on a per 'devicepair' base. It can be set to '0' other wise.See getRegRaw for full description
        Supported register for a device can be explored using
          set regSet ? 0 0
        Condensed register description will be printed using
          set regSet <regname> ? 0
      • reset
        Factory reset the device. You need to pair it again to use it with fhem.
      • sign [on|off]
        Activate or deactivate signing (also called AES encryption, see the note above). Warning: if the device is attached via a CUL, you won't be able to switch it (or deactivate signing) from fhem before you reset the device directly.
      • statusRequest
        Update device status. For multichannel devices it should be issued on an per channel base
      • unpair
        "Unpair" the device, i.e. make it available to pair with other master devices. See pair for description.
      • virtual <number of buttons>
        configures a defined curcuit as virtual remote controll. Then number of button being added is 1 to 255. If the command is issued a second time for the same entity additional buttons will be added.
        Example for usage:
          define vRemote CUL_HM 100000 # the selected HMid must not be in use
          set vRemote virtual 20 # define 20 button remote controll
          set vRemote_Btn4 devicepair 0 <actorchannel> # pairs Button 4 and 5 to the given channel
          set vRemote_Btn4 press
          set vRemote_Btn5 press long
        see also press

      subType (i.e family) dependent commands:

      • switch
        • on - set the switch on
        • off - set the switch off
        • on-for-timer <sec> - set the switch on for the given seconds [0-85825945].
          Note: off-for-timer like FS20 is not supported. It needs to be programmed on link level.
        • on-till <time> - set the switch on for the given end time.
            set <name> on-till 20:32:10
          Currently a max of 24h is supported with endtime.
        • toggle - toggle the switch.

      • dimmer, blindActuator
        • 0 - 100 [on-time] [ramp-time]
          set the actuator to the given value (in percent) with a resolution of 0.5.
          Optional for dimmer on-time and ramp time can be choosen, both in seconds with 0.1s granularity.
          On-time is analog "on-for-timer".
          Ramp-time default is 2.5s, 0 means instantanous
        • on set level to 100%
        • off set level to 0%
        • toggle - toggle between off and the last on-value
        • on-for-timer <sec> - Dimmer only!
        • on-till <time> - Dimmer only!
        • stop - stop motion or dim ramp

      • remotes, pushButton
        This class of devices does not react on requests unless they are put to learn mode. FHEM obeys this behavior by stacking all requests until learn mode is detected. Manual interaction of the user is necessary to activate learn mode. Whether commands are pending is reported on device level with parameter 'protCmdPend'.
        • devicepair <btn_no> <hmDevice> [single|dual] [set|unset] [actor|remote]
          Pair/unpair will establish a connection between a sender-channel and an actuator-channel called link in HM nomenclatur. Trigger from sender-channel, e.g. button press, will be processed by the actuator-channel without CCU interaction. Sender-channel waits for an acknowledge of each actuator paired to it. Positive indication will be given once all actuator responded.
          Sender must be set into learning mode after command execution. FHEM postpones the commands until then.
          devicepair can be repeated for an existing devicepair. This will cause parameter reset to HM defaults for this link.
          Even though the command is executed on a remote or push-button it will as well take effect on the actuator directly. Both sides' pairing is virtually independant and has different impact on sender and receiver side.
          Devicepairing of one actuator-channel to multiple sender-channel as well as one sender-channel to multiple Actuator-channel is possible.
          <hmDevice> is the actuator-channel to be paired.
          <btn_no> is the sender-channel (button) to be paired. If 'single' is choosen buttons are counted from 1. For 'dual' btn_no is the number of the Button-pair to be used. I.e. '3' in dual is the 3rd button pair correcponding to button 5 and 6 in single mode.
          If the command is executed on a channel the btn_no is ignored.
          [single|dual]: this mode impacts the default behavior of the Actuator upon using this button. E.g. a dimmer can be learned to a single button or to a button pair.
          'dual' (default) Button pairs two buttons to one actuator. With a dimmer this means one button for dim-up and one for dim-down.
          'single' uses only one button of the sender. It is useful for e.g. for simple switch actuator to toggle on/off. Nevertheless also dimmer can be learned to only one button.
          'set' will setup pairing for the channels
          'unset' will remove the pairing for the channels
          [actor|remote|both] limits the execution to only actor or only remote. This gives the user the option to redo the pairing on the remote channel while the settings in the actor will not be removed.
          Example:
            set myRemote devicepair 2 mySwActChn single set # pair second button to an actuator channel
            set myRmtBtn devicepair 0 mySwActChn single set #myRmtBtn is a button of the remote. '0' is not processed here
            set myRemote devicepair 2 mySwActChn dual set #pair button 3 and 4
            set myRemote devicepair 3 mySwActChn dual unset #remove pairing for button 5 and 6
            set myRemote devicepair 3 mySwActChn dual unset aktor #remove pairing for button 5 and 6 in actor only
            set myRemote devicepair 3 mySwActChn dual set remote #pair button 5 and 6 on remote only. Link settings il mySwActChn will be maintained

      • virtual
        • devicepair see remote
        • press [long|short] simulates a button press short (default) or long. Note that the current implementation will not specify the duration for long. Only one trigger will be sent of type "long".
      • smokeDetector
        Note: All these commands work right now only if you have more then one smoekDetector, and you paired them to form a group. For issuing the commands you have to use the master of this group, and currently you have to guess which of the detectors is the master.
        • test - execute a network test
        • alarmOn - initiate an alarm
        • alarmOff - switch off the alarm
      • 4Dis (HM-PB-4DIS-WM)
        • text <btn_no> [on|off] <text1> <text2>
          Set the text on the display of the device. To this purpose issue this set command first (or a number of them), and then choose from the teach-in menu of the 4Dis the "Central" to transmit the data. Example:
            set 4Dis text 1 on On Lamp
            set 4Dis text 1 off Kitchen Off

      • Climate-Control (HM-CC-TC)
        • day-temp <tmp>
          night-temp <tmp>
          party-temp <tmp>
          desired-temp <tmp>
          Set different temperatures. Temp must be between 6 and 30 Celsius, and precision is half a degree.
        • tempListSat HH:MM temp ... 24:00 temp
          tempListSun HH:MM temp ... 24:00 temp
          tempListMon HH:MM temp ... 24:00 temp
          tempListTue HH:MM temp ... 24:00 temp
          tempListThu HH:MM temp ... 24:00 temp
          tempListWed HH:MM temp ... 24:00 temp
          tempListFri HH:MM temp ... 24:00 temp
          Specify a list of temperature intervals. Up to 24 intervals can be specified for each week day, the resolution is 10 Minutes. The last time spec must always be 24:00.
          Example: set th tempListSat 06:00 19 23:00 22.5 24:00 19
          Meaning: until 6:00 temperature shall be 19, from then until 23:00 temperature shall be 22.5, thereafter until midnight, 19 degrees celsius is desired.
        • displayMode [temp-only|temp-hum]
          displayTemp [actual|setpoint]
          displayTempUnit [celsius|fahrenheit]
          controlMode [manual|auto|central|party]
          decalcDay <day>

      • OutputUnit (HM-OU-LED16)
        • led [off|red|green|yellow]
          switches the LED of the channel to the color. If the command is executed on a device it will set all LEDs to the specified color.
          For Expert all LEDs can be set individual by providing a 8-digit hex number to the device.
        • ilum <brightness><duration>
          <brightness> [0-15] of backlight.
          <duration> [0-127] in sec. 0 is permanent 'on'.

      • OutputUnit (HM-OU-CFM-PL)
        • led <color>[,<color>..]
          Possible colors are [redL|greenL|yellowL|redS|greenS|yellowS]. A sequence of colors can be given separating the color entries by ','. White spaces must not be used in the list. 'S' indicates short and 'L' long ilumination.
        • playTone <MP3No>[,<MP3No>..]
          Play a series of tones. List is to be entered separated by ','. White spaces must not be used in the list.

      • HM-RC-19xxx
        • alarm <count>
          issue an alarm message to the remote
        • service <count>
          issue an service message to the remote
        • symbol <symbol> [set|unset]
          activate a symbol as available on the remote.
        • beep [off|1|2|3]
          activate tone
        • backlight [off|on|slow|fast]
          activate backlight
        • display <text> comma unit tone backlight <symbol(s)>
          control display of the remote
          <text> : up to 5 chars
          comma : 'comma' activates the comma, 'no' leaves it off
          [unit] : set the unit symbols. [off|Proz|Watt|x3|C|x5|x6|x7|F|x9|x10|x11|x12|x13|x14|x15]. Currently the x3..x15 display is not tested.
          tone : activate one of the 3 tones [off|1|2|3]
          backlight: activate backlight flash mode [off|on|slow|fast]
          <symbol(s)> activate symbol display. Multople symbols can be acticated at the same time, concatinating them comma separated. Don't use spaces here. Possiblesymbols are [bulb|switch|window|door|blind|scene|phone|bell|clock|arrowUp|arrowDown]

          Example:
            # "hello" in display, symb bulb on, backlight, beep
            set FB1 display Hello no off 1 on bulb
            # "1234,5" in display with unit 'W'. Symbols scene,phone,bell and # clock are active. Backlight flashing fast, Beep is second tone
            set FB1 display 12345 comma Watt 2 fast scene,phone,bell,clock

      • keyMatic

          The Keymatic uses the AES signed communication. Therefore the control of the Keymatic is only together with the HM-LAN adapter possible. But the CUL can read and react on the status information of the Keymatic.

        • lock
          The lock bolt moves to the locking position
        • unlock [sec]
          The lock bolt moves to the unlocking position.
          [sec]: Sets the delay in seconds after the lock automatically locked again.
          0 - 65535 seconds
        • open [sec]
          Unlocked the door so that the door can be opened.
          [sec]: Sets the delay in seconds after the lock automatically locked again.
          0 - 65535 seconds
        • inhibit [on|off]
          Block / unblock all directly paired remotes and the hardware buttons of the keyMatic. If inhibit set on, the door lock drive can be controlled only by FHEM.

          Examples:
            # Lock the lock
            set keymatic lock

            # open the door and relock the lock after 60 seconds
            set keymatic unlock 60

      Debugging:
      • raw <data> ...
        Only needed for experimentation. send a list of "raw" commands. The first command will be immediately sent, the next one after the previous one is acked by the target. The length will be computed automatically, and the message counter will be incremented if the first two charcters are ++. Example (enable AES):
           set hm1 raw ++A001F100001234560105000000001\
                       ++A001F10000123456010802010AF10B000C00\
                       ++A001F1000012345601080801\
                       ++A001F100001234560106

    Get
    • param <paramName>
      returns the content of the relevant parameter for the entity.
      Note: if this command is executed on a channel and 'model' is requested the content hosting device's 'model' will be returned.
    • reg <addr> <list> <peerID>
      returns the value of a register. The data is taken from the storage in FHEM. It is not read again. If registercontent is not present at this point in time please use getReg in advance.
      <addr> address in hex of the register. Registername can be used alternaly if available in FHEM.
      <list> list from which the register is taken. If rgistername is used list is ignored and can be set to 0.
      <peerID> identifies the registerbank in case of list3 and list4. It an be set to dummy if not used.
    • regList
      returns a list of register that are implemented in FHEM for this device.

    Attributes
    • eventMap
    • do_not_notify
    • ignore
    • dummy
    • showtime
    • loglevel
    • hmClass, model, subType
      These attributes are set automatically after a successful pairing. They are not supposed to be set by hand, and are necessary in order to correctly interpret device messages or to be able to send them.
    • rawToReadable
      Used to convert raw KFM100 values to readable data, based on measured values. E.g. fill slowly your container, while monitoring the values reported with inform. You'll see:
        10 (at 0%)
        50 (at 20%)
        79 (at 40%)
        270 (at 100%)
      Apply these values with: "attr KFM100 rawToReadable 10:0 50:20 79:40 270:100". fhem will do a linear interpolation for values between the bounderies.
    • unit
      set the reported unit by the KFM100 if rawToReadable is active. E.g.
      attr KFM100 unit Liter

    Generated events:
    • KS550/HM-WDS100-C6-O:
      T: $t H: $h W: $w R: $r IR: $ir WD: $wd WDR: $wdr S: $s B: $b
    • HM-CC-TC:
      T: $t H: $h
      temperature $t
      humidity $h
      actuator $vp %
      desired-temp $t
      desired-temp-ack $t
      tempList$wd hh:mm $t hh:mm $t ...
      ValveErrorPosition $dname $vep %
      ValveOffset $dname $of %
      windowopentemp-$tchan $t (sensor:$tdev)
    • HM-CC-VD:
      actuator $vp %
      motor [opening|closing|blocked|loose|adjusting range too small|ok]
      battery [low|ok]
      ValveErrorPosition $vep %
      ValveOffset $dname $of %
    • KFM100:
      rawValue $v
      Sequence $s
      $cv $unit
    • switch/dimmer/blindActuator:
      deviceMsg [on|off|$val %]
      poweron [on|off|$val]
    • dimmer:
      dim: [up|down|stop]
    • HM-LC-BL1-PB-FM:
      motor: [opening|closing]
    • remote/pushButton
        (to $dest) is added if the button is peered and does not send to broadcast
        Release is provided for peered channels only
      Btn$x onShort
      Btn$x offShort
      Btn$x onLong $counter
      Btn$x offLong $counter
      Btn$x onLongRelease $counter
      Btn$x offLongRelease $counter
      Btn$x onShort (to $dest)
      Btn$x offShort (to $dest)
      Btn$x onLong $counter (to $dest)
      Btn$x offLong $counter (to $dest)
      Btn$x onLongRelease $counter (to $dest)
      Btn$x offLongRelease $counter (to $dest)
      battery: [low|ok]
    • swi
      Btn$x toggle
      Btn$x toggle (to $dest)
      battery: [low|ok]
    • motionDetector
      brightness:$b
      alive
      motion
      cover closed
      cover open
    • smokeDetector
      on
      smoke_detect on
      all-clear
      alive
      test $t
    • threeStateSensor (all)
      sabotage
      alive
    • threeStateSensor (HM-SEC-WDS)
      contact wet
      contact damp
      contact dry
    • threeStateSensor (generic)
      contact closed
      contact open
      contact tilted
    • THSensor
      T: $t H: $h
      temperature $t
      humidity $h
    • WDC7000
      T: $t H: $h AP: $ap
      temperature $t
      humidity $h
      airpress $ap
    • winMatic
      contact closed
      contact open
      contact tilted
      contact movement_tilted
      contact movement_closed
      contact lock_on
      airing: $air
      course: tilt
      course: close

=end html =cut