###################################################### # InterTechno Switch Manager as FHM-Module # # (c) Olaf Droegehorn / DHS-Computertechnik GmbH # (c) Björn Hempel # # Published under GNU GPL License ###################################################### package main; # $Id$ use strict; use warnings; use SetExtensions; my %codes = ( "XMIToff" => "off", "XMITon" => "on", # Set to previous dim value (before switching it off) "00" => "off", "01" => "dim06%", "02" => "dim12%", "03" => "dim18%", "04" => "dim25%", "05" => "dim31%", "06" => "dim37%", "07" => "dim43%", "08" => "dim50%", "09" => "dim56%", "0a" => "dim62%", "0b" => "dim68%", "0c" => "dim75%", "0d" => "dim81%", "0e" => "dim87%", "0f" => "dim93%", "10" => "dim100%", "XMITdimup" => "dimup", "XMITdimdown" => "dimdown", "99" => "on-till", ); my %it_c2b; my $it_defrepetition = 6; ## Default number of InterTechno Repetitions my $it_simple ="off on"; my %models = ( itremote => 'sender', itswitch => 'simple', itdimmer => 'dimmer', ); my %bintotristate=( "00" => "0", "01" => "F", "11" => "1" ); my %bintotristateV3=( "10" => "1", "01" => "0", "00" => "D" ); sub bin2dec { unpack("N", pack("B32", substr("0" x 32 . shift, -32))); } sub IT_Initialize($) { my ($hash) = @_; foreach my $k (keys %codes) { $it_c2b{$codes{$k}} = $k; } $hash->{Match} = "^i......"; $hash->{SetFn} = "IT_Set"; $hash->{StateFn} = "IT_SetState"; $hash->{DefFn} = "IT_Define"; $hash->{UndefFn} = "IT_Undef"; $hash->{ParseFn} = "IT_Parse"; $hash->{AttrList} = "IODev ITfrequency ITrepetition switch_rfmode:1,0 do_not_notify:1,0 ignore:0,1 protocol:V1,V3 unit group dummy:1,0 " . "$readingFnAttributes " . "loglevel:0,1,2,3,4,5,6 " . "model:".join(",", sort keys %models); $hash->{AutoCreate}= { "IT.*" => { GPLOT => "", FILTER => "%NAME" } }; } ##################################### sub IT_SetState($$$$) { my ($hash, $tim, $vt, $val) = @_; return undef; $val = $1 if($val =~ m/^(.*) \d+$/); return "Undefined value $val" if(!defined($it_c2b{$val})); } ############################# sub IT_Do_On_Till($@) { my ($hash, @a) = @_; return "Timespec (HH:MM[:SS]) needed for the on-till command" if(@a != 3); my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($a[2]); return $err if($err); my @lt = localtime; my $hms_till = sprintf("%02d:%02d:%02d", $hr, $min, $sec); my $hms_now = sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]); if($hms_now ge $hms_till) { Log 4, "on-till: won't switch as now ($hms_now) is later than $hms_till"; return ""; } my @b = ($a[0], "on"); IT_Set($hash, @b); my $tname = $hash->{NAME} . "_till"; CommandDelete(undef, $tname) if($defs{$tname}); CommandDefine(undef, "$tname at $hms_till set $a[0] off"); } ################################### sub IT_Set($@) { my ($hash, $name, @a) = @_; my $ret = undef; my $na = int(@a); my $message; return "no set value specified" if($na < 1); my $list = ""; $list .= "off:noArg on:noArg " if( AttrVal($name, "model", "") ne "itremote" ); if ($hash->{READINGS}{protocol}{VAL} eq "V3") { if($na > 1 && $a[0] eq "dim") { $a[0] = ($a[1] eq "0" ? "off" : sprintf("dim%02d%%",$a[1]) ); splice @a, 1, 1; $na = int(@a); } $list = (join(" ", sort keys %it_c2b) . " dim:slider,0,6.25,100") if( AttrVal($name, "model", "") eq "itdimmer" ); } else { $list .= "dimUp:noArg dimDown:noArg on-till" if( AttrVal($name, "model", "") eq "itdimmer" ); } return SetExtensions($hash, $list, $name, @a) if( $a[0] eq "?" ); return SetExtensions($hash, $list, $name, @a) if( !grep( $_ =~ /^$a[0]($|:)/, split( ' ', $list ) ) ); my $c = $it_c2b{$a[0]}; return IT_Do_On_Till($hash, @a) if($a[0] eq "on-till"); return "Bad time spec" if($na == 2 && $a[1] !~ m/^\d*\.?\d+$/); my $io = $hash->{IODev}; ## Do we need to change RFMode to SlowRF?? if(defined($attr{$name}) && defined($attr{$name}{"switch_rfmode"})) { if ($attr{$name}{"switch_rfmode"} eq "1") { # do we need to change RFMode of IODev my $ret = CallFn($io->{NAME}, "AttrFn", "set", ($io->{NAME}, "rfmode", "SlowRF")); } } ## Do we need to change ITrepetition ?? if(defined($attr{$name}) && defined($attr{$name}{"ITrepetition"})) { $message = "isr".$attr{$name}{"ITrepetition"}; CallFn($io->{NAME}, "GetFn", $io, (" ", "raw", $message)); Log GetLogLevel($name,4), "IT set ITrepetition: $message for $io->{NAME}"; } ## Do we need to change ITfrequency ?? if(defined($attr{$name}) && defined($attr{$name}{"ITfrequency"})) { my $f = $attr{$name}{"ITfrequency"}/26*65536; my $f2 = sprintf("%02x", $f / 65536); my $f1 = sprintf("%02x", int($f % 65536) / 256); my $f0 = sprintf("%02x", $f % 256); my $arg = sprintf("%.3f", (hex($f2)*65536+hex($f1)*256+hex($f0))/65536*26); Log GetLogLevel($name,4), "Setting ITfrequency (0D,0E,0F) to $f2 $f1 $f0 = $arg MHz"; CallFn($io->{NAME}, "GetFn", $io, (" ", "raw", "if$f2$f1$f0")); } my $v = $name ." ". join(" ", @a); if ($hash->{READINGS}{protocol}{VAL} eq "V3") { if( AttrVal($name, "model", "") eq "itdimmer" ) { my @itvalues = split(' ', $v); if ($itvalues[1] eq "dimup") { $a[0] = "dim100%"; $hash->{READINGS}{dim}{VAL} = 100; $message = "is".uc(substr($hash->{XMIT},0,length($hash->{XMIT})-5).$hash->{READINGS}{group}{VAL}."D".$hash->{READINGS}{unit}{VAL}."1111"); } elsif ($itvalues[1] eq "dimdown") { $a[0] = "dim06%"; $hash->{READINGS}{dim}{VAL} = 6; $message = "is".uc(substr($hash->{XMIT},0,length($hash->{XMIT})-5).$hash->{READINGS}{group}{VAL}."D".$hash->{READINGS}{unit}{VAL}."0000"); } elsif ($itvalues[1] =~ /dim/) { my $dperc = substr($itvalues[1], 3, -1); my $dec = (15*$dperc)/100; my $bin = sprintf ("%b",$dec); while (length($bin) < 4) { # suffix 0 $bin = '0'.$bin; } $hash->{READINGS}{dim}{VAL} = $dperc; if ($dperc == 0) { $message = "is".uc(substr($hash->{XMIT},0,length($hash->{XMIT})-5).$hash->{READINGS}{group}{VAL}."0".$hash->{READINGS}{unit}{VAL}); } else { $message = "is".uc(substr($hash->{XMIT},0,length($hash->{XMIT})-5).$hash->{READINGS}{group}{VAL}."D".$hash->{READINGS}{unit}{VAL}.$bin); } } else { $message = "is".uc(substr($hash->{XMIT},0,length($hash->{XMIT})-5).$hash->{READINGS}{group}{VAL}.$hash->{$c}.$hash->{READINGS}{unit}{VAL}); } } else { $message = "is".uc(substr($hash->{XMIT},0,length($hash->{XMIT})-5).$hash->{READINGS}{group}{VAL}.$hash->{$c}.$hash->{READINGS}{unit}{VAL}); } } else { $message = "is".uc($hash->{XMIT}.$hash->{$c}); } ## Log that we are going to switch InterTechno Log GetLogLevel($name,2), "IT set $v"; (undef, $v) = split(" ", $v, 2); # Not interested in the name... ## Send Message to IODev and wait for correct answer my $msg = CallFn($io->{NAME}, "GetFn", $io, (" ", "raw", $message)); if ($msg =~ m/raw => $message/) { Log 4, "Answer from $io->{NAME}: $msg"; } else { Log 2, "IT IODev device didn't answer is command correctly: $msg"; } ## Do we need to change ITrepetition back?? if(defined($attr{$name}) && defined($attr{$name}{"ITrepetition"})) { $message = "isr".$it_defrepetition; CallFn($io->{NAME}, "GetFn", $io, (" ", "raw", $message)); Log GetLogLevel($name,4), "IT set ITrepetition back: $message for $io->{NAME}"; } ## Do we need to change ITfrequency back?? if(defined($attr{$name}) && defined($attr{$name}{"ITfrequency"})) { Log GetLogLevel($name,4), "Setting ITfrequency back to 433.92 MHz"; CallFn($io->{NAME}, "GetFn", $io, (" ", "raw", "if0")); } ## Do we need to change RFMode back to HomeMatic?? if(defined($attr{$name}) && defined($attr{$name}{"switch_rfmode"})) { if ($attr{$name}{"switch_rfmode"} eq "1") { # do we need to change RFMode of IODev my $ret = CallFn($io->{NAME}, "AttrFn", "set", ($io->{NAME}, "rfmode", "HomeMatic")); } } ########################## # Look for all devices with the same code, and set state, timestamp my $code = "$hash->{XMIT}"; my $tn = TimeNow(); foreach my $n (keys %{ $modules{IT}{defptr}{$code} }) { my $lh = $modules{IT}{defptr}{$code}{$n}; $lh->{CHANGED}[0] = $v; $lh->{STATE} = $v; $lh->{READINGS}{state}{TIME} = $tn; if ($hash->{READINGS}{protocol}{VAL} eq "V3") { if( AttrVal($name, "model", "") eq "itdimmer" ) { if ($v eq "on") { $hash->{READINGS}{dim}{VAL} = "100"; $lh->{READINGS}{state}{VAL} = "on"; } elsif ($v eq "off") { $hash->{READINGS}{dim}{VAL} = "0"; $lh->{READINGS}{state}{VAL} = "off"; } else { if ($v eq "dim100%") { $lh->{STATE} = "on"; $lh->{READINGS}{state}{VAL} = "on"; } elsif ($v eq "dim00%") { $lh->{STATE} = "off"; $lh->{READINGS}{state}{VAL} = "off"; } else { $lh->{STATE} = $v; $lh->{READINGS}{state}{VAL} = $v; } } } } else { $lh->{READINGS}{state}{VAL} = $v; } } return $ret; } ############################# sub IT_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); # calculate transmit code from IT A-P rotary switches if($a[2] =~ /^([A-O])(([0]{0,1}[1-9])|(1[0-6]))$/i) { my %it_1st = ( "A","0000","B","F000","C","0F00","D","FF00","E","00F0","F","F0F0", "G","0FF0","H","FFF0","I","000F","J","F00F","K","0F0F","L","FF0F", "M","00FF","N","F0FF","O","0FFF","P","FFFF" ); my %it_2nd = ( 1 ,"0000",2 ,"F000",3 ,"0F00",4 ,"FF00",5 ,"00F0",6 ,"F0F0", 7 ,"0FF0",8 ,"FFF0",9 ,"000F",10,"F00F",11,"0F0F",12,"FF0F", 13,"00FF",14,"F0FF",15,"0FFF",16,"FFFF" ); $a[2] = $it_1st{$1}.$it_2nd{int($2)}."0F"; defined $a[3] or $a[3] = "FF"; defined $a[4] or $a[4] = "F0"; defined $a[5] or $a[5] = "0F"; defined $a[6] or $a[6] = "00"; } # calculate transmit code from FLS 100 I,II,III,IV rotary switches if($a[2] =~ /^(I|II|III|IV)([1-4])$/i) { my %fls_1st = ("I","0FFF","II","F0FF","III","FF0F","IV","FFF0" ); my %fls_2nd = (1 ,"0FFF",2 ,"F0FF",3 ,"FF0F",4 ,"FFF0"); $a[2] = $fls_1st{$1}.$fls_2nd{int($2)}."0F"; defined $a[3] or $a[3] = "FF"; defined $a[4] or $a[4] = "F0"; defined $a[5] or $a[5] = "0F"; defined $a[6] or $a[6] = "00"; } my $u = "wrong syntax: define IT 10-bit-housecode " . "off-code on-code [dimup-code] [dimdown-code] or for protocol V3 " . "define IT <26 bit Address> <1 bit group bit> <4 bit unit>"; return $u if(int(@a) < 5); my $housecode; my $oncode; my $offcode; my $unitCode; my $groupBit; if (length($a[2]) == 26) { # Is Protocol V3 return "Define $a[0]: wrong IT-Code format: specify a 26 digits 0/1 " if( ($a[2] !~ m/^[0-1]{26}$/i) ); return "Define $a[0]: wrong Bit Group format: specify a 1 digits 0/1 " if( ($a[3] !~ m/^[0-1]{1}$/i) ); return "Define $a[0]: wrong Unit format: specify 4 digits 0/1 " if( ($a[4] !~ m/^[0-1]{4}$/i) ); #return "Define $a[0]: wrong on/off/dimm format: specify a 1 digits 0/1/d " # if( ($a[3] !~ m/^[d0-1]{1}$/i) ); $housecode=$a[2].$a[3].$a[4]; $groupBit=$a[3]; $unitCode=$a[4]; $oncode = 1; $offcode = 0; $hash->{READINGS}{protocol}{VAL} = 'V3'; $hash->{READINGS}{unit}{VAL} = $unitCode; $hash->{READINGS}{group}{VAL} = $groupBit; } else { return "Define $a[0]: wrong IT-Code format: specify a 10 digits 0/1/f " if( ($a[2] !~ m/^[f0-1]{10}$/i) ); return "Define $a[0]: wrong ON format: specify a 2 digits 0/1/f " if( ($a[3] !~ m/^[f0-1]{2}$/i) ); return "Define $a[0]: wrong OFF format: specify a 2 digits 0/1/f " if( ($a[4] !~ m/^[f0-1]{2}$/i) ); $housecode = $a[2]; $oncode = $a[3]; $offcode = $a[4]; $hash->{READINGS}{protocol}{VAL} = 'V1'; } $hash->{XMIT} = lc($housecode); $hash->{$it_c2b{"on"}} = lc($oncode); $hash->{$it_c2b{"off"}} = lc($offcode); if (int(@a) > 5) { return "Define $a[0]: wrong dimup-code format: specify a 2 digits 0/1/f " if( ($a[5] !~ m/^[f0-1]{2}$/i) ); $hash->{$it_c2b{"dimup"}} = lc($a[5]); if (int(@a) == 7) { return "Define $a[0]: wrong dimdown-code format: specify a 2 digits 0/1/f " if( ($a[6] !~ m/^[f0-1]{2}$/i) ); $hash->{$it_c2b{"dimdown"}} = lc($a[6]); } } else { $hash->{$it_c2b{"dimup"}} = "00"; $hash->{$it_c2b{"dimdown"}} = "00"; } my $code = lc($housecode); my $ncode = 1; my $name = $a[0]; $hash->{CODE}{$ncode++} = $code; $modules{IT}{defptr}{$code}{$name} = $hash; AssignIoPort($hash); } ############################# sub IT_Undef($$) { my ($hash, $name) = @_; foreach my $c (keys %{ $hash->{CODE} } ) { $c = $hash->{CODE}{$c}; # As after a rename the $name my be different from the $defptr{$c}{$n} # we look for the hash. foreach my $dname (keys %{ $modules{IT}{defptr}{$c} }) { delete($modules{IT}{defptr}{$c}{$dname}) if($modules{IT}{defptr}{$c}{$dname} == $hash); } } return undef; } sub IT_Parse($$) { my ($hash, $msg) = @_; my $housecode; my $dimCode; my $unitCode; my $groupBit; my $onoffcode; my $def; my $newstate; my @list; if ((substr($msg, 0, 1)) ne 'i') { Log3 undef,4,"message not supported by IT \"$msg\"!"; return undef; } if (length($msg) != 7 && length($msg) != 17 && length($msg) != 19) { Log3 undef,3,"message \"$msg\" too short!"; return undef; } my $bin = undef; my $isDimMode = 0; if (length($msg) == 17) { my $bin1=sprintf("%024b",hex(substr($msg,1,length($msg)-1-8))); while (length($bin1) < 32) { # suffix 0 $bin1 = '0'.$bin1; } my $bin2=sprintf("%024b",hex(substr($msg,1+8,length($msg)-1))); while (length($bin2) < 32) { # suffix 0 $bin2 = '0'.$bin2; } $bin = $bin1 . $bin2; } elsif (length($msg) == 19 ) { my $bin1=sprintf("%024b",hex(substr($msg,1,length($msg)-1-8-8))); while (length($bin1) < 32) { # suffix 0 $bin1 = '0'.$bin1; } my $bin2=sprintf("%024b",hex(substr($msg,1+2,length($msg)-1-8-2))); while (length($bin2) < 32) { # suffix 0 $bin2 = '0'.$bin2; } my $bin3=sprintf("%024b",hex(substr($msg,1+8+2,length($msg)-1))); while (length($bin3) < 32) { # suffix 0 $bin3 = '0'.$bin3; } $bin = substr($bin1 . $bin2 . $bin3,24,length($bin1 . $bin2 . $bin3)-1); } else { $bin=sprintf("%024b",hex(substr($msg,1,length($msg)-1))); } if ((length($bin) % 2) != 0) { # suffix 0 $bin = '0'.$bin; } #Log3 undef,4,"BIN: $bin"; my $msgcode=""; while (length($bin)>=2) { if (length($msg) == 7) { if (substr($bin,0,2) != "10") { $msgcode=$msgcode.$bintotristate{substr($bin,0,2)}; } else { Log3 undef,4,"unknown tristate in \"$bin\""; return "unknown tristate in \"$bin\"" } } else { $msgcode=$msgcode.$bintotristateV3{substr($bin,0,2)}; } $bin=substr($bin,2,length($bin)-2); } if (length($msg) == 7) { $housecode=substr($msgcode,0,length($msgcode)-2); $onoffcode=substr($msgcode,length($msgcode)-2,2); } elsif (length($msg) == 17 || length($msg) == 19) { $groupBit=substr($msgcode,26,1); $onoffcode=substr($msgcode,27,1); $unitCode=substr($msgcode,28,4); $housecode=substr($msgcode,0,26).$groupBit.$unitCode; if (length($msg) == 19) { $dimCode=substr($msgcode,32,4); } } else { Log3 undef,4,"Wrong IT message received: $msgcode"; return "Wrong IT message received: $msgcode"; } if(!defined($modules{IT}{defptr}{lc("$housecode")})) { if(length($msg) == 7) { Log3 undef,4,"$housecode not defined (Switch code: $onoffcode)"; #return "$housecode not defined (Switch code: $onoffcode)!"; if ($onoffcode eq "F0") { # on code IT Log3 undef,4,"For autocreate please use the on button."; return "$housecode not defined (Switch code: $onoffcode)! \n For autocreate please use the on button."; } my $tmpOffCode = "F0"; my $tmpOnCode = "0F"; if ($onoffcode eq "FF") { # on code IT $tmpOnCode = "FF"; } return "UNDEFINED IT_$housecode IT $housecode $tmpOnCode $tmpOffCode" if(!$def); } else { Log3 undef,4,"$housecode not defined (Address: ".substr($msgcode,0,26)." Group: $groupBit Unit: $unitCode Switch code: $onoffcode)"; #return "$housecode not defined (Address: ".substr($msgcode,0,26)." Group: $groupBit Unit: $unitCode Switch code: $onoffcode)!"; return "UNDEFINED IT_$housecode IT " . substr($msgcode,0,26) . " $groupBit $unitCode" if(!$def); } } $def=$modules{IT}{defptr}{lc($housecode)}; foreach my $name (keys %{$def}) { if (length($msg) == 17 || length($msg) == 19) { if ($def->{$name}->{READINGS}{group}{VAL} != $groupBit || $def->{$name}->{READINGS}{unit}{VAL} != $unitCode) { next; } } if ($def->{$name}->{$it_c2b{"on"}} eq lc($onoffcode)) { $newstate="on"; if( AttrVal($name, "model", "") eq "itdimmer" ) { readingsSingleUpdate($def->{$name},"dim",1,1); } } elsif ($def->{$name}->{$it_c2b{"off"}} eq lc($onoffcode)) { $newstate="off"; if( AttrVal($name, "model", "") eq "itdimmer" ) { readingsSingleUpdate($def->{$name},"dim",0,1); } } elsif ('d' eq lc($onoffcode)) { # dim my $binVal = ((bin2dec($dimCode)+1)*100)/16; $binVal = int($binVal); $newstate = sprintf("dim%02d%%",$binVal); readingsSingleUpdate($def->{$name},"dim",$binVal,1); if ($binVal == 100) { $newstate="on"; } elsif ($binVal == 0) { $newstate="off"; } } else { Log3 $def->{$name}{NAME},3,"Code $onoffcode not supported by $def->{$name}{NAME}."; next; } Log3 $def->{$name}{NAME},3,"$def->{$name}{NAME} ".$def->{$name}->{STATE}."->".$newstate; push(@list,$def->{$name}{NAME}); readingsSingleUpdate($def->{$name},"state",$newstate,1); } return @list; } 1; =pod =begin html

IT - InterTechno

=end html =begin html_DE

IT - InterTechno

=end html_DE =cut