Progress on removing TelegramParserV2_2 and TelegramParserV4 in favor of a generic TelegramParser
This commit is contained in:
		
							parent
							
								
									e2e4bb36a2
								
							
						
					
					
						commit
						07634abed1
					
				| @ -9,7 +9,7 @@ from serial_asyncio import create_serial_connection | |||||||
| from dsmr_parser import telegram_specifications | from dsmr_parser import telegram_specifications | ||||||
| from dsmr_parser.clients.telegram_buffer import TelegramBuffer | from dsmr_parser.clients.telegram_buffer import TelegramBuffer | ||||||
| from dsmr_parser.exceptions import ParseError | from dsmr_parser.exceptions import ParseError | ||||||
| from dsmr_parser.parsers import TelegramParserV2_2, TelegramParserV4 | from dsmr_parser.parsers import TelegramParser | ||||||
| from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \ | from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \ | ||||||
|     SERIAL_SETTINGS_V4 |     SERIAL_SETTINGS_V4 | ||||||
| 
 | 
 | ||||||
| @ -18,18 +18,16 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None): | |||||||
|     """Creates a DSMR asyncio protocol.""" |     """Creates a DSMR asyncio protocol.""" | ||||||
| 
 | 
 | ||||||
|     if dsmr_version == '2.2': |     if dsmr_version == '2.2': | ||||||
|         specifications = telegram_specifications.V2_2 |         specification = telegram_specifications.V2_2 | ||||||
|         telegram_parser = TelegramParserV2_2 |  | ||||||
|         serial_settings = SERIAL_SETTINGS_V2_2 |         serial_settings = SERIAL_SETTINGS_V2_2 | ||||||
|     elif dsmr_version == '4': |     elif dsmr_version == '4': | ||||||
|         specifications = telegram_specifications.V4 |         specification = telegram_specifications.V4 | ||||||
|         telegram_parser = TelegramParserV4 |  | ||||||
|         serial_settings = SERIAL_SETTINGS_V4 |         serial_settings = SERIAL_SETTINGS_V4 | ||||||
|     else: |     else: | ||||||
|         raise NotImplementedError("No telegram parser found for version: %s", |         raise NotImplementedError("No telegram parser found for version: %s", | ||||||
|                                   dsmr_version) |                                   dsmr_version) | ||||||
| 
 | 
 | ||||||
|     protocol = partial(DSMRProtocol, loop, telegram_parser(specifications), |     protocol = partial(DSMRProtocol, loop, TelegramParser(specification), | ||||||
|                        telegram_callback=telegram_callback) |                        telegram_callback=telegram_callback) | ||||||
| 
 | 
 | ||||||
|     return protocol, serial_settings |     return protocol, serial_settings | ||||||
|  | |||||||
| @ -5,10 +5,7 @@ import serial_asyncio | |||||||
| 
 | 
 | ||||||
| from dsmr_parser.clients.telegram_buffer import TelegramBuffer | from dsmr_parser.clients.telegram_buffer import TelegramBuffer | ||||||
| from dsmr_parser.exceptions import ParseError | from dsmr_parser.exceptions import ParseError | ||||||
| from dsmr_parser.parsers import TelegramParser, TelegramParserV2_2, \ | from dsmr_parser.parsers import TelegramParser | ||||||
|     TelegramParserV4 |  | ||||||
| from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \ |  | ||||||
|     SERIAL_SETTINGS_V4 |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| @ -21,14 +18,7 @@ class SerialReader(object): | |||||||
|         self.serial_settings = serial_settings |         self.serial_settings = serial_settings | ||||||
|         self.serial_settings[self.PORT_KEY] = device |         self.serial_settings[self.PORT_KEY] = device | ||||||
| 
 | 
 | ||||||
|         if serial_settings is SERIAL_SETTINGS_V2_2: |         self.telegram_parser = TelegramParser(telegram_specification) | ||||||
|             telegram_parser = TelegramParserV2_2 |  | ||||||
|         elif serial_settings is SERIAL_SETTINGS_V4: |  | ||||||
|             telegram_parser = TelegramParserV4 |  | ||||||
|         else: |  | ||||||
|             telegram_parser = TelegramParser |  | ||||||
| 
 |  | ||||||
|         self.telegram_parser = telegram_parser(telegram_specification) |  | ||||||
|         self.telegram_buffer = TelegramBuffer() |         self.telegram_buffer = TelegramBuffer() | ||||||
| 
 | 
 | ||||||
