# # 09_CUL_FHTTK.pm # # A module for FHEM to handle ELV's FHT80 TF-type sensors # written by Kai 'wusel' Siering, 2009-11-06 with help # from previously written FHEM code as well as members # of fhem-users at googlegroups.com! Thanks, guys! # # e-mail: wusel+source at uu punkt org # # This module reads, despite setting an IODev explicitely, # from any (CUL-) source and drops any identical message # arriving within 5 seconds. It does handle the automatic # retransmission of FHT80 TF as well as concurrent recep- # tion from multiple sources; in my system, it could happen # that the CUL in the same room "overhears" a telegram from # FHT80 TF (most likely due to other messages sent/received # at the same time) but the one downstairs still picks it up. # My implementation should be safe for the device in question, # if you see problems, the "only on this IODev"-code is still # in place but commented out. # # # Note: The sensor in question is named "FHT80 TF", # in it's (formerly current, now old) design it looks # similar to "FS20 TFK" but operates differently. # # FHT80 TF is designed to serve as a sensor to FHT80 B, # only the B receives TF's transmissions (after made # known to each FHT80 B) normally. The B then, if in- # structed that way, turns down the heating while any # of the TFs known to it signal "Window open". The TF # transmits about every 255 seconds a telegram stating # whether or nor the (reed-) contact is open (which # means Window or Door, relevant for heating, open) # and whether the battery is still full enough. # # The FS20 TFK on the other hand just directly addresses # another FS20 device on opening/closing of it's (reed-) # contact. # # Finally, the HMS100 TFK is designed to notify a HMS- # central about opened/closed contacts immediately, # but you can't directly address FS20 devices ... # # So, to notify e. g. FHEM instantly about opening # or closure of doors/windows, your best buy might be # an HMS100 TFK (as of this writing EUR 29,95 @ ELV). # You could use an FS20 TFK as well (EUR 34,95 @ ELV), # that way you could directly have FS20 switches act # on opened/closed doors or windows in parallel or # even without FHEM. The FHT80 TF (as eQ-3 FHT 80 TF # currently for EUR 14,95 available @ ELV) only sends # out a status telegram every ca. 2,5 minutes, so it's # ok for seeing where one might have left a window # open before leaving the house but by no means suit- # able for any alerting uses (unless a delay of said # amount of time doesn't matter, of course ;)). # # in charge of code: Matscher # # $Id$ ############################################## package main; use strict; use warnings; my %fhttfk_codes = ( "02" => "Window:Closed", "82" => "Window:Closed", "01" => "Window:Open", "81" => "Window:Open", "0c" => "Sync:Syncing", "91" => "Window:Open, Low Batt", "11" => "Window:Open, Low Batt", "92" => "Window:Closed, Low Batt", "12" => "Window:Closed, Low Batt", "0f" => "Test:Success"); # -wusel, 2009-11-09: Map retransmission codes to major (8x) ones (0x) # As I'm somewhat lazy, I just list all codes from # %fhttfk_codes and map them to their major one. # (FIXME: it would be sufficient to have %fhttfk_codes # only list these major, "translated" ones.) my %fhttfk_translatedcodes = ( "01" => "01", "11" => "11", "12" => "12", "02" => "02", "0c" => "0c", "0f" => "0f", "81" => "01", "82" => "02", "91" => "11", "92" => "12"); # set my %fhttfk_c2b; # command->button hash my %canset = ( "01" => "Open", "02" => "Closed", "0c" => "Pair", "ff" => "ReSync"); # -wusel, 2009-11-06 # # Parse messages from FHT80TK, normally interpreted only by FHT80 # # Format as follows: "TCCCCCCXX" with CCCCCC being the id of the # sensor in hex, XX being the current status: 02/82 is Window # closes, 01/81 is Window open, 0C is synchronization, ?? is the # battery low warning. FIXME! ############################# sub CUL_FHTTK_Initialize($) { my ($hash) = @_; foreach my $k (keys %canset) { my $v = $canset{$k}; $fhttfk_c2b{$v} = $k; } $hash->{Match} = "^T[A-F0-9]{8}"; $hash->{SetFn} = "CUL_FHTTK_Set"; $hash->{DefFn} = "CUL_FHTTK_Define"; $hash->{UndefFn} = "CUL_FHTTK_Undef"; $hash->{ParseFn} = "CUL_FHTTK_Parse"; $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:0,1 showtime:0,1 " . "model:FHT80TF,FHT80TF-2,dummy ". $readingFnAttributes; $hash->{AutoCreate}= { "CUL_FHTTK.*" => { GPLOT => "fht80tf:Window,", FILTER => "%NAME" } }; } ############################# sub CUL_FHTTK_Set($@) { my ($hash, @a) = @_; my $ret = ""; return "\"set $a[0]\" needs at least two parameters" if(@a < 2); my $name = shift(@a); my $opt = shift @a; # suppress SET option if(defined($attr{$name}) && defined($attr{$name}{"model"})) { if($attr{$name}{"model"} ne "dummy") { return $ret; } } else { return $ret; } my $value = join("", @a); Log3 $name, 5, "CUL_FHTTK ($name) option: $opt and value: $value"; if(!defined($fhttfk_c2b{$opt})) { my @cList = keys %fhttfk_c2b; return "Unknown argument $opt ($value), choose one of " . join(" ", @cList); } if ($opt eq "Open" ) { Log3 $name, 3, "CUL_FHTTK ($name) changed window state to open."; IOWrite($hash, "", sprintf("T%s01", $hash->{CODE})); # 0x01 - open or 0x81 } elsif ($opt eq "Closed" ) { Log3 $name, 3, "CUL_FHTTK ($name) changed window state to closed."; IOWrite($hash, "", sprintf("T%s02", $hash->{CODE})); # 0x02 - closed or 0x82 } elsif($opt eq "Pair" ) { Log3 $name, 3, "CUL_FHTTK ($name) pairing with FHT80b."; IOWrite($hash, "", sprintf("T%s0c", $hash->{CODE})); # 0x0c - sync # window state switch to closed through cul FW implementation $opt = "Closed"; } elsif($opt eq "ReSync" ) { Log3 $name, 3, "CUL_FHTTK ($name) resyncing with FHT80b."; IOWrite($hash, "", sprintf("T%s%s", $hash->{CODE}, $fhttfk_c2b{$opt})); # 0xff - ReSync # window state switch to closed through cul FW implementation $opt = "Closed"; } else { return "Unknown argument $a[1], choose one of Pair ReSync Open Closed" } # update new state readingsSingleUpdate($hash, "state", $opt, 1); readingsSingleUpdate($hash, "Window", $opt, 1); return $ret; } ############################# sub CUL_FHTTK_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); my $u= "wrong syntax: define CUL_FHTTK "; return $u if((int(@a)< 3) || (int(@a)>3)); my $name = $a[0]; my $sensor = lc($a[2]); if($sensor !~ /^[0-9a-f]{6}$/) { return "wrong sensor specification $sensor, need a 6 digit hex number!"; } $hash->{CODE} = $sensor; $modules{CUL_FHTTK}{defptr}{$sensor} = $hash; AssignIoPort($hash); return undef; } ############################# sub CUL_FHTTK_Undef($$) { my ($hash, $name) = @_; delete($modules{CUL_FHTTK}{defptr}{$hash->{CODE}}) if($hash && $hash->{CODE}); return undef; } ############################# sub CUL_FHTTK_Parse($$) { my ($hash, $msg) = @_; my $sensor= lc(substr($msg, 1, 6)); my $def = $modules{CUL_FHTTK}{defptr}{$sensor}; if(!$def) { Log3 $hash, 1, "FHTTK Unknown device $sensor, please define it"; return "UNDEFINED CUL_FHTTK_$sensor CUL_FHTTK $sensor"; } my $name = $def->{NAME}; my $state = lc(substr($msg, 7, 2)); return "" if(IsIgnored($name)); if(!defined($fhttfk_translatedcodes{$state})) { Log3 $name, 1, sprintf("FHTTK $def Unknown state $state"); $defs{$name}{READINGS}{"Unknown"}{VAL} = $state; $defs{$name}{READINGS}{"Unknown"}{TIME} = TimeNow(); return ""; } $state=$fhttfk_translatedcodes{$state}; # PREVIOUS # FIXME: Message regarded as similar if last char is identical; # sure that's always the differentiator? -wusel, 2009-11-09 if(defined($defs{$name}{PREV}{TIMESTAMP})) { if($defs{$name}{PREV}{TIMESTAMP} > time()-5) { if(defined($defs{$name}{PREV}{STATE})) { if($defs{$name}{PREV}{STATE} eq $state) { Log3 $name, 4, sprintf("FHTTK skipping state $state as last similar telegram was received less than 5 (%s) secs ago", time()-$defs{$name}{PREV}{TIMESTAMP}); return ""; } } } } if (! defined($defs{$name}{READINGS}{"Previous"})) { $defs{$name}{READINGS}{"Previous"}{VAL} = ""; $defs{$name}{READINGS}{"Previous"}{TIME} = ""; } if (defined($defs{$name}{PREV}{STATE}) && $defs{$name}{PREV}{STATE} ne $state) { my $prevState = $defs{$name}{PREV}{STATE}; my ($windowReading,$windowState) = split(/:/, $fhttfk_codes{$prevState}); $defs{$name}{READINGS}{"Previous"}{VAL} = $windowState if defined($windowState) && $windowState ne ""; $defs{$name}{READINGS}{"Previous"}{TIME} = TimeNow(); } $def->{PREVTIMESTAMP} = defined($defs{$name}{PREV}{TIMESTAMP})?$defs{$name}{PREV}{TIMESTAMP}:time(); $def->{PREVSTATE} = defined($def->{STATE})?$def->{STATE}:"Unknown"; $defs{$name}{PREV}{STATE}=$state; # # from here readings are effectively updated # readingsBeginUpdate($def); #READINGS my ($reading,$val) = split(/:/, $fhttfk_codes{$state}); readingsBulkUpdate($def, $reading, $val); $defs{$name}{PREV}{TIMESTAMP} = time(); # -wusel, 2009-11-09: According to http://fhz4linux.info/tiki-index.php?page=FHT+protocol, # FHT80TF usually transmitts between 60 and 240 seconds. (255-256 sec in # my experience ...) If we got no fresh data for over 5 minutes (300 sec), # flag this. if($defs{$name}{PREV}{TIMESTAMP}+720 < time()) { readingsBulkUpdate($def, "Reliability", "dead"); } elsif($defs{$name}{PREV}{TIMESTAMP}+600 < time()) { readingsBulkUpdate($def, "Reliability", "low"); } elsif($defs{$name}{PREV}{TIMESTAMP}+300 < time()) { readingsBulkUpdate($def, "Reliability", "medium"); } else { readingsBulkUpdate($def, "Reliability", "ok"); } # Flag the battery warning separately if($state eq "11" || $state eq "12") { readingsBulkUpdate($def, "Battery", "Low"); } else { readingsBulkUpdate($def, "Battery", "ok"); } #CHANGED readingsBulkUpdate($def, "state", $val); $def->{OPEN} = lc($val) eq "open" ? 1 : 0; Log3 $name, 4, "FHTTK Device $name ($reading: $val)"; # # now we are done with updating readings # readingsEndUpdate($def, 1); return $def->{NAME}; } ############################# 1; =pod =item summary support for the window sensor fht80tf and fht80tf-2 =item summary_DE Einbindung des fht80tf und fht80tf-2 Fensterkontaktes =begin html

CUL_FHTTK

=end html =begin html_DE

CUL_FHTTK

=end html_DE =cut