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