|     def read(self): |     def read(self): | ||||||
|  | |||||||
| @ -1,36 +1,44 @@ | |||||||
| P1_MESSAGE_HEADER = r'1-3:0\.2\.8' | """ | ||||||
| P1_MESSAGE_TIMESTAMP = r'0-0:1\.0\.0' | Contains the signatures of each telegram line. | ||||||
| ELECTRICITY_USED_TARIFF_1 = r'1-0:1\.8\.1' | 
 | ||||||
| ELECTRICITY_USED_TARIFF_2 = r'1-0:1\.8\.2' | Previously contained the channel + obis reference signatures, but has been | ||||||
| ELECTRICITY_DELIVERED_TARIFF_1 = r'1-0:2\.8\.1' | refactored to full line signatures to maintain backwards compatibility. | ||||||
| ELECTRICITY_DELIVERED_TARIFF_2 = r'1-0:2\.8\.2' | Might be refactored in a backwards incompatible way as soon as proper telegram | ||||||
| ELECTRICITY_ACTIVE_TARIFF = r'0-0:96\.14\.0' | objects are introduced. | ||||||
| EQUIPMENT_IDENTIFIER = r'0-0:96\.1\.1' | """ | ||||||
| CURRENT_ELECTRICITY_USAGE = r'1-0:1\.7\.0' | P1_MESSAGE_HEADER = r'\d-\d:0\.2\.8.+?\r\n' | ||||||
| CURRENT_ELECTRICITY_DELIVERY = r'1-0:2\.7\.0' | P1_MESSAGE_TIMESTAMP = r'\d-\d:1\.0\.0.+?\r\n' | ||||||
| LONG_POWER_FAILURE_COUNT = r'96\.7\.9' | ELECTRICITY_USED_TARIFF_1 = r'\d-\d:1\.8\.1.+?\r\n' | ||||||
| POWER_EVENT_FAILURE_LOG = r'99\.97\.0' | ELECTRICITY_USED_TARIFF_2 = r'\d-\d:1\.8\.2.+?\r\n' | ||||||
| VOLTAGE_SAG_L1_COUNT = r'1-0:32\.32\.0' | ELECTRICITY_DELIVERED_TARIFF_1 = r'\d-\d:2\.8\.1.+?\r\n' | ||||||
| VOLTAGE_SAG_L2_COUNT = r'1-0:52\.32\.0' | ELECTRICITY_DELIVERED_TARIFF_2 = r'\d-\d:2\.8\.2.+?\r\n' | ||||||
| VOLTAGE_SAG_L3_COUNT = r'1-0:72\.32\.0' | ELECTRICITY_ACTIVE_TARIFF = r'\d-\d:96\.14\.0.+?\r\n' | ||||||
| VOLTAGE_SWELL_L1_COUNT = r'1-0:32\.36\.0' | EQUIPMENT_IDENTIFIER = r'\d-\d:96\.1\.1.+?\r\n' | ||||||
| VOLTAGE_SWELL_L2_COUNT = r'1-0:52\.36\.0' | CURRENT_ELECTRICITY_USAGE = r'\d-\d:1\.7\.0.+?\r\n' | ||||||
| VOLTAGE_SWELL_L3_COUNT = r'1-0:72\.36\.0' | CURRENT_ELECTRICITY_DELIVERY = r'\d-\d:2\.7\.0.+?\r\n' | ||||||
| TEXT_MESSAGE_CODE = r'0-0:96\.13\.1' | LONG_POWER_FAILURE_COUNT = r'96\.7\.9.+?\r\n' | ||||||
| TEXT_MESSAGE = r'0-0:96\.13\.0' | POWER_EVENT_FAILURE_LOG = r'99\.97\.0.+?\r\n' | ||||||
| DEVICE_TYPE = r'0-\d:24\.1\.0' | VOLTAGE_SAG_L1_COUNT = r'\d-\d:32\.32\.0.+?\r\n' | ||||||
| INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE = r'1-0:21\.7\.0' | VOLTAGE_SAG_L2_COUNT = r'\d-\d:52\.32\.0.+?\r\n' | ||||||
| INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE = r'1-0:41\.7\.0' | VOLTAGE_SAG_L3_COUNT = r'\d-\d:72\.32\.0.+?\r\n' | ||||||
| INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE = r'1-0:61\.7\.0' | VOLTAGE_SWELL_L1_COUNT = r'\d-\d:32\.36\.0.+?\r\n' | ||||||
| INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE = r'1-0:22\.7\.0' | VOLTAGE_SWELL_L2_COUNT = r'\d-\d:52\.36\.0.+?\r\n' | ||||||
| INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE = r'1-0:42\.7\.0' | VOLTAGE_SWELL_L3_COUNT = r'\d-\d:72\.36\.0.+?\r\n' | ||||||
| INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE = r'1-0:62\.7\.0' | TEXT_MESSAGE_CODE = r'\d-\d:96\.13\.1.+?\r\n' | ||||||
| EQUIPMENT_IDENTIFIER_GAS = r'0-\d:96\.1\.0' | TEXT_MESSAGE = r'\d-\d:96\.13\.0.+?\r\n' | ||||||
| HOURLY_GAS_METER_READING = r'0-1:24\.2\.1' | DEVICE_TYPE = r'\d-\d:24\.1\.0.+?\r\n' | ||||||
| GAS_METER_READING = r'0-\d:24\.3\.0' | INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE = r'\d-\d:21\.7\.0.+?\r\n' | ||||||
| ACTUAL_TRESHOLD_ELECTRICITY = r'0-0:17\.0\.0' | INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE = r'\d-\d:41\.7\.0.+?\r\n' | ||||||
| ACTUAL_SWITCH_POSITION = r'0-0:96\.3\.10' | INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE = r'\d-\d:61\.7\.0.+?\r\n' | ||||||
| VALVE_POSITION_GAS = r'0-\d:24\.4\.0' | 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' | ||||||
|  | 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' | ||||||
| 
 | 
 | ||||||
