Progress on removing TelegramParserV2_2 and TelegramParserV4 in favor of a generic TelegramParser

This commit is contained in:
Nigel Dokter 2017-01-20 23:02:19 +01:00
parent e2e4bb36a2
commit 07634abed1
7 changed files with 70 additions and 128 deletions

View File

@ -9,7 +9,7 @@ from serial_asyncio import create_serial_connection
from dsmr_parser import telegram_specifications
from dsmr_parser.clients.telegram_buffer import TelegramBuffer
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, \
SERIAL_SETTINGS_V4
@ -18,18 +18,16 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None):
"""Creates a DSMR asyncio protocol."""
if dsmr_version == '2.2':
specifications = telegram_specifications.V2_2
telegram_parser = TelegramParserV2_2
specification = telegram_specifications.V2_2
serial_settings = SERIAL_SETTINGS_V2_2
elif dsmr_version == '4':
specifications = telegram_specifications.V4
telegram_parser = TelegramParserV4
specification = telegram_specifications.V4
serial_settings = SERIAL_SETTINGS_V4
else:
raise NotImplementedError("No telegram parser found for version: %s",
dsmr_version)
protocol = partial(DSMRProtocol, loop, telegram_parser(specifications),
protocol = partial(DSMRProtocol, loop, TelegramParser(specification),
telegram_callback=telegram_callback)
return protocol, serial_settings

View File

@ -5,10 +5,7 @@ import serial_asyncio
from dsmr_parser.clients.telegram_buffer import TelegramBuffer
from dsmr_parser.exceptions import ParseError
from dsmr_parser.parsers import TelegramParser, TelegramParserV2_2, \
TelegramParserV4
from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \
SERIAL_SETTINGS_V4
from dsmr_parser.parsers import TelegramParser
logger = logging.getLogger(__name__)
@ -21,14 +18,7 @@ class SerialReader(object):
self.serial_settings = serial_settings
self.serial_settings[self.PORT_KEY] = device
if serial_settings is SERIAL_SETTINGS_V2_2:
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_parser = TelegramParser(telegram_specification)
self.telegram_buffer = TelegramBuffer()
def read(self):

View File

@ -1,36 +1,44 @@
P1_MESSAGE_HEADER = r'1-3:0\.2\.8'
P1_MESSAGE_TIMESTAMP = r'0-0:1\.0\.0'
ELECTRICITY_USED_TARIFF_1 = r'1-0:1\.8\.1'
ELECTRICITY_USED_TARIFF_2 = r'1-0:1\.8\.2'
ELECTRICITY_DELIVERED_TARIFF_1 = r'1-0:2\.8\.1'
ELECTRICITY_DELIVERED_TARIFF_2 = r'1-0:2\.8\.2'
ELECTRICITY_ACTIVE_TARIFF = r'0-0:96\.14\.0'
EQUIPMENT_IDENTIFIER = r'0-0:96\.1\.1'
CURRENT_ELECTRICITY_USAGE = r'1-0:1\.7\.0'
CURRENT_ELECTRICITY_DELIVERY = r'1-0:2\.7\.0'
LONG_POWER_FAILURE_COUNT = r'96\.7\.9'
POWER_EVENT_FAILURE_LOG = r'99\.97\.0'
VOLTAGE_SAG_L1_COUNT = r'1-0:32\.32\.0'
VOLTAGE_SAG_L2_COUNT = r'1-0:52\.32\.0'
VOLTAGE_SAG_L3_COUNT = r'1-0:72\.32\.0'
VOLTAGE_SWELL_L1_COUNT = r'1-0:32\.36\.0'
VOLTAGE_SWELL_L2_COUNT = r'1-0:52\.36\.0'
VOLTAGE_SWELL_L3_COUNT = r'1-0:72\.36\.0'
TEXT_MESSAGE_CODE = r'0-0:96\.13\.1'
TEXT_MESSAGE = r'0-0:96\.13\.0'
DEVICE_TYPE = r'0-\d:24\.1\.0'
INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE = r'1-0:21\.7\.0'
INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE = r'1-0:41\.7\.0'
INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE = r'1-0:61\.7\.0'
INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE = r'1-0:22\.7\.0'
INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE = r'1-0:42\.7\.0'
INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE = r'1-0:62\.7\.0'
EQUIPMENT_IDENTIFIER_GAS = r'0-\d:96\.1\.0'
HOURLY_GAS_METER_READING = r'0-1:24\.2\.1'
GAS_METER_READING = r'0-\d:24\.3\.0'
ACTUAL_TRESHOLD_ELECTRICITY = r'0-0:17\.0\.0'
ACTUAL_SWITCH_POSITION = r'0-0:96\.3\.10'
VALVE_POSITION_GAS = r'0-\d:24\.4\.0'
"""
Contains the signatures of each telegram line.
Previously contained the channel + obis reference signatures, but has been
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_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'
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'
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'
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_1,

View File

@ -5,28 +5,22 @@ from PyCRC.CRC16 import CRC16
from dsmr_parser.objects import MBusObject, MBusObjectV2_2, CosemObject
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
from dsmr_parser.obis_references import GAS_METER_READING
logger = logging.getLogger(__name__)
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
:type telegram_specification: dict
"""
self.telegram_specification = telegram_specification
self.enable_checksum_validation = enable_checksum_validation
def _find_line_parser(self, line_value):
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):
def parse(self, telegram_data):
"""
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
for obis_reference, dsmr_object in parsed_lines}
if self.enable_checksum_validation:
self.validate_checksum(telegram_data)
def parse_line(self, line):
logger.debug("Parsing line '%s'", line)
telegram = {}
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:
logger.debug("No line class found for: '%s'", line)
return None, None
if match:
telegram[signature] = parser.parse(match.group(0))
return obis_reference, parser.parse(line)
class TelegramParserV4(TelegramParser):
return telegram
@staticmethod
def validate_telegram_checksum(telegram):
def validate_checksum(telegram):
"""
:param str telegram:
: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):

