Skip to content

Commit

Permalink
Refactor of name mapping, added E.ON Hungary (#137)
Browse files Browse the repository at this point in the history
* Added EON HUNGARY specification

* refactoring obis name mapping
  • Loading branch information
balazs92117 authored Aug 1, 2023
1 parent af2e655 commit 2f1e080
Show file tree
Hide file tree
Showing 11 changed files with 1,843 additions and 410 deletions.
7 changes: 6 additions & 1 deletion dsmr_parser/clients/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs):
return protocol


def _create_dsmr_protocol(dsmr_version, telegram_callback, protocol, loop=None, **kwargs):
# pylama noqa - because of "complex" (too long) if-elif-else.
# Match - case might be a solution but it is not available in <3.10
def _create_dsmr_protocol(dsmr_version, telegram_callback, protocol, loop=None, **kwargs): #noqa
"""Creates a DSMR asyncio protocol."""

if dsmr_version == '2.2':
Expand Down Expand Up @@ -51,6 +53,9 @@ def _create_dsmr_protocol(dsmr_version, telegram_callback, protocol, loop=None,
elif dsmr_version == 'ISKRA_IE':
specification = telegram_specifications.ISKRA_IE
serial_settings = SERIAL_SETTINGS_V5
elif dsmr_version == '5EONHU':
specification = telegram_specifications.EON_HUNGARY
serial_settings = SERIAL_SETTINGS_V5
else:
raise NotImplementedError("No telegram parser found for version: %s",
dsmr_version)
Expand Down
7 changes: 6 additions & 1 deletion dsmr_parser/clients/socket_.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ def read(self):
continue

for data in lines:
self.telegram_buffer.append(data.decode('ascii'))
try:
self.telegram_buffer.append(data.decode('ascii'))
except UnicodeDecodeError:
# Some garbage came through the channel
# E.g.: Happens at EON_HUNGARY, but only once at the start of the socket.
logger.error('Failed to parse telegram due to unicode decode error')

for telegram in self.telegram_buffer.get_all():
try:
Expand Down
98 changes: 0 additions & 98 deletions dsmr_parser/obis_name_mapping.py

This file was deleted.

22 changes: 22 additions & 0 deletions dsmr_parser/obis_references.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@
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_USED_TARIFF_3 = r'^\d-\d:1\.8\.3.+?\r\n'
ELECTRICITY_USED_TARIFF_4 = r'^\d-\d:1\.8\.4.+?\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_DELIVERED_TARIFF_3 = r'^\d-\d:2\.8\.3.+?\r\n'
ELECTRICITY_DELIVERED_TARIFF_4 = r'^\d-\d:2\.8\.4.+?\r\n'
CURRENT_REACTIVE_IMPORTED = r'^\d-\d:3\.7\.0.+?\r\n'
ELECTRICITY_REACTIVE_IMPORTED_TOTAL = r'^\d-\d:3\.8\.0.+?\r\n'
ELECTRICITY_REACTIVE_IMPORTED_TARIFF_1 = r'^\d-\d:3\.8\.1.+?\r\n'
Expand Down Expand Up @@ -122,3 +126,21 @@
Q3D_EQUIPMENT_IDENTIFIER = r'^\d-\d:0\.0\.0.+?\r\n' # Logical device name
Q3D_EQUIPMENT_STATE = r'^\d-\d:96\.5\.5.+?\r\n' # Device state (hexadecimal)
Q3D_EQUIPMENT_SERIALNUMBER = r'^\d-\d:96\.1\.255.+?\r\n' # Device Serialnumber

# EON Hungary
EON_HU_ELECTRICITY_REACTIVE_TOTAL_Q1 = r'^\d-\d:5\.8\.0.+?\r\n'
EON_HU_ELECTRICITY_REACTIVE_TOTAL_Q2 = r'^\d-\d:6\.8\.0.+?\r\n'
EON_HU_ELECTRICITY_REACTIVE_TOTAL_Q3 = r'^\d-\d:7\.8\.0.+?\r\n'
EON_HU_ELECTRICITY_REACTIVE_TOTAL_Q4 = r'^\d-\d:8\.8\.0.+?\r\n'
EON_HU_ELECTRICITY_COMBINED = r'^\d-\d:15\.8\.0.+?\r\n'
EON_HU_INSTANTANEOUS_POWER_FACTOR_TOTAL = r'^\d-\d:13\.7\.0.+?\r\n'
EON_HU_INSTANTANEOUS_POWER_FACTOR_L1 = r'^\d-\d:33\.7\.0.+?\r\n'
EON_HU_INSTANTANEOUS_POWER_FACTOR_L2 = r'^\d-\d:53\.7\.0.+?\r\n'
EON_HU_INSTANTANEOUS_POWER_FACTOR_L3 = r'^\d-\d:73\.7\.0.+?\r\n'
EON_HU_FREQUENCY = r'^\d-\d:14\.7\.0.+?\r\n'
EON_HU_INSTANTANEOUS_REACTIVE_POWER_Q1 = r'^\d-\d:5\.7\.0.+?\r\n'
EON_HU_INSTANTANEOUS_REACTIVE_POWER_Q2 = r'^\d-\d:6\.7\.0.+?\r\n'
EON_HU_INSTANTANEOUS_REACTIVE_POWER_Q3 = r'^\d-\d:7\.7\.0.+?\r\n'
EON_HU_INSTANTANEOUS_REACTIVE_POWER_Q4 = r'^\d-\d:8\.7\.0.+?\r\n'
EON_HU_MAX_POWER_ON_L2 = r'^\d-\d:51\.4\.0.+?\r\n'
EON_HU_MAX_POWER_ON_L3 = r'^\d-\d:71\.4\.0.+?\r\n'
14 changes: 5 additions & 9 deletions dsmr_parser/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

import pytz

from dsmr_parser import obis_name_mapping


class Telegram(dict):
"""
Expand All @@ -27,22 +25,21 @@ def __init__(self, *args, **kwargs):
self._mbus_devices = []
super().__init__(*args, **kwargs)

def add(self, obis_reference, dsmr_object):
def add(self, obis_reference, dsmr_object, obis_name):
# Update name mapping used to get value by attribute. Example: telegram.P1_MESSAGE_HEADER
obis_name = obis_name_mapping.EN[obis_reference]
setattr(self, obis_name, dsmr_object)
if obis_name not in self._item_names: # TODO repeating obis references
self._item_names.append(obis_name)

# TODO isinstance check: MaxDemandParser (BELGIUM_MAXIMUM_DEMAND_13_MONTHS) returns a list
if isinstance(dsmr_object, DSMRObject) and dsmr_object.is_mbus_reading:
self._add_mbus(obis_reference, dsmr_object)
self._add_mbus(obis_reference, dsmr_object, obis_name)

# Fill dict which is only used for backwards compatibility
if obis_reference not in self:
self[obis_reference] = dsmr_object

def _add_mbus(self, obis_reference, dsmr_object):
def _add_mbus(self, obis_reference, dsmr_object, obis_name):
"""
The given DsmrObject is assumed to be Mbus related and will be grouped into a MbusDevice.
Grouping is done by the DsmrObject channel ID.
Expand All @@ -55,7 +52,7 @@ def _add_mbus(self, obis_reference, dsmr_object):
mbus_device = MbusDevice(channel_id=channel_id)
self._mbus_devices.append(mbus_device)

mbus_device.add(obis_reference, dsmr_object)
mbus_device.add(obis_reference, dsmr_object, obis_name)

if not hasattr(self, 'MBUS_DEVICES'):
setattr(self, 'MBUS_DEVICES', self._mbus_devices)
Expand Down Expand Up @@ -331,10 +328,9 @@ def __init__(self, channel_id):
self.channel_id = channel_id
self._item_names = []

def add(self, obis_reference, dsmr_object):
def add(self, obis_reference, dsmr_object, obis_name):
# Update name mapping used to get value by attribute. Example: telegram.P1_MESSAGE_HEADER
# Also keep track of the added names internally
obis_name = obis_name_mapping.EN[obis_reference]
setattr(self, obis_name, dsmr_object)
self._item_names.append(obis_name)

Expand Down
22 changes: 14 additions & 8 deletions dsmr_parser/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ def __init__(self, telegram_specification, apply_checksum_validation=True):
self.telegram_specification = telegram_specification
# Regexes are compiled once to improve performance
self.telegram_specification_regexes = {
signature: re.compile(signature, re.DOTALL | re.MULTILINE)
for signature in self.telegram_specification['objects'].keys()
object["obis_reference"]: re.compile(object["obis_reference"], re.DOTALL | re.MULTILINE)
for object in self.telegram_specification['objects']
}

def parse(self, telegram_data, encryption_key="", authentication_key="", throw_ex=False): # noqa: C901
Expand Down Expand Up @@ -84,25 +84,31 @@ def parse(self, telegram_data, encryption_key="", authentication_key="", throw_e

telegram = Telegram()

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

# Some signatures are optional and may not be present,
# so only parse lines that match
for match in matches:
try:
dsmr_object = parser.parse(match)
dsmr_object = object["value_parser"].parse(match)
except ParseError:
logger.error("ignore line with signature {}, because parsing failed.".format(signature),
exc_info=True)
logger.error(
"ignore line with signature {}, because parsing failed.".format(object["obis_reference"]),
exc_info=True
)
if throw_ex:
raise
except Exception as err:
logger.error("Unexpected {}: {}".format(type(err), err))
raise
else:
telegram.add(obis_reference=signature, dsmr_object=dsmr_object)
telegram.add(
obis_reference=object["obis_reference"],
dsmr_object=dsmr_object,
obis_name=object["value_name"]
)

return telegram

Expand Down
Loading

0 comments on commit 2f1e080

Please sign in to comment.