| ELECTRICITY_USED_TARIFF_ALL = ( | ELECTRICITY_USED_TARIFF_ALL = ( | ||||||
|     ELECTRICITY_USED_TARIFF_1, |     ELECTRICITY_USED_TARIFF_1, | ||||||
|  | |||||||
| @ -5,28 +5,22 @@ from PyCRC.CRC16 import CRC16 | |||||||
| 
 | 
 | ||||||
| from dsmr_parser.objects import MBusObject, MBusObjectV2_2, CosemObject | from dsmr_parser.objects import MBusObject, MBusObjectV2_2, CosemObject | ||||||
| from dsmr_parser.exceptions import ParseError, InvalidChecksumError | from dsmr_parser.exceptions import ParseError, InvalidChecksumError | ||||||
| from dsmr_parser.obis_references import GAS_METER_READING |  | ||||||
| 
 | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TelegramParser(object): | class TelegramParser(object): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, telegram_specification): |     def __init__(self, telegram_specification, | ||||||
|  |                  enable_checksum_validation=False): | ||||||
|         """ |         """ | ||||||
|         :param telegram_specification: determines how the telegram is parsed |         :param telegram_specification: determines how the telegram is parsed | ||||||
|         :type telegram_specification: dict |         :type telegram_specification: dict | ||||||
|         """ |         """ | ||||||
|         self.telegram_specification = telegram_specification |         self.telegram_specification = telegram_specification | ||||||
|  |         self.enable_checksum_validation = enable_checksum_validation | ||||||
| 
 | 
 | ||||||
|     def _find_line_parser(self, line_value): |     def parse(self, telegram_data): | ||||||
|         for obis_reference, parser in self.telegram_specification.items(): |  | ||||||
|             if re.search(obis_reference, line_value): |  | ||||||
|                 return obis_reference, parser |  | ||||||
| 
 |  | ||||||
|         return None, None |  | ||||||
| 
 |  | ||||||
|     def parse(self, telegram): |  | ||||||
|         """ |         """ | ||||||
|         Parse telegram from string to dict. |         Parse telegram from string to dict. | ||||||
| 
 | 
 | ||||||
| @ -44,28 +38,22 @@ class TelegramParser(object): | |||||||
|                 .. |                 .. | ||||||
|             } |             } | ||||||
|         """ |         """ | ||||||
|         telegram_lines = telegram.splitlines() |  | ||||||
|         parsed_lines = map(self.parse_line, telegram_lines) |  | ||||||
| 
 | 
 | ||||||
|         return {obis_reference: dsmr_object |         if self.enable_checksum_validation: | ||||||
|                 for obis_reference, dsmr_object in parsed_lines} |             self.validate_checksum(telegram_data) | ||||||
| 
 | 
 | ||||||
