diff --git a/fhem/FHEM/42_RFXMETER.pm b/fhem/FHEM/42_RFXMETER.pm new file mode 100755 index 000000000..4a01d58af --- /dev/null +++ b/fhem/FHEM/42_RFXMETER.pm @@ -0,0 +1,225 @@ +################################################################################# +# 42_RFXMETER.pm +# Modul for FHEM to decode RFXMETER messages +# +# This code is derived from http://www.xpl-perl.org.uk/. +# Thanks a lot to Mark Hindess who wrote xPL. +# +# Special thanks to RFXCOM, http://www.rfxcom.com/, for their +# help. I own an USB-RFXCOM-Receiver (433.92MHz, USB, order code 80002) +# and highly recommend it. +# +# Willi Herzig, 2010 +# +# 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 +# +package main; + +use strict; +use warnings; +use Switch; + +my $time_old = 0; + +sub +RFXMETER_Initialize($) +{ + my ($hash) = @_; + + $hash->{Match} = "^0.*"; + $hash->{DefFn} = "RFXMETER_Define"; + $hash->{UndefFn} = "RFXMETER_Undef"; + $hash->{ParseFn} = "RFXMETER_Parse"; + $hash->{AttrList} = "IODev do_not_notify:1,0 loglevel:0,1,2,3,4,5,6"; + +} + +##################################### +sub +RFXMETER_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + my $a = int(@a); + #print "a0 = $a[0]"; + #return "wrong syntax: define RFXMETER code " if(int(@a) != 3); + return "wrong syntax: define RFXMETER code [] []" + if(int(@a) < 3 || int(@a) > 5); + + my $name = $a[0]; + my $code = $a[2]; + + $hash->{scalefactor} = ((int(@a) > 3) ? $a[3] : 0.001); + $hash->{unitname} = ((int(@a) > 4) ? $a[4] : "kwh"); + + $hash->{CODE} = $code; + #$modules{RFXMETER}{defptr}{$name} = $hash; + $modules{RFXMETER}{defptr}{$code} = $hash; + AssignIoPort($hash); + + return undef; +} + +##################################### +sub +RFXMETER_Undef($$) +{ + my ($hash, $name) = @_; + delete($modules{RFXMETER}{defptr}{$name}); + return undef; +} + +#my $DOT = q{.}; +# Important: change it to _, because FHEM uses regexp +my $DOT = q{_}; + +sub parse_RFXmeter { + my $bytes = shift; + + #($bytes->[0] == ($bytes->[1]^0xf0)) or return; + if ( ($bytes->[0] + ($bytes->[1]^0xf)) != 0xff) { + #Log 1, "RFXMETER: check1 failed"; + return; + } + + #my $device = sprintf "%02x%02x", $bytes->[0], $bytes->[1]; + my $device = sprintf "%02x", $bytes->[0]; + Log 4, "RFXMETER: device=$device"; + + my $type = hi_nibble($bytes->[5]); + #Log 1, "RFXMETER: type=$type"; + + my $check = lo_nibble($bytes->[5]); + #Log 1, "RFXMETER: check=$check"; + + my $nibble_sum = nibble_sum(5.5, $bytes); + my $parity = 0xf^($nibble_sum&0xf); + unless ($parity == $check) { + #warn "RFXMeter parity error $parity != $check\n"; + return ""; + } + my $time = + { 0x01 => '30s', + 0x02 => '1m', + 0x04 => '5m', + 0x08 => '10m', + 0x10 => '15m', + 0x20 => '30m', + 0x40 => '45m', + 0x80 => '60m', + }; + my $type_str = + [ + 'normal data packet', + 'new interval time set', + 'calibrate value', + 'new address set', + 'counter value reset to zero', + 'set 1st digit of counter value integer part', + 'set 2nd digit of counter value integer part', + 'set 3rd digit of counter value integer part', + 'set 4th digit of counter value integer part', + 'set 5th digit of counter value integer part', + 'set 6th digit of counter value integer part', + 'counter value set', + 'set interval mode within 5 seconds', + 'calibration mode within 5 seconds', + 'set address mode within 5 seconds', + 'identification packet', + ]->[$type]; + unless ($type == 0) { + warn "Unsupported rfxmeter message $type_str\n"; + return ""; + } + #my $kwh = ( ($bytes->[4]<<16) + ($bytes->[2]<<8) + ($bytes->[3]) ) / 100; + #Log 1, "RFXMETER: kwh=$kwh"; + my $current = ($bytes->[4]<<16) + ($bytes->[2]<<8) + ($bytes->[3]) ; + Log 4, "RFXMETER: current=$current"; + + my $device_name = "RFXMeter".$DOT.$device; + Log 4, "device_name=$device_name"; + + #my $def = $modules{RFXMETER}{defptr}{"$device_name"}; + my $def = $modules{RFXMETER}{defptr}{"$device"}; + if(!$def) { + Log 3, "RFXMETER: Unknown device $device_name, please define it"; + return "UNDEFINED $device_name RFXMETER $device"; + } + # 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 $val = ""; + + my $hash = $def; + if (defined($hash->{scalefactor})) { + $current = $current * $hash->{scalefactor}; + #Log 1, "scalefactor=$hash->{scalefactor}, current=$current"; + } + my $unitname = "kwh"; + if (defined($hash->{unitname})) { + $unitname = $hash->{unitname}; + #Log 1, "unitname=$hash->{unitname}, current=$current"; + } + + my $sensor = "meter"; + $val .= "CNT: " . $current; + $def->{READINGS}{$sensor}{TIME} = $tm; + $def->{READINGS}{$sensor}{VAL} = $current . " " . $unitname; + $def->{CHANGED}[$n++] = $sensor . ": " . $current . " " . $unitname; + + $def->{STATE} = $val; + $def->{TIME} = $tm; + $def->{CHANGED}[$n++] = $val; + + DoTrigger($name, undef); + + return ""; +} + +sub +RFXMETER_Parse($$) +{ + my ($hash, $msg) = @_; + + my $time = time(); + my $hexline = unpack('H*', $msg); + if ($time_old ==0) { + Log 5, "RFXMETER: decoding delay=0 hex=$hexline"; + } else { + my $time_diff = $time - $time_old ; + Log 5, "RFXMETER: 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 $bits = ord($msg); + my $num_bytes = $bits >> 3; if (($bits & 0x7) != 0) { $num_bytes++; } + Log 4, "RFXMETER: bits=$bits num_bytes=$num_bytes hex=$hexline"; + + my @res = ""; + if ($bits == 48) { + @res = parse_RFXmeter(\@rfxcom_data_array); + #parse_RFXmeter(\@rfxcom_data_array); + } + + return @res; +} + +1;