2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-01-31 18:59:33 +00:00

WMBus: some more fixes for Easymeter, new attribute useVIFasReadingName, new set rawmsg

git-svn-id: https://svn.fhem.de/fhem/trunk@20084 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
kaihs 2019-08-31 16:54:50 +00:00
parent c69ee18b06
commit 3eacd88eeb
2 changed files with 446 additions and 230 deletions

View File

@ -1,5 +1,5 @@
#
# kaihs@FHEM_Forum (forum.fhem.de)
# kaihs@FHEM_Forum (forum.fhem.de)
#
# $Id$
#
@ -22,7 +22,7 @@ sub WMBUS_Initialize($) {
my ($hash) = @_;
$hash->{Match} = "^b.*";
#$hash->{SetFn} = "WMBUS_Set";
$hash->{SetFn} = "WMBUS_Set";
#$hash->{GetFn} = "WMBUS_Get";
$hash->{DefFn} = "WMBUS_Define";
$hash->{UndefFn} = "WMBUS_Undef";
@ -35,7 +35,9 @@ sub WMBUS_Initialize($) {
" rawmsg_as_reading:0,1".
" ignoreUnknownDataBlocks:0,1".
" ignoreMasterMessages:0,1".
" $readingFnAttributes";
" useVIFasReadingName:0,1".
" $readingFnAttributes"
;
}
sub
@ -70,11 +72,11 @@ WMBUS_Define($$)
{
my ($hash, $def) = @_;
my @a = split("[ \t][ \t]*", $def);
my $mb;
my $rssi;
my $mb;
my $rssi;
if(@a != 6 && @a != 3) {
my $msg = "wrong syntax: define <name> WMBUS [<ManufacturerID> <SerialNo> <Version> <Type> [<MessageEncoding>]]|b<HexMessage>";
my $msg = "wrong syntax: define <name> WMBUS <ManufacturerID> <SerialNo> <Version> <Type> [<MessageEncoding>]|b[<MessageEncoding>]<HexMessage>";
Log3 undef, 2, $msg;
return $msg;
}
@ -82,70 +84,70 @@ WMBUS_Define($$)
my $name = $a[0];
if (@a == 3) {
# unparsed message
my $msg = $a[2];
$mb = new WMBus;
($msg, $rssi, $hash->{MessageEncoding}) = WMBUS_HandleEncoding($mb, $msg);
my $minSize = ($mb->getCRCsize() + WMBus::TL_BLOCK_SIZE) * 2;
# unparsed message
my $msg = $a[2];
$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};
$hash->{Version} = $mb->{afield_ver};
$hash->{DeviceType} = $mb->{afield_type};
if ($mb->{errormsg}) {
$hash->{Error} = $mb->{errormsg};
} else {
delete $hash->{Error};
}
WMBUS_SetRSSI($hash, $mb, $rssi);
} else {
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};
$hash->{Version} = $mb->{afield_ver};
$hash->{DeviceType} = $mb->{afield_type};
if ($mb->{errormsg}) {
$hash->{Error} = $mb->{errormsg};
} else {
delete $hash->{Error};
}
WMBUS_SetRSSI($hash, $mb, $rssi);
} else {
my $error = "failed to parse msg: $mb->{errormsg}";
if ($mb->{errorcode} == WMBus::ERR_MSG_TOO_SHORT && $hash->{MessageEncoding} eq 'CUL') {
if ($mb->{errorcode} == WMBus::ERR_MSG_TOO_SHORT && $hash->{MessageEncoding} eq 'CUL') {
$error .= ". Please make sure that TTY_BUFSIZE in culfw is at least two times the message length + 1";
}
return $error;
}
}
} else {
my $encoding = "CUL";
# manual specification
} else {
my $encoding = "CUL";
# manual specification
if ($a[2] !~ m/[A-Z]{3}/) {
return "$a[2] is not a valid WMBUS manufacturer id";
}
return "$a[2] is not a valid WMBUS manufacturer id";
}
if ($a[3] !~ m/[0-9]{1,8}/) {
return "$a[3] is not a valid WMBUS serial number";
}
return "$a[3] is not a valid WMBUS serial number";
}
if ($a[4] !~ m/[0-9]{1,2}/) {
return "$a[4] is not a valid WMBUS version";
}
return "$a[4] is not a valid WMBUS version";
}
if ($a[5] !~ m/[0-9]{1,2}/) {
return "$a[5] is not a valid WMBUS type";
}
if (defined($a[6])) {
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;
$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}) ;
@ -162,24 +164,17 @@ WMBUS_Define($$)
}
$hash->{DEF} = join(" ", $hash->{Manufacturer},$hash->{IdentNumber},$hash->{Version},$hash->{DeviceType});
$hash->{DeviceMedium} = WMBus::->type2string($hash->{DeviceType});
if (defined($mb)) {
if ($mb->parseApplicationLayer()) {
if ($mb->{cifield} == WMBus::CI_RESP_12) {
$hash->{Meter_Id} = $mb->{meter_id};
$hash->{Meter_Manufacturer} = $mb->{meter_manufacturer};
$hash->{Meter_Version} = $mb->{meter_vers};
$hash->{Meter_Dev} = $mb->{meter_devtypestring};
$hash->{Access_No} = $mb->{access_no};
$hash->{Status} = $mb->{status};
}
WMBUS_SetReadings($hash, $name, $mb);
} else {
$hash->{Error} = $mb->{errormsg};
}
}
$hash->{DeviceMedium} = WMBus::->type2string($hash->{DeviceType});
if (defined($mb)) {
if ($mb->parseApplicationLayer()) {
WMBUS_SetReadings($hash, $name, $mb);
} else {
$hash->{Error} = $mb->{errormsg};
}
}
return undef;
}
@ -231,58 +226,60 @@ WMBUS_Parse($$)
# $hash is the hash of the IODev!
if( $rawMsg =~ m/^b/ ) {
# WMBus message received
Log3 $name, 5, "WMBUS raw msg " . $rawMsg;
my $mb = new WMBus;
($msg, $rssi, $hash->{MessageEncoding}) = WMBUS_HandleEncoding($mb, $rawMsg);
if (uc(substr($msg, 0, 8)) eq "1144FF03") {
# WMBus message received
Log3 $name, 5, "WMBUS raw msg " . $rawMsg;
$hash->{internal}{rawMsg} = $rawMsg;
my $mb = new WMBus;
($msg, $rssi, $hash->{MessageEncoding}) = WMBUS_HandleEncoding($mb, $rawMsg);
if (uc(substr($msg, 0, 8)) eq "1144FF03") {
Log3 $name, 2, "received possible KNX-RF message, ignoring it";
return undef;
}
if ($mb->parseLinkLayer(pack('H*',substr($msg,1)))) {
$addr = join("_", $mb->{manufacturer}, $mb->{afield_id}, $mb->{afield_ver}, $mb->{afield_type});
if ($mb->parseLinkLayer(pack('H*',substr($msg,1)))) {
$addr = join("_", $mb->{manufacturer}, $mb->{afield_id}, $mb->{afield_ver}, $mb->{afield_type});
$rhash = $modules{WMBUS}{defptr}{$addr};
$rhash = $modules{WMBUS}{defptr}{$addr};
if( !$rhash ) {
Log3 $name, 3, "WMBUS Unknown device $rawMsg, please define it";
return "UNDEFINED WMBUS_$addr WMBUS $rawMsg";
}
if( !$rhash ) {
Log3 $name, 3, "WMBUS Unknown device $rawMsg, please define it";
return "UNDEFINED WMBUS_$addr WMBUS $rawMsg";
}
my $rname = $rhash->{NAME};
return "" if(IsIgnored($rname));
$rhash->{model} =join("_", $mb->{manufacturer}, $mb->{afield_type}, $mb->{afield_ver});
WMBUS_SetRSSI($rhash, $mb, $rssi);
WMBUS_SetRSSI($rhash, $mb, $rssi);
my $aeskey;
my $aeskey;
if ($aeskey = AttrVal($rname, 'AESkey', undef)) {
$mb->{aeskey} = pack("H*",$aeskey);
} else {
$mb->{aeskey} = undef;
}
if ($mb->parseApplicationLayer()) {
return WMBUS_SetReadings($rhash, $rname, $mb);
} else {
Log3 $rname, 2, "WMBUS $rname Error during ApplicationLayer parse:" . $mb->{errormsg};
readingsSingleUpdate($rhash, "state", $mb->{errormsg}, 1);
return $rname;
}
} else {
# error
Log3 $name, 2, "WMBUS Error during LinkLayer parse:" . $mb->{errormsg};
if ($mb->{errorcode} == WMBus::ERR_MSG_TOO_SHORT && $hash->{MessageEncoding} eq 'CUL') {
if ($aeskey = AttrVal($rname, 'AESkey', undef)) {
$mb->{aeskey} = pack("H*",$aeskey);
} else {
$mb->{aeskey} = undef;
}
if ($mb->parseApplicationLayer()) {
return WMBUS_SetReadings($rhash, $rname, $mb);
} else {
Log3 $rname, 2, "WMBUS $rname Error during ApplicationLayer parse:" . $mb->{errormsg};
readingsSingleUpdate($rhash, "state", $mb->{errormsg}, 1);
return $rname;
}
} else {
# error
Log3 $name, 2, "WMBUS Error during LinkLayer parse:" . $mb->{errormsg};
if ($mb->{errorcode} == WMBus::ERR_MSG_TOO_SHORT && $hash->{MessageEncoding} eq 'CUL') {
Log3 $name, 2, "Please make sure that TTY_BUFSIZE in culfw is at least two times the message length + 1";
}
return undef;
}
return undef;
}
} else {
DoTrigger($name, "UNKNOWNCODE $rawMsg");
Log3 $name, 3, "$name: Unknown code $rawMsg, help me!";
@ -297,86 +294,109 @@ WMBUS_Parse($$)
# If it is a valid RSSI it will be ignored by the WMBus parser (the data contains the length of the data itself
# and only that much is parsed).
sub WMBUS_RSSIAsRaw($) {
my $rssi = shift;
if (defined $rssi) {
if ($rssi < -74) {
$b = ($rssi+74)*2+256;
} else {
$b = ($rssi+74)*2;
}
return sprintf("%02X", $b);
} else {
return "";
}
my $rssi = shift;
if (defined $rssi) {
if ($rssi < -74) {
$b = ($rssi+74)*2+256;
} else {
$b = ($rssi+74)*2;
}
return sprintf("%02X", $b);
} else {
return "";
}
}
sub WMBUS_SetRSSI($$$) {
my ($hash, $mb, $rssi) = @_;
if (defined $mb->{remainingData} && length($mb->{remainingData}) >= 2) {
# if there are trailing bytes after the WMBUS message it is the LQI and the RSSI
readingsBeginUpdate($hash);
my ($lqi, $rssi) = unpack("CC", $mb->{remainingData});
my ($hash, $mb, $rssi) = @_;
if (defined $mb->{remainingData} && length($mb->{remainingData}) >= 2) {
# if there are trailing bytes after the WMBUS message it is the LQI and the RSSI
readingsBeginUpdate($hash);
my ($lqi, $rssi) = unpack("CC", $mb->{remainingData});
$rssi = ($rssi>=128 ? (($rssi-256)/2-74) : ($rssi/2-74));
readingsBulkUpdate($hash, "RSSI", $rssi);
readingsBulkUpdate($hash, "LQI", unpack("C", $mb->{remainingData}));
readingsEndUpdate($hash,1);
}
readingsBulkUpdate($hash, "RSSI", $rssi);
readingsBulkUpdate($hash, "LQI", unpack("C", $mb->{remainingData}));
readingsEndUpdate($hash,1);
}
}
sub WMBUS_SetReadings($$$)
{
my ($hash, $name, $mb) = @_;
my @list;
push(@list, $name);
my ($hash, $name, $mb) = @_;
my @list;
push(@list, $name);
readingsBeginUpdate($hash);
if ($mb->{decrypted} &&
# decode messages sent from master to slave/meter only if it is explictly enabled
( $mb->{sent_from_master} == 0 || AttrVal($name, "ignoreMasterMessages", 1) )
if ($mb->{cifield} == WMBus::CI_RESP_12) {
$hash->{Meter_Id} = $mb->{meter_id};
$hash->{Meter_Manufacturer} = $mb->{meter_manufacturer};
$hash->{Meter_Version} = $mb->{meter_vers};
$hash->{Meter_Dev} = $mb->{meter_devtypestring};
$hash->{Access_No} = $mb->{access_no};
$hash->{Status} = $mb->{status};
}
readingsBeginUpdate($hash);
if ($mb->{decrypted} &&
# decode messages sent from master to slave/meter only if it is explictly enabled
( $mb->{sent_from_master} == 0 || AttrVal($name, "ignoreMasterMessages", 1) )
)
{
my $dataBlocks = $mb->{datablocks};
my $dataBlock;
for $dataBlock ( @$dataBlocks ) {
my $dataBlocks = $mb->{datablocks};
my $dataBlock;
my $readingBase;
my $useVIFasReadingName = defined($hash->{internal}{useVIFasReadingName}) ?
$hash->{internal}{useVIFasReadingName} : AttrVal($name, "useVIFasReadingName", 0);
for $dataBlock ( @$dataBlocks ) {
next if AttrVal($name, "ignoreUnknownDataBlocks", 0) && $dataBlock->{type} eq 'MANUFACTURER SPECIFIC'; #WMBus::VIF_TYPE_MANUFACTURER_SPECIFIC
readingsBulkUpdate($hash, "$dataBlock->{number}_storage_no", $dataBlock->{storageNo});
readingsBulkUpdate($hash, "$dataBlock->{number}_type", $dataBlock->{type});
readingsBulkUpdate($hash, "$dataBlock->{number}_value", $dataBlock->{value});
readingsBulkUpdate($hash, "$dataBlock->{number}_unit", $dataBlock->{unit});
readingsBulkUpdate($hash, "$dataBlock->{number}_value_type", $dataBlock->{functionFieldText});
if (defined($dataBlock->{extension})) {
readingsBulkUpdate($hash, "$dataBlock->{number}_extension", $dataBlock->{extension});
if ($useVIFasReadingName) {
$readingBase = "$dataBlock->{storageNo}_$dataBlock->{type}";
if (defined($dataBlock->{extension_value})) {
$readingBase .= "_$dataBlock->{extension_value}";
}
} else {
$readingBase = $dataBlock->{number};
readingsBulkUpdate($hash, "${readingBase}_type", $dataBlock->{type});
readingsBulkUpdate($hash, "${readingBase}_storage_no", $dataBlock->{storageNo});
if (defined($dataBlock->{extension_value})) {
readingsBulkUpdate($hash, "${readingBase}_extension_value", $dataBlock->{extension_value});
}
}
readingsBulkUpdate($hash, "${readingBase}_value", $dataBlock->{value});
readingsBulkUpdate($hash, "${readingBase}_unit", $dataBlock->{unit});
readingsBulkUpdate($hash, "${readingBase}_value_type", $dataBlock->{functionFieldText});
if (defined($dataBlock->{extension_unit})) {
readingsBulkUpdate($hash, "${readingBase}_extension_unit", $dataBlock->{extension_unit});
}
if ($dataBlock->{errormsg}) {
readingsBulkUpdate($hash, "$dataBlock->{number}_errormsg", $dataBlock->{errormsg});
}
}
if ($dataBlock->{errormsg}) {
readingsBulkUpdate($hash, "${readingBase}_errormsg", $dataBlock->{errormsg});
}
}
readingsBulkUpdate($hash, "batteryState", $mb->{status} & 4 ? "low" : "ok");
WMBUS_SetDeviceSpecificReadings($hash, $name, $mb);
}
readingsBulkUpdate($hash, "is_encrypted", $mb->{isEncrypted});
readingsBulkUpdate($hash, "decryption_ok", $mb->{decrypted});
if ($mb->{decrypted}) {
readingsBulkUpdate($hash, "state", $mb->{statusstring});
} else {
readingsBulkUpdate($hash, "state", 'decryption failed');
}
if (AttrVal($name, "rawmsg_as_reading", 0)) {
readingsBulkUpdate($hash, "is_encrypted", $mb->{isEncrypted});
readingsBulkUpdate($hash, "decryption_ok", $mb->{decrypted});
if ($mb->{decrypted}) {
readingsBulkUpdate($hash, "state", $mb->{statusstring});
} else {
readingsBulkUpdate($hash, "state", 'decryption failed');
}
if (AttrVal($name, "rawmsg_as_reading", 0)) {
readingsBulkUpdate($hash, "rawmsg", $mb->getFrameType() eq WMBus::FRAME_TYPE_B ? "Y" : "" . unpack("H*",$mb->{msg}));
}
readingsEndUpdate($hash,1);
readingsEndUpdate($hash,1);
return @list;
return @list;
}
@ -422,15 +442,19 @@ WMBUS_Set($@)
my $name = shift @a;
my $cmd = shift @a;
my $arg = join(" ", @a);
my $list = "rawmsg";
my $list = "resetAccumulatedPower";
# only for Letrika solar inverters
$list .= " requestCurrentPower requestTotalEnergy" if $hash->{Manufacturer} eq 'LET' and $hash->{DeviceType} == 2;
return $list if( $cmd eq '?' || $cmd eq '');
if($cmd eq "resetAccumulatedPower") {
CommandAttr(undef, "$name accumulatedPowerOffset " . $hash->{READINGS}{accumulatedPowerMeasured}{VAL});
}
if ($cmd eq 'rawmsg') {
WMBUS_Parse($hash, 'b'.$arg);
} elsif ($cmd eq "requestCurrentPower") {
IOWrite($hash, "", "bss");
} elsif ($cmd eq "requestTotalEnergy") {
}
else {
return "Unknown argument $cmd, choose one of ".$list;
}
@ -442,18 +466,28 @@ sub
WMBUS_Attr(@)
{
my ($cmd, $name, $attrName, $attrVal) = @_;
my $hash = $defs{$name};
my $msg = '';
my $hash = $defs{$name};
my $msg = '';
if ($attrName eq 'AESkey') {
if ($attrVal =~ /^[0-9A-Fa-f]{32}$/) {
$hash->{wmbus}->{aeskey} = $attrVal;
} else {
$msg = "AESkey must be a 32 digit hexadecimal value";
}
}
return ($msg) ? $msg : undef;
if ($attrName eq 'AESkey') {
if ($attrVal =~ /^[0-9A-Fa-f]{32}$/) {
$hash->{wmbus}->{aeskey} = $attrVal;
} else {
$msg = "AESkey must be a 32 digit hexadecimal value";
}
} elsif ($attrName eq 'useVIFasReadingName') {
if ($attrVal ne AttrVal($name, 'useVIFasReadingName', '0')) {
# delete all readings on change of namimg format
fhem "deletereading $name .*";
# and recreate them
if (defined($hash->{internal}{rawMsg})) {
$hash->{internal}{useVIFasReadingName} = $attrVal;
WMBUS_Parse($hash, $hash->{internal}{rawMsg});
delete $hash->{internal}{useVIFasReadingName};
}
}
}
return ($msg) ? $msg : undef;
}
1;
@ -499,11 +533,11 @@ WMBUS_Attr(@)
<br>
For a manual definition there are two ways.
<ul>
<li>
<li>
By specifying a raw WMBus message as received by an IODev. Such a message starts with a lower case 'b' and contains at least 24 hexadecimal digits.
The WMBUS module extracts all relevant information from such a message.
</li>
<li>
The WMBUS module extracts all relevant information from such a message.
</li>
<li>
Explictly specify the information that uniquely identifies a WMBus device. <br>
The manufacturer code, which is is a three letter shortcut of the manufacturer name. See
<a href="https://www.dlms.com/flag-id/flag-id-list">dlms.com</a> for a list of registered ids.<br>
@ -518,7 +552,13 @@ WMBUS_Attr(@)
<br>
<a name="WMBUSset"></a>
<b>Set</b> <ul>N/A</ul><br>
<b>Set</b>
<ul>
<li>
rawmsg hexadecimal contents of a raw message (without the leading b)<br>
Will be parsed as if the message has been received by the IODev. Mainly useful for debugging.
</li>
</ul><br>
<a name="WMBUSget"></a>
<b>Get</b> <ul>N/A</ul><br>
@ -529,11 +569,11 @@ WMBUS_Attr(@)
<li><a href="#IODev">IODev</a><br>
Set the IO or physical device which should be used for receiving signals
for this "logical" device. An example for the physical device is a CUL.
</li><br>
</li><br>
<a name="AESkey"></a>
<li>AESkey<br>
A 16 byte AES-Key in hexadecimal digits. Used to decrypt messages from meters which have encryption enabled.
</li><br>
A 16 byte AES-Key in hexadecimal digits. Used to decrypt messages from meters which have encryption enabled.
</li><br>
<li>
<a name="ignore"></a>
<a href="#ignore">ignore</a>
@ -552,9 +592,28 @@ WMBUS_Attr(@)
<li>ignoreMasterMessages<br>
Some devices (e.g. Letrika solar inverters) only send data if they have received a special message from a master device.
The messages sent by the master are ignored unless explictly enabled by this attribute.
</li><br>
<a name="useVIFasReadingName"></a>
<li>useVIFasReadingName<br>
Some devices send several types of messages with different logical content. As the readings are normally numbered consecutively they will be overwitten
by blocks with a different semantic meaning.
If ths attribute is set to 1 the naming of the readings will be changed to start with storage number and VIF (Value Information Field) name.
Therefor each semantically different value will get a unique reading name.<br>
Example:<br>
<pre>
1_storage_no 0
1_type VIF_ENERGY_WATT
1_unit Wh
1_value 1234.5
</pre>
will be changed to<br>
<pre>
0_VIF_ENERGY_WATT_unit Wh
0_VIF_ENERGY_WATT_value 1234.5
</pre>
</li>
</ul>
<br>
<br>
<a name="WMBUSreadings"></a>
<b>Readings</b><br>
<ul>
@ -567,8 +626,8 @@ WMBUS_Attr(@)
<code>1_type VIF_ENERGY_WATT</code><br>
<code>1_unit Wh</code><br>
<code>1_value 2948787</code><br>
</ul>
<br>
</ul>
<br>
There is also a fixed set of readings.
<ul>
<li><code>is_encrypted</code> is 1 if the received message is encrypted.</li>
@ -592,8 +651,8 @@ WMBUS_Attr(@)
Dieses Modul unterst&uuml;tzt Z&auml;hler mit Wireless M-Bus, z. B. f&uuml;r Wasser, Gas oder Elektrizit&auml;t.
Wireless M-Bus ist ein standardisiertes Protokoll das von unterschiedlichen Herstellern unterst&uuml;tzt wird.
Es verwendet das 868 MHz Band f&uuml;r Radio&uuml;bertragungen.
Daher wird ein Ger&auml;t ben&ouml;tigt das die Wireless M-Bus Nachrichten empfangen kann, z. B. ein <a href="#CUL">CUL</a> mit culfw >= 1.59 oder ein AMBER Wireless AMB8465-M.
Es verwendet das 868 MHz Band f&uuml;r Radio&uuml;bertragungen.
Daher wird ein Ger&auml;t ben&ouml;tigt das die Wireless M-Bus Nachrichten empfangen kann, z. B. ein <a href="#CUL">CUL</a> mit culfw >= 1.59 oder ein AMBER Wireless AMB8465-M.
<br>
WMBus verwendet drei unterschiedliche Radioprotokolle, T-Mode, S-Mode und C-Mode. Der Empf&auml;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 <a href="#rfmode">rfmode</a> auf WMBus_T, WMBus_S bzw. WMBus_C gesetzt wird.
@ -605,7 +664,7 @@ WMBUS_Attr(@)
Andernfalls wird die Entschl&uuml;sselung fehlschlagen und es k&ouml;nnen keine relevanten Daten ausgelesen werden. Das Modul kann mit Security Profile A oder B (Mode 5 und 7) verschl&uuml;sselte Nachrichten entschl&uuml;sseln.
<br><br>
<b>Voraussetzungen</b><br>
Dieses Modul ben&ouml;tigt die perl Module Digest::CRC, Crypt::Mode::CBC, Crypt::ModeL::CTR und Digest::CMAC (die letzten drei Module werden nur ben&ouml;tigt wenn verschl&uuml;sselte Nachrichten verarbeitet werden sollen).<br>
Dieses Modul ben&ouml;tigt die perl Module Digest::CRC, Crypt::Mode::CBC, Crypt::Mode::CTR und Digest::CMAC (die letzten drei Module werden nur ben&ouml;tigt wenn verschl&uuml;sselte Nachrichten verarbeitet werden sollen).<br>
Bei einem Debian basierten System k&ouml;nnen diese so installiert werden<br>
<code>
sudo apt-get install libdigest-crc-perl<br>
@ -622,14 +681,14 @@ WMBUS_Attr(@)
<br>
F&uuml;r eine manuelle Definition gibt es zwei Wege.
<ul>
<li>
Durch Verwendung einer WMBus Rohnachricht wie sie vom IODev empfangen wurde. So eine Nachricht beginnt mit einem kleinen 'b' und enth&auml;lt mindestens
24 hexadezimale Zeichen.
Das WMBUS Modul extrahiert daraus alle ben&ouml;tigten Informationen.
</li>
<li>
Durch explizite Angabe der Informationen die ein WMBus Ger&auml;t eindeutig identfizieren.<br>
Der Hersteller Code, besteht aus drei Buchstaben als Abk&uuml;rzung des Herstellernamens. Eine Liste der Abk&uuml;rzungen findet sich unter
<li>
Durch Verwendung einer WMBus Rohnachricht wie sie vom IODev empfangen wurde. So eine Nachricht beginnt mit einem kleinen 'b' und enth&auml;lt mindestens
24 hexadezimale Zeichen.
Das WMBUS Modul extrahiert daraus alle ben&ouml;tigten Informationen.
</li>
<li>
Durch explizite Angabe der Informationen die ein WMBus Ger&auml;t eindeutig identfizieren.<br>
Der Hersteller Code, besteht aus drei Buchstaben als Abk&uuml;rzung des Herstellernamens. Eine Liste der Abk&uuml;rzungen findet sich unter
<a href="https://www.dlms.com/flag-id/flag-id-list">dlms.com</a><br>
Die Idenitfikationsnummer ist die Seriennummer des Z&auml;hlers.<br>
Version ist ein Versionscode des Z&auml;hlers.<br>
@ -642,7 +701,13 @@ WMBUS_Attr(@)
<br>
<a name="WMBUSset"></a>
<b>Set</b> <ul>N/A</ul><br>
<b>Set</b>
<ul>
<li>
rawmsg Hexadezimaler Inhalt einer Rohnachricht (ohne f&uuml;hrendes b)<br>
Wird interpretiert als ob die Nachricht von einem IODev empfangen worden w&auml;re. Haupts&auml;chlich n&uuml;tzlich zum debuggen.
</li>
</ul><br>
<a name="WMBUSget"></a>
<b>Get</b> <ul>N/A</ul><br>
@ -651,14 +716,14 @@ WMBUS_Attr(@)
<ul>
<a name="IODev"></a>
<li><a href="#IODev">IODev</a><br>
Setzt den IO oder physisches Ger&auml;t welches f&uuml;r den Empfang der Signale f&uuml;r dieses 'logische' Ger&auml;t verwendet werden soll.
Ein Beispiel f&uuml;r ein solches Ger&auml;t ist ein CUL.
</li><br>
Setzt den IO oder physisches Ger&auml;t welches f&uuml;r den Empfang der Signale f&uuml;r dieses 'logische' Ger&auml;t verwendet werden soll.
Ein Beispiel f&uuml;r ein solches Ger&auml;t ist ein CUL.
</li><br>
<a name="AESkey"></a>
<li>AESKey<br>
Ein 16 Bytes langer AES-Schl&uuml;ssel in hexadezimaler Schreibweise. Wird verwendet um Nachrichten von Z&auml;hlern zu entschl&uuml;sseln bei denen
die Verschl&uuml;sselung aktiviert ist.
</li><br>
<li>AESKey<br>
Ein 16 Bytes langer AES-Schl&uuml;ssel in hexadezimaler Schreibweise. Wird verwendet um Nachrichten von Z&auml;hlern zu entschl&uuml;sseln bei denen
die Verschl&uuml;sselung aktiviert ist.
</li><br>
<li>
<a name="ignore"></a>
<a href="#ignore">ignore</a>
@ -676,8 +741,27 @@ WMBUS_Attr(@)
Einige Geräte (z. B. Letrika Wechselrichter) senden nur dann Daten wenn sie eine spezielle Nachricht von einem Mastergerät erhalten haben.
Die Nachrichten von dem Master werden ignoriert es sei denn es wird explizit mit diesem Attribut eingeschaltet.
</li>
<a name="useVIFasReadingName"></a>
<li>useVIFasReadingName<br>
Einige Ger&auml;te senden verschiedene Arten von Nachrichten mit logisch unterschiedlichem Inhalt. Da die Readings normalerweise aufsteigend nummeriert werden
k&ouml;nnen Readings durch semantisch unterschiedliche Readings &uuml;berschrieben werden.
Wenn dieses Attribut auf 1 gesetzt ist &auml;ndert sich die Namenskonvention der Readings. Die Namen setzen sich dann aus der Storagenumber und dem
VIF (Value Information Field) zusammen. Dadurch bekommt jeder semantisch unterschiedliche Wert einen eindeutigen Readingnamen.
Beispiel:<br>
<pre>
1_storage_no 0
1_type VIF_ENERGY_WATT
1_unit Wh
1_value 1234.5
</pre>
wird zu<br>
<pre>
0_VIF_ENERGY_WATT_unit Wh
0_VIF_ENERGY_WATT_value 1234.5
</pre>
</li>
</ul>
<br>
<br>
<a name="WMBUSreadings"></a>
<b>Readings</b><br>
<ul>
@ -691,9 +775,9 @@ WMBUS_Attr(@)
<code>1_type VIF_ENERGY_WATT</code><br>
<code>1_unit Wh</code><br>
<code>1_value 2948787</code><br>
</ul>
<br>
Es gibt auch eine Anzahl von festen Readings.
</ul>
<br>
Es gibt auch eine Anzahl von festen Readings.
<ul>
<li><code>is_encrypted</code> ist 1 wenn die empfangene Nachricht verschl&uuml;sselt ist.</li>
<li><code>decryption_ok</code> ist 1 wenn die Nachricht entweder erfolgreich entschl&uuml;sselt wurde oder gar nicht verschl&uuml;sselt war.</li>

View File

@ -109,6 +109,13 @@ use constant {
FRAME_TYPE_A => 'A',
FRAME_TYPE_B => 'B',
# content type (CC bits of configuration field)
# stored in $self->{cw_parts}{content}
CONTENT_STANDARD => 0b00, # Standard data message with unsigned variable meter data
CONTENT_STATIC => 0b10, # Static message (consists of parameter, OBIS definitions and other data points
# which are not frequently changed see also 4.3.2.4).
};
sub valueCalcNumeric($$) {
@ -193,7 +200,19 @@ sub valueCalcHex($$) {
my $value = shift;
my $dataBlock = shift;
return sprintf("%x", $value);
return unpack("H*", $value);
}
sub valueCalcAscii($$) {
my $value = shift;
my $dataBlock = shift;
my $result = unpack('a*',$value);
# replace non printable chars
$result =~ s/[\x00-\x1f\x7f-\xff]/?/g;
return $result;
}
sub valueCalcu($$) {
@ -453,7 +472,7 @@ my %VIFInfo = (
type => 0b01111000,
bias => 0,
unit => '',
calcFunc => \&valueCalcNumeric,
calcFunc => \&valueCalcAscii,
},
VIF_OWNER_NO => { # Eigentumsnummer (used by Easymeter even though the standard allows this only for writing to a slave)
typeMask => 0b01111111,
@ -556,7 +575,7 @@ my %VIFInfo_FD = (
type => 0b00001001,
bias => 0,
unit => '',
calcFunc => \&valueCalcNumeric,
calcFunc => \&valueCalcAscii,
},
VIF_MANUFACTURER => { # Manufacturer (as in fixed header)
typeMask => 0b01111111,
@ -606,6 +625,57 @@ my %VIFInfo_FD = (
unit => '',
calcFunc => \&valueCalcNumeric,
},
VIF_CUSTOMER_LOCATION => { # Customer location
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00010000,
bias => 0,
unit => '',
calcFunc => \&valueCalcHex
},
VIF_CUSTOMER_CUSTOMER => { # Customer
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00010001,
bias => 0,
unit => '',
calcFunc => \&valueCalcHex
},
VIF_ACCESS_CODE_USER => { # Access code user
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00010010,
bias => 0,
unit => '',
calcFunc => \&valueCalcHex
},
VIF_ACCESS_CODE_OPERATOR => { # Access code operator
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00010011,
bias => 0,
unit => '',
calcFunc => \&valueCalcHex
},
VIF_ACCESS_CODE_SYSTEM_OPERATOR => { # Access code system operator
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00010100,
bias => 0,
unit => '',
calcFunc => \&valueCalcHex
},
VIF_PASSWORD => { # Password
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00010110,
bias => 0,
unit => '',
calcFunc => \&valueCalcHex
},
VIF_ERROR_FLAGS => { # Error flags (binary)
typeMask => 0b01111111,
expMask => 0b00000000,
@ -960,6 +1030,9 @@ my %VIFInfo_ESY = (
unit => 'W',
calcFunc => \&valueCalcNumeric,
},
);
my %VIFInfo_ESY2 = (
VIF_ELECTRIC_POWER_PHASE_NO => {
typeMask => 0b01111110,
expMask => 0b00000000,
@ -1346,8 +1419,10 @@ sub decodeValueInformationBlock($$$) {
if ($self->{manufacturer} eq 'ESY') {
# Easymeter
$vif = unpack('C', substr($vib,$offset++,1));
#printf("ESY VIF %x\n", $vif);
$vifInfoRef = \%VIFInfo_ESY;
} elsif ($self->{manufacturer} eq 'KAM') {
# Kamstrup
$vif = unpack('C', substr($vib,$offset++,1));
$vifInfoRef = \%VIFInfo_KAM;
} else {
@ -1363,7 +1438,8 @@ sub decodeValueInformationBlock($$$) {
#print "other extension\n";
$dataBlockExt = {};
if ($self->{manufacturer} eq 'ESY') {
$vifInfoRef = \%VIFInfo_ESY;
#print "ESY\n";
$vifInfoRef = \%VIFInfo_ESY2;
$dataBlockExt->{value} = unpack('C',substr($vib,2,1)) * 100;
} else {
$dataBlockExt->{value} = $vif;
@ -1386,7 +1462,7 @@ sub decodeValueInformationBlock($$$) {
# Plaintext VIF
$offset = $self->decodePlaintext($vib, $dataBlockRef, $offset);
} elsif (findVIF($vif, $vifInfoRef, $dataBlockRef) == 0) {
$dataBlockRef->{errormsg} = "unknown VIF " . sprintf("%x", $vifExtension) . " at offset " . ($offset-1);
$dataBlockRef->{errormsg} = "unknown VIFE " . sprintf("%x", $vifExtension) . " at offset " . ($offset-1);
$dataBlockRef->{errorcode} = ERR_UNKNOWN_VIFE;
}
}
@ -1564,10 +1640,7 @@ sub decodePayload($$) {
#print "VALUE: " . $value . "\n";
} else {
# ASCII string with LVAR characters
$value = unpack('a*',substr($payload, $offset, $lvar));
# replace non printable chars
$value =~ s/[\x00-\x1f\x7f]/?/g;
$value = valueCalcAscii(substr($payload, $offset, $lvar), $dataBlock);
if ($self->{manufacturer} eq 'ESY') {
# Easymeter stores the string backwards!
@ -1610,12 +1683,13 @@ sub decodePayload($$) {
my $VIFExtensions = $dataBlock->{VIFExtensions};
for my $VIFExtension (@$VIFExtensions) {
$dataBlock->{extension} = $VIFExtension->{unit};
$dataBlock->{extension_unit} = $VIFExtension->{unit};
#printf("extension unit %s\n", $dataBlock->{extension_unit});
if (defined $VIFExtension->{calcFunc}) {
#printf("Extension value %d, valueFactor %d\n", $VIFExtension->{value}, $VIFExtension->{valueFactor});
$dataBlock->{extension} .= ", " . $VIFExtension->{calcFunc}->($VIFExtension->{value}, $dataBlock);
$dataBlock->{extension_value} = $VIFExtension->{calcFunc}->($VIFExtension->{value}, $dataBlock);
} elsif (defined $VIFExtension->{value}) {
$dataBlock->{extension} .= ", " . sprintf("%x",$VIFExtension->{value});
$dataBlock->{extension_value} = sprintf("%x",$VIFExtension->{value});
} else {
#$dataBlock->{extension} = "";
}
@ -2312,4 +2386,62 @@ sub parseApplicationLayer($)
return $self->decodeApplicationLayer();
}
sub dumpResult($)
{
my $self = shift;
if ($self->{linkLayerOk}) {
printf("Manufacturer %x %s\n", $self->{mfield}, $self->{manufacturer});
printf("IdentNumber %s\n", $self->{afield_id});
printf("Version %d\n", $self->{afield_ver});
printf("Type %x %s\n", $self->{afield_type}, $self->{typestring});
printf("IsEncrypted %d\n", $self->{isEncrypted});
printf("Status: %x %s\n", $self->{status}, $self->{statusstring});
if ($self->{cw_parts}{mode} == 5) {
print "Codeword:\n";
print "bidirectional: ". $self->{cw_parts}{bidirectional} . "\n";
print "accessability: ". $self->{cw_parts}{accessability} . "\n";
print "synchronous: $self->{cw_parts}{synchronous}\n";
print "mode: $self->{cw_parts}{mode}\n";
print "encrypted_blocks: $self->{cw_parts}{encrypted_blocks}\n";
print "content: $self->{cw_parts}{content}\n";
print "hops: $self->{cw_parts}{hops}\n";
}
}
if ($self->{errorcode} == ERR_NO_ERROR) {
if ($self->{cifield} == CI_RESP_12) {
printf("Meter Id %d\n", $self->{meter_id});
printf("Meter Manufacturer %x %s\n", $self->{meter_man}, $self->manId2ascii($self->{meter_man}));
printf("Meter Version %d\n", $self->{meter_vers});
printf("Meter Dev %x %s\n", $self->{meter_dev}, $self->type2string($self->{meter_dev}));
printf("Access No %d\n", $self->{access_no});
printf("Status %x\n", $self->{status});
}
my $dataBlocks = $self->{datablocks};
my $dataBlock;
for $dataBlock ( @$dataBlocks ) {
#if ( $dataBlock->{type} eq "MANUFACTURER SPECIFIC") {
# print $dataBlock->{number} . " " . $dataBlock->{type} . "\n";
#} else {
print $dataBlock->{number} . ". StorageNo " . $dataBlock->{storageNo} . " " ;
print $dataBlock->{functionFieldText} . " ";
print $dataBlock->{type} . " " . $dataBlock->{value} . " " . $dataBlock->{unit};
if ($dataBlock->{errormsg}) {
print "(" . $dataBlock->{errormsg} . ")";
}
if (defined($dataBlock->{extension_unit})) {
print " [" . $dataBlock->{extension_unit} . ", " . $dataBlock->{extension_value} . "]";
}
print "\n";
#}
}
} else {
printf("Error %d: %s\n", $self->{errorcode}, $self->{errormsg});
}
}
1;