|     def parse_line(self, line): |         telegram = {} | ||||||
|         logger.debug("Parsing line '%s'", line) |  | ||||||
| 
 | 
 | ||||||
|         obis_reference, parser = self._find_line_parser(line) |         for signature, parser in self.telegram_specification.items(): | ||||||
|  |             match = re.search(signature, telegram_data, re.DOTALL) | ||||||
| 
 | 
 | ||||||
|         if not obis_reference: |             if match: | ||||||
|             logger.debug("No line class found for: '%s'", line) |                 telegram[signature] = parser.parse(match.group(0)) | ||||||
|             return None, None |  | ||||||
| 
 | 
 | ||||||
|         return obis_reference, parser.parse(line) |         return telegram | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class TelegramParserV4(TelegramParser): |  | ||||||
| 
 | 
 | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def validate_telegram_checksum(telegram): |     def validate_checksum(telegram): | ||||||
|         """ |         """ | ||||||
|         :param str telegram: |         :param str telegram: | ||||||
|         :raises ParseError: |         :raises ParseError: | ||||||
| @ -97,45 +85,6 @@ class TelegramParserV4(TelegramParser): | |||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|     def parse(self, telegram): |  | ||||||
|         """ |  | ||||||
|         :param str telegram: |  | ||||||
|         :rtype: dict |  | ||||||
|         """ |  | ||||||
|         self.validate_telegram_checksum(telegram) |  | ||||||
| 
 |  | ||||||
|         return super().parse(telegram) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class TelegramParserV2_2(TelegramParser): |  | ||||||
| 
 |  | ||||||
|     def parse(self, telegram): |  | ||||||
|         """ |  | ||||||
|         :param str telegram: |  | ||||||
|         :rtype: dict |  | ||||||
|         """ |  | ||||||
| 
 |  | ||||||
|         # TODO fix this in the specification: telegram_specifications.V2_2 |  | ||||||
|         def join_lines(telegram): |  | ||||||
|             """Join lines for gas meter.""" |  | ||||||
|             join_next = re.compile(GAS_METER_READING) |  | ||||||
| 
 |  | ||||||
|             join = None |  | ||||||
|             for line_value in telegram.splitlines(): |  | ||||||
|                 if join: |  | ||||||
|                     yield join + line_value |  | ||||||
|                     join = None |  | ||||||
|                 elif join_next.match(line_value): |  | ||||||
|                     join = line_value |  | ||||||
|                 else: |  | ||||||
|                     yield line_value |  | ||||||
| 
 |  | ||||||
|         # TODO temporary workaround |  | ||||||
|         lines = join_lines(telegram) |  | ||||||
|         telegram = '\r\n'.join(lines) |  | ||||||
| 
 |  | ||||||
|         return super().parse(telegram) |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| class DSMRObjectParser(object): | class DSMRObjectParser(object): | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,16 +1,16 @@ | |||||||
| import unittest | import unittest | ||||||
| 
 | 
 | ||||||
| from test.example_telegrams import TELEGRAM_V2_2 | from dsmr_parser.parsers import TelegramParser | ||||||
| from dsmr_parser.parsers import TelegramParserV2_2 |  | ||||||
| from dsmr_parser import telegram_specifications | from dsmr_parser import telegram_specifications | ||||||
| from dsmr_parser import obis_references as obis | from dsmr_parser import obis_references as obis | ||||||
|  | from test.example_telegrams import TELEGRAM_V2_2 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TelegramParserV2_2Test(unittest.TestCase): | class TelegramParserV2_2Test(unittest.TestCase): | ||||||
|     """ Test parsing of a DSMR v2.2 telegram. """ |     """ Test parsing of a DSMR v2.2 telegram. """ | ||||||
| 
 | 
 | ||||||
|     def test_parse(self): |     def test_parse(self): | ||||||
|         parser = TelegramParserV2_2(telegram_specifications.V2_2) |         parser = TelegramParser(telegram_specifications.V2_2) | ||||||
|         result = parser.parse(TELEGRAM_V2_2) |         result = parser.parse(TELEGRAM_V2_2) | ||||||
| 
 | 
 | ||||||
|         assert float(result[obis.CURRENT_ELECTRICITY_USAGE].value) == 1.01 |         assert float(result[obis.CURRENT_ELECTRICITY_USAGE].value) == 1.01 | ||||||
|  | |||||||
| @ -4,12 +4,12 @@ import unittest | |||||||
| 
 | 
 | ||||||
