2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-02-27 20:34:52 +00:00

36_WMBUS: add preliminary support for Letrika solar inverters

git-svn-id: https://svn.fhem.de/fhem/trunk@18847 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
kaihs 2019-03-10 12:48:20 +00:00
parent ac9d29375c
commit 8009dc6143
3 changed files with 189 additions and 12 deletions

@ -1,5 +1,6 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # 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. # Do not insert empty lines here, update check depends on it.
- feature: 36_WMBUS: add preliminary support for Letrika solar inverters
- bugfix: 14_SD_WS07.pm: fix bug autocreate define loop, - bugfix: 14_SD_WS07.pm: fix bug autocreate define loop,
broken definitions will be repaired. You need so save your config! broken definitions will be repaired. You need so save your config!
removed rssi, slider for offsets and battery reading removed rssi, slider for offsets and battery reading

@ -34,6 +34,7 @@ sub WMBUS_Initialize($) {
" ignore:0,1". " ignore:0,1".
" rawmsg_as_reading:0,1". " rawmsg_as_reading:0,1".
" ignoreUnknownDataBlocks:0,1". " ignoreUnknownDataBlocks:0,1".
" ignoreMasterMessages:0,1".
" $readingFnAttributes"; " $readingFnAttributes";
} }
@ -238,6 +239,11 @@ WMBUS_Parse($$)
($msg, $rssi, $hash->{MessageEncoding}) = WMBUS_HandleEncoding($mb, $rawMsg); ($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)))) { if ($mb->parseLinkLayer(pack('H*',substr($msg,1)))) {
$addr = join("_", $mb->{manufacturer}, $mb->{afield_id}, $mb->{afield_ver}, $mb->{afield_type}); $addr = join("_", $mb->{manufacturer}, $mb->{afield_id}, $mb->{afield_ver}, $mb->{afield_type});
@ -329,7 +335,11 @@ sub WMBUS_SetReadings($$$)
readingsBeginUpdate($hash); readingsBeginUpdate($hash);
if ($mb->{decrypted}) { 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 $dataBlocks = $mb->{datablocks};
my $dataBlock; my $dataBlock;
@ -533,6 +543,10 @@ WMBUS_Attr(@)
formats of which some can be interpreted and some not. This prevents the unknown data overwriting the readings of the data that can be formats of which some can be interpreted and some not. This prevents the unknown data overwriting the readings of the data that can be
interpreted. interpreted.
</li> </li>
<li>ignoreMasterMessages
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>
</ul> </ul>
<br> <br>
<a name="WMBUSreadings"></a> <a name="WMBUSreadings"></a>
@ -647,6 +661,10 @@ WMBUS_Attr(@)
Wenn auf 1 gesetzt so werden Datenblocks die unbekannte/herstellerspezifische Daten enthalten ignoriert. Das ist hilfreich wenn ein Z&auml;hler Daten in unterschiedlichen Wenn auf 1 gesetzt so werden Datenblocks die unbekannte/herstellerspezifische Daten enthalten ignoriert. Das ist hilfreich wenn ein Z&auml;hler Daten in unterschiedlichen
Formaten sendet von denen einige nicht interpretiert werden k&ouml;nnen. Es verhindert, dass die unbekannten Daten die Readings der interpretierbaren Daten &uuml;berschreiben. Formaten sendet von denen einige nicht interpretiert werden k&ouml;nnen. Es verhindert, dass die unbekannten Daten die Readings der interpretierbaren Daten &uuml;berschreiben.
</li> </li>
<li>ignoreMasterMessages
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>
</ul> </ul>
<br> <br>
<a name="WMBUSreadings"></a> <a name="WMBUSreadings"></a>

@ -50,6 +50,7 @@ use constant {
CI_AFL => 0x90, # Authentification and Fragmentation Layer, variable size CI_AFL => 0x90, # Authentification and Fragmentation Layer, variable size
CI_RESP_SML_4 => 0x7e, # Response from device, 4 Bytes, application layer SML encoded CI_RESP_SML_4 => 0x7e, # Response from device, 4 Bytes, application layer SML encoded
CI_RESP_SML_12 => 0x7f, # Response from device, 12 Bytes, application layer SML encoded CI_RESP_SML_12 => 0x7f, # Response from device, 12 Bytes, application layer SML encoded
CI_SND_UD_MODE_1 => 0x51, # The master can send data to a slave using a SND_UD with CI-Field 51h for mode 1 or 55h for mode 2
# DIF types (Data Information Field), see page 32 # DIF types (Data Information Field), see page 32
DIF_NONE => 0x00, DIF_NONE => 0x00,
@ -546,6 +547,22 @@ my %VIFInfo_FD = (
unit => '', unit => '',
calcFunc => \&valueCalcNumeric, calcFunc => \&valueCalcNumeric,
}, },
VIF_MANUFACTURER => { # Manufacturer (as in fixed header)
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00001010,
bias => 0,
unit => '',
calcFunc => \&valueCalcNumeric,
},
VIF_PARAMETER_SET_ID => { # Parameter set identification
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00001011,
bias => 0,
unit => '',
calcFunc => \&valueCalcNumeric,
},
VIF_MODEL_VERSION => { # Model / Version VIF_MODEL_VERSION => { # Model / Version
typeMask => 0b01111111, typeMask => 0b01111111,
expMask => 0b00000000, expMask => 0b00000000,
@ -554,6 +571,30 @@ my %VIFInfo_FD = (
unit => '', unit => '',
calcFunc => \&valueCalcNumeric, calcFunc => \&valueCalcNumeric,
}, },
VIF_HARDWARE_VERSION => { # Hardware version #
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00001101,
bias => 0,
unit => '',
calcFunc => \&valueCalcNumeric,
},
VIF_FIRMWARE_VERSION => { # Firmware version #
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00001110,
bias => 0,
unit => '',
calcFunc => \&valueCalcNumeric,
},
VIF_SOFTWARE_VERSION => { # Software version #
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00001111,
bias => 0,
unit => '',
calcFunc => \&valueCalcNumeric,
},
VIF_ERROR_FLAGS => { # Error flags (binary) VIF_ERROR_FLAGS => { # Error flags (binary)
typeMask => 0b01111111, typeMask => 0b01111111,
expMask => 0b00000000, expMask => 0b00000000,
@ -586,7 +627,7 @@ my %VIFInfo_FD = (
unit => 'A', unit => 'A',
calcFunc => \&valueCalcNumeric, calcFunc => \&valueCalcNumeric,
}, },
VIF_RECEPTION_LEVEL => { # reception level of a received radio device. VIF_RECEPTION_LEVEL => { # reception level of a received radio device.
typeMask => 0b01111111, typeMask => 0b01111111,
expMask => 0b00000000, expMask => 0b00000000,
type => 0b01110001, type => 0b01110001,
@ -594,6 +635,23 @@ my %VIFInfo_FD = (
unit => 'dBm', unit => 'dBm',
calcFunc => \&valueCalcNumeric, calcFunc => \&valueCalcNumeric,
}, },
VIF_STATE_PARAMETER_ACTIVATION => { # State of parameter activation
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b01100110,
bias => 0,
unit => '',
calcFunc => \&valueCalcNumeric,
},
VIF_SPECIAL_SUPPLIER_INFORMATION => { # Special supplier information
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b01100111,
bias => 0,
unit => '',
calcFunc => \&valueCalcNumeric,
},
VIF_FD_RESERVED => { # Reserved VIF_FD_RESERVED => { # Reserved
typeMask => 0b01110000, typeMask => 0b01110000,
expMask => 0b00000000, expMask => 0b00000000,
@ -642,6 +700,13 @@ my %VIFInfo_other = (
unit => 'Illegal VIF-Group', unit => 'Illegal VIF-Group',
}, },
VIF_DATA_UNDERFLOW => {
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00010111,
bias => 0,
unit => 'Data underflow',
},
VIF_PER_SECOND => { VIF_PER_SECOND => {
@ -723,6 +788,90 @@ my %VIFInfo_other = (
bias => 0, bias => 0,
unit => 'per liter', unit => 'per liter',
}, },
VIF_PER_M3 => {
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00101101,
bias => 0,
unit => 'per m³',
},
VIF_PER_KG => {
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00101110,
bias => 0,
unit => 'per kg',
},
VIF_PER_K => {
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00101111,
bias => 0,
unit => 'per K',
},
VIF_PER_KWH => {
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00110000,
bias => 0,
unit => 'per kWh',
},
VIF_PER_GJ => {
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00110001,
bias => 0,
unit => 'per GJ',
},
VIF_PER_KW => {
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00110010,
bias => 0,
unit => 'per kW',
},
VIF_PER_KL => {
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00110011,
bias => 0,
unit => 'per (K*l)',
},
VIF_PER_V => {
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00110100,
bias => 0,
unit => 'per V',
},
VIF_PER_A => {
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00110101,
bias => 0,
unit => 'per A',
},
VIF_PER_MULT_S => {
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00110110,
bias => 0,
unit => 'multiplied by sek',
},
VIF_PER_MULT_SV => {
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00110111,
bias => 0,
unit => 'multiplied by sek / V',
},
VIF_PER_MULT_SA => {
typeMask => 0b01111111,
expMask => 0b00000000,
type => 0b00111000,
bias => 0,
unit => 'multiplied by sek / A',
},
VIF_START_DATE_TIME => { VIF_START_DATE_TIME => {
typeMask => 0b01111111, typeMask => 0b01111111,
@ -1162,7 +1311,7 @@ sub decodeValueInformationBlock($$$) {
# Plaintext VIF # Plaintext VIF
my $vifLength = unpack('C', substr($vib,$offset++,1)); my $vifLength = unpack('C', substr($vib,$offset++,1));
$dataBlockRef->{type} = "see unit"; $dataBlockRef->{type} = "see unit";
$dataBlockRef->{unit} = unpack(sprintf("C%d",$vifLength), substr($vib, $offset, $vifLength)); $dataBlockRef->{unit} = substr($vib, $offset, $vifLength);
$offset += $vifLength; $offset += $vifLength;
$analyzeVIF = 0; $analyzeVIF = 0;
last EXTENSION; last EXTENSION;
@ -1207,7 +1356,13 @@ sub decodeValueInformationBlock($$$) {
} }
if ($analyzeVIF) { if ($analyzeVIF) {
if (findVIF($vif, $vifInfoRef, $dataBlockRef) == 0) { if ($vif == 0x7C) {
# Plaintext VIF
my $vifLength = unpack('C', substr($vib,$offset++,1));
$dataBlockRef->{type} = "see unit";
$dataBlockRef->{unit} = substr($vib, $offset, $vifLength);
$offset += $vifLength;
} elsif (findVIF($vif, $vifInfoRef, $dataBlockRef) == 0) {
$dataBlockRef->{errormsg} = "unknown VIF " . sprintf("%x", $vifExtension) . " at offset " . ($offset-1); $dataBlockRef->{errormsg} = "unknown VIF " . sprintf("%x", $vifExtension) . " at offset " . ($offset-1);
$dataBlockRef->{errorcode} = ERR_UNKNOWN_VIFE; $dataBlockRef->{errorcode} = ERR_UNKNOWN_VIFE;
} }
@ -1327,11 +1482,9 @@ sub decodePayload($$) {
$offset += $self->decodeDataRecordHeader(substr($payload,$offset), $dataBlock); $offset += $self->decodeDataRecordHeader(substr($payload,$offset), $dataBlock);
#printf("No. %d, type %x at offset %d\n", $dataBlockNo, $dataBlock->{dataField}, $offset-1); #printf("No. %d, type %x at offset %d\n", $dataBlockNo, $dataBlock->{dataField}, $offset-1);
if ($dataBlock->{dataField} == DIF_NONE) { if ($dataBlock->{dataField} == DIF_NONE or $dataBlock->{dataField} == DIF_READOUT) {
} elsif ($dataBlock->{dataField} == DIF_READOUT) { $dataBlockNo--;
$self->{errormsg} = "in datablock $dataBlockNo: unexpected DIF_READOUT"; $offset++;
$self->{errorcode} = ERR_UNKNOWN_DATAFIELD;
return 0;
} elsif ($dataBlock->{dataField} == DIF_BCD2) { } elsif ($dataBlock->{dataField} == DIF_BCD2) {
$value = $self->decodeBCD(2, substr($payload,$offset,1)); $value = $self->decodeBCD(2, substr($payload,$offset,1));
$offset += 1; $offset += 1;
@ -1407,7 +1560,7 @@ sub decodePayload($$) {
#print "DIF_SPECIAL at $offset\n"; #print "DIF_SPECIAL at $offset\n";
$value = unpack("H*", substr($payload,$offset)); $value = unpack("H*", substr($payload,$offset));
last PAYLOAD; last PAYLOAD;
} else { } else {
$self->{errormsg} = "in datablock $dataBlockNo: unhandled datafield " . sprintf("%x",$dataBlock->{dataField}); $self->{errormsg} = "in datablock $dataBlockNo: unhandled datafield " . sprintf("%x",$dataBlock->{dataField});
$self->{errorcode} = ERR_UNKNOWN_DATAFIELD; $self->{errorcode} = ERR_UNKNOWN_DATAFIELD;
return 0; return 0;
@ -1744,20 +1897,23 @@ sub decodeApplicationLayer($) {
$self->{status} = 0; $self->{status} = 0;
$self->{statusstring} = ""; $self->{statusstring} = "";
$self->{access_no} = 0; $self->{access_no} = 0;
$self->{sent_from_master} = 0;
#printf("CI Field %02x\n", $self->{cifield});
if ($self->{cifield} == CI_RESP_4 || $self->{cifield} == CI_RESP_SML_4) { if ($self->{cifield} == CI_RESP_4 || $self->{cifield} == CI_RESP_SML_4) {
# Short header # Short header
#print "short header\n";
($self->{access_no}, $self->{status}, $self->{cw_1}, $self->{cw_2}) = unpack('CCCC', substr($applicationlayer,$offset)); ($self->{access_no}, $self->{status}, $self->{cw_1}, $self->{cw_2}) = unpack('CCCC', substr($applicationlayer,$offset));
#printf("Short header access_no %x\n", $self->{access_no});
$offset += 4; $offset += 4;
} elsif ($self->{cifield} == CI_RESP_12 || $self->{cifield} == CI_RESP_SML_12) { } elsif ($self->{cifield} == CI_RESP_12 || $self->{cifield} == CI_RESP_SML_12) {
# Long header # Long header
#print "Long header\n";
($self->{meter_id}, $self->{meter_man}, $self->{meter_vers}, $self->{meter_dev}, $self->{access_no}, $self->{status}, $self->{cw_1}, $self->{cw_2}) ($self->{meter_id}, $self->{meter_man}, $self->{meter_vers}, $self->{meter_dev}, $self->{access_no}, $self->{status}, $self->{cw_1}, $self->{cw_2})
= unpack('VvCCCCCC', substr($applicationlayer,$offset)); = unpack('VvCCCCCC', substr($applicationlayer,$offset));
$self->{meter_id} = sprintf("%08d", $self->{meter_id}); $self->{meter_id} = sprintf("%08d", $self->{meter_id});
$self->{meter_devtypestring} = $validDeviceTypes{$self->{meter_dev}} || 'unknown'; $self->{meter_devtypestring} = $validDeviceTypes{$self->{meter_dev}} || 'unknown';
$self->{meter_manufacturer} = uc($self->manId2ascii($self->{meter_man})); $self->{meter_manufacturer} = uc($self->manId2ascii($self->{meter_man}));
#printf("Long header access_no %x\n", $self->{access_no});
$offset += 12; $offset += 12;
} elsif ($self->{cifield} == CI_RESP_0) { } elsif ($self->{cifield} == CI_RESP_0) {
# no header # no header
@ -1802,6 +1958,8 @@ sub decodeApplicationLayer($) {
$self->{errorcode} = ERR_CRC_FAILED; $self->{errorcode} = ERR_CRC_FAILED;
return 0; return 0;
} }
} elsif ($self->{cifield} == CI_SND_UD_MODE_1) {
$self->{sent_from_master} = 1;
} else { } else {
# unsupported # unsupported
$self->decodeConfigword(); $self->decodeConfigword();