fhem-mirror/FHEM/10_CUL_HM.pm
rudolfkoenig 233ad19d91 Humidity value changed
git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@772 2b470e98-0d58-463d-a4d8-8e2adae1ed80
2010-12-18 17:14:42 +00:00

620 lines
17 KiB
Perl
Executable File

##############################################
# 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($@);
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",
"0008" => "HM-RC-4",
"0009" => "HM-LC-SW2-FM",
"000A" => "HM-LC-SW2-SM",
"000B" => "HM-WDC7000",
"000D" => "ASH550",
"000E" => "ASH550I",
"000F" => "S550IA",
"0011" => "HM-LC-SW1-PL",
"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",
"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",
"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",
"003A" => "HM-CC-VD",
"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",
"0041" => "HM-WDC7000",
"0042" => "HM-SEC-SD",
"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",
"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",
"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",
"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";
}
#############################
sub
CUL_HM_Define($$)
{
my ($hash, $def) = @_;
my @a = split("[ \t][ \t]*", $def);
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}$/i);
$modules{CUL_HM}{defptr}{uc($a[2])} = $hash;
$hash->{STATE} = "???";
AssignIoPort($hash);
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,$channel,$msgtype,$src,$dst,$p) = @msgarr;
Log 1, "CUL_HM L:$len N:$msgcnt C:$channel T:$msgtype SRC:$src DST:$dst $p";
my $shash = $modules{CUL_HM}{defptr}{$src};
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(!$shash) {
my $sname = "CUL_HM_$src";
if("$channel$msgtype" eq "8400" && $len eq "1A") {
my $model = substr($p, 2, 4);
if($culHmModel{$model}) {
$sname = $culHmModel{$model} . "_" . $src;
$sname =~ s/-/_/g;
}
}
Log 3, "CUL_HM Unknown device $sname, please define it";
return "UNDEFINED $sname CUL_HM $src $msg";
}
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 $cm = "$channel$msgtype";
my $lcm = "$len$channel$msgtype";
if($lcm eq "1A8400") { #### Pairing-Request
push @event, CUL_HM_Pair($name, $shash, @msgarr);
} elsif($cm =~ m/A00[01]/ && #### Pairing-Request-Conversation
$dst eq $id) {
CUL_HM_SendCmd($shash, $msgcnt."8002".$id.$src."00", 1, 0); # Ack
push @event, "";
} elsif($lcm eq "11A002") {
push @event, "signRequest:$p";
} elsif($lcm eq "19A003") {
push @event, "signAnswer:$p";
} elsif(!$st) { # Will trigger unknown
;
} elsif("$channel$msgtype" eq "8002") { #### Ack
if($shash->{cmdStack}) {
CUL_HM_SendCmd($shash, shift @{$shash->{cmdStack}}, 1, 1);
delete($shash->{cmdStack}) if(!@{$shash->{cmdStack}});
}
push @event, "";
} elsif($st eq "switch" || ############################################
$st eq "dimmer" ||
$st eq "blindActuator") {
if($p =~ m/^(0.0.)(..)00/) {
my $lt = $1;
my $val = hex($2)/2;
$val = ($val == 100 ? "on" : ($val == 0 ? "off" : "$val %"));
my $msg = "unknown";
$msg = "ackedCmd" if($lt =~ m/0.01/);
$msg = "powerOn" if($lt =~ m/0600/);
push @event, "$msg:$val";
push @event, "state:$val" if(!$isack);
}
} elsif($st eq "remote") { ############################################
if("$channel$msgtype" =~ m/A.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) {
CUL_HM_SendCmd($shash, "++8002".$id.$src."0101". # Send Ack.
($state =~ m/on/?"C8":"00")."0028", 1, 0);
# Sign-Test
# CUL_HM_SendCmd($shash, "01A002".$id.$src."0400000000000000",1,0);
}
} elsif($p =~ m/0600/) {
push @event, "powerOn:$p";
}
} elsif($st eq "smokeDetector") { #####################################
if($p eq "0106C8") {
push @event, "state:on";
push @event, "smoke_detect:on";
} elsif($p =~ m/^00..$/) {
push @event, "test:$p";
}
} 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($st eq "KFM" && $model eq "KFM-Sensor") {
if($p =~ m/814(.)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);
push @event, "rawValue:$v";
}
}
push @event, "unknownMsg:$p" if(!@event);
my $tn = TimeNow();
for(my $i = 0; $i < int(@event); $i++) {
next if($event[$i] eq "");
if($shash->{lastMsgNr} && $shash->{lastMsgNr} eq $msgcnt) {
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") {
$shash->{STATE} = $vv;
$shash->{CHANGED}[$i] = $vv;
} else {
$shash->{CHANGED}[$i] = "$vn: $vv";
}
$shash->{READINGS}{$vn}{TIME} = $tn;
$shash->{READINGS}{$vn}{VAL} = $vv;
}
$shash->{lastMsgNr} = $msgcnt;
return $name;
}
###################################
sub
CUL_HM_Set($@)
{
my ($hash, @a) = @_;
return "no set value specified" if(@a < 2);
my $name = $hash->{NAME};
my $st = AttrVal($name, "subType", undef);
my $cmd = $a[1];
my $id = CUL_HM_Id($hash->{IODev});
my $sndcmd;
my $state;
if($cmd eq "raw") { ##################################################
return "Usage: set $a[0] $cmd rowdata" if(@a != 3);
CUL_HM_SendCmd($hash, $a[2], 0, 1);
return "";
} elsif($cmd eq "rawStack") { ########################################
return "Usage: set $a[0] $cmd rowdata" if(@a != 3);
CUL_HM_PushCmdStack($hash, $a[2]);
return "";
} elsif($st eq "switch") { ############################################
my %scmd = (on => "02", off => "01", onLong => "42", offLong => "41") ;
if($scmd{$cmd}) {
$state = $cmd;
$sndcmd = sprintf("++A440%s%s%s%02d", $id, $hash->{DEF},
$scmd{$cmd}, $hash->{"${cmd}MsgNr"}++);
} else {
return "Unknown argument $cmd, choose one of " .join(" ",sort keys %scmd);
}
} elsif($st eq "dimmer" || ############################################
$st eq "blindActuator") {
if($cmd =~ m/^\d+/ && $cmd >= 0 && $cmd <= 100) {
$state = "$cmd %";
$sndcmd = sprintf("++A011%s%s0201%02X", $id, $hash->{DEF}, $cmd*2);
} else {
my @scmd = (0..100);
return "Unknown argument $cmd, choose one of " .join(" ",@scmd);
}
} elsif($st eq "remote") {#############################################
my %scmd = (text => "01");
if($cmd eq "text") {
return "Usage: set $a[0] $cmd <btn> [on|off] <txt1> <txt2>" if(@a != 6);
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);
CUL_HM_PushCmdStack($hash,
sprintf("++A001%s%s%02d050000000001", $id, $hash->{DEF}, $bn));
my ($l1, $l2, $s, $tl);
$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;
$tl = length($l1);
for(my $l = 0; $l < $tl; $l+=28) {
my $ml = $tl-$l < 28 ? $tl-$l : 28;
CUL_HM_PushCmdStack($hash, sprintf("++A001%s%s%02d08%s",
$id, $hash->{DEF}, $bn, substr($l1,$l,$ml)));
}
CUL_HM_PushCmdStack($hash,
sprintf("++A001%s%s%02d06", $id, $hash->{DEF}, $bn));
return;
} else {
return "Unknown argument $cmd, choose one " . join(" ", sort keys %scmd);
}
}
return "$name: Unknown device subtype or command" if(!$sndcmd);
CUL_HM_SendCmd($hash, $sndcmd, 0, 1);
if($state) {
$hash->{STATE} = $state;
$hash->{READINGS}{state}{TIME} = TimeNow();
$hash->{READINGS}{state}{VAL} = $state;
}
return "";
}
###################################
# 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,$channel,$msgtype,$src,$dst,$p) = @_;
my $iohash = $hash->{IODev};
my $id = CUL_HM_Id($iohash);
my $l4 = GetLogLevel($name,4);
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));
my $isSender = (AttrVal($name,"hmClass","") eq "sender");
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}";
# Abort if we are not authorized
if($dst eq "000000") {
if(!$iohash->{hmPair}) {
Log 4, $iohash->{NAME}. " pairing (hmPairForSec) not enabled";
return "";
}
} elsif($dst ne $id) {
return "" ;
}
if($stn eq "unknown") {
Log GetLogLevel($name,1), "CUL_HM unknown subType $stc, cannot pair";
return "";
}
$hash->{pairButtons} = substr($p, 30, 4);
if($hash->{pairButtons} eq "0000") { # Sender pair mode, before btn is pressed
if($hash->{cmdStack}) {
CUL_HM_SendCmd($hash, shift @{$hash->{cmdStack}}, 1, 1);
delete($hash->{cmdStack}) if(!@{$hash->{cmdStack}});
}
return "";
}
my ($mystc, $mymodel, $mybtn, $myserNr);
if($isSender) {
$mymodel = "0011"; # Emulate a HM-LC-SW1-PL
$mystc = "10"; # switch
$mybtn = "010100";# No buttons (?)
} else {
$mymodel = "0060"; # Emulate a HM-PB-4DIS-WM
$mystc = "40"; # remote
$mybtn = "940201";# Buttons 02 (on) & 01 (off)
}
$myserNr = unpack('H*', "FHEM$id");
CUL_HM_SendCmd($hash,
$msgcnt."A000".$id.$src."19".$mymodel.$myserNr.$mystc.$mybtn, 1, 0);
# switch emulation (actor/receiver is ack only);
if($isSender) {
$hash->{pairButtons} =~ m/(..)(..)/;
my ($b1, $b2, $cmd) = ($1, $2, "");
delete($hash->{cmdStack});
CUL_HM_SendCmd($hash, "++A001$id$src${b1}05$src${b1}04", 1, 1);
CUL_HM_PushCmdStack($hash, "++A001$id$src${b1}07020201");
CUL_HM_PushCmdStack($hash, "++A001$id$src${b1}06");
CUL_HM_PushCmdStack($hash, "++A001$id$src${b2}05$src${b1}04");
CUL_HM_PushCmdStack($hash, "++A001$id$src${b2}07020201");
CUL_HM_PushCmdStack($hash, "++A001$id$src${b2}06");
}
return "";
}
###################################
sub
CUL_HM_SendCmd($$$$)
{
my ($hash, $cmd, $sleep, $waitforack) = @_;
my $io = $hash->{IODev};
my $l4 = GetLogLevel($hash->{NAME},4);
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);
Log $l4, "CUL_HM SEND $cmd";
IOWrite($hash, "", $cmd);
if($waitforack) {
$hash->{ackWaiting} = $cmd;
$hash->{ackCmdSent} = 1;
InternalTimer(gettimeofday()+0.4, "CUL_HM_Resend", $hash, 0);
}
}
###################################
sub
CUL_HM_PushCmdStack($$)
{
my ($hash, $cmd) = @_;
my $l4 = GetLogLevel($hash->{NAME},4);
my @arr = ();
$hash->{cmdStack} = \@arr if(!$hash->{cmdStack});
Log $l4, $cmd;
push(@{$hash->{cmdStack}}, $cmd);
}
###################################
sub
CUL_HM_Resend($)
{
my $hash = shift;
my $name = $hash->{NAME};
if($hash->{ackCmdSent} == 3) {
delete($hash->{ackCmdSent});
delete($hash->{ackWaiting});
$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) = @_;
return "123456" if(!$io || !$io->{FHTID});
return "F1" . $io->{FHTID};
}
1;