| import pytz | import pytz | ||||||
| 
 | 
 | ||||||
| from test.example_telegrams import TELEGRAM_V4_2 |  | ||||||
| from dsmr_parser import obis_references as obis | from dsmr_parser import obis_references as obis | ||||||
| from dsmr_parser import telegram_specifications | from dsmr_parser import telegram_specifications | ||||||
| from dsmr_parser.exceptions import InvalidChecksumError, ParseError | from dsmr_parser.exceptions import InvalidChecksumError, ParseError | ||||||
| from dsmr_parser.objects import CosemObject, MBusObject | from dsmr_parser.objects import CosemObject, MBusObject | ||||||
| from dsmr_parser.parsers import TelegramParser, TelegramParserV4 | from dsmr_parser.parsers import TelegramParser | ||||||
|  | from test.example_telegrams import TELEGRAM_V4_2 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TelegramParserV4_2Test(unittest.TestCase): | class TelegramParserV4_2Test(unittest.TestCase): | ||||||
| @ -17,7 +17,7 @@ class TelegramParserV4_2Test(unittest.TestCase): | |||||||
| 
 | 
 | ||||||
|     def test_valid(self): |     def test_valid(self): | ||||||
|         # No exception is raised. |         # No exception is raised. | ||||||
|         TelegramParserV4.validate_telegram_checksum(TELEGRAM_V4_2) |         TelegramParser.validate_checksum(TELEGRAM_V4_2) | ||||||
| 
 | 
 | ||||||
|     def test_invalid(self): |     def test_invalid(self): | ||||||
|         # Remove the electricty used data value. This causes the checksum to |         # Remove the electricty used data value. This causes the checksum to | ||||||
| @ -28,14 +28,14 @@ class TelegramParserV4_2Test(unittest.TestCase): | |||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         with self.assertRaises(InvalidChecksumError): |         with self.assertRaises(InvalidChecksumError): | ||||||
|             TelegramParserV4.validate_telegram_checksum(corrupted_telegram) |             TelegramParser.validate_checksum(corrupted_telegram) | ||||||
| 
 | 
 | ||||||
|     def test_missing_checksum(self): |     def test_missing_checksum(self): | ||||||
|         # Remove the checksum value causing a ParseError. |         # Remove the checksum value causing a ParseError. | ||||||
|         corrupted_telegram = TELEGRAM_V4_2.replace('!6796\r\n', '') |         corrupted_telegram = TELEGRAM_V4_2.replace('!6796\r\n', '') | ||||||
| 
 | 
 | ||||||
|         with self.assertRaises(ParseError): |         with self.assertRaises(ParseError): | ||||||
|             TelegramParserV4.validate_telegram_checksum(corrupted_telegram) |             TelegramParser.validate_checksum(corrupted_telegram) | ||||||
| 
 | 
 | ||||||
|     def test_parse(self): |     def test_parse(self): | ||||||
|         parser = TelegramParser(telegram_specifications.V4) |         parser = TelegramParser(telegram_specifications.V4) | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ import unittest | |||||||
| 
 | 
 | ||||||
| from dsmr_parser import obis_references as obis | from dsmr_parser import obis_references as obis | ||||||
| from dsmr_parser import telegram_specifications | from dsmr_parser import telegram_specifications | ||||||
| from dsmr_parser.parsers import TelegramParserV2_2 | from dsmr_parser.parsers import TelegramParser | ||||||
| from dsmr_parser.clients.protocol import DSMRProtocol | from dsmr_parser.clients.protocol import DSMRProtocol | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -35,10 +35,7 @@ TELEGRAM_V2_2 = ( | |||||||
| class ProtocolTest(unittest.TestCase): | class ProtocolTest(unittest.TestCase): | ||||||
| 
 | 
 | ||||||
|     def setUp(self): |     def setUp(self): | ||||||
|         parser = TelegramParserV2_2 |         telegram_parser = TelegramParser(telegram_specifications.V2_2) | ||||||
|         specification = telegram_specifications.V2_2 |  | ||||||
| 
 |  | ||||||
|         telegram_parser = parser(specification) |  | ||||||
|         self.protocol = DSMRProtocol(None, telegram_parser, |         self.protocol = DSMRProtocol(None, telegram_parser, | ||||||
|                                      telegram_callback=Mock()) |                                      telegram_callback=Mock()) | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user