From a88dfe1a4180f9f86e1795b036188a51416e51f5 Mon Sep 17 00:00:00 2001 From: Nigel Dokter Date: Thu, 26 Jan 2017 18:50:30 +0100 Subject: [PATCH] added DSMR v3 specification; updated changelog; --- CHANGELOG.rst | 10 +++ dsmr_parser/obis_references.py | 5 +- dsmr_parser/telegram_specifications.py | 29 ++++++++ setup.py | 2 +- test/example_telegrams.py | 25 +++++++ test/test_parse_v2_2.py | 78 +++++++++++++++++++- test/test_parse_v3.py | 98 ++++++++++++++++++++++++++ 7 files changed, 242 insertions(+), 5 deletions(-) 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')