From 1318204d0ccc0bbd6472f58603dfb3dbd82f571b Mon Sep 17 00:00:00 2001 From: albert Date: Mon, 7 Sep 2020 18:15:49 +0200 Subject: [PATCH 1/6] 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 2/6] 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 3/6] 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 4/6] 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 5/6] 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 6/6] 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