diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 599c0f4..3100b59 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,16 @@ Change Log ---------- +**0.8** (2017-01-29) + +- added support for DSMR v3 +- added support for DSMR v5 + +**IMPORTANT: this release has the following backwards incompatible changes:** + +- Removed TelegramParserV2_2 in favor of TelegramParser +- Removed TelegramParserV4 in favor of TelegramParser + **0.7** (2017-01-14) - Internal refactoring related to the way clients feed their data into the parse module. Clients can now supply the telegram data in single characters, lines (which was common) or complete telegram strings. (`pull request #17 `_) diff --git a/dsmr_parser/obis_references.py b/dsmr_parser/obis_references.py index c33f2a5..6791f1e 100644 --- a/dsmr_parser/obis_references.py +++ b/dsmr_parser/obis_references.py @@ -34,13 +34,16 @@ INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE = r'\d-\d:22\.7\.0.+?\r\n' INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE = r'\d-\d:42\.7\.0.+?\r\n' INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE = r'\d-\d:62\.7\.0.+?\r\n' EQUIPMENT_IDENTIFIER_GAS = r'\d-\d:96\.1\.0.+?\r\n' -# TODO +# TODO differences between gas meter readings in v3 and lower and v4 and up HOURLY_GAS_METER_READING = r'\d-\d:24\.2\.1.+?\r\n' GAS_METER_READING = r'\d-\d:24\.3\.0.+?\r\n.+?\r\n' ACTUAL_TRESHOLD_ELECTRICITY = r'\d-\d:17\.0\.0.+?\r\n' ACTUAL_SWITCH_POSITION = r'\d-\d:96\.3\.10.+?\r\n' VALVE_POSITION_GAS = r'\d-\d:24\.4\.0.+?\r\n' +# TODO 17.0.0 +# TODO 96.3.10 + ELECTRICITY_USED_TARIFF_ALL = ( ELECTRICITY_USED_TARIFF_1, ELECTRICITY_USED_TARIFF_2 diff --git a/dsmr_parser/telegram_specifications.py b/dsmr_parser/telegram_specifications.py index 646a2da..43642f9 100644 --- a/dsmr_parser/telegram_specifications.py +++ b/dsmr_parser/telegram_specifications.py @@ -42,6 +42,35 @@ V2_2 = { } } +V3 = { + 'checksum_support': False, + 'objects': { + obis.EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), + 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.CURRENT_ELECTRICITY_USAGE: CosemParser(ValueParser(Decimal)), + obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), + obis.ACTUAL_TRESHOLD_ELECTRICITY: CosemParser(ValueParser(Decimal)), + obis.ACTUAL_SWITCH_POSITION: CosemParser(ValueParser(str)), + obis.TEXT_MESSAGE_CODE: CosemParser(ValueParser(int)), + obis.TEXT_MESSAGE: CosemParser(ValueParser(str)), + obis.EQUIPMENT_IDENTIFIER_GAS: CosemParser(ValueParser(str)), + obis.DEVICE_TYPE: CosemParser(ValueParser(str)), + obis.VALVE_POSITION_GAS: CosemParser(ValueParser(str)), + obis.GAS_METER_READING: MBusParser( + ValueParser(timestamp), + ValueParser(int), + ValueParser(int), + ValueParser(int), + ValueParser(str), + ValueParser(Decimal), + ), + } +} + V4 = { 'checksum_support': True, 'objects': { diff --git a/setup.py b/setup.py index fba27c8..a4b5fdd 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( author='Nigel Dokter', author_email='nigeldokter@gmail.com', url='https://github.com/ndokter/dsmr_parser', - version='0.7', + version='0.8', packages=find_packages(), install_requires=[ 'pyserial>=3,<4', diff --git a/test/example_telegrams.py b/test/example_telegrams.py index 55e7897..2df8606 100644 --- a/test/example_telegrams.py +++ b/test/example_telegrams.py @@ -21,6 +21,31 @@ TELEGRAM_V2_2 = ( '!\r\n' ) +TELEGRAM_V3 = ( + '/ISk5\2MT382-1000\r\n' + '\r\n' + '0-0:96.1.1(4B384547303034303436333935353037)\r\n' + '1-0:1.8.1(12345.678*kWh)\r\n' + '1-0:1.8.2(12345.678*kWh)\r\n' + '1-0:2.8.1(12345.678*kWh)\r\n' + '1-0:2.8.2(12345.678*kWh)\r\n' + '0-0:96.14.0(0002)\r\n' + '1-0:1.7.0(001.19*kW)\r\n' + '1-0:2.7.0(000.00*kW)\r\n' + '0-0:17.0.0(016*A)\r\n' + '0-0:96.3.10(1)\r\n' + '0-0:96.13.1(303132333435363738)\r\n' + '0-0:96.13.0(303132333435363738393A3B3C3D3E3F303132333435363738393A3B3C3D3E' + '3F303132333435363738393A3B3C3D3E3F303132333435363738393A3B3C3D3E3F30313233' + '3435363738393A3B3C3D3E3F)\r\n' + '0-1:96.1.0(3232323241424344313233343536373839)\r\n' + '0-1:24.1.0(03)\r\n' + '0-1:24.3.0(090212160000)(00)(60)(1)(0-1:24.2.1)(m3)\r\n' + '(00001.001)\r\n' + '0-1:24.4.0(1)\r\n' + '!\r\n' +) + TELEGRAM_V4_2 = ( '/KFM5KAIFA-METER\r\n' '\r\n' diff --git a/test/test_parse_v2_2.py b/test/test_parse_v2_2.py index 5ae5485..e7203ab 100644 --- a/test/test_parse_v2_2.py +++ b/test/test_parse_v2_2.py @@ -1,5 +1,8 @@ import unittest +from decimal import Decimal + +from dsmr_parser.objects import MBusObject, CosemObject from dsmr_parser.parsers import TelegramParser from dsmr_parser import telegram_specifications from dsmr_parser import obis_references as obis @@ -13,8 +16,77 @@ class TelegramParserV2_2Test(unittest.TestCase): parser = TelegramParser(telegram_specifications.V2_2) result = parser.parse(TELEGRAM_V2_2) - assert float(result[obis.CURRENT_ELECTRICITY_USAGE].value) == 1.01 - assert result[obis.CURRENT_ELECTRICITY_USAGE].unit == 'kW' + # 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('1.001') - assert float(result[obis.GAS_METER_READING].value) == 1.001 + # 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('1.001') + + # 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('1.001') + + # 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('1.001') + + # 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' + + # 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 == '00000000000000' + + # 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('1.01') + + # 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') + + # TEXT_MESSAGE_CODE (0-0:96.13.1) + assert isinstance(result[obis.TEXT_MESSAGE_CODE], CosemObject) + assert result[obis.TEXT_MESSAGE_CODE].unit is None + + # 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 + + # DEVICE_TYPE (0-x:24.1.0) + assert isinstance(result[obis.TEXT_MESSAGE], CosemObject) + assert result[obis.DEVICE_TYPE].unit is None + assert isinstance(result[obis.DEVICE_TYPE].value, str) + assert result[obis.DEVICE_TYPE].value == '3' + + # EQUIPMENT_IDENTIFIER_GAS (0-x:96.1.0) + assert isinstance(result[obis.EQUIPMENT_IDENTIFIER_GAS], CosemObject) + assert result[obis.EQUIPMENT_IDENTIFIER_GAS].unit is None + assert isinstance(result[obis.EQUIPMENT_IDENTIFIER_GAS].value, str) + assert result[obis.EQUIPMENT_IDENTIFIER_GAS].value == '000000000000' + + # GAS_METER_READING (0-1:24.3.0) + assert isinstance(result[obis.GAS_METER_READING], MBusObject) assert result[obis.GAS_METER_READING].unit == 'm3' + assert isinstance(result[obis.GAS_METER_READING].value, Decimal) + assert result[obis.GAS_METER_READING].value == Decimal('1.001') diff --git a/test/test_parse_v3.py b/test/test_parse_v3.py index e69de29..c50a86e 100644 --- a/test/test_parse_v3.py +++ b/test/test_parse_v3.py @@ -0,0 +1,98 @@ +import unittest + +from decimal import Decimal + +from dsmr_parser.objects import CosemObject, MBusObject +from dsmr_parser.parsers import TelegramParser +from dsmr_parser import telegram_specifications +from dsmr_parser import obis_references as obis +from test.example_telegrams import TELEGRAM_V3 + + +class TelegramParserV3Test(unittest.TestCase): + """ Test parsing of a DSMR v3 telegram. """ + + def test_parse(self): + parser = TelegramParser(telegram_specifications.V3) + result = parser.parse(TELEGRAM_V3) + + # 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('12345.678') + + # 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('12345.678') + + # 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('12345.678') + + # 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('12345.678') + + # 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 == '0002' + + # 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 == '4B384547303034303436333935353037' + + # 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('1.19') + + # 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') + + # TEXT_MESSAGE_CODE (0-0:96.13.1) + assert isinstance(result[obis.TEXT_MESSAGE_CODE], CosemObject) + assert result[obis.TEXT_MESSAGE_CODE].unit is None + assert isinstance(result[obis.TEXT_MESSAGE_CODE].value, int) + assert result[obis.TEXT_MESSAGE_CODE].value == 303132333435363738 + + # TEXT_MESSAGE (0-0:96.13.0) + assert isinstance(result[obis.TEXT_MESSAGE], CosemObject) + assert result[obis.TEXT_MESSAGE].unit is None + assert isinstance(result[obis.TEXT_MESSAGE].value, str) + assert result[obis.TEXT_MESSAGE].value == \ + '303132333435363738393A3B3C3D3E3F303132333435363738393A3B3C3D3E3F' \ + '303132333435363738393A3B3C3D3E3F303132333435363738393A3B3C3D3E3F' \ + '303132333435363738393A3B3C3D3E3F' + + # DEVICE_TYPE (0-x:24.1.0) + assert isinstance(result[obis.TEXT_MESSAGE], CosemObject) + assert result[obis.DEVICE_TYPE].unit is None + assert isinstance(result[obis.DEVICE_TYPE].value, str) + assert result[obis.DEVICE_TYPE].value == '03' + + # EQUIPMENT_IDENTIFIER_GAS (0-x:96.1.0) + assert isinstance(result[obis.EQUIPMENT_IDENTIFIER_GAS], CosemObject) + assert result[obis.EQUIPMENT_IDENTIFIER_GAS].unit is None + assert isinstance(result[obis.EQUIPMENT_IDENTIFIER_GAS].value, str) + assert result[obis.EQUIPMENT_IDENTIFIER_GAS].value == '3232323241424344313233343536373839' + + # GAS_METER_READING (0-1:24.3.0) + assert isinstance(result[obis.GAS_METER_READING], MBusObject) + assert result[obis.GAS_METER_READING].unit == 'm3' + assert isinstance(result[obis.GAS_METER_READING].value, Decimal) + assert result[obis.GAS_METER_READING].value == Decimal('1.001')