diff --git a/fhem/FHEM/00_HMLAN.pm b/fhem/FHEM/00_HMLAN.pm index 742611c16..1c44396cd 100755 --- a/fhem/FHEM/00_HMLAN.pm +++ b/fhem/FHEM/00_HMLAN.pm @@ -1,394 +1,422 @@ -############################################## -# $Id$ -package main; - -use strict; -use warnings; -use Time::HiRes qw(gettimeofday); - -sub HMLAN_Parse($$); -sub HMLAN_Read($); -sub HMLAN_Write($$$); -sub HMLAN_ReadAnswer($$$); -sub HMLAN_uptime($); -sub HMLAN_secSince2000(); - -sub HMLAN_SimpleWrite(@); - -my %sets = ( - "hmPairForSec" => "HomeMatic", - "hmPairSerial" => "HomeMatic", -); - -sub -HMLAN_Initialize($) -{ - my ($hash) = @_; - - require "$attr{global}{modpath}/FHEM/DevIo.pm"; - -# Provider - $hash->{ReadFn} = "HMLAN_Read"; - $hash->{WriteFn} = "HMLAN_Write"; - $hash->{ReadyFn} = "HMLAN_Ready"; - $hash->{SetFn} = "HMLAN_Set"; - $hash->{Clients} = ":CUL_HM:"; - my %mc = ( - "1:CUL_HM" => "^A......................", - ); - $hash->{MatchList} = \%mc; - -# Normal devices - $hash->{DefFn} = "HMLAN_Define"; - $hash->{UndefFn} = "HMLAN_Undef"; - $hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 " . - "loglevel:0,1,2,3,4,5,6 addvaltrigger " . - "hmId hmKey " . - "hmProtocolEvents:0_off,1_dump,2_dumpFull,3_dumpTrigger"; - -} - -##################################### -sub -HMLAN_Define($$) -{ - my ($hash, $def) = @_; - my @a = split("[ \t][ \t]*", $def); - - if(@a != 3) { - my $msg = "wrong syntax: define HMLAN ip[:port]"; - Log 2, $msg; - return $msg; - } - DevIo_CloseDev($hash); - - my $name = $a[0]; - my $dev = $a[2]; - $dev .= ":1000" if($dev !~ m/:/ && $dev ne "none" && $dev !~ m/\@/); - $attr{$name}{hmId} = sprintf("%06X", time() % 0xffffff); # Will be overwritten - - if($dev eq "none") { - Log 1, "$name device is none, commands will be echoed only"; - $attr{$name}{dummy} = 1; - return undef; - } - $hash->{DeviceName} = $dev; - my $ret = DevIo_OpenDev($hash, 0, "HMLAN_DoInit"); - return $ret; -} - - -##################################### -sub -HMLAN_Undef($$) -{ - my ($hash, $arg) = @_; - my $name = $hash->{NAME}; - - foreach my $d (sort keys %defs) { - if(defined($defs{$d}) && - defined($defs{$d}{IODev}) && - $defs{$d}{IODev} == $hash) - { - my $lev = ($reread_active ? 4 : 2); - Log GetLogLevel($name,$lev), "deleting port for $d"; - delete $defs{$d}{IODev}; - } - } - - DevIo_CloseDev($hash); - return undef; -} - -##################################### -sub -HMLAN_RemoveHMPair($) -{ - my $hash = shift; - delete($hash->{hmPair}); -} - - -##################################### -sub -HMLAN_Set($@) -{ - my ($hash, @a) = @_; - - return "\"set HMLAN\" needs at least one parameter" if(@a < 2); - return "Unknown argument $a[1], choose one of " . join(" ", sort keys %sets) - if(!defined($sets{$a[1]})); - - my $name = shift @a; - my $type = shift @a; - my $arg = join("", @a); - my $ll = GetLogLevel($name,3); - - if($type eq "hmPairForSec") { #################################### - return "Usage: set $name hmPairForSec " - if(!$arg || $arg !~ m/^\d+$/); - $hash->{hmPair} = 1; - InternalTimer(gettimeofday()+$arg, "HMLAN_RemoveHMPair", $hash, 1); - - } elsif($type eq "hmPairSerial") { ################################ - return "Usage: set $name hmPairSerial <10-character-serialnumber>" - if(!$arg || $arg !~ m/^.{10}$/); - - my $id = AttrVal($hash->{NAME}, "hmId", "123456"); - $hash->{HM_CMDNR} = $hash->{HM_CMDNR} ? ($hash->{HM_CMDNR}+1)%256 : 1; - - HMLAN_Write($hash, undef, sprintf("As15%02X8401%s000000010A%s", - $hash->{HM_CMDNR}, $id, unpack('H*', $arg))); - $hash->{hmPairSerial} = $arg; - - } - return undef; -} - - -##################################### -# This is a direct read for commands like get -sub -HMLAN_ReadAnswer($$$) -{ - my ($hash, $arg, $regexp) = @_; - my $type = $hash->{TYPE}; - - return ("No FD", undef) - if(!$hash && !defined($hash->{FD})); - - my ($mdata, $rin) = ("", ''); - my $buf; - my $to = 3; # 3 seconds timeout - $to = $hash->{RA_Timeout} if($hash->{RA_Timeout}); # ...or less - for(;;) { - - return ("Device lost when reading answer for get $arg", undef) - if(!$hash->{FD}); - vec($rin, $hash->{FD}, 1) = 1; - my $nfound = select($rin, undef, undef, $to); - if($nfound < 0) { - next if ($! == EAGAIN() || $! == EINTR() || $! == 0); - my $err = $!; - DevIo_Disconnected($hash); - return("HMLAN_ReadAnswer $arg: $err", undef); - } - return ("Timeout reading answer for get $arg", undef) - if($nfound == 0); - $buf = DevIo_SimpleRead($hash); - return ("No data", undef) if(!defined($buf)); - - if($buf) { - Log 5, "HMLAN/RAW (ReadAnswer): $buf"; - $mdata .= $buf; - } - if($mdata =~ m/\r\n/) { - if($regexp && $mdata !~ m/$regexp/) { - HMLAN_Parse($hash, $mdata); - } else { - return (undef, $mdata) - } - } - } -} - -my %lhash; - -##################################### -sub -HMLAN_Write($$$) -{ - my ($hash,$fn,$msg) = @_; - - my $dst = substr($msg, 16, 6); - { # occationally necessary. Works fine if we do it always - HMLAN_SimpleWrite($hash, "+$dst,00,00,"); - HMLAN_SimpleWrite($hash, "+$dst,00,00,"); - HMLAN_SimpleWrite($hash, "+$dst,00,00,"); - HMLAN_SimpleWrite($hash, "-$dst"); - HMLAN_SimpleWrite($hash, "+$dst,00,00,"); - HMLAN_SimpleWrite($hash, "+$dst,00,00,"); - HMLAN_SimpleWrite($hash, "+$dst,00,00,"); - HMLAN_SimpleWrite($hash, "+$dst,00,00,"); - $lhash{$dst} = 1; - } - my $tm = int(gettimeofday()*1000) % 0xffffffff; - $msg = sprintf("S%08X,00,00000000,01,%08X,%s",$tm, $tm, substr($msg, 4)); - HMLAN_SimpleWrite($hash, $msg); - - # Avoid problems with structure set - # TODO: rewrite it to use a queue+internaltimer like the CUL - select(undef, undef, undef, 0.01); # needed for structure set by meesus. -} - -##################################### -# called from the global loop, when the select for hash->{FD} reports data -sub -HMLAN_Read($) -{ - my ($hash) = @_; - - my $buf = DevIo_SimpleRead($hash); - return "" if(!defined($buf)); - my $name = $hash->{NAME}; - - my $hmdata = $hash->{PARTIAL}; - Log 5, "HMLAN/RAW: $hmdata/$buf"; - $hmdata .= $buf; - - while($hmdata =~ m/\n/) { - my $rmsg; - ($rmsg,$hmdata) = split("\n", $hmdata, 2); - $rmsg =~ s/\r//; - HMLAN_Parse($hash, $rmsg) if($rmsg); - } - $hash->{PARTIAL} = $hmdata; -} - -sub -HMLAN_uptime($) -{ - my $msec = shift; - - $msec = hex($msec); - my $sec = int($msec/1000); - return sprintf("%03d %02d:%02d:%02d.%03d", - int($msec/86400000), int($sec/3600), - int(($sec%3600)/60), $sec%60, $msec % 1000); -} - -sub -HMLAN_Parse($$) -{ - my ($hash, $rmsg) = @_; - my $name = $hash->{NAME}; - my $ll5 = GetLogLevel($name,5); - my ($src, $status, $msec, $d2, $rssi, $msg); - - my $dmsg = $rmsg; - - Log $ll5, "HMLAN_Parse: $name $rmsg"; - if($rmsg =~ m/^E(......),(....),(........),(..),(....),(.*)/) { - ($src, $status, $msec, $d2, $rssi, $msg) = - ($1, $2, $3, $4, $5, $6); - $dmsg = sprintf("A%02X%s", length($msg)/2, uc($msg)); - $hash->{uptime} = HMLAN_uptime($msec); - - } elsif($rmsg =~ m/^R(........),(....),(........),(..),(....),(.*)/) { - ($src, $status, $msec, $d2, $rssi, $msg) = - ($1, $2, $3, $4, $5, $6); - - $dmsg = sprintf("A%02X%s", length($msg)/2, uc($msg)); - $dmsg .= "NACK" if($status !~ m/00(01|02|21)/); - - $hash->{uptime} = HMLAN_uptime($msec); - - } elsif($rmsg =~ - m/^HHM-LAN-IF,(....),(..........),(......),(......),(........),(....)/) { - my ($vers, $serno, $d1, $owner, $msec, $d2) = - (hex($1), $2, $3, $4, $5, $6); - $hash->{serialNr} = $serno; - $hash->{firmware} = sprintf("%d.%d", ($vers>>12)&0xf, $vers & 0xffff); - $hash->{owner} = $owner; - $hash->{uptime} = HMLAN_uptime($msec); - my $myId = AttrVal($name, "hmId", $owner); - if(lc($owner) ne lc($myId) && !AttrVal($name, "dummy", 0)) { - Log 1, "HMLAN setting owner to $myId from $owner"; - HMLAN_SimpleWrite($hash, "A$myId"); - } - return; - - } elsif($rmsg =~ m/^I00.*/) { - # Ack from the HMLAN - return; - - } else { - Log $ll5, "$name Unknown msg >$rmsg<"; - return; - - } - - $hash->{"${name}_MSGCNT"}++; - $hash->{"${name}_TIME"} = TimeNow(); - $hash->{RAWMSG} = $rmsg; - my %addvals = (RAWMSG => $rmsg); - if(defined($rssi)) { - $rssi = hex($rssi)-65536; - $hash->{RSSI} = $rssi; - $addvals{RSSI} = $rssi; - } - Dispatch($hash, $dmsg, \%addvals); -} - - -##################################### -sub -HMLAN_Ready($) -{ - my ($hash) = @_; - - return DevIo_OpenDev($hash, 1, "HMLAN_DoInit"); -} - -######################## -sub -HMLAN_SimpleWrite(@) -{ - my ($hash, $msg, $nonl) = @_; - my $name = $hash->{NAME}; - return if(!$hash || AttrVal($hash->{NAME}, "dummy", undef)); - - select(undef, undef, undef, 0.01); - Log GetLogLevel($name,5), "SW: $msg"; - - $msg .= "\r\n" unless($nonl); - syswrite($hash->{TCPDev}, $msg) if($hash->{TCPDev}); -} - -######################## -sub -HMLAN_DoInit($) -{ - my ($hash) = @_; - my $name = $hash->{NAME}; - - my $id = AttrVal($name, "hmId", undef); - my $key = AttrVal($name, "hmKey", ""); # 36(!) hex digits - - my $s2000 = sprintf("%02X", HMLAN_secSince2000()); - - HMLAN_SimpleWrite($hash, "A$id") if($id); - HMLAN_SimpleWrite($hash, "C"); - HMLAN_SimpleWrite($hash, "Y01,01,$key"); - HMLAN_SimpleWrite($hash, "Y02,00,"); - HMLAN_SimpleWrite($hash, "Y03,00,"); - HMLAN_SimpleWrite($hash, "Y03,00,"); - HMLAN_SimpleWrite($hash, "T$s2000,04,00,00000000"); - - InternalTimer(gettimeofday()+25, "HMLAN_KeepAlive", $hash, 0); - return undef; -} - -##################################### -sub -HMLAN_KeepAlive($) -{ - my $hash = shift; - return if(!$hash->{FD}); - HMLAN_SimpleWrite($hash, "K"); - InternalTimer(gettimeofday()+25, "HMLAN_KeepAlive", $hash, 1); -} - -sub -HMLAN_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; -} - -1; +############################################## +# $Id$ +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); + +sub HMLAN_Parse($$); +sub HMLAN_Read($); +sub HMLAN_Write($$$); +sub HMLAN_ReadAnswer($$$); +sub HMLAN_uptime($); +sub HMLAN_secSince2000(); + +sub HMLAN_SimpleWrite(@); + +my %sets = ( + "hmPairForSec" => "HomeMatic", + "hmPairSerial" => "HomeMatic", +); + +sub +HMLAN_Initialize($) +{ + my ($hash) = @_; + + require "$attr{global}{modpath}/FHEM/DevIo.pm"; + +# Provider + $hash->{ReadFn} = "HMLAN_Read"; + $hash->{WriteFn} = "HMLAN_Write"; + $hash->{ReadyFn} = "HMLAN_Ready"; + $hash->{SetFn} = "HMLAN_Set"; + $hash->{Clients} = ":CUL_HM:"; + my %mc = ( + "1:CUL_HM" => "^A......................", + ); + $hash->{MatchList} = \%mc; + +# Normal devices + $hash->{DefFn} = "HMLAN_Define"; + $hash->{UndefFn} = "HMLAN_Undef"; + $hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 " . + "loglevel:0,1,2,3,4,5,6 addvaltrigger " . + "hmId hmKey " . + "hmProtocolEvents:0_off,1_dump,2_dumpFull,3_dumpTrigger"; +} + +##################################### +sub +HMLAN_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + if(@a != 3) { + my $msg = "wrong syntax: define HMLAN ip[:port]"; + Log 2, $msg; + return $msg; + } + DevIo_CloseDev($hash); + + my $name = $a[0]; + my $dev = $a[2]; + $dev .= ":1000" if($dev !~ m/:/ && $dev ne "none" && $dev !~ m/\@/); + $attr{$name}{hmId} = sprintf("%06X", time() % 0xffffff); # Will be overwritten + + if($dev eq "none") { + Log 1, "$name device is none, commands will be echoed only"; + $attr{$name}{dummy} = 1; + return undef; + } + $hash->{DeviceName} = $dev; + my $ret = DevIo_OpenDev($hash, 0, "HMLAN_DoInit"); + return $ret; +} + + +##################################### +sub +HMLAN_Undef($$) +{ + my ($hash, $arg) = @_; + my $name = $hash->{NAME}; + + foreach my $d (sort keys %defs) { + if(defined($defs{$d}) && + defined($defs{$d}{IODev}) && + $defs{$d}{IODev} == $hash) + { + my $lev = ($reread_active ? 4 : 2); + Log GetLogLevel($name,$lev), "deleting port for $d"; + delete $defs{$d}{IODev}; + } + } + + DevIo_CloseDev($hash); + return undef; +} + +##################################### +sub +HMLAN_RemoveHMPair($) +{ + my $hash = shift; + delete($hash->{hmPair}); +} + + +##################################### +sub +HMLAN_Set($@) +{ + my ($hash, @a) = @_; + + return "\"set HMLAN\" needs at least one parameter" if(@a < 2); + return "Unknown argument $a[1], choose one of " . join(" ", sort keys %sets) + if(!defined($sets{$a[1]})); + + my $name = shift @a; + my $type = shift @a; + my $arg = join("", @a); + my $ll = GetLogLevel($name,3); + + if($type eq "hmPairForSec") { #################################### + return "Usage: set $name hmPairForSec " + if(!$arg || $arg !~ m/^\d+$/); + $hash->{hmPair} = 1; + InternalTimer(gettimeofday()+$arg, "HMLAN_RemoveHMPair", $hash, 1); + + } elsif($type eq "hmPairSerial") { ################################ + return "Usage: set $name hmPairSerial <10-character-serialnumber>" + if(!$arg || $arg !~ m/^.{10}$/); + + my $id = AttrVal($hash->{NAME}, "hmId", "123456"); + $hash->{HM_CMDNR} = $hash->{HM_CMDNR} ? ($hash->{HM_CMDNR}+1)%256 : 1; + + HMLAN_Write($hash, undef, sprintf("As15%02X8401%s000000010A%s", + $hash->{HM_CMDNR}, $id, unpack('H*', $arg))); + $hash->{hmPairSerial} = $arg; + + } + return undef; +} + + +##################################### +# This is a direct read for commands like get +sub +HMLAN_ReadAnswer($$$) +{ + my ($hash, $arg, $regexp) = @_; + my $type = $hash->{TYPE}; + + return ("No FD", undef) + if(!$hash && !defined($hash->{FD})); + + my ($mdata, $rin) = ("", ''); + my $buf; + my $to = 3; # 3 seconds timeout + $to = $hash->{RA_Timeout} if($hash->{RA_Timeout}); # ...or less + for(;;) { + + return ("Device lost when reading answer for get $arg", undef) + if(!$hash->{FD}); + vec($rin, $hash->{FD}, 1) = 1; + my $nfound = select($rin, undef, undef, $to); + if($nfound < 0) { + next if ($! == EAGAIN() || $! == EINTR() || $! == 0); + my $err = $!; + DevIo_Disconnected($hash); + return("HMLAN_ReadAnswer $arg: $err", undef); + } + return ("Timeout reading answer for get $arg", undef) + if($nfound == 0); + $buf = DevIo_SimpleRead($hash); + return ("No data", undef) if(!defined($buf)); + + if($buf) { + Log 5, "HMLAN/RAW (ReadAnswer): $buf"; + $mdata .= $buf; + } + if($mdata =~ m/\r\n/) { + if($regexp && $mdata !~ m/$regexp/) { + HMLAN_Parse($hash, $mdata); + } else { + return (undef, $mdata) + } + } + } +} + +my %lhash; + +##################################### +sub +HMLAN_Write($$$) +{ + my ($hash,$fn,$msg) = @_; + my $dst = substr($msg, 16, 6); + +# my $IDHM = '+'.$dst.',01,00,F1EF'; #used by HMconfig - meanning?? + my $IDadd = '+'.$dst.',00,00,'; # guess: add ID? + my $IDsub = '-'.$dst; # guess: ID remove? +# my $IDact = '+'.$dst; # guess: ID recover? Different to IDadd? +# my $IDack = '+'.$dst.',02,00,'; # guess: ID acknowledge + + HMLAN_SimpleWrite($hash, $IDadd) if (!$lhash{$dst}); + $lhash{$dst} = 1; + + if(hex(substr($msg, 6, 2))&0x01){ # wakeup sequence for TC... hmmm + HMLAN_SimpleWrite($hash, $IDadd); + HMLAN_SimpleWrite($hash, $IDadd); + HMLAN_SimpleWrite($hash, $IDadd); + HMLAN_SimpleWrite($hash, $IDsub); + HMLAN_SimpleWrite($hash, $IDadd); + HMLAN_SimpleWrite($hash, $IDadd); + HMLAN_SimpleWrite($hash, $IDadd); + HMLAN_SimpleWrite($hash, $IDadd); + #HMLAN_SimpleWrite($hash, "+$dst,01,00,F1EF\x0d\x0a"); + } + my $tm = int(gettimeofday()*1000) % 0xffffffff; + $msg = sprintf("S%08X,00,00000000,01,%08X,%s",$tm, $tm, substr($msg, 4)); + HMLAN_SimpleWrite($hash, $msg); + + # Avoid problems with structure set + # TODO: rewrite it to use a queue+internaltimer like the CUL +} + +##################################### +# called from the global loop, when the select for hash->{FD} reports data +sub +HMLAN_Read($) +{ + my ($hash) = @_; + + my $buf = DevIo_SimpleRead($hash); + return "" if(!defined($buf)); + my $name = $hash->{NAME}; + + my $hmdata = $hash->{PARTIAL}; +# Log 5, "HMLAN/RAW: $hmdata/$buf"; + $hmdata .= $buf; + + while($hmdata =~ m/\n/) { + my $rmsg; + ($rmsg,$hmdata) = split("\n", $hmdata, 2); + $rmsg =~ s/\r//; + HMLAN_Parse($hash, $rmsg) if($rmsg); + } + $hash->{PARTIAL} = $hmdata; +} + +sub +HMLAN_uptime($) +{ + my $msec = shift; + + $msec = hex($msec); + my $sec = int($msec/1000); + return sprintf("%03d %02d:%02d:%02d.%03d", + int($msec/86400000), int($sec/3600), + int(($sec%3600)/60), $sec%60, $msec % 1000); +} + +sub +HMLAN_Parse($$) +{ + my ($hash, $rmsg) = @_; + my $name = $hash->{NAME}; + my $ll5 = GetLogLevel($name,5); + my @mFld = split(',', $rmsg); + my $letter = substr($mFld[0],0,1); # get leading char + + if ($letter =~ m/^[ER]/){#@mFld=($src, $status, $msec, $d2, $rssi, $msg) + Log $ll5, 'HMLAN_Parse: '.$name.' S:'.$mFld[0] +# .(if($mFld[0] =~ m/^E/)?' ':'') + .' stat:'.$mFld[1] + .' t:'.$mFld[2].' d:'.$mFld[3] + .' r:'.$mFld[4]. + 'm:'.$mFld[5]; +# ' m:'.substr($mFld[5],0,2). +# ' '.substr($mFld[5],2,4). +# ' '.substr($mFld[5],6,6). +# ' '.substr($mFld[5],12,6). +# ' '.substr($mFld[5],18); + + my $dmsg = sprintf("A%02X%s", length($mFld[5])/2, uc($mFld[5])); + + my $src = substr($mFld[5],6,6); + my $dst = substr($mFld[5],12,6); + my $flg = hex(substr($mFld[5],2,2)); + # handle status. 1-ack,8=nack,21=?,02=? 81=open + + HMLAN_SimpleWrite($hash, '+'.$src) if (($letter eq 'R')); #ok + + if (!($flg & 0x25)){#rule out other messages + HMLAN_SimpleWrite($hash, '-'.$src); + HMLAN_SimpleWrite($hash, '+'.$src); + } + $dmsg .= "NACK" if($mFld[1] !~ m/00(01|02|21)/ && $letter eq 'R'); + + $hash->{uptime} = HMLAN_uptime($mFld[2]); + $hash->{RSSI} = hex($mFld[4]); + $hash->{RAWMSG} = $rmsg; + $hash->{"${name}_MSGCNT"}++; + $hash->{"${name}_TIME"} = TimeNow(); + my %addvals = (RAWMSG => $rmsg, RSSI => hex($mFld[4])); + Dispatch($hash, $dmsg, \%addvals); + } + elsif($mFld[0] eq 'HHM-LAN-IF'){#@mFld=(undef,$vers,$serno,$d1,$owner,$msec,$d2) + $hash->{serialNr} = $mFld[2]; + $hash->{firmware} = sprintf("%d.%d", (hex($mFld[1])>>12)&0xf, hex($mFld[1]) & 0xffff); + $hash->{owner} = $mFld[4]; + $hash->{uptime} = HMLAN_uptime($mFld[5]); + Log $ll5, 'HMLAN_Parse: '.$name. ' V:'.$mFld[1] + .' sNo:'.$mFld[2].' d:'.$mFld[3] + .' O:' .$mFld[4].' m:'.$mFld[5].' d2:'.$mFld[6]; + my $myId = AttrVal($name, "hmId", $mFld[4]); + if(lc($mFld[4]) ne lc($myId) && !AttrVal($name, "dummy", 0)) { + Log 1, 'HMLAN setting owner to '.$myId.' from '.$mFld[4]; + HMLAN_SimpleWrite($hash, "A$myId"); + } + } + elsif($rmsg =~ m/^I00.*/) {; + # Ack from the HMLAN + } + else { + Log $ll5, "$name Unknown msg >$rmsg<"; + } +} + + +##################################### +sub +HMLAN_Ready($) +{ + my ($hash) = @_; + + return DevIo_OpenDev($hash, 1, "HMLAN_DoInit"); +} + +######################## +sub +HMLAN_SimpleWrite(@) +{ + my ($hash, $msg, $nonl) = @_; + my $name = $hash->{NAME}; + return if(!$hash || AttrVal($hash->{NAME}, "dummy", undef)); + + select(undef, undef, undef, 0.01); # todo check necessity +#---------- confort trace-------------- +# Log GetLogLevel($name,5), 'HMLAN_Send: S:'. +# substr($msg,0,9). +# ' stat: '.substr($msg,10,2). +# ' t:' .substr($msg,13,8). +# ' d:' .substr($msg,22,2). +# ' r:' .substr($msg,25,8). +# ' m:' .substr($msg,34) + +# ' m:' .substr($msg,34,2). +# ' ' .substr($msg,36,4). +# ' ' .substr($msg,40,6). +# ' ' .substr($msg,46,6). +# ' ' .substr($msg,52) +# if (length($msg )>19); +# Log GetLogLevel($name,5), 'HMLAN_Send: '.$msg if (length($msg) <=19); +#----------- normal trace,better speed----------- + Log GetLogLevel($name,5), 'HMLAN_Send: '.$msg; #normal trace + + $msg .= "\r\n" unless($nonl); + syswrite($hash->{TCPDev}, $msg) if($hash->{TCPDev}); +} + +######################## +sub +HMLAN_DoInit($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + my $id = AttrVal($name, "hmId", undef); + my $key = AttrVal($name, "hmKey", ""); # 36(!) hex digits + + my $s2000 = sprintf("%02X", HMLAN_secSince2000()); + + HMLAN_SimpleWrite($hash, "A$id") if($id); + HMLAN_SimpleWrite($hash, "C"); + HMLAN_SimpleWrite($hash, "Y01,01,$key"); + HMLAN_SimpleWrite($hash, "Y02,00,"); + HMLAN_SimpleWrite($hash, "Y03,00,"); + HMLAN_SimpleWrite($hash, "Y03,00,"); + HMLAN_SimpleWrite($hash, "T$s2000,04,00,00000000"); + + InternalTimer(gettimeofday()+25, "HMLAN_KeepAlive", $hash, 0); + return undef; +} + +##################################### +sub +HMLAN_KeepAlive($) +{ + my $hash = shift; + return if(!$hash->{FD}); + HMLAN_SimpleWrite($hash, "K"); + InternalTimer(gettimeofday()+25, "HMLAN_KeepAlive", $hash, 1); +} + +sub +HMLAN_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; +} + +1; diff --git a/fhem/FHEM/10_CUL_HM.pm b/fhem/FHEM/10_CUL_HM.pm index 39b86ec4c..1a70671c5 100755 --- a/fhem/FHEM/10_CUL_HM.pm +++ b/fhem/FHEM/10_CUL_HM.pm @@ -130,7 +130,7 @@ my %culHmModel=( "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=>'28:00' ,rxt=>'b' ,lst=>'' ,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=>"",}, @@ -529,8 +529,12 @@ CUL_HM_Parse($$) # channel is in sType for this message my ( $of, $vep) = (hex($1), hex($2)); - push @event, "ValveErrorPosition $dname: $vep %"; - push @event, "ValveOffset $dname: $of %"; + push @event, "ValveErrorPosition for $dname: $vep %"; + push @event, "ValveOffset for $dname: $of %"; + #set the condition in destination + DoTrigger($dname,'ValveErrorPosition: changeTo '.$vep.'%'); + DoTrigger($dname,'ValveOffset: changeTo '.$of.'%'); + push @event,""; # nothing to report for TC } # ($cmd eq "A112" && $p =~ m/^0202(..)$/)) { # Set desired temp elsif(($msgType eq '02' &&$sType eq '01')|| # ackStatus @@ -589,8 +593,8 @@ CUL_HM_Parse($$) # => Link discriminator (00000000) is fixed elsif($msgType eq "10" && $p =~ m/^0401000000000509(..)0A(..)/) { my ( $of, $vep) = (hex($1), hex($2)); - push @event, "valve error position:$vep %"; - push @event, "ValveOffset $dname: $of %"; + push @event, "ValveErrorPosition: $vep%"; + push @event, "ValveOffset: $of%"; } } @@ -741,7 +745,7 @@ CUL_HM_Parse($$) 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 + ($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") { @@ -854,7 +858,7 @@ CUL_HM_Parse($$) push @event, "alive:yes"; push @event, "battery:". (($err&0x80)?"low" :"ok" ) if($cmpVal&0x80); - if (!$model eq "HM-SEC-WDS"){ + if ($model ne "HM-SEC-WDS"){ push @event, "cover:". (($err&0x0E)?"open" :"closed")if($cmpVal&0x0E); } } @@ -892,34 +896,31 @@ CUL_HM_Parse($$) } elsif($st eq "winMatic") { #################################### - if($msgType eq "10" && $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 = ""; - } - - if($msgType eq "10" && $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"); - } - - if($msgType eq "10" && - $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"; + 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"; + } } } @@ -1039,6 +1040,11 @@ my %culHmRegDefine = ( 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"}, @@ -1061,6 +1067,11 @@ my %culHmRegDefine = ( 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"}, @@ -1149,6 +1160,12 @@ my %culHmRegSupported = ( 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, @@ -1158,8 +1175,9 @@ my %culHmRegSupported = ( dis4=> {language => 1,stbyTime => 1, #todo insert correct name }, motionDetector=>{ - evtFltrPeriod =>1,evtFltrNum =>1,minInterval =>1, - captInInterval=>1,brightFilter =>1,ledOnTime =>1}, + 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, @@ -1584,8 +1602,12 @@ CUL_HM_Set($@) # as of now only hex value allowed check range and convert $chn = "00" if ($list eq "00"); - - $peerID = ($peerID eq 'all')?'all': CUL_HM_Name2Id($peerID,$hash); #todo add self + 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? @@ -1662,9 +1684,14 @@ CUL_HM_Set($@) 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); - $peerID = ($peerChnIn =~ m/^self(.*)/)?$dst:CUL_HM_Name2Id($peerChnIn); - $peerChn = ($1)?sprintf("%02X",$1):""; - $peerChn = ((length($peerID) == 8)?substr($peerID,6,2):"01") if (!$peerChn); + + 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); } @@ -1960,22 +1987,22 @@ CUL_HM_Set($@) 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") { ################################### - CUL_HM_PushCmdStack($hash, - sprintf("++%s01%s%s0104%s%02X03",$flag,$id, $dst, $id, $a[2])); + 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"); - } elsif ($a[3] eq "close") {CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,"0B550D838B558D83"); - } elsif ($a[3] eq "closed") {CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,"0F008F00"); - } elsif ($a[3] eq "bolt") {CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,"0FFF8FFF"); - } 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)); - } 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)); - } elsif ($a[3] eq "delete") {CUL_HM_PushCmdStack($hash,sprintf("++%s01%s%s0102%s%02X%s",$flag,$id, $dst, $id, $a[2], $chn)); + 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]"; + return 'unknown argument '.$a[3]; } } elsif($cmd eq "test") { ##################################################### @@ -2307,7 +2334,7 @@ CUL_HM_responseSetup($$$) $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); + InternalTimer(gettimeofday()+$rTo,"CUL_HM_respPendTout","respPend:$dst", 0);#todo General change timer to 1.5 #--- remove channel entries that will be replaced my $chnhash = $modules{CUL_HM}{defptr}{"$dst$chn"}; $chnhash = $hash if(!$chnhash); @@ -2523,7 +2550,7 @@ CUL_HM_id2Name($) } my $defPtr = $modules{CUL_HM}{defptr}; return $defPtr->{$chnId}{NAME} if($chnId && $defPtr->{$chnId}); - return $defPtr->{$devId}{NAME}.(defined($chn)?'_chn:'.$chn:'') if($defPtr->{$devId}); + return $defPtr->{$devId}{NAME} if($defPtr->{$devId}); return $devId. ($chn ? ("_chn:".$chn):""); } ###################################