Created
November 29, 2011 16:40
-
-
Save rmoriz/1405454 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ############################################## | |
| # CUL HomeMatic handler | |
| package main; | |
| use strict; | |
| use warnings; | |
| sub CUL_HM_Define($$); | |
| sub CUL_HM_Id($); | |
| sub CUL_HM_Initialize($); | |
| sub CUL_HM_Pair(@); | |
| sub CUL_HM_Parse($$); | |
| sub CUL_HM_PushCmdStack($$); | |
| sub CUL_HM_SendCmd($$$$); | |
| sub CUL_HM_Set($@); | |
| sub CUL_HM_DumpProtocol($$@); | |
| sub CUL_HM_convTemp($); | |
| my %culHmDevProps=( | |
| "10" => { st => "switch", cl => "receiver" }, # Parse,Set | |
| "20" => { st => "dimmer", cl => "receiver" }, # Parse,Set | |
| "30" => { st => "blindActuator", cl => "receiver" }, # Parse,Set | |
| "40" => { st => "remote", cl => "sender" }, # Parse | |
| "41" => { st => "sensor", cl => "sender" }, | |
| "42" => { st => "swi", cl => "sender" }, | |
| "43" => { st => "pushButton", cl => "sender" }, | |
| "60" => { st => "KFM100", cl => "sender" }, # Parse,unfinished | |
| "70" => { st => "THSensor", cl => "sender" }, # Parse,unfinished | |
| "80" => { st => "threeStateSensor",cl => "sender" }, | |
| "81" => { st => "motionDetector", cl => "sender" }, | |
| "C0" => { st => "keyMatic", cl => "sender" }, | |
| "C1" => { st => "winMatic", cl => "receiver" }, | |
| "CD" => { st => "smokeDetector", cl => "sender" }, # Parse | |
| ); | |
| my %culHmModel=( | |
| "0001" => "HM-LC-SW1-PL-OM54", | |
| "0002" => "HM-LC-SW1-SM", | |
| "0003" => "HM-LC-SW4-SM", | |
| "0004" => "HM-LC-SW1-FM", | |
| "0005" => "HM-LC-BL1-FM", | |
| "0006" => "HM-LC-BL1-SM", | |
| "0007" => "KS550", # Tested | |
| "0008" => "HM-RC-4", # cant pair(AES)-> broadcast only | |
| "0009" => "HM-LC-SW2-FM", | |
| "000A" => "HM-LC-SW2-SM", | |
| "000B" => "HM-WDC7000", | |
| "000D" => "ASH550", | |
| "000E" => "ASH550I", | |
| "000F" => "S550IA", | |
| "0011" => "HM-LC-SW1-PL", # Tested | |
| "0012" => "HM-LC-DIM1L-CV", | |
| "0013" => "HM-LC-DIM1L-PL", | |
| "0014" => "HM-LC-SW1-SM-ATMEGA168", | |
| "0015" => "HM-LC-SW4-SM-ATMEGA168", | |
| "0016" => "HM-LC-DIM2L-CV", | |
| "0018" => "CMM", | |
| "0019" => "HM-SEC-KEY", | |
| "001A" => "HM-RC-P1", | |
| "001B" => "HM-RC-SEC3", | |
| "001C" => "HM-RC-SEC3-B", | |
| "001D" => "HM-RC-KEY3", | |
| "001E" => "HM-RC-KEY3-B", | |
| "0022" => "WS888", | |
| "0026" => "HM-SEC-KEY-S", | |
| "0027" => "HM-SEC-KEY-O", | |
| "0028" => "HM-SEC-WIN", # some experiments | |
| "0029" => "HM-RC-12", | |
| "002A" => "HM-RC-12-B", | |
| "002D" => "HM-LC-SW4-PCB", | |
| "002E" => "HM-LC-DIM2L-SM", | |
| "002F" => "HM-SEC-SC", | |
| "0030" => "HM-SEC-RHS", # Tested | |
| "0034" => "HM-PBI-4-FM", | |
| "0035" => "HM-PB-4-WM", | |
| "0036" => "HM-PB-2-WM", | |
| "0037" => "HM-RC-19", | |
| "0038" => "HM-RC-19-B", | |
| "0039" => "HM-CC-TC", # Selected commands | |
| "003A" => "HM-CC-VD", # Actuator, battery/etc missing | |
| "003B" => "HM-RC-4-B", | |
| "003C" => "HM-WDS20-TH-O", | |
| "003D" => "HM-WDS10-TH-O", | |
| "003E" => "HM-WDS30-T-O", | |
| "003F" => "HM-WDS40-TH-I", | |
| "0040" => "HM-WDS100-C6-O", # Identical to KS550? | |
| "0041" => "HM-WDC7000", | |
| "0042" => "HM-SEC-SD", # Tested | |
| "0043" => "HM-SEC-TIS", | |
| "0044" => "HM-SEN-EP", | |
| "0045" => "HM-SEC-WDS", | |
| "0046" => "HM-SWI-3-FM", | |
| "0047" => "KFM-Display", | |
| "0048" => "IS-WDS-TH-OD-S-R3", | |
| "0049" => "KFM-Sensor", | |
| "004A" => "HM-SEC-MDIR", # Tested | |
| "004C" => "HM-RC-12-SW", | |
| "004D" => "HM-RC-19-SW", | |
| "004E" => "HM-LC-DDC1-PCB", | |
| "004F" => "HM-SEN-MDIR-SM", | |
| "0050" => "HM-SEC-SFA-SM", | |
| "0051" => "HM-LC-SW1-PB-FM", | |
| "0052" => "HM-LC-SW2-PB-FM", # Tested | |
| "0053" => "HM-LC-BL1-PB-FM", | |
| "0056" => "HM-CC-SCD", | |
| "0057" => "HM-LC-DIM1T-PL", | |
| "0058" => "HM-LC-DIM1T-CV", | |
| "0059" => "HM-LC-DIM1T-FM", | |
| "005A" => "HM-LC-DIM2T-SM", | |
| "005C" => "HM-OU-CF-PL", | |
| "005F" => "HM-SCI-3-FM", | |
| "0060" => "HM-PB-4DIS-WM", # Tested | |
| "0061" => "HM-LC-SW4-DR", | |
| "0062" => "HM-LC-SW2-DR", | |
| ); | |
| sub | |
| CUL_HM_Initialize($) | |
| { | |
| my ($hash) = @_; | |
| $hash->{Match} = "^A......................"; | |
| $hash->{DefFn} = "CUL_HM_Define"; | |
| $hash->{ParseFn} = "CUL_HM_Parse"; | |
| $hash->{SetFn} = "CUL_HM_Set"; | |
| $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:0,1 " . | |
| "showtime:1,0 loglevel:0,1,2,3,4,5,6 model " . | |
| "subType:switch,dimmer,blindActuator,remote,sensor,". | |
| "swi,pushButton,threeStateSensor,motionDetector,". | |
| "keyMatic,winMatic,smokeDetector " . | |
| "hmClass:receiver,sender serialNr firmware devInfo ". | |
| "rawToReadable unit"; | |
| } | |
| ############################# | |
| sub | |
| CUL_HM_Define($$) | |
| { | |
| my ($hash, $def) = @_; | |
| my @a = split("[ \t][ \t]*", $def); | |
| my $name = $hash->{NAME}; | |
| return "wrong syntax: define <name> CUL_HM 6-digit-hex-code [Raw-Message]" | |
| if(!(int(@a)==3 || int(@a)==4) || $a[2] !~ m/^[A-F0-9]{6,8}$/i); | |
| $modules{CUL_HM}{defptr}{uc($a[2])} = $hash; | |
| AssignIoPort($hash); | |
| # shadow switch device, look for the real one, and copy its attributes | |
| if(length($a[2]) == 8) { | |
| my $chiefId = substr($a[2], 0, 6); | |
| my $chiefHash = $modules{CUL_HM}{defptr}{uc($chiefId)}; | |
| if($chiefHash) { | |
| my $cname = $chiefHash->{NAME}; | |
| if($attr{$cname}) { | |
| foreach my $attrName (keys %{$attr{$cname}}) { | |
| $attr{$name}{$attrName} = $attr{$cname}{$attrName}; | |
| } | |
| } | |
| } | |
| } | |
| if(int(@a) == 4) { | |
| $hash->{DEF} = $a[2]; | |
| CUL_HM_Parse($hash, $a[3]); | |
| } | |
| return undef; | |
| } | |
| ############################# | |
| sub | |
| CUL_HM_Parse($$) | |
| { | |
| my ($iohash, $msg) = @_; | |
| my $id = CUL_HM_Id($iohash); | |
| # Msg format: Allnnccttssssssddddddpp... | |
| $msg =~ m/A(..)(..)(....)(......)(......)(.*)/; | |
| my @msgarr = ($1,$2,$3,$4,$5,$6,$7); | |
| my ($len,$msgcnt,$cmd,$src,$dst,$p) = @msgarr; | |
| # $shash will be replaced for multichannel commands | |
| my $shash = $modules{CUL_HM}{defptr}{$src}; | |
| my $lcm = "$len$cmd"; | |
| my $dhash = $modules{CUL_HM}{defptr}{$dst}; | |
| my $dname = $dhash ? $dhash->{NAME} : "unknown"; | |
| $dname = "broadcast" if($dst eq "000000"); | |
| $dname = $iohash->{NAME} if($dst eq $id); | |
| if($p =~ m/NACK$/) { # HMLAN special | |
| if($dhash) { | |
| delete($dhash->{ackCmdSent}); | |
| delete($dhash->{ackWaiting}); | |
| delete($dhash->{cmdStack}); | |
| $dhash->{STATE} = "MISSING ACK"; | |
| DoTrigger($dname, "MISSING ACK"); | |
| } | |
| return ""; | |
| } | |
| CUL_HM_DumpProtocol("CUL_HM RCV", $iohash, @msgarr); | |
| # Generate an UNKNOWN event with a better name | |
| if(!$shash) { | |
| my $sname = "CUL_HM_$src"; | |
| if($lcm =~ m/1A8.00/) { # Pairing request | |
| # Prefer subType over model to make autocreate easier. | |
| # besides the model names are quite cryptic | |
| 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}) { | |
| $sname = "CUL_HM_".$culHmModel{$model} . "_" . $src; | |
| $sname =~ s/-/_/g; | |
| } | |
| Log 3, "CUL_HM Unknown device $sname, please define it"; | |
| return "UNDEFINED $sname CUL_HM $src $msg"; | |
| } | |
| return ""; | |
| } | |
| my $name = $shash->{NAME}; | |
| my @event; | |
| my $isack; | |
| if($shash->{ackWaiting}) { | |
| delete($shash->{ackWaiting}); | |
| delete($shash->{ackCmdSent}); | |
| RemoveInternalTimer($shash); | |
| $isack = 1; | |
| } | |
| my $st = AttrVal($name, "subType", ""); | |
| my $model = AttrVal($name, "model", ""); | |
| my $tn = TimeNow(); | |
| if($cmd eq "8002") { # Ack | |
| if($shash->{cmdStack}) { # Send next msg from the stack | |
| CUL_HM_SendCmd($shash, shift @{$shash->{cmdStack}}, 1, 1); | |
| delete($shash->{cmdStack}) if(!@{$shash->{cmdStack}}); | |
| $shash->{lastStackAck} = 1; | |
| } elsif($shash->{lastStackAck}) { # Ack of our last stack msg | |
| delete($shash->{lastStackAck}); | |
| } | |
| push @event, ""; | |
| } | |
| if($lcm eq "1A8400" || $lcm eq "1A8000") { #### Pairing-Request | |
| push @event, CUL_HM_Pair($name, $shash, @msgarr); | |
| } elsif($cmd =~ m/^A0[01]{2}$/ && $dst eq $id) {#### Pairing-Request-Convers. | |
| CUL_HM_SendCmd($shash, $msgcnt."8002".$id.$src."00", 1, 0); # Ack | |
| push @event, ""; | |
| } elsif($st eq "switch" || ############################################ | |
| $st eq "dimmer" || | |
| $st eq "blindActuator") { | |
| if($p =~ m/^(0.)(..)(..).0/ | |
| && $cmd ne "A010" | |
| && $cmd ne "A002") { | |
| my $msgType = $1; | |
| my $chn = $2; | |
| if($chn ne "01" && $chn ne "00") { # Switch to the shadow device | |
| my $cSrc = "$src$chn"; | |
| if($modules{CUL_HM}{defptr}{$cSrc}) { | |
| $shash = $modules{CUL_HM}{defptr}{$cSrc}; | |
| $name = $shash->{NAME} | |
| } | |
| } | |
| my $val = hex($3)/2; | |
| $val = ($val == 100 ? "on" : ($val == 0 ? "off" : "$val %")); | |
| my $msg = "unknown"; | |
| $msg = "deviceMsg" if($msgType =~ m/0./); | |
| $msg = "powerOn" if($msgType =~ m/06/ && $chn eq "00"); | |
| push @event, "$msg:$val"; | |
| push @event, "state:$val"; | |
| } | |
| } elsif($st eq "remote") { ############################################ | |
| if($cmd =~ m/^..4./ && $p =~ m/^(..)(..)$/) { | |
| my ($button, $bno) = (hex($1), hex($2)); | |
| my $btn = int((($button&0x3f)+1)/2); | |
| my $state = ($button&1 ? "off" : "on") . ($button & 0x40 ? "Long" : ""); | |
| my $add = ($dst eq $id) ? "" : " (to $dname)"; | |
| push @event, "state:Btn$btn $state$add"; | |
| if($id eq $dst && $cmd ne "8002") { # Send Ack | |
| CUL_HM_SendCmd($shash, "++8002".$id.$src."0101". # Send Ack. | |
| ($state =~ m/on/?"C8":"00")."00", 1, 0); | |
| } | |
| } | |
| } elsif($st eq "motionDetector") { ##################################### | |
| # Code with help of Bassem | |
| my $state; | |
| if($cmd =~ m/^.410/ && $p =~ m/^0601(..)(..)/) { | |
| ($state, undef) = ($1, $2); | |
| push @event, "brightness:".hex($state); | |
| push @event, "state:alive"; | |
| } | |
| if($cmd =~ m/^.441/ && $p =~ m/^01(......)/) { | |
| $state = $1; | |
| push @event, "state:motion"; | |
| # A0D258410143DFABC82AD0601240E | |
| } | |
| CUL_HM_SendCmd($shash, "++8002".$id.$src."0101${state}00",1,0) | |
| if($id eq $dst && $cmd ne "8002"); # Send Ack | |
| } elsif($st eq "smokeDetector") { ##################################### | |
| if($p =~ m/01..C8/) { | |
| push @event, "state:on"; | |
| push @event, "smoke_detect:on"; | |
| } elsif($p =~ m/^01..01$/) { | |
| push @event, "state:all-clear"; # Entwarnung | |
| } elsif($p =~ m/^06010000$/) { | |
| push @event, "state:alive"; | |
| } elsif($p =~ m/^00(..)$/) { | |
| push @event, "test:$1"; | |
| } | |
| } elsif($st eq "threeStateSensor") { ##################################### | |
| push @event, "cover:closed" if($p =~ m/^0601..00$/); | |
| push @event, "cover:open" if($p =~ m/^0601..0E$/); | |
| push @event, "state:alive" if($p =~ m/^0601000E$/); | |
| my $lst = "undef"; | |
| $p =~ m/^....(..)$/; | |
| $lst = $1 if(defined($1)); | |
| if($lst eq "C8") { push @event, "contact:open"; | |
| } elsif($lst eq "64") { push @event, "contact:tilted"; | |
| } elsif($lst eq "00") { push @event, "contact:closed"; | |
| } else { $lst = "00"; # for the ack | |
| } | |
| CUL_HM_SendCmd($shash, "++8002".$id.$src."0101".$lst."00",1,0) # Send Ack | |
| if($id eq $dst); | |
| push @event, "unknownMsg:$p" if(!@event); | |
| } elsif($st eq "THSensor") { ########################################## | |
| if($p =~ m/^(....)(..)$/) { | |
| my ($t, $h) = ($1, $2); | |
| $t = hex($t)/10; | |
| $t -= 3276.8 if($t > 1638.4); | |
| $h = hex($h); | |
| push @event, "state:T: $t H: $h"; | |
| push @event, "temperature:$t"; | |
| push @event, "humidity:$h"; | |
| } elsif($p =~ m/^(....)$/) { | |
| my $t = $1; | |
| $t = hex($t)/10; | |
| $t -= 3276.8 if($t > 1638.4); | |
| push @event, "temperature:$t"; | |
| } | |
| } 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, "KS550 unknown: $p"; | |
| } | |
| } elsif($model eq "HM-CC-TC") { #################################### | |
| if($cmd eq "8670" && $p =~ m/^(....)(..)/) { | |
| my ( $t, $h) = | |
| (hex($1), hex($2)); | |
| my $tsgn = ($t & 0x4000); | |
| $t = ($t & 0x3fff)/10; | |
| $t = sprintf("%0.1f", $t-1638.4) if($tsgn); | |
| push @event, "state:T: $t H: $h"; | |
| push @event, "temperature:$t"; | |
| push @event, "humidity:$h"; | |
| # If we have something to tell: | |
| CUL_HM_SendCmd($shash, "++A112$id$src", 1, 1) if($shash->{cmdStack}); | |
| } | |
| if($cmd eq "A258" && $p =~ m/^(..)(..)/) { | |
| my ( $d1, $vp) = | |
| (hex($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) { | |
| $dhash->{STATE} = "$vp %"; | |
| $dhash->{READINGS}{STATE}{TIME} = $tn; | |
| $dhash->{READINGS}{STATE}{VAL} = "$vp %"; | |
| } | |
| } | |
| if($cmd eq "A410" && $p =~ m/^0602(..)........$/) { | |
| push @event, "desired-temp: " .hex($1)/2; | |
| } | |
| CUL_HM_SendCmd($shash, "++8002$id${src}00",1,0) # Send Ack | |
| if($id eq $dst && $cmd ne "8002"); | |
| } elsif($st eq "winMatic") { #################################### | |
| if($cmd eq "A410" && $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 "00" && $flg eq "10") { push @event, "contact:movement_tilted"; | |
| } elsif($lst eq "00" && $flg eq "20") { push @event, "contact:movement_closed"; | |
| } elsif($lst eq "FF" && $flg eq "10") { push @event, "contact:lock_on"; | |
| } elsif($lst eq "00" && $flg eq "30") { push @event, "contact:open"; | |
| } | |
| CUL_HM_SendCmd($shash, "++8002".$id.$src."0101".$lst."00",1,0) # Send Ack | |
| if($id eq $dst); | |
| } | |
| if($cmd eq "A010" && $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"); | |
| CUL_HM_SendCmd($shash, "++8002".$id.$src."00",1,0) # Send Ack | |
| if($id eq $dst); | |
| } | |
| if($cmd eq "A010" && | |
| $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"; | |
| CUL_HM_SendCmd($shash, "++8002".$id.$src."00",1,0) # Send Ack | |
| if($id eq $dst); | |
| } | |
| } elsif($st eq "KFM100" && $model eq "KFM-Sensor") { ################### | |
| if($p =~ m/.14(.)0200(..)(..)(..)/) { | |
| 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, "state:$cv$unit"; | |
| last; | |
| } | |
| } | |
| } else { | |
| push @event, "state:$v"; | |
| } | |
| } | |
| } | |
| #push @event, "unknownMsg:$p" if(!@event); | |
| my @changed; | |
| for(my $i = 0; $i < int(@event); $i++) { | |
| next if($event[$i] eq ""); | |
| if($shash->{lastMsg} && $shash->{lastMsg} eq $msg) { | |
| Log GetLogLevel($name,4), "CUL_HM $name dup mesg"; | |
| next; | |
| } | |
| my ($vn, $vv) = split(":", $event[$i], 2); | |
| Log GetLogLevel($name,2), "CUL_HM $name $vn:$vv" if($vn eq "unknown"); | |
| 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"; | |
| } | |
| $shash->{READINGS}{$vn}{TIME} = $tn; | |
| $shash->{READINGS}{$vn}{VAL} = $vv; | |
| } | |
| $shash->{CHANGED} = \@changed; | |
| $shash->{lastMsg} = $msg; | |
| return $name; | |
| } | |
| my %culHmGlobalSets = ( | |
| raw => "data ...", | |
| reset => "", | |
| pair => "", | |
| unpair => "", | |
| sign => "[on|off]", | |
| statusRequest => "", | |
| ); | |
| my %culHmSubTypeSets = ( | |
| switch => | |
| { "on-for-timer"=>"sec", on=>"", off=>"", toggle=>"" }, | |
| dimmer => | |
| { "on-for-timer"=>"sec", on=>"", off=>"", toggle=>"", pct=>"" }, | |
| blindActuator=> | |
| { "on-for-timer"=>"sec", on =>"", off=>"", toggle=>"", pct=>"" }, | |
| remote => | |
| { text => "<btn> [on|off] <txt1> <txt2>" }, | |
| winMatic => | |
| { matic => "<btn>", read => "<btn>", config => "<btn> <txt1> <txt2>", create => "<txt>" }, | |
| ); | |
| my %culHmModelSets = ( | |
| "HM-CC-TC"=> | |
| { "day-temp" => "temp", | |
| "night-temp" => "temp", | |
| "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 ...", | |
| }, | |
| ); | |
| ################################### | |
| sub | |
| CUL_HM_Set($@) | |
| { | |
| my ($hash, @a) = @_; | |
| my $ret = ""; | |
| my $tval; | |
| return "no set value specified" if(@a < 2); | |
| my $name = $hash->{NAME}; | |
| my $st = AttrVal($name, "subType", ""); | |
| my $md = AttrVal($name, "model", ""); | |
| my $cmd = $a[1]; | |
| my $dst = $hash->{DEF}; | |
| my $chn = "01"; | |
| my $shash = $hash; | |
| if(length($dst) == 8) { # shadow switch device for multi-channel switch | |
| $chn = substr($dst, 6, 2); | |
| $dst = substr($dst, 0, 6); | |
| $shash = $modules{CUL_HM}{defptr}{$dst}; | |
| } | |
| my $h = $culHmGlobalSets{$cmd}; | |
| $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 $usg = "Unknown argument $cmd, choose one of " . | |
| join(" ",sort keys %culHmGlobalSets); | |
| $usg .= " ". join(" ",sort keys %{$culHmSubTypeSets{$st}}) | |
| if($culHmSubTypeSets{$st}); | |
| $usg .= " ". join(" ",sort keys %{$culHmModelSets{$md}}) | |
| if($culHmModelSets{$md}); | |
| my $pct = join(" ", (0..100)); | |
| $usg =~ s/ pct/ $pct/; | |
| 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($shash->{IODev}); | |
| my $sndcmd; | |
| my $state = $cmd; | |
| if($cmd eq "raw") { ################################################## | |
| return "Usage: set $a[0] $cmd data [data ...]" if(@a < 3); | |
| $sndcmd = $a[2]; | |
| for (my $i = 3; $i < @a; $i++) { | |
| CUL_HM_PushCmdStack($shash, $a[$i]); | |
| } | |
| $state = ""; | |
| } elsif($cmd eq "reset") { ############################################ | |
| $sndcmd = sprintf("++A011%s%s0400", $id,$dst); | |
| } elsif($cmd eq "pair") { ############################################# | |
| my $serialNr = AttrVal($name, "serialNr", undef); | |
| return "serialNr is not set" if(!$serialNr); | |
| $sndcmd = sprintf("++A401%s000000010A%s", $id, unpack("H*",$serialNr)); | |
| $shash->{hmPairSerial} = $serialNr; | |
| } elsif($cmd eq "unpair") { ########################################### | |
| CUL_HM_pushConfig($shash, $id, $dst, 0, 0, "02010A000B000C00"); | |
| $sndcmd = shift @{$shash->{cmdStack}}; | |
| } elsif($cmd eq "sign") { ############################################ | |
| CUL_HM_pushConfig($shash, $id, $dst, $chn, $chn, | |
| "08" . ($a[2] eq "on" ? "01":"02")); | |
| $sndcmd = shift @{$shash->{cmdStack}}; | |
| } elsif($cmd eq "statusRequest") { #################################### | |
| $sndcmd = sprintf("++A001%s%s%s0E", $id,$dst, $chn); | |
| } elsif($cmd eq "on") { ############################################### | |
| $sndcmd = sprintf("++A011%s%s02%sC80000", $id,$dst, $chn); | |
| } elsif($cmd eq "off") { ############################################## | |
| $sndcmd = sprintf("++A011%s%s02%s000000", $id,$dst,$chn); | |
| } elsif($cmd eq "on-for-timer") { ##################################### | |
| ($tval,$ret) = CUL_HM_encodeTime16($a[2]); | |
| $sndcmd = sprintf("++A011%s%s02%sC80000%s", $id,$dst, $chn, $tval); | |
| } elsif($cmd eq "toggle") { ########################################### | |
| $shash->{toggleIndex} = 1 if(!$shash->{toggleIndex}); | |
| $shash->{toggleIndex} = (($shash->{toggleIndex}+1) % 128); | |
| $sndcmd = sprintf("++A03E%s%s%s40%s%02X", $id, $dst, | |
| $dst, $chn, $shash->{toggleIndex}); | |
| } elsif($cmd eq "pct") { ############################################## | |
| $a[1] = 100 if ($a[1] > 100); | |
| $sndcmd = sprintf("++A011%s%s02%s%02X0000", $id, $dst, $chn, $a[1]*2); | |
| if(@a > 2) { | |
| ($tval,$ret) = CUL_HM_encodeTime16($a[2]); | |
| $sndcmd .= $tval; | |
| } | |
| } elsif($cmd eq "text") { ############################################# | |
| $state = ""; | |
| return "$a[2] is not a button number" if($a[2] !~ m/^\d$/); | |
| 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, 1, $l1); | |
| return "Set your remote in learning mode to transmit the data"; | |
| } elsif($cmd =~ m/^(day|night)-temp$/) { ############################### | |
| my $temp = CUL_HM_convTemp($a[2]); | |
| return $temp if(length($temp) > 2); | |
| CUL_HM_pushConfig($hash, $id, $dst, 2, 5, | |
| $st eq "day-temp" ? "03$temp" : "04$temp"); | |
| return; | |
| } elsif($cmd =~ m/^tempList(...)/) { ################################## | |
| my %day2off = ( "Sat"=>"5 0B", "Sun"=>"5 3B", "Mon"=>"5 6B", | |
| "Tue"=>"5 9B", "Thu"=>"5 CB", "Wed"=>"6 01", | |
| "Fri"=>"6 31"); | |
| my ($list,$addr) = split(" ", $day2off{$1}); | |
| 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 = ""; | |
| 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; | |
| } | |
| CUL_HM_pushConfig($hash, $id, $dst, 2, $list, $data); | |
| return; | |
| } elsif($cmd eq "matic") { ##################################### | |
| # Trigger pre-programmed action in the winmatic. These actions must be | |
| # programmed via the original software. | |
| $sndcmd = sprintf("++B03E%s%s%s40%02X%s", $id, $dst, $id, $a[2], $chn); | |
| } elsif($cmd eq "create") { ################################### | |
| $sndcmd = sprintf("++B001%s%s0101%s%02X%s", $id, $dst, $id, $a[2], $chn); | |
| CUL_HM_SendCmd($hash, $sndcmd, 1, 1); | |
| sleep(2); | |
| $sndcmd = sprintf("++A001%s%s0104%s%02X%s", $id, $dst, $id, $a[2], $chn); | |
| } elsif($cmd eq "read") { ################################### | |
| $sndcmd = sprintf("++B001%s%s0104%s%02X03", $id, $dst, $id, $a[2]); | |
| } elsif($cmd eq "config") { ##################################### | |
| if ($a[3] eq "tilt") { | |
| $sndcmd = sprintf("++B001%s%s0105%s%02X03", $id, $dst, $id, $a[2]); | |
| CUL_HM_SendCmd ($hash, $sndcmd, 2, 2); | |
| $sndcmd = sprintf("++A001%s%s01080B220D838B228D83", $id, $dst); | |
| sleep (2); | |
| CUL_HM_SendCmd ($hash, $sndcmd, 2, 2); | |
| sleep(2); | |
| $sndcmd = sprintf("++A001%s%s0106", $id, $dst); | |
| } elsif ($a[3] eq "close") { | |
| $sndcmd = sprintf("++B001%s%s0105%s%02X03", $id, $dst, $id, $a[2]); | |
| CUL_HM_SendCmd($hash, $sndcmd, 2, 2); | |
| $sndcmd = sprintf("++A001%s%s01080B550D838B558D83", $id, $dst); | |
| sleep(2); | |
| CUL_HM_SendCmd($hash, $sndcmd, 2, 2); | |
| sleep(2); | |
| $sndcmd = sprintf("++A001%s%s0106", $id, $dst); | |
| } elsif ($a[3] eq "closed") { | |
| $sndcmd = sprintf("++B001%s%s0105%s%02X03", $id, $dst, $id, $a[2]); | |
| CUL_HM_SendCmd($hash, $sndcmd , 2, 2); | |
| $sndcmd = sprintf("++A001%s%s01080F008F00", $id, $dst); | |
| sleep(2); | |
| CUL_HM_SendCmd($hash, $sndcmd , 2, 2); | |
| sleep(2); | |
| $sndcmd = sprintf("++A001%s%s0106", $id, $dst); | |
| } elsif ($a[3] eq "bolt") { | |
| $sndcmd = sprintf("++B001%s%s0105%s%02X03", $id, $dst, $id, $a[2]); | |
| CUL_HM_SendCmd($hash, $sndcmd , 2, 2); | |
| $sndcmd = sprintf("++A001%s%s01080FFF8FFF", $id, $dst); | |
| sleep(2); | |
| CUL_HM_SendCmd($hash, $sndcmd , 2, 2); | |
| sleep(2); | |
| $sndcmd = sprintf("++A001%s%s0106", $id, $dst); | |
| } elsif ($a[3] eq "delete") { | |
| $sndcmd = sprintf("++B001%s%s0102%s%02X%s", $id, $dst, $id, $a[2], $chn); | |
| } elsif ($a[3] eq "speedclose") { | |
| $sndcmd = sprintf("++B001%s%s0105%s%02X03", $id, $dst, $id, $a[2]); | |
| CUL_HM_SendCmd($hash, $sndcmd , 2, 2); | |
| $sndcmd = $a[4]*2; | |
| $sndcmd = sprintf("++A001%s%s010823%02XA3%02X", $id, $dst, $sndcmd, $sndcmd); | |
| sleep(2); | |
| CUL_HM_SendCmd($hash, $sndcmd , 2, 2); | |
| sleep(2); | |
| $sndcmd = sprintf("++A001%s%s0106", $id, $dst); | |
| } elsif ($a[3] eq "speedtilt") { | |
| $sndcmd = sprintf("++B001%s%s0105%s%02X03", $id, $dst, $id, $a[2]); | |
| CUL_HM_SendCmd($hash, $sndcmd , 2, 2); | |
| $sndcmd = $a[4]*2; | |
| $sndcmd = sprintf("++A001%s%s010822%02XA2%02X", $id, $dst, $sndcmd, $sndcmd); | |
| sleep(2); | |
| CUL_HM_SendCmd($hash, $sndcmd , 2, 2); | |
| sleep(2); | |
| $sndcmd = sprintf("++A001%s%s0106", $id, $dst); | |
| } | |
| } | |
| if($state) { | |
| $hash->{STATE} = $state; | |
| $hash->{cmdSent} = $state; | |
| } | |
| CUL_HM_SendCmd($shash, $sndcmd, 0, 1) if($sndcmd); | |
| return $ret; | |
| } | |
| ################################### | |
| # A pairing between rrrrrr (remote) and ssssss (switch) looks like the | |
| # following (nn and ff is the index of the on and off button): | |
| # 1A CF 84 00 rrrrrr 000000 10 0060 serialnumberxxxxxxxx 40 04 nnff | |
| # 1A 66 A0 00 ssssss rrrrrr 19 0011 serialnumberxxxxxxxx 10 01 0100 | |
| # 0A D0 80 02 rrrrrr ssssss 00 | |
| # 10 D0 A0 01 ssssss rrrrrr nn05 ssssss 0104 | |
| # 0A D0 80 02 rrrrrr ssssss 00 | |
| # 0E D0 A0 01 ssssss rrrrrr nn07 020201 | |
| # 0A D0 80 02 rrrrrr ssssss 00 | |
| # 0B D0 A0 01 ssssss rrrrrr nn06 | |
| # 0A D0 80 02 rrrrrr ssssss 00 | |
| # 10 D0 A0 01 ssssss rrrrrr ff05 ssssss 0104 | |
| # 0A D0 80 02 rrrrrr ssssss 00 | |
| # 0E D0 A0 01 ssssss rrrrrr ff07 020201 | |
| # 0A D0 80 02 rrrrrr ssssss 02 | |
| # 0B D0 A0 01 ssssss rrrrrr ff06 | |
| # 0A D0 80 02 rrrrrr ssssss 00 | |
| sub | |
| CUL_HM_Pair(@) | |
| { | |
| my ($name, $hash, $len,$msgcnt,$cmd,$src,$dst,$p) = @_; | |
| my $iohash = $hash->{IODev}; | |
| my $id = CUL_HM_Id($iohash); | |
| my $l4 = GetLogLevel($name,4); | |
| my ($idstr, $s) = ($id, 0xA); | |
| $idstr =~ s/(..)/sprintf("%02X%s",$s++,$1)/ge; | |
| my $stc = substr($p, 26, 2); # subTypeCode | |
| my $model = substr($p, 2, 4); | |
| my $dp = $culHmDevProps{$stc}; | |
| $attr{$name}{model} = $culHmModel{$model}? $culHmModel{$model} :"unknown"; | |
| $attr{$name}{subType} = $dp ? $dp->{st} : "unknown"; | |
| $attr{$name}{hmClass} = $dp ? $dp->{cl} : "unknown"; | |
| $attr{$name}{serialNr} = pack('H*', substr($p, 6, 20)); | |
| $attr{$name}{firmware} = sprintf("%d.%d", hex(substr($p,0,1)),hex(substr($p,1,1))); | |
| $attr{$name}{devInfo} = substr($p,28); | |
| my $stn = $attr{$name}{subType}; # subTypeName | |
| my $stt = $stn eq "unknown" ? "subType unknown" : "is a $stn"; | |
| Log GetLogLevel($name,2), | |
| "CUL_HM pair: $name $stt, model $attr{$name}{model} ". | |
| "serialNr $attr{$name}{serialNr}"; | |
| # Create shadow device for multi-channel | |
| if($stn eq "switch" && | |
| $attr{$name}{devInfo} =~ m,(..)(..)(..), ) { | |
| my ($b1, $b2, $b3) = (hex($1), hex($2), $3); | |
| for(my $i = $b2+1; $i<=$b1; $i++) { | |
| my $nSrc = sprintf("%s%02X", $src, $i); | |
| if(!defined($modules{CUL_HM}{defptr}{$nSrc})) { | |
| delete($defs{"global"}{INTRIGGER}); # Hack | |
| DoTrigger("global", "UNDEFINED ${name}_CHN_$i CUL_HM $nSrc"); | |
| } | |
| } | |
| } | |
| # Abort if we are not authorized | |
| if($dst eq "000000") { | |
| if(!$iohash->{hmPair}) { | |
| Log GetLogLevel($name,2), | |
| $iohash->{NAME}. " pairing (hmPairForSec) not enabled"; | |
| return ""; | |
| } | |
| } elsif($dst ne $id) { | |
| return "" ; | |
| } elsif($iohash->{hmPairSerial}) { | |
| delete($iohash->{hmPairSerial}); | |
| } | |
| # delete($hash->{cmdStack}); # Why? Disables 4Dis text commands | |
| if($stn ne "remote") { | |
| CUL_HM_pushConfig($hash, $id, $src, 0, 0, "0201$idstr"); | |
| CUL_HM_SendCmd($hash, shift @{$hash->{cmdStack}}, 1, 1); | |
| } else { | |
| # remotes are AES per default, so the method above does not work. the | |
| # following works at least for the 4Dis, not for the HM-RC-4 | |
| my ($fwvers, $mystc, $mymodel, $mybtn, $myserNr) = | |
| ("10", "10", "0011", "010100", unpack('H*',"FHEM$id")); | |
| CUL_HM_SendCmd($hash, | |
| $msgcnt."A000".$id.$src.$fwvers.$mymodel.$myserNr.$mystc.$mybtn, 1, 1); | |
| CUL_HM_SendCmd($hash, "++A001$id${src}0105${src}0104", 1, 1); | |
| CUL_HM_PushCmdStack($hash, "++A001$id${src}01080201$idstr"); | |
| CUL_HM_PushCmdStack($hash, "++A001$id${src}0106"); | |
| } | |
| return ""; | |
| } | |
| ################################### | |
| sub | |
| CUL_HM_SendCmd($$$$) | |
| { | |
| my ($hash, $cmd, $sleep, $waitforack) = @_; | |
| my $io = $hash->{IODev}; | |
| select(undef, undef, undef, 0.1*$sleep) if($sleep); | |
| $cmd =~ m/^(..)(.*)$/; | |
| my ($mn, $cmd2) = ($1, $2); | |
| if($mn eq "++") { | |
| $mn = $io->{HM_CMDNR} ? ($io->{HM_CMDNR} +1) : 1; | |
| $mn = 0 if($mn > 255); | |
| } else { | |
| $mn = hex($mn); | |
| } | |
| $io->{HM_CMDNR} = $mn; | |
| $cmd = sprintf("As%02X%02X%s", length($cmd2)/2+1, $mn, $cmd2); | |
| IOWrite($hash, "", $cmd); | |
| if($waitforack) { | |
| if($hash->{IODev} && $hash->{IODev}{TYPE} ne "HMLAN") { | |
| $hash->{ackWaiting} = $cmd; | |
| $hash->{ackCmdSent} = 1; | |
| InternalTimer(gettimeofday()+0.4, "CUL_HM_Resend", $hash, 0) | |
| } | |
| } | |
| $cmd =~ m/As(..)(..)(....)(......)(......)(.*)/; | |
| CUL_HM_DumpProtocol("CUL_HM SND", $io, ($1,$2,$3,$4,$5,$6)); | |
| } | |
| ################################### | |
| sub | |
| CUL_HM_PushCmdStack($$) | |
| { | |
| my ($hash, $cmd) = @_; | |
| my @arr = (); | |
| $hash->{cmdStack} = \@arr if(!$hash->{cmdStack}); | |
| #Log 1, "PushStack: $cmd"; | |
| push(@{$hash->{cmdStack}}, $cmd); | |
| } | |
| ################################### | |
| sub | |
| CUL_HM_Resend($) | |
| { | |
| my $hash = shift; | |
| my $name = $hash->{NAME}; | |
| return if(!$hash->{ackCmdSent}); # Double timer? | |
| if($hash->{ackCmdSent} == 3) { | |
| delete($hash->{ackCmdSent}); | |
| delete($hash->{ackWaiting}); | |
| delete($hash->{cmdStack}); | |
| $hash->{STATE} = "MISSING ACK"; | |
| DoTrigger($name, "MISSING ACK"); | |
| return; | |
| } | |
| IOWrite($hash, "", $hash->{ackWaiting}); | |
| $hash->{ackCmdSent}++; | |
| DoTrigger($name, "resend nr ".$hash->{ackCmdSent}); | |
| InternalTimer(gettimeofday()+0.4, "CUL_HM_Resend", $hash, 0); | |
| } | |
| ################################### | |
| sub | |
| CUL_HM_Id($) | |
| { | |
| my ($io) = @_; | |
| my $fhtid = defined($io->{FHTID}) ? $io->{FHTID} : "0000"; | |
| return AttrVal($io->{NAME}, "hmId", "F1$fhtid"); | |
| } | |
| ############################# | |
| my %culHmBits = ( | |
| "8000" => { 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", } }, | |
| "8002;p01=01" => { txt => "ACK_STATUS", params => { | |
| CHANNEL => "02,2", | |
| STATUS => "04,2", | |
| RSSI => "08,2", } }, | |
| "8002;p01=00" => { txt => "ACK" }, | |
| "8002;p01=80" => { txt => "NACK" }, | |
| "8002;p01=84" => { txt => "NACK_TARGET_INVALID" }, | |
| "A001;p11=01" => { txt => "CONFIG_PEER_ADD", params => { | |
| CHANNEL => "00,2", | |
| PEER_ADDRESS => "04,6", | |
| PEER_CHANNEL_A => "10,2", | |
| PEER_CHANNEL_B => "12,2", } }, | |
| "A001;p11=03" => { txt => "CONFIG_PEER_LIST_REQ", params => { | |
| CHANNEL => "0,2", } }, | |
| "A001;p11=04" => { txt => "CONFIG_PARAM_REQ", params => { | |
| CHANNEL => "00,2", | |
| PEER_ADDRESS => "04,6", | |
| PEER_CHANNEL => "10,2", | |
| PARAM_LIST => "12,2", } }, | |
| "A001;p11=05" => { txt => "CONFIG_START", params => { | |
| CHANNEL => "00,2", | |
| PEER_ADDRESS => "04,6", | |
| PEER_CHANNEL => "10,2", | |
| PARAM_LIST => "12,2", } }, | |
| "A001;p11=06" => { txt => "CONFIG_END", params => { | |
| CHANNEL => "0,2", } }, | |
| "A001;p11=08" => { txt => "CONFIG_WRITE_INDEX", params => { | |
| CHANNEL => "0,2", | |
| DATA => '4,,$val =~ s/(..)(..)/ $1:$2/g', } }, | |
| "A001;p11=0E" => { txt => "CONFIG_STATUS_REQUEST", params => { | |
| CHANNEL => "0,2", } }, | |
| "A002" => { txt => "Request AES", params => { | |
| DATA => "0," } }, | |
| "A003" => { txt => "AES reply", params => { | |
| DATA => "0," } }, | |
| "A010;p01=01" => { txt => "INFO_PEER_LIST", params => { | |
| PEER_ADDR1 => "02,6", PEER_CH1 => "08,2", | |
| PEER_ADDR2 => "10,6", PEER_CH2 => "16,2", | |
| PEER_ADDR3 => "18,6", PEER_CH3 => "24,2", | |
| PEER_ADDR4 => "26,6", PEER_CH4 => "32,2", } }, | |
| "A010;p01=02" => { txt => "INFO_PARAM_RESPONSE_PAIRS", params => { | |
| DATA => "2,", } }, | |
| "A010;p01=03" => { txt => "INFO_PARAM_RESPONSE_SEQ", params => { | |
| OFFSET => "2,2", | |
| DATA => "4,", } }, | |
| "A011;p02=0400" => { txt => "RESET" }, | |
| "A011;p01=02" => { txt => "SET" , params => { | |
| CHANNEL => "02,2", | |
| VALUE => "04,2", | |
| RAMPTIME => "06,2", | |
| ONTIME => "08,2", | |
| DURATION => '10,4,$val=CUL_HM_decodeTime16($val)', } }, | |
| "A03E" => { txt => "SWITCH", params => { | |
| DST => "00,6", | |
| UNKNOWN => "06,2", | |
| CHANNEL => "08,2", | |
| COUNTER => "10,2", } }, | |
| "A401;p02=010A" => { txt => "PAIR_SERIAL", params => { | |
| SERIALNO => '04,,$val=pack("H*",$val)', } }, | |
| "A410;p01=06" => { txt => "INFO_ACTUATOR_STATUS", params => { | |
| CHANNEL => "2,2", | |
| STATUS => '4,2', | |
| UNKNOWN => "6,2", | |
| RSSI => "8,2" } }, | |
| "A440" => { txt => "REMOTE", params => { | |
| BUTTON => '00,02,$val=(hex($val)&0x3F)', | |
| LONG => '00,02,$val=(hex($val)&0x40)?1:0', | |
| LOWBAT => '00,02,$val=(hex($val)&0x80)?1:0', | |
| COUNTER => "02,02", } }, | |
| ); | |
| sub | |
| CUL_HM_DumpProtocol($$@) | |
| { | |
| my ($prefix, $iohash, $len,$cnt,$cmd,$src,$dst,$p) = @_; | |
| my $iname = $iohash->{NAME}; | |
| my $ev = AttrVal($iname, "hmProtocolEvents", 0); | |
| my $l4 = GetLogLevel($iname, 4); | |
| return if(!$ev && $attr{global}{verbose} < $l4); | |
| my $p01 = substr($p,0,2); | |
| my $p02 = substr($p,0,4); | |
| my $p11 = (length($p) > 2 ? substr($p,2,2) : ""); | |
| $cmd = "0A$1" if($cmd =~ m/0B(..)/); | |
| $cmd = "A4$1" if($cmd =~ m/84(..)/); | |
| $cmd = "8000" if($cmd =~ m/A40./ && $len eq "1A"); | |
| my $ps; | |
| $ps = $culHmBits{"$cmd;p11=$p11"} if(!$ps); | |
| $ps = $culHmBits{"$cmd;p01=$p01"} if(!$ps); | |
| $ps = $culHmBits{"$cmd;p02=$p02"} if(!$ps); | |
| $ps = $culHmBits{"$cmd"} 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($expr); | |
| $txt .= " $k:$val"; | |
| } | |
| } | |
| $txt = " ($txt)" if($txt); | |
| } | |
| my $msg = "$prefix L:$len N:$cnt CMD:$cmd SRC:$src DST:$dst $p$txt"; | |
| Log $l4, $msg; | |
| DoTrigger($iname, $msg) if($ev); | |
| } | |
| ############################# | |
| 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); | |
| return ($ret, "Timeout $v rounded to $v2") if($v != $v2); | |
| return ($ret, ""); | |
| } | |
| sub | |
| CUL_HM_convTemp($) | |
| { | |
| my ($in) = @_; | |
| return "$in is not a number" if($in !~ m/^\d+$/ && $in !~ m/\d+\.\d+$/); | |
| return "$in is out of bounds (6 .. 30)" if($in < 6 || $in > 30); | |
| return sprintf("%02X", $in*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($$$$$$) | |
| { | |
| my ($hash,$src,$dst,$chn,$list,$content) = @_; | |
| CUL_HM_PushCmdStack($hash, | |
| sprintf("++A001%s%s%02X0500000000%02X",$src,$dst,$chn,$list)); | |
| my $tl = length($content); | |
| for(my $l = 0; $l < $tl; $l+=28) { | |
| my $ml = $tl-$l < 28 ? $tl-$l : 28; | |
| CUL_HM_PushCmdStack($hash, | |
| sprintf("++A001%s%s%02X08%s", $src,$dst,$chn, substr($content,$l,$ml))); | |
| } | |
| CUL_HM_PushCmdStack($hash, | |
| sprintf("++A001%s%s%02X06",$src,$dst,$chn)); | |
| } | |
| 1; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment