From 4369875de1aa756fee7ea1efe01004efa3ed3013 Mon Sep 17 00:00:00 2001 From: kaihs Date: Tue, 8 Nov 2016 19:34:33 +0000 Subject: [PATCH] 36_WMBUS: support for Amber Wireless AMB8465-M as IoDev git-svn-id: https://svn.fhem.de/fhem/trunk@12532 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/36_WMBUS.pm | 91 ++++++++++++++++++++++++++++++------------- fhem/FHEM/WMBus.pm | 60 +++++++++++++++++++--------- 3 files changed, 106 insertions(+), 46 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 02700159b..38f8cc2da 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: 36_WMBUS: support for Amber Wireless AMB8465-M as IoDev - feature: 67_ECMDDevice: new attribute noState - bugfix: 02_RSS: make hash reference explicit - updated: 74_AMAD: New Version 2.6.6 remove APSSID in define, add Attribut diff --git a/fhem/FHEM/36_WMBUS.pm b/fhem/FHEM/36_WMBUS.pm index fdf49521f..d36999008 100644 --- a/fhem/FHEM/36_WMBUS.pm +++ b/fhem/FHEM/36_WMBUS.pm @@ -36,6 +36,29 @@ sub WMBUS_Initialize($) { " $readingFnAttributes"; } +sub +WMBUS_HandleEncoding($$) +{ + my ($mb, $msg) = @_; + my $encoding = "CUL"; + my $rssi; + + ($msg, $rssi) = split(/::/,$msg); + + if (substr($msg,1,3) eq "AMB") { + # Amber Wireless AMB8425-M encoding, does not include CRC16 + $encoding = "AMB"; + $mb->setCRCsize(0); + # message length (first byte) contains 1 byte for rssi, + # remove it + my $msglen = sprintf("%1x", hex(substr($msg,4,1)) - 1); + $msg = "b" . $msglen . substr($msg,5); + } else { + $msg .= WMBUS_RSSIAsRaw($rssi); + } + return ($msg, $rssi, $encoding); +} + sub WMBUS_Define($$) { @@ -45,7 +68,7 @@ WMBUS_Define($$) my $rssi; if(@a != 6 && @a != 3) { - my $msg = "wrong syntax: define WMBUS [ ]|b"; + my $msg = "wrong syntax: define WMBUS [ []]|b"; Log3 undef, 2, $msg; return $msg; } @@ -55,14 +78,16 @@ WMBUS_Define($$) if (@a == 3) { # unparsed message my $msg = $a[2]; - - ($msg, $rssi) = split(/::/,$msg); - - $msg .= WMBUS_RSSIAsRaw($rssi); - - return "a WMBus message must be a least 12 bytes long" if $msg !~ m/b[a-zA-Z0-9]{24,}/; - $mb = new WMBus; + + + ($msg, $rssi, $hash->{MessageEncoding}) = WMBUS_HandleEncoding($mb, $msg); + + my $minSize = ($mb->getCRCsize() + WMBus::TL_BLOCK_SIZE) * 2; + my $reMinSize = qr/b[a-zA-Z0-9]{${minSize},}/; + + return "a WMBus message must be a least $minSize bytes long, $msg" if $msg !~ m/${reMinSize}/; + if ($mb->parseLinkLayer(pack('H*',substr($msg,1)))) { $hash->{Manufacturer} = $mb->{manufacturer}; $hash->{IdentNumber} = $mb->{afield_id}; @@ -79,6 +104,7 @@ WMBUS_Define($$) } } else { + my $encoding = "CUL"; # manual specification if ($a[2] !~ m/[A-Z]{3}/) { return "$a[2] is not a valid WMBUS manufacturer id"; @@ -95,11 +121,20 @@ WMBUS_Define($$) if ($a[5] !~ m/[0-9]{1,2}/) { return "$a[5] is not a valid WMBUS type"; } + + if (defined($a[6])) { + $encoding = $a[6]; + } + if ($encoding ne "CUL" && $encoding ne "AMB") { + return "$a[6] isn't a supported encoding, use either CUL or AMB"; + } + $hash->{Manufacturer} = $a[2]; $hash->{IdentNumber} = sprintf("%08d",$a[3]); $hash->{Version} = $a[4]; $hash->{DeviceType} = $a[5]; + $hash->{MessageEncoding} = $encoding; } my $addr = join("_", $hash->{Manufacturer},$hash->{IdentNumber},$hash->{Version},$hash->{DeviceType}) ; @@ -176,23 +211,23 @@ WMBUS_Fingerprint($$) sub WMBUS_Parse($$) { - my ($hash, $msg) = @_; + my ($hash, $rawMsg) = @_; my $name = $hash->{NAME}; my $addr; my $rhash; my $rssi; + my $msg; # $hash is the hash of the IODev! - if( $msg =~ m/^b/ ) { + if( $rawMsg =~ m/^b/ ) { # WMBus message received - Log3 $name, 5, "WMBUS raw msg " . $msg; + Log3 $name, 5, "WMBUS raw msg " . $rawMsg; my $mb = new WMBus; - ($msg, $rssi) = split(/::/,$msg); - $msg .= WMBUS_RSSIAsRaw($rssi); + ($msg, $rssi, $hash->{MessageEncoding}) = WMBUS_HandleEncoding($mb, $rawMsg); if ($mb->parseLinkLayer(pack('H*',substr($msg,1)))) { $addr = join("_", $mb->{manufacturer}, $mb->{afield_id}, $mb->{afield_ver}, $mb->{afield_type}); @@ -200,9 +235,9 @@ WMBUS_Parse($$) $rhash = $modules{WMBUS}{defptr}{$addr}; if( !$rhash ) { - Log3 $name, 3, "WMBUS Unknown device $msg, please define it"; + Log3 $name, 3, "WMBUS Unknown device $rawMsg, please define it"; - return "UNDEFINED WMBUS_$addr WMBUS $msg"; + return "UNDEFINED WMBUS_$addr WMBUS $rawMsg"; } my $rname = $rhash->{NAME}; @@ -230,14 +265,14 @@ WMBUS_Parse($$) return undef; } } else { - DoTrigger($name, "UNKNOWNCODE $msg"); - Log3 $name, 3, "$name: Unknown code $msg, help me!"; + DoTrigger($name, "UNKNOWNCODE $rawMsg"); + Log3 $name, 3, "$name: Unknown code $rawMsg, help me!"; return undef; } } -# if the culfw doesn't send the RSSI value (because it an old version that doesn't implement this) but 00_CUL.pm already expects it +# if the culfw doesn't send the RSSI value (because it is an old version that doesn't implement this) but 00_CUL.pm already expects it # one byte is missing from the data which leads to CRC errors # To avoid this calculate the raw data byte from the RSSI and append it to the data. # If it is a valid RSSI it will be ignored by the WMBus parser (the data contains the length of the data itself @@ -346,7 +381,7 @@ sub WMBUS_SetDeviceSpecificReadings($$$) for $dataBlock ( @$dataBlocks ) { # search for VIF_VOLUME - if ($dataBlock->{type} eq 'VIF_VOLUME') { + if ($dataBlock->{type} eq 'VIF_VOLUME' && $dataBlock->{functionFieldText} eq "Instantaneous value") { readingsBulkUpdate($hash, "volume", $dataBlock->{value}); readingsBulkUpdate($hash, "unit", $dataBlock->{unit}); } @@ -412,7 +447,7 @@ WMBUS_Attr(@) Wireless M-Bus is a standard protocol supported by various manufacturers. It uses the 868 MHz band for radio transmissions. - Therefore you need a device which can receive Wireless M-Bus messages, e.g. a CUL with culfw >= 1.59. + Therefore you need a device which can receive Wireless M-Bus messages, e.g. a CUL with culfw >= 1.59 or an AMBER Wireless AMB8465M.
WMBus uses two different radio protocols, T-Mode and S-Mode. The receiver must be configured to use the same protocol as the sender. In case of a CUL this can be done by setting rfmode to WMBus_T or WMBus_S respectively. @@ -434,7 +469,7 @@ WMBUS_Attr(@) Define
    - define <name> WMBUS [<manufacturer id> <identification number> <version> <type>]|<bHexCode>
    + define <name> WMBUS [<manufacturer id> <identification number> <version> <type> [<MessageEncoding>]]|<bHexCode>

    Normally a WMBus device isn't defined manually but automatically through the autocreate mechanism upon the first reception of a message.
    @@ -450,7 +485,8 @@ WMBUS_Attr(@) dlms.com for a list of registered ids.
    The identification number is the serial no of the meter.
    version is the version code of the meter
    - type is the type of the meter, e.g. water or electricity encoded as a number. + type is the type of the meter, e.g. water or electricity encoded as a number.
    + MessageEncoding is either CUL or AMB, depending on which kind of IODev is used.
@@ -518,7 +554,7 @@ WMBUS_Attr(@) Wireless M-Bus ist ein standardisiertes Protokoll das von unterschiedlichen Herstellern unterstützt wird. Es verwendet das 868 MHz Band für Radioübertragungen. - Daher wird ein Gerät benötigt das die Wireless M-Bus Nachrichten empfangen kann, z. B. ein CUL mit culfw >= 1.59. + Daher wird ein Gerät benötigt das die Wireless M-Bus Nachrichten empfangen kann, z. B. ein CUL mit culfw >= 1.59 oder ein AMBER Wireless AMB8465-M.
WMBus verwendet zwei unterschiedliche Radioprotokolle, T-Mode und S-Mode. Der Empfänger muss daher so konfiguriert werden, dass er das selbe Protokoll verwendet wie der Sender. Im Falle eines CUL kann das erreicht werden, in dem das Attribut rfmode auf WMBus_T bzw. WMBus_S gesetzt wird. @@ -540,7 +576,7 @@ WMBUS_Attr(@) Define
    - define <name> WMBUS [<manufacturer id> <identification number> <version> <type>]|<bHexCode>
    + define <name> WMBUS [<manufacturer id> <identification number> <version> <type> [<:MessageEncoding>]]|<bHexCode>

    Normalerweise wird ein WMBus Device nicht manuell angelegt. Dies geschieht automatisch bem Empfang der ersten Nachrichten eines Gerätes über den fhem autocreate Mechanismus. @@ -548,7 +584,7 @@ WMBUS_Attr(@) Für eine manuelle Definition gibt es zwei Wege.
    • - Durch Verwendung einer WMBus Rohnachricht wie sie vom CUL empfangen wurde. So eine Nachricht beginnt mit einem kleinen 'b' und enthält mindestens + Durch Verwendung einer WMBus Rohnachricht wie sie vom IODev empfangen wurde. So eine Nachricht beginnt mit einem kleinen 'b' und enthält mindestens 24 hexadezimale Zeichen. Das WMBUS Modul extrahiert daraus alle benötigten Informationen.
    • @@ -558,7 +594,8 @@ WMBUS_Attr(@) dlms.com
      Die Idenitfikationsnummer ist die Seriennummer des Zählers.
      Version ist ein Versionscode des Zählers.
      - Typ ist die Art des Zählers, z. B. Wasser oder Elektrizität, kodiert als Zahl. + Typ ist die Art des Zählers, z. B. Wasser oder Elektrizität, kodiert als Zahl.
      + MessageEncoding ist entweder CUL oder AMB, je nachdem welche Art von IODev verwendet wird
    @@ -618,4 +655,4 @@ WMBUS_Attr(@)
