diff --git a/fhem/CHANGED b/fhem/CHANGED index 9dc4818d2..29badb9c8 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -14,6 +14,7 @@ - feature: FHEMWEB redirectCmds attribute added - feature: CUL_TX minsecs attribute (by Arno) - feature: webCmd in smallScreen added + - feature: TRX modules by Willi - 2011-12-31 (5.2) - bugfix: applying smallscreen attributes to firefox/opera diff --git a/fhem/FHEM/45_TRX.pm b/fhem/FHEM/45_TRX.pm new file mode 100755 index 000000000..dc5845767 --- /dev/null +++ b/fhem/FHEM/45_TRX.pm @@ -0,0 +1,363 @@ +################################################################################# +# 45_TRX.pm +# Module for FHEM +# +# Tested with RFXtrx-Receiver (433.92MHz, USB) +# (see http://www.RFXCOM.com/). +# To use this module, you need to define an RFXTRX transceiver: +# define RFXTRX TRX /dev/ttyUSB0 +# +# The module also has code to access a RFXtrx transceiver attached via LAN. +# +# To use it define the IP-Adresss and the Port: +# define RFXTRX TRX 192.168.169.111:10001 +# optionally you may issue not to initialize the device (useful if you share an RFXtrx device with other programs) +# define RFXTRX TRX 192.168.169.111:10001 noinit +# +# The RFXtrx transceivers supports lots of protocols that may be implemented for FHEM +# writing the appropriate FHEM modules. See the +# +# Willi Herzig, 2012 +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +################################################################################# +# derived from 00_CUL.pm +# +########################### +# $Id: +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); + +my $last_rmsg = "abcd"; +my $last_time = 1; + +sub TRX_Clear($); +sub TRX_Read($); +sub TRX_Ready($); +sub TRX_Parse($$$$); + +sub +TRX_Initialize($) +{ + my ($hash) = @_; + + + require "$attr{global}{modpath}/FHEM/DevIo.pm"; + +# Provider + $hash->{ReadFn} = "TRX_Read"; + $hash->{WriteFn} = "TRX_Write"; + $hash->{Clients} = + ":TRX_WEATHER:TRX_SECURITY:TRX_LIGHT:"; + my %mc = ( + "1:TRX_WEATHER" => "^..(50|52|54|55|56).*", + "2:TRX_SECURITY" => "^..(20).*", + "3:TRX_LIGHT" => "^..(10).*", + "4:TRX_ELSE" => "^..(0|3|4|6|7|8|9).*", + ); + $hash->{MatchList} = \%mc; + + $hash->{ReadyFn} = "TRX_Ready"; + +# Normal devices + $hash->{DefFn} = "TRX_Define"; + $hash->{UndefFn} = "TRX_Undef"; + $hash->{GetFn} = "TRX_Get"; + $hash->{SetFn} = "TRX_Set"; + $hash->{StateFn} = "TRX_SetState"; + $hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 do_not_init:1:0 addvaltrigger:1:0 longids loglevel:0,1,2,3,4,5,6"; + $hash->{ShutdownFn} = "TRX_Shutdown"; +} + +##################################### +sub +TRX_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + return "wrong syntax: define TRX devicename [noinit]" + if(@a != 3 && @a != 4); + + DevIo_CloseDev($hash); + + my $name = $a[0]; + my $dev = $a[2]; + my $opt = $a[3] if(@a == 4);; + + if($dev eq "none") { + Log 1, "TRX: $name device is none, commands will be echoed only"; + $attr{$name}{dummy} = 1; + return undef; + } + + if(defined($opt)) { + if($opt eq "noinit") { + Log 1, "TRX: $name no init is done"; + $attr{$name}{do_not_init} = 1; + } else { + return "wrong syntax: define TRX devicename [noinit]" + } + } + + + $hash->{DeviceName} = $dev; + my $ret = DevIo_OpenDev($hash, 0, "TRX_DoInit"); + return $ret; +} + +##################################### +# Input is hexstring +sub +TRX_Write($$$) +{ + my ($hash,$fn,$msg) = @_; + my $name = $hash->{NAME}; + my $ll5 = GetLogLevel($name,5); + + return if(!defined($fn)); + + my $bstring; + $bstring = "$fn$msg"; + Log $ll5, "$hash->{NAME} sending $bstring"; + + DevIo_SimpleWrite($hash, $bstring, 1); +} + + +##################################### +sub +TRX_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 +TRX_Shutdown($) +{ + my ($hash) = @_; + return undef; +} + +##################################### +sub +TRX_Set($@) +{ + my ($hash, @a) = @_; + + my $msg; + my $name=$a[0]; + my $reading= $a[1]; + $msg="$name => No Set function ($reading) implemented"; + return $msg; +} + +##################################### +sub +TRX_Get($@) +{ + my ($hash, @a) = @_; + + my $msg; + my $name=$a[0]; + my $reading= $a[1]; + $msg="$name => No Get function ($reading) implemented"; + Log 1,$msg; + return $msg; +} + +##################################### +sub +TRX_SetState($$$$) +{ + my ($hash, $tim, $vt, $val) = @_; + return undef; +} + +sub +TRX_Clear($) +{ + my $hash = shift; + my $buf; + + # clear buffer: + if($hash->{USBDev}) { + while ($hash->{USBDev}->lookfor()) { + $buf = DevIo_SimpleRead($hash); + } + } + if($hash->{TCPDev}) { + # TODO + return $buf; + } +} + +##################################### +sub +TRX_DoInit($) +{ + my $hash = shift; + my $name = $hash->{NAME}; + my $err; + my $msg = undef; + my $buf; + my $char = undef ; + + + # Reset + my $init = pack('H*', "0D00000000000000000000000000"); + DevIo_SimpleWrite($hash, $init, 0); + DevIo_TimeoutRead($hash, 0.5); + + TRX_Clear($hash); + + if(defined($attr{$name}) && defined($attr{$name}{"do_not_init"})) { + Log 1, "TRX: defined with noinit. Do not send init string to device."; + $hash->{STATE} = "Initialized" if(!$hash->{STATE}); + + # Reset the counter + delete($hash->{XMIT_TIME}); + delete($hash->{NR_CMD_LAST_H}); + + return undef; + } + + # + # Get Status + $init = pack('H*', "0D00000102000000000000000000"); + DevIo_SimpleWrite($hash, $init, 0); + $buf = DevIo_TimeoutRead($hash, 0.1); + + if (! $buf) { + Log 1, "TRX: Initialization Error: No character read"; + return "TRX: Initialization Error $name: no char read"; + } elsif ($buf !~ m/^\x0d\x01\x00.........../) { + my $hexline = unpack('H*', $buf); + Log 1, "TRX: Initialization Error hexline='$hexline'"; + return "TRX: Initialization Error %name expected char=0x2c, but char=$char received."; + } else { + Log 1, "TRX: Init OK"; + $hash->{STATE} = "Initialized" if(!$hash->{STATE}); + } + # + + # Reset the counter + delete($hash->{XMIT_TIME}); + delete($hash->{NR_CMD_LAST_H}); + + return undef; +} + + +##################################### +# called from the global loop, when the select for hash->{FD} reports data +sub +TRX_Read($) +{ + my ($hash) = @_; + + my $name = $hash->{NAME}; + + my $char; + + my $mybuf = DevIo_SimpleRead($hash); + + if(!defined($mybuf) || length($mybuf) == 0) { + DevIo_Disconnected($hash); + return ""; + } + + my $TRX_data = $hash->{PARTIAL}; + #Log 5, "TRX/RAW: $TRX_data/$mybuf"; + $TRX_data .= $mybuf; + + #my $hexline = unpack('H*', $TRX_data); + #Log 1, "TRX: TRX_Read '$hexline'"; + + # first char as byte represents number of bytes of the message + my $num_bytes = ord($TRX_data); + + while(length($TRX_data) > $num_bytes) { + # the buffer contains at least the number of bytes we need + my $rmsg; + $rmsg = substr($TRX_data, 0, $num_bytes+1); + #my $hexline = unpack('H*', $rmsg); + #Log 1, "TRX_Read rmsg '$hexline'"; + $TRX_data = substr($TRX_data, $num_bytes+1);; + #$hexline = unpack('H*', $TRX_data); + #Log 1, "TRX_Read TRX_data '$hexline'"; + # + TRX_Parse($hash, $hash, $name, unpack('H*', $rmsg)); + } + #Log 1, "TRX_Read END"; + + $hash->{PARTIAL} = $TRX_data; +} + +sub +TRX_Parse($$$$) +{ + my ($hash, $iohash, $name, $rmsg) = @_; + + #Log 1, "TRX_Parse1 '$rmsg'"; + Log 5, "TRX_Parse1 '$rmsg'"; + + my %addvals; + # Parse only if message is different within 2 seconds + # (some Oregon sensors always sends the message twice, X10 security sensors even sends the message five times) + if (("$last_rmsg" ne "$rmsg") || (time() - $last_time) > 1) { + #Log 1, "TRX_Dispatch '$rmsg'"; + %addvals = (RAWMSG => $rmsg); + Dispatch($hash, $rmsg, \%addvals); + $hash->{"${name}_MSGCNT"}++; + $hash->{"${name}_TIME"} = TimeNow(); + $hash->{RAWMSG} = $rmsg; + } else { + #Log 1, "TRX_Dispatch '$rmsg' dup"; + #Log 1, "<-duplicate->"; + } + + $last_rmsg = $rmsg; + $last_time = time(); + +} + + +##################################### +sub +TRX_Ready($) +{ + my ($hash) = @_; + + return DevIo_OpenDev($hash, 1, "TRX_Ready") + if($hash->{STATE} eq "disconnected"); + + # This is relevant for windows/USB only + my $po = $hash->{USBDev}; + my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status; + return ($InBytes>0); +} + +1; diff --git a/fhem/FHEM/46_TRX_ELSE.pm b/fhem/FHEM/46_TRX_ELSE.pm new file mode 100755 index 000000000..938cb73ac --- /dev/null +++ b/fhem/FHEM/46_TRX_ELSE.pm @@ -0,0 +1,102 @@ +################################################################################# +# 46_TRX_ELSE.pm +# Modul for FHEM for unkown RFXTRX messages +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +################################## +# +# values for "set global verbose" +# 4: log unknown protocols +# 5: log decoding hexlines for debugging +# +# $Id: +package main; + +use strict; +use warnings; +use Switch; + +my $time_old = 0; + +sub +TRX_ELSE_Initialize($) +{ + my ($hash) = @_; + + $hash->{Match} = "^.*"; + $hash->{DefFn} = "TRX_ELSE_Define"; + $hash->{UndefFn} = "TRX_ELSE_Undef"; + $hash->{ParseFn} = "TRX_ELSE_Parse"; + $hash->{AttrList} = "IODev do_not_notify:1,0 loglevel:0,1,2,3,4,5,6"; +Log 1, "TRX_ELSE: Initialize"; + +} + +##################################### +sub +TRX_ELSE_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + my $a = int(@a); + #print "a0 = $a[0]"; + return "wrong syntax: define TRX_ELSE code" if(int(@a) != 3); + + my $name = $a[0]; + my $code = $a[2]; + + $hash->{CODE} = $code; + #$modules{TRX_ELSE}{defptr}{$name} = $hash; + $modules{TRX_ELSE}{defptr}{$code} = $hash; + AssignIoPort($hash); + + return undef; +} + +##################################### +sub +TRX_ELSE_Undef($$) +{ + my ($hash, $name) = @_; + delete($modules{TRX_ELSE}{defptr}{$name}); + return undef; +} + + +my $DOT = q{_}; + +sub +TRX_ELSE_Parse($$) +{ + my ($hash, $msg) = @_; + + my $time = time(); + if ($time_old ==0) { + Log 5, "TRX_ELSE: decoding delay=0 hex=$msg"; + } else { + my $time_diff = $time - $time_old ; + Log 5, "TRX_ELSE: decoding delay=$time_diff hex=$msg"; + } + $time_old = $time; + + # convert to binary + my $bin_msg = pack('H*', $msg); + #my $hexline = unpack('H*', $bin_msg); + #Log 1, "TRX_ELSE: 2 hex=$hexline"; + + # convert string to array of bytes. Skip length byte + my @rfxcom_data_array = (); + foreach (split(//, substr($bin_msg,1))) { + push (@rfxcom_data_array, ord($_) ); + } + + Log 0, "TRX_ELSE: hex=$msg"; + + return "Test"; +} + +1; diff --git a/fhem/FHEM/46_TRX_LIGHT.pm b/fhem/FHEM/46_TRX_LIGHT.pm new file mode 100755 index 000000000..52ee22411 --- /dev/null +++ b/fhem/FHEM/46_TRX_LIGHT.pm @@ -0,0 +1,425 @@ +################################################################################# +# 46_TRX_LIGHT.pm +# +# Modul for FHEM for +# "X10" -> X10 lighting +# "ARC" -> ARC +# "AB400D" -> ELRO AB400D +# "WAVEMAN" -> Waveman +# "EMW200" -> Chacon EMW200 +# "IMPULS" -> IMPULS +# +# - ms14a: motion sensor +# - x10: generic X10 sensor +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +################################## +# +# values for "set global verbose" +# 4: log unknown protocols +# 5: log decoding hexlines for debugging +# +# $Id: 43_TRX_LIGHT.pm 1098 2011-11-12 07:51:08Z rudolfkoenig $ +package main; + +use strict; +use warnings; +use Switch; + +# Debug this module? YES = 1, NO = 0 +my $TRX_LIGHT_debug = 0; + +my $time_old = 0; + +my $TRX_LIGHT_type_default = "ds10a"; +my $TRX_LIGHT_X10_type_default = "x10"; + +my $DOT = q{_}; + +my %light_device_codes = ( # HEXSTRING => "NAME", "name of reading", + 0x00 => [ "X10", "light" ], + 0x01 => [ "ARC", "light" ], + 0x02 => [ "AB400D", "light" ], + 0x03 => [ "WAVEMAN", "light" ], + 0x04 => [ "EMW200", "light"], + 0x05 => [ "IMPULS", "light"], +); + +my %light_device_commands = ( # HEXSTRING => commands + 0x00 => [ "off", "on", "dim", "bright", "all_off", "all_on", ""], + 0x01 => [ "off", "on", "", "", "all_off", "all_on", "chime"], + 0x02 => [ "off", "on", "", "", "", "", ""], + 0x03 => [ "off", "on", "", "", "", "", ""], + 0x04 => [ "off", "on", "", "", "all_off", "all_on", ""], + 0x05 => [ "off", "on", "", "", "", "", ""], +); + +my %light_device_c2b; # DEVICE_TYPE->hash (reverse of light_device_codes) + +# Get the binary value for a command +# return -1 if command not valid dor dev_type +sub TRX_LIGHT_cmd_to_binary { + my ($dev_type, $command) = @_; + + return -1; +} + + +sub +TRX_LIGHT_Initialize($) +{ + my ($hash) = @_; + + foreach my $k (keys %light_device_codes) { + $light_device_c2b{$light_device_codes{$k}->[0]} = $k; + } + + #$hash->{Match} = "^\\).*"; # 0x29 + $hash->{Match} = "^(\\ |\\)).*"; # 0x20 or 0x29 + $hash->{SetFn} = "TRX_LIGHT_Set"; + $hash->{DefFn} = "TRX_LIGHT_Define"; + $hash->{UndefFn} = "TRX_LIGHT_Undef"; + $hash->{ParseFn} = "TRX_LIGHT_Parse"; + $hash->{AttrList} = "IODev do_not_notify:1,0 loglevel:0,1,2,3,4,5,6"; + +} + +##################################### +sub +TRX_LIGHT_SetState($$$$) +{ + my ($hash, $tim, $vt, $val) = @_; + + $val = $1 if($val =~ m/^(.*) \d+$/); + # to be done. Just accept everything right now. + #return "Undefined value $val" if(!defined($fs20_c2b{$val})); + return undef; +} + +################################### +sub +TRX_LIGHT_Set($@) +{ + my ($hash, @a) = @_; + my $ret = undef; + my $na = int(@a); + + return "no set value specified" if($na < 2 || $na > 3); + + # look for device_type + + my $name = $a[0]; + my $command = $a[1]; + + my $device_type = $hash->{TRX_LIGHT_type}; + my $deviceid = $hash->{TRX_LIGHT_deviceid}; + + my $house; + my $unit; + if ($deviceid =~ /(.)(.*)/ ) { + $house = ord("$1"); + $unit = $2; + } else { + Log 4,"TRX_LIGHT_Set wrong deviceid: name=$name device_type=$device_type, deviceid=$deviceid"; + return "error set name=$name deviveid=$deviceid"; + } + + my $device_type_num = $light_device_c2b{$device_type}; + if(!defined($device_type_num)) { + return "Unknown device_type, choose one of " . + join(" ", sort keys %light_device_c2b); + } + + # Now check if the command is valid and retrieve the command id: + my $rec = $light_device_commands{$device_type_num}; + my $i; + for ($i=0; $i <= $#$rec && ($rec->[$i] ne $command); $i++) { ;} + + if($i > $#$rec) { + my $error = "Unknown command $command, choose one of " . join(" ", sort @$rec); + Log 4, $error; + return $error; + } + + + my $seqnr = 0; + my $cmnd = $i; + + my $hex_command = sprintf "0710%02x%02x%02x%02x%02x00", $device_type_num, $seqnr, $house, $unit, $cmnd; + Log 1,"TRX_LIGHT_Set name=$name device_type=$device_type, deviceid=$deviceid house=$house, unit=$unit command=$command" if ($TRX_LIGHT_debug == 1); + Log 1,"TRX_LIGHT_Set hexline=$hex_command" if ($TRX_LIGHT_debug == 1); + + #IOWrite($hash, pack('H*', $hex_command)); + IOWrite($hash, "", $hex_command); + + my $tn = TimeNow(); + $hash->{CHANGED}[0] = $command; + $hash->{STATE} = $command; + $hash->{READINGS}{state}{TIME} = $tn; + $hash->{READINGS}{state}{VAL} = $command; + + return $ret; +} + + +##################################### +sub +TRX_LIGHT_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + my $a = int(@a); + + if(int(@a) != 5 && int(@a) != 7) { + Log 1,"TRX_LIGHT wrong syntax '@a'. \nCorrect syntax is 'define TRX_LIGHT [ ]'"; + return "wrong syntax: define TRX_LIGHT [ ]"; + } + + + my $name = $a[0]; + + my $type = lc($a[2]); + my $deviceid = $a[3]; + my $devicelog = $a[4]; + + + $type = uc($type); + + my $device_name = "TRX".$DOT.$type.$DOT.$deviceid; + + if ($type ne "X10" && $type ne "ARC" && $type ne "MS14A" && $type ne "AB400D" && $type ne "WAVEMAN" && $type ne "EMW200" && $type ne "IMPULS") { + Log 1,"RFX10SEC define: wrong type: $type"; + return "RFX10SEC: wrong type: $type"; + } + + $hash->{TRX_LIGHT_deviceid} = $deviceid; + $hash->{TRX_LIGHT_devicelog} = $devicelog; + $hash->{TRX_LIGHT_type} = $type; + #$hash->{TRX_LIGHT_CODE} = $deviceid; + $modules{TRX_LIGHT}{defptr}{$device_name} = $hash; + + + if (int(@a) == 7) { + # there is a second deviceid: + # + my $deviceid2 = $a[5]; + my $devicelog2 = $a[6]; + + my $device_name2 = "TRX".$DOT.$type.$DOT.$deviceid2; + + $hash->{TRX_LIGHT_deviceid2} = $deviceid2; + $hash->{TRX_LIGHT_devicelog2} = $devicelog2; + #$hash->{TRX_LIGHT_CODE2} = $deviceid2; + $modules{TRX_LIGHT}{defptr2}{$device_name2} = $hash; + } + + AssignIoPort($hash); + + return undef; +} + +##################################### +sub +TRX_LIGHT_Undef($$) +{ + my ($hash, $name) = @_; + delete($modules{TRX_LIGHT}{defptr}{$name}); + return undef; +} + + +##################################### +sub TRX_LIGHT_parse_X10 { + my $bytes = shift; + + my $error; + + + #my $device; + + my $subtype = $bytes->[1]; + my $dev_type; + my $dev_reading; + my $rest; + if (exists $light_device_codes{$subtype}) { + my $rec = $light_device_codes{$subtype}; + ($dev_type, $dev_reading) = @$rec; + } else { + $error = sprintf "TRX_LIGHT: error undefined subtype=%02x", $subtype; + Log 1, $error; + return $error; + } + + my $dev_first = "?"; + + my %x10_housecode = + ( + 0x41 => "A", + 0x42 => "B", + 0x43 => "C", + 0x44 => "D", + 0x45 => "E", + 0x46 => "F", + 0x47 => "G", + 0x48 => "H", + 0x49 => "I", + 0x4A => "J", + 0x4B => "K", + 0x4C => "L", + 0x4D => "M", + 0x4E => "N", + 0x4F => "O", + 0x50 => "P", + ); + my $devnr = $bytes->[3]; # housecode + if (exists $x10_housecode{$devnr}) { + $dev_first = $x10_housecode{$devnr}; + } else { + $error = sprintf "TRX_SECURITY: x10_housecode wrong housecode=%02x", $devnr; + Log 1, $error; + return $error; + } + + my $unit = $bytes->[4]; # unitcode + + my $device = sprintf '%s%0d', $dev_first, $unit; + + my $data = $bytes->[5]; + my $hexdata = sprintf '%02x', $data; + + + my $command = ""; + if ($data == 0xff) { + $command = "illegal_cmd"; + } else { + if (exists $light_device_commands{$subtype}) { + my $code = $light_device_commands{$subtype}; + if (exists $code->[$data]) { + $command = $code->[$data]; + } else { + $error = sprintf "TRX_LIGHT: out of range for subtype=%02x data=%02x", $subtype, $data; + Log 1, $error; + return $error; + } + } + } + + #my @res; + my $current = ""; + + #-------------- + my $device_name = "TRX".$DOT.$dev_type.$DOT.$device; + Log 1, "TRX_LIGHT: device_name=$device_name data=$hexdata" if ($TRX_LIGHT_debug == 1); + + my $firstdevice = 1; + my $def = $modules{TRX_LIGHT}{defptr}{$device_name}; + if(!$def) { + $firstdevice = 0; + $def = $modules{TRX_LIGHT}{defptr2}{$device_name}; + if (!$def) { + Log 1, "UNDEFINED $device_name TRX_SECURITY $dev_type $device $dev_reading" if ($TRX_LIGHT_debug == 1); + Log 3, "TRX_LIGHT: TRX_LIGHT Unknown device $device_name, please define it"; + return "UNDEFINED $device_name TRX_LIGHT $dev_type $device $dev_reading"; + + } + } + + # Use $def->{NAME}, because the device may be renamed: + my $name = $def->{NAME}; + + Log 1, "TRX_LIGHT: $name devn=$device_name first=$firstdevice command=$command, cmd=$hexdata" if ($TRX_LIGHT_debug == 1); + + my $n = 0; + my $tm = TimeNow(); + my $val = ""; + + my $device_type = $def->{TRX_LIGHT_type}; + + my $sensor = ""; + + if ($device_type eq "MS14A") { + # for ms14a behave like x10, but flip second deviceid + $device_type = "X10"; + if ($firstdevice == 1) { + $command = ($command eq "on") ? "alert" : "normal" ; + } else { + $command = ($command eq "on") ? "off" : "on" ; + } + } + + #if ($device_type eq "X10") { + if (1) { + # try to use it for all types: + $current = $command; + + $sensor = $firstdevice == 1 ? $def->{TRX_LIGHT_devicelog} : $def->{TRX_LIGHT_devicelog2}; + $val .= $current; + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $current; + $def->{CHANGED}[$n++] = $sensor . ": " . $current; + } else { + Log 1, "TRX_LIGHT: X10 error unknown sensor type=$device_type $name devn=$device_name first=$firstdevice type=$command, user=$device (hex $hexdata)"; + return "TRX_LIGHT X10 error unknown sensor type=$device_type for $device_name device=$device"; + } + + if (($firstdevice == 1) && $val) { + $def->{STATE} = $val; + $def->{TIME} = $tm; + $def->{CHANGED}[$n++] = $val; + } + + DoTrigger($name, undef); + + return ""; +} + +##################################### +sub +TRX_LIGHT_Parse($$) +{ + my ($iohash, $hexline) = @_; + + my $time = time(); + # convert to binary + my $msg = pack('H*', $hexline); + if ($time_old ==0) { + Log 5, "TRX_LIGHT: decoding delay=0 hex=$hexline"; + } else { + my $time_diff = $time - $time_old ; + Log 5, "TRX_LIGHT: decoding delay=$time_diff hex=$hexline"; + } + $time_old = $time; + + # convert string to array of bytes. Skip length byte + my @rfxcom_data_array = (); + foreach (split(//, substr($msg,1))) { + push (@rfxcom_data_array, ord($_) ); + } + + my $num_bytes = ord($msg); + + if ($num_bytes < 3) { + return; + } + + my $type = $rfxcom_data_array[0]; + + #Log 1, "TRX_LIGHT: num_bytes=$num_bytes hex=$hexline type=$type" if ($TRX_LIGHT_debug == 1); + my $res = ""; + if ($type == 0x10) { + Log 1, "TRX_LIGHT: X10 num_bytes=$num_bytes hex=$hexline" if ($TRX_LIGHT_debug == 1); + $res = TRX_LIGHT_parse_X10(\@rfxcom_data_array); + Log 1, "TRX_LIGHT: unsupported hex=$hexline" if ($res ne "" && $res !~ /^UNDEFINED.*/); + return $res; + } else { + Log 0, "TRX_LIGHT: not implemented num_bytes=$num_bytes hex=$hexline"; + } + + return ""; +} + +1; diff --git a/fhem/FHEM/46_TRX_SECURITY.pm b/fhem/FHEM/46_TRX_SECURITY.pm new file mode 100755 index 000000000..ffc03ae65 --- /dev/null +++ b/fhem/FHEM/46_TRX_SECURITY.pm @@ -0,0 +1,401 @@ +################################################################################# +# 46_TRX_SECURITY.pm +# +# Modul for FHEM for X10, KD101, Visonic +# - X10 security messages tested for +# - ds10a: X10 Door / Window Sensor or compatible devices +# - ss10a: X10 motion sensor +# - sd90: Marmitek smoke detector +# - kr18: X10 remote control +# +################################## +# +# Willi Herzig, 2012 +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +################################## +# +# Some code from X10security code is derived from http://www.xpl-perl.org.uk/. +# xpl-perl/lib/xPL/RF/X10Security.pm: +# Thanks a lot to Mark Hindess who wrote xPL. +# +#SEE ALSO +# Project website: http://www.xpl-perl.org.uk/ +# AUTHOR: Mark Hindess, soft-xpl-perl@temporalanomaly.com +# +#Copyright (C) 2007, 2009 by Mark Hindess +# +#This library is free software; you can redistribute it and/or modify +#it under the same terms as Perl itself, either Perl version 5.8.7 or, +#at your option, any later version of Perl 5 you may have available. +# +################################## +# +# values for "set global verbose" +# 4: log unknown protocols +# 5: log decoding hexlines for debugging +# +# $Id: 43_TRX_SECURITY.pm 1098 2011-11-12 07:51:08Z rudolfkoenig $ +package main; + +use strict; +use warnings; +use Switch; + +# Debug this module? YES = 1, NO = 0 +my $TRX_SECURITY_debug = 0; + +my $time_old = 0; + +my $TRX_SECURITY_type_default = "ds10a"; + +my $DOT = q{_}; + +sub +TRX_SECURITY_Initialize($) +{ + my ($hash) = @_; + + $hash->{Match} = "^(\\ |\\)).*"; # 0x20 or 0x29 + $hash->{DefFn} = "TRX_SECURITY_Define"; + $hash->{UndefFn} = "TRX_SECURITY_Undef"; + $hash->{ParseFn} = "TRX_SECURITY_Parse"; + $hash->{AttrList} = "IODev do_not_notify:1,0 loglevel:0,1,2,3,4,5,6"; + +} + +##################################### +sub +TRX_SECURITY_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + my $a = int(@a); + + if(int(@a) != 5 && int(@a) != 7) { + Log 1,"TRX_SECURITY wrong syntax '@a'. \nCorrect syntax is 'define TRX_SECURITY [ ]'"; + return "wrong syntax: define TRX_SECURITY [ ]"; + } + + + my $name = $a[0]; + + my $type = lc($a[2]); + my $deviceid = $a[3]; + my $devicelog = $a[4]; + + + $type = uc($type); + + my $device_name = "TRX".$DOT.$type.$DOT.$deviceid; + + if ($type ne "DS10A" && $type ne "SD90" && $type ne "MS10A" && $type ne "MS14A" && $type ne "KR18" && $type ne "KD101" && $type ne "VISONIC_WINDOW" & $type ne "VISONIC_MOTION" & $type ne "VISONIC_REMOTE") { + Log 1,"RFX10SEC define: wrong type: $type"; + return "RFX10SEC: wrong type: $type"; + } + + $hash->{TRX_SECURITY_deviceid} = $deviceid; + $hash->{TRX_SECURITY_devicelog} = $devicelog; + $hash->{TRX_SECURITY_type} = $type; + #$hash->{TRX_SECURITY_CODE} = $deviceid; + $modules{TRX_SECURITY}{defptr}{$device_name} = $hash; + + + if (int(@a) == 7) { + # there is a second deviceid: + # + my $deviceid2 = $a[5]; + my $devicelog2 = $a[6]; + + my $device_name2 = "TRX_SECURITY".$DOT.$deviceid2; + + $hash->{TRX_SECURITY_deviceid2} = $deviceid2; + $hash->{TRX_SECURITY_devicelog2} = $devicelog2; + #$hash->{TRX_SECURITY_CODE2} = $deviceid2; + $modules{TRX_SECURITY}{defptr2}{$device_name2} = $hash; + } + + AssignIoPort($hash); + + return undef; +} + +##################################### +sub +TRX_SECURITY_Undef($$) +{ + my ($hash, $name) = @_; + delete($modules{TRX_SECURITY}{defptr}{$name}); + return undef; +} + + + +##################################### +sub TRX_SECURITY_parse_X10Sec { + my $bytes = shift; + + my $error; + + my $subtype = $bytes->[1]; + + my $device; + if ($subtype >= 3) { + $device = sprintf '%02x%02x%02x', $bytes->[3], $bytes->[4], $bytes->[5]; + } else { + # that's how we do it on 43_RFXX10REC.pm + $device = sprintf '%02x%02x', $bytes->[5], $bytes->[3]; + } + + my %security_devtype = + ( # HEXSTRING => + 0x00 => [ "DS10A", "Window" ], # X10 security door/window sensor + 0x01 => [ "MS10A", "motion" ], # X10 security motion sensor + 0x02 => [ "KR18", "key" ], # X10 security remote (no alive packets) + 0x03 => [ "KD101", "smoke" ], # KD101 (no alive packets) + 0x04 => [ "VISONIC_WINDOW", "window" ], # Visonic PowerCode door/window sensor – primary contact (with alive packets) + 0x05 => [ "VISONIC_MOTION", "motion" ], # Visonic PowerCode motion sensor (with alive packets) + 0x06 => [ "VISONIC_REMOTE", "key" ], # Visonic CodeSecure (no alive packets) + 0x07 => [ "VISONIC_WINDOW", "window" ], # Visonic PowerCode door/window sensor – auxiliary contact (no alive packets) + ); + + my $dev_type; + my $dev_reading; + if (exists $security_devtype{$subtype}) { + my $rec = $security_devtype{$subtype}; + if (ref $rec) { + ($dev_type, $dev_reading ) = @$rec; + } else { + $error = "TRX_SECURITY: x10_devtype wrong for subtype=$subtype"; + Log 1, $error; + return $error; + } + } else { + $error = "TRX_SECURITY: error undefined subtype=$subtype"; + Log 1, $error; + return $error; + } + + #Log 4, "device_type=$device_type"; + + #-------------- + my $device_name = "TRX".$DOT.$dev_type.$DOT.$device; + Log 4, "device_name=$device_name"; + + my $firstdevice = 1; + my $def = $modules{TRX_SECURITY}{defptr}{$device_name}; + if(!$def) { + $firstdevice = 0; + $def = $modules{TRX_SECURITY}{defptr2}{$device_name}; + if (!$def) { + Log 1, "UNDEFINED $device_name TRX_SECURITY $dev_type $device $dev_reading"; + Log 3, "TRX_SECURITY: TRX_SECURITY Unknown device $device_name, please define it"; + return "UNDEFINED $device_name TRX_SECURITY $dev_type $device $dev_reading"; + } + } + + # Use $def->{NAME}, because the device may be renamed: + my $name = $def->{NAME}; + + my $data = $bytes->[6]; + + my $hexdata = sprintf '%02x', $data; + + my %x10_security = + ( + 0x00 => ['X10Sec', 'normal', 'min_delay', ''], + 0x01 => ['X10Sec', 'normal', 'max_delay', ''], + + 0x02 => ['X10Sec', 'alert', 'min_delay', ''], + 0x03 => ['X10Sec', 'alert', 'max_delay', ''], + + 0x04 => ['X10Sec', 'alert', '', ''], + 0x05 => ['X10Sec', 'normal', '', ''], + + 0x06 => ['X10Sec', 'alert', '', ''], + 0x07 => ['X10Sec', 'normal', '', ''], + + 0x08 => ['X10Sec', 'tamper', '', ''], + + 0x09 => ['X10Sec', 'Security-Arm_Away', 'min_delay', ''], # kr18 + 0x0a => ['X10Sec', 'Security-Arm_Away', 'max_delay', ''], # kr18 + 0x0b => ['X10Sec', 'Security-Arm_Home', 'min_delay', ''], # kr18 + 0x0c => ['X10Sec', 'Security-Arm_Home', 'max_delay', ''], # kr18 + 0x0d => ['X10Sec', 'Security-Disarm', 'min_delay', ''], # kr18 + + 0x10 => ['X10Sec', 'ButtonA-on', '', ''], # kr18 + 0x11 => ['X10Sec', 'ButtonA-off', '', ''], # kr18 + 0x12 => ['X10Sec', 'ButtonB-on', '', ''], # kr18 + 0x13 => ['X10Sec', 'ButtonB-off', '', ''], # kr18 + + 0x14 => ['X10Sec', 'dark', '', ''], + 0x15 => ['X10Sec', 'light', '', ''], + 0x16 => ['X10Sec', 'normal', '', 'batt_low'], + + 0x17 => ['X10Sec', 'pair KD101', '', ''], + + ); + + my $command = ""; + my $type = ""; + my $delay = ""; + my $battery = ""; + my @res; + if (exists $x10_security{$data}) { + my $rec = $x10_security{$data}; + if (ref $rec) { + ($type, $command, $delay, $battery) = @$rec; + } else { + $command = $rec; + } + } else { + Log 1, "TRX_SECURITY undefined command cmd=$data device-nr=$device, hex=$hexdata"; + return "TRX_SECURITY undefined command"; + } + + my $battery_level = $bytes->[7] & 0x0f; + if (($battery eq "") && ($dev_type ne "kd101")) { + if ($battery_level == 0x9) { $battery = 'batt_ok'} + elsif ($battery_level == 0x0) { $battery = 'batt_low'} + else { + Log 1,"TRX-X10: X10Sec unkown battery_level=$battery_level"; + } + } + + my $current = ""; + + Log 1, "TRX_SECURITY: $name devn=$device_name first=$firstdevice subtype=$subtype command=$command, delay=$delay, batt=$battery cmd=$hexdata" if ($TRX_SECURITY_debug == 1); + + my $n = 0; + my $tm = TimeNow(); + my $val = ""; + + my $device_type = $def->{TRX_SECURITY_type}; + + my $sensor = ""; + + if ($device_type eq "sd90") { + $sensor = $firstdevice == 1 ? $def->{TRX_SECURITY_devicelog} : $def->{TRX_SECURITY_devicelog2}; + } else { + $sensor = $def->{TRX_SECURITY_devicelog}; + } + + $current =$command; + if (($device_type eq "DS10A") || ($device_type eq "VISONIC_WINDOW")) { + $current = "Error"; + $current = "Open" if ($command eq "alert"); + $current = "Closed" if ($command eq "normal"); + } + + if (($dev_type ne "kr18") || ($dev_type ne "VISONIC_REMOTE")) { + if ($firstdevice == 1) { + $val .= $current; + } + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $current; + $def->{CHANGED}[$n++] = $sensor . ": " . $current; + + if (($def->{STATE} ne $val)) { + $sensor = "statechange"; + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $current; + $def->{CHANGED}[$n++] = $sensor . ": " . $current; + } + } else { + # kr18 remote control or VISONIC_REMOTE + $current = $command; + + #$sensor = $def->{TRX_SECURITY_devicelog}; + $val = $current; + #$def->{READINGS}{$sensor}{TIME} = $tm; + #$def->{READINGS}{$sensor}{VAL} = $current; + #$def->{CHANGED}[$n++] = $sensor . ": " . $current; + + my @cmd_split = split(/-/, $command); + $sensor = $cmd_split[0]; + $current = $cmd_split[1]; + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $current; + $def->{CHANGED}[$n++] = $sensor . ": " . $current; + } + + if ($battery ne "") { + $sensor = "battery"; + $current = "Error"; + $current = "ok" if ($battery eq "batt_ok"); + $current = "low" if ($battery eq "batt_low"); + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $current; + $def->{CHANGED}[$n++] = $sensor . ": " . $current; + } + + if ($delay ne '') { + $sensor = "delay"; + $current = "Error"; + $current = "min" if ($delay eq "min_delay"); + $current = "max" if ($delay eq "max_delay"); + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $current; + $def->{CHANGED}[$n++] = $sensor . ": " . $current; + } + + if (($firstdevice == 1) && $val) { + $def->{STATE} = $val; + $def->{TIME} = $tm; + $def->{CHANGED}[$n++] = $val; + } + + DoTrigger($name, undef); + + return ""; +} + + +sub +TRX_SECURITY_Parse($$) +{ + my ($iohash, $hexline) = @_; + + my $time = time(); + # convert to binary + my $msg = pack('H*', $hexline); + if ($time_old ==0) { + Log 5, "TRX_SECURITY: decoding delay=0 hex=$hexline"; + } else { + my $time_diff = $time - $time_old ; + Log 5, "TRX_SECURITY: decoding delay=$time_diff hex=$hexline"; + } + $time_old = $time; + + # convert string to array of bytes. Skip length byte + my @rfxcom_data_array = (); + foreach (split(//, substr($msg,1))) { + push (@rfxcom_data_array, ord($_) ); + } + + my $num_bytes = ord($msg); + + if ($num_bytes < 3) { + return; + } + + my $type = $rfxcom_data_array[0]; + + #Log 1, "TRX_SECURITY: X10Sec num_bytes=$num_bytes hex=$hexline type=$type" if ($TRX_SECURITY_debug == 1); + my $res = ""; + if ($type == 0x20) { + Log 1, "TRX_SECURITY: X10Sec num_bytes=$num_bytes hex=$hexline" if ($TRX_SECURITY_debug == 1); + $res = TRX_SECURITY_parse_X10Sec(\@rfxcom_data_array); + Log 1, "TRX_SECURITY: unsupported hex=$hexline" if ($res ne "" && $res !~ /^UNDEFINED.*/); + return $res; + } else { + Log 0, "TRX_SECURITY: not implemented num_bytes=$num_bytes hex=$hexline"; + } + + return ""; +} + +1; diff --git a/fhem/FHEM/46_TRX_WEATHER.pm b/fhem/FHEM/46_TRX_WEATHER.pm new file mode 100755 index 000000000..fadddeeed --- /dev/null +++ b/fhem/FHEM/46_TRX_WEATHER.pm @@ -0,0 +1,828 @@ +################################################################################# +# 46_TRX_WEATHER.pm +# Module for FHEM to decode weather sensor messages for RFXtrx +# +# The following devices are implemented to be received: +# +# temperature sensors (TEMP): +# * "THR128" is THR128/138, THC138 +# * "THGR132N" is THC238/268,THN132,THWR288,THRN122,THN122,AW129/131 +# * "THWR800" is THWR800 +# * "RTHN318" is RTHN318 +# * "TX3_T" is LaCrosse TX3, TX4, TX17 +# +# temperature/humidity sensors (TEMPHYDRO): +# * "THGR228N" is THGN122/123, THGN132, THGR122/228/238/268 +# * "THGR810" is THGR810 +# * "RTGR328" is RTGR328 +# * "THGR328" is THGR328 +# * "WTGR800_T" is WTGR800 +# * "THGR918" is THGR918, THGRN228, THGN500 +# * "TFATS34C" is TFA TS34C +# +# temperature/humidity/pressure sensors (TEMPHYDROBARO): +# * "BTHR918" is BTHR918 +# * "BTHR918N" is BTHR918N, BTHR968 +# +# rain gauge sensors (RAIN): +# * "RGR918" is RGR126/682/918 +# * "PCR800" is PCR800 +# * "TFA_RAIN" is TFA +# +# wind sensors (WIND): +# * "WTGR800_A" is WTGR800 +# * "WGR800_A" is WGR800 +# * "WGR918_A" is STR918, WGR918 +# * "TFA_WIND" is TFA +# +# derived from 41_OREGON.pm +# +# Willi Herzig, 2012 +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +################################## +# +# values for "set global verbose" +# 4: log unknown protocols +# 5: log decoding hexlines for debugging +# +# $Id: +package main; + +use strict; +use warnings; + +# Hex-Debugging into READING hexline? YES = 1, NO = 0 +my $TRX_HEX_debug = 0; + +my $time_old = 0; + +sub +TRX_WEATHER_Initialize($) +{ + my ($hash) = @_; + + $hash->{Match} = "^[\x38-\x78].*"; + #$hash->{Match} = "^[^\x30]"; + $hash->{DefFn} = "TRX_WEATHER_Define"; + $hash->{UndefFn} = "TRX_WEATHER_Undef"; + $hash->{ParseFn} = "TRX_WEATHER_Parse"; + $hash->{AttrList} = "IODev do_not_notify:1,0 loglevel:0,1,2,3,4,5,6"; + +} + +##################################### +sub +TRX_WEATHER_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + my $a = int(@a); + #print "a0 = $a[0]"; + return "wrong syntax: define TRX_WEATHER code" if(int(@a) != 3); + + my $name = $a[0]; + my $code = $a[2]; + + $hash->{CODE} = $code; + #$modules{TRX_WEATHER}{defptr}{$name} = $hash; + $modules{TRX_WEATHER}{defptr}{$code} = $hash; + AssignIoPort($hash); + + return undef; +} + +##################################### +sub +TRX_WEATHER_Undef($$) +{ + my ($hash, $name) = @_; + delete($modules{TRX_WEATHER}{defptr}{$name}); + return undef; +} + + + +######################################### +# From xpl-perl/lib/xPL/Util.pm: +sub hi_nibble { + ($_[0]&0xf0)>>4; +} +sub lo_nibble { + $_[0]&0xf; +} +sub nibble_sum { + my $c = $_[0]; + my $s = 0; + foreach (0..$_[0]-1) { + $s += hi_nibble($_[1]->[$_]); + $s += lo_nibble($_[1]->[$_]); + } + $s += hi_nibble($_[1]->[$_[0]]) if (int($_[0]) != $_[0]); + return $s; +} + +# -------------------------------------------- +# From xpl-perl/lib/xPL/RF/Oregon.pm: +# This function creates a simple key from a device type and message +# length (in bits). It is used to as the index for the parts table. +sub type_length_key { + ($_[0] << 8) + $_[1] +} + +# -------------------------------------------- +# sensor types + +my %types = + ( + # TEMP + type_length_key(0x50, 0x08) => + { + part => 'TEMP', method => \&common_temp, + }, + # TEMP HYDRO + type_length_key(0x52, 0x0a) => + { + part => 'TEMPHYDRO', method => \&common_temphydro, + }, + # TEMP HYDRO BARO + type_length_key(0x54, 0x0d) => + { + part => 'TEMPHYDROBARO', method => \&common_temphydrobaro, + }, + # RAIN + type_length_key(0x55, 0x0b) => + { + part => 'RAIN', method => \&common_rain, + }, + # WIND + type_length_key(0x56, 0x10) => + { + part => 'WIND', method => \&common_anemometer, + }, + ); + + +# -------------------------------------------- + +#my $DOT = q{.}; +# Important: change it to _, because FHEM uses regexp +my $DOT = q{_}; + +my @TRX_WEATHER_winddir_name=("N","NNE","NE","ENE","E","ESE","SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW"); + +# -------------------------------------------- +# The following functions are changed: +# - some parameter like "parent" and others are removed +# - @res array return the values directly (no usage of xPL::Message) + +sub temperature { + my ($bytes, $dev, $res, $off) = @_; + + my $temp = + ( + (($bytes->[$off] & 0x80) ? -1 : 1) * + (($bytes->[$off] & 0x7f)*256 + $bytes->[$off+1]) + )/10; + + push @$res, { + device => $dev, + type => 'temp', + current => $temp, + units => 'Grad Celsius' + } + +} + +sub humidity { + my ($bytes, $dev, $res, $off) = @_; + my $hum = $bytes->[$off]; + my $hum_str = ['dry', 'comfortable', 'normal', 'wet']->[$bytes->[$off+1]]; + push @$res, { + device => $dev, + type => 'humidity', + current => $hum, + string => $hum_str, + units => '%' + } +} + +sub pressure { + my ($bytes, $dev, $res, $off) = @_; + + #my $offset = 795 unless ($offset); + my $hpa = ($bytes->[$off])*256 + $bytes->[$off+1]; + my $forecast = { 0x00 => 'noforecast', + 0x01 => 'sunny', + 0x02 => 'partly', + 0x03 => 'cloudy', + 0x04 => 'rain', + }->{$bytes->[$off+2]} || 'unknown'; + push @$res, { + device => $dev, + type => 'pressure', + current => $hpa, + units => 'hPa', + forecast => $forecast, + } +} + +sub simple_battery { + my ($bytes, $dev, $res, $off) = @_; + + my $battery; + + my $battery_level = $bytes->[$off] & 0x0f; + if ($battery_level == 0x9) { $battery = 'ok'} + elsif ($battery_level == 0x0) { $battery = 'low'} + else { + $battery = sprintf("unknown-%02x",$battery_level); + } + + push @$res, { + device => $dev, + type => 'battery', + current => $battery, + } +} + +sub battery { + my ($bytes, $dev, $res, $off) = @_; + + my $battery; + + my $battery_level = ($bytes->[$off] & 0x0f) + 1; + + if ($battery_level > 5) { + $battery = sprintf("ok %d0%%",$battery_level); + } else { + $battery = sprintf("low %d0%%",$battery_level); + } + + push @$res, { + device => $dev, + type => 'battery', + current => $battery, + } +} + + +my @uv_str = + ( + qw/low low low/, # 0 - 2 + qw/medium medium medium/, # 3 - 5 + qw/high high/, # 6 - 7 + 'very high', 'very high', 'very high', # 8 - 10 + ); + +sub uv_string { + $uv_str[$_[0]] || 'dangerous'; +} + +# Test if to use longid for device type +sub use_longid { + my ($longids,$dev_type) = @_; + + return 0 if ($longids eq ""); + return 0 if ($longids eq "0"); + + return 1 if ($longids eq "1"); + return 1 if ($longids eq "ALL"); + + return 1 if(",$longids," =~ m/,$dev_type,/); + + return 0; +} + +# ------------------------------------------------------------ +# +sub common_anemometer { + my $type = shift; + my $longids = shift; + my $bytes = shift; + + my $subtype = sprintf "%02x", $bytes->[1]; + #Log 1,"subtype=$subtype"; + my $dev_type; + + my %devname = + ( # HEXSTRING => "NAME" + 0x01 => "WTGR800_A", + 0x02 => "WGR800_A", + 0x03 => "WGR918_A", + 0x04 => "TFA_WIND", + ); + + if (exists $devname{$bytes->[1]}) { + $dev_type = $devname{$bytes->[1]}; + } else { + Log 1,"TRX_WEATHER: error undefined subtype=$subtype"; + my @res = (); + return @res; + } + + #my $seqnbr = sprintf "%02x", $bytes->[2]; + #Log 1,"seqnbr=$seqnbr"; + + my $dev_str = $dev_type; + if (use_longid($longids,$dev_type)) { + $dev_str .= $DOT.sprintf("%02x", $bytes->[3]); + } + if ($bytes->[4] > 0) { + $dev_str .= $DOT.sprintf("%d", $bytes->[4]); + } + + my @res = (); + + # hexline debugging + if ($TRX_HEX_debug) { + my $hexline = ""; for (my $i=0;$i<@$bytes;$i++) { $hexline .= sprintf("%02x",$bytes->[$i]);} + push @res, { device => $dev_str, type => 'hexline', current => $hexline, units => 'hex', }; + } + + my $dir = $bytes->[5]*256 + $bytes->[6]; + my $dirname = $TRX_WEATHER_winddir_name[$dir/22.5]; + + my $avspeed = $bytes->[7]*256 + $bytes->[8]; + my $speed = $bytes->[9]*256 + $bytes->[10]; + + push @res, { + device => $dev_str, + type => 'speed', + current => $speed, + average => $avspeed, + units => 'mps', + } , { + device => $dev_str, + type => 'direction', + current => $dir, + string => $dirname, + units => 'degrees', + } + ; + simple_battery($bytes, $dev_str, \@res, 15); + + return @res; +} + + +# ----------------------------- +sub common_temp { + my $type = shift; + my $longids = shift; + my $bytes = shift; + + my $subtype = sprintf "%02x", $bytes->[1]; + #Log 1,"subtype=$subtype"; + my $dev_type; + + my %devname = + ( # HEXSTRING => "NAME" + 0x01 => "THR128", + 0x02 => "THGR132N", # was THGR228N, + 0x03 => "THWR800", + 0x04 => "RTHN318", + 0x05 => "TX3_T", + ); + + if (exists $devname{$bytes->[1]}) { + $dev_type = $devname{$bytes->[1]}; + } else { + Log 1,"RFX_WEATHER: error undefined subtype=$subtype"; + my @res = (); + return @res; + } + + #my $seqnbr = sprintf "%02x", $bytes->[2]; + #Log 1,"seqnbr=$seqnbr"; + + my $dev_str = $dev_type; + if (use_longid($longids,$dev_type)) { + $dev_str .= $DOT.sprintf("%02x", $bytes->[3]); + } + if ($bytes->[4] > 0) { + $dev_str .= $DOT.sprintf("%d", $bytes->[4]); + } + #Log 1,"dev_str=$dev_str"; + + my @res = (); + + # hexline debugging + if ($TRX_HEX_debug) { + my $hexline = ""; for (my $i=0;$i<@$bytes;$i++) { $hexline .= sprintf("%02x",$bytes->[$i]);} + push @res, { device => $dev_str, type => 'hexline', current => $hexline, units => 'hex', }; + } + + temperature($bytes, $dev_str, \@res, 5); + simple_battery($bytes, $dev_str, \@res, 7); + return @res; +} + +# ----------------------------- +sub common_temphydro { + my $type = shift; + my $longids = shift; + my $bytes = shift; + + my $subtype = sprintf "%02x", $bytes->[1]; + #Log 1,"subtype=$subtype"; + my $dev_type; + + my %devname = + ( # HEXSTRING => "NAME" + 0x01 => "THGR228N", # THGN122/123, THGN132, THGR122/228/238/268 + 0x02 => "THGR810", + 0x03 => "RTGR328", + 0x04 => "THGR328", + 0x05 => "WTGR800_T", + 0x06 => "THGR918", + 0x07 => "TFATS34C", + ); + + if (exists $devname{$bytes->[1]}) { + $dev_type = $devname{$bytes->[1]}; + } else { + Log 1,"RFX_WEATHER: error undefined subtype=$subtype"; + my @res = (); + return @res; + } + + my $dev_str = $dev_type; + if (use_longid($longids,$dev_type)) { + $dev_str .= $DOT.sprintf("%02x", $bytes->[3]); + } + if ($bytes->[4] > 0) { + $dev_str .= $DOT.sprintf("%d", $bytes->[4]); + } + #Log 1,"dev_str=$dev_str"; + + my @res = (); + + # hexline debugging + if ($TRX_HEX_debug) { + my $hexline = ""; for (my $i=0;$i<@$bytes;$i++) { $hexline .= sprintf("%02x",$bytes->[$i]);} + push @res, { device => $dev_str, type => 'hexline', current => $hexline, units => 'hex', }; + } + + temperature($bytes, $dev_str, \@res, 5); + humidity($bytes, $dev_str, \@res, 7); + simple_battery($bytes, $dev_str, \@res, 9); + return @res; +} + +# ----------------------------- +sub common_temphydrobaro { + my $type = shift; + my $longids = shift; + my $bytes = shift; + + my $subtype = sprintf "%02x", $bytes->[1]; + #Log 1,"subtype=$subtype"; + my $dev_type; + + my %devname = + ( # HEXSTRING => "NAME" + 0x01 => "BTHR918", + 0x02 => "BTHR918N", + ); + + if (exists $devname{$bytes->[1]}) { + $dev_type = $devname{$bytes->[1]}; + } else { + Log 1,"RFX_WEATHER: error undefined subtype=$subtype"; + my @res = (); + return @res; + } + + my $dev_str = $dev_type; + if (use_longid($longids,$dev_type)) { + $dev_str .= $DOT.sprintf("%02x", $bytes->[3]); + } + if ($bytes->[4] > 0) { + $dev_str .= $DOT.sprintf("%d", $bytes->[4]); + } + #Log 1,"dev_str=$dev_str"; + + my @res = (); + + # hexline debugging + if ($TRX_HEX_debug) { + my $hexline = ""; for (my $i=0;$i<@$bytes;$i++) { $hexline .= sprintf("%02x",$bytes->[$i]);} + push @res, { device => $dev_str, type => 'hexline', current => $hexline, units => 'hex', }; + } + + temperature($bytes, $dev_str, \@res, 5); + humidity($bytes, $dev_str, \@res, 7); + pressure($bytes, $dev_str, \@res, 9); + simple_battery($bytes, $dev_str, \@res, 12); + return @res; +} + +# ----------------------------- +sub common_rain { + my $type = shift; + my $longids = shift; + my $bytes = shift; + + + my $subtype = sprintf "%02x", $bytes->[1]; + #Log 1,"subtype=$subtype"; + my $dev_type; + + my %devname = + ( # HEXSTRING => "NAME" + 0x01 => "RGR918", + 0x02 => "PCR800", + 0x03 => "TFA_RAIN", + ); + + if (exists $devname{$bytes->[1]}) { + $dev_type = $devname{$bytes->[1]}; + } else { + Log 1,"RFX_WEATHER: error undefined subtype=$subtype"; + my @res = (); + return @res; + } + + my $dev_str = $dev_type; + if (use_longid($longids,$dev_type)) { + $dev_str .= $DOT.sprintf("%02x", $bytes->[3]); + } + if ($bytes->[4] > 0) { + $dev_str .= $DOT.sprintf("%d", $bytes->[4]); + } + #Log 1,"dev_str=$dev_str"; + + my @res = (); + + # hexline debugging + if ($TRX_HEX_debug) { + my $hexline = ""; for (my $i=0;$i<@$bytes;$i++) { $hexline .= sprintf("%02x",$bytes->[$i]);} + push @res, { device => $dev_str, type => 'hexline', current => $hexline, units => 'hex', }; + } + + my $rain = $bytes->[5]*256 + $bytes->[6]; + my $train = $bytes->[7]*256*256 + $bytes->[8]*256 + $bytes->[9]; + + push @res, { + device => $dev_str, + type => 'rain', + current => $rain, + units => 'mm/h', + } ; + push @res, { + device => $dev_str, + type => 'train', + current => $train, + units => 'mm', + }; + battery($bytes, $dev_str, \@res, 10); + return @res; +} + +sub raw { + $_[0]->{raw} or $_[0]->{raw} = pack 'H*', $_[0]->{hex}; +} + +# ----------------------------- + +sub +TRX_WEATHER_Parse($$) +{ + my ($iohash, $hexline) = @_; + + #my $hashname = $iohash->{NAME}; + #my $longid = AttrVal($hashname,"longids",""); + #Log 1,"2: name=$hashname, attr longids = $longid"; + + my $longids = 0; + if (defined($attr{$iohash->{NAME}}{longids})) { + $longids = $attr{$iohash->{NAME}}{longids}; + #Log 1,"0: attr longids = $longids"; + } + + my $time = time(); + # convert to binary + my $msg = pack('H*', $hexline); + if ($time_old ==0) { + Log 5, "TRX_WEATHER: decoding delay=0 hex=$hexline"; + } else { + my $time_diff = $time - $time_old ; + Log 5, "TRX_WEATHER: decoding delay=$time_diff hex=$hexline"; + } + $time_old = $time; + + # convert string to array of bytes. Skip length byte + my @rfxcom_data_array = (); + foreach (split(//, substr($msg,1))) { + push (@rfxcom_data_array, ord($_) ); + } + + my $num_bytes = ord($msg); + + if ($num_bytes < 3) { + return; + } + + my $type = $rfxcom_data_array[0]; + + my $sensor_id = unpack('H*', chr $type); + #Log 1, "TRX_WEATHER: sensor_id=$sensor_id"; + + my $key = type_length_key($type, $num_bytes); + + my $rec = $types{$key} || $types{$key&0xfffff}; + unless ($rec) { +#Log 3, "TRX_WEATHER: ERROR: Unknown sensor_id=$sensor_id num_bytes=$num_bytes message='$hexline'."; + Log 4, "TRX_WEATHER: ERROR: Unknown sensor_id=$sensor_id message='$hexline'"; +Log 1, "TRX_WEATHER: ERROR: Unknown sensor_id=$sensor_id message='$hexline'"; + return "TRX_WEATHER: ERROR: Unknown sensor_id=$sensor_id \n"; + } + + my $method = $rec->{method}; + unless ($method) { + Log 4, "TRX_WEATHER: Possible message from Oregon part '$rec->{part}'"; + Log 4, "TRX_WEATHER: sensor_id=$sensor_id"; + return; + } + + my @res; + + if (! defined(&$method)) { + Log 4, "TRX_WEATHER: Error: Unknown function=$method. Please define it in file $0"; + Log 4, "TRX_WEATHER: sensor_id=$sensor_id\n"; + return "TRX_WEATHER: Error: Unknown function=$method. Please define it in file $0"; + } else { + #Log 1, "TRX_WEATHER: parsing sensor_id=$sensor_id message='$hexline'"; + @res = $method->($rec->{part}, $longids, \@rfxcom_data_array); + } + + # get device name from first entry + my $device_name = $res[0]->{device}; + #Log 1, "device_name=$device_name"; + + if (! defined($device_name)) { + Log 4, "TRX_WEATHER: error device_name undefined\n"; + return "TRX_WEATHER: Error: Unknown devicename."; + } + + my $def = $modules{TRX_WEATHER}{defptr}{"$device_name"}; + if(!$def) { + Log 3, "TRX_WEATHER: Unknown device $device_name, please define it"; + return "UNDEFINED $device_name TRX_WEATHER $device_name"; + } + # Use $def->{NAME}, because the device may be renamed: + my $name = $def->{NAME}; + #Log 1, "name=$new_name"; + + my $n = 0; + my $tm = TimeNow(); + + my $i; + my $val = ""; + my $sensor = ""; + foreach $i (@res){ + #print "!> i=".$i."\n"; + #printf "%s\t",$i->{device}; + if ($i->{type} eq "temp") { + #printf "Temperatur %2.1f %s ; ",$i->{current},$i->{units}; + $val .= "T: ".$i->{current}." "; + + $sensor = "temperature"; + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $i->{current}; + $def->{CHANGED}[$n++] = $sensor . ": " . $i->{current}; + } + elsif ($i->{type} eq "humidity") { + #printf "Luftfeuchtigkeit %d%s, %s ;",$i->{current},$i->{units},$i->{string}; + $val .= "H: ".$i->{current}." "; + + $sensor = "humidity"; + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $i->{current}; + $def->{CHANGED}[$n++] = $sensor . ": " . $i->{current};; + } + elsif ($i->{type} eq "battery") { + #printf "Batterie %d%s; ",$i->{current},$i->{units}; + my $tmp_battery = $i->{current}; + my @words = split(/\s+/,$i->{current}); + $val .= "BAT: ".$words[0]." "; #user only first word + + $sensor = "battery"; + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $i->{current}; + $def->{CHANGED}[$n++] = $sensor . ": " . $i->{current};; + } + elsif ($i->{type} eq "pressure") { + #printf "Luftdruck %d %s, Vorhersage=%s ; ",$i->{current},$i->{units},$i->{forecast}; + # do not add it due to problems with hms.gplot + $val .= "P: ".$i->{current}." "; + + $sensor = "pressure"; + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $i->{current}; + $def->{CHANGED}[$n++] = $sensor . ": " . $i->{current};; + + $sensor = "forecast"; + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $i->{forecast}; + $def->{CHANGED}[$n++] = $sensor . ": " . $i->{forecast};; + } + elsif ($i->{type} eq "speed") { + $val .= "W: ".$i->{current}." "; + $val .= "WA: ".$i->{average}." "; + + $sensor = "wind_speed"; + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $i->{current}; + $def->{CHANGED}[$n++] = $sensor . ": " . $i->{current};; + + $sensor = "wind_avspeed"; + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $i->{average}; + $def->{CHANGED}[$n++] = $sensor . ": " . $i->{average};; + } + elsif ($i->{type} eq "direction") { + $val .= "WD: ".$i->{current}." "; + $val .= "WDN: ".$i->{string}." "; + + $sensor = "wind_dir"; + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $i->{current} . " " . $i->{string}; + $def->{CHANGED}[$n++] = $sensor . ": " . $i->{current} . " " . $i->{string};; + } + elsif ($i->{type} eq "rain") { + $val .= "RR: ".$i->{current}." "; + + $sensor = "rain_rate"; + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $i->{current}; + $def->{CHANGED}[$n++] = $sensor . ": " . $i->{current};; + } + elsif ($i->{type} eq "train") { + $val .= "TR: ".$i->{current}." "; + + $sensor = "rain_total"; + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $i->{current}; + $def->{CHANGED}[$n++] = $sensor . ": " . $i->{current};; + } + elsif ($i->{type} eq "flip") { + $val .= "F: ".$i->{current}." "; + + $sensor = "rain_flip"; + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $i->{current}; + $def->{CHANGED}[$n++] = $sensor . ": " . $i->{current};; + } + elsif ($i->{type} eq "uv") { + $val .= "UV: ".$i->{current}." "; + $val .= "UVR: ".$i->{risk}." "; + + $sensor = "uv_val"; + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $i->{current}; + $def->{CHANGED}[$n++] = $sensor . ": " . $i->{current};; + + $sensor = "uv_risk"; + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $i->{risk}; + $def->{CHANGED}[$n++] = $sensor . ": " . $i->{risk};; + } + elsif ($i->{type} eq "hexline") { + $sensor = "hexline"; + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $i->{current}; + $def->{CHANGED}[$n++] = $sensor . ": " . $i->{current};; + } + else { + print "\nTRX_WEATHER: Unknown: "; + print "Type: ".$i->{type}.", "; + print "Value: ".$i->{current}."\n"; + } + } + + if ("$val" ne "") { + # remove heading and trailing space chars from $val + $val =~ s/^\s+|\s+$//g; + + $def->{STATE} = $val; + $def->{TIME} = $tm; + $def->{CHANGED}[$n++] = $val; + } + + # + #$def->{READINGS}{state}{TIME} = $tm; + #$def->{READINGS}{state}{VAL} = $val; + #$def->{CHANGED}[$n++] = "state: ".$val; + + DoTrigger($name, undef); + + return $val; +} + +1; diff --git a/fhem/HISTORY b/fhem/HISTORY index 50b67ba15..0446a2ff8 100644 --- a/fhem/HISTORY +++ b/fhem/HISTORY @@ -489,4 +489,8 @@ - Sun Jan 29 2012 (Maz Rashid) - Improving 10_EIB.pm by introducing Get and interpreting received value according to the selected model (based on datapoint types.) - - Introduced documentation for TUL / EIB modules. \ No newline at end of file + - Introduced documentation for TUL / EIB modules. + +- Fr Feb 24 2012 (Willi) + - New modules TRX for RFXCOM RFXtrx transceiver +