From be4386bc595d2cb9007c9c79152a104c821e93b3 Mon Sep 17 00:00:00 2001 From: albert Date: Mon, 7 Sep 2020 18:15:49 +0200 Subject: [PATCH 01/85] tempfix for empty profileGenericParser --- dsmr_parser/parsers.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dsmr_parser/parsers.py b/dsmr_parser/parsers.py index 8528ec8..5dedd09 100644 --- a/dsmr_parser/parsers.py +++ b/dsmr_parser/parsers.py @@ -225,6 +225,8 @@ class ProfileGenericParser(DSMRObjectParser): self.parsers_for_unidentified = parsers_for_unidentified def _is_line_wellformed(self, line, values): + if values and (len(values) == 1) and (values[0] == ''): + return True if values and (len(values) >= 2) and (values[0].isdigit()): buffer_length = int(values[0]) return (buffer_length <= 10) and (len(values) == (buffer_length * 2 + 2)) @@ -232,6 +234,9 @@ class ProfileGenericParser(DSMRObjectParser): return False def _parse_values(self, values): + if values and (len(values) == 1) and (values[0] == None): + return [self.value_formats[i].parse(value) + for i, value in enumerate(values)] buffer_length = int(values[0]) buffer_value_obis_ID = values[1] if (buffer_length > 0): From dc902a83e9fa683bac50e40251c175c79974d734 Mon Sep 17 00:00:00 2001 From: albert Date: Tue, 8 Sep 2020 12:30:51 +0200 Subject: [PATCH 02/85] referring to parent method from _parse_values in ProfileGenericParser --- dsmr_parser/parsers.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dsmr_parser/parsers.py b/dsmr_parser/parsers.py index 5dedd09..e8cc704 100644 --- a/dsmr_parser/parsers.py +++ b/dsmr_parser/parsers.py @@ -225,8 +225,11 @@ class ProfileGenericParser(DSMRObjectParser): self.parsers_for_unidentified = parsers_for_unidentified def _is_line_wellformed(self, line, values): + + # allow empty parentheses (indicated by empty string) if values and (len(values) == 1) and (values[0] == ''): return True + if values and (len(values) >= 2) and (values[0].isdigit()): buffer_length = int(values[0]) return (buffer_length <= 10) and (len(values) == (buffer_length * 2 + 2)) @@ -234,9 +237,10 @@ class ProfileGenericParser(DSMRObjectParser): return False def _parse_values(self, values): + # in case of empty parentheses return if values and (len(values) == 1) and (values[0] == None): - return [self.value_formats[i].parse(value) - for i, value in enumerate(values)] + return super()._parse_values(values) #calling parent + buffer_length = int(values[0]) buffer_value_obis_ID = values[1] if (buffer_length > 0): From 684023d0b64cd2519b9b34d7ec4410a04e54d413 Mon Sep 17 00:00:00 2001 From: Nigel Dokter Date: Fri, 27 Nov 2020 21:18:59 +0100 Subject: [PATCH 03/85] Preparing for release 0.24 --- CHANGELOG.rst | 3 +++ setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c094a5d..bef44b1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,9 @@ Change Log ---------- +**0.24** (2020-11-27) +- Add Luxembourg equipment identifier (`pull request #62 `_). + **0.23** (2020-11-07) - Resolved issue with x-x:24.3.0 where it contains non-integer character (`pull request #61 `_). - Tests are not installed anymore (`pull request #59 `_). diff --git a/setup.py b/setup.py index 8e5b325..f8fba0c 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( author='Nigel Dokter', author_email='nigel@nldr.net', url='https://github.com/ndokter/dsmr_parser', - version='0.23', + version='0.24', packages=find_packages(exclude=('test', 'test.*')), install_requires=[ 'pyserial>=3,<4', From b7c8626d0c481711a33ebc674a0312244d7fd588 Mon Sep 17 00:00:00 2001 From: Guy Foetz Date: Sun, 13 Dec 2020 12:15:51 +0000 Subject: [PATCH 04/85] adding the # Alternate codes for foreign countries. --- dsmr_parser/obis_name_mapping.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dsmr_parser/obis_name_mapping.py b/dsmr_parser/obis_name_mapping.py index 0401f5e..bf00f13 100644 --- a/dsmr_parser/obis_name_mapping.py +++ b/dsmr_parser/obis_name_mapping.py @@ -48,7 +48,11 @@ EN = { obis.GAS_METER_READING: 'GAS_METER_READING', obis.ACTUAL_TRESHOLD_ELECTRICITY: 'ACTUAL_TRESHOLD_ELECTRICITY', obis.ACTUAL_SWITCH_POSITION: 'ACTUAL_SWITCH_POSITION', - obis.VALVE_POSITION_GAS: 'VALVE_POSITION_GAS' + obis.VALVE_POSITION_GAS: 'VALVE_POSITION_GAS', + obis.BELGIUM_HOURLY_GAS_METER_READING: 'BELGIUM_HOURLY_GAS_METER_READING', + obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: 'LUXEMBOURG_EQUIPMENT_IDENTIFIER', + obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: 'LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL', + obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: 'LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL' } REVERSE_EN = dict([(v, k) for k, v in EN.items()]) From a7b4929eabfa9b9fdd0e1aee7c3d3738f6e5c9f0 Mon Sep 17 00:00:00 2001 From: Nigel Dokter Date: Mon, 14 Dec 2020 17:21:16 +0100 Subject: [PATCH 05/85] preparation for v0.25 --- CHANGELOG.rst | 3 +++ setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bef44b1..94071bf 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,9 @@ Change Log ---------- +**0.25** (2020-12-14) +- fix for empty parentheses in ProfileGenericParser (`pull request #57 `_). + **0.24** (2020-11-27) - Add Luxembourg equipment identifier (`pull request #62 `_). diff --git a/setup.py b/setup.py index f8fba0c..ec32a09 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( author='Nigel Dokter', author_email='nigel@nldr.net', url='https://github.com/ndokter/dsmr_parser', - version='0.24', + version='0.25', packages=find_packages(exclude=('test', 'test.*')), install_requires=[ 'pyserial>=3,<4', From af9a99d995c70575e66f18ec96288aed47b30735 Mon Sep 17 00:00:00 2001 From: Nigel Dokter Date: Tue, 15 Dec 2020 14:59:35 +0100 Subject: [PATCH 06/85] Revert "fix for empty parentheses in ProfileGenericParser" --- dsmr_parser/parsers.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/dsmr_parser/parsers.py b/dsmr_parser/parsers.py index e8cc704..8528ec8 100644 --- a/dsmr_parser/parsers.py +++ b/dsmr_parser/parsers.py @@ -225,11 +225,6 @@ class ProfileGenericParser(DSMRObjectParser): self.parsers_for_unidentified = parsers_for_unidentified def _is_line_wellformed(self, line, values): - - # allow empty parentheses (indicated by empty string) - if values and (len(values) == 1) and (values[0] == ''): - return True - if values and (len(values) >= 2) and (values[0].isdigit()): buffer_length = int(values[0]) return (buffer_length <= 10) and (len(values) == (buffer_length * 2 + 2)) @@ -237,10 +232,6 @@ class ProfileGenericParser(DSMRObjectParser): return False def _parse_values(self, values): - # in case of empty parentheses return - if values and (len(values) == 1) and (values[0] == None): - return super()._parse_values(values) #calling parent - buffer_length = int(values[0]) buffer_value_obis_ID = values[1] if (buffer_length > 0): From feb0f88ddc0aa577d0d377e2b8e48a0be88feb4f Mon Sep 17 00:00:00 2001 From: Nigel Dokter Date: Tue, 15 Dec 2020 15:03:22 +0100 Subject: [PATCH 07/85] Preparing release 0.26 --- CHANGELOG.rst | 3 +++ setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 94071bf..42a197a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,9 @@ Change Log ---------- +**0.26** (2020-12-15) +- reverted fix for empty parentheses in ProfileGenericParser (`pull request #68 `_). + **0.25** (2020-12-14) - fix for empty parentheses in ProfileGenericParser (`pull request #57 `_). diff --git a/setup.py b/setup.py index ec32a09..bc1a584 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( author='Nigel Dokter', author_email='nigel@nldr.net', url='https://github.com/ndokter/dsmr_parser', - version='0.25', + version='0.26', packages=find_packages(exclude=('test', 'test.*')), install_requires=[ 'pyserial>=3,<4', From 1318204d0ccc0bbd6472f58603dfb3dbd82f571b Mon Sep 17 00:00:00 2001 From: albert Date: Mon, 7 Sep 2020 18:15:49 +0200 Subject: [PATCH 08/85] tempfix for empty profileGenericParser --- dsmr_parser/parsers.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dsmr_parser/parsers.py b/dsmr_parser/parsers.py index 8528ec8..5dedd09 100644 --- a/dsmr_parser/parsers.py +++ b/dsmr_parser/parsers.py @@ -225,6 +225,8 @@ class ProfileGenericParser(DSMRObjectParser): self.parsers_for_unidentified = parsers_for_unidentified def _is_line_wellformed(self, line, values): + if values and (len(values) == 1) and (values[0] == ''): + return True if values and (len(values) >= 2) and (values[0].isdigit()): buffer_length = int(values[0]) return (buffer_length <= 10) and (len(values) == (buffer_length * 2 + 2)) @@ -232,6 +234,9 @@ class ProfileGenericParser(DSMRObjectParser): return False def _parse_values(self, values): + if values and (len(values) == 1) and (values[0] == None): + return [self.value_formats[i].parse(value) + for i, value in enumerate(values)] buffer_length = int(values[0]) buffer_value_obis_ID = values[1] if (buffer_length > 0): From 2d712b506d2d8c70f88552dced4eafa26ee21d0d Mon Sep 17 00:00:00 2001 From: albert Date: Tue, 8 Sep 2020 12:30:51 +0200 Subject: [PATCH 09/85] referring to parent method from _parse_values in ProfileGenericParser --- dsmr_parser/parsers.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dsmr_parser/parsers.py b/dsmr_parser/parsers.py index 5dedd09..e8cc704 100644 --- a/dsmr_parser/parsers.py +++ b/dsmr_parser/parsers.py @@ -225,8 +225,11 @@ class ProfileGenericParser(DSMRObjectParser): self.parsers_for_unidentified = parsers_for_unidentified def _is_line_wellformed(self, line, values): + + # allow empty parentheses (indicated by empty string) if values and (len(values) == 1) and (values[0] == ''): return True + if values and (len(values) >= 2) and (values[0].isdigit()): buffer_length = int(values[0]) return (buffer_length <= 10) and (len(values) == (buffer_length * 2 + 2)) @@ -234,9 +237,10 @@ class ProfileGenericParser(DSMRObjectParser): return False def _parse_values(self, values): + # in case of empty parentheses return if values and (len(values) == 1) and (values[0] == None): - return [self.value_formats[i].parse(value) - for i, value in enumerate(values)] + return super()._parse_values(values) #calling parent + buffer_length = int(values[0]) buffer_value_obis_ID = values[1] if (buffer_length > 0): From 5b1e83001874b10c1e9705b7f760d57ea8a62918 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Thu, 24 Dec 2020 00:22:29 +0100 Subject: [PATCH 10/85] make sure that for the special case (actually invalid syntax) where a ProfileGeneric line only contains (); an empty ProfileGenericObject is created --- dsmr_parser/parsers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dsmr_parser/parsers.py b/dsmr_parser/parsers.py index e8cc704..272b59b 100644 --- a/dsmr_parser/parsers.py +++ b/dsmr_parser/parsers.py @@ -226,8 +226,9 @@ class ProfileGenericParser(DSMRObjectParser): def _is_line_wellformed(self, line, values): - # allow empty parentheses (indicated by empty string) + if values and (len(values) == 1) and (values[0] == ''): + # special case: single empty parentheses (indicated by empty string) return True if values and (len(values) >= 2) and (values[0].isdigit()): @@ -237,10 +238,9 @@ class ProfileGenericParser(DSMRObjectParser): return False def _parse_values(self, values): - # in case of empty parentheses return if values and (len(values) == 1) and (values[0] == None): - return super()._parse_values(values) #calling parent - + # special case: single empty parentheses; make sure empty ProfileGenericObject is created + values = [0, None] # buffer_length=0, buffer_value_obis_ID=None buffer_length = int(values[0]) buffer_value_obis_ID = values[1] if (buffer_length > 0): From 81cccbd2289e9a27ca7a2bf3e87930bdb60a8395 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Thu, 24 Dec 2020 01:52:17 +0100 Subject: [PATCH 11/85] fix tox tests --- dsmr_parser/obis_references.py | 2 +- dsmr_parser/parsers.py | 6 ++---- test/example_telegrams.py | 2 +- test/test_parse_v5.py | 3 +-- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/dsmr_parser/obis_references.py b/dsmr_parser/obis_references.py index ffc215e..fe8952e 100644 --- a/dsmr_parser/obis_references.py +++ b/dsmr_parser/obis_references.py @@ -63,6 +63,6 @@ ELECTRICITY_DELIVERED_TARIFF_ALL = ( # Alternate codes for foreign countries. BELGIUM_HOURLY_GAS_METER_READING = r'\d-\d:24\.2\.3.+?\r\n' # Different code, same format. -LUXEMBOURG_EQUIPMENT_IDENTIFIER = r'\d-\d:42\.0\.0.+?\r\n' # Logical device name +LUXEMBOURG_EQUIPMENT_IDENTIFIER = r'\d-\d:42\.0\.0.+?\r\n' # Logical device name LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL = r'\d-\d:1\.8\.0.+?\r\n' # Total imported energy register (P+) LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = r'\d-\d:2\.8\.0.+?\r\n' # Total exported energy register (P-) diff --git a/dsmr_parser/parsers.py b/dsmr_parser/parsers.py index 272b59b..5c44f8b 100644 --- a/dsmr_parser/parsers.py +++ b/dsmr_parser/parsers.py @@ -225,8 +225,6 @@ class ProfileGenericParser(DSMRObjectParser): self.parsers_for_unidentified = parsers_for_unidentified def _is_line_wellformed(self, line, values): - - if values and (len(values) == 1) and (values[0] == ''): # special case: single empty parentheses (indicated by empty string) return True @@ -238,9 +236,9 @@ class ProfileGenericParser(DSMRObjectParser): return False def _parse_values(self, values): - if values and (len(values) == 1) and (values[0] == None): + if values and (len(values) == 1) and (values[0] is None): # special case: single empty parentheses; make sure empty ProfileGenericObject is created - values = [0, None] # buffer_length=0, buffer_value_obis_ID=None + values = [0, None] # buffer_length=0, buffer_value_obis_ID=None buffer_length = int(values[0]) buffer_value_obis_ID = values[1] if (buffer_length > 0): diff --git a/test/example_telegrams.py b/test/example_telegrams.py index 143b2a4..f74ed16 100644 --- a/test/example_telegrams.py +++ b/test/example_telegrams.py @@ -127,4 +127,4 @@ TELEGRAM_V5 = ( '0-2:24.1.0(003)\r\n' '0-2:96.1.0()\r\n' '!6EEE\r\n' -) \ No newline at end of file +) diff --git a/test/test_parse_v5.py b/test/test_parse_v5.py index 67d7cd8..fe3ed84 100644 --- a/test/test_parse_v5.py +++ b/test/test_parse_v5.py @@ -241,7 +241,6 @@ class TelegramParserV5Test(unittest.TestCase): def test_checksum_missing(self): # Remove the checksum value causing a ParseError. - corrupted_telegram = TELEGRAM_V5.replace('!87B3\r\n', '') - + corrupted_telegram = TELEGRAM_V5.replace('!6EEE\r\n', '') with self.assertRaises(ParseError): TelegramParser.validate_checksum(corrupted_telegram) From 629767590be0e7ed2fb13e414cb24c4c5d0774d5 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Thu, 24 Dec 2020 12:37:45 +0100 Subject: [PATCH 12/85] finished tox tests for issue 57 fix --- test/test_parser_corner_cases.py | 89 ++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 test/test_parser_corner_cases.py diff --git a/test/test_parser_corner_cases.py b/test/test_parser_corner_cases.py new file mode 100644 index 0000000..7ca927d --- /dev/null +++ b/test/test_parser_corner_cases.py @@ -0,0 +1,89 @@ +import unittest + +from dsmr_parser import telegram_specifications + +from dsmr_parser.objects import Telegram +from dsmr_parser.objects import ProfileGenericObject +from dsmr_parser.parsers import TelegramParser +from dsmr_parser.parsers import ProfileGenericParser +from dsmr_parser.profile_generic_specifications import BUFFER_TYPES +from dsmr_parser.profile_generic_specifications import PG_HEAD_PARSERS +from dsmr_parser.profile_generic_specifications import PG_UNIDENTIFIED_BUFFERTYPE_PARSERS +from test.example_telegrams import TELEGRAM_V5 + + +class TestParserCornerCases(unittest.TestCase): + """ Test instantiation of Telegram object """ + + def test_power_event_log_empty_1(self): + # POWER_EVENT_FAILURE_LOG (1-0:99.97.0) + parser = TelegramParser(telegram_specifications.V4) + telegram = Telegram(TELEGRAM_V5, parser, telegram_specifications.V5) + + object_type = ProfileGenericObject + testitem = telegram.POWER_EVENT_FAILURE_LOG + assert isinstance(testitem, object_type) + assert testitem.buffer_length == 0 + assert testitem.buffer_type == '0-0:96.7.19' + buffer = testitem.buffer + assert isinstance(testitem.buffer, list) + assert len(buffer) == 0 + + def test_power_event_log_empty_2(self): + pef_parser = ProfileGenericParser(BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS) + object_type = ProfileGenericObject + + # Power Event Log with 0 items and no object type + pefl_line = r'1-0:99.97.0(0)()\r\n' + testitem = pef_parser.parse(pefl_line) + + assert isinstance(testitem, object_type) + assert testitem.buffer_length == 0 + assert testitem.buffer_type is None + buffer = testitem.buffer + assert isinstance(testitem.buffer, list) + assert len(buffer) == 0 + assert testitem.values == [{'value': 0, 'unit': None}, {'value': None, 'unit': None}] + json = testitem.to_json() + assert json == '{"buffer_length": 0, "buffer_type": null, "buffer": []}' + + def test_power_event_log_null_values(self): + pef_parser = ProfileGenericParser(BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS) + object_type = ProfileGenericObject + + # Power Event Log with 1 item and no object type and nno values for the item + pefl_line = r'1-0:99.97.0(1)()()()\r\n' + testitem = pef_parser.parse(pefl_line) + + assert isinstance(testitem, object_type) + assert testitem.buffer_length == 1 + assert testitem.buffer_type is None + buffer = testitem.buffer + assert isinstance(testitem.buffer, list) + assert len(buffer) == 1 + assert testitem.values == [{'value': 1, 'unit': None}, {'value': None, 'unit': None}, + {'value': None, 'unit': None}, {'value': None, 'unit': None}] + json = testitem.to_json() + assert json == \ + '{"buffer_length": 1, "buffer_type": null, "buffer": [{"datetime": null, "value": null, "unit": null}]}' + + def test_power_event_log_brackets_only(self): + # POWER_EVENT_FAILURE_LOG (1-0:99.97.0) + # Issue 57 + # Test of an ill formatted empty POWER_EVENT_FAILURE_LOG, observed on some smartmeters + # The idea is that instead of failing, the parser converts it to an empty POWER_EVENT_FAILURE_LOG + pef_parser = ProfileGenericParser(BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS) + object_type = ProfileGenericObject + + pefl_line = r'1-0:99.97.0()\r\n' + testitem = pef_parser.parse(pefl_line) + + assert isinstance(testitem, object_type) + assert testitem.buffer_length == 0 + assert testitem.buffer_type is None + buffer = testitem.buffer + assert isinstance(testitem.buffer, list) + assert len(buffer) == 0 + assert testitem.values == [{'value': 0, 'unit': None}, {'value': None, 'unit': None}] + json = testitem.to_json() + assert json == '{"buffer_length": 0, "buffer_type": null, "buffer": []}' From 3ddf0366e60e925b78662608c9bfa1e42b6452b3 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Thu, 24 Dec 2020 12:44:49 +0100 Subject: [PATCH 13/85] small fix --- test/test_parser_corner_cases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_parser_corner_cases.py b/test/test_parser_corner_cases.py index 7ca927d..3f203e7 100644 --- a/test/test_parser_corner_cases.py +++ b/test/test_parser_corner_cases.py @@ -17,7 +17,7 @@ class TestParserCornerCases(unittest.TestCase): def test_power_event_log_empty_1(self): # POWER_EVENT_FAILURE_LOG (1-0:99.97.0) - parser = TelegramParser(telegram_specifications.V4) + parser = TelegramParser(telegram_specifications.V5) telegram = Telegram(TELEGRAM_V5, parser, telegram_specifications.V5) object_type = ProfileGenericObject From 3dc77a823139501f569f6925ee87ec3f5e6572ae Mon Sep 17 00:00:00 2001 From: Nigel Dokter Date: Thu, 24 Dec 2020 21:46:41 +0100 Subject: [PATCH 14/85] Prepare for release 0.27 --- CHANGELOG.rst | 3 +++ setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 42a197a..2549c80 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,9 @@ Change Log ---------- +**0.27** (2020-12-24) +- fix for empty parentheses in ProfileGenericParser (redone) (`pull request #69 `_). + **0.26** (2020-12-15) - reverted fix for empty parentheses in ProfileGenericParser (`pull request #68 `_). diff --git a/setup.py b/setup.py index bc1a584..427d205 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( author='Nigel Dokter', author_email='nigel@nldr.net', url='https://github.com/ndokter/dsmr_parser', - version='0.26', + version='0.27', packages=find_packages(exclude=('test', 'test.*')), install_requires=[ 'pyserial>=3,<4', From e12aff5c0d2dd7647313788aaa77ba3053b81a64 Mon Sep 17 00:00:00 2001 From: bremme Date: Sun, 27 Dec 2020 18:57:21 +0100 Subject: [PATCH 15/85] Add SocketReader for reading ipv4 tcp sockets --- dsmr_parser/clients/__init__.py | 1 + dsmr_parser/clients/socket_.py | 91 +++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 dsmr_parser/clients/socket_.py diff --git a/dsmr_parser/clients/__init__.py b/dsmr_parser/clients/__init__.py index 7323ecd..9563399 100644 --- a/dsmr_parser/clients/__init__.py +++ b/dsmr_parser/clients/__init__.py @@ -1,5 +1,6 @@ from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \ SERIAL_SETTINGS_V4, SERIAL_SETTINGS_V5 from dsmr_parser.clients.serial_ import SerialReader, AsyncSerialReader +from dsmr_parser.clients.socket_ import SocketReader from dsmr_parser.clients.protocol import create_dsmr_protocol, \ create_dsmr_reader, create_tcp_dsmr_reader diff --git a/dsmr_parser/clients/socket_.py b/dsmr_parser/clients/socket_.py new file mode 100644 index 0000000..7c13f02 --- /dev/null +++ b/dsmr_parser/clients/socket_.py @@ -0,0 +1,91 @@ +import logging +import socket + +from dsmr_parser.clients.telegram_buffer import TelegramBuffer +from dsmr_parser.exceptions import ParseError, InvalidChecksumError +from dsmr_parser.parsers import TelegramParser +from dsmr_parser.objects import Telegram + + +logger = logging.getLogger(__name__) + + +class SocketReader(object): + + BUFFER_SIZE = 256 + + def __init__(self, host, port, telegram_specification): + self.host = host + self.port = port + + self.telegram_parser = TelegramParser(telegram_specification) + self.telegram_buffer = TelegramBuffer() + self.telegram_specification = telegram_specification + + + def read(self): + """ + Read complete DSMR telegram's from remote interface and parse it + into CosemObject's and MbusObject's + + :rtype: generator + """ + buffer = b"" + + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as socket_handle: + + socket_handle.connect((self.host, self.port)) + + while True: + buffer += socket_handle.recv(self.BUFFER_SIZE) + + lines = buffer.splitlines(keepends=True) + + if len(lines) == 0: + continue + + for data in lines: + self.telegram_buffer.append(data.decode('ascii')) + + for telegram in self.telegram_buffer.get_all(): + try: + yield self.telegram_parser.parse(telegram) + except InvalidChecksumError as e: + logger.warning(str(e)) + except ParseError as e: + logger.error('Failed to parse telegram: %s', e) + + buffer = b"" + + def read_as_object(self): + """ + Read complete DSMR telegram's from remote and return a Telegram object. + + :rtype: generator + """ + buffer = b"" + + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as socket_handle: + + socket_handle.connect((self.host, self.port)) + + while True: + buffer += socket_handle.recv(self.BUFFER_SIZE) + + lines = buffer.splitlines(keepends=True) + + if len(lines) == 0: + continue + + for data in lines: + self.telegram_buffer.append(data.decode('ascii')) + + for telegram in self.telegram_buffer.get_all(): + try: + yield Telegram(telegram, self.telegram_parser, self.telegram_specification) + except InvalidChecksumError as e: + logger.warning(str(e)) + except ParseError as e: + logger.error('Failed to parse telegram: %s', e) + + buffer = b"" \ No newline at end of file From 97786576cf4dc2aaee5d10c737f44eb30c6923ae Mon Sep 17 00:00:00 2001 From: bremme Date: Sun, 27 Dec 2020 18:58:14 +0100 Subject: [PATCH 16/85] Add SocketReader documentation --- README.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.rst b/README.rst index 03de485..b2d932f 100644 --- a/README.rst +++ b/README.rst @@ -43,6 +43,25 @@ process because the code is blocking (not asynchronous): To be documented. +**Socket client** + +Read a remote serial port (for example using ser2net) and work with the parsed telegrams. +It should be run in a separate process because the code is blocking (not asynchronous): + +.. code-block:: python + + from dsmr_parser import telegram_specifications + from dsmr_parser.clients import SocketReader + + socket_reader = SocketReader( + host='127.0.0.1', + port=2001, + telegram_specification=telegram_specifications.V4 + ) + + for telegram in socket_reader.read(): + print(telegram) # see 'Telegram object' docs below + Parsing module usage -------------------- From 804747c3703d93f73e54b3b7a166d28bfd6efd49 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Sun, 3 Jan 2021 19:58:19 +0100 Subject: [PATCH 17/85] add value and unit properties to ProfileGenericObject to make sure that code like iterators that rely on that do not break --- dsmr_parser/objects.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dsmr_parser/objects.py b/dsmr_parser/objects.py index 4cd987d..dcadfce 100644 --- a/dsmr_parser/objects.py +++ b/dsmr_parser/objects.py @@ -155,6 +155,16 @@ class ProfileGenericObject(DSMRObject): super().__init__(values) self._buffer_list = None + @property + def value(self): + # value is added to make sure the telegram iterator does not break + return self.__str__() + + @property + def unit(self): + # value is added to make sure all items have a unit so code that relies on that does not break + return None + @property def buffer_length(self): return self.values[0]['value'] From bbd73897a0c4e8bdd75dd65f1ea4a4ad798e8c74 Mon Sep 17 00:00:00 2001 From: Rene Hogendoorn Date: Tue, 19 Jan 2021 08:26:28 +0100 Subject: [PATCH 18/85] Optional keep alive monitoring for TCP/IP connections * Since dsmr-parser is listen-only, it will not notice interrupted connections and DSMR device restarts. The connection will be reset after an (optional) keep-alive interval if no messages were received from the device. --- dsmr_parser/__main__.py | 4 ++-- dsmr_parser/clients/protocol.py | 28 +++++++++++++++++++++++----- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/dsmr_parser/__main__.py b/dsmr_parser/__main__.py index 8d9da8b..a9fbaa0 100644 --- a/dsmr_parser/__main__.py +++ b/dsmr_parser/__main__.py @@ -16,8 +16,8 @@ def console(): help='alternatively connect using TCP host.') parser.add_argument('--port', default=None, help='TCP port to use for connection') - parser.add_argument('--version', default='2.2', choices=['2.2', '4'], - help='DSMR version (2.2, 4)') + parser.add_argument('--version', default='2.2', choices=['2.2', '4', '5', '5B', '5L'], + help='DSMR version (2.2, 4, 5, 5B, 5L)') parser.add_argument('--verbose', '-v', action='count') args = parser.parse_args() diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index 66d0a39..ef48ae7 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -14,7 +14,7 @@ from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \ SERIAL_SETTINGS_V4, SERIAL_SETTINGS_V5 -def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None): +def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **args): """Creates a DSMR asyncio protocol.""" if dsmr_version == '2.2': @@ -37,7 +37,7 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None): dsmr_version) protocol = partial(DSMRProtocol, loop, TelegramParser(specification), - telegram_callback=telegram_callback) + telegram_callback=telegram_callback, **args) return protocol, serial_settings @@ -53,12 +53,14 @@ def create_dsmr_reader(port, dsmr_version, telegram_callback, loop=None): def create_tcp_dsmr_reader(host, port, dsmr_version, - telegram_callback, loop=None): + telegram_callback, loop=None, + keep_alive_interval=None): """Creates a DSMR asyncio protocol coroutine using TCP connection.""" if not loop: loop = asyncio.get_event_loop() protocol, _ = create_dsmr_protocol( - dsmr_version, telegram_callback, loop=loop) + dsmr_version, telegram_callback, loop=loop, + keep_alive_interval=keep_alive_interval) conn = loop.create_connection(protocol, host, port) return conn @@ -69,7 +71,8 @@ class DSMRProtocol(asyncio.Protocol): transport = None telegram_callback = None - def __init__(self, loop, telegram_parser, telegram_callback=None): + def __init__(self, loop, telegram_parser, + telegram_callback=None, keep_alive_interval=None): """Initialize class.""" self.loop = loop self.log = logging.getLogger(__name__) @@ -80,21 +83,36 @@ class DSMRProtocol(asyncio.Protocol): self.telegram_buffer = TelegramBuffer() # keep a lock until the connection is closed self._closed = asyncio.Event() + self._keep_alive_interval = keep_alive_interval + self._active = True def connection_made(self, transport): """Just logging for now.""" self.transport = transport self.log.debug('connected') + self._active = False + if self._keep_alive_interval: + self.loop.call_later(self._keep_alive_interval, self.keep_alive) def data_received(self, data): """Add incoming data to buffer.""" data = data.decode('ascii') + self._active = True self.log.debug('received data: %s', data) self.telegram_buffer.append(data) for telegram in self.telegram_buffer.get_all(): self.handle_telegram(telegram) + def keep_alive(self): + if self._active: + self.log.debug('keep-alive checked') + self._active = False + self.loop.call_later(self._keep_alive_interval, self.keep_alive) + else: + self.log.debug('keep-alive failed') + self.transport.close() + def connection_lost(self, exc): """Stop when connection is lost.""" if exc: From 74535349278e3b136894388888949584f28c1725 Mon Sep 17 00:00:00 2001 From: Rene Hogendoorn Date: Tue, 19 Jan 2021 10:55:20 +0100 Subject: [PATCH 19/85] Raised log level to warning for failed keep-alive check --- dsmr_parser/clients/protocol.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index ef48ae7..e5e6a66 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -14,7 +14,7 @@ from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \ SERIAL_SETTINGS_V4, SERIAL_SETTINGS_V5 -def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **args): +def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): """Creates a DSMR asyncio protocol.""" if dsmr_version == '2.2': @@ -37,7 +37,7 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **args): dsmr_version) protocol = partial(DSMRProtocol, loop, TelegramParser(specification), - telegram_callback=telegram_callback, **args) + telegram_callback=telegram_callback, **kwargs) return protocol, serial_settings @@ -110,7 +110,7 @@ class DSMRProtocol(asyncio.Protocol): self._active = False self.loop.call_later(self._keep_alive_interval, self.keep_alive) else: - self.log.debug('keep-alive failed') + self.log.warning('keep-alive check failed') self.transport.close() def connection_lost(self, exc): From 1cdda2eaba66a7b558586315da379c23f9620cec Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Sun, 7 Feb 2021 13:40:20 +0100 Subject: [PATCH 20/85] catch parse errors in TelegramParser, ignore lines that can not be parsed --- dsmr_parser/parsers.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dsmr_parser/parsers.py b/dsmr_parser/parsers.py index 5c44f8b..fab9a50 100644 --- a/dsmr_parser/parsers.py +++ b/dsmr_parser/parsers.py @@ -10,7 +10,6 @@ logger = logging.getLogger(__name__) class TelegramParser(object): - crc16_tab = [] def __init__(self, telegram_specification, apply_checksum_validation=True): @@ -56,7 +55,11 @@ class TelegramParser(object): # Some signatures are optional and may not be present, # so only parse lines that match if match: - telegram[signature] = parser.parse(match.group(0)) + try: + telegram[signature] = parser.parse(match.group(0)) + except Exception: + logger.error("ignore line with signature {}, because parsing failed.".format(signature), + exc_info=True) return telegram @@ -219,6 +222,7 @@ class ProfileGenericParser(DSMRObjectParser): 8) Buffer value 2 (oldest entry of buffer attribute without unit) 9) Unit of buffer values (Unit of capture objects attribute) """ + def __init__(self, buffer_types, head_parsers, parsers_for_unidentified): self.value_formats = head_parsers self.buffer_types = buffer_types @@ -271,7 +275,6 @@ class ValueParser(object): self.coerce_type = coerce_type def parse(self, value): - unit_of_measurement = None if value and '*' in value: From b901b3f74eddd52883e7f7e75f78ec13d2e5d819 Mon Sep 17 00:00:00 2001 From: Rene Hogendoorn Date: Thu, 11 Feb 2021 11:08:19 +0100 Subject: [PATCH 21/85] Add unit test for keep-alive --- dsmr_parser/clients/protocol.py | 8 +++++--- test/test_protocol.py | 29 +++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index e5e6a66..9b4536e 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -91,7 +91,7 @@ class DSMRProtocol(asyncio.Protocol): self.transport = transport self.log.debug('connected') self._active = False - if self._keep_alive_interval: + if self.loop and self._keep_alive_interval: self.loop.call_later(self._keep_alive_interval, self.keep_alive) def data_received(self, data): @@ -108,10 +108,12 @@ class DSMRProtocol(asyncio.Protocol): if self._active: self.log.debug('keep-alive checked') self._active = False - self.loop.call_later(self._keep_alive_interval, self.keep_alive) + if self.loop: + self.loop.call_later(self._keep_alive_interval, self.keep_alive) else: self.log.warning('keep-alive check failed') - self.transport.close() + if self.transport: + self.transport.close() def connection_lost(self, exc): """Stop when connection is lost.""" diff --git a/test/test_protocol.py b/test/test_protocol.py index 2fb14e0..c298d5c 100644 --- a/test/test_protocol.py +++ b/test/test_protocol.py @@ -5,7 +5,7 @@ import unittest from dsmr_parser import obis_references as obis from dsmr_parser import telegram_specifications from dsmr_parser.parsers import TelegramParser -from dsmr_parser.clients.protocol import DSMRProtocol +from dsmr_parser.clients.protocol import create_dsmr_protocol TELEGRAM_V2_2 = ( @@ -35,9 +35,10 @@ TELEGRAM_V2_2 = ( class ProtocolTest(unittest.TestCase): def setUp(self): - telegram_parser = TelegramParser(telegram_specifications.V2_2) - self.protocol = DSMRProtocol(None, telegram_parser, - telegram_callback=Mock()) + new_protocol, _ = create_dsmr_protocol('2.2', + telegram_callback=Mock(), + keep_alive_interval=1) + self.protocol = new_protocol() def test_complete_packet(self): """Protocol should assemble incoming lines into complete packet.""" @@ -52,3 +53,23 @@ class ProtocolTest(unittest.TestCase): assert float(telegram[obis.GAS_METER_READING].value) == 1.001 assert telegram[obis.GAS_METER_READING].unit == 'm3' + + def test_receive_packet(self): + """Protocol packet reception.""" + + mock_transport = Mock() + self.protocol.connection_made(mock_transport) + assert not self.protocol._active + + self.protocol.data_received(TELEGRAM_V2_2.encode('ascii')) + assert self.protocol._active + + # 1st call of keep_alive resets 'active' flag + self.protocol.keep_alive() + assert not self.protocol._active + + # 2nd call of keep_alive should close the transport + self.protocol.keep_alive() + assert mock_transport.close.called_once() + + self.protocol.connection_lost(None) From f806cc01d37a9e2ffd9821d3317caf82dce401ad Mon Sep 17 00:00:00 2001 From: Nigel Dokter Date: Fri, 12 Feb 2021 17:56:33 +0100 Subject: [PATCH 22/85] Preparing release 0.28 --- CHANGELOG.rst | 4 ++++ setup.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2549c80..8c24591 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,10 @@ Change Log ---------- +**0.28** (2021-02-21) +- Optional keep alive monitoring for TCP/IP connections (`pull request #73 `_). +- Catch parse errors in TelegramParser, ignore lines that can not be parsed (`pull request #74 `_). + **0.27** (2020-12-24) - fix for empty parentheses in ProfileGenericParser (redone) (`pull request #69 `_). diff --git a/setup.py b/setup.py index 427d205..e08d8b3 100644 --- a/setup.py +++ b/setup.py @@ -3,10 +3,10 @@ from setuptools import setup, find_packages setup( name='dsmr-parser', description='Library to parse Dutch Smart Meter Requirements (DSMR)', - author='Nigel Dokter', + author='Nigel Dokter and many others', author_email='nigel@nldr.net', url='https://github.com/ndokter/dsmr_parser', - version='0.27', + version='0.28', packages=find_packages(exclude=('test', 'test.*')), install_requires=[ 'pyserial>=3,<4', From 8c861ee308092368690a1d9e23a67b0197d2346b Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Sun, 14 Feb 2021 22:08:35 +0100 Subject: [PATCH 23/85] resolved comment --- dsmr_parser/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsmr_parser/objects.py b/dsmr_parser/objects.py index dcadfce..c062d9d 100644 --- a/dsmr_parser/objects.py +++ b/dsmr_parser/objects.py @@ -158,7 +158,7 @@ class ProfileGenericObject(DSMRObject): @property def value(self): # value is added to make sure the telegram iterator does not break - return self.__str__() + return self.values @property def unit(self): From adaa2dcad54fa3bfdcf679f6cc793675c9ed807d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 3 Mar 2021 11:48:49 +0100 Subject: [PATCH 24/85] Remove deprecated asyncio coroutine decorator --- README.rst | 2 +- dsmr_parser/clients/protocol.py | 5 ++--- dsmr_parser/clients/serial_.py | 7 +++---- tox.ini | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index 03de485..a8d405c 100644 --- a/README.rst +++ b/README.rst @@ -14,7 +14,7 @@ also includes client implementation to directly read and parse smart meter data. Features -------- -DSMR Parser supports DSMR versions 2, 3, 4 and 5. It has been tested with Python 3.4, 3.5 and 3.6. +DSMR Parser supports DSMR versions 2, 3, 4 and 5. It has been tested with Python 3.5, 3.6, 3.7. 3.8 and 3.9. Client module usage diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index 9b4536e..34a405e 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -136,7 +136,6 @@ class DSMRProtocol(asyncio.Protocol): else: self.telegram_callback(parsed_telegram) - @asyncio.coroutine - def wait_closed(self): + async def wait_closed(self): """Wait until connection is closed.""" - yield from self._closed.wait() + await self._closed.wait() diff --git a/dsmr_parser/clients/serial_.py b/dsmr_parser/clients/serial_.py index 94e3b6f..f63ff07 100644 --- a/dsmr_parser/clients/serial_.py +++ b/dsmr_parser/clients/serial_.py @@ -68,8 +68,7 @@ class AsyncSerialReader(SerialReader): PORT_KEY = 'url' - @asyncio.coroutine - def read(self, queue): + async def read(self, queue): """ Read complete DSMR telegram's from the serial interface and parse it into CosemObject's and MbusObject's. @@ -81,12 +80,12 @@ class AsyncSerialReader(SerialReader): """ # create Serial StreamReader conn = serial_asyncio.open_serial_connection(**self.serial_settings) - reader, _ = yield from conn + reader, _ = await conn while True: # Read line if available or give control back to loop until new # data has arrived. - data = yield from reader.readline() + data = await reader.readline() self.telegram_buffer.append(data.decode('ascii')) for telegram in self.telegram_buffer.get_all(): diff --git a/tox.ini b/tox.ini index f2e6de4..a9a403d 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35,py36,py37,py38 +envlist = py35,py36,py37,py38,py39 [testenv] deps= From c590109a1d3e787bfd0ac64d008bddb5fd614cf8 Mon Sep 17 00:00:00 2001 From: Nigel Dokter Date: Thu, 4 Mar 2021 21:47:12 +0100 Subject: [PATCH 25/85] Update README.rst Fixed small typo --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a8d405c..403a723 100644 --- a/README.rst +++ b/README.rst @@ -14,7 +14,7 @@ also includes client implementation to directly read and parse smart meter data. Features -------- -DSMR Parser supports DSMR versions 2, 3, 4 and 5. It has been tested with Python 3.5, 3.6, 3.7. 3.8 and 3.9. +DSMR Parser supports DSMR versions 2, 3, 4 and 5. It has been tested with Python 3.5, 3.6, 3.7, 3.8 and 3.9. Client module usage From ada02bf993e33f0f0224d85e2592ee52dff696bf Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 21 Apr 2021 23:36:25 +0200 Subject: [PATCH 26/85] Add license tag --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index e08d8b3..0551d71 100644 --- a/setup.py +++ b/setup.py @@ -5,6 +5,7 @@ setup( description='Library to parse Dutch Smart Meter Requirements (DSMR)', author='Nigel Dokter and many others', author_email='nigel@nldr.net', + license='MIT', url='https://github.com/ndokter/dsmr_parser', version='0.28', packages=find_packages(exclude=('test', 'test.*')), From e30c951c7fc0c7b1477b8521f1e751d93dd2ea6b Mon Sep 17 00:00:00 2001 From: Lennart99 Date: Thu, 10 Jun 2021 12:23:20 +0200 Subject: [PATCH 27/85] add special option for Landis+Gyr E360 (DMSR 5 with serial version 4) --- dsmr_parser/clients/protocol.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index 34a405e..06ff997 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -23,6 +23,9 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): elif dsmr_version == '4': specification = telegram_specifications.V4 serial_settings = SERIAL_SETTINGS_V4 + elif dsmr_version == '4+': + specification = telegram_specifications.V5 + serial_settings = SERIAL_SETTINGS_V4 elif dsmr_version == '5': specification = telegram_specifications.V5 serial_settings = SERIAL_SETTINGS_V5 From fa70ada0bfee02d33aa5f400672c930bd42ec1d9 Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 14 Aug 2021 17:08:27 +0200 Subject: [PATCH 28/85] Add support for Swedish smart meters --- dsmr_parser/__main__.py | 4 ++-- dsmr_parser/clients/protocol.py | 3 +++ dsmr_parser/obis_name_mapping.py | 4 +++- dsmr_parser/obis_references.py | 2 ++ dsmr_parser/telegram_specifications.py | 25 +++++++++++++++++++++++++ 5 files changed, 35 insertions(+), 3 deletions(-) diff --git a/dsmr_parser/__main__.py b/dsmr_parser/__main__.py index a9fbaa0..a24a6a2 100644 --- a/dsmr_parser/__main__.py +++ b/dsmr_parser/__main__.py @@ -16,8 +16,8 @@ def console(): help='alternatively connect using TCP host.') parser.add_argument('--port', default=None, help='TCP port to use for connection') - parser.add_argument('--version', default='2.2', choices=['2.2', '4', '5', '5B', '5L'], - help='DSMR version (2.2, 4, 5, 5B, 5L)') + parser.add_argument('--version', default='2.2', choices=['2.2', '4', '5', '5B', '5L', '5S'], + help='DSMR version (2.2, 4, 5, 5B, 5L, 5S)') parser.add_argument('--verbose', '-v', action='count') args = parser.parse_args() diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index 34a405e..fce549d 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -32,6 +32,9 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): elif dsmr_version == "5L": specification = telegram_specifications.LUXEMBOURG_SMARTY serial_settings = SERIAL_SETTINGS_V5 + elif dsmr_version == "5S": + specification = telegram_specifications.SWEDEN + serial_settings = SERIAL_SETTINGS_V5 else: raise NotImplementedError("No telegram parser found for version: %s", dsmr_version) diff --git a/dsmr_parser/obis_name_mapping.py b/dsmr_parser/obis_name_mapping.py index bf00f13..4028890 100644 --- a/dsmr_parser/obis_name_mapping.py +++ b/dsmr_parser/obis_name_mapping.py @@ -52,7 +52,9 @@ EN = { obis.BELGIUM_HOURLY_GAS_METER_READING: 'BELGIUM_HOURLY_GAS_METER_READING', obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: 'LUXEMBOURG_EQUIPMENT_IDENTIFIER', obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: 'LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL', - obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: 'LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL' + obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: 'LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL', + obis.SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL: 'SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL', + obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: 'SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL', } REVERSE_EN = dict([(v, k) for k, v in EN.items()]) diff --git a/dsmr_parser/obis_references.py b/dsmr_parser/obis_references.py index fe8952e..5ac3b66 100644 --- a/dsmr_parser/obis_references.py +++ b/dsmr_parser/obis_references.py @@ -66,3 +66,5 @@ BELGIUM_HOURLY_GAS_METER_READING = r'\d-\d:24\.2\.3.+?\r\n' # Different code, s LUXEMBOURG_EQUIPMENT_IDENTIFIER = r'\d-\d:42\.0\.0.+?\r\n' # Logical device name LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL = r'\d-\d:1\.8\.0.+?\r\n' # Total imported energy register (P+) LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = r'\d-\d:2\.8\.0.+?\r\n' # Total exported energy register (P-) +SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL = r'\d-\d:1\.8\.0.+?\r\n' # Total imported energy register (P+) +SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = r'\d-\d:2\.8\.0.+?\r\n' # Total exported energy register (P-) diff --git a/dsmr_parser/telegram_specifications.py b/dsmr_parser/telegram_specifications.py index b06e4f4..4e59f51 100644 --- a/dsmr_parser/telegram_specifications.py +++ b/dsmr_parser/telegram_specifications.py @@ -156,3 +156,28 @@ LUXEMBOURG_SMARTY['objects'].update({ obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), }) + +# Source: https://www.energiforetagen.se/globalassets/energiforetagen/det-erbjuder-vi/kurser-och-konferenser/elnat/branschrekommendation-lokalt-granssnitt-v2_0-201912.pdf +SWEDEN = { + 'checksum_support': True, + 'objects': { + obis.P1_MESSAGE_HEADER: CosemParser(ValueParser(str)), + obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)), + obis.SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), + obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), + 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)), + } +} From 45de34906244cd7963f5b75f1ff0ed7f47a3ffad Mon Sep 17 00:00:00 2001 From: Nigel Dokter Date: Wed, 18 Aug 2021 16:54:44 +0200 Subject: [PATCH 29/85] Prepare version 0.29 --- CHANGELOG.rst | 3 +++ setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8c24591..724ae96 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,9 @@ Change Log ---------- +**0.29** (2021-08-18) +- Add support for Swedish smart meters (`pull request #86 `_). + **0.28** (2021-02-21) - Optional keep alive monitoring for TCP/IP connections (`pull request #73 `_). - Catch parse errors in TelegramParser, ignore lines that can not be parsed (`pull request #74 `_). diff --git a/setup.py b/setup.py index 0551d71..f15dcdd 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( author_email='nigel@nldr.net', license='MIT', url='https://github.com/ndokter/dsmr_parser', - version='0.28', + version='0.29', packages=find_packages(exclude=('test', 'test.*')), install_requires=[ 'pyserial>=3,<4', From d7e1f4116239a3202ae0e8566144c69525e1f448 Mon Sep 17 00:00:00 2001 From: Nigel Dokter Date: Wed, 18 Aug 2021 16:59:19 +0200 Subject: [PATCH 30/85] Prepare version 0.30 --- CHANGELOG.rst | 6 +++++- setup.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 724ae96..d539d2b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,9 +1,13 @@ Change Log ---------- -**0.29** (2021-08-18) +**0.30** (2021-08-18) - Add support for Swedish smart meters (`pull request #86 `_). +**0.29** (2021-04-18) +- Add value and unit properties to ProfileGenericObject to make sure that code like iterators that rely on that do not break (`pull request #71 `_). +Remove deprecated asyncio coroutine decorator (`pull request #76 `_). + **0.28** (2021-02-21) - Optional keep alive monitoring for TCP/IP connections (`pull request #73 `_). - Catch parse errors in TelegramParser, ignore lines that can not be parsed (`pull request #74 `_). diff --git a/setup.py b/setup.py index f15dcdd..7ad7c68 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( author_email='nigel@nldr.net', license='MIT', url='https://github.com/ndokter/dsmr_parser', - version='0.29', + version='0.30', packages=find_packages(exclude=('test', 'test.*')), install_requires=[ 'pyserial>=3,<4', From 4688cf905862a02f26cc63484d4e5b0edf8f9440 Mon Sep 17 00:00:00 2001 From: Dennis Siemensma Date: Wed, 22 Sep 2021 21:26:54 +0200 Subject: [PATCH 31/85] test/test_protocol.py:6:1: W0611 'dsmr_parser.telegram_specifications' imported but unused [pyflakes] test/test_protocol.py:7:1: W0611 'dsmr_parser.parsers.TelegramParser' imported but unused [pyflakes] --- test/test_protocol.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/test_protocol.py b/test/test_protocol.py index c298d5c..d1393f3 100644 --- a/test/test_protocol.py +++ b/test/test_protocol.py @@ -3,8 +3,6 @@ from unittest.mock import Mock import unittest from dsmr_parser import obis_references as obis -from dsmr_parser import telegram_specifications -from dsmr_parser.parsers import TelegramParser from dsmr_parser.clients.protocol import create_dsmr_protocol From b3a705a74d8eac98210a1fbb3f18406d65c2299c Mon Sep 17 00:00:00 2001 From: Dennis Siemensma Date: Wed, 22 Sep 2021 21:27:22 +0200 Subject: [PATCH 32/85] "pytest-catchlog plugin has been merged into the core, please remove it from your requirements" --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index a9a403d..855f11c 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,6 @@ deps= pytest-cov pylama pytest-asyncio - pytest-catchlog pytest-mock commands= py.test --cov=dsmr_parser test {posargs} From fcb0dc600baae87a7b5be0580f37cfb56a7e995b Mon Sep 17 00:00:00 2001 From: Dennis Siemensma Date: Wed, 22 Sep 2021 21:28:17 +0200 Subject: [PATCH 33/85] CI tests using Github Actions --- .github/workflows/tests.yml | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..95abb2c --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,44 @@ +name: Tests + +on: + push: ~ + pull_request: ~ + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 10 # Don't run forever when stale + + strategy: + matrix: + python-version: + - 3.6 + - 3.7 + - 3.8 + - 3.9 + + name: Python ${{ matrix.python-version }} + steps: + - uses: actions/checkout@v2 + + - name: Setup Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Cached PIP dependencies + uses: actions/cache@v2 + with: + path: | + ~/.cache/pip + ~/.tox/python/.pytest_cache + key: pip-${{ matrix.python-version }}-${{ hashFiles('setup.py', 'tox.ini') }} + restore-keys: pip-${{ matrix.python-version }}- + + - name: Run tests + run: | + pip install tox + tox + + - name: Code coverage upload + uses: codecov/codecov-action@v1 From da98cf1d057147f62d0329fc25eb45659e0219fb Mon Sep 17 00:00:00 2001 From: Dennis Siemensma Date: Wed, 22 Sep 2021 21:28:36 +0200 Subject: [PATCH 34/85] Drop Travis (travis.org is EOL) --- .travis.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index bc6b513..0000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -language: python - -python: - - 2.7 - - 3.5 - - 3.6 - - 3.8 - -install: pip install tox-travis codecov - -script: tox - -after_success: - - codecov - -matrix: - allow_failures: - - python: 2.7 From 62aa0ac3abf22637d3c793e489227e0817493d1e Mon Sep 17 00:00:00 2001 From: Dennis Siemensma Date: Wed, 22 Sep 2021 21:31:11 +0200 Subject: [PATCH 35/85] W0611 'asyncio' imported but unused [pyflakes] --- dsmr_parser/clients/serial_.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dsmr_parser/clients/serial_.py b/dsmr_parser/clients/serial_.py index f63ff07..12d2245 100644 --- a/dsmr_parser/clients/serial_.py +++ b/dsmr_parser/clients/serial_.py @@ -1,4 +1,3 @@ -import asyncio import logging import serial import serial_asyncio From 947fd6437763cad68fc99a1182c65c857639c65e Mon Sep 17 00:00:00 2001 From: Dennis Siemensma Date: Wed, 22 Sep 2021 21:33:22 +0200 Subject: [PATCH 36/85] E501 line too long (170 > 120 characters) [pycodestyle] --- dsmr_parser/telegram_specifications.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dsmr_parser/telegram_specifications.py b/dsmr_parser/telegram_specifications.py index 4e59f51..8a0abf5 100644 --- a/dsmr_parser/telegram_specifications.py +++ b/dsmr_parser/telegram_specifications.py @@ -157,7 +157,8 @@ LUXEMBOURG_SMARTY['objects'].update({ obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), }) -# Source: https://www.energiforetagen.se/globalassets/energiforetagen/det-erbjuder-vi/kurser-och-konferenser/elnat/branschrekommendation-lokalt-granssnitt-v2_0-201912.pdf +# Source: https://www.energiforetagen.se/globalassets/energiforetagen/det-erbjuder-vi/kurser-och-konferenser/elnat/ +# branschrekommendation-lokalt-granssnitt-v2_0-201912.pdf SWEDEN = { 'checksum_support': True, 'objects': { From 61de170a79d119606dee08f98309fdc938064722 Mon Sep 17 00:00:00 2001 From: Dennis Siemensma Date: Wed, 22 Sep 2021 21:36:46 +0200 Subject: [PATCH 37/85] Tox no longer relies on every Python version installed, due to multiple CI runners --- tox.ini | 3 --- 1 file changed, 3 deletions(-) diff --git a/tox.ini b/tox.ini index 855f11c..27fc713 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,3 @@ -[tox] -envlist = py35,py36,py37,py38,py39 - [testenv] deps= pytest From 88ef4f4921ba9ac7ab6ba79384b69ec89b47042c Mon Sep 17 00:00:00 2001 From: Dennis Siemensma Date: Wed, 22 Sep 2021 21:38:24 +0200 Subject: [PATCH 38/85] Split dependency installation step in Actions --- .github/workflows/tests.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 95abb2c..5f6b2b0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -35,10 +35,11 @@ jobs: key: pip-${{ matrix.python-version }}-${{ hashFiles('setup.py', 'tox.ini') }} restore-keys: pip-${{ matrix.python-version }}- + - name: Install dependencies + run: pip install tox + - name: Run tests - run: | - pip install tox - tox + run: tox - name: Code coverage upload uses: codecov/codecov-action@v1 From dc608f9da3250e10121dcf112c9bd9941a70fd70 Mon Sep 17 00:00:00 2001 From: Nigel Dokter Date: Sun, 31 Oct 2021 13:08:42 +0100 Subject: [PATCH 39/85] Update README.rst --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index 403a723..01b9af3 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,6 @@ +**Notice:** this repository is in need of a new maintainer. If you are interested or have ideas about this, please let me know. + + DSMR Parser =========== From 761aaccb3f7a7551733174d5dfeecb7e6ca2f834 Mon Sep 17 00:00:00 2001 From: Gunnar Klauberg Date: Fri, 12 Nov 2021 15:19:43 +0000 Subject: [PATCH 40/85] adding EasyMeter Q3D support --- dsmr_parser/__main__.py | 40 +++++---- dsmr_parser/clients/protocol.py | 71 +++++++++------ dsmr_parser/obis_name_mapping.py | 97 +++++++++++---------- dsmr_parser/obis_references.py | 115 ++++++++++++++----------- dsmr_parser/telegram_specifications.py | 113 +++++++++++++++--------- 5 files changed, 255 insertions(+), 181 deletions(-) diff --git a/dsmr_parser/__main__.py b/dsmr_parser/__main__.py index a24a6a2..1a127cf 100644 --- a/dsmr_parser/__main__.py +++ b/dsmr_parser/__main__.py @@ -10,15 +10,20 @@ def console(): """Output DSMR data to console.""" parser = argparse.ArgumentParser(description=console.__doc__) - parser.add_argument('--device', default='/dev/ttyUSB0', - help='port to read DSMR data from') - parser.add_argument('--host', default=None, - help='alternatively connect using TCP host.') - parser.add_argument('--port', default=None, - help='TCP port to use for connection') - parser.add_argument('--version', default='2.2', choices=['2.2', '4', '5', '5B', '5L', '5S'], - help='DSMR version (2.2, 4, 5, 5B, 5L, 5S)') - parser.add_argument('--verbose', '-v', action='count') + parser.add_argument( + "--device", default="/dev/ttyUSB0", help="port to read DSMR data from" + ) + parser.add_argument( + "--host", default=None, help="alternatively connect using TCP host." + ) + parser.add_argument("--port", default=None, help="TCP port to use for connection") + parser.add_argument( + "--version", + default="2.2", + choices=["2.2", "4", "5", "5B", "5L", "5S", "Q3D"], + help="DSMR version (2.2, 4, 5, 5B, 5L, 5S, Q3D)", + ) + parser.add_argument("--verbose", "-v", action="count") args = parser.parse_args() @@ -39,13 +44,18 @@ def console(): # create tcp or serial connection depending on args if args.host and args.port: - create_connection = partial(create_tcp_dsmr_reader, - args.host, args.port, args.version, - print_callback, loop=loop) + create_connection = partial( + create_tcp_dsmr_reader, + args.host, + args.port, + args.version, + print_callback, + loop=loop, + ) else: - create_connection = partial(create_dsmr_reader, - args.device, args.version, - print_callback, loop=loop) + create_connection = partial( + create_dsmr_reader, args.device, args.version, print_callback, loop=loop + ) try: # connect and keep connected until interrupted by ctrl-c diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index fce549d..560c0aa 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -10,23 +10,26 @@ from dsmr_parser import telegram_specifications from dsmr_parser.clients.telegram_buffer import TelegramBuffer from dsmr_parser.exceptions import ParseError, InvalidChecksumError from dsmr_parser.parsers import TelegramParser -from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \ - SERIAL_SETTINGS_V4, SERIAL_SETTINGS_V5 +from dsmr_parser.clients.settings import ( + SERIAL_SETTINGS_V2_2, + SERIAL_SETTINGS_V4, + SERIAL_SETTINGS_V5, +) def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): """Creates a DSMR asyncio protocol.""" - if dsmr_version == '2.2': + if dsmr_version == "2.2": specification = telegram_specifications.V2_2 serial_settings = SERIAL_SETTINGS_V2_2 - elif dsmr_version == '4': + elif dsmr_version == "4": specification = telegram_specifications.V4 serial_settings = SERIAL_SETTINGS_V4 - elif dsmr_version == '5': + elif dsmr_version == "5": specification = telegram_specifications.V5 serial_settings = SERIAL_SETTINGS_V5 - elif dsmr_version == '5B': + elif dsmr_version == "5B": specification = telegram_specifications.BELGIUM_FLUVIUS serial_settings = SERIAL_SETTINGS_V5 elif dsmr_version == "5L": @@ -35,12 +38,21 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): elif dsmr_version == "5S": specification = telegram_specifications.SWEDEN serial_settings = SERIAL_SETTINGS_V5 + elif dsmr_version == "Q3D": + specification = telegram_specifications.Q3D + serial_settings = SERIAL_SETTINGS_V5 else: - raise NotImplementedError("No telegram parser found for version: %s", - dsmr_version) + raise NotImplementedError( + "No telegram parser found for version: %s", dsmr_version + ) - protocol = partial(DSMRProtocol, loop, TelegramParser(specification), - telegram_callback=telegram_callback, **kwargs) + protocol = partial( + DSMRProtocol, + loop, + TelegramParser(specification), + telegram_callback=telegram_callback, + **kwargs + ) return protocol, serial_settings @@ -48,22 +60,26 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): def create_dsmr_reader(port, dsmr_version, telegram_callback, loop=None): """Creates a DSMR asyncio protocol coroutine using serial port.""" protocol, serial_settings = create_dsmr_protocol( - dsmr_version, telegram_callback, loop=None) - serial_settings['url'] = port + dsmr_version, telegram_callback, loop=None + ) + serial_settings["url"] = port conn = create_serial_connection(loop, protocol, **serial_settings) return conn -def create_tcp_dsmr_reader(host, port, dsmr_version, - telegram_callback, loop=None, - keep_alive_interval=None): +def create_tcp_dsmr_reader( + host, port, dsmr_version, telegram_callback, loop=None, keep_alive_interval=None +): """Creates a DSMR asyncio protocol coroutine using TCP connection.""" if not loop: loop = asyncio.get_event_loop() protocol, _ = create_dsmr_protocol( - dsmr_version, telegram_callback, loop=loop, - keep_alive_interval=keep_alive_interval) + dsmr_version, + telegram_callback, + loop=loop, + keep_alive_interval=keep_alive_interval, + ) conn = loop.create_connection(protocol, host, port) return conn @@ -74,8 +90,9 @@ class DSMRProtocol(asyncio.Protocol): transport = None telegram_callback = None - def __init__(self, loop, telegram_parser, - telegram_callback=None, keep_alive_interval=None): + def __init__( + self, loop, telegram_parser, telegram_callback=None, keep_alive_interval=None + ): """Initialize class.""" self.loop = loop self.log = logging.getLogger(__name__) @@ -92,16 +109,16 @@ class DSMRProtocol(asyncio.Protocol): def connection_made(self, transport): """Just logging for now.""" self.transport = transport - self.log.debug('connected') + self.log.debug("connected") self._active = False if self.loop and self._keep_alive_interval: self.loop.call_later(self._keep_alive_interval, self.keep_alive) def data_received(self, data): """Add incoming data to buffer.""" - data = data.decode('ascii') + data = data.decode("ascii") self._active = True - self.log.debug('received data: %s', data) + self.log.debug("received data: %s", data) self.telegram_buffer.append(data) for telegram in self.telegram_buffer.get_all(): @@ -109,26 +126,26 @@ class DSMRProtocol(asyncio.Protocol): def keep_alive(self): if self._active: - self.log.debug('keep-alive checked') + self.log.debug("keep-alive checked") self._active = False if self.loop: self.loop.call_later(self._keep_alive_interval, self.keep_alive) else: - self.log.warning('keep-alive check failed') + self.log.warning("keep-alive check failed") if self.transport: self.transport.close() def connection_lost(self, exc): """Stop when connection is lost.""" if exc: - self.log.exception('disconnected due to exception', exc_info=exc) + self.log.exception("disconnected due to exception", exc_info=exc) else: - self.log.info('disconnected because of close/abort.') + self.log.info("disconnected because of close/abort.") self._closed.set() def handle_telegram(self, telegram): """Send off parsed telegram to handling callback.""" - self.log.debug('got telegram: %s', telegram) + self.log.debug("got telegram: %s", telegram) try: parsed_telegram = self.telegram_parser.parse(telegram) diff --git a/dsmr_parser/obis_name_mapping.py b/dsmr_parser/obis_name_mapping.py index 4028890..f7aff77 100644 --- a/dsmr_parser/obis_name_mapping.py +++ b/dsmr_parser/obis_name_mapping.py @@ -8,53 +8,56 @@ This module contains a mapping of obis references to names. """ EN = { - obis.P1_MESSAGE_HEADER: 'P1_MESSAGE_HEADER', - obis.P1_MESSAGE_TIMESTAMP: 'P1_MESSAGE_TIMESTAMP', - obis.ELECTRICITY_IMPORTED_TOTAL: 'ELECTRICITY_IMPORTED_TOTAL', - obis.ELECTRICITY_USED_TARIFF_1: 'ELECTRICITY_USED_TARIFF_1', - obis.ELECTRICITY_USED_TARIFF_2: 'ELECTRICITY_USED_TARIFF_2', - obis.ELECTRICITY_DELIVERED_TARIFF_1: 'ELECTRICITY_DELIVERED_TARIFF_1', - obis.ELECTRICITY_DELIVERED_TARIFF_2: 'ELECTRICITY_DELIVERED_TARIFF_2', - obis.ELECTRICITY_ACTIVE_TARIFF: 'ELECTRICITY_ACTIVE_TARIFF', - obis.EQUIPMENT_IDENTIFIER: 'EQUIPMENT_IDENTIFIER', - obis.CURRENT_ELECTRICITY_USAGE: 'CURRENT_ELECTRICITY_USAGE', - obis.CURRENT_ELECTRICITY_DELIVERY: 'CURRENT_ELECTRICITY_DELIVERY', - obis.LONG_POWER_FAILURE_COUNT: 'LONG_POWER_FAILURE_COUNT', - obis.SHORT_POWER_FAILURE_COUNT: 'SHORT_POWER_FAILURE_COUNT', - obis.POWER_EVENT_FAILURE_LOG: 'POWER_EVENT_FAILURE_LOG', - obis.VOLTAGE_SAG_L1_COUNT: 'VOLTAGE_SAG_L1_COUNT', - obis.VOLTAGE_SAG_L2_COUNT: 'VOLTAGE_SAG_L2_COUNT', - obis.VOLTAGE_SAG_L3_COUNT: 'VOLTAGE_SAG_L3_COUNT', - obis.VOLTAGE_SWELL_L1_COUNT: 'VOLTAGE_SWELL_L1_COUNT', - obis.VOLTAGE_SWELL_L2_COUNT: 'VOLTAGE_SWELL_L2_COUNT', - obis.VOLTAGE_SWELL_L3_COUNT: 'VOLTAGE_SWELL_L3_COUNT', - obis.INSTANTANEOUS_VOLTAGE_L1: 'INSTANTANEOUS_VOLTAGE_L1', - obis.INSTANTANEOUS_VOLTAGE_L2: 'INSTANTANEOUS_VOLTAGE_L2', - obis.INSTANTANEOUS_VOLTAGE_L3: 'INSTANTANEOUS_VOLTAGE_L3', - obis.INSTANTANEOUS_CURRENT_L1: 'INSTANTANEOUS_CURRENT_L1', - obis.INSTANTANEOUS_CURRENT_L2: 'INSTANTANEOUS_CURRENT_L2', - obis.INSTANTANEOUS_CURRENT_L3: 'INSTANTANEOUS_CURRENT_L3', - obis.TEXT_MESSAGE_CODE: 'TEXT_MESSAGE_CODE', - obis.TEXT_MESSAGE: 'TEXT_MESSAGE', - obis.DEVICE_TYPE: 'DEVICE_TYPE', - obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: 'INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE', - obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE: 'INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE', - obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE: 'INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE', - obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE: 'INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE', - obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE: 'INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE', - obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: 'INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE', - obis.EQUIPMENT_IDENTIFIER_GAS: 'EQUIPMENT_IDENTIFIER_GAS', - obis.HOURLY_GAS_METER_READING: 'HOURLY_GAS_METER_READING', - obis.GAS_METER_READING: 'GAS_METER_READING', - obis.ACTUAL_TRESHOLD_ELECTRICITY: 'ACTUAL_TRESHOLD_ELECTRICITY', - obis.ACTUAL_SWITCH_POSITION: 'ACTUAL_SWITCH_POSITION', - obis.VALVE_POSITION_GAS: 'VALVE_POSITION_GAS', - obis.BELGIUM_HOURLY_GAS_METER_READING: 'BELGIUM_HOURLY_GAS_METER_READING', - obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: 'LUXEMBOURG_EQUIPMENT_IDENTIFIER', - obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: 'LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL', - obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: 'LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL', - obis.SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL: 'SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL', - obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: 'SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL', + obis.P1_MESSAGE_HEADER: "P1_MESSAGE_HEADER", + obis.P1_MESSAGE_TIMESTAMP: "P1_MESSAGE_TIMESTAMP", + obis.ELECTRICITY_IMPORTED_TOTAL: "ELECTRICITY_IMPORTED_TOTAL", + obis.ELECTRICITY_USED_TARIFF_1: "ELECTRICITY_USED_TARIFF_1", + obis.ELECTRICITY_USED_TARIFF_2: "ELECTRICITY_USED_TARIFF_2", + obis.ELECTRICITY_DELIVERED_TARIFF_1: "ELECTRICITY_DELIVERED_TARIFF_1", + obis.ELECTRICITY_DELIVERED_TARIFF_2: "ELECTRICITY_DELIVERED_TARIFF_2", + obis.ELECTRICITY_ACTIVE_TARIFF: "ELECTRICITY_ACTIVE_TARIFF", + obis.EQUIPMENT_IDENTIFIER: "EQUIPMENT_IDENTIFIER", + obis.CURRENT_ELECTRICITY_USAGE: "CURRENT_ELECTRICITY_USAGE", + obis.CURRENT_ELECTRICITY_DELIVERY: "CURRENT_ELECTRICITY_DELIVERY", + obis.LONG_POWER_FAILURE_COUNT: "LONG_POWER_FAILURE_COUNT", + obis.SHORT_POWER_FAILURE_COUNT: "SHORT_POWER_FAILURE_COUNT", + obis.POWER_EVENT_FAILURE_LOG: "POWER_EVENT_FAILURE_LOG", + obis.VOLTAGE_SAG_L1_COUNT: "VOLTAGE_SAG_L1_COUNT", + obis.VOLTAGE_SAG_L2_COUNT: "VOLTAGE_SAG_L2_COUNT", + obis.VOLTAGE_SAG_L3_COUNT: "VOLTAGE_SAG_L3_COUNT", + obis.VOLTAGE_SWELL_L1_COUNT: "VOLTAGE_SWELL_L1_COUNT", + obis.VOLTAGE_SWELL_L2_COUNT: "VOLTAGE_SWELL_L2_COUNT", + obis.VOLTAGE_SWELL_L3_COUNT: "VOLTAGE_SWELL_L3_COUNT", + obis.INSTANTANEOUS_VOLTAGE_L1: "INSTANTANEOUS_VOLTAGE_L1", + obis.INSTANTANEOUS_VOLTAGE_L2: "INSTANTANEOUS_VOLTAGE_L2", + obis.INSTANTANEOUS_VOLTAGE_L3: "INSTANTANEOUS_VOLTAGE_L3", + obis.INSTANTANEOUS_CURRENT_L1: "INSTANTANEOUS_CURRENT_L1", + obis.INSTANTANEOUS_CURRENT_L2: "INSTANTANEOUS_CURRENT_L2", + obis.INSTANTANEOUS_CURRENT_L3: "INSTANTANEOUS_CURRENT_L3", + obis.TEXT_MESSAGE_CODE: "TEXT_MESSAGE_CODE", + obis.TEXT_MESSAGE: "TEXT_MESSAGE", + obis.DEVICE_TYPE: "DEVICE_TYPE", + obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: "INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE", + obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE: "INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE", + obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE: "INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE", + obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE: "INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE", + obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE: "INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE", + obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: "INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE", + obis.EQUIPMENT_IDENTIFIER_GAS: "EQUIPMENT_IDENTIFIER_GAS", + obis.HOURLY_GAS_METER_READING: "HOURLY_GAS_METER_READING", + obis.GAS_METER_READING: "GAS_METER_READING", + obis.ACTUAL_TRESHOLD_ELECTRICITY: "ACTUAL_TRESHOLD_ELECTRICITY", + obis.ACTUAL_SWITCH_POSITION: "ACTUAL_SWITCH_POSITION", + obis.VALVE_POSITION_GAS: "VALVE_POSITION_GAS", + obis.BELGIUM_HOURLY_GAS_METER_READING: "BELGIUM_HOURLY_GAS_METER_READING", + obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: "LUXEMBOURG_EQUIPMENT_IDENTIFIER", + obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: "LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL", + obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: "LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL", + obis.SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL: "SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL", + obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: "SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL", + obis.Q3D_EQUIPMENT_IDENTIFIER: "Q3D_EQUIPMENT_IDENTIFIER", + obis.Q3D_EQUIPMENT_STATE: "Q3D_EQUIPMENT_STATE", + obis.Q3D_EQUIPMENT_SERIALNUMBER: "Q3D_EQUIPMENT_SERIALNUMBER", } REVERSE_EN = dict([(v, k) for k, v in EN.items()]) diff --git a/dsmr_parser/obis_references.py b/dsmr_parser/obis_references.py index 5ac3b66..e355ead 100644 --- a/dsmr_parser/obis_references.py +++ b/dsmr_parser/obis_references.py @@ -6,65 +6,76 @@ refactored to full line signatures to maintain backwards compatibility. Might be refactored in a backwards incompatible way as soon as proper telegram objects are introduced. """ -P1_MESSAGE_HEADER = r'\d-\d:0\.2\.8.+?\r\n' -P1_MESSAGE_TIMESTAMP = r'\d-\d:1\.0\.0.+?\r\n' -ELECTRICITY_IMPORTED_TOTAL = r'\d-\d:1\.8\.0.+?\r\n' -ELECTRICITY_USED_TARIFF_1 = r'\d-\d:1\.8\.1.+?\r\n' -ELECTRICITY_USED_TARIFF_2 = r'\d-\d:1\.8\.2.+?\r\n' -ELECTRICITY_DELIVERED_TARIFF_1 = r'\d-\d:2\.8\.1.+?\r\n' -ELECTRICITY_DELIVERED_TARIFF_2 = r'\d-\d:2\.8\.2.+?\r\n' -ELECTRICITY_ACTIVE_TARIFF = r'\d-\d:96\.14\.0.+?\r\n' -EQUIPMENT_IDENTIFIER = r'\d-\d:96\.1\.1.+?\r\n' -CURRENT_ELECTRICITY_USAGE = r'\d-\d:1\.7\.0.+?\r\n' -CURRENT_ELECTRICITY_DELIVERY = r'\d-\d:2\.7\.0.+?\r\n' -LONG_POWER_FAILURE_COUNT = r'96\.7\.9.+?\r\n' -SHORT_POWER_FAILURE_COUNT = r'96\.7\.21.+?\r\n' -POWER_EVENT_FAILURE_LOG = r'99\.97\.0.+?\r\n' -VOLTAGE_SAG_L1_COUNT = r'\d-\d:32\.32\.0.+?\r\n' -VOLTAGE_SAG_L2_COUNT = r'\d-\d:52\.32\.0.+?\r\n' -VOLTAGE_SAG_L3_COUNT = r'\d-\d:72\.32\.0.+?\r\n' -VOLTAGE_SWELL_L1_COUNT = r'\d-\d:32\.36\.0.+?\r\n' -VOLTAGE_SWELL_L2_COUNT = r'\d-\d:52\.36\.0.+?\r\n' -VOLTAGE_SWELL_L3_COUNT = r'\d-\d:72\.36\.0.+?\r\n' -INSTANTANEOUS_VOLTAGE_L1 = r'\d-\d:32\.7\.0.+?\r\n' -INSTANTANEOUS_VOLTAGE_L2 = r'\d-\d:52\.7\.0.+?\r\n' -INSTANTANEOUS_VOLTAGE_L3 = r'\d-\d:72\.7\.0.+?\r\n' -INSTANTANEOUS_CURRENT_L1 = r'\d-\d:31\.7\.0.+?\r\n' -INSTANTANEOUS_CURRENT_L2 = r'\d-\d:51\.7\.0.+?\r\n' -INSTANTANEOUS_CURRENT_L3 = r'\d-\d:71\.7\.0.+?\r\n' -TEXT_MESSAGE_CODE = r'\d-\d:96\.13\.1.+?\r\n' -TEXT_MESSAGE = r'\d-\d:96\.13\.0.+?\r\n' -DEVICE_TYPE = r'\d-\d:24\.1\.0.+?\r\n' -INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE = r'\d-\d:21\.7\.0.+?\r\n' -INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE = r'\d-\d:41\.7\.0.+?\r\n' -INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE = r'\d-\d:61\.7\.0.+?\r\n' -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' +P1_MESSAGE_HEADER = r"\d-\d:0\.2\.8.+?\r\n" +P1_MESSAGE_TIMESTAMP = r"\d-\d:1\.0\.0.+?\r\n" +ELECTRICITY_IMPORTED_TOTAL = r"\d-\d:1\.8\.0.+?\r\n" +ELECTRICITY_USED_TARIFF_1 = r"\d-\d:1\.8\.1.+?\r\n" +ELECTRICITY_USED_TARIFF_2 = r"\d-\d:1\.8\.2.+?\r\n" +ELECTRICITY_DELIVERED_TARIFF_1 = r"\d-\d:2\.8\.1.+?\r\n" +ELECTRICITY_DELIVERED_TARIFF_2 = r"\d-\d:2\.8\.2.+?\r\n" +ELECTRICITY_ACTIVE_TARIFF = r"\d-\d:96\.14\.0.+?\r\n" +EQUIPMENT_IDENTIFIER = r"\d-\d:96\.1\.1.+?\r\n" +CURRENT_ELECTRICITY_USAGE = r"\d-\d:1\.7\.0.+?\r\n" +CURRENT_ELECTRICITY_DELIVERY = r"\d-\d:2\.7\.0.+?\r\n" +LONG_POWER_FAILURE_COUNT = r"96\.7\.9.+?\r\n" +SHORT_POWER_FAILURE_COUNT = r"96\.7\.21.+?\r\n" +POWER_EVENT_FAILURE_LOG = r"99\.97\.0.+?\r\n" +VOLTAGE_SAG_L1_COUNT = r"\d-\d:32\.32\.0.+?\r\n" +VOLTAGE_SAG_L2_COUNT = r"\d-\d:52\.32\.0.+?\r\n" +VOLTAGE_SAG_L3_COUNT = r"\d-\d:72\.32\.0.+?\r\n" +VOLTAGE_SWELL_L1_COUNT = r"\d-\d:32\.36\.0.+?\r\n" +VOLTAGE_SWELL_L2_COUNT = r"\d-\d:52\.36\.0.+?\r\n" +VOLTAGE_SWELL_L3_COUNT = r"\d-\d:72\.36\.0.+?\r\n" +INSTANTANEOUS_VOLTAGE_L1 = r"\d-\d:32\.7\.0.+?\r\n" +INSTANTANEOUS_VOLTAGE_L2 = r"\d-\d:52\.7\.0.+?\r\n" +INSTANTANEOUS_VOLTAGE_L3 = r"\d-\d:72\.7\.0.+?\r\n" +INSTANTANEOUS_CURRENT_L1 = r"\d-\d:31\.7\.0.+?\r\n" +INSTANTANEOUS_CURRENT_L2 = r"\d-\d:51\.7\.0.+?\r\n" +INSTANTANEOUS_CURRENT_L3 = r"\d-\d:71\.7\.0.+?\r\n" +TEXT_MESSAGE_CODE = r"\d-\d:96\.13\.1.+?\r\n" +TEXT_MESSAGE = r"\d-\d:96\.13\.0.+?\r\n" +DEVICE_TYPE = r"\d-\d:24\.1\.0.+?\r\n" +INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE = r"\d-\d:21\.7\.0.+?\r\n" +INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE = r"\d-\d:41\.7\.0.+?\r\n" +INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE = r"\d-\d:61\.7\.0.+?\r\n" +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 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' +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 -) +ELECTRICITY_USED_TARIFF_ALL = (ELECTRICITY_USED_TARIFF_1, ELECTRICITY_USED_TARIFF_2) ELECTRICITY_DELIVERED_TARIFF_ALL = ( ELECTRICITY_DELIVERED_TARIFF_1, - ELECTRICITY_DELIVERED_TARIFF_2 + ELECTRICITY_DELIVERED_TARIFF_2, ) # Alternate codes for foreign countries. -BELGIUM_HOURLY_GAS_METER_READING = r'\d-\d:24\.2\.3.+?\r\n' # Different code, same format. -LUXEMBOURG_EQUIPMENT_IDENTIFIER = r'\d-\d:42\.0\.0.+?\r\n' # Logical device name -LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL = r'\d-\d:1\.8\.0.+?\r\n' # Total imported energy register (P+) -LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = r'\d-\d:2\.8\.0.+?\r\n' # Total exported energy register (P-) -SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL = r'\d-\d:1\.8\.0.+?\r\n' # Total imported energy register (P+) -SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = r'\d-\d:2\.8\.0.+?\r\n' # Total exported energy register (P-) +BELGIUM_HOURLY_GAS_METER_READING = ( + r"\d-\d:24\.2\.3.+?\r\n" # Different code, same format. +) +LUXEMBOURG_EQUIPMENT_IDENTIFIER = r"\d-\d:42\.0\.0.+?\r\n" # Logical device name +LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL = ( + r"\d-\d:1\.8\.0.+?\r\n" # Total imported energy register (P+) +) +LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = ( + r"\d-\d:2\.8\.0.+?\r\n" # Total exported energy register (P-) +) +SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL = ( + r"\d-\d:1\.8\.0.+?\r\n" # Total imported energy register (P+) +) +SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = ( + r"\d-\d:2\.8\.0.+?\r\n" # Total exported energy register (P-) +) + +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/telegram_specifications.py b/dsmr_parser/telegram_specifications.py index 4e59f51..0e3ed61 100644 --- a/dsmr_parser/telegram_specifications.py +++ b/dsmr_parser/telegram_specifications.py @@ -2,9 +2,18 @@ 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, +) from dsmr_parser.value_types import timestamp -from dsmr_parser.profile_generic_specifications import BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS +from dsmr_parser.profile_generic_specifications import ( + BUFFER_TYPES, + PG_HEAD_PARSERS, + PG_UNIDENTIFIED_BUFFERTYPE_PARSERS, +) """ dsmr_parser.telegram_specifications @@ -15,8 +24,8 @@ how the telegram lines are parsed. """ V2_2 = { - 'checksum_support': False, - 'objects': { + "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)), @@ -41,14 +50,14 @@ V2_2 = { ValueParser(str), # unit, position 5 ValueParser(Decimal), # meter reading, position 6 ), - } + }, } V3 = V2_2 V4 = { - 'checksum_support': True, - 'objects': { + "checksum_support": True, + "objects": { obis.P1_MESSAGE_HEADER: CosemParser(ValueParser(str)), obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)), obis.EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), @@ -61,10 +70,9 @@ V4 = { obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), obis.SHORT_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), obis.LONG_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), - obis.POWER_EVENT_FAILURE_LOG: - ProfileGenericParser(BUFFER_TYPES, - PG_HEAD_PARSERS, - PG_UNIDENTIFIED_BUFFERTYPE_PARSERS), + obis.POWER_EVENT_FAILURE_LOG: ProfileGenericParser( + BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS + ), obis.VOLTAGE_SAG_L1_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SAG_L2_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SAG_L3_COUNT: CosemParser(ValueParser(int)), @@ -85,15 +93,14 @@ V4 = { obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: CosemParser(ValueParser(Decimal)), obis.EQUIPMENT_IDENTIFIER_GAS: CosemParser(ValueParser(str)), obis.HOURLY_GAS_METER_READING: MBusParser( - ValueParser(timestamp), - ValueParser(Decimal) - ) - } + ValueParser(timestamp), ValueParser(Decimal) + ), + }, } V5 = { - 'checksum_support': True, - 'objects': { + "checksum_support": True, + "objects": { obis.P1_MESSAGE_HEADER: CosemParser(ValueParser(str)), obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)), obis.EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), @@ -107,10 +114,9 @@ V5 = { obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), obis.LONG_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), obis.SHORT_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), - obis.POWER_EVENT_FAILURE_LOG: - ProfileGenericParser(BUFFER_TYPES, - PG_HEAD_PARSERS, - PG_UNIDENTIFIED_BUFFERTYPE_PARSERS), + obis.POWER_EVENT_FAILURE_LOG: ProfileGenericParser( + BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS + ), obis.VOLTAGE_SAG_L1_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SAG_L2_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SAG_L3_COUNT: CosemParser(ValueParser(int)), @@ -133,38 +139,46 @@ V5 = { obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: CosemParser(ValueParser(Decimal)), obis.EQUIPMENT_IDENTIFIER_GAS: CosemParser(ValueParser(str)), obis.HOURLY_GAS_METER_READING: MBusParser( - ValueParser(timestamp), - ValueParser(Decimal) - ) - } + ValueParser(timestamp), ValueParser(Decimal) + ), + }, } ALL = (V2_2, V3, V4, V5) BELGIUM_FLUVIUS = deepcopy(V5) -BELGIUM_FLUVIUS['objects'].update({ - obis.BELGIUM_HOURLY_GAS_METER_READING: MBusParser( - ValueParser(timestamp), - ValueParser(Decimal) - ) -}) +BELGIUM_FLUVIUS["objects"].update( + { + obis.BELGIUM_HOURLY_GAS_METER_READING: MBusParser( + ValueParser(timestamp), ValueParser(Decimal) + ) + } +) LUXEMBOURG_SMARTY = deepcopy(V5) -LUXEMBOURG_SMARTY['objects'].update({ - obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), - obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), - obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), -}) +LUXEMBOURG_SMARTY["objects"].update( + { + obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), + obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser( + ValueParser(Decimal) + ), + obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser( + ValueParser(Decimal) + ), + } +) # Source: https://www.energiforetagen.se/globalassets/energiforetagen/det-erbjuder-vi/kurser-och-konferenser/elnat/branschrekommendation-lokalt-granssnitt-v2_0-201912.pdf SWEDEN = { - 'checksum_support': True, - 'objects': { + "checksum_support": True, + "objects": { obis.P1_MESSAGE_HEADER: CosemParser(ValueParser(str)), obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)), obis.SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), - obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), + obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser( + ValueParser(Decimal) + ), obis.CURRENT_ELECTRICITY_USAGE: CosemParser(ValueParser(Decimal)), obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: CosemParser(ValueParser(Decimal)), @@ -179,5 +193,24 @@ SWEDEN = { obis.INSTANTANEOUS_CURRENT_L1: CosemParser(ValueParser(Decimal)), obis.INSTANTANEOUS_CURRENT_L2: CosemParser(ValueParser(Decimal)), obis.INSTANTANEOUS_CURRENT_L3: CosemParser(ValueParser(Decimal)), - } + }, +} + +Q3D = { + "checksum_support": False, + "objects": { + obis.Q3D_EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), + obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser( + ValueParser(Decimal) + ), + obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: 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.CURRENT_ELECTRICITY_USAGE: CosemParser(ValueParser(Decimal)), + obis.Q3D_EQUIPMENT_STATE: CosemParser(ValueParser(str)), + obis.Q3D_EQUIPMENT_SERIALNUMBER: CosemParser(ValueParser(str)), + }, } From 007b3ea0895ecd91441fb918d48290cee94d1949 Mon Sep 17 00:00:00 2001 From: Gunnar Klauberg Date: Fri, 12 Nov 2021 18:13:48 +0100 Subject: [PATCH 41/85] ignoring trailing \xff in byte stream --- dsmr_parser/clients/protocol.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index 560c0aa..56ed26d 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -116,7 +116,7 @@ class DSMRProtocol(asyncio.Protocol): def data_received(self, data): """Add incoming data to buffer.""" - data = data.decode("ascii") + data = data.decode("ascii", errors="ignore") self._active = True self.log.debug("received data: %s", data) self.telegram_buffer.append(data) diff --git a/setup.py b/setup.py index 7ad7c68..3852064 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( author_email='nigel@nldr.net', license='MIT', url='https://github.com/ndokter/dsmr_parser', - version='0.30', + version='0.31', packages=find_packages(exclude=('test', 'test.*')), install_requires=[ 'pyserial>=3,<4', From 8b8b952ce15447cde6e2d0e18c53b37500de845a Mon Sep 17 00:00:00 2001 From: Gunnar Klauberg Date: Fri, 12 Nov 2021 17:38:16 +0000 Subject: [PATCH 42/85] clean-up re-format --- dsmr_parser/__main__.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/dsmr_parser/__main__.py b/dsmr_parser/__main__.py index 1a127cf..24ed65d 100644 --- a/dsmr_parser/__main__.py +++ b/dsmr_parser/__main__.py @@ -10,20 +10,15 @@ def console(): """Output DSMR data to console.""" parser = argparse.ArgumentParser(description=console.__doc__) - parser.add_argument( - "--device", default="/dev/ttyUSB0", help="port to read DSMR data from" - ) - parser.add_argument( - "--host", default=None, help="alternatively connect using TCP host." - ) - parser.add_argument("--port", default=None, help="TCP port to use for connection") - parser.add_argument( - "--version", - default="2.2", - choices=["2.2", "4", "5", "5B", "5L", "5S", "Q3D"], - help="DSMR version (2.2, 4, 5, 5B, 5L, 5S, Q3D)", - ) - parser.add_argument("--verbose", "-v", action="count") + parser.add_argument('--device', default='/dev/ttyUSB0', + help='port to read DSMR data from') + parser.add_argument('--host', default=None, + help='alternatively connect using TCP host.') + parser.add_argument('--port', default=None, + help='TCP port to use for connection') + parser.add_argument('--version', default='2.2', choices=['2.2', '4', '5', '5B', '5L', '5S', 'Q3D'], + help='DSMR version (2.2, 4, 5, 5B, 5L, 5S, Q3D)') + parser.add_argument('--verbose', '-v', action='count') args = parser.parse_args() From e4f384c2b76615b68f32f6d6df24017b0a383fcf Mon Sep 17 00:00:00 2001 From: Gunnar Klauberg Date: Fri, 12 Nov 2021 17:48:21 +0000 Subject: [PATCH 43/85] clean-up re-format --- dsmr_parser/__main__.py | 17 +++----- dsmr_parser/clients/protocol.py | 74 +++++++++++++-------------------- 2 files changed, 36 insertions(+), 55 deletions(-) diff --git a/dsmr_parser/__main__.py b/dsmr_parser/__main__.py index 24ed65d..9169318 100644 --- a/dsmr_parser/__main__.py +++ b/dsmr_parser/__main__.py @@ -39,18 +39,13 @@ def console(): # create tcp or serial connection depending on args if args.host and args.port: - create_connection = partial( - create_tcp_dsmr_reader, - args.host, - args.port, - args.version, - print_callback, - loop=loop, - ) + create_connection = partial(create_tcp_dsmr_reader, + args.host, args.port, args.version, + print_callback, loop=loop) else: - create_connection = partial( - create_dsmr_reader, args.device, args.version, print_callback, loop=loop - ) + create_connection = partial(create_dsmr_reader, + args.device, args.version, + print_callback, loop=loop) try: # connect and keep connected until interrupted by ctrl-c diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index 56ed26d..6a7a17e 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -10,49 +10,40 @@ from dsmr_parser import telegram_specifications from dsmr_parser.clients.telegram_buffer import TelegramBuffer from dsmr_parser.exceptions import ParseError, InvalidChecksumError from dsmr_parser.parsers import TelegramParser -from dsmr_parser.clients.settings import ( - SERIAL_SETTINGS_V2_2, - SERIAL_SETTINGS_V4, - SERIAL_SETTINGS_V5, -) +from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \ + SERIAL_SETTINGS_V4, SERIAL_SETTINGS_V5 def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): """Creates a DSMR asyncio protocol.""" - if dsmr_version == "2.2": + if dsmr_version == '2.2': specification = telegram_specifications.V2_2 serial_settings = SERIAL_SETTINGS_V2_2 - elif dsmr_version == "4": + elif dsmr_version == '4': specification = telegram_specifications.V4 serial_settings = SERIAL_SETTINGS_V4 - elif dsmr_version == "5": + elif dsmr_version == '5': specification = telegram_specifications.V5 serial_settings = SERIAL_SETTINGS_V5 - elif dsmr_version == "5B": + elif dsmr_version == '5B': specification = telegram_specifications.BELGIUM_FLUVIUS serial_settings = SERIAL_SETTINGS_V5 - elif dsmr_version == "5L": + elif dsmr_version == '5L': specification = telegram_specifications.LUXEMBOURG_SMARTY serial_settings = SERIAL_SETTINGS_V5 - elif dsmr_version == "5S": + elif dsmr_version == '5S': specification = telegram_specifications.SWEDEN serial_settings = SERIAL_SETTINGS_V5 - elif dsmr_version == "Q3D": + elif dsmr_version == 'Q3D': specification = telegram_specifications.Q3D serial_settings = SERIAL_SETTINGS_V5 else: - raise NotImplementedError( - "No telegram parser found for version: %s", dsmr_version - ) + raise NotImplementedError("No telegram parser found for version: %s", + dsmr_version) - protocol = partial( - DSMRProtocol, - loop, - TelegramParser(specification), - telegram_callback=telegram_callback, - **kwargs - ) + protocol = partial(DSMRProtocol, loop, TelegramParser(specification), + telegram_callback=telegram_callback, **kwargs) return protocol, serial_settings @@ -60,26 +51,22 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): def create_dsmr_reader(port, dsmr_version, telegram_callback, loop=None): """Creates a DSMR asyncio protocol coroutine using serial port.""" protocol, serial_settings = create_dsmr_protocol( - dsmr_version, telegram_callback, loop=None - ) - serial_settings["url"] = port + dsmr_version, telegram_callback, loop=None) + serial_settings['url'] = port conn = create_serial_connection(loop, protocol, **serial_settings) return conn -def create_tcp_dsmr_reader( - host, port, dsmr_version, telegram_callback, loop=None, keep_alive_interval=None -): +def create_tcp_dsmr_reader(host, port, dsmr_version, + telegram_callback, loop=None, + keep_alive_interval=None): """Creates a DSMR asyncio protocol coroutine using TCP connection.""" if not loop: loop = asyncio.get_event_loop() protocol, _ = create_dsmr_protocol( - dsmr_version, - telegram_callback, - loop=loop, - keep_alive_interval=keep_alive_interval, - ) + dsmr_version, telegram_callback, loop=loop, + keep_alive_interval=keep_alive_interval) conn = loop.create_connection(protocol, host, port) return conn @@ -90,9 +77,8 @@ class DSMRProtocol(asyncio.Protocol): transport = None telegram_callback = None - def __init__( - self, loop, telegram_parser, telegram_callback=None, keep_alive_interval=None - ): + def __init__(self, loop, telegram_parser, + telegram_callback=None, keep_alive_interval=None): """Initialize class.""" self.loop = loop self.log = logging.getLogger(__name__) @@ -109,16 +95,16 @@ class DSMRProtocol(asyncio.Protocol): def connection_made(self, transport): """Just logging for now.""" self.transport = transport - self.log.debug("connected") + self.log.debug('connected') self._active = False if self.loop and self._keep_alive_interval: self.loop.call_later(self._keep_alive_interval, self.keep_alive) def data_received(self, data): """Add incoming data to buffer.""" - data = data.decode("ascii", errors="ignore") + data = data.decode('ascii', errors='ignore') self._active = True - self.log.debug("received data: %s", data) + self.log.debug('received data: %s', data) self.telegram_buffer.append(data) for telegram in self.telegram_buffer.get_all(): @@ -126,26 +112,26 @@ class DSMRProtocol(asyncio.Protocol): def keep_alive(self): if self._active: - self.log.debug("keep-alive checked") + self.log.debug('keep-alive checked') self._active = False if self.loop: self.loop.call_later(self._keep_alive_interval, self.keep_alive) else: - self.log.warning("keep-alive check failed") + self.log.warning('keep-alive check failed') if self.transport: self.transport.close() def connection_lost(self, exc): """Stop when connection is lost.""" if exc: - self.log.exception("disconnected due to exception", exc_info=exc) + self.log.exception('disconnected due to exception', exc_info=exc) else: - self.log.info("disconnected because of close/abort.") + self.log.info('disconnected because of close/abort.') self._closed.set() def handle_telegram(self, telegram): """Send off parsed telegram to handling callback.""" - self.log.debug("got telegram: %s", telegram) + self.log.debug('got telegram: %s', telegram) try: parsed_telegram = self.telegram_parser.parse(telegram) From 17b56ae07bff4080c27524872046cf377f8f9ccb Mon Sep 17 00:00:00 2001 From: Gunnar Klauberg Date: Fri, 12 Nov 2021 17:51:24 +0000 Subject: [PATCH 44/85] clean-up re-format --- dsmr_parser/obis_name_mapping.py | 100 +++++++++++++------------- dsmr_parser/obis_references.py | 118 ++++++++++++++----------------- 2 files changed, 105 insertions(+), 113 deletions(-) diff --git a/dsmr_parser/obis_name_mapping.py b/dsmr_parser/obis_name_mapping.py index f7aff77..fcba570 100644 --- a/dsmr_parser/obis_name_mapping.py +++ b/dsmr_parser/obis_name_mapping.py @@ -8,56 +8,56 @@ This module contains a mapping of obis references to names. """ EN = { - obis.P1_MESSAGE_HEADER: "P1_MESSAGE_HEADER", - obis.P1_MESSAGE_TIMESTAMP: "P1_MESSAGE_TIMESTAMP", - obis.ELECTRICITY_IMPORTED_TOTAL: "ELECTRICITY_IMPORTED_TOTAL", - obis.ELECTRICITY_USED_TARIFF_1: "ELECTRICITY_USED_TARIFF_1", - obis.ELECTRICITY_USED_TARIFF_2: "ELECTRICITY_USED_TARIFF_2", - obis.ELECTRICITY_DELIVERED_TARIFF_1: "ELECTRICITY_DELIVERED_TARIFF_1", - obis.ELECTRICITY_DELIVERED_TARIFF_2: "ELECTRICITY_DELIVERED_TARIFF_2", - obis.ELECTRICITY_ACTIVE_TARIFF: "ELECTRICITY_ACTIVE_TARIFF", - obis.EQUIPMENT_IDENTIFIER: "EQUIPMENT_IDENTIFIER", - obis.CURRENT_ELECTRICITY_USAGE: "CURRENT_ELECTRICITY_USAGE", - obis.CURRENT_ELECTRICITY_DELIVERY: "CURRENT_ELECTRICITY_DELIVERY", - obis.LONG_POWER_FAILURE_COUNT: "LONG_POWER_FAILURE_COUNT", - obis.SHORT_POWER_FAILURE_COUNT: "SHORT_POWER_FAILURE_COUNT", - obis.POWER_EVENT_FAILURE_LOG: "POWER_EVENT_FAILURE_LOG", - obis.VOLTAGE_SAG_L1_COUNT: "VOLTAGE_SAG_L1_COUNT", - obis.VOLTAGE_SAG_L2_COUNT: "VOLTAGE_SAG_L2_COUNT", - obis.VOLTAGE_SAG_L3_COUNT: "VOLTAGE_SAG_L3_COUNT", - obis.VOLTAGE_SWELL_L1_COUNT: "VOLTAGE_SWELL_L1_COUNT", - obis.VOLTAGE_SWELL_L2_COUNT: "VOLTAGE_SWELL_L2_COUNT", - obis.VOLTAGE_SWELL_L3_COUNT: "VOLTAGE_SWELL_L3_COUNT", - obis.INSTANTANEOUS_VOLTAGE_L1: "INSTANTANEOUS_VOLTAGE_L1", - obis.INSTANTANEOUS_VOLTAGE_L2: "INSTANTANEOUS_VOLTAGE_L2", - obis.INSTANTANEOUS_VOLTAGE_L3: "INSTANTANEOUS_VOLTAGE_L3", - obis.INSTANTANEOUS_CURRENT_L1: "INSTANTANEOUS_CURRENT_L1", - obis.INSTANTANEOUS_CURRENT_L2: "INSTANTANEOUS_CURRENT_L2", - obis.INSTANTANEOUS_CURRENT_L3: "INSTANTANEOUS_CURRENT_L3", - obis.TEXT_MESSAGE_CODE: "TEXT_MESSAGE_CODE", - obis.TEXT_MESSAGE: "TEXT_MESSAGE", - obis.DEVICE_TYPE: "DEVICE_TYPE", - obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: "INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE", - obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE: "INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE", - obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE: "INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE", - obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE: "INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE", - obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE: "INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE", - obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: "INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE", - obis.EQUIPMENT_IDENTIFIER_GAS: "EQUIPMENT_IDENTIFIER_GAS", - obis.HOURLY_GAS_METER_READING: "HOURLY_GAS_METER_READING", - obis.GAS_METER_READING: "GAS_METER_READING", - obis.ACTUAL_TRESHOLD_ELECTRICITY: "ACTUAL_TRESHOLD_ELECTRICITY", - obis.ACTUAL_SWITCH_POSITION: "ACTUAL_SWITCH_POSITION", - obis.VALVE_POSITION_GAS: "VALVE_POSITION_GAS", - obis.BELGIUM_HOURLY_GAS_METER_READING: "BELGIUM_HOURLY_GAS_METER_READING", - obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: "LUXEMBOURG_EQUIPMENT_IDENTIFIER", - obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: "LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL", - obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: "LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL", - obis.SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL: "SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL", - obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: "SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL", - obis.Q3D_EQUIPMENT_IDENTIFIER: "Q3D_EQUIPMENT_IDENTIFIER", - obis.Q3D_EQUIPMENT_STATE: "Q3D_EQUIPMENT_STATE", - obis.Q3D_EQUIPMENT_SERIALNUMBER: "Q3D_EQUIPMENT_SERIALNUMBER", + obis.P1_MESSAGE_HEADER: 'P1_MESSAGE_HEADER', + obis.P1_MESSAGE_TIMESTAMP: 'P1_MESSAGE_TIMESTAMP', + obis.ELECTRICITY_IMPORTED_TOTAL: 'ELECTRICITY_IMPORTED_TOTAL', + obis.ELECTRICITY_USED_TARIFF_1: 'ELECTRICITY_USED_TARIFF_1', + obis.ELECTRICITY_USED_TARIFF_2: 'ELECTRICITY_USED_TARIFF_2', + obis.ELECTRICITY_DELIVERED_TARIFF_1: 'ELECTRICITY_DELIVERED_TARIFF_1', + obis.ELECTRICITY_DELIVERED_TARIFF_2: 'ELECTRICITY_DELIVERED_TARIFF_2', + obis.ELECTRICITY_ACTIVE_TARIFF: 'ELECTRICITY_ACTIVE_TARIFF', + obis.EQUIPMENT_IDENTIFIER: 'EQUIPMENT_IDENTIFIER', + obis.CURRENT_ELECTRICITY_USAGE: 'CURRENT_ELECTRICITY_USAGE', + obis.CURRENT_ELECTRICITY_DELIVERY: 'CURRENT_ELECTRICITY_DELIVERY', + obis.LONG_POWER_FAILURE_COUNT: 'LONG_POWER_FAILURE_COUNT', + obis.SHORT_POWER_FAILURE_COUNT: 'SHORT_POWER_FAILURE_COUNT', + obis.POWER_EVENT_FAILURE_LOG: 'POWER_EVENT_FAILURE_LOG', + obis.VOLTAGE_SAG_L1_COUNT: 'VOLTAGE_SAG_L1_COUNT', + obis.VOLTAGE_SAG_L2_COUNT: 'VOLTAGE_SAG_L2_COUNT', + obis.VOLTAGE_SAG_L3_COUNT: 'VOLTAGE_SAG_L3_COUNT', + obis.VOLTAGE_SWELL_L1_COUNT: 'VOLTAGE_SWELL_L1_COUNT', + obis.VOLTAGE_SWELL_L2_COUNT: 'VOLTAGE_SWELL_L2_COUNT', + obis.VOLTAGE_SWELL_L3_COUNT: 'VOLTAGE_SWELL_L3_COUNT', + obis.INSTANTANEOUS_VOLTAGE_L1: 'INSTANTANEOUS_VOLTAGE_L1', + obis.INSTANTANEOUS_VOLTAGE_L2: 'INSTANTANEOUS_VOLTAGE_L2', + obis.INSTANTANEOUS_VOLTAGE_L3: 'INSTANTANEOUS_VOLTAGE_L3', + obis.INSTANTANEOUS_CURRENT_L1: 'INSTANTANEOUS_CURRENT_L1', + obis.INSTANTANEOUS_CURRENT_L2: 'INSTANTANEOUS_CURRENT_L2', + obis.INSTANTANEOUS_CURRENT_L3: 'INSTANTANEOUS_CURRENT_L3', + obis.TEXT_MESSAGE_CODE: 'TEXT_MESSAGE_CODE', + obis.TEXT_MESSAGE: 'TEXT_MESSAGE', + obis.DEVICE_TYPE: 'DEVICE_TYPE', + obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: 'INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE', + obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE: 'INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE', + obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE: 'INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE', + obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE: 'INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE', + obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE: 'INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE', + obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: 'INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE', + obis.EQUIPMENT_IDENTIFIER_GAS: 'EQUIPMENT_IDENTIFIER_GAS', + obis.HOURLY_GAS_METER_READING: 'HOURLY_GAS_METER_READING', + obis.GAS_METER_READING: 'GAS_METER_READING', + obis.ACTUAL_TRESHOLD_ELECTRICITY: 'ACTUAL_TRESHOLD_ELECTRICITY', + obis.ACTUAL_SWITCH_POSITION: 'ACTUAL_SWITCH_POSITION', + obis.VALVE_POSITION_GAS: 'VALVE_POSITION_GAS', + obis.BELGIUM_HOURLY_GAS_METER_READING: 'BELGIUM_HOURLY_GAS_METER_READING', + obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: 'LUXEMBOURG_EQUIPMENT_IDENTIFIER', + obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: 'LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL', + obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: 'LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL', + obis.SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL: 'SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL', + obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: 'SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL', + obis.Q3D_EQUIPMENT_IDENTIFIER: 'Q3D_EQUIPMENT_IDENTIFIER', + obis.Q3D_EQUIPMENT_STATE: 'Q3D_EQUIPMENT_STATE', + obis.Q3D_EQUIPMENT_SERIALNUMBER: 'Q3D_EQUIPMENT_SERIALNUMBER', } REVERSE_EN = dict([(v, k) for k, v in EN.items()]) diff --git a/dsmr_parser/obis_references.py b/dsmr_parser/obis_references.py index e355ead..3808848 100644 --- a/dsmr_parser/obis_references.py +++ b/dsmr_parser/obis_references.py @@ -6,76 +6,68 @@ refactored to full line signatures to maintain backwards compatibility. Might be refactored in a backwards incompatible way as soon as proper telegram objects are introduced. """ -P1_MESSAGE_HEADER = r"\d-\d:0\.2\.8.+?\r\n" -P1_MESSAGE_TIMESTAMP = r"\d-\d:1\.0\.0.+?\r\n" -ELECTRICITY_IMPORTED_TOTAL = r"\d-\d:1\.8\.0.+?\r\n" -ELECTRICITY_USED_TARIFF_1 = r"\d-\d:1\.8\.1.+?\r\n" -ELECTRICITY_USED_TARIFF_2 = r"\d-\d:1\.8\.2.+?\r\n" -ELECTRICITY_DELIVERED_TARIFF_1 = r"\d-\d:2\.8\.1.+?\r\n" -ELECTRICITY_DELIVERED_TARIFF_2 = r"\d-\d:2\.8\.2.+?\r\n" -ELECTRICITY_ACTIVE_TARIFF = r"\d-\d:96\.14\.0.+?\r\n" -EQUIPMENT_IDENTIFIER = r"\d-\d:96\.1\.1.+?\r\n" -CURRENT_ELECTRICITY_USAGE = r"\d-\d:1\.7\.0.+?\r\n" -CURRENT_ELECTRICITY_DELIVERY = r"\d-\d:2\.7\.0.+?\r\n" -LONG_POWER_FAILURE_COUNT = r"96\.7\.9.+?\r\n" -SHORT_POWER_FAILURE_COUNT = r"96\.7\.21.+?\r\n" -POWER_EVENT_FAILURE_LOG = r"99\.97\.0.+?\r\n" -VOLTAGE_SAG_L1_COUNT = r"\d-\d:32\.32\.0.+?\r\n" -VOLTAGE_SAG_L2_COUNT = r"\d-\d:52\.32\.0.+?\r\n" -VOLTAGE_SAG_L3_COUNT = r"\d-\d:72\.32\.0.+?\r\n" -VOLTAGE_SWELL_L1_COUNT = r"\d-\d:32\.36\.0.+?\r\n" -VOLTAGE_SWELL_L2_COUNT = r"\d-\d:52\.36\.0.+?\r\n" -VOLTAGE_SWELL_L3_COUNT = r"\d-\d:72\.36\.0.+?\r\n" -INSTANTANEOUS_VOLTAGE_L1 = r"\d-\d:32\.7\.0.+?\r\n" -INSTANTANEOUS_VOLTAGE_L2 = r"\d-\d:52\.7\.0.+?\r\n" -INSTANTANEOUS_VOLTAGE_L3 = r"\d-\d:72\.7\.0.+?\r\n" -INSTANTANEOUS_CURRENT_L1 = r"\d-\d:31\.7\.0.+?\r\n" -INSTANTANEOUS_CURRENT_L2 = r"\d-\d:51\.7\.0.+?\r\n" -INSTANTANEOUS_CURRENT_L3 = r"\d-\d:71\.7\.0.+?\r\n" -TEXT_MESSAGE_CODE = r"\d-\d:96\.13\.1.+?\r\n" -TEXT_MESSAGE = r"\d-\d:96\.13\.0.+?\r\n" -DEVICE_TYPE = r"\d-\d:24\.1\.0.+?\r\n" -INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE = r"\d-\d:21\.7\.0.+?\r\n" -INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE = r"\d-\d:41\.7\.0.+?\r\n" -INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE = r"\d-\d:61\.7\.0.+?\r\n" -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" +P1_MESSAGE_HEADER = r'\d-\d:0\.2\.8.+?\r\n' +P1_MESSAGE_TIMESTAMP = r'\d-\d:1\.0\.0.+?\r\n' +ELECTRICITY_IMPORTED_TOTAL = r'\d-\d:1\.8\.0.+?\r\n' +ELECTRICITY_USED_TARIFF_1 = r'\d-\d:1\.8\.1.+?\r\n' +ELECTRICITY_USED_TARIFF_2 = r'\d-\d:1\.8\.2.+?\r\n' +ELECTRICITY_DELIVERED_TARIFF_1 = r'\d-\d:2\.8\.1.+?\r\n' +ELECTRICITY_DELIVERED_TARIFF_2 = r'\d-\d:2\.8\.2.+?\r\n' +ELECTRICITY_ACTIVE_TARIFF = r'\d-\d:96\.14\.0.+?\r\n' +EQUIPMENT_IDENTIFIER = r'\d-\d:96\.1\.1.+?\r\n' +CURRENT_ELECTRICITY_USAGE = r'\d-\d:1\.7\.0.+?\r\n' +CURRENT_ELECTRICITY_DELIVERY = r'\d-\d:2\.7\.0.+?\r\n' +LONG_POWER_FAILURE_COUNT = r'96\.7\.9.+?\r\n' +SHORT_POWER_FAILURE_COUNT = r'96\.7\.21.+?\r\n' +POWER_EVENT_FAILURE_LOG = r'99\.97\.0.+?\r\n' +VOLTAGE_SAG_L1_COUNT = r'\d-\d:32\.32\.0.+?\r\n' +VOLTAGE_SAG_L2_COUNT = r'\d-\d:52\.32\.0.+?\r\n' +VOLTAGE_SAG_L3_COUNT = r'\d-\d:72\.32\.0.+?\r\n' +VOLTAGE_SWELL_L1_COUNT = r'\d-\d:32\.36\.0.+?\r\n' +VOLTAGE_SWELL_L2_COUNT = r'\d-\d:52\.36\.0.+?\r\n' +VOLTAGE_SWELL_L3_COUNT = r'\d-\d:72\.36\.0.+?\r\n' +INSTANTANEOUS_VOLTAGE_L1 = r'\d-\d:32\.7\.0.+?\r\n' +INSTANTANEOUS_VOLTAGE_L2 = r'\d-\d:52\.7\.0.+?\r\n' +INSTANTANEOUS_VOLTAGE_L3 = r'\d-\d:72\.7\.0.+?\r\n' +INSTANTANEOUS_CURRENT_L1 = r'\d-\d:31\.7\.0.+?\r\n' +INSTANTANEOUS_CURRENT_L2 = r'\d-\d:51\.7\.0.+?\r\n' +INSTANTANEOUS_CURRENT_L3 = r'\d-\d:71\.7\.0.+?\r\n' +TEXT_MESSAGE_CODE = r'\d-\d:96\.13\.1.+?\r\n' +TEXT_MESSAGE = r'\d-\d:96\.13\.0.+?\r\n' +DEVICE_TYPE = r'\d-\d:24\.1\.0.+?\r\n' +INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE = r'\d-\d:21\.7\.0.+?\r\n' +INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE = r'\d-\d:41\.7\.0.+?\r\n' +INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE = r'\d-\d:61\.7\.0.+?\r\n' +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 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" +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) +ELECTRICITY_USED_TARIFF_ALL = ( + ELECTRICITY_USED_TARIFF_1, + ELECTRICITY_USED_TARIFF_2 +) ELECTRICITY_DELIVERED_TARIFF_ALL = ( ELECTRICITY_DELIVERED_TARIFF_1, - ELECTRICITY_DELIVERED_TARIFF_2, + ELECTRICITY_DELIVERED_TARIFF_2 ) # Alternate codes for foreign countries. -BELGIUM_HOURLY_GAS_METER_READING = ( - r"\d-\d:24\.2\.3.+?\r\n" # Different code, same format. -) -LUXEMBOURG_EQUIPMENT_IDENTIFIER = r"\d-\d:42\.0\.0.+?\r\n" # Logical device name -LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL = ( - r"\d-\d:1\.8\.0.+?\r\n" # Total imported energy register (P+) -) -LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = ( - r"\d-\d:2\.8\.0.+?\r\n" # Total exported energy register (P-) -) -SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL = ( - r"\d-\d:1\.8\.0.+?\r\n" # Total imported energy register (P+) -) -SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = ( - r"\d-\d:2\.8\.0.+?\r\n" # Total exported energy register (P-) -) - -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 +BELGIUM_HOURLY_GAS_METER_READING = r'\d-\d:24\.2\.3.+?\r\n' # Different code, same format. +LUXEMBOURG_EQUIPMENT_IDENTIFIER = r'\d-\d:42\.0\.0.+?\r\n' # Logical device name +LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL = r'\d-\d:1\.8\.0.+?\r\n' # Total imported energy register (P+) +LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = r'\d-\d:2\.8\.0.+?\r\n' # Total exported energy register (P-) +SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL = r'\d-\d:1\.8\.0.+?\r\n' # Total imported energy register (P+) +SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = r'\d-\d:2\.8\.0.+?\r\n' # Total exported energy register (P-) +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 From 2b724b87c3a18f9edda64b1ea5b79a1eef80bfd1 Mon Sep 17 00:00:00 2001 From: Gunnar Klauberg Date: Fri, 12 Nov 2021 19:21:32 +0000 Subject: [PATCH 45/85] clean-up re-format --- dsmr_parser/telegram_specifications.py | 104 ++++++++++--------------- 1 file changed, 43 insertions(+), 61 deletions(-) diff --git a/dsmr_parser/telegram_specifications.py b/dsmr_parser/telegram_specifications.py index 0e3ed61..bb82868 100644 --- a/dsmr_parser/telegram_specifications.py +++ b/dsmr_parser/telegram_specifications.py @@ -2,30 +2,20 @@ 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 from dsmr_parser.value_types import timestamp -from dsmr_parser.profile_generic_specifications import ( - BUFFER_TYPES, - PG_HEAD_PARSERS, - PG_UNIDENTIFIED_BUFFERTYPE_PARSERS, -) +from dsmr_parser.profile_generic_specifications import BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS """ dsmr_parser.telegram_specifications ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - This module contains DSMR telegram specifications. Each specifications describes how the telegram lines are parsed. """ V2_2 = { - "checksum_support": False, - "objects": { + '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)), @@ -50,14 +40,14 @@ V2_2 = { ValueParser(str), # unit, position 5 ValueParser(Decimal), # meter reading, position 6 ), - }, + } } V3 = V2_2 V4 = { - "checksum_support": True, - "objects": { + 'checksum_support': True, + 'objects': { obis.P1_MESSAGE_HEADER: CosemParser(ValueParser(str)), obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)), obis.EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), @@ -70,9 +60,10 @@ V4 = { obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), obis.SHORT_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), obis.LONG_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), - obis.POWER_EVENT_FAILURE_LOG: ProfileGenericParser( - BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS - ), + obis.POWER_EVENT_FAILURE_LOG: + ProfileGenericParser(BUFFER_TYPES, + PG_HEAD_PARSERS, + PG_UNIDENTIFIED_BUFFERTYPE_PARSERS), obis.VOLTAGE_SAG_L1_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SAG_L2_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SAG_L3_COUNT: CosemParser(ValueParser(int)), @@ -93,14 +84,15 @@ V4 = { obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: CosemParser(ValueParser(Decimal)), obis.EQUIPMENT_IDENTIFIER_GAS: CosemParser(ValueParser(str)), obis.HOURLY_GAS_METER_READING: MBusParser( - ValueParser(timestamp), ValueParser(Decimal) - ), - }, + ValueParser(timestamp), + ValueParser(Decimal) + ) + } } V5 = { - "checksum_support": True, - "objects": { + 'checksum_support': True, + 'objects': { obis.P1_MESSAGE_HEADER: CosemParser(ValueParser(str)), obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)), obis.EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), @@ -114,9 +106,10 @@ V5 = { obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), obis.LONG_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), obis.SHORT_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), - obis.POWER_EVENT_FAILURE_LOG: ProfileGenericParser( - BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS - ), + obis.POWER_EVENT_FAILURE_LOG: + ProfileGenericParser(BUFFER_TYPES, + PG_HEAD_PARSERS, + PG_UNIDENTIFIED_BUFFERTYPE_PARSERS), obis.VOLTAGE_SAG_L1_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SAG_L2_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SAG_L3_COUNT: CosemParser(ValueParser(int)), @@ -139,46 +132,38 @@ V5 = { obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: CosemParser(ValueParser(Decimal)), obis.EQUIPMENT_IDENTIFIER_GAS: CosemParser(ValueParser(str)), obis.HOURLY_GAS_METER_READING: MBusParser( - ValueParser(timestamp), ValueParser(Decimal) - ), - }, + ValueParser(timestamp), + ValueParser(Decimal) + ) + } } ALL = (V2_2, V3, V4, V5) BELGIUM_FLUVIUS = deepcopy(V5) -BELGIUM_FLUVIUS["objects"].update( - { - obis.BELGIUM_HOURLY_GAS_METER_READING: MBusParser( - ValueParser(timestamp), ValueParser(Decimal) - ) - } -) +BELGIUM_FLUVIUS['objects'].update({ + obis.BELGIUM_HOURLY_GAS_METER_READING: MBusParser( + ValueParser(timestamp), + ValueParser(Decimal) + ) +}) LUXEMBOURG_SMARTY = deepcopy(V5) -LUXEMBOURG_SMARTY["objects"].update( - { - obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), - obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser( - ValueParser(Decimal) - ), - obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser( - ValueParser(Decimal) - ), - } -) +LUXEMBOURG_SMARTY['objects'].update({ + obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), + obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), + obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), +}) # Source: https://www.energiforetagen.se/globalassets/energiforetagen/det-erbjuder-vi/kurser-och-konferenser/elnat/branschrekommendation-lokalt-granssnitt-v2_0-201912.pdf SWEDEN = { - "checksum_support": True, - "objects": { + 'checksum_support': True, + 'objects': { obis.P1_MESSAGE_HEADER: CosemParser(ValueParser(str)), obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)), obis.SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), - obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser( - ValueParser(Decimal) - ), + obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), obis.CURRENT_ELECTRICITY_USAGE: CosemParser(ValueParser(Decimal)), obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: CosemParser(ValueParser(Decimal)), @@ -193,19 +178,16 @@ SWEDEN = { obis.INSTANTANEOUS_CURRENT_L1: CosemParser(ValueParser(Decimal)), obis.INSTANTANEOUS_CURRENT_L2: CosemParser(ValueParser(Decimal)), obis.INSTANTANEOUS_CURRENT_L3: CosemParser(ValueParser(Decimal)), - }, + } } + Q3D = { "checksum_support": False, "objects": { obis.Q3D_EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), - obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser( - ValueParser(Decimal) - ), - obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser( - ValueParser(Decimal) - ), + obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), + obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: 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)), From 8e2bdd32792949fc35898c94d402570aac509287 Mon Sep 17 00:00:00 2001 From: Gunnar Klauberg Date: Fri, 12 Nov 2021 19:23:38 +0000 Subject: [PATCH 46/85] clean-up re-format --- dsmr_parser/telegram_specifications.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dsmr_parser/telegram_specifications.py b/dsmr_parser/telegram_specifications.py index bb82868..83f6bd8 100644 --- a/dsmr_parser/telegram_specifications.py +++ b/dsmr_parser/telegram_specifications.py @@ -9,6 +9,7 @@ from dsmr_parser.profile_generic_specifications import BUFFER_TYPES, PG_HEAD_PAR """ dsmr_parser.telegram_specifications ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + This module contains DSMR telegram specifications. Each specifications describes how the telegram lines are parsed. """ From 3092ba8b1f0a1895d12b7de2a3de7f4e4a538dd3 Mon Sep 17 00:00:00 2001 From: Gunnar Klauberg Date: Fri, 12 Nov 2021 19:24:44 +0000 Subject: [PATCH 47/85] clean-up re-format --- dsmr_parser/clients/protocol.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index 6a7a17e..88b25fb 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -29,13 +29,13 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): elif dsmr_version == '5B': specification = telegram_specifications.BELGIUM_FLUVIUS serial_settings = SERIAL_SETTINGS_V5 - elif dsmr_version == '5L': + elif dsmr_version == "5L": specification = telegram_specifications.LUXEMBOURG_SMARTY serial_settings = SERIAL_SETTINGS_V5 - elif dsmr_version == '5S': + elif dsmr_version == "5S": specification = telegram_specifications.SWEDEN serial_settings = SERIAL_SETTINGS_V5 - elif dsmr_version == 'Q3D': + elif dsmr_version == 'Q3D": specification = telegram_specifications.Q3D serial_settings = SERIAL_SETTINGS_V5 else: From c1202f33e9dc90283acaf8ad884aaabb83ca03d6 Mon Sep 17 00:00:00 2001 From: Gunnar Klauberg Date: Fri, 12 Nov 2021 20:12:47 +0000 Subject: [PATCH 48/85] clean-up re-format --- dsmr_parser/clients/protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index 88b25fb..45f6d48 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -35,7 +35,7 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): elif dsmr_version == "5S": specification = telegram_specifications.SWEDEN serial_settings = SERIAL_SETTINGS_V5 - elif dsmr_version == 'Q3D": + elif dsmr_version == "Q3D": specification = telegram_specifications.Q3D serial_settings = SERIAL_SETTINGS_V5 else: From 4073917d3e269d6fb7175fbbd18e61fb835010f8 Mon Sep 17 00:00:00 2001 From: Gunnar Klauberg Date: Sun, 14 Nov 2021 21:29:24 +0000 Subject: [PATCH 49/85] decode latin1 and added ELECTRICITY_EXPORTED_TOTAL --- dsmr_parser/clients/protocol.py | 68 ++++++++------ dsmr_parser/obis_name_mapping.py | 101 ++++++++++----------- dsmr_parser/obis_references.py | 118 +++++++++++++------------ dsmr_parser/telegram_specifications.py | 99 ++++++++++++--------- 4 files changed, 211 insertions(+), 175 deletions(-) diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index 45f6d48..3b4c062 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -10,23 +10,26 @@ from dsmr_parser import telegram_specifications from dsmr_parser.clients.telegram_buffer import TelegramBuffer from dsmr_parser.exceptions import ParseError, InvalidChecksumError from dsmr_parser.parsers import TelegramParser -from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \ - SERIAL_SETTINGS_V4, SERIAL_SETTINGS_V5 +from dsmr_parser.clients.settings import ( + SERIAL_SETTINGS_V2_2, + SERIAL_SETTINGS_V4, + SERIAL_SETTINGS_V5, +) def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): """Creates a DSMR asyncio protocol.""" - if dsmr_version == '2.2': + if dsmr_version == "2.2": specification = telegram_specifications.V2_2 serial_settings = SERIAL_SETTINGS_V2_2 - elif dsmr_version == '4': + elif dsmr_version == "4": specification = telegram_specifications.V4 serial_settings = SERIAL_SETTINGS_V4 - elif dsmr_version == '5': + elif dsmr_version == "5": specification = telegram_specifications.V5 serial_settings = SERIAL_SETTINGS_V5 - elif dsmr_version == '5B': + elif dsmr_version == "5B": specification = telegram_specifications.BELGIUM_FLUVIUS serial_settings = SERIAL_SETTINGS_V5 elif dsmr_version == "5L": @@ -39,11 +42,17 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): specification = telegram_specifications.Q3D serial_settings = SERIAL_SETTINGS_V5 else: - raise NotImplementedError("No telegram parser found for version: %s", - dsmr_version) + raise NotImplementedError( + "No telegram parser found for version: %s", dsmr_version + ) - protocol = partial(DSMRProtocol, loop, TelegramParser(specification), - telegram_callback=telegram_callback, **kwargs) + protocol = partial( + DSMRProtocol, + loop, + TelegramParser(specification), + telegram_callback=telegram_callback, + **kwargs + ) return protocol, serial_settings @@ -51,22 +60,26 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): def create_dsmr_reader(port, dsmr_version, telegram_callback, loop=None): """Creates a DSMR asyncio protocol coroutine using serial port.""" protocol, serial_settings = create_dsmr_protocol( - dsmr_version, telegram_callback, loop=None) - serial_settings['url'] = port + dsmr_version, telegram_callback, loop=None + ) + serial_settings["url"] = port conn = create_serial_connection(loop, protocol, **serial_settings) return conn -def create_tcp_dsmr_reader(host, port, dsmr_version, - telegram_callback, loop=None, - keep_alive_interval=None): +def create_tcp_dsmr_reader( + host, port, dsmr_version, telegram_callback, loop=None, keep_alive_interval=None +): """Creates a DSMR asyncio protocol coroutine using TCP connection.""" if not loop: loop = asyncio.get_event_loop() protocol, _ = create_dsmr_protocol( - dsmr_version, telegram_callback, loop=loop, - keep_alive_interval=keep_alive_interval) + dsmr_version, + telegram_callback, + loop=loop, + keep_alive_interval=keep_alive_interval, + ) conn = loop.create_connection(protocol, host, port) return conn @@ -77,8 +90,9 @@ class DSMRProtocol(asyncio.Protocol): transport = None telegram_callback = None - def __init__(self, loop, telegram_parser, - telegram_callback=None, keep_alive_interval=None): + def __init__( + self, loop, telegram_parser, telegram_callback=None, keep_alive_interval=None + ): """Initialize class.""" self.loop = loop self.log = logging.getLogger(__name__) @@ -95,16 +109,16 @@ class DSMRProtocol(asyncio.Protocol): def connection_made(self, transport): """Just logging for now.""" self.transport = transport - self.log.debug('connected') + self.log.debug("connected") self._active = False if self.loop and self._keep_alive_interval: self.loop.call_later(self._keep_alive_interval, self.keep_alive) def data_received(self, data): """Add incoming data to buffer.""" - data = data.decode('ascii', errors='ignore') + data = data.decode("latin1") self._active = True - self.log.debug('received data: %s', data) + self.log.debug("received data: %s", data) self.telegram_buffer.append(data) for telegram in self.telegram_buffer.get_all(): @@ -112,26 +126,26 @@ class DSMRProtocol(asyncio.Protocol): def keep_alive(self): if self._active: - self.log.debug('keep-alive checked') + self.log.debug("keep-alive checked") self._active = False if self.loop: self.loop.call_later(self._keep_alive_interval, self.keep_alive) else: - self.log.warning('keep-alive check failed') + self.log.warning("keep-alive check failed") if self.transport: self.transport.close() def connection_lost(self, exc): """Stop when connection is lost.""" if exc: - self.log.exception('disconnected due to exception', exc_info=exc) + self.log.exception("disconnected due to exception", exc_info=exc) else: - self.log.info('disconnected because of close/abort.') + self.log.info("disconnected because of close/abort.") self._closed.set() def handle_telegram(self, telegram): """Send off parsed telegram to handling callback.""" - self.log.debug('got telegram: %s', telegram) + self.log.debug("got telegram: %s", telegram) try: parsed_telegram = self.telegram_parser.parse(telegram) diff --git a/dsmr_parser/obis_name_mapping.py b/dsmr_parser/obis_name_mapping.py index fcba570..5a05f00 100644 --- a/dsmr_parser/obis_name_mapping.py +++ b/dsmr_parser/obis_name_mapping.py @@ -8,56 +8,57 @@ This module contains a mapping of obis references to names. """ EN = { - obis.P1_MESSAGE_HEADER: 'P1_MESSAGE_HEADER', - obis.P1_MESSAGE_TIMESTAMP: 'P1_MESSAGE_TIMESTAMP', - obis.ELECTRICITY_IMPORTED_TOTAL: 'ELECTRICITY_IMPORTED_TOTAL', - obis.ELECTRICITY_USED_TARIFF_1: 'ELECTRICITY_USED_TARIFF_1', - obis.ELECTRICITY_USED_TARIFF_2: 'ELECTRICITY_USED_TARIFF_2', - obis.ELECTRICITY_DELIVERED_TARIFF_1: 'ELECTRICITY_DELIVERED_TARIFF_1', - obis.ELECTRICITY_DELIVERED_TARIFF_2: 'ELECTRICITY_DELIVERED_TARIFF_2', - obis.ELECTRICITY_ACTIVE_TARIFF: 'ELECTRICITY_ACTIVE_TARIFF', - obis.EQUIPMENT_IDENTIFIER: 'EQUIPMENT_IDENTIFIER', - obis.CURRENT_ELECTRICITY_USAGE: 'CURRENT_ELECTRICITY_USAGE', - obis.CURRENT_ELECTRICITY_DELIVERY: 'CURRENT_ELECTRICITY_DELIVERY', - obis.LONG_POWER_FAILURE_COUNT: 'LONG_POWER_FAILURE_COUNT', - obis.SHORT_POWER_FAILURE_COUNT: 'SHORT_POWER_FAILURE_COUNT', - obis.POWER_EVENT_FAILURE_LOG: 'POWER_EVENT_FAILURE_LOG', - obis.VOLTAGE_SAG_L1_COUNT: 'VOLTAGE_SAG_L1_COUNT', - obis.VOLTAGE_SAG_L2_COUNT: 'VOLTAGE_SAG_L2_COUNT', - obis.VOLTAGE_SAG_L3_COUNT: 'VOLTAGE_SAG_L3_COUNT', - obis.VOLTAGE_SWELL_L1_COUNT: 'VOLTAGE_SWELL_L1_COUNT', - obis.VOLTAGE_SWELL_L2_COUNT: 'VOLTAGE_SWELL_L2_COUNT', - obis.VOLTAGE_SWELL_L3_COUNT: 'VOLTAGE_SWELL_L3_COUNT', - obis.INSTANTANEOUS_VOLTAGE_L1: 'INSTANTANEOUS_VOLTAGE_L1', - obis.INSTANTANEOUS_VOLTAGE_L2: 'INSTANTANEOUS_VOLTAGE_L2', - obis.INSTANTANEOUS_VOLTAGE_L3: 'INSTANTANEOUS_VOLTAGE_L3', - obis.INSTANTANEOUS_CURRENT_L1: 'INSTANTANEOUS_CURRENT_L1', - obis.INSTANTANEOUS_CURRENT_L2: 'INSTANTANEOUS_CURRENT_L2', - obis.INSTANTANEOUS_CURRENT_L3: 'INSTANTANEOUS_CURRENT_L3', - obis.TEXT_MESSAGE_CODE: 'TEXT_MESSAGE_CODE', - obis.TEXT_MESSAGE: 'TEXT_MESSAGE', - obis.DEVICE_TYPE: 'DEVICE_TYPE', - obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: 'INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE', - obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE: 'INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE', - obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE: 'INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE', - obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE: 'INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE', - obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE: 'INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE', - obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: 'INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE', - obis.EQUIPMENT_IDENTIFIER_GAS: 'EQUIPMENT_IDENTIFIER_GAS', - obis.HOURLY_GAS_METER_READING: 'HOURLY_GAS_METER_READING', - obis.GAS_METER_READING: 'GAS_METER_READING', - obis.ACTUAL_TRESHOLD_ELECTRICITY: 'ACTUAL_TRESHOLD_ELECTRICITY', - obis.ACTUAL_SWITCH_POSITION: 'ACTUAL_SWITCH_POSITION', - obis.VALVE_POSITION_GAS: 'VALVE_POSITION_GAS', - obis.BELGIUM_HOURLY_GAS_METER_READING: 'BELGIUM_HOURLY_GAS_METER_READING', - obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: 'LUXEMBOURG_EQUIPMENT_IDENTIFIER', - obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: 'LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL', - obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: 'LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL', - obis.SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL: 'SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL', - obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: 'SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL', - obis.Q3D_EQUIPMENT_IDENTIFIER: 'Q3D_EQUIPMENT_IDENTIFIER', - obis.Q3D_EQUIPMENT_STATE: 'Q3D_EQUIPMENT_STATE', - obis.Q3D_EQUIPMENT_SERIALNUMBER: 'Q3D_EQUIPMENT_SERIALNUMBER', + obis.P1_MESSAGE_HEADER: "P1_MESSAGE_HEADER", + obis.P1_MESSAGE_TIMESTAMP: "P1_MESSAGE_TIMESTAMP", + obis.ELECTRICITY_IMPORTED_TOTAL: "ELECTRICITY_IMPORTED_TOTAL", + obis.ELECTRICITY_USED_TARIFF_1: "ELECTRICITY_USED_TARIFF_1", + obis.ELECTRICITY_USED_TARIFF_2: "ELECTRICITY_USED_TARIFF_2", + obis.ELECTRICITY_EXPORTED_TOTAL: "ELECTRICITY_EXPORTED_TOTAL", + obis.ELECTRICITY_DELIVERED_TARIFF_1: "ELECTRICITY_DELIVERED_TARIFF_1", + obis.ELECTRICITY_DELIVERED_TARIFF_2: "ELECTRICITY_DELIVERED_TARIFF_2", + obis.ELECTRICITY_ACTIVE_TARIFF: "ELECTRICITY_ACTIVE_TARIFF", + obis.EQUIPMENT_IDENTIFIER: "EQUIPMENT_IDENTIFIER", + obis.CURRENT_ELECTRICITY_USAGE: "CURRENT_ELECTRICITY_USAGE", + obis.CURRENT_ELECTRICITY_DELIVERY: "CURRENT_ELECTRICITY_DELIVERY", + obis.LONG_POWER_FAILURE_COUNT: "LONG_POWER_FAILURE_COUNT", + obis.SHORT_POWER_FAILURE_COUNT: "SHORT_POWER_FAILURE_COUNT", + obis.POWER_EVENT_FAILURE_LOG: "POWER_EVENT_FAILURE_LOG", + obis.VOLTAGE_SAG_L1_COUNT: "VOLTAGE_SAG_L1_COUNT", + obis.VOLTAGE_SAG_L2_COUNT: "VOLTAGE_SAG_L2_COUNT", + obis.VOLTAGE_SAG_L3_COUNT: "VOLTAGE_SAG_L3_COUNT", + obis.VOLTAGE_SWELL_L1_COUNT: "VOLTAGE_SWELL_L1_COUNT", + obis.VOLTAGE_SWELL_L2_COUNT: "VOLTAGE_SWELL_L2_COUNT", + obis.VOLTAGE_SWELL_L3_COUNT: "VOLTAGE_SWELL_L3_COUNT", + obis.INSTANTANEOUS_VOLTAGE_L1: "INSTANTANEOUS_VOLTAGE_L1", + obis.INSTANTANEOUS_VOLTAGE_L2: "INSTANTANEOUS_VOLTAGE_L2", + obis.INSTANTANEOUS_VOLTAGE_L3: "INSTANTANEOUS_VOLTAGE_L3", + obis.INSTANTANEOUS_CURRENT_L1: "INSTANTANEOUS_CURRENT_L1", + obis.INSTANTANEOUS_CURRENT_L2: "INSTANTANEOUS_CURRENT_L2", + obis.INSTANTANEOUS_CURRENT_L3: "INSTANTANEOUS_CURRENT_L3", + obis.TEXT_MESSAGE_CODE: "TEXT_MESSAGE_CODE", + obis.TEXT_MESSAGE: "TEXT_MESSAGE", + obis.DEVICE_TYPE: "DEVICE_TYPE", + obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: "INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE", + obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE: "INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE", + obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE: "INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE", + obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE: "INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE", + obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE: "INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE", + obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: "INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE", + obis.EQUIPMENT_IDENTIFIER_GAS: "EQUIPMENT_IDENTIFIER_GAS", + obis.HOURLY_GAS_METER_READING: "HOURLY_GAS_METER_READING", + obis.GAS_METER_READING: "GAS_METER_READING", + obis.ACTUAL_TRESHOLD_ELECTRICITY: "ACTUAL_TRESHOLD_ELECTRICITY", + obis.ACTUAL_SWITCH_POSITION: "ACTUAL_SWITCH_POSITION", + obis.VALVE_POSITION_GAS: "VALVE_POSITION_GAS", + obis.BELGIUM_HOURLY_GAS_METER_READING: "BELGIUM_HOURLY_GAS_METER_READING", + obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: "LUXEMBOURG_EQUIPMENT_IDENTIFIER", + obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: "LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL", + obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: "LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL", + obis.SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL: "SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL", + obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: "SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL", + obis.Q3D_EQUIPMENT_IDENTIFIER: "Q3D_EQUIPMENT_IDENTIFIER", + obis.Q3D_EQUIPMENT_STATE: "Q3D_EQUIPMENT_STATE", + obis.Q3D_EQUIPMENT_SERIALNUMBER: "Q3D_EQUIPMENT_SERIALNUMBER", } REVERSE_EN = dict([(v, k) for k, v in EN.items()]) diff --git a/dsmr_parser/obis_references.py b/dsmr_parser/obis_references.py index 3808848..02e1e33 100644 --- a/dsmr_parser/obis_references.py +++ b/dsmr_parser/obis_references.py @@ -6,68 +6,76 @@ refactored to full line signatures to maintain backwards compatibility. Might be refactored in a backwards incompatible way as soon as proper telegram objects are introduced. """ -P1_MESSAGE_HEADER = r'\d-\d:0\.2\.8.+?\r\n' -P1_MESSAGE_TIMESTAMP = r'\d-\d:1\.0\.0.+?\r\n' -ELECTRICITY_IMPORTED_TOTAL = r'\d-\d:1\.8\.0.+?\r\n' -ELECTRICITY_USED_TARIFF_1 = r'\d-\d:1\.8\.1.+?\r\n' -ELECTRICITY_USED_TARIFF_2 = r'\d-\d:1\.8\.2.+?\r\n' -ELECTRICITY_DELIVERED_TARIFF_1 = r'\d-\d:2\.8\.1.+?\r\n' -ELECTRICITY_DELIVERED_TARIFF_2 = r'\d-\d:2\.8\.2.+?\r\n' -ELECTRICITY_ACTIVE_TARIFF = r'\d-\d:96\.14\.0.+?\r\n' -EQUIPMENT_IDENTIFIER = r'\d-\d:96\.1\.1.+?\r\n' -CURRENT_ELECTRICITY_USAGE = r'\d-\d:1\.7\.0.+?\r\n' -CURRENT_ELECTRICITY_DELIVERY = r'\d-\d:2\.7\.0.+?\r\n' -LONG_POWER_FAILURE_COUNT = r'96\.7\.9.+?\r\n' -SHORT_POWER_FAILURE_COUNT = r'96\.7\.21.+?\r\n' -POWER_EVENT_FAILURE_LOG = r'99\.97\.0.+?\r\n' -VOLTAGE_SAG_L1_COUNT = r'\d-\d:32\.32\.0.+?\r\n' -VOLTAGE_SAG_L2_COUNT = r'\d-\d:52\.32\.0.+?\r\n' -VOLTAGE_SAG_L3_COUNT = r'\d-\d:72\.32\.0.+?\r\n' -VOLTAGE_SWELL_L1_COUNT = r'\d-\d:32\.36\.0.+?\r\n' -VOLTAGE_SWELL_L2_COUNT = r'\d-\d:52\.36\.0.+?\r\n' -VOLTAGE_SWELL_L3_COUNT = r'\d-\d:72\.36\.0.+?\r\n' -INSTANTANEOUS_VOLTAGE_L1 = r'\d-\d:32\.7\.0.+?\r\n' -INSTANTANEOUS_VOLTAGE_L2 = r'\d-\d:52\.7\.0.+?\r\n' -INSTANTANEOUS_VOLTAGE_L3 = r'\d-\d:72\.7\.0.+?\r\n' -INSTANTANEOUS_CURRENT_L1 = r'\d-\d:31\.7\.0.+?\r\n' -INSTANTANEOUS_CURRENT_L2 = r'\d-\d:51\.7\.0.+?\r\n' -INSTANTANEOUS_CURRENT_L3 = r'\d-\d:71\.7\.0.+?\r\n' -TEXT_MESSAGE_CODE = r'\d-\d:96\.13\.1.+?\r\n' -TEXT_MESSAGE = r'\d-\d:96\.13\.0.+?\r\n' -DEVICE_TYPE = r'\d-\d:24\.1\.0.+?\r\n' -INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE = r'\d-\d:21\.7\.0.+?\r\n' -INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE = r'\d-\d:41\.7\.0.+?\r\n' -INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE = r'\d-\d:61\.7\.0.+?\r\n' -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' +P1_MESSAGE_HEADER = r"\d-\d:0\.2\.8.+?\r\n" +P1_MESSAGE_TIMESTAMP = r"\d-\d:1\.0\.0.+?\r\n" +ELECTRICITY_IMPORTED_TOTAL = r"\d-\d:1\.8\.0.+?\r\n" +ELECTRICITY_USED_TARIFF_1 = r"\d-\d:1\.8\.1.+?\r\n" +ELECTRICITY_USED_TARIFF_2 = r"\d-\d:1\.8\.2.+?\r\n" +ELECTRICITY_EXPORTED_TOTAL = r"\d-\d:2\.8\.0.+?\r\n" +ELECTRICITY_DELIVERED_TARIFF_1 = r"\d-\d:2\.8\.1.+?\r\n" +ELECTRICITY_DELIVERED_TARIFF_2 = r"\d-\d:2\.8\.2.+?\r\n" +ELECTRICITY_ACTIVE_TARIFF = r"\d-\d:96\.14\.0.+?\r\n" +EQUIPMENT_IDENTIFIER = r"\d-\d:96\.1\.1.+?\r\n" +CURRENT_ELECTRICITY_USAGE = r"\d-\d:1\.7\.0.+?\r\n" +CURRENT_ELECTRICITY_DELIVERY = r"\d-\d:2\.7\.0.+?\r\n" +LONG_POWER_FAILURE_COUNT = r"96\.7\.9.+?\r\n" +SHORT_POWER_FAILURE_COUNT = r"96\.7\.21.+?\r\n" +POWER_EVENT_FAILURE_LOG = r"99\.97\.0.+?\r\n" +VOLTAGE_SAG_L1_COUNT = r"\d-\d:32\.32\.0.+?\r\n" +VOLTAGE_SAG_L2_COUNT = r"\d-\d:52\.32\.0.+?\r\n" +VOLTAGE_SAG_L3_COUNT = r"\d-\d:72\.32\.0.+?\r\n" +VOLTAGE_SWELL_L1_COUNT = r"\d-\d:32\.36\.0.+?\r\n" +VOLTAGE_SWELL_L2_COUNT = r"\d-\d:52\.36\.0.+?\r\n" +VOLTAGE_SWELL_L3_COUNT = r"\d-\d:72\.36\.0.+?\r\n" +INSTANTANEOUS_VOLTAGE_L1 = r"\d-\d:32\.7\.0.+?\r\n" +INSTANTANEOUS_VOLTAGE_L2 = r"\d-\d:52\.7\.0.+?\r\n" +INSTANTANEOUS_VOLTAGE_L3 = r"\d-\d:72\.7\.0.+?\r\n" +INSTANTANEOUS_CURRENT_L1 = r"\d-\d:31\.7\.0.+?\r\n" +INSTANTANEOUS_CURRENT_L2 = r"\d-\d:51\.7\.0.+?\r\n" +INSTANTANEOUS_CURRENT_L3 = r"\d-\d:71\.7\.0.+?\r\n" +TEXT_MESSAGE_CODE = r"\d-\d:96\.13\.1.+?\r\n" +TEXT_MESSAGE = r"\d-\d:96\.13\.0.+?\r\n" +DEVICE_TYPE = r"\d-\d:24\.1\.0.+?\r\n" +INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE = r"\d-\d:21\.7\.0.+?\r\n" +INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE = r"\d-\d:41\.7\.0.+?\r\n" +INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE = r"\d-\d:61\.7\.0.+?\r\n" +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 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' +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 -) +ELECTRICITY_USED_TARIFF_ALL = (ELECTRICITY_USED_TARIFF_1, ELECTRICITY_USED_TARIFF_2) ELECTRICITY_DELIVERED_TARIFF_ALL = ( ELECTRICITY_DELIVERED_TARIFF_1, - ELECTRICITY_DELIVERED_TARIFF_2 + ELECTRICITY_DELIVERED_TARIFF_2, ) # Alternate codes for foreign countries. -BELGIUM_HOURLY_GAS_METER_READING = r'\d-\d:24\.2\.3.+?\r\n' # Different code, same format. -LUXEMBOURG_EQUIPMENT_IDENTIFIER = r'\d-\d:42\.0\.0.+?\r\n' # Logical device name -LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL = r'\d-\d:1\.8\.0.+?\r\n' # Total imported energy register (P+) -LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = r'\d-\d:2\.8\.0.+?\r\n' # Total exported energy register (P-) -SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL = r'\d-\d:1\.8\.0.+?\r\n' # Total imported energy register (P+) -SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = r'\d-\d:2\.8\.0.+?\r\n' # Total exported energy register (P-) -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 +BELGIUM_HOURLY_GAS_METER_READING = ( + r"\d-\d:24\.2\.3.+?\r\n" # Different code, same format. +) +LUXEMBOURG_EQUIPMENT_IDENTIFIER = r"\d-\d:42\.0\.0.+?\r\n" # Logical device name +LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL = ( + r"\d-\d:1\.8\.0.+?\r\n" # Total imported energy register (P+) +) +LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = ( + r"\d-\d:2\.8\.0.+?\r\n" # Total exported energy register (P-) +) +SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL = ( + r"\d-\d:1\.8\.0.+?\r\n" # Total imported energy register (P+) +) +SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = ( + r"\d-\d:2\.8\.0.+?\r\n" # Total exported energy register (P-) +) +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/telegram_specifications.py b/dsmr_parser/telegram_specifications.py index 83f6bd8..77ea49e 100644 --- a/dsmr_parser/telegram_specifications.py +++ b/dsmr_parser/telegram_specifications.py @@ -2,9 +2,18 @@ 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, +) from dsmr_parser.value_types import timestamp -from dsmr_parser.profile_generic_specifications import BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS +from dsmr_parser.profile_generic_specifications import ( + BUFFER_TYPES, + PG_HEAD_PARSERS, + PG_UNIDENTIFIED_BUFFERTYPE_PARSERS, +) """ dsmr_parser.telegram_specifications @@ -15,8 +24,8 @@ how the telegram lines are parsed. """ V2_2 = { - 'checksum_support': False, - 'objects': { + "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)), @@ -41,14 +50,14 @@ V2_2 = { ValueParser(str), # unit, position 5 ValueParser(Decimal), # meter reading, position 6 ), - } + }, } V3 = V2_2 V4 = { - 'checksum_support': True, - 'objects': { + "checksum_support": True, + "objects": { obis.P1_MESSAGE_HEADER: CosemParser(ValueParser(str)), obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)), obis.EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), @@ -61,10 +70,9 @@ V4 = { obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), obis.SHORT_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), obis.LONG_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), - obis.POWER_EVENT_FAILURE_LOG: - ProfileGenericParser(BUFFER_TYPES, - PG_HEAD_PARSERS, - PG_UNIDENTIFIED_BUFFERTYPE_PARSERS), + obis.POWER_EVENT_FAILURE_LOG: ProfileGenericParser( + BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS + ), obis.VOLTAGE_SAG_L1_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SAG_L2_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SAG_L3_COUNT: CosemParser(ValueParser(int)), @@ -85,15 +93,14 @@ V4 = { obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: CosemParser(ValueParser(Decimal)), obis.EQUIPMENT_IDENTIFIER_GAS: CosemParser(ValueParser(str)), obis.HOURLY_GAS_METER_READING: MBusParser( - ValueParser(timestamp), - ValueParser(Decimal) - ) - } + ValueParser(timestamp), ValueParser(Decimal) + ), + }, } V5 = { - 'checksum_support': True, - 'objects': { + "checksum_support": True, + "objects": { obis.P1_MESSAGE_HEADER: CosemParser(ValueParser(str)), obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)), obis.EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), @@ -107,10 +114,9 @@ V5 = { obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), obis.LONG_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), obis.SHORT_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), - obis.POWER_EVENT_FAILURE_LOG: - ProfileGenericParser(BUFFER_TYPES, - PG_HEAD_PARSERS, - PG_UNIDENTIFIED_BUFFERTYPE_PARSERS), + obis.POWER_EVENT_FAILURE_LOG: ProfileGenericParser( + BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS + ), obis.VOLTAGE_SAG_L1_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SAG_L2_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SAG_L3_COUNT: CosemParser(ValueParser(int)), @@ -133,38 +139,46 @@ V5 = { obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: CosemParser(ValueParser(Decimal)), obis.EQUIPMENT_IDENTIFIER_GAS: CosemParser(ValueParser(str)), obis.HOURLY_GAS_METER_READING: MBusParser( - ValueParser(timestamp), - ValueParser(Decimal) - ) - } + ValueParser(timestamp), ValueParser(Decimal) + ), + }, } ALL = (V2_2, V3, V4, V5) BELGIUM_FLUVIUS = deepcopy(V5) -BELGIUM_FLUVIUS['objects'].update({ - obis.BELGIUM_HOURLY_GAS_METER_READING: MBusParser( - ValueParser(timestamp), - ValueParser(Decimal) - ) -}) +BELGIUM_FLUVIUS["objects"].update( + { + obis.BELGIUM_HOURLY_GAS_METER_READING: MBusParser( + ValueParser(timestamp), ValueParser(Decimal) + ) + } +) LUXEMBOURG_SMARTY = deepcopy(V5) -LUXEMBOURG_SMARTY['objects'].update({ - obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), - obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), - obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), -}) +LUXEMBOURG_SMARTY["objects"].update( + { + obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), + obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser( + ValueParser(Decimal) + ), + obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser( + ValueParser(Decimal) + ), + } +) # Source: https://www.energiforetagen.se/globalassets/energiforetagen/det-erbjuder-vi/kurser-och-konferenser/elnat/branschrekommendation-lokalt-granssnitt-v2_0-201912.pdf SWEDEN = { - 'checksum_support': True, - 'objects': { + "checksum_support": True, + "objects": { obis.P1_MESSAGE_HEADER: CosemParser(ValueParser(str)), obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)), obis.SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), - obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), + obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser( + ValueParser(Decimal) + ), obis.CURRENT_ELECTRICITY_USAGE: CosemParser(ValueParser(Decimal)), obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: CosemParser(ValueParser(Decimal)), @@ -179,16 +193,15 @@ SWEDEN = { obis.INSTANTANEOUS_CURRENT_L1: CosemParser(ValueParser(Decimal)), obis.INSTANTANEOUS_CURRENT_L2: CosemParser(ValueParser(Decimal)), obis.INSTANTANEOUS_CURRENT_L3: CosemParser(ValueParser(Decimal)), - } + }, } - Q3D = { "checksum_support": False, "objects": { obis.Q3D_EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), - obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), - obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), + obis.ELECTRICITY_IMPORTED_TOTAL: CosemParser(ValueParser(Decimal)), + obis.ELECTRICITY_EXPORTED_TOTAL: 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)), From df59f3a4989bacb70e8748b4ad56f51b14c8f011 Mon Sep 17 00:00:00 2001 From: Gunnar Klauberg Date: Sun, 14 Nov 2021 21:49:43 +0000 Subject: [PATCH 50/85] decode latin1 and added ELECTRICITY_EXPORTED_TOTAL --- dsmr_parser/clients/protocol.py | 66 ++++++-------- dsmr_parser/obis_name_mapping.py | 102 ++++++++++----------- dsmr_parser/obis_references.py | 119 ++++++++++++------------- dsmr_parser/telegram_specifications.py | 94 +++++++++---------- 4 files changed, 173 insertions(+), 208 deletions(-) diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index 3b4c062..208ed1b 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -10,26 +10,23 @@ from dsmr_parser import telegram_specifications from dsmr_parser.clients.telegram_buffer import TelegramBuffer from dsmr_parser.exceptions import ParseError, InvalidChecksumError from dsmr_parser.parsers import TelegramParser -from dsmr_parser.clients.settings import ( - SERIAL_SETTINGS_V2_2, - SERIAL_SETTINGS_V4, - SERIAL_SETTINGS_V5, -) +from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \ + SERIAL_SETTINGS_V4, SERIAL_SETTINGS_V5 def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): """Creates a DSMR asyncio protocol.""" - if dsmr_version == "2.2": + if dsmr_version == '2.2': specification = telegram_specifications.V2_2 serial_settings = SERIAL_SETTINGS_V2_2 - elif dsmr_version == "4": + elif dsmr_version == '4': specification = telegram_specifications.V4 serial_settings = SERIAL_SETTINGS_V4 - elif dsmr_version == "5": + elif dsmr_version == '5': specification = telegram_specifications.V5 serial_settings = SERIAL_SETTINGS_V5 - elif dsmr_version == "5B": + elif dsmr_version == '5B': specification = telegram_specifications.BELGIUM_FLUVIUS serial_settings = SERIAL_SETTINGS_V5 elif dsmr_version == "5L": @@ -42,17 +39,11 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): specification = telegram_specifications.Q3D serial_settings = SERIAL_SETTINGS_V5 else: - raise NotImplementedError( - "No telegram parser found for version: %s", dsmr_version - ) + raise NotImplementedError("No telegram parser found for version: %s", + dsmr_version) - protocol = partial( - DSMRProtocol, - loop, - TelegramParser(specification), - telegram_callback=telegram_callback, - **kwargs - ) + protocol = partial(DSMRProtocol, loop, TelegramParser(specification), + telegram_callback=telegram_callback, **kwargs) return protocol, serial_settings @@ -60,26 +51,22 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): def create_dsmr_reader(port, dsmr_version, telegram_callback, loop=None): """Creates a DSMR asyncio protocol coroutine using serial port.""" protocol, serial_settings = create_dsmr_protocol( - dsmr_version, telegram_callback, loop=None - ) - serial_settings["url"] = port + dsmr_version, telegram_callback, loop=None) + serial_settings['url'] = port conn = create_serial_connection(loop, protocol, **serial_settings) return conn -def create_tcp_dsmr_reader( - host, port, dsmr_version, telegram_callback, loop=None, keep_alive_interval=None -): +def create_tcp_dsmr_reader(host, port, dsmr_version, + telegram_callback, loop=None, + keep_alive_interval=None): """Creates a DSMR asyncio protocol coroutine using TCP connection.""" if not loop: loop = asyncio.get_event_loop() protocol, _ = create_dsmr_protocol( - dsmr_version, - telegram_callback, - loop=loop, - keep_alive_interval=keep_alive_interval, - ) + dsmr_version, telegram_callback, loop=loop, + keep_alive_interval=keep_alive_interval) conn = loop.create_connection(protocol, host, port) return conn @@ -90,9 +77,8 @@ class DSMRProtocol(asyncio.Protocol): transport = None telegram_callback = None - def __init__( - self, loop, telegram_parser, telegram_callback=None, keep_alive_interval=None - ): + def __init__(self, loop, telegram_parser, + telegram_callback=None, keep_alive_interval=None): """Initialize class.""" self.loop = loop self.log = logging.getLogger(__name__) @@ -109,7 +95,7 @@ class DSMRProtocol(asyncio.Protocol): def connection_made(self, transport): """Just logging for now.""" self.transport = transport - self.log.debug("connected") + self.log.debug('connected') self._active = False if self.loop and self._keep_alive_interval: self.loop.call_later(self._keep_alive_interval, self.keep_alive) @@ -118,7 +104,7 @@ class DSMRProtocol(asyncio.Protocol): """Add incoming data to buffer.""" data = data.decode("latin1") self._active = True - self.log.debug("received data: %s", data) + self.log.debug('received data: %s', data) self.telegram_buffer.append(data) for telegram in self.telegram_buffer.get_all(): @@ -126,26 +112,26 @@ class DSMRProtocol(asyncio.Protocol): def keep_alive(self): if self._active: - self.log.debug("keep-alive checked") + self.log.debug('keep-alive checked') self._active = False if self.loop: self.loop.call_later(self._keep_alive_interval, self.keep_alive) else: - self.log.warning("keep-alive check failed") + self.log.warning('keep-alive check failed') if self.transport: self.transport.close() def connection_lost(self, exc): """Stop when connection is lost.""" if exc: - self.log.exception("disconnected due to exception", exc_info=exc) + self.log.exception('disconnected due to exception', exc_info=exc) else: - self.log.info("disconnected because of close/abort.") + self.log.info('disconnected because of close/abort.') self._closed.set() def handle_telegram(self, telegram): """Send off parsed telegram to handling callback.""" - self.log.debug("got telegram: %s", telegram) + self.log.debug('got telegram: %s', telegram) try: parsed_telegram = self.telegram_parser.parse(telegram) diff --git a/dsmr_parser/obis_name_mapping.py b/dsmr_parser/obis_name_mapping.py index 5a05f00..87c720d 100644 --- a/dsmr_parser/obis_name_mapping.py +++ b/dsmr_parser/obis_name_mapping.py @@ -8,57 +8,57 @@ This module contains a mapping of obis references to names. """ EN = { - obis.P1_MESSAGE_HEADER: "P1_MESSAGE_HEADER", - obis.P1_MESSAGE_TIMESTAMP: "P1_MESSAGE_TIMESTAMP", - obis.ELECTRICITY_IMPORTED_TOTAL: "ELECTRICITY_IMPORTED_TOTAL", - obis.ELECTRICITY_USED_TARIFF_1: "ELECTRICITY_USED_TARIFF_1", - obis.ELECTRICITY_USED_TARIFF_2: "ELECTRICITY_USED_TARIFF_2", - obis.ELECTRICITY_EXPORTED_TOTAL: "ELECTRICITY_EXPORTED_TOTAL", - obis.ELECTRICITY_DELIVERED_TARIFF_1: "ELECTRICITY_DELIVERED_TARIFF_1", - obis.ELECTRICITY_DELIVERED_TARIFF_2: "ELECTRICITY_DELIVERED_TARIFF_2", - obis.ELECTRICITY_ACTIVE_TARIFF: "ELECTRICITY_ACTIVE_TARIFF", - obis.EQUIPMENT_IDENTIFIER: "EQUIPMENT_IDENTIFIER", - obis.CURRENT_ELECTRICITY_USAGE: "CURRENT_ELECTRICITY_USAGE", - obis.CURRENT_ELECTRICITY_DELIVERY: "CURRENT_ELECTRICITY_DELIVERY", - obis.LONG_POWER_FAILURE_COUNT: "LONG_POWER_FAILURE_COUNT", - obis.SHORT_POWER_FAILURE_COUNT: "SHORT_POWER_FAILURE_COUNT", - obis.POWER_EVENT_FAILURE_LOG: "POWER_EVENT_FAILURE_LOG", - obis.VOLTAGE_SAG_L1_COUNT: "VOLTAGE_SAG_L1_COUNT", - obis.VOLTAGE_SAG_L2_COUNT: "VOLTAGE_SAG_L2_COUNT", - obis.VOLTAGE_SAG_L3_COUNT: "VOLTAGE_SAG_L3_COUNT", - obis.VOLTAGE_SWELL_L1_COUNT: "VOLTAGE_SWELL_L1_COUNT", - obis.VOLTAGE_SWELL_L2_COUNT: "VOLTAGE_SWELL_L2_COUNT", - obis.VOLTAGE_SWELL_L3_COUNT: "VOLTAGE_SWELL_L3_COUNT", - obis.INSTANTANEOUS_VOLTAGE_L1: "INSTANTANEOUS_VOLTAGE_L1", - obis.INSTANTANEOUS_VOLTAGE_L2: "INSTANTANEOUS_VOLTAGE_L2", - obis.INSTANTANEOUS_VOLTAGE_L3: "INSTANTANEOUS_VOLTAGE_L3", - obis.INSTANTANEOUS_CURRENT_L1: "INSTANTANEOUS_CURRENT_L1", - obis.INSTANTANEOUS_CURRENT_L2: "INSTANTANEOUS_CURRENT_L2", - obis.INSTANTANEOUS_CURRENT_L3: "INSTANTANEOUS_CURRENT_L3", - obis.TEXT_MESSAGE_CODE: "TEXT_MESSAGE_CODE", - obis.TEXT_MESSAGE: "TEXT_MESSAGE", - obis.DEVICE_TYPE: "DEVICE_TYPE", - obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: "INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE", - obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE: "INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE", - obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE: "INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE", - obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE: "INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE", - obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE: "INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE", - obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: "INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE", - obis.EQUIPMENT_IDENTIFIER_GAS: "EQUIPMENT_IDENTIFIER_GAS", - obis.HOURLY_GAS_METER_READING: "HOURLY_GAS_METER_READING", - obis.GAS_METER_READING: "GAS_METER_READING", - obis.ACTUAL_TRESHOLD_ELECTRICITY: "ACTUAL_TRESHOLD_ELECTRICITY", - obis.ACTUAL_SWITCH_POSITION: "ACTUAL_SWITCH_POSITION", - obis.VALVE_POSITION_GAS: "VALVE_POSITION_GAS", - obis.BELGIUM_HOURLY_GAS_METER_READING: "BELGIUM_HOURLY_GAS_METER_READING", - obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: "LUXEMBOURG_EQUIPMENT_IDENTIFIER", - obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: "LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL", - obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: "LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL", - obis.SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL: "SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL", - obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: "SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL", - obis.Q3D_EQUIPMENT_IDENTIFIER: "Q3D_EQUIPMENT_IDENTIFIER", - obis.Q3D_EQUIPMENT_STATE: "Q3D_EQUIPMENT_STATE", - obis.Q3D_EQUIPMENT_SERIALNUMBER: "Q3D_EQUIPMENT_SERIALNUMBER", + obis.P1_MESSAGE_HEADER: 'P1_MESSAGE_HEADER', + obis.P1_MESSAGE_TIMESTAMP: 'P1_MESSAGE_TIMESTAMP', + obis.ELECTRICITY_IMPORTED_TOTAL: 'ELECTRICITY_IMPORTED_TOTAL', + obis.ELECTRICITY_USED_TARIFF_1: 'ELECTRICITY_USED_TARIFF_1', + obis.ELECTRICITY_USED_TARIFF_2: 'ELECTRICITY_USED_TARIFF_2', + obis.ELECTRICITY_EXPORTED_TOTAL: 'ELECTRICITY_EXPORTED_TOTAL', + obis.ELECTRICITY_DELIVERED_TARIFF_1: 'ELECTRICITY_DELIVERED_TARIFF_1', + obis.ELECTRICITY_DELIVERED_TARIFF_2: 'ELECTRICITY_DELIVERED_TARIFF_2', + obis.ELECTRICITY_ACTIVE_TARIFF: 'ELECTRICITY_ACTIVE_TARIFF', + obis.EQUIPMENT_IDENTIFIER: 'EQUIPMENT_IDENTIFIER', + obis.CURRENT_ELECTRICITY_USAGE: 'CURRENT_ELECTRICITY_USAGE', + obis.CURRENT_ELECTRICITY_DELIVERY: 'CURRENT_ELECTRICITY_DELIVERY', + obis.LONG_POWER_FAILURE_COUNT: 'LONG_POWER_FAILURE_COUNT', + obis.SHORT_POWER_FAILURE_COUNT: 'SHORT_POWER_FAILURE_COUNT', + obis.POWER_EVENT_FAILURE_LOG: 'POWER_EVENT_FAILURE_LOG', + obis.VOLTAGE_SAG_L1_COUNT: 'VOLTAGE_SAG_L1_COUNT', + obis.VOLTAGE_SAG_L2_COUNT: 'VOLTAGE_SAG_L2_COUNT', + obis.VOLTAGE_SAG_L3_COUNT: 'VOLTAGE_SAG_L3_COUNT', + obis.VOLTAGE_SWELL_L1_COUNT: 'VOLTAGE_SWELL_L1_COUNT', + obis.VOLTAGE_SWELL_L2_COUNT: 'VOLTAGE_SWELL_L2_COUNT', + obis.VOLTAGE_SWELL_L3_COUNT: 'VOLTAGE_SWELL_L3_COUNT', + obis.INSTANTANEOUS_VOLTAGE_L1: 'INSTANTANEOUS_VOLTAGE_L1', + obis.INSTANTANEOUS_VOLTAGE_L2: 'INSTANTANEOUS_VOLTAGE_L2', + obis.INSTANTANEOUS_VOLTAGE_L3: 'INSTANTANEOUS_VOLTAGE_L3', + obis.INSTANTANEOUS_CURRENT_L1: 'INSTANTANEOUS_CURRENT_L1', + obis.INSTANTANEOUS_CURRENT_L2: 'INSTANTANEOUS_CURRENT_L2', + obis.INSTANTANEOUS_CURRENT_L3: 'INSTANTANEOUS_CURRENT_L3', + obis.TEXT_MESSAGE_CODE: 'TEXT_MESSAGE_CODE', + obis.TEXT_MESSAGE: 'TEXT_MESSAGE', + obis.DEVICE_TYPE: 'DEVICE_TYPE', + obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: 'INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE', + obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE: 'INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE', + obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE: 'INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE', + obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE: 'INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE', + obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE: 'INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE', + obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: 'INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE', + obis.EQUIPMENT_IDENTIFIER_GAS: 'EQUIPMENT_IDENTIFIER_GAS', + obis.HOURLY_GAS_METER_READING: 'HOURLY_GAS_METER_READING', + obis.GAS_METER_READING: 'GAS_METER_READING', + obis.ACTUAL_TRESHOLD_ELECTRICITY: 'ACTUAL_TRESHOLD_ELECTRICITY', + obis.ACTUAL_SWITCH_POSITION: 'ACTUAL_SWITCH_POSITION', + obis.VALVE_POSITION_GAS: 'VALVE_POSITION_GAS', + obis.BELGIUM_HOURLY_GAS_METER_READING: 'BELGIUM_HOURLY_GAS_METER_READING', + obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: 'LUXEMBOURG_EQUIPMENT_IDENTIFIER', + obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: 'LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL', + obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: 'LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL', + obis.SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL: 'SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL', + obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: 'SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL', + obis.Q3D_EQUIPMENT_IDENTIFIER: 'Q3D_EQUIPMENT_IDENTIFIER', + obis.Q3D_EQUIPMENT_STATE: 'Q3D_EQUIPMENT_STATE', + obis.Q3D_EQUIPMENT_SERIALNUMBER: 'Q3D_EQUIPMENT_SERIALNUMBER', } REVERSE_EN = dict([(v, k) for k, v in EN.items()]) diff --git a/dsmr_parser/obis_references.py b/dsmr_parser/obis_references.py index 02e1e33..ad3bd98 100644 --- a/dsmr_parser/obis_references.py +++ b/dsmr_parser/obis_references.py @@ -6,76 +6,69 @@ refactored to full line signatures to maintain backwards compatibility. Might be refactored in a backwards incompatible way as soon as proper telegram objects are introduced. """ -P1_MESSAGE_HEADER = r"\d-\d:0\.2\.8.+?\r\n" -P1_MESSAGE_TIMESTAMP = r"\d-\d:1\.0\.0.+?\r\n" -ELECTRICITY_IMPORTED_TOTAL = r"\d-\d:1\.8\.0.+?\r\n" -ELECTRICITY_USED_TARIFF_1 = r"\d-\d:1\.8\.1.+?\r\n" -ELECTRICITY_USED_TARIFF_2 = r"\d-\d:1\.8\.2.+?\r\n" -ELECTRICITY_EXPORTED_TOTAL = r"\d-\d:2\.8\.0.+?\r\n" -ELECTRICITY_DELIVERED_TARIFF_1 = r"\d-\d:2\.8\.1.+?\r\n" -ELECTRICITY_DELIVERED_TARIFF_2 = r"\d-\d:2\.8\.2.+?\r\n" -ELECTRICITY_ACTIVE_TARIFF = r"\d-\d:96\.14\.0.+?\r\n" -EQUIPMENT_IDENTIFIER = r"\d-\d:96\.1\.1.+?\r\n" -CURRENT_ELECTRICITY_USAGE = r"\d-\d:1\.7\.0.+?\r\n" -CURRENT_ELECTRICITY_DELIVERY = r"\d-\d:2\.7\.0.+?\r\n" -LONG_POWER_FAILURE_COUNT = r"96\.7\.9.+?\r\n" -SHORT_POWER_FAILURE_COUNT = r"96\.7\.21.+?\r\n" -POWER_EVENT_FAILURE_LOG = r"99\.97\.0.+?\r\n" -VOLTAGE_SAG_L1_COUNT = r"\d-\d:32\.32\.0.+?\r\n" -VOLTAGE_SAG_L2_COUNT = r"\d-\d:52\.32\.0.+?\r\n" -VOLTAGE_SAG_L3_COUNT = r"\d-\d:72\.32\.0.+?\r\n" -VOLTAGE_SWELL_L1_COUNT = r"\d-\d:32\.36\.0.+?\r\n" -VOLTAGE_SWELL_L2_COUNT = r"\d-\d:52\.36\.0.+?\r\n" -VOLTAGE_SWELL_L3_COUNT = r"\d-\d:72\.36\.0.+?\r\n" -INSTANTANEOUS_VOLTAGE_L1 = r"\d-\d:32\.7\.0.+?\r\n" -INSTANTANEOUS_VOLTAGE_L2 = r"\d-\d:52\.7\.0.+?\r\n" -INSTANTANEOUS_VOLTAGE_L3 = r"\d-\d:72\.7\.0.+?\r\n" -INSTANTANEOUS_CURRENT_L1 = r"\d-\d:31\.7\.0.+?\r\n" -INSTANTANEOUS_CURRENT_L2 = r"\d-\d:51\.7\.0.+?\r\n" -INSTANTANEOUS_CURRENT_L3 = r"\d-\d:71\.7\.0.+?\r\n" -TEXT_MESSAGE_CODE = r"\d-\d:96\.13\.1.+?\r\n" -TEXT_MESSAGE = r"\d-\d:96\.13\.0.+?\r\n" -DEVICE_TYPE = r"\d-\d:24\.1\.0.+?\r\n" -INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE = r"\d-\d:21\.7\.0.+?\r\n" -INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE = r"\d-\d:41\.7\.0.+?\r\n" -INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE = r"\d-\d:61\.7\.0.+?\r\n" -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" +P1_MESSAGE_HEADER = r'\d-\d:0\.2\.8.+?\r\n' +P1_MESSAGE_TIMESTAMP = r'\d-\d:1\.0\.0.+?\r\n' +ELECTRICITY_IMPORTED_TOTAL = r'\d-\d:1\.8\.0.+?\r\n' +ELECTRICITY_USED_TARIFF_1 = r'\d-\d:1\.8\.1.+?\r\n' +ELECTRICITY_USED_TARIFF_2 = r'\d-\d:1\.8\.2.+?\r\n' +ELECTRICITY_EXPORTED_TOTAL = r'\d-\d:2\.8\.0.+?\r\n' +ELECTRICITY_DELIVERED_TARIFF_1 = r'\d-\d:2\.8\.1.+?\r\n' +ELECTRICITY_DELIVERED_TARIFF_2 = r'\d-\d:2\.8\.2.+?\r\n' +ELECTRICITY_ACTIVE_TARIFF = r'\d-\d:96\.14\.0.+?\r\n' +EQUIPMENT_IDENTIFIER = r'\d-\d:96\.1\.1.+?\r\n' +CURRENT_ELECTRICITY_USAGE = r'\d-\d:1\.7\.0.+?\r\n' +CURRENT_ELECTRICITY_DELIVERY = r'\d-\d:2\.7\.0.+?\r\n' +LONG_POWER_FAILURE_COUNT = r'96\.7\.9.+?\r\n' +SHORT_POWER_FAILURE_COUNT = r'96\.7\.21.+?\r\n' +POWER_EVENT_FAILURE_LOG = r'99\.97\.0.+?\r\n' +VOLTAGE_SAG_L1_COUNT = r'\d-\d:32\.32\.0.+?\r\n' +VOLTAGE_SAG_L2_COUNT = r'\d-\d:52\.32\.0.+?\r\n' +VOLTAGE_SAG_L3_COUNT = r'\d-\d:72\.32\.0.+?\r\n' +VOLTAGE_SWELL_L1_COUNT = r'\d-\d:32\.36\.0.+?\r\n' +VOLTAGE_SWELL_L2_COUNT = r'\d-\d:52\.36\.0.+?\r\n' +VOLTAGE_SWELL_L3_COUNT = r'\d-\d:72\.36\.0.+?\r\n' +INSTANTANEOUS_VOLTAGE_L1 = r'\d-\d:32\.7\.0.+?\r\n' +INSTANTANEOUS_VOLTAGE_L2 = r'\d-\d:52\.7\.0.+?\r\n' +INSTANTANEOUS_VOLTAGE_L3 = r'\d-\d:72\.7\.0.+?\r\n' +INSTANTANEOUS_CURRENT_L1 = r'\d-\d:31\.7\.0.+?\r\n' +INSTANTANEOUS_CURRENT_L2 = r'\d-\d:51\.7\.0.+?\r\n' +INSTANTANEOUS_CURRENT_L3 = r'\d-\d:71\.7\.0.+?\r\n' +TEXT_MESSAGE_CODE = r'\d-\d:96\.13\.1.+?\r\n' +TEXT_MESSAGE = r'\d-\d:96\.13\.0.+?\r\n' +DEVICE_TYPE = r'\d-\d:24\.1\.0.+?\r\n' +INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE = r'\d-\d:21\.7\.0.+?\r\n' +INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE = r'\d-\d:41\.7\.0.+?\r\n' +INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE = r'\d-\d:61\.7\.0.+?\r\n' +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 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" +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) +ELECTRICITY_USED_TARIFF_ALL = ( + ELECTRICITY_USED_TARIFF_1, + ELECTRICITY_USED_TARIFF_2 +) ELECTRICITY_DELIVERED_TARIFF_ALL = ( ELECTRICITY_DELIVERED_TARIFF_1, - ELECTRICITY_DELIVERED_TARIFF_2, + ELECTRICITY_DELIVERED_TARIFF_2 ) # Alternate codes for foreign countries. -BELGIUM_HOURLY_GAS_METER_READING = ( - r"\d-\d:24\.2\.3.+?\r\n" # Different code, same format. -) -LUXEMBOURG_EQUIPMENT_IDENTIFIER = r"\d-\d:42\.0\.0.+?\r\n" # Logical device name -LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL = ( - r"\d-\d:1\.8\.0.+?\r\n" # Total imported energy register (P+) -) -LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = ( - r"\d-\d:2\.8\.0.+?\r\n" # Total exported energy register (P-) -) -SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL = ( - r"\d-\d:1\.8\.0.+?\r\n" # Total imported energy register (P+) -) -SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = ( - r"\d-\d:2\.8\.0.+?\r\n" # Total exported energy register (P-) -) -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 +BELGIUM_HOURLY_GAS_METER_READING = r'\d-\d:24\.2\.3.+?\r\n' # Different code, same format. +LUXEMBOURG_EQUIPMENT_IDENTIFIER = r'\d-\d:42\.0\.0.+?\r\n' # Logical device name +LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL = r'\d-\d:1\.8\.0.+?\r\n' # Total imported energy register (P+) +LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = r'\d-\d:2\.8\.0.+?\r\n' # Total exported energy register (P-) +SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL = r'\d-\d:1\.8\.0.+?\r\n' # Total imported energy register (P+) +SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = r'\d-\d:2\.8\.0.+?\r\n' # Total exported energy register (P-) +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/telegram_specifications.py b/dsmr_parser/telegram_specifications.py index 77ea49e..00d0c31 100644 --- a/dsmr_parser/telegram_specifications.py +++ b/dsmr_parser/telegram_specifications.py @@ -2,18 +2,9 @@ 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 from dsmr_parser.value_types import timestamp -from dsmr_parser.profile_generic_specifications import ( - BUFFER_TYPES, - PG_HEAD_PARSERS, - PG_UNIDENTIFIED_BUFFERTYPE_PARSERS, -) +from dsmr_parser.profile_generic_specifications import BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS """ dsmr_parser.telegram_specifications @@ -24,8 +15,8 @@ how the telegram lines are parsed. """ V2_2 = { - "checksum_support": False, - "objects": { + '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)), @@ -50,14 +41,14 @@ V2_2 = { ValueParser(str), # unit, position 5 ValueParser(Decimal), # meter reading, position 6 ), - }, + } } V3 = V2_2 V4 = { - "checksum_support": True, - "objects": { + 'checksum_support': True, + 'objects': { obis.P1_MESSAGE_HEADER: CosemParser(ValueParser(str)), obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)), obis.EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), @@ -70,9 +61,10 @@ V4 = { obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), obis.SHORT_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), obis.LONG_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), - obis.POWER_EVENT_FAILURE_LOG: ProfileGenericParser( - BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS - ), + obis.POWER_EVENT_FAILURE_LOG: + ProfileGenericParser(BUFFER_TYPES, + PG_HEAD_PARSERS, + PG_UNIDENTIFIED_BUFFERTYPE_PARSERS), obis.VOLTAGE_SAG_L1_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SAG_L2_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SAG_L3_COUNT: CosemParser(ValueParser(int)), @@ -93,14 +85,15 @@ V4 = { obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: CosemParser(ValueParser(Decimal)), obis.EQUIPMENT_IDENTIFIER_GAS: CosemParser(ValueParser(str)), obis.HOURLY_GAS_METER_READING: MBusParser( - ValueParser(timestamp), ValueParser(Decimal) - ), - }, + ValueParser(timestamp), + ValueParser(Decimal) + ) + } } V5 = { - "checksum_support": True, - "objects": { + 'checksum_support': True, + 'objects': { obis.P1_MESSAGE_HEADER: CosemParser(ValueParser(str)), obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)), obis.EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), @@ -114,9 +107,10 @@ V5 = { obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), obis.LONG_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), obis.SHORT_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), - obis.POWER_EVENT_FAILURE_LOG: ProfileGenericParser( - BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS - ), + obis.POWER_EVENT_FAILURE_LOG: + ProfileGenericParser(BUFFER_TYPES, + PG_HEAD_PARSERS, + PG_UNIDENTIFIED_BUFFERTYPE_PARSERS), obis.VOLTAGE_SAG_L1_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SAG_L2_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SAG_L3_COUNT: CosemParser(ValueParser(int)), @@ -139,46 +133,38 @@ V5 = { obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: CosemParser(ValueParser(Decimal)), obis.EQUIPMENT_IDENTIFIER_GAS: CosemParser(ValueParser(str)), obis.HOURLY_GAS_METER_READING: MBusParser( - ValueParser(timestamp), ValueParser(Decimal) - ), - }, + ValueParser(timestamp), + ValueParser(Decimal) + ) + } } ALL = (V2_2, V3, V4, V5) BELGIUM_FLUVIUS = deepcopy(V5) -BELGIUM_FLUVIUS["objects"].update( - { - obis.BELGIUM_HOURLY_GAS_METER_READING: MBusParser( - ValueParser(timestamp), ValueParser(Decimal) - ) - } -) +BELGIUM_FLUVIUS['objects'].update({ + obis.BELGIUM_HOURLY_GAS_METER_READING: MBusParser( + ValueParser(timestamp), + ValueParser(Decimal) + ) +}) LUXEMBOURG_SMARTY = deepcopy(V5) -LUXEMBOURG_SMARTY["objects"].update( - { - obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), - obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser( - ValueParser(Decimal) - ), - obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser( - ValueParser(Decimal) - ), - } -) +LUXEMBOURG_SMARTY['objects'].update({ + obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), + obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), + obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), +}) # Source: https://www.energiforetagen.se/globalassets/energiforetagen/det-erbjuder-vi/kurser-och-konferenser/elnat/branschrekommendation-lokalt-granssnitt-v2_0-201912.pdf SWEDEN = { - "checksum_support": True, - "objects": { + 'checksum_support': True, + 'objects': { obis.P1_MESSAGE_HEADER: CosemParser(ValueParser(str)), obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)), obis.SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), - obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser( - ValueParser(Decimal) - ), + obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), obis.CURRENT_ELECTRICITY_USAGE: CosemParser(ValueParser(Decimal)), obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: CosemParser(ValueParser(Decimal)), @@ -193,7 +179,7 @@ SWEDEN = { obis.INSTANTANEOUS_CURRENT_L1: CosemParser(ValueParser(Decimal)), obis.INSTANTANEOUS_CURRENT_L2: CosemParser(ValueParser(Decimal)), obis.INSTANTANEOUS_CURRENT_L3: CosemParser(ValueParser(Decimal)), - }, + } } Q3D = { From 6866e7d585bca78839e881737c5d4827710cc825 Mon Sep 17 00:00:00 2001 From: Gunnar Klauberg Date: Sun, 14 Nov 2021 21:52:38 +0000 Subject: [PATCH 51/85] leaving version as it was --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3852064..7ad7c68 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( author_email='nigel@nldr.net', license='MIT', url='https://github.com/ndokter/dsmr_parser', - version='0.31', + version='0.30', packages=find_packages(exclude=('test', 'test.*')), install_requires=[ 'pyserial>=3,<4', From de4dc2ec9823f8ee4c83af5cc0cc76a514638878 Mon Sep 17 00:00:00 2001 From: Gunnar Klauberg Date: Sun, 14 Nov 2021 22:05:27 +0000 Subject: [PATCH 52/85] Q3D COM-1 test telegrams --- test/example_telegrams.py | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/example_telegrams.py b/test/example_telegrams.py index f74ed16..c59280c 100644 --- a/test/example_telegrams.py +++ b/test/example_telegrams.py @@ -128,3 +128,43 @@ TELEGRAM_V5 = ( '0-2:96.1.0()\r\n' '!6EEE\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 +# - D0-Specs on page 20 +# +# last two lines are added by the COM-1 Ethernet Gateway + +TELEGRAM_ESY5Q3DB1024_V304 = ( # Easymeter an Hauptstromzähler + '/ESY5Q3DB1024 V3.04\r\n' + '\r\n' + '1-0:0.0.0*255(0272031312565)\r\n' + '1-0:1.8.0*255(00052185.7825309*kWh)\r\n' + '1-0:2.8.0*255(00019949.3221493*kWh)\r\n' + '1-0:21.7.0*255(000747.85*W)\r\n' + '1-0:41.7.0*255(000737.28*W)\r\n' + '1-0:61.7.0*255(000639.73*W)\r\n' + '1-0:1.7.0*255(002124.86*W)\r\n' + '1-0:96.5.5*255(80)\r\n' + '0-0:96.1.255*255(1ESY1313002565)\r\n' + '!\r\n' + ' 25803103\r\n' + '\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\r\n' +) + +TELEGRAM_ESY5Q3DA1004_V304 = ( # Easymeter an Wärmepumpe + '/ESY5Q3DA1004 V3.04\r\n' + '\r\n' + '1-0:0.0.0*255(1336001560)\r\n' + '1-0:1.8.0*255(00032549.5061662*kWh)\r\n' + '1-0:21.7.0*255(000557.29*W)\r\n' + '1-0:41.7.0*255(000521.62*W)\r\n' + '1-0:61.7.0*255(000609.30*W)\r\n' + '1-0:1.7.0*255(001688.21*W)\r\n' + '1-0:96.5.5*255(80)\r\n' + '0-0:96.1.255*255(1ESY1336001560)\r\n' + '!\r\n' + ' 25818685\r\n' + 'DE0000000000000000000000000000003\r\n' +) From 7a89d6e97be0a2798d33dfdae16398efa6aaa87d Mon Sep 17 00:00:00 2001 From: Gunnar Klauberg Date: Sun, 14 Nov 2021 22:20:00 +0000 Subject: [PATCH 53/85] re-adding ascii decoding at telegram level --- dsmr_parser/clients/protocol.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index 208ed1b..a9a9205 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -134,6 +134,7 @@ class DSMRProtocol(asyncio.Protocol): self.log.debug('got telegram: %s', telegram) try: + telegram = telegram.decode("ascii") parsed_telegram = self.telegram_parser.parse(telegram) except InvalidChecksumError as e: self.log.warning(str(e)) From 26ac27c347c39d473c7f034eab2bdb295927d2df Mon Sep 17 00:00:00 2001 From: Gunnar Klauberg Date: Sun, 14 Nov 2021 22:29:37 +0000 Subject: [PATCH 54/85] re-adding ascii decoding at telegram level --- dsmr_parser/clients/protocol.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index a9a9205..c2d032f 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -134,7 +134,10 @@ class DSMRProtocol(asyncio.Protocol): self.log.debug('got telegram: %s', telegram) try: - telegram = telegram.decode("ascii") + # we accepted 8-bit at transport level (e.g. tcp) + telegram_data = telegram.encode("latin1") + # we need to ensure 7-bit at telegram level (IEC 646 required in section 5.4 of IEC 62056-21) + telegram = telegram_data.decode("ascii") parsed_telegram = self.telegram_parser.parse(telegram) except InvalidChecksumError as e: self.log.warning(str(e)) From 45cc88942a6394a1bca9dd984b86903cfda97690 Mon Sep 17 00:00:00 2001 From: Gunnar Klauberg Date: Sun, 21 Nov 2021 11:25:05 +0000 Subject: [PATCH 55/85] latin-1 changes folded into data_received --- dsmr_parser/clients/protocol.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index c2d032f..bbdcff6 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -102,12 +102,16 @@ class DSMRProtocol(asyncio.Protocol): def data_received(self, data): """Add incoming data to buffer.""" + + # accept latin-1 (8-bit) on the line, to allow for non-ascii transport or padding data = data.decode("latin1") self._active = True self.log.debug('received data: %s', data) self.telegram_buffer.append(data) for telegram in self.telegram_buffer.get_all(): + # ensure actual telegram is ascii (7-bit) only (IEC 646 required in section 5.4 of IEC 62056-21) + telegram = telegram.encode("latin1").decode("ascii") self.handle_telegram(telegram) def keep_alive(self): @@ -134,10 +138,6 @@ class DSMRProtocol(asyncio.Protocol): self.log.debug('got telegram: %s', telegram) try: - # we accepted 8-bit at transport level (e.g. tcp) - telegram_data = telegram.encode("latin1") - # we need to ensure 7-bit at telegram level (IEC 646 required in section 5.4 of IEC 62056-21) - telegram = telegram_data.decode("ascii") parsed_telegram = self.telegram_parser.parse(telegram) except InvalidChecksumError as e: self.log.warning(str(e)) From 60af45be257b6b1547050f03eca9dd5cb94bdc7f Mon Sep 17 00:00:00 2001 From: Nigel Dokter Date: Sun, 21 Nov 2021 14:06:11 +0100 Subject: [PATCH 56/85] Prepare version 0.31 --- CHANGELOG.rst | 3 +++ setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d539d2b..d320b73 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,9 @@ Change Log ---------- +**0.31** (2021-11-21) +- Support for (German) EasyMeter Q3D using COM-1 Ethernet Gateway (`pull request #92 `_). + **0.30** (2021-08-18) - Add support for Swedish smart meters (`pull request #86 `_). diff --git a/setup.py b/setup.py index 7ad7c68..3852064 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( author_email='nigel@nldr.net', license='MIT', url='https://github.com/ndokter/dsmr_parser', - version='0.30', + version='0.31', packages=find_packages(exclude=('test', 'test.*')), install_requires=[ 'pyserial>=3,<4', From 28e3c51f0d5cfea4e8f5f4a2c5f9dbcf9bcc7e89 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Sun, 21 Nov 2021 16:08:47 +0100 Subject: [PATCH 57/85] fix pylama errors on PR92 + editorial fix --- dsmr_parser/clients/protocol.py | 2 +- test/example_telegrams.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index f46aea5..52f8383 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -113,7 +113,7 @@ class DSMRProtocol(asyncio.Protocol): self.telegram_buffer.append(data) for telegram in self.telegram_buffer.get_all(): - # ensure actual telegram is ascii (7-bit) only (IEC 646 required in section 5.4 of IEC 62056-21) + # ensure actual telegram is ascii (7-bit) only (ISO 646:1991 IRV required in section 5.5 of IEC 62056-21) telegram = telegram.encode("latin1").decode("ascii") self.handle_telegram(telegram) diff --git a/test/example_telegrams.py b/test/example_telegrams.py index c59280c..1ccb8ce 100644 --- a/test/example_telegrams.py +++ b/test/example_telegrams.py @@ -136,7 +136,7 @@ TELEGRAM_V5 = ( # # last two lines are added by the COM-1 Ethernet Gateway -TELEGRAM_ESY5Q3DB1024_V304 = ( # Easymeter an Hauptstromzähler +TELEGRAM_ESY5Q3DB1024_V304 = ( '/ESY5Q3DB1024 V3.04\r\n' '\r\n' '1-0:0.0.0*255(0272031312565)\r\n' @@ -150,10 +150,11 @@ TELEGRAM_ESY5Q3DB1024_V304 = ( # Easymeter an Hauptstromzähler '0-0:96.1.255*255(1ESY1313002565)\r\n' '!\r\n' ' 25803103\r\n' - '\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\r\n' + '\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' + '\xff\xff\xff\xff\xff\r\n' ) -TELEGRAM_ESY5Q3DA1004_V304 = ( # Easymeter an Wärmepumpe +TELEGRAM_ESY5Q3DA1004_V304 = ( '/ESY5Q3DA1004 V3.04\r\n' '\r\n' '1-0:0.0.0*255(1336001560)\r\n' From 83ef354c1285def5beecbd077670971e74b388e3 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Sat, 27 Nov 2021 19:55:58 +0100 Subject: [PATCH 58/85] resolve name clash as reported in issue #95 --- dsmr_parser/clients/socket_.py | 3 +-- dsmr_parser/obis_name_mapping.py | 4 ---- dsmr_parser/obis_references.py | 12 +++++------- dsmr_parser/telegram_specifications.py | 8 ++++---- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/dsmr_parser/clients/socket_.py b/dsmr_parser/clients/socket_.py index 7c13f02..6727979 100644 --- a/dsmr_parser/clients/socket_.py +++ b/dsmr_parser/clients/socket_.py @@ -22,7 +22,6 @@ class SocketReader(object): self.telegram_buffer = TelegramBuffer() self.telegram_specification = telegram_specification - def read(self): """ Read complete DSMR telegram's from remote interface and parse it @@ -88,4 +87,4 @@ class SocketReader(object): except ParseError as e: logger.error('Failed to parse telegram: %s', e) - buffer = b"" \ No newline at end of file + buffer = b"" diff --git a/dsmr_parser/obis_name_mapping.py b/dsmr_parser/obis_name_mapping.py index 87c720d..b224b7a 100644 --- a/dsmr_parser/obis_name_mapping.py +++ b/dsmr_parser/obis_name_mapping.py @@ -52,10 +52,6 @@ EN = { obis.VALVE_POSITION_GAS: 'VALVE_POSITION_GAS', obis.BELGIUM_HOURLY_GAS_METER_READING: 'BELGIUM_HOURLY_GAS_METER_READING', obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: 'LUXEMBOURG_EQUIPMENT_IDENTIFIER', - obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: 'LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL', - obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: 'LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL', - obis.SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL: 'SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL', - obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: 'SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL', obis.Q3D_EQUIPMENT_IDENTIFIER: 'Q3D_EQUIPMENT_IDENTIFIER', obis.Q3D_EQUIPMENT_STATE: 'Q3D_EQUIPMENT_STATE', obis.Q3D_EQUIPMENT_SERIALNUMBER: 'Q3D_EQUIPMENT_SERIALNUMBER', diff --git a/dsmr_parser/obis_references.py b/dsmr_parser/obis_references.py index ad3bd98..d4a4cbf 100644 --- a/dsmr_parser/obis_references.py +++ b/dsmr_parser/obis_references.py @@ -8,10 +8,8 @@ objects are introduced. """ P1_MESSAGE_HEADER = r'\d-\d:0\.2\.8.+?\r\n' P1_MESSAGE_TIMESTAMP = r'\d-\d:1\.0\.0.+?\r\n' -ELECTRICITY_IMPORTED_TOTAL = r'\d-\d:1\.8\.0.+?\r\n' ELECTRICITY_USED_TARIFF_1 = r'\d-\d:1\.8\.1.+?\r\n' ELECTRICITY_USED_TARIFF_2 = r'\d-\d:1\.8\.2.+?\r\n' -ELECTRICITY_EXPORTED_TOTAL = r'\d-\d:2\.8\.0.+?\r\n' ELECTRICITY_DELIVERED_TARIFF_1 = r'\d-\d:2\.8\.1.+?\r\n' ELECTRICITY_DELIVERED_TARIFF_2 = r'\d-\d:2\.8\.2.+?\r\n' ELECTRICITY_ACTIVE_TARIFF = r'\d-\d:96\.14\.0.+?\r\n' @@ -62,13 +60,13 @@ ELECTRICITY_DELIVERED_TARIFF_ALL = ( ELECTRICITY_DELIVERED_TARIFF_2 ) -# Alternate codes for foreign countries. +# International generalized additions +ELECTRICITY_IMPORTED_TOTAL = r'\d-\d:1\.8\.0.+?\r\n' # Total imported energy register (P+) +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_HOURLY_GAS_METER_READING = r'\d-\d:24\.2\.3.+?\r\n' # Different code, same format. LUXEMBOURG_EQUIPMENT_IDENTIFIER = r'\d-\d:42\.0\.0.+?\r\n' # Logical device name -LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL = r'\d-\d:1\.8\.0.+?\r\n' # Total imported energy register (P+) -LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = r'\d-\d:2\.8\.0.+?\r\n' # Total exported energy register (P-) -SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL = r'\d-\d:1\.8\.0.+?\r\n' # Total imported energy register (P+) -SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = r'\d-\d:2\.8\.0.+?\r\n' # Total exported energy register (P-) 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/telegram_specifications.py b/dsmr_parser/telegram_specifications.py index 978a65c..c8f05a5 100644 --- a/dsmr_parser/telegram_specifications.py +++ b/dsmr_parser/telegram_specifications.py @@ -153,8 +153,8 @@ BELGIUM_FLUVIUS['objects'].update({ LUXEMBOURG_SMARTY = deepcopy(V5) LUXEMBOURG_SMARTY['objects'].update({ obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), - obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), - obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), + obis.ELECTRICITY_IMPORTED_TOTAL: CosemParser(ValueParser(Decimal)), + obis.ELECTRICITY_EXPORTED_TOTAL: CosemParser(ValueParser(Decimal)), }) # Source: https://www.energiforetagen.se/globalassets/energiforetagen/det-erbjuder-vi/kurser-och-konferenser/elnat/ @@ -164,8 +164,8 @@ SWEDEN = { 'objects': { obis.P1_MESSAGE_HEADER: CosemParser(ValueParser(str)), obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)), - obis.SWEDEN_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), - obis.SWEDEN_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), + obis.ELECTRICITY_IMPORTED_TOTAL: CosemParser(ValueParser(Decimal)), + obis.ELECTRICITY_EXPORTED_TOTAL: CosemParser(ValueParser(Decimal)), obis.CURRENT_ELECTRICITY_USAGE: CosemParser(ValueParser(Decimal)), obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: CosemParser(ValueParser(Decimal)), From f0e035c8ed39e821e2a155e3d62151d31f643a91 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Sun, 28 Nov 2021 01:12:32 +0100 Subject: [PATCH 59/85] fix newer version pylama errors --- dsmr_parser/objects.py | 2 +- dsmr_parser/profile_generic_specifications.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dsmr_parser/objects.py b/dsmr_parser/objects.py index c062d9d..af7d068 100644 --- a/dsmr_parser/objects.py +++ b/dsmr_parser/objects.py @@ -179,7 +179,7 @@ class ProfileGenericObject(DSMRObject): self._buffer_list = [] values_offset = 2 for i in range(self.buffer_length): - offset = values_offset + i*2 + offset = values_offset + i * 2 self._buffer_list.append(MBusObject([self.values[offset], self.values[offset + 1]])) return self._buffer_list diff --git a/dsmr_parser/profile_generic_specifications.py b/dsmr_parser/profile_generic_specifications.py index a52416c..e753c01 100644 --- a/dsmr_parser/profile_generic_specifications.py +++ b/dsmr_parser/profile_generic_specifications.py @@ -7,4 +7,4 @@ PG_HEAD_PARSERS = [ValueParser(int), ValueParser(str)] PG_UNIDENTIFIED_BUFFERTYPE_PARSERS = [ValueParser(str), ValueParser(str)] BUFFER_TYPES = { PG_FAILURE_EVENT: [ValueParser(timestamp), ValueParser(int)] - } +} From 3eed3654d4839b0cd2e6c2c98d0f6b6804d76fe1 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Thu, 30 Dec 2021 17:01:29 +0100 Subject: [PATCH 60/85] Wrap DSMR protocol in RFXtrx wrapper --- dsmr_parser/clients/protocol.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index fce549d..982fd81 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -142,3 +142,26 @@ class DSMRProtocol(asyncio.Protocol): async def wait_closed(self): """Wait until connection is closed.""" await self._closed.wait() + + +PACKETTYPE_DSMR = 0x62 +SUBTYPE_P1 = 0x01 + +class RFXtrxDSMRProtocol(DSMRProtocol): + + _data = b'' + + def data_received(self, data): + """Add incoming data to buffer.""" + + data = self._data + data + + while (len(data) > 0 and (packetlength := data[0]+1) <= len(data)): + packettype = data[1] + subtype = data[2] + if (packettype == PACKETTYPE_DSMR and subtype == SUBTYPE_P1): + dsmr_data = data[4:packetlength] + super().data_received(dsmr_data) + data = data[packetlength:] + + self._data = data From 8b64adb80c0699f6448e55f64be95052065f37e4 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Thu, 30 Dec 2021 17:23:31 +0100 Subject: [PATCH 61/85] Small rename --- dsmr_parser/clients/protocol.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index 982fd81..567b0f4 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -149,12 +149,12 @@ SUBTYPE_P1 = 0x01 class RFXtrxDSMRProtocol(DSMRProtocol): - _data = b'' + remaining_data = b'' def data_received(self, data): """Add incoming data to buffer.""" - data = self._data + data + data = self.remaining_data + data while (len(data) > 0 and (packetlength := data[0]+1) <= len(data)): packettype = data[1] @@ -164,4 +164,4 @@ class RFXtrxDSMRProtocol(DSMRProtocol): super().data_received(dsmr_data) data = data[packetlength:] - self._data = data + self.remaining_data = data From c7ed4acb034c44ce47eaf944654b0fcc8a10a895 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Thu, 30 Dec 2021 20:23:58 +0100 Subject: [PATCH 62/85] Refactor into separate file --- dsmr_parser/clients/protocol.py | 32 ++++--------- dsmr_parser/clients/rfxtrx_protocol.py | 62 ++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 24 deletions(-) create mode 100644 dsmr_parser/clients/rfxtrx_protocol.py diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index 567b0f4..4e6d85d 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -16,6 +16,13 @@ from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): """Creates a DSMR asyncio protocol.""" + protocol = _create_dsmr_protocol(dsmr_version, telegram_callback, + DSMRProtocol, loop, **kwargs) + return protocol + + +def _create_dsmr_protocol(dsmr_version, telegram_callback, protocol loop=None, **kwargs): + """Creates a DSMR asyncio protocol.""" if dsmr_version == '2.2': specification = telegram_specifications.V2_2 @@ -39,7 +46,7 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): raise NotImplementedError("No telegram parser found for version: %s", dsmr_version) - protocol = partial(DSMRProtocol, loop, TelegramParser(specification), + protocol = partial(protocol, loop, TelegramParser(specification), telegram_callback=telegram_callback, **kwargs) return protocol, serial_settings @@ -142,26 +149,3 @@ class DSMRProtocol(asyncio.Protocol): async def wait_closed(self): """Wait until connection is closed.""" await self._closed.wait() - - -PACKETTYPE_DSMR = 0x62 -SUBTYPE_P1 = 0x01 - -class RFXtrxDSMRProtocol(DSMRProtocol): - - remaining_data = b'' - - def data_received(self, data): - """Add incoming data to buffer.""" - - data = self.remaining_data + data - - while (len(data) > 0 and (packetlength := data[0]+1) <= len(data)): - packettype = data[1] - subtype = data[2] - if (packettype == PACKETTYPE_DSMR and subtype == SUBTYPE_P1): - dsmr_data = data[4:packetlength] - super().data_received(dsmr_data) - data = data[packetlength:] - - self.remaining_data = data diff --git a/dsmr_parser/clients/rfxtrx_protocol.py b/dsmr_parser/clients/rfxtrx_protocol.py new file mode 100644 index 0000000..b8a347d --- /dev/null +++ b/dsmr_parser/clients/rfxtrx_protocol.py @@ -0,0 +1,62 @@ +"""Asyncio protocol implementation for handling telegrams over a RFXtrx connection .""" + +from functools import partial +import asyncio + +from serial_asyncio import create_serial_connection + +from .protocol import DSMRProtocol, _create_dsmr_protocol + + +def create_rfxtrx_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): + """Creates a DSMR asyncio protocol.""" + protocol = _create_dsmr_protocol(dsmr_version, telegram_callback, + RFXtrxDSMRProtocol, loop, **kwargs) + return protocol + + +def create_rfxtrx_dsmr_reader(port, dsmr_version, telegram_callback, loop=None): + """Creates a DSMR asyncio protocol coroutine using a RFXtrx serial port.""" + protocol, serial_settings = create_rfxtrx_dsmr_protocol( + dsmr_version, telegram_callback, loop=None) + serial_settings['url'] = port + + conn = create_serial_connection(loop, protocol, **serial_settings) + return conn + + +def create_rfxtrx_tcp_dsmr_reader(host, port, dsmr_version, + telegram_callback, loop=None, + keep_alive_interval=None): + """Creates a DSMR asyncio protocol coroutine using a RFXtrx TCP connection.""" + if not loop: + loop = asyncio.get_event_loop() + protocol, _ = create_rfxtrx_dsmr_protocol( + dsmr_version, telegram_callback, loop=loop, + keep_alive_interval=keep_alive_interval) + conn = loop.create_connection(protocol, host, port) + return conn + + +PACKETTYPE_DSMR = 0x62 +SUBTYPE_P1 = 0x01 + + +class RFXtrxDSMRProtocol(DSMRProtocol): + + remaining_data = b'' + + def data_received(self, data): + """Add incoming data to buffer.""" + + data = self.remaining_data + data + + while (len(data) > 0 and (packetlength := data[0]+1) <= len(data)): + packettype = data[1] + subtype = data[2] + if (packettype == PACKETTYPE_DSMR and subtype == SUBTYPE_P1): + dsmr_data = data[4:packetlength] + super().data_received(dsmr_data) + data = data[packetlength:] + + self.remaining_data = data From 188cac52877ba702a6e8dcb623b0742187afc872 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Thu, 30 Dec 2021 20:32:44 +0100 Subject: [PATCH 63/85] Small update --- dsmr_parser/clients/protocol.py | 2 +- dsmr_parser/clients/rfxtrx_protocol.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/dsmr_parser/clients/protocol.py b/dsmr_parser/clients/protocol.py index 4e6d85d..e996423 100644 --- a/dsmr_parser/clients/protocol.py +++ b/dsmr_parser/clients/protocol.py @@ -21,7 +21,7 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): return protocol -def _create_dsmr_protocol(dsmr_version, telegram_callback, protocol loop=None, **kwargs): +def _create_dsmr_protocol(dsmr_version, telegram_callback, protocol, loop=None, **kwargs): """Creates a DSMR asyncio protocol.""" if dsmr_version == '2.2': diff --git a/dsmr_parser/clients/rfxtrx_protocol.py b/dsmr_parser/clients/rfxtrx_protocol.py index b8a347d..281f4c2 100644 --- a/dsmr_parser/clients/rfxtrx_protocol.py +++ b/dsmr_parser/clients/rfxtrx_protocol.py @@ -1,15 +1,13 @@ """Asyncio protocol implementation for handling telegrams over a RFXtrx connection .""" -from functools import partial import asyncio from serial_asyncio import create_serial_connection - from .protocol import DSMRProtocol, _create_dsmr_protocol def create_rfxtrx_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs): - """Creates a DSMR asyncio protocol.""" + """Creates a RFXtrxDSMR asyncio protocol.""" protocol = _create_dsmr_protocol(dsmr_version, telegram_callback, RFXtrxDSMRProtocol, loop, **kwargs) return protocol From c082cf4868753c6335612274ff00605b5f00dd67 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Sat, 1 Jan 2022 20:19:24 +0100 Subject: [PATCH 64/85] Add test case --- test/test_rfxtrx_protocol.py | 79 ++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 test/test_rfxtrx_protocol.py diff --git a/test/test_rfxtrx_protocol.py b/test/test_rfxtrx_protocol.py new file mode 100644 index 0000000..14f54bc --- /dev/null +++ b/test/test_rfxtrx_protocol.py @@ -0,0 +1,79 @@ +from unittest.mock import Mock + +import unittest + +from dsmr_parser import obis_references as obis +from dsmr_parser import telegram_specifications +from dsmr_parser.parsers import TelegramParser +from dsmr_parser.clients.rfxtrx_protocol import create_rfxtrx_dsmr_protocol, PACKETTYPE_DSMR, SUBTYPE_P1 + + +TELEGRAM_V2_2 = ( + '/ISk5\2MT382-1004\r\n' + '\r\n' + '0-0:96.1.1(00000000000000)\r\n' + '1-0:1.8.1(00001.001*kWh)\r\n' + '1-0:1.8.2(00001.001*kWh)\r\n' + '1-0:2.8.1(00001.001*kWh)\r\n' + '1-0:2.8.2(00001.001*kWh)\r\n' + '0-0:96.14.0(0001)\r\n' + '1-0:1.7.0(0001.01*kW)\r\n' + '1-0:2.7.0(0000.00*kW)\r\n' + '0-0:17.0.0(0999.00*kW)\r\n' + '0-0:96.3.10(1)\r\n' + '0-0:96.13.1()\r\n' + '0-0:96.13.0()\r\n' + '0-1:24.1.0(3)\r\n' + '0-1:96.1.0(000000000000)\r\n' + '0-1:24.3.0(161107190000)(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' +) + +OTHER_RF_PACKET = b'\x03\x01\x02\x03' + + +def encode_telegram_as_RF_packets(telegram): + data = b'' + + for line in telegram.split('\n'): + packet_data = (line + '\n').encode('ascii') + packet_header = bytes(bytearray([ + len(packet_data) + 3, # excluding length byte + PACKETTYPE_DSMR, + SUBTYPE_P1, + 0 # seq num (ignored) + ])) + + data += packet_header + packet_data + # other RF packets can pass by on the line + data += OTHER_RF_PACKET + + return data + + +class RFXtrxProtocolTest(unittest.TestCase): + + def setUp(self): + new_protocol, _ = create_rfxtrx_dsmr_protocol('2.2', + telegram_callback=Mock(), + keep_alive_interval=1) + self.protocol = new_protocol() + + def test_complete_packet(self): + """Protocol should assemble incoming lines into complete packet.""" + + data = encode_telegram_as_RF_packets(TELEGRAM_V2_2) + # send data broken up in two parts + self.protocol.data_received(data[0:200]) + self.protocol.data_received(data[200:]) + + telegram = self.protocol.telegram_callback.call_args_list[0][0][0] + assert isinstance(telegram, dict) + + assert float(telegram[obis.CURRENT_ELECTRICITY_USAGE].value) == 1.01 + assert telegram[obis.CURRENT_ELECTRICITY_USAGE].unit == 'kW' + + assert float(telegram[obis.GAS_METER_READING].value) == 1.001 + assert telegram[obis.GAS_METER_READING].unit == 'm3' From 7d28d0e3709a5ab581d754ff7075899cad516dc2 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Mon, 3 Jan 2022 21:25:19 +0100 Subject: [PATCH 65/85] Update according to coding style --- dsmr_parser/clients/rfxtrx_protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsmr_parser/clients/rfxtrx_protocol.py b/dsmr_parser/clients/rfxtrx_protocol.py index 281f4c2..e4080a2 100644 --- a/dsmr_parser/clients/rfxtrx_protocol.py +++ b/dsmr_parser/clients/rfxtrx_protocol.py @@ -49,7 +49,7 @@ class RFXtrxDSMRProtocol(DSMRProtocol): data = self.remaining_data + data - while (len(data) > 0 and (packetlength := data[0]+1) <= len(data)): + while len(data) > 0 and (packetlength := data[0] + 1) <= len(data): packettype = data[1] subtype = data[2] if (packettype == PACKETTYPE_DSMR and subtype == SUBTYPE_P1): From dd6d26670eb703a0f5db70033c79a374f6432ce0 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Tue, 4 Jan 2022 09:49:11 +0100 Subject: [PATCH 66/85] Rewrite for compatibility with python 3.6 --- dsmr_parser/clients/rfxtrx_protocol.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dsmr_parser/clients/rfxtrx_protocol.py b/dsmr_parser/clients/rfxtrx_protocol.py index e4080a2..848de71 100644 --- a/dsmr_parser/clients/rfxtrx_protocol.py +++ b/dsmr_parser/clients/rfxtrx_protocol.py @@ -49,12 +49,14 @@ class RFXtrxDSMRProtocol(DSMRProtocol): data = self.remaining_data + data - while len(data) > 0 and (packetlength := data[0] + 1) <= len(data): + packetlength = data[0] + 1 if len(data) > 0 else 1 + while packetlength <= len(data): packettype = data[1] subtype = data[2] if (packettype == PACKETTYPE_DSMR and subtype == SUBTYPE_P1): dsmr_data = data[4:packetlength] super().data_received(dsmr_data) data = data[packetlength:] + packetlength = data[0] + 1 if len(data) > 0 else 1 self.remaining_data = data From eace91b5910b85f54bf301cf5a646630533a9bec Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Tue, 4 Jan 2022 09:53:49 +0100 Subject: [PATCH 67/85] Fix coding style issues --- dsmr_parser/clients/serial_.py | 1 - dsmr_parser/objects.py | 2 +- dsmr_parser/profile_generic_specifications.py | 2 +- dsmr_parser/telegram_specifications.py | 2 +- test/test_protocol.py | 2 -- test/test_rfxtrx_protocol.py | 2 -- 6 files changed, 3 insertions(+), 8 deletions(-) diff --git a/dsmr_parser/clients/serial_.py b/dsmr_parser/clients/serial_.py index f63ff07..12d2245 100644 --- a/dsmr_parser/clients/serial_.py +++ b/dsmr_parser/clients/serial_.py @@ -1,4 +1,3 @@ -import asyncio import logging import serial import serial_asyncio diff --git a/dsmr_parser/objects.py b/dsmr_parser/objects.py index c062d9d..af7d068 100644 --- a/dsmr_parser/objects.py +++ b/dsmr_parser/objects.py @@ -179,7 +179,7 @@ class ProfileGenericObject(DSMRObject): self._buffer_list = [] values_offset = 2 for i in range(self.buffer_length): - offset = values_offset + i*2 + offset = values_offset + i * 2 self._buffer_list.append(MBusObject([self.values[offset], self.values[offset + 1]])) return self._buffer_list diff --git a/dsmr_parser/profile_generic_specifications.py b/dsmr_parser/profile_generic_specifications.py index a52416c..e753c01 100644 --- a/dsmr_parser/profile_generic_specifications.py +++ b/dsmr_parser/profile_generic_specifications.py @@ -7,4 +7,4 @@ PG_HEAD_PARSERS = [ValueParser(int), ValueParser(str)] PG_UNIDENTIFIED_BUFFERTYPE_PARSERS = [ValueParser(str), ValueParser(str)] BUFFER_TYPES = { PG_FAILURE_EVENT: [ValueParser(timestamp), ValueParser(int)] - } +} diff --git a/dsmr_parser/telegram_specifications.py b/dsmr_parser/telegram_specifications.py index 4e59f51..5a06ce0 100644 --- a/dsmr_parser/telegram_specifications.py +++ b/dsmr_parser/telegram_specifications.py @@ -157,7 +157,7 @@ LUXEMBOURG_SMARTY['objects'].update({ obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)), }) -# Source: https://www.energiforetagen.se/globalassets/energiforetagen/det-erbjuder-vi/kurser-och-konferenser/elnat/branschrekommendation-lokalt-granssnitt-v2_0-201912.pdf +# Source: https://www.energiforetagen.se/globalassets/energiforetagen/det-erbjuder-vi/kurser-och-konferenser/elnat/branschrekommendation-lokalt-granssnitt-v2_0-201912.pdf # noqa SWEDEN = { 'checksum_support': True, 'objects': { diff --git a/test/test_protocol.py b/test/test_protocol.py index c298d5c..d1393f3 100644 --- a/test/test_protocol.py +++ b/test/test_protocol.py @@ -3,8 +3,6 @@ from unittest.mock import Mock import unittest from dsmr_parser import obis_references as obis -from dsmr_parser import telegram_specifications -from dsmr_parser.parsers import TelegramParser from dsmr_parser.clients.protocol import create_dsmr_protocol diff --git a/test/test_rfxtrx_protocol.py b/test/test_rfxtrx_protocol.py index 14f54bc..7c79d22 100644 --- a/test/test_rfxtrx_protocol.py +++ b/test/test_rfxtrx_protocol.py @@ -3,8 +3,6 @@ from unittest.mock import Mock import unittest from dsmr_parser import obis_references as obis -from dsmr_parser import telegram_specifications -from dsmr_parser.parsers import TelegramParser from dsmr_parser.clients.rfxtrx_protocol import create_rfxtrx_dsmr_protocol, PACKETTYPE_DSMR, SUBTYPE_P1 From 9f66c075d69f695c6376c0b18d69b4585a61494a Mon Sep 17 00:00:00 2001 From: Nigel Dokter Date: Tue, 4 Jan 2022 21:14:53 +0100 Subject: [PATCH 68/85] Prepare version 0.32 --- CHANGELOG.rst | 3 +++ setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d320b73..3a8876c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,9 @@ Change Log ---------- +**0.32** (2022-01-04) +- Support DSMR data read via RFXtrx with integrated P1 reader (`pull request #98 `_). + **0.31** (2021-11-21) - Support for (German) EasyMeter Q3D using COM-1 Ethernet Gateway (`pull request #92 `_). diff --git a/setup.py b/setup.py index 3852064..197759a 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( author_email='nigel@nldr.net', license='MIT', url='https://github.com/ndokter/dsmr_parser', - version='0.31', + version='0.32', packages=find_packages(exclude=('test', 'test.*')), install_requires=[ 'pyserial>=3,<4', From 5c838efbe1826e2c3e717b065af34a562413d587 Mon Sep 17 00:00:00 2001 From: Nigel Dokter Date: Tue, 4 Jan 2022 21:16:03 +0100 Subject: [PATCH 69/85] Fix CHANGELOG formatting --- CHANGELOG.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3a8876c..497c0a7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,35 +2,45 @@ Change Log ---------- **0.32** (2022-01-04) + - Support DSMR data read via RFXtrx with integrated P1 reader (`pull request #98 `_). **0.31** (2021-11-21) + - Support for (German) EasyMeter Q3D using COM-1 Ethernet Gateway (`pull request #92 `_). **0.30** (2021-08-18) + - Add support for Swedish smart meters (`pull request #86 `_). **0.29** (2021-04-18) + - Add value and unit properties to ProfileGenericObject to make sure that code like iterators that rely on that do not break (`pull request #71 `_). Remove deprecated asyncio coroutine decorator (`pull request #76 `_). **0.28** (2021-02-21) + - Optional keep alive monitoring for TCP/IP connections (`pull request #73 `_). - Catch parse errors in TelegramParser, ignore lines that can not be parsed (`pull request #74 `_). **0.27** (2020-12-24) + - fix for empty parentheses in ProfileGenericParser (redone) (`pull request #69 `_). **0.26** (2020-12-15) + - reverted fix for empty parentheses in ProfileGenericParser (`pull request #68 `_). **0.25** (2020-12-14) + - fix for empty parentheses in ProfileGenericParser (`pull request #57 `_). **0.24** (2020-11-27) + - Add Luxembourg equipment identifier (`pull request #62 `_). **0.23** (2020-11-07) + - Resolved issue with x-x:24.3.0 where it contains non-integer character (`pull request #61 `_). - Tests are not installed anymore (`pull request #59 `_). - Example telegram improvement (`pull request #58 `_). From 0340b25733bd9c51b2cabc0b12a7625cecbfd68a Mon Sep 17 00:00:00 2001 From: Nigel Dokter Date: Mon, 21 Feb 2022 13:38:06 +0100 Subject: [PATCH 70/85] Update README.rst --- README.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.rst b/README.rst index b97d440..a166aec 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,3 @@ -**Notice:** this repository is in need of a new maintainer. If you are interested or have ideas about this, please let me know. - - DSMR Parser =========== From a6bc66667bdd316c89ac8027d00272f9f047be9e Mon Sep 17 00:00:00 2001 From: Nigel Dokter Date: Mon, 21 Feb 2022 20:05:23 +0100 Subject: [PATCH 71/85] fix FileReader from not completing --- dsmr_parser/clients/filereader.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dsmr_parser/clients/filereader.py b/dsmr_parser/clients/filereader.py index 061eda7..9b9cf6e 100644 --- a/dsmr_parser/clients/filereader.py +++ b/dsmr_parser/clients/filereader.py @@ -64,8 +64,11 @@ class FileReader(object): with open(self._file, "rb") as file_handle: while True: data = file_handle.readline() - str = data.decode() - self.telegram_buffer.append(str) + + if not data: + break + + self.telegram_buffer.append(data.decode()) for telegram in self.telegram_buffer.get_all(): try: From 7610646e10b4f725cb7e6a4466bbc24a2bffac1a Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 22 Feb 2022 09:50:11 +0100 Subject: [PATCH 72/85] Add FileReader test --- test/test_filereader.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 test/test_filereader.py diff --git a/test/test_filereader.py b/test/test_filereader.py new file mode 100644 index 0000000..857a111 --- /dev/null +++ b/test/test_filereader.py @@ -0,0 +1,21 @@ +import unittest +import tempfile + +from dsmr_parser.clients.filereader import FileReader +from dsmr_parser.telegram_specifications import V5 +from test.example_telegrams import TELEGRAM_V5 + + +class FileReaderTest(unittest.TestCase): + def test_read_as_object(self): + with tempfile.NamedTemporaryFile() as file: + with open(file.name, "w") as f: + f.write(TELEGRAM_V5) + + telegrams = [] + reader = FileReader(file=file.name, telegram_specification=V5) + # Call + for telegram in reader.read_as_object(): + telegrams.append(telegram) + + self.assertEqual(len(telegrams), 1) From 3f41a73b9d98bb1d97e003ab66737f0c9b2b13e8 Mon Sep 17 00:00:00 2001 From: gigatexel <65073191+gigatexel@users.noreply.github.com> Date: Wed, 6 Apr 2022 13:28:57 +0200 Subject: [PATCH 73/85] Update obis_references.py --- dsmr_parser/obis_references.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dsmr_parser/obis_references.py b/dsmr_parser/obis_references.py index d4a4cbf..b52d1e6 100644 --- a/dsmr_parser/obis_references.py +++ b/dsmr_parser/obis_references.py @@ -65,7 +65,9 @@ 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_HOURLY_GAS_METER_READING = r'\d-\d:24\.2\.3.+?\r\n' # Different code, same format. +BELGIUM_5MIN_GAS_METER_READING = r'\d-\d:24\.2\.3.+?\r\n' # Different code, same format. +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 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) From 527730781c5b6cfac42b5b644fba3f6d3254c5ed Mon Sep 17 00:00:00 2001 From: gigatexel <65073191+gigatexel@users.noreply.github.com> Date: Wed, 6 Apr 2022 13:30:12 +0200 Subject: [PATCH 74/85] Update obis_name_mapping.py --- dsmr_parser/obis_name_mapping.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dsmr_parser/obis_name_mapping.py b/dsmr_parser/obis_name_mapping.py index b224b7a..7490b06 100644 --- a/dsmr_parser/obis_name_mapping.py +++ b/dsmr_parser/obis_name_mapping.py @@ -50,7 +50,9 @@ EN = { obis.ACTUAL_TRESHOLD_ELECTRICITY: 'ACTUAL_TRESHOLD_ELECTRICITY', obis.ACTUAL_SWITCH_POSITION: 'ACTUAL_SWITCH_POSITION', obis.VALVE_POSITION_GAS: 'VALVE_POSITION_GAS', - obis.BELGIUM_HOURLY_GAS_METER_READING: 'BELGIUM_HOURLY_GAS_METER_READING', + obis.BELGIUM_5MIN_GAS_METER_READING: 'BELGIUM_5MIN_GAS_METER_READING', + obis.BELGIUM_MAX_POWER_PER_PHASE: 'BELGIUM_MAX_POWER_PER_PHASE', + obis.BELGIUM_MAX_CURRENT_PER_PHASE : 'BELGIUM_MAX_CURRENT_PER_PHASE', obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: 'LUXEMBOURG_EQUIPMENT_IDENTIFIER', obis.Q3D_EQUIPMENT_IDENTIFIER: 'Q3D_EQUIPMENT_IDENTIFIER', obis.Q3D_EQUIPMENT_STATE: 'Q3D_EQUIPMENT_STATE', From 15b3653a0270cdb7307161e5000f9b4d88420e7f Mon Sep 17 00:00:00 2001 From: gigatexel <65073191+gigatexel@users.noreply.github.com> Date: Wed, 6 Apr 2022 13:30:58 +0200 Subject: [PATCH 75/85] Update telegram_specifications.py --- dsmr_parser/telegram_specifications.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/dsmr_parser/telegram_specifications.py b/dsmr_parser/telegram_specifications.py index c8f05a5..f14cef2 100644 --- a/dsmr_parser/telegram_specifications.py +++ b/dsmr_parser/telegram_specifications.py @@ -144,10 +144,18 @@ ALL = (V2_2, V3, V4, V5) BELGIUM_FLUVIUS = deepcopy(V5) BELGIUM_FLUVIUS['objects'].update({ - obis.BELGIUM_HOURLY_GAS_METER_READING: MBusParser( + obis.BELGIUM_5MIN_GAS_METER_READING: MBusParser( ValueParser(timestamp), ValueParser(Decimal) - ) + ), + obis.BELGIUM_MAX_POWER_PER_PHASE: MBusParser( + ValueParser(timestamp), + ValueParser(Decimal) + ), + obis.BELGIUM_MAX_CURRENT_PER_PHASE: MBusParser( + ValueParser(timestamp), + ValueParser(Decimal) + ), }) LUXEMBOURG_SMARTY = deepcopy(V5) From 99ab86fffb2e7e2d785b29bf92c7065b0ddf995f Mon Sep 17 00:00:00 2001 From: gigatexel <65073191+gigatexel@users.noreply.github.com> Date: Fri, 8 Apr 2022 09:56:36 +0200 Subject: [PATCH 76/85] Update telegram_specifications.py --- dsmr_parser/telegram_specifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsmr_parser/telegram_specifications.py b/dsmr_parser/telegram_specifications.py index f14cef2..07f60cb 100644 --- a/dsmr_parser/telegram_specifications.py +++ b/dsmr_parser/telegram_specifications.py @@ -155,7 +155,7 @@ BELGIUM_FLUVIUS['objects'].update({ obis.BELGIUM_MAX_CURRENT_PER_PHASE: MBusParser( ValueParser(timestamp), ValueParser(Decimal) - ), + ), }) LUXEMBOURG_SMARTY = deepcopy(V5) From 3d5599289ebe9d3f7f0fcc693c12e721c1b510c0 Mon Sep 17 00:00:00 2001 From: gigatexel <65073191+gigatexel@users.noreply.github.com> Date: Fri, 8 Apr 2022 09:57:02 +0200 Subject: [PATCH 77/85] Update obis_name_mapping.py --- dsmr_parser/obis_name_mapping.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsmr_parser/obis_name_mapping.py b/dsmr_parser/obis_name_mapping.py index 7490b06..d5a6d92 100644 --- a/dsmr_parser/obis_name_mapping.py +++ b/dsmr_parser/obis_name_mapping.py @@ -52,7 +52,7 @@ EN = { obis.VALVE_POSITION_GAS: 'VALVE_POSITION_GAS', obis.BELGIUM_5MIN_GAS_METER_READING: 'BELGIUM_5MIN_GAS_METER_READING', obis.BELGIUM_MAX_POWER_PER_PHASE: 'BELGIUM_MAX_POWER_PER_PHASE', - obis.BELGIUM_MAX_CURRENT_PER_PHASE : 'BELGIUM_MAX_CURRENT_PER_PHASE', + obis.BELGIUM_MAX_CURRENT_PER_PHASE: 'BELGIUM_MAX_CURRENT_PER_PHASE', obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: 'LUXEMBOURG_EQUIPMENT_IDENTIFIER', obis.Q3D_EQUIPMENT_IDENTIFIER: 'Q3D_EQUIPMENT_IDENTIFIER', obis.Q3D_EQUIPMENT_STATE: 'Q3D_EQUIPMENT_STATE', From 0ed5fc02203dfaccff3056fe992b98778ebd77ad Mon Sep 17 00:00:00 2001 From: gigatexel <65073191+gigatexel@users.noreply.github.com> Date: Fri, 8 Apr 2022 14:09:56 +0200 Subject: [PATCH 78/85] Update telegram_specifications.py --- dsmr_parser/telegram_specifications.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/dsmr_parser/telegram_specifications.py b/dsmr_parser/telegram_specifications.py index 07f60cb..4abe328 100644 --- a/dsmr_parser/telegram_specifications.py +++ b/dsmr_parser/telegram_specifications.py @@ -148,14 +148,10 @@ BELGIUM_FLUVIUS['objects'].update({ ValueParser(timestamp), ValueParser(Decimal) ), - obis.BELGIUM_MAX_POWER_PER_PHASE: MBusParser( - ValueParser(timestamp), - ValueParser(Decimal) - ), - obis.BELGIUM_MAX_CURRENT_PER_PHASE: 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)), }) LUXEMBOURG_SMARTY = deepcopy(V5) From e5eddd006e63fb7ffa3e088069fdcee1938f3e45 Mon Sep 17 00:00:00 2001 From: gigatexel <65073191+gigatexel@users.noreply.github.com> Date: Fri, 8 Apr 2022 14:20:11 +0200 Subject: [PATCH 79/85] Update telegram_specifications.py --- dsmr_parser/telegram_specifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsmr_parser/telegram_specifications.py b/dsmr_parser/telegram_specifications.py index 4abe328..ca2f23f 100644 --- a/dsmr_parser/telegram_specifications.py +++ b/dsmr_parser/telegram_specifications.py @@ -151,7 +151,7 @@ BELGIUM_FLUVIUS['objects'].update({ 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)), + obis.VALVE_POSITION_GAS: CosemParser(ValueParser(str)), }) LUXEMBOURG_SMARTY = deepcopy(V5) From bc6eab73debbe7dc66a775967435c0359c65e08a Mon Sep 17 00:00:00 2001 From: Dennis Siemensma Date: Wed, 20 Apr 2022 21:25:24 +0200 Subject: [PATCH 80/85] Replace legacy Travis status badge with GitHub Actions --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index a166aec..d53d649 100644 --- a/README.rst +++ b/README.rst @@ -4,8 +4,8 @@ DSMR Parser .. image:: https://img.shields.io/pypi/v/dsmr-parser.svg :target: https://pypi.python.org/pypi/dsmr-parser -.. image:: https://travis-ci.org/ndokter/dsmr_parser.svg?branch=master - :target: https://travis-ci.org/ndokter/dsmr_parser +.. image:: https://img.shields.io/github/workflow/status/ndokter/dsmr_parser/Tests/master + :target: https://github.com/ndokter/dsmr_parser/actions/workflows/tests.yml A library for parsing Dutch Smart Meter Requirements (DSMR) telegram data. It also includes client implementation to directly read and parse smart meter data. From f5bedb1e6eafb9ca70bd2dcd913b995398bf727f Mon Sep 17 00:00:00 2001 From: Dennis Siemensma Date: Wed, 20 Apr 2022 21:26:07 +0200 Subject: [PATCH 81/85] CI now tests Python 3.10 as well --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5f6b2b0..0d9d338 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,6 +16,7 @@ jobs: - 3.7 - 3.8 - 3.9 + - 3.10 name: Python ${{ matrix.python-version }} steps: From 602ed4928a2df3ffa8fdbaa694829278384dd5de Mon Sep 17 00:00:00 2001 From: Dennis Siemensma Date: Wed, 20 Apr 2022 21:28:31 +0200 Subject: [PATCH 82/85] YAML fix for CI config --- .github/workflows/tests.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0d9d338..e3b6f4d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,11 +12,11 @@ jobs: strategy: matrix: python-version: - - 3.6 - - 3.7 - - 3.8 - - 3.9 - - 3.10 + - '3.6' + - '3.7' + - '3.8' + - '3.9' + - '3.10' name: Python ${{ matrix.python-version }} steps: From 32c20b61ac0746af714f52a55fc77586ce39eb27 Mon Sep 17 00:00:00 2001 From: Dennis Siemensma Date: Wed, 20 Apr 2022 21:29:08 +0200 Subject: [PATCH 83/85] Refer to CI config for the currently supported/tested Python versions --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index d53d649..83f1e2f 100644 --- a/README.rst +++ b/README.rst @@ -14,7 +14,7 @@ also includes client implementation to directly read and parse smart meter data. Features -------- -DSMR Parser supports DSMR versions 2, 3, 4 and 5. It has been tested with Python 3.5, 3.6, 3.7, 3.8 and 3.9. +DSMR Parser supports DSMR versions 2, 3, 4 and 5. See for the [currently supported/tested Python versions here](https://github.com/ndokter/dsmr_parser/blob/master/.github/workflows/tests.yml#L14). Client module usage From 179a75e58c31953135d22d35021fe4a67a8b762f Mon Sep 17 00:00:00 2001 From: Dennis Siemensma Date: Wed, 20 Apr 2022 21:30:50 +0200 Subject: [PATCH 84/85] Typo fix --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 83f1e2f..1b7fe63 100644 --- a/README.rst +++ b/README.rst @@ -155,7 +155,7 @@ Example to get some of the values: gas_reading = telegram[obis_references.HOURLY_GAS_METER_READING] # See dsmr_reader.obis_references for all readable telegram values. - # Note that the avilable values differ per DSMR version. + # Note that the available values differ per DSMR version. Telegram as an Object --------------------- From f238eb14a12143290bdffe64c6989c6a8e55467d Mon Sep 17 00:00:00 2001 From: Dennis Siemensma Date: Wed, 20 Apr 2022 21:32:41 +0200 Subject: [PATCH 85/85] Converted markdown link to restructured link --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 1b7fe63..071a58a 100644 --- a/README.rst +++ b/README.rst @@ -14,7 +14,7 @@ also includes client implementation to directly read and parse smart meter data. Features -------- -DSMR Parser supports DSMR versions 2, 3, 4 and 5. See for the [currently supported/tested Python versions here](https://github.com/ndokter/dsmr_parser/blob/master/.github/workflows/tests.yml#L14). +DSMR Parser supports DSMR versions 2, 3, 4 and 5. See for the `currently supported/tested Python versions here `_. Client module usage