=end html_DE -=cut +=cut \ No newline at end of file diff --git a/fhem/FHEM/WMBus.pm b/fhem/FHEM/WMBus.pm index 63bc42b40..f62bcbd0c 100644 --- a/fhem/FHEM/WMBus.pm +++ b/fhem/FHEM/WMBus.pm @@ -20,6 +20,8 @@ sub manId2ascii($$); use constant { + # Transport Layer block size + TL_BLOCK_SIZE => 10, # Link Layer block size LL_BLOCK_SIZE => 16, # size of CRC in bytes @@ -920,12 +922,17 @@ sub removeCRC($$) my $res; my $crc; my $blocksize = LL_BLOCK_SIZE; - my $blocksize_with_crc = LL_BLOCK_SIZE + CRC_SIZE; + my $blocksize_with_crc = LL_BLOCK_SIZE + $self->{crc_size}; my $crcoffset; my $msgLen = $self->{datalen}; # size without CRCs my $noOfBlocks = $self->{datablocks}; # total number of data blocks, each with a CRC appended my $rest = $msgLen % LL_BLOCK_SIZE; # size of the last data block, can be smaller than 16 bytes + + + #print "crc_size $self->{crc_size}\n"; + + return $msg if $self->{crc_size} == 0; # each block is 16 bytes + 2 bytes CRC @@ -934,14 +941,14 @@ sub removeCRC($$) for ($i=0; $i < $noOfBlocks; $i++) { $crcoffset = $blocksize_with_crc * $i + LL_BLOCK_SIZE; #print "$i: crc offset $crcoffset\n"; - if ($rest > 0 && $crcoffset + CRC_SIZE > ($noOfBlocks - 1) * $blocksize_with_crc + $rest) { + if ($rest > 0 && $crcoffset + $self->{crc_size} > ($noOfBlocks - 1) * $blocksize_with_crc + $rest) { # last block is smaller $crcoffset = ($noOfBlocks - 1) * $blocksize_with_crc + $rest; #print "last crc offset $crcoffset\n"; $blocksize = $msgLen - ($i * $blocksize); } - $crc = unpack('n',substr($msg, $crcoffset, CRC_SIZE)); + $crc = unpack('n',substr($msg, $crcoffset, $self->{crc_size})); #printf("%d: CRC %x, calc %x blocksize $blocksize\n", $i, $crc, $self->checkCRC(substr($msg, $blocksize_with_crc*$i, $blocksize))); if ($crc != $self->checkCRC(substr($msg, $blocksize_with_crc*$i, $blocksize))) { $self->{errormsg} = "crc check failed for block $i"; @@ -984,7 +991,19 @@ sub new { sub _initialize { my $self = shift; - #$self->{dataBlocks} = []; + $self->{crc_size} = CRC_SIZE; +} + +sub setCRCsize { + my $self = shift; + + $self->{crc_size} = shift; +} + +sub getCRCsize { + my $self = shift; + + return $self->{crc_size}; } sub decodeConfigword($) { @@ -1411,7 +1430,7 @@ sub decrypt($) { sub decodeApplicationLayer($) { my $self = shift; - my $applicationlayer = $self->removeCRC(substr($self->{msg},12)); + my $applicationlayer = $self->removeCRC(substr($self->{msg},TL_BLOCK_SIZE + $self->{crc_size})); #print unpack("H*", $applicationlayer) . "\n"; @@ -1504,26 +1523,29 @@ sub decodeLinkLayer($$) ($self->{lfield}, $self->{cfield}, $self->{mfield}) = unpack('CCv', $linklayer); $self->{afield_id} = sprintf("%08d", $self->decodeBCD(8,substr($linklayer,4,4))); - ($self->{afield_ver}, $self->{afield_type}, $self->{crc0}) = unpack('CCn', substr($linklayer,8,4)); - - + ($self->{afield_ver}, $self->{afield_type}) = unpack('CC', substr($linklayer,8,2)); #printf("lfield %d\n", $self->{lfield}); - #printf("crc0 %x calc %x\n", $self->{crc0}, $self->checkCRC(substr($linklayer,0,10))); + + if ($self->{crc_size} > 0) { + $self->{crc0} = unpack('n', substr($linklayer,TL_BLOCK_SIZE, $self->{crc_size})); - if ($self->{crc0} != $self->checkCRC(substr($linklayer,0,10))) { - $self->{errormsg} = "CRC check failed on link layer"; - $self->{errorcode} = ERR_CRC_FAILED; - #print "CRC check failed on link layer\n"; - return 0; + #printf("crc0 %x calc %x\n", $self->{crc0}, $self->checkCRC(substr($linklayer,0,10))); + + if ($self->{crc0} != $self->checkCRC(substr($linklayer,0,TL_BLOCK_SIZE))) { + $self->{errormsg} = "CRC check failed on link layer"; + $self->{errorcode} = ERR_CRC_FAILED; + #print "CRC check failed on link layer\n"; + return 0; + } } - # header block is 12 bytes, each following block is 16 bytes + 2 bytes CRC, the last block may be smaller - $self->{datalen} = $self->{lfield} - 9; # this is without CRCs and the lfield itself + # header block is 10 bytes + 2 bytes CRC, each following block is 16 bytes + 2 bytes CRC, the last block may be smaller + $self->{datalen} = $self->{lfield} - (TL_BLOCK_SIZE - 1); # this is without CRCs and the lfield itself $self->{datablocks} = int($self->{datalen} / LL_BLOCK_SIZE); $self->{datablocks}++ if $self->{datalen} % LL_BLOCK_SIZE != 0; - $self->{msglen} = 12 + $self->{datalen} + $self->{datablocks} * CRC_SIZE; - + $self->{msglen} = TL_BLOCK_SIZE + $self->{crc_size} + $self->{datalen} + $self->{datablocks} * $self->{crc_size}; + #printf("calc len %d, actual %d\n", $self->{msglen}, length($self->{msg})); if (length($self->{msg}) > $self->{msglen}) { $self->{remainingData} = substr($self->{msg},$self->{msglen}); @@ -1576,4 +1598,4 @@ sub parseApplicationLayer($) return $self->decodeApplicationLayer(); } -1; +1; \ No newline at end of file