From 99bfeab03785fcd950099c1040fddcc317dc1b7e Mon Sep 17 00:00:00 2001 From: herrmannj <> Date: Wed, 11 Nov 2015 22:27:58 +0000 Subject: [PATCH] 32_TechemWZ.pm: initial check-in git-svn-id: https://svn.fhem.de/fhem/trunk@9865 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/32_TechemWZ.pm | 563 +++++++++++++++++++++++++++++++++++++++ fhem/MAINTAINER.txt | 2 + 2 files changed, 565 insertions(+) create mode 100644 fhem/FHEM/32_TechemWZ.pm diff --git a/fhem/FHEM/32_TechemWZ.pm b/fhem/FHEM/32_TechemWZ.pm new file mode 100644 index 000000000..affe25f0e --- /dev/null +++ b/fhem/FHEM/32_TechemWZ.pm @@ -0,0 +1,563 @@ +############################################################################### +# $Id$ +# +# this module is part of fhem under the same license +# copyright 2015, joerg herrmann +# +# history +# initial checkin +# +############################################################################### +package main; + +use strict; +use warnings; + +# TODO remove if debug ok +use Data::Dumper; +use Time::HiRes qw(time); + +my %typeText = ( + '62' => 'warm water', # + '72' => 'cold water', # + '43' => 'heat meter', # compact V +); + +sub +TechemWZ_Initialize(@) { + my ($hash) = @_; + + # require "Broker.pm"; + + $hash->{Match} = "^b..446850[\\d]{8}..(?:43|62|72).*"; + + $hash->{DefFn} = "TechemWZ_Define"; + $hash->{UndefFn} = "TechemWZ_Undef"; + $hash->{SetFn} = "TechemWZ_Set"; + $hash->{GetFn} = "TechemWZ_Get"; + $hash->{NotifyFn} = "TechemWZ_Notify"; + $hash->{ParseFn} = "TechemWZ_Parse"; + + $hash->{AttrList} = "".$readingFnAttributes; + + return undef; +} + +sub +TechemWZ_Define(@) { + my ($hash, $def) = @_; + my ($name, $t, $id); + ($name, $t, $id, $def) = split(/ /, $def,4); + + return "ID must have 8 digits" if ($id !~ /^\d{8}$/); + return "ID $id already defined" if exists($modules{TechemWZ}{defptr}{$id}); + + # house keeping + if (exists($hash->{OLDDEF}) && ($hash->{DEF} ne $hash->{OLDDEF}) ) { + my @a = split(/ /, $hash->{OLDDEF}); + delete($hash->{VERSION}); + delete($hash->{METER}); + delete($hash->{READINGS}); + delete($modules{TechemWZ}{defptr}{$a[0]}); + delete($hash->{helper}->{list}); + } + + # create crc table if required + $data{WMBUS}{crc_table_13757} = TechemWZ_createCrcTable() unless (exists($data{WMBUS}{crc_table_13757})); + + $hash->{helper}->{listmode} = ($id eq '00000000')?1:0; + $hash->{ID} = $id; + $modules{TechemWZ}{defptr}{$id} = $hash; + $hash->{FRIENDLY} = $def if (defined($def)); + + # subscribe broadcast channels + # TechemWZ_subscribe($hash, 'foo'); + TechemWZ_Run($hash) if $init_done; + return undef; +} + +sub +TechemWZ_Undef(@) { + my ($hash) = @_; + my $id = $hash->{ID}; + delete($modules{TechemWZ}{defptr}{$id}); + return undef; +} + +sub +TechemWZ_Set(@) { + my ($hash, $name, $cmd, @args) = @_; + my $cnt = @args; + + return undef; +} + +sub +TechemWZ_Get(@) { + my ($hash, $name, $cmd, @args) = @_; + return undef unless ($hash->{helper}->{listmode}); + return "unknown command ($cmd): choose one of list" if ($cmd eq "?"); + return "unknown command ($cmd): choose one of list" if ($cmd ne "list"); + + my $result = ""; + + my $l = $hash->{helper}->{list}; + + foreach my $key (sort { $l->{$a}->{msg}->{meter} <=> $l->{$b}->{msg}->{meter} } keys %{$l} ) { + $result .= "$l->{$key}->{msg}->{long}\t"; + $result .= $typeText{$l->{$key}->{msg}->{type}}."\t"; + $result .= "$l->{$key}->{msg}->{meter}\t"; + $result .= "$l->{$key}->{msg}->{rssi}\t\n"; + } + + return $result; +} + +sub +TechemWZ_Notify (@) { + my ($hash, $ntfyDev) = @_; + return unless (($ntfyDev->{TYPE} eq 'CUL') || ($ntfyDev->{TYPE} eq 'Global')); + foreach my $event (@{$ntfyDev->{CHANGED}}) { + my @e = split(' ', $event); + TechemWZ_Run($hash) if ($e[0] eq 'INITIALIZED'); + # patch CUL.pm + TechemWZ_IOPatch($hash, $e[1]) if (($e[0] eq 'ATTR') && ($e[2] eq 'rfmode') && ($e[3] eq 'WMBus_T')); + # disable receiver + if (($e[0] eq 'ATTR') && ($e[2] eq 'rfmode') && ($e[3] ne 'WMBus_T')) { + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "state", "standby (IO missing)", 1); + readingsEndUpdate($hash, 1); + } + } + return undef; +} + +sub +TechemWZ_Receive(@) { + my ($hash, $msg, $raw) = @_; + + my @t = localtime(time); + my ($ats, $ts); + + $hash->{VERSION} = $msg->{version}; + $hash->{METER} = $typeText{$msg->{type}}; + + # day period changed + $ats = ReadingsTimestamp($hash->{NAME},"current_period", "0"); + $ts = sprintf ("%02d-%02d-%02d 00:00:00", $msg->{actual}->{year}, $msg->{actual}->{month}, $msg->{actual}->{day}); + if ($ats ne $ts) { + readingsBeginUpdate($hash); + $hash->{".updateTimestamp"} = $ts; + readingsBulkUpdate($hash, "meter", $msg->{meter}); + readingsBulkUpdate($hash, "current_period", $msg->{actualVal}); + $hash->{CHANGETIME}[0] = $ts; + readingsEndUpdate($hash, 1); + delete $hash->{CHANGETIME}; + } + + # billing period changed + $ats = ReadingsTimestamp($hash->{NAME},"previous_period", "0"); + $ts = sprintf ("20%02d-%02d-%02d 00:00:00", $msg->{last}->{year}, $msg->{last}->{month}, $msg->{last}->{day}); + if ($ats ne $ts) { + readingsBeginUpdate($hash); + $hash->{".updateTimestamp"} = $ts; + readingsBulkUpdate($hash, "previous_period", $msg->{lastVal}); + $hash->{CHANGETIME}[0] = $ts; + readingsEndUpdate($hash, 1); + delete $hash->{CHANGETIME}; + } + + return undef; +} + +sub +TechemWZ_Run(@) { + my ($hash) = @_; + # find a CUL + foreach my $d (keys %defs) { + # live patch CUL.pm + TechemWZ_IOPatch($hash, $d) if ($defs{$d}{TYPE} eq "CUL"); + } + return undef; +} + +# live patch CUL.pm, aka THE HACK +sub +TechemWZ_IOPatch(@) { + my ($hash, $iodev) = @_; + return undef unless (AttrVal($iodev, 'rfmode', '') eq 'WMBus_T'); + # see if already patched + readingsSingleUpdate($hash, 'state', 'listening', 1); + return undef if ($defs{$iodev}{Clients} =~ /TechemWZ/ ); + $defs{$iodev}{Clients} = ':TechemWZ'.$defs{$iodev}{Clients}; + $defs{$iodev}{'.clientArray'} = undef; + return undef; +} + +sub +TechemWZ_Parse(@) { + + my ($iohash, $msg) = @_; + my ($message, $rssi); + ($msg, $rssi) = split (/::/, $msg); + $msg = TechemWZ_SanityCheck($msg); + return '' unless $msg; + + my @m = ($msg =~ m/../g); + my @d; + + # parse + ($message->{long}, $message->{short}) = TechemWZ_ParseID(@m); + $message->{type} = TechemWZ_ParseSubType(@m); + $message->{version} = TechemWZ_ParseSubVersion(@m); + $message->{lastVal} = TechemWZ_ParseLastPeriod(@m); + $message->{actualVal} = TechemWZ_ParseActualPeriod(@m); + ($message->{actual}->{year}, $message->{actual}->{month}, $message->{actual}->{day}) = TechemWZ_ParseActualDate(@m); + ($message->{last}->{year}, $message->{last}->{month}, $message->{last}->{day}) = TechemWZ_ParseLastDate(@m); + $message->{rssi} = ($rssi)?$rssi:"?"; + + # metertype specific adjustment + if ($message->{type} =~ /62|72/) { + $message->{lastVal} /= 10; + $message->{actualVal} /= 10; + $message->{meter} = $message->{lastVal} + $message->{actualVal}; + } elsif ($message->{type} =~ /43/) { + $message->{meter} = $message->{lastVal} + $message->{actualVal}; + } + + # list + if (exists( $modules{TechemWZ}{defptr}{'00000000'} ) && defined( $defs{$modules{TechemWZ}{defptr}{'00000000'}->{NAME}} )) { + my $listdev = $modules{TechemWZ}{defptr}{'00000000'}; + $listdev->{helper}->{list}->{$message->{long}}->{msg} = $message; + push @d, $listdev; + } + + # dispatch + if (exists( $modules{TechemWZ}{defptr}{$message->{long}})) { + my $deviceHash = $modules{TechemWZ}{defptr}{$message->{long}}; + TechemWZ_Receive($deviceHash, $message); + push @d, $deviceHash; + } + + return (@d); +} + +sub +TechemWZ_SanityCheck(@) { + + my ($msg) = @_; + my $rssi; + my $t; + my $dbg = 4; + + #($msg, $rssi) = split (/::/, $msg); + my @m = ((substr $msg,1) =~ m/../g); + # at least 3 chars + if (length($msg) < 3) { + Log3 ("TechemWZ", $dbg, "msg incomplete $msg"); + return undef; + } + # msg length without crc blocks + my $l = hex(substr $msg, 1, 2) + 1; + # full crc payload blocks + my $fb = int(($l - 10) / 16); + # remaining bytes ? + my $rb = ($l - 10) % 16; + # required len + my $rl = $l + 2 + ($fb * 2) + (($rb)?2:0); + + if (($rl * 2) > (length($msg) -1)) { + Log3 ("TechemWZ", $dbg, "msg incomplete $msg"); + return undef; + } + + # CRC first 10 byte, then chunks of 16 byte then remaining + if ((substr $msg, 21, 4) ne TechemWZ_crc16_13757(substr $msg, 1, 20)) { + Log3 ("TechemWZ", $dbg, "crc error $msg"); + return undef; + } else { + $t = substr $msg, 3, 18; + } + for (my $i = 0; $i<$fb; $i++) { + if ((substr $msg, 57 + ($i * 36), 4) ne TechemWZ_crc16_13757(substr $msg, 25 + ($i * 36), 32)) { + Log3 ("TechemWZ", $dbg, "crc error $msg"); + return undef; + } else { + $t .= substr $msg, 25 + ($i * 36), 32; + } + } + if ($rb) { + if ((substr $msg, 25 + ($fb * 36) + ($rb * 2), 4) ne TechemWZ_crc16_13757(substr $msg, 25 + ($fb * 36), $rb * 2)) { + Log3 ("TechemWZ", $dbg, "crc error $msg"); + return undef; + } else { + $t .= substr $msg, 25 + ($fb * 36), ($rb * 2); + } + } + return $t; +} + +sub +TechemWZ_ParseID(@) { + my @m = @_; + return ("$m[6]$m[5]$m[4]$m[3]", "$m[4]$m[3]"); +} + +sub +TechemWZ_ParseSubType(@) { + my @m = @_; + return "$m[8]"; +} + +sub +TechemWZ_ParseSubVersion(@) { + my @m = @_; + return "$m[7]"; +} + +sub +TechemWZ_ParseLastPeriod(@) { + my @m = @_; + return hex("$m[14]$m[13]"); +} + + +sub +TechemWZ_ParseActualPeriod(@) { + my @m = @_; + return hex("$m[18]$m[17]"); +} + + +sub +TechemWZ_ParseActualDate(@) { + my @m = @_; + my @t = localtime(time); + my $b = hex("$m[16]$m[15]"); + my $d = ($b >> 4) & 0x1F; + my $m = ($b >> 9) & 0x0F; + my $y = $t[5] + 1900; + return ($y, $m, $d); +} + +sub +TechemWZ_ParseLastDate(@) { + my @m = @_; + my $b = hex("$m[12]$m[11]"); + my $d = ($b >> 0) & 0x1F; + my $m = ($b >> 5) & 0x0F; + my $y = ($b >> 9) & 0x3F; + return ($y, $m, $d); +} + +sub +TechemWZ_createCrcTable(@) { + + my $poly = 0x3D65; + my $c; + my @table; + + for (my $i=0; $i<256; $i++) { + $c = ($i << 8); + + for (my $j=0; $j<8; $j++) { + if (($c & 0x8000) != 0) { + $c = 0xFFFF & (($c << 1) ^ $poly); + + } else { + $c <<= 1; + + } + } + $table[$i] = $c; + } + return \@table; +} + +sub +TechemWZ_crc16_13757(@) { + + my ($msg) = @_; + my @table = @{$data{WMBUS}{crc_table_13757}}; + + my @in = split '', pack 'H*', $msg; + my $crc = 0x0000; + for (my $i=0; $i> 8) ^ ord($in[$i]))] ); + + } + return sprintf ("%04lX", $crc ^ 0xFFFF); +} + +# message bus ahead +# sub +#TechemWZ_subscribe(@) { +# my ($hash, $topic) = @_; +# broker::subscribe ($topic, $hash->{NAME}, \&TechemWZ_rcvBCST); +# return undef; +#} + +#sub +#TechemWZ_sendBCST(@) { +# my ($hash, $topic, $msg) = @_; +# broker::publish ($topic, $hash->{NAME}, $msg); +# return undef; +#} + +#sub +#TechemWZ_rcvBCST(@) { +# my ($name, $topic, $sender, $msg) = @_; +# my $hash = $defs{$name}; +# return undef; +#} + +1; + +=pod +=begin html + + +

TechemWZ

+ +=end html + +=begin html_DE + + +

TechemWZ

+ + +=end html_DE +=cut + diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index ca086c22c..e11550b38 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -126,6 +126,8 @@ FHEM/31_ENECSYSINV.pm akw http://forum.fhem.de Sonstige FHEM/31_LightScene.pm justme1968 http://forum.fhem.de Automatisierung FHEM/32_SYSSTAT.pm justme1968 http://forum.fhem.de Unterstuetzende Dienste FHEM/32_mailcheck.pm justme1968 http://forum.fhem.de Automatisierung +FHEM/32_TechemHKV.pm herrmannj http://forum.fhem.de Heizungssteuerung/Raumklima +FHEM/32_TechemWZ.pm herrmannj http://forum.fhem.de Sonstiges FHEM/32_yowsup.pm justme1968 http://forum.fhem.de Unterstuetzende Dienste FHEM/32_withings.pm markus-m http://forum.fhem.de Sonstiges FHEM/33_readingsGroup.pm justme1968 http://forum.fhem.de Frontends