diff --git a/dsmr_parser/obis_name_mapping.py b/dsmr_parser/obis_name_mapping.py index d5a6d92..d9a21a3 100644 --- a/dsmr_parser/obis_name_mapping.py +++ b/dsmr_parser/obis_name_mapping.py @@ -50,9 +50,29 @@ EN = { obis.ACTUAL_TRESHOLD_ELECTRICITY: 'ACTUAL_TRESHOLD_ELECTRICITY', obis.ACTUAL_SWITCH_POSITION: 'ACTUAL_SWITCH_POSITION', obis.VALVE_POSITION_GAS: 'VALVE_POSITION_GAS', - obis.BELGIUM_5MIN_GAS_METER_READING: 'BELGIUM_5MIN_GAS_METER_READING', + obis.BELGIUM_EQUIPMENT_IDENTIFIER: 'BELGIUM_EQUIPMENT_IDENTIFIER', + obis.BELGIUM_CURRENT_AVERAGE_DEMAND: 'BELGIUM_CURRENT_AVERAGE_DEMAND', + obis.BELGIUM_MAXIMUM_DEMAND_MONTH: 'BELGIUM_MAXIMUM_DEMAND_MONTH', + obis.BELGIUM_MAXIMUM_DEMAND_13_MONTHS: 'BELGIUM_MAXIMUM_DEMAND_13_MONTHS', obis.BELGIUM_MAX_POWER_PER_PHASE: 'BELGIUM_MAX_POWER_PER_PHASE', obis.BELGIUM_MAX_CURRENT_PER_PHASE: 'BELGIUM_MAX_CURRENT_PER_PHASE', + obis.BELGIUM_MBUS1_DEVICE_TYPE: 'BELGIUM_MBUS1_DEVICE_TYPE', + obis.BELGIUM_MBUS1_EQUIPMENT_IDENTIFIER: 'BELGIUM_MBUS1_EQUIPMENT_IDENTIFIER', + obis.BELGIUM_MBUS1_VALVE_POSITION: 'BELGIUM_MBUS1_VALVE_POSITION', + obis.BELGIUM_MBUS1_METER_READING1: 'BELGIUM_MBUS1_METER_READING1', + obis.BELGIUM_MBUS1_METER_READING2: 'BELGIUM_MBUS1_METER_READING2', + obis.BELGIUM_MBUS2_EQUIPMENT_IDENTIFIER: 'BELGIUM_MBUS2_EQUIPMENT_IDENTIFIER', + obis.BELGIUM_MBUS2_VALVE_POSITION: 'BELGIUM_MBUS2_VALVE_POSITION', + obis.BELGIUM_MBUS2_METER_READING1: 'BELGIUM_MBUS2_METER_READING1', + obis.BELGIUM_MBUS2_METER_READING2: 'BELGIUM_MBUS2_METER_READING2', + obis.BELGIUM_MBUS3_EQUIPMENT_IDENTIFIER: 'BELGIUM_MBUS3_EQUIPMENT_IDENTIFIER', + obis.BELGIUM_MBUS3_VALVE_POSITION: 'BELGIUM_MBUS3_VALVE_POSITION', + obis.BELGIUM_MBUS3_METER_READING1: 'BELGIUM_MBUS3_METER_READING1', + obis.BELGIUM_MBUS3_METER_READING2: 'BELGIUM_MBUS3_METER_READING2', + obis.BELGIUM_MBUS4_EQUIPMENT_IDENTIFIER: 'BELGIUM_MBUS4_EQUIPMENT_IDENTIFIER', + obis.BELGIUM_MBUS4_VALVE_POSITION: 'BELGIUM_MBUS4_VALVE_POSITION', + obis.BELGIUM_MBUS4_METER_READING1: 'BELGIUM_MBUS4_METER_READING1', + obis.BELGIUM_MBUS4_METER_READING2: 'BELGIUM_MBUS4_METER_READING2', obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: 'LUXEMBOURG_EQUIPMENT_IDENTIFIER', obis.Q3D_EQUIPMENT_IDENTIFIER: 'Q3D_EQUIPMENT_IDENTIFIER', obis.Q3D_EQUIPMENT_STATE: 'Q3D_EQUIPMENT_STATE', diff --git a/dsmr_parser/obis_references.py b/dsmr_parser/obis_references.py index a5ffd61..b09252b 100644 --- a/dsmr_parser/obis_references.py +++ b/dsmr_parser/obis_references.py @@ -73,10 +73,46 @@ ELECTRICITY_IMPORTED_TOTAL = r'\d-\d:1\.8\.0.+?\r\n' # Total imported energy re ELECTRICITY_EXPORTED_TOTAL = r'\d-\d:2\.8\.0.+?\r\n' # Total exported energy register (P-) # International non generalized additions (country specific) / risk for necessary refactoring -BELGIUM_5MIN_GAS_METER_READING = r'\d-\d:24\.2\.3.+?\r\n' # Different code, same format. +BELGIUM_VERSION_INFORMATION = r'\d-\d:96\.1\.4.+?\r\n' +BELGIUM_EQUIPMENT_IDENTIFIER = r'\d-0:96\.1\.1.+?\r\n' +BELGIUM_CURRENT_AVERAGE_DEMAND = r'\d-\d:1\.4\.0.+?\r\n' +BELGIUM_MAXIMUM_DEMAND_MONTH = r'\d-\d:1\.6\.0.+?\r\n' +BELGIUM_MAXIMUM_DEMAND_13_MONTHS = r'\d-\d:98\.1\.0.+?\r\n' BELGIUM_MAX_POWER_PER_PHASE = r'\d-\d:17\.0\.0.+?\r\n' # Applicable when power limitation is active BELGIUM_MAX_CURRENT_PER_PHASE = r'\d-\d:31\.4\.0.+?\r\n' # Applicable when current limitation is active + +# Multiple 'slaves' can be linked to the main device. +# Mostly MBUS1 = GAS METER with values on 24.2.3 +# While WATER METER reports it's values on 24.2.1 +# The GAS METER also reports its valve state on 24.4.0 +# Dev type for gas = 7 and water = 8 +BELGIUM_MBUS1_DEVICE_TYPE = r'\d-1:24\.1\.0.+?\r\n' +BELGIUM_MBUS1_EQUIPMENT_IDENTIFIER = r'\d-1:96\.1\.1.+?\r\n' +BELGIUM_MBUS1_VALVE_POSITION = r'\d-1:24\.4\.0.+?\r\n' +BELGIUM_MBUS1_METER_READING1 = r'\d-1:24\.2\.1.+?\r\n' +BELGIUM_MBUS1_METER_READING2 = r'\d-1:24\.2\.3.+?\r\n' + +BELGIUM_MBUS2_DEVICE_TYPE = r'\d-2:24\.1\.0.+?\r\n' +BELGIUM_MBUS2_EQUIPMENT_IDENTIFIER = r'\d-2:96\.1\.1.+?\r\n' +BELGIUM_MBUS2_VALVE_POSITION = r'\d-2:24\.4\.0.+?\r\n' +BELGIUM_MBUS2_METER_READING1 = r'\d-2:24\.2\.1.+?\r\n' +BELGIUM_MBUS2_METER_READING2 = r'\d-2:24\.2\.3.+?\r\n' + +BELGIUM_MBUS3_DEVICE_TYPE = r'\d-3:24\.1\.0.+?\r\n' +BELGIUM_MBUS3_EQUIPMENT_IDENTIFIER = r'\d-3:96\.1\.1.+?\r\n' +BELGIUM_MBUS3_VALVE_POSITION = r'\d-3:24\.4\.0.+?\r\n' +BELGIUM_MBUS3_METER_READING1 = r'\d-3:24\.2\.1.+?\r\n' +BELGIUM_MBUS3_METER_READING2 = r'\d-3:24\.2\.3.+?\r\n' + +BELGIUM_MBUS4_DEVICE_TYPE = r'\d-4:24\.1\.0.+?\r\n' +BELGIUM_MBUS4_EQUIPMENT_IDENTIFIER = r'\d-4:96\.1\.1.+?\r\n' +BELGIUM_MBUS4_VALVE_POSITION = r'\d-4:24\.4\.0.+?\r\n' +BELGIUM_MBUS4_METER_READING1 = r'\d-4:24\.2\.1.+?\r\n' +BELGIUM_MBUS4_METER_READING2 = r'\d-4:24\.2\.3.+?\r\n' + + LUXEMBOURG_EQUIPMENT_IDENTIFIER = r'\d-\d:42\.0\.0.+?\r\n' # Logical device name + Q3D_EQUIPMENT_IDENTIFIER = r'\d-\d:0\.0\.0.+?\r\n' # Logical device name Q3D_EQUIPMENT_STATE = r'\d-\d:96\.5\.5.+?\r\n' # Device state (hexadecimal) Q3D_EQUIPMENT_SERIALNUMBER = r'\d-\d:96\.1\.255.+?\r\n' # Device Serialnumber diff --git a/dsmr_parser/objects.py b/dsmr_parser/objects.py index af7d068..510a77b 100644 --- a/dsmr_parser/objects.py +++ b/dsmr_parser/objects.py @@ -113,6 +113,48 @@ class MBusObject(DSMRObject): } return json.dumps(output) +class MBusObjectPeak(DSMRObject): + + @property + def datetime(self): + return self.values[0]['value'] + + @property + def occurred(self): + return self.values[1]['value'] + + @property + def value(self): + return self.values[2]['value'] + + @property + def unit(self): + return self.values[2]['unit'] + + def __str__(self): + output = "{}\t[{}] at {} occurred {}".format(str(self.value), str(self.unit), str(self.datetime.astimezone().isoformat()), str(self.occurred.astimezone().isoformat())) + return output + + def to_json(self): + timestamp = self.datetime + if isinstance(self.datetime, datetime.datetime): + timestamp = self.datetime.astimezone().isoformat() + timestamp_occurred = self.occurred + if isinstance(self.occurred, datetime.datetime): + timestamp_occurred = self.occurred.astimezone().isoformat() + value = self.value + if isinstance(self.value, datetime.datetime): + value = self.value.astimezone().isoformat() + if isinstance(self.value, Decimal): + value = float(self.value) + output = { + 'datetime': timestamp, + 'occurred': timestamp_occurred, + 'value': value, + 'unit': self.unit + } + return json.dumps(output) + class CosemObject(DSMRObject): diff --git a/dsmr_parser/parsers.py b/dsmr_parser/parsers.py index 984253e..101d08f 100644 --- a/dsmr_parser/parsers.py +++ b/dsmr_parser/parsers.py @@ -3,12 +3,14 @@ import re from binascii import unhexlify from ctypes import c_ushort +from decimal import Decimal from dlms_cosem.connection import XDlmsApduFactory from dlms_cosem.protocol.xdlms import GeneralGlobalCipher -from dsmr_parser.objects import MBusObject, CosemObject, ProfileGenericObject +from dsmr_parser.objects import MBusObject, MBusObjectPeak, CosemObject, ProfileGenericObject from dsmr_parser.exceptions import ParseError, InvalidChecksumError +from dsmr_parser.value_types import timestamp logger = logging.getLogger(__name__) @@ -214,6 +216,43 @@ class MBusParser(DSMRObjectParser): return MBusObject(self._parse(line)) +class MaxDemandParser(DSMRObjectParser): + """ + Max demand history parser. + + These are lines with multiple values. Each containing 2 timestamps and a value + + Line format: + 'ID (Count) (ID) (ID) (TST) (TST) (Mv1*U1)' + + 1 2 3 4 5 6 7 + + 1) OBIS Reduced ID-code + 2) Amount of values in the response + 3) ID of the source + 4) ^^ + 5) Time Stamp (TST) of the month + 6) Time Stamp (TST) when the max demand occured + 6) Measurement value 1 (most recent entry of buffer attribute without unit) + 7) Unit of measurement values (Unit of capture objects attribute) + """ + + def parse(self, line): + pattern = re.compile(r'((?<=\()[0-9a-zA-Z\.\*\-\:]{0,}(?=\)))') + values = re.findall(pattern, line) + + objects = [] + + count = int(values[0]) + for i in range(1, count+1): + timestamp_month = ValueParser(timestamp).parse(values[i*3+1]) + timestamp_occurred = ValueParser(timestamp).parse(values[i*3+1]) + value = ValueParser(Decimal).parse(values[i*3+2]) + objects.append(MBusObjectPeak([timestamp_month, timestamp_occurred, value])) + + return objects + + class CosemParser(DSMRObjectParser): """ Cosem object parser. diff --git a/dsmr_parser/telegram_specifications.py b/dsmr_parser/telegram_specifications.py index 000d61c..2729073 100644 --- a/dsmr_parser/telegram_specifications.py +++ b/dsmr_parser/telegram_specifications.py @@ -2,7 +2,7 @@ from decimal import Decimal from copy import deepcopy from dsmr_parser import obis_references as obis -from dsmr_parser.parsers import CosemParser, ValueParser, MBusParser, ProfileGenericParser +from dsmr_parser.parsers import CosemParser, ValueParser, MBusParser, ProfileGenericParser, MaxDemandParser from dsmr_parser.value_types import timestamp from dsmr_parser.profile_generic_specifications import BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS @@ -141,18 +141,88 @@ V5 = { ALL = (V2_2, V3, V4, V5) - -BELGIUM_FLUVIUS = deepcopy(V5) -BELGIUM_FLUVIUS['objects'].update({ - obis.BELGIUM_5MIN_GAS_METER_READING: MBusParser( - ValueParser(timestamp), - ValueParser(Decimal) - ), - obis.BELGIUM_MAX_POWER_PER_PHASE: CosemParser(ValueParser(Decimal)), - obis.BELGIUM_MAX_CURRENT_PER_PHASE: CosemParser(ValueParser(Decimal)), - obis.ACTUAL_SWITCH_POSITION: CosemParser(ValueParser(str)), - obis.VALVE_POSITION_GAS: CosemParser(ValueParser(str)), -}) +BELGIUM_FLUVIUS = { + 'checksum_support': True, + 'objects': { + obis.BELGIUM_VERSION_INFORMATION: CosemParser(ValueParser(str)), + obis.EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), + obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)), + obis.ELECTRICITY_USED_TARIFF_1: CosemParser(ValueParser(Decimal)), + obis.ELECTRICITY_USED_TARIFF_2: CosemParser(ValueParser(Decimal)), + obis.ELECTRICITY_DELIVERED_TARIFF_1: CosemParser(ValueParser(Decimal)), + obis.ELECTRICITY_DELIVERED_TARIFF_2: CosemParser(ValueParser(Decimal)), + obis.ELECTRICITY_ACTIVE_TARIFF: CosemParser(ValueParser(str)), + obis.BELGIUM_CURRENT_AVERAGE_DEMAND: CosemParser(ValueParser(Decimal)), + obis.BELGIUM_MAXIMUM_DEMAND_MONTH: MBusParser( + ValueParser(timestamp), + ValueParser(Decimal) + ), + obis.BELGIUM_MAXIMUM_DEMAND_13_MONTHS: MaxDemandParser(), + obis.CURRENT_ELECTRICITY_USAGE: CosemParser(ValueParser(Decimal)), + obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), + obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: CosemParser(ValueParser(Decimal)), + obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE: CosemParser(ValueParser(Decimal)), + obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE: CosemParser(ValueParser(Decimal)), + obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE: CosemParser(ValueParser(Decimal)), + obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE: CosemParser(ValueParser(Decimal)), + obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: CosemParser(ValueParser(Decimal)), + obis.INSTANTANEOUS_VOLTAGE_L1: CosemParser(ValueParser(Decimal)), + obis.INSTANTANEOUS_VOLTAGE_L2: CosemParser(ValueParser(Decimal)), + obis.INSTANTANEOUS_VOLTAGE_L3: CosemParser(ValueParser(Decimal)), + obis.INSTANTANEOUS_CURRENT_L1: CosemParser(ValueParser(Decimal)), + obis.INSTANTANEOUS_CURRENT_L2: CosemParser(ValueParser(Decimal)), + obis.INSTANTANEOUS_CURRENT_L3: CosemParser(ValueParser(Decimal)), + obis.ACTUAL_SWITCH_POSITION: CosemParser(ValueParser(int)), + obis.ACTUAL_TRESHOLD_ELECTRICITY: CosemParser(ValueParser(Decimal)), + obis.BELGIUM_MAX_POWER_PER_PHASE: CosemParser(ValueParser(Decimal)), + obis.BELGIUM_MAX_CURRENT_PER_PHASE: CosemParser(ValueParser(Decimal)), + obis.TEXT_MESSAGE: CosemParser(ValueParser(str)), + obis.BELGIUM_MBUS1_DEVICE_TYPE: CosemParser(ValueParser(int)), + obis.BELGIUM_MBUS1_EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), + obis.BELGIUM_MBUS1_VALVE_POSITION: CosemParser(ValueParser(int)), + obis.BELGIUM_MBUS1_METER_READING1: MBusParser( + ValueParser(timestamp), + ValueParser(Decimal) + ), + obis.BELGIUM_MBUS1_METER_READING2: MBusParser( + ValueParser(timestamp), + ValueParser(Decimal) + ), + obis.BELGIUM_MBUS2_DEVICE_TYPE: CosemParser(ValueParser(int)), + obis.BELGIUM_MBUS2_EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), + obis.BELGIUM_MBUS2_VALVE_POSITION: CosemParser(ValueParser(int)), + obis.BELGIUM_MBUS2_METER_READING1: MBusParser( + ValueParser(timestamp), + ValueParser(Decimal) + ), + obis.BELGIUM_MBUS2_METER_READING2: MBusParser( + ValueParser(timestamp), + ValueParser(Decimal) + ), + obis.BELGIUM_MBUS3_DEVICE_TYPE: CosemParser(ValueParser(int)), + obis.BELGIUM_MBUS3_EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), + obis.BELGIUM_MBUS3_VALVE_POSITION: CosemParser(ValueParser(int)), + obis.BELGIUM_MBUS3_METER_READING1: MBusParser( + ValueParser(timestamp), + ValueParser(Decimal) + ), + obis.BELGIUM_MBUS3_METER_READING2: MBusParser( + ValueParser(timestamp), + ValueParser(Decimal) + ), + obis.BELGIUM_MBUS4_DEVICE_TYPE: CosemParser(ValueParser(int)), + obis.BELGIUM_MBUS4_EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), + obis.BELGIUM_MBUS4_VALVE_POSITION: CosemParser(ValueParser(int)), + obis.BELGIUM_MBUS4_METER_READING1: MBusParser( + ValueParser(timestamp), + ValueParser(Decimal) + ), + obis.BELGIUM_MBUS4_METER_READING2: MBusParser( + ValueParser(timestamp), + ValueParser(Decimal) + ), + } +} LUXEMBOURG_SMARTY = deepcopy(V5) LUXEMBOURG_SMARTY['objects'].update({ diff --git a/test/example_telegrams.py b/test/example_telegrams.py index 04b9005..5a350ed 100644 --- a/test/example_telegrams.py +++ b/test/example_telegrams.py @@ -129,6 +129,48 @@ TELEGRAM_V5 = ( '!6EEE\r\n' ) +TELEGRAM_FLUVIUS_V171 = ( + '/FLU5\253769484_A\r\n' + '\r\n' + '0-0:96.1.4(50217)\r\n' + '0-0:96.1.1(3153414733313031303231363035)\r\n' + '0-0:1.0.0(200512135409S)\r\n' + '1-0:1.8.1(000000.034*kWh)\r\n' + '1-0:1.8.2(000015.758*kWh)\r\n' + '1-0:2.8.1(000000.000*kWh)\r\n' + '1-0:2.8.2(000000.011*kWh)\r\n' + '1-0:1.4.0(02.351*kW)\r\n' + '1-0:1.6.0(200509134558S)(02.589*kW)\r\n' + '0-0:98.1.0(3)(1-0:1.6.0)(1-0:1.6.0)(200501000000S)(200423192538S)(03.695*kW)(200401000000S)(200305122139S)(05.980*kW)(200301000000S)(200210035421W)(04.318*kW)\r\n' + '0-0:96.14.0(0001)\r\n' + '1-0:1.7.0(00.000*kW)\r\n' + '1-0:2.7.0(00.000*kW)\r\n' + '1-0:21.7.0(00.000*kW)\r\n' + '1-0:41.7.0(00.000*kW)\r\n' + '1-0:61.7.0(00.000*kW)\r\n' + '1-0:22.7.0(00.000*kW)\r\n' + '1-0:42.7.0(00.000*kW)\r\n' + '1-0:62.7.0(00.000*kW)\r\n' + '1-0:32.7.0(234.7*V)\r\n' + '1-0:52.7.0(234.7*V)\r\n' + '1-0:72.7.0(234.7*V)\r\n' + '1-0:31.7.0(000.00*A)\r\n' + '1-0:51.7.0(000.00*A)\r\n' + '1-0:71.7.0(000.00*A)\r\n' + '0-0:96.3.10(1)\r\n' + '0-0:17.0.0(999.9*kW)\r\n' + '1-0:31.4.0(999*A)\r\n' + '0-0:96.13.0()\r\n' + '0-1:24.1.0(003)\r\n' + '0-1:96.1.1(37464C4F32313139303333373333)\r\n' + '0-1:24.4.0(1)\r\n' + '0-1:24.2.3(200512134558S)(00112.384*m3)\r\n' + '0-2:24.1.0(007)\r\n' + '0-2:96.1.1(3853414731323334353637383930)\r\n' + '0-2:24.2.1(200512134558S)(00872.234*m3)\r\n' + '!911C\r\n' +) + # EasyMeter via COM-1 Ethernet Gateway # Q3D Manual (german) https://www.easymeter.com/downloads/products/zaehler/Q3D/Easymeter_Q3D_DE_2016-06-15.pdf # - type code on page 8 diff --git a/test/test_parse_fluvius.py b/test/test_parse_fluvius.py new file mode 100644 index 0000000..c530248 --- /dev/null +++ b/test/test_parse_fluvius.py @@ -0,0 +1,269 @@ +from decimal import Decimal + +import datetime +import unittest + +import pytz + +from dsmr_parser import obis_references as obis +from dsmr_parser import telegram_specifications +from dsmr_parser.exceptions import InvalidChecksumError, ParseError +from dsmr_parser.objects import CosemObject, MBusObject, MBusObjectPeak +from dsmr_parser.parsers import TelegramParser +from test.example_telegrams import TELEGRAM_FLUVIUS_V171 + + +class TelegramParserFluviusTest(unittest.TestCase): + """ Test parsing of a DSMR Fluvius telegram. """ + + def test_parse(self): + parser = TelegramParser(telegram_specifications.BELGIUM_FLUVIUS) + result = parser.parse(TELEGRAM_FLUVIUS_V171) + + # BELGIUM_VERSION_INFORMATION (0-0:96.1.4) + assert isinstance(result[obis.BELGIUM_VERSION_INFORMATION], CosemObject) + assert result[obis.BELGIUM_VERSION_INFORMATION].unit is None + assert isinstance(result[obis.BELGIUM_VERSION_INFORMATION].value, str) + assert result[obis.BELGIUM_VERSION_INFORMATION].value == '50217' + + # EQUIPMENT_IDENTIFIER (0-0:96.1.1) + assert isinstance(result[obis.EQUIPMENT_IDENTIFIER], CosemObject) + assert result[obis.EQUIPMENT_IDENTIFIER].unit is None + assert isinstance(result[obis.EQUIPMENT_IDENTIFIER].value, str) + assert result[obis.EQUIPMENT_IDENTIFIER].value == '3153414733313031303231363035' + + # P1_MESSAGE_TIMESTAMP (0-0:1.0.0) + assert isinstance(result[obis.P1_MESSAGE_TIMESTAMP], CosemObject) + assert result[obis.P1_MESSAGE_TIMESTAMP].unit is None + assert isinstance(result[obis.P1_MESSAGE_TIMESTAMP].value, datetime.datetime) + assert result[obis.P1_MESSAGE_TIMESTAMP].value == \ + datetime.datetime(2020, 5, 12, 11, 54, 9, tzinfo=pytz.UTC) + + # ELECTRICITY_USED_TARIFF_1 (1-0:1.8.1) + assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_1], CosemObject) + assert result[obis.ELECTRICITY_USED_TARIFF_1].unit == 'kWh' + assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_1].value, Decimal) + assert result[obis.ELECTRICITY_USED_TARIFF_1].value == Decimal('0.034') + + # ELECTRICITY_USED_TARIFF_2 (1-0:1.8.2) + assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_2], CosemObject) + assert result[obis.ELECTRICITY_USED_TARIFF_2].unit == 'kWh' + assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_2].value, Decimal) + assert result[obis.ELECTRICITY_USED_TARIFF_2].value == Decimal('15.758') + + # ELECTRICITY_DELIVERED_TARIFF_1 (1-0:2.8.1) + assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_1], CosemObject) + assert result[obis.ELECTRICITY_DELIVERED_TARIFF_1].unit == 'kWh' + assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_1].value, Decimal) + assert result[obis.ELECTRICITY_DELIVERED_TARIFF_1].value == Decimal('0.000') + + # ELECTRICITY_DELIVERED_TARIFF_2 (1-0:2.8.2) + assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_2], CosemObject) + assert result[obis.ELECTRICITY_DELIVERED_TARIFF_2].unit == 'kWh' + assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_2].value, Decimal) + assert result[obis.ELECTRICITY_DELIVERED_TARIFF_2].value == Decimal('0.011') + + # ELECTRICITY_ACTIVE_TARIFF (0-0:96.14.0) + assert isinstance(result[obis.ELECTRICITY_ACTIVE_TARIFF], CosemObject) + assert result[obis.ELECTRICITY_ACTIVE_TARIFF].unit is None + assert isinstance(result[obis.ELECTRICITY_ACTIVE_TARIFF].value, str) + assert result[obis.ELECTRICITY_ACTIVE_TARIFF].value == '0001' + + # BELGIUM_CURRENT_AVERAGE_DEMAND (1-0:1.4.0) + assert isinstance(result[obis.BELGIUM_CURRENT_AVERAGE_DEMAND], CosemObject) + assert result[obis.BELGIUM_CURRENT_AVERAGE_DEMAND].unit == 'kW' + assert isinstance(result[obis.BELGIUM_CURRENT_AVERAGE_DEMAND].value, Decimal) + assert result[obis.BELGIUM_CURRENT_AVERAGE_DEMAND].value == Decimal('2.351') + + # BELGIUM_MAXIMUM_DEMAND_MONTH (1-0:1.6.0) + assert isinstance(result[obis.BELGIUM_MAXIMUM_DEMAND_MONTH], MBusObject) + assert result[obis.BELGIUM_MAXIMUM_DEMAND_MONTH].unit == 'kW' + assert isinstance(result[obis.BELGIUM_MAXIMUM_DEMAND_MONTH].value, Decimal) + assert result[obis.BELGIUM_MAXIMUM_DEMAND_MONTH].value == Decimal('2.589') + + # BELGIUM_MAXIMUM_DEMAND_13_MONTHS (0-0:98.1.0) Value 0 + assert isinstance(result[obis.BELGIUM_MAXIMUM_DEMAND_13_MONTHS][0], MBusObjectPeak) + assert result[obis.BELGIUM_MAXIMUM_DEMAND_13_MONTHS][0].unit == 'kW' + assert isinstance(result[obis.BELGIUM_MAXIMUM_DEMAND_13_MONTHS][0].value, Decimal) + assert result[obis.BELGIUM_MAXIMUM_DEMAND_13_MONTHS][0].value == Decimal('3.695') + # BELGIUM_MAXIMUM_DEMAND_13_MONTHS (0-0:98.1.0) Value 1 + assert isinstance(result[obis.BELGIUM_MAXIMUM_DEMAND_13_MONTHS][1], MBusObjectPeak) + assert result[obis.BELGIUM_MAXIMUM_DEMAND_13_MONTHS][1].unit == 'kW' + assert isinstance(result[obis.BELGIUM_MAXIMUM_DEMAND_13_MONTHS][1].value, Decimal) + assert result[obis.BELGIUM_MAXIMUM_DEMAND_13_MONTHS][1].value == Decimal('5.980') + # BELGIUM_MAXIMUM_DEMAND_13_MONTHS (0-0:98.1.0) Value 2 + assert isinstance(result[obis.BELGIUM_MAXIMUM_DEMAND_13_MONTHS][2], MBusObjectPeak) + assert result[obis.BELGIUM_MAXIMUM_DEMAND_13_MONTHS][2].unit == 'kW' + assert isinstance(result[obis.BELGIUM_MAXIMUM_DEMAND_13_MONTHS][2].value, Decimal) + assert result[obis.BELGIUM_MAXIMUM_DEMAND_13_MONTHS][2].value == Decimal('4.318') + + # CURRENT_ELECTRICITY_USAGE (1-0:1.7.0) + assert isinstance(result[obis.CURRENT_ELECTRICITY_USAGE], CosemObject) + assert result[obis.CURRENT_ELECTRICITY_USAGE].unit == 'kW' + assert isinstance(result[obis.CURRENT_ELECTRICITY_USAGE].value, Decimal) + assert result[obis.CURRENT_ELECTRICITY_USAGE].value == Decimal('0.000') + + # CURRENT_ELECTRICITY_DELIVERY (1-0:2.7.0) + assert isinstance(result[obis.CURRENT_ELECTRICITY_DELIVERY], CosemObject) + assert result[obis.CURRENT_ELECTRICITY_DELIVERY].unit == 'kW' + assert isinstance(result[obis.CURRENT_ELECTRICITY_DELIVERY].value, Decimal) + assert result[obis.CURRENT_ELECTRICITY_DELIVERY].value == Decimal('0.000') + + # INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE (1-0:21.7.0) + assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE], CosemObject) + assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE].unit == 'kW' + assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE].value, Decimal) + assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE].value == Decimal('0.000') + + # INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE (1-0:41.7.0) + assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE], CosemObject) + assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE].unit == 'kW' + assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE].value, Decimal) + assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE].value == Decimal('0.000') + + # INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE (1-0:61.7.0) + assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE], CosemObject) + assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE].unit == 'kW' + assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE].value, Decimal) + assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE].value == Decimal('0.000') + + # INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE (1-0:22.7.0) + assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE], CosemObject) + assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE].unit == 'kW' + assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE].value, Decimal) + assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE].value == Decimal('0.000') + + # INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE (1-0:42.7.0) + assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE], CosemObject) + assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE].unit == 'kW' + assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE].value, Decimal) + assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE].value == Decimal('0.000') + + # INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE (1-0:62.7.0) + assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE], CosemObject) + assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE].unit == 'kW' + assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE].value, Decimal) + assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE].value == Decimal('0.000') + + # INSTANTANEOUS_VOLTAGE_L1 (1-0:32.7.0) + assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L1], CosemObject) + assert result[obis.INSTANTANEOUS_VOLTAGE_L1].unit == 'V' + assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L1].value, Decimal) + assert result[obis.INSTANTANEOUS_VOLTAGE_L1].value == Decimal('234.7') + + # INSTANTANEOUS_VOLTAGE_L2 (1-0:52.7.0) + assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L2], CosemObject) + assert result[obis.INSTANTANEOUS_VOLTAGE_L2].unit == 'V' + assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L2].value, Decimal) + assert result[obis.INSTANTANEOUS_VOLTAGE_L2].value == Decimal('234.7') + + # INSTANTANEOUS_VOLTAGE_L3 (1-0:72.7.0) + assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L3], CosemObject) + assert result[obis.INSTANTANEOUS_VOLTAGE_L3].unit == 'V' + assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L3].value, Decimal) + assert result[obis.INSTANTANEOUS_VOLTAGE_L3].value == Decimal('234.7') + + # INSTANTANEOUS_CURRENT_L1 (1-0:31.7.0) + assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L1], CosemObject) + assert result[obis.INSTANTANEOUS_CURRENT_L1].unit == 'A' + assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L1].value, Decimal) + assert result[obis.INSTANTANEOUS_CURRENT_L1].value == Decimal('0.000') + + # INSTANTANEOUS_CURRENT_L2 (1-0:51.7.0) + assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L2], CosemObject) + assert result[obis.INSTANTANEOUS_CURRENT_L2].unit == 'A' + assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L2].value, Decimal) + assert result[obis.INSTANTANEOUS_CURRENT_L2].value == Decimal('0.000') + + # INSTANTANEOUS_CURRENT_L3 (1-0:71.7.0) + assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L3], CosemObject) + assert result[obis.INSTANTANEOUS_CURRENT_L3].unit == 'A' + assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L3].value, Decimal) + assert result[obis.INSTANTANEOUS_CURRENT_L3].value == Decimal('0.000') + + # ACTUAL_SWITCH_POSITION (0-0:96.3.10) + assert isinstance(result[obis.ACTUAL_SWITCH_POSITION], CosemObject) + assert result[obis.ACTUAL_SWITCH_POSITION].unit is None + assert isinstance(result[obis.ACTUAL_SWITCH_POSITION].value, int) + assert result[obis.ACTUAL_SWITCH_POSITION].value == 1 + + # BELGIUM_MAX_POWER_PER_PHASE (0-0:17.0.0) + assert isinstance(result[obis.BELGIUM_MAX_POWER_PER_PHASE], CosemObject) + assert result[obis.BELGIUM_MAX_POWER_PER_PHASE].unit == 'kW' + assert isinstance(result[obis.BELGIUM_MAX_POWER_PER_PHASE].value, Decimal) + assert result[obis.BELGIUM_MAX_POWER_PER_PHASE].value == Decimal('999.9') + + # BELGIUM_MAX_POWER_PER_PHASE (1-0:31.4.0) + assert isinstance(result[obis.BELGIUM_MAX_CURRENT_PER_PHASE], CosemObject) + assert result[obis.BELGIUM_MAX_CURRENT_PER_PHASE].unit == 'A' + assert isinstance(result[obis.BELGIUM_MAX_CURRENT_PER_PHASE].value, Decimal) + assert result[obis.BELGIUM_MAX_CURRENT_PER_PHASE].value == Decimal('999') + + # TEXT_MESSAGE (0-0:96.13.0) + assert isinstance(result[obis.TEXT_MESSAGE], CosemObject) + assert result[obis.TEXT_MESSAGE].unit is None + assert result[obis.TEXT_MESSAGE].value is None + + # BELGIUM_MBUS1_DEVICE_TYPE (0-1:24.1.0) + assert isinstance(result[obis.BELGIUM_MBUS1_DEVICE_TYPE], CosemObject) + assert result[obis.BELGIUM_MBUS1_DEVICE_TYPE].unit is None + assert isinstance(result[obis.BELGIUM_MBUS1_DEVICE_TYPE].value, int) + assert result[obis.BELGIUM_MBUS1_DEVICE_TYPE].value == 3 + + # BELGIUM_MBUS1_EQUIPMENT_IDENTIFIER (0-1:96.1.1) + assert isinstance(result[obis.BELGIUM_MBUS1_EQUIPMENT_IDENTIFIER], CosemObject) + assert result[obis.BELGIUM_MBUS1_EQUIPMENT_IDENTIFIER].unit is None + assert isinstance(result[obis.BELGIUM_MBUS1_EQUIPMENT_IDENTIFIER].value, str) + assert result[obis.BELGIUM_MBUS1_EQUIPMENT_IDENTIFIER].value == '37464C4F32313139303333373333' + + # BELGIUM_MBUS1_VALVE_POSITION (0-1:24.4.0) + assert isinstance(result[obis.BELGIUM_MBUS1_VALVE_POSITION], CosemObject) + assert result[obis.BELGIUM_MBUS1_VALVE_POSITION].unit is None + assert isinstance(result[obis.BELGIUM_MBUS1_VALVE_POSITION].value, int) + assert result[obis.BELGIUM_MBUS1_VALVE_POSITION].value == 1 + + # BELGIUM_MBUS1_METER_READING2 (0-1:24.2.3) + assert isinstance(result[obis.BELGIUM_MBUS1_METER_READING2], MBusObject) + assert result[obis.BELGIUM_MBUS1_METER_READING2].unit == 'm3' + assert isinstance(result[obis.BELGIUM_MBUS1_METER_READING2].value, Decimal) + assert result[obis.BELGIUM_MBUS1_METER_READING2].value == Decimal('112.384') + + # BELGIUM_MBUS2_DEVICE_TYPE (0-2:24.1.0) + assert isinstance(result[obis.BELGIUM_MBUS2_DEVICE_TYPE], CosemObject) + assert result[obis.BELGIUM_MBUS2_DEVICE_TYPE].unit is None + assert isinstance(result[obis.BELGIUM_MBUS2_DEVICE_TYPE].value, int) + assert result[obis.BELGIUM_MBUS2_DEVICE_TYPE].value == 7 + + # BELGIUM_MBUS2_EQUIPMENT_IDENTIFIER (0-2:96.1.1) + assert isinstance(result[obis.BELGIUM_MBUS2_EQUIPMENT_IDENTIFIER], CosemObject) + assert result[obis.BELGIUM_MBUS2_EQUIPMENT_IDENTIFIER].unit is None + assert isinstance(result[obis.BELGIUM_MBUS2_EQUIPMENT_IDENTIFIER].value, str) + assert result[obis.BELGIUM_MBUS2_EQUIPMENT_IDENTIFIER].value == '3853414731323334353637383930' + + # BELGIUM_MBUS2_METER_READING1 (0-1:24.2.1) + assert isinstance(result[obis.BELGIUM_MBUS2_METER_READING1], MBusObject) + assert result[obis.BELGIUM_MBUS2_METER_READING1].unit == 'm3' + assert isinstance(result[obis.BELGIUM_MBUS2_METER_READING1].value, Decimal) + assert result[obis.BELGIUM_MBUS2_METER_READING1].value == Decimal('872.234') + + + def test_checksum_valid(self): + # No exception is raised. + TelegramParser.validate_checksum(TELEGRAM_FLUVIUS_V171) + + def test_checksum_invalid(self): + # Remove the electricty used data value. This causes the checksum to + # not match anymore. + corrupted_telegram = TELEGRAM_FLUVIUS_V171.replace( + '1-0:1.8.1(000000.034*kWh)\r\n', + '' + ) + + with self.assertRaises(InvalidChecksumError): + TelegramParser.validate_checksum(corrupted_telegram) + + def test_checksum_missing(self): + # Remove the checksum value causing a ParseError. + corrupted_telegram = TELEGRAM_FLUVIUS_V171.replace('!911C\r\n', '') + with self.assertRaises(ParseError): + TelegramParser.validate_checksum(corrupted_telegram)