Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autodetecttelegram #166

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
Change Log
----------

**1.5.0** (2024-08-25)
- Allow the telegram specification to optionally be autodetected (`PR #87 <https://github.com/ndokter/dsmr_parser/pull/87>`_ by `clonerswords <https://github.com/clonerswords>`_)

**1.4.2** (2024-07-14)

- Bump Github Actions to latest versions in favor of Node deprecations (`PR #159 <https://github.com/ndokter/dsmr_parser/pull/159>`_ by `dennissiemensma <https://github.com/dennissiemensma>`_)
Expand Down Expand Up @@ -167,7 +170,7 @@ Remove deprecated asyncio coroutine decorator (`PR #76 <https://github.com/ndokt
**0.12** (2018-09-23)

- Add serial settings for DSMR v5.0 (`PR #31 <https://github.com/ndokter/dsmr_parser/pull/31>`_).
- Lux-creos-obis-1.8.0 (`PR #32 <https://github.com/ndokter/dsmr_parser/pull/32>`_).
- Lux-creos-obis-1.8.0 (`PR #32 <https://github.com/ndokter/dsmr_parser/pull/32>`_).

**0.11** (2017-09-18)

Expand Down
4 changes: 4 additions & 0 deletions dsmr_parser/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ class ParseError(Exception):

class InvalidChecksumError(ParseError):
pass


class TelegramSpecificationMatchError(ParseError):
pass
55 changes: 46 additions & 9 deletions dsmr_parser/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from dlms_cosem.protocol.xdlms import GeneralGlobalCipher

from dsmr_parser.objects import MBusObject, MBusObjectPeak, CosemObject, ProfileGenericObject, Telegram
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
from dsmr_parser.exceptions import ParseError, InvalidChecksumError, TelegramSpecificationMatchError
from dsmr_parser.value_types import timestamp

logger = logging.getLogger(__name__)
Expand All @@ -18,20 +18,17 @@
class TelegramParser(object):
crc16_tab = []

def __init__(self, telegram_specification, apply_checksum_validation=True):
def __init__(self, telegram_specification=None, apply_checksum_validation=True):
"""
:param telegram_specification: determines how the telegram is parsed
:param telegram_specification: determines how the telegram is parsed.
Will attempt to autodetect if omitted.
:param apply_checksum_validation: validate checksum if applicable for
telegram DSMR version (v4 and up).
:type telegram_specification: dict
"""
self.apply_checksum_validation = apply_checksum_validation
self.telegram_specification = telegram_specification
# Regexes are compiled once to improve performance
self.telegram_specification_regexes = {
object["obis_reference"]: re.compile(object["obis_reference"], re.DOTALL | re.MULTILINE)
for object in self.telegram_specification['objects']
}
self._telegram_specification_regex = None

def parse(self, telegram_data, encryption_key="", authentication_key="", throw_ex=False): # noqa: C901
"""
Expand All @@ -46,6 +43,15 @@ def parse(self, telegram_data, encryption_key="", authentication_key="", throw_e
:raises ParseError:
:raises InvalidChecksumError:
"""
if not self.telegram_specification:
self.telegram_specification = \
match_telegram_specification(telegram_data)
if not self._telegram_specification_regex:
# Regexes are compiled once to improve performance
self._telegram_specification_regexes = {
object["obis_reference"]: re.compile(object["obis_reference"], re.DOTALL | re.MULTILINE)
for object in self.telegram_specification['objects']
}

if "general_global_cipher" in self.telegram_specification:
if self.telegram_specification["general_global_cipher"]:
Expand Down Expand Up @@ -85,7 +91,7 @@ def parse(self, telegram_data, encryption_key="", authentication_key="", throw_e
telegram = Telegram()

for object in self.telegram_specification['objects']:
pattern = self.telegram_specification_regexes[object["obis_reference"]]
pattern = self._telegram_specification_regexes[object["obis_reference"]]
matches = pattern.findall(telegram_data)

# Some signatures are optional and may not be present,
Expand Down Expand Up @@ -173,6 +179,37 @@ def crc16(telegram):
return crcValue


def match_telegram_specification(telegram_data):
"""
Find telegram specification that matches the telegram data by trying all
specifications.

Could be further optimized to check the actual 0.2.8 OBIS reference which
is available for DSMR version 4 and up.

:param str telegram_data: full telegram from start ('/') to checksum
('!ABCD') including line endings in between the telegram's lines
:return: telegram specification
:rtype: dict
"""
# Prevent circular import
from dsmr_parser import telegram_specifications

for specification in telegram_specifications.ALL:
try:
TelegramParser(specification).parse(telegram_data)
except ParseError:
pass
else:
return specification

raise TelegramSpecificationMatchError(
'Could automatically match telegram specification. Make sure the data'
'is not corrupt. Alternatively manually specify one.'
)



class DSMRObjectParser(object):
"""
Parses an object (can also be see as a 'line') from a telegram.
Expand Down
16 changes: 14 additions & 2 deletions dsmr_parser/telegram_specifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,8 +486,6 @@
]
}

ALL = (V2_2, V3, V4, V5)

BELGIUM_FLUVIUS = {
'checksum_support': True,
'objects': [
Expand Down Expand Up @@ -1368,3 +1366,17 @@
}
]
}

ALL = (
V2_2,
V3,
V4,
V5,
BELGIUM_FLUVIUS,
LUXEMBOURG_SMARTY,
SWEDEN,
SAGEMCOM_T210_D_R,
AUSTRIA_ENERGIENETZE_STEIERMARK,
ISKRA_IE,
EON_HUNGARY
)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
author_email='[email protected]',
license='MIT',
url='https://github.com/ndokter/dsmr_parser',
version='1.4.2',
version='1.5.0',
packages=find_packages(exclude=('test', 'test.*')),
install_requires=[
'pyserial>=3,<4',
Expand Down
29 changes: 29 additions & 0 deletions test/test_match_telegram_specification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import unittest

from dsmr_parser.exceptions import TelegramSpecificationMatchError
from dsmr_parser.parsers import match_telegram_specification
from dsmr_parser import telegram_specifications
from test import example_telegrams


class MatchTelegramSpecificationTest(unittest.TestCase):

def test_v2_2(self):
assert match_telegram_specification(example_telegrams.TELEGRAM_V2_2) \
== telegram_specifications.V2_2

def test_v3(self):
assert match_telegram_specification(example_telegrams.TELEGRAM_V3) \
== telegram_specifications.V3

def test_v4_2(self):
assert match_telegram_specification(example_telegrams.TELEGRAM_V4_2) \
== telegram_specifications.V4

def test_v5(self):
assert match_telegram_specification(example_telegrams.TELEGRAM_V5) \
== telegram_specifications.V5

def test_malformed_telegram(self):
with self.assertRaises(TelegramSpecificationMatchError):
match_telegram_specification(example_telegrams.TELEGRAM_V5[:-4])
9 changes: 9 additions & 0 deletions test/test_parse_v2_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@
class TelegramParserV2_2Test(unittest.TestCase):
""" Test parsing of a DSMR v2.2 telegram. """

def test_telegram_specification_matching(self):
parser = TelegramParser()
parser.parse(TELEGRAM_V2_2)

self.assertEqual(
parser.telegram_specification,
telegram_specifications.V2_2
)

def test_parse(self):
parser = TelegramParser(telegram_specifications.V2_2)
try:
Expand Down
9 changes: 9 additions & 0 deletions test/test_parse_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@
class TelegramParserV3Test(unittest.TestCase):
""" Test parsing of a DSMR v3 telegram. """

def test_telegram_specification_matching(self):
parser = TelegramParser()
parser.parse(TELEGRAM_V3)

self.assertEqual(
parser.telegram_specification,
telegram_specifications.V3
)

def test_parse(self):
parser = TelegramParser(telegram_specifications.V3)
try:
Expand Down
9 changes: 9 additions & 0 deletions test/test_parse_v4_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@
class TelegramParserV4_2Test(unittest.TestCase):
""" Test parsing of a DSMR v4.2 telegram. """

def test_telegram_specification_matching(self):
parser = TelegramParser()
parser.parse(TELEGRAM_V4_2)

self.assertEqual(
parser.telegram_specification,
telegram_specifications.V4
)

def test_parse(self):
parser = TelegramParser(telegram_specifications.V4)
try:
Expand Down
9 changes: 9 additions & 0 deletions test/test_parse_v5.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@
class TelegramParserV5Test(unittest.TestCase):
""" Test parsing of a DSMR v5.x telegram. """

def test_telegram_specification_matching(self):
parser = TelegramParser()
parser.parse(TELEGRAM_V5)

self.assertEqual(
parser.telegram_specification,
telegram_specifications.V5
)

def test_parse(self):
parser = TelegramParser(telegram_specifications.V5)
try:
Expand Down
Loading