View File

@ -1,16 +1,16 @@
import unittest
from test.example_telegrams import TELEGRAM_V2_2
from dsmr_parser.parsers import TelegramParserV2_2
from dsmr_parser.parsers import TelegramParser
from dsmr_parser import telegram_specifications
from dsmr_parser import obis_references as obis
from test.example_telegrams import TELEGRAM_V2_2
class TelegramParserV2_2Test(unittest.TestCase):
""" Test parsing of a DSMR v2.2 telegram. """
def test_parse(self):
parser = TelegramParserV2_2(telegram_specifications.V2_2)
parser = TelegramParser(telegram_specifications.V2_2)
result = parser.parse(TELEGRAM_V2_2)
assert float(result[obis.CURRENT_ELECTRICITY_USAGE].value) == 1.01

View File

@ -4,12 +4,12 @@ import unittest
import pytz
from test.example_telegrams import TELEGRAM_V4_2
from dsmr_parser import obis_references as obis
from dsmr_parser import telegram_specifications
from dsmr_parser.exceptions import InvalidChecksumError, ParseError
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):
@ -17,7 +17,7 @@ class TelegramParserV4_2Test(unittest.TestCase):
def test_valid(self):
# No exception is raised.
TelegramParserV4.validate_telegram_checksum(TELEGRAM_V4_2)
TelegramParser.validate_checksum(TELEGRAM_V4_2)
def test_invalid(self):
# Remove the electricty used data value. This causes the checksum to
@ -28,14 +28,14 @@ class TelegramParserV4_2Test(unittest.TestCase):
)
with self.assertRaises(InvalidChecksumError):
TelegramParserV4.validate_telegram_checksum(corrupted_telegram)
TelegramParser.validate_checksum(corrupted_telegram)
def test_missing_checksum(self):
# Remove the checksum value causing a ParseError.
corrupted_telegram = TELEGRAM_V4_2.replace('!6796\r\n', '')
with self.assertRaises(ParseError):
TelegramParserV4.validate_telegram_checksum(corrupted_telegram)
TelegramParser.validate_checksum(corrupted_telegram)
def test_parse(self):
parser = TelegramParser(telegram_specifications.V4)

View File

@ -4,7 +4,7 @@ import unittest
from dsmr_parser import obis_references as obis
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
@ -35,10 +35,7 @@ TELEGRAM_V2_2 = (
class ProtocolTest(unittest.TestCase):
def setUp(self):
parser = TelegramParserV2_2
specification = telegram_specifications.V2_2
telegram_parser = parser(specification)
telegram_parser = TelegramParser(telegram_specifications.V2_2)
self.protocol = DSMRProtocol(None, telegram_parser,
telegram_callback=Mock())