issue-51-telegram WIP

This commit is contained in:
Nigel Dokter 2023-01-15 13:59:15 +01:00
parent 3e0332963c
commit 2adcd2b207
3 changed files with 62 additions and 23 deletions

View File

@ -24,9 +24,9 @@ ELECTRICITY_ACTIVE_TARIFF = r'\d-\d:96\.14\.0.+?\r\n'
EQUIPMENT_IDENTIFIER = r'\d-\d:96\.1\.1.+?\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_USAGE = r'\d-\d:1\.7\.0.+?\r\n'
CURRENT_ELECTRICITY_DELIVERY = r'\d-\d:2\.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' LONG_POWER_FAILURE_COUNT = r'\d-\d:96\.7\.9.+?\r\n'
SHORT_POWER_FAILURE_COUNT = r'96\.7\.21.+?\r\n' SHORT_POWER_FAILURE_COUNT = r'\d-\d:96\.7\.21.+?\r\n'
POWER_EVENT_FAILURE_LOG = r'99\.97\.0.+?\r\n' POWER_EVENT_FAILURE_LOG = r'\d-\d:99\.97\.0.+?\r\n'
VOLTAGE_SAG_L1_COUNT = r'\d-\d:32\.32\.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_L2_COUNT = r'\d-\d:52\.32\.0.+?\r\n'
VOLTAGE_SAG_L3_COUNT = r'\d-\d:72\.32\.0.+?\r\n' VOLTAGE_SAG_L3_COUNT = r'\d-\d:72\.32\.0.+?\r\n'

View File

@ -24,11 +24,18 @@ class Telegram(object):
self._reverse_obis_name_mapping = dsmr_parser.obis_name_mapping.REVERSE_EN self._reverse_obis_name_mapping = dsmr_parser.obis_name_mapping.REVERSE_EN
self._item_names = self._get_item_names() self._item_names = self._get_item_names()
def add(self, obis_reference, value): def add(self, obis_reference, dsmr_object):
self._telegram_data[obis_reference].append(value) self._telegram_data[obis_reference].append(dsmr_object)
def get(self, obis_reference, channel): # TODO experiment with api to see what is nice
return next(filter(lambda x: x.channel == channel, self._telegram_data[obis_reference])) def get(self, obis_reference, channel=None):
if channel is None:
return self._telegram_data[obis_reference]
try:
return next(filter(lambda x: x.channel == channel, self._telegram_data[obis_reference]))
except StopIteration:
return None
def __getattr__(self, name): def __getattr__(self, name):
""" will only get called for undefined attributes """ """ will only get called for undefined attributes """
@ -66,13 +73,10 @@ class DSMRObject(object):
Represents all data from a single telegram line. Represents all data from a single telegram line.
""" """
def __init__(self, values): def __init__(self, channel, values):
self.channel = channel # TODO consider if only MBus should have channels
self.values = values self.values = values
@property
def channel(self):
return 0 # TODO
class MBusObject(DSMRObject): class MBusObject(DSMRObject):
@ -203,8 +207,8 @@ class ProfileGenericObject(DSMRObject):
containing the datetime (timestamp) and the value. containing the datetime (timestamp) and the value.
""" """
def __init__(self, values): def __init__(self, *args, **kwargs):
super().__init__(values) super().__init__(*args, **kwargs)
self._buffer_list = None self._buffer_list = None
@property @property
@ -230,9 +234,16 @@ class ProfileGenericObject(DSMRObject):
if self._buffer_list is None: if self._buffer_list is None:
self._buffer_list = [] self._buffer_list = []
values_offset = 2 values_offset = 2
for i in range(self.buffer_length): for i in range(self.buffer_length):
offset = values_offset + i * 2 offset = values_offset + i * 2
self._buffer_list.append(MBusObject([self.values[offset], self.values[offset + 1]])) self._buffer_list.append(
MBusObject(
channel=self.channel,
values=[self.values[offset], self.values[offset + 1]]
)
)
return self._buffer_list return self._buffer_list
def __str__(self): def __str__(self):

View File

@ -18,7 +18,7 @@ logger = logging.getLogger(__name__)
class TelegramParser(object): class TelegramParser(object):
crc16_tab = [] crc16_tab = []
def __init__(self, telegram_specification, apply_checksum_validation=True, gas_meter_channel=None): def __init__(self, telegram_specification, apply_checksum_validation=True):
""" """
:param telegram_specification: determines how the telegram is parsed :param telegram_specification: determines how the telegram is parsed
:param apply_checksum_validation: validate checksum if applicable for :param apply_checksum_validation: validate checksum if applicable for
@ -87,12 +87,12 @@ class TelegramParser(object):
# so only parse lines that match # so only parse lines that match
for match in matches: for match in matches:
try: try:
value = parser.parse(match) dsmr_object = parser.parse(match)
except Exception: except Exception:
logger.error("ignore line with signature {}, because parsing failed.".format(signature), logger.error("ignore line with signature {}, because parsing failed.".format(signature),
exc_info=True) exc_info=True)
else: else:
telegram.add(obis_reference=signature, value=value) telegram.add(obis_reference=signature, dsmr_object=dsmr_object)
return telegram return telegram
@ -174,6 +174,20 @@ class DSMRObjectParser(object):
return [self.value_formats[i].parse(value) return [self.value_formats[i].parse(value)
for i, value in enumerate(values)] for i, value in enumerate(values)]
def _parse_channel(self, line):
"""
Get the channel identifier of a line.
Line format:
'0-2:24.2.1(200426223001S)(00246.138*m3)'
^
channel
"""
try:
return int(line[2])
except ValueError:
raise ParseError("Invalid channel for line '%s' in '%s'", line, self)
def _parse(self, line): def _parse(self, line):
# Match value groups, but exclude the parentheses # Match value groups, but exclude the parentheses
pattern = re.compile(r'((?<=\()[0-9a-zA-Z\.\*\-\:]{0,}(?=\)))') pattern = re.compile(r'((?<=\()[0-9a-zA-Z\.\*\-\:]{0,}(?=\)))')
@ -207,7 +221,10 @@ class MBusParser(DSMRObjectParser):
""" """
def parse(self, line): def parse(self, line):
return MBusObject(self._parse(line)) return MBusObject(
channel=self._parse_channel(line),
values=self._parse(line)
)
class MaxDemandParser(DSMRObjectParser): class MaxDemandParser(DSMRObjectParser):
@ -235,6 +252,8 @@ class MaxDemandParser(DSMRObjectParser):
pattern = re.compile(r'((?<=\()[0-9a-zA-Z\.\*\-\:]{0,}(?=\)))') pattern = re.compile(r'((?<=\()[0-9a-zA-Z\.\*\-\:]{0,}(?=\)))')
values = re.findall(pattern, line) values = re.findall(pattern, line)
channel = self._parse_channel(line)
objects = [] objects = []
count = int(values[0]) count = int(values[0])
@ -242,7 +261,10 @@ class MaxDemandParser(DSMRObjectParser):
timestamp_month = ValueParser(timestamp).parse(values[i * 3 + 1]) timestamp_month = ValueParser(timestamp).parse(values[i * 3 + 1])
timestamp_occurred = ValueParser(timestamp).parse(values[i * 3 + 1]) timestamp_occurred = ValueParser(timestamp).parse(values[i * 3 + 1])
value = ValueParser(Decimal).parse(values[i * 3 + 2]) value = ValueParser(Decimal).parse(values[i * 3 + 2])
objects.append(MBusObjectPeak([timestamp_month, timestamp_occurred, value])) objects.append(MBusObjectPeak(
channel=channel,
values=[timestamp_month, timestamp_occurred, value]
))
return objects return objects
@ -268,7 +290,10 @@ class CosemParser(DSMRObjectParser):
""" """
def parse(self, line): def parse(self, line):
return CosemObject(self._parse(line)) return CosemObject(
channel=self._parse_channel(line),
values=self._parse(line)
)
class ProfileGenericParser(DSMRObjectParser): class ProfileGenericParser(DSMRObjectParser):
@ -327,7 +352,10 @@ class ProfileGenericParser(DSMRObjectParser):
return [self.value_formats[i].parse(value) for i, value in enumerate(values)] return [self.value_formats[i].parse(value) for i, value in enumerate(values)]
def parse(self, line): def parse(self, line):
return ProfileGenericObject(self._parse(line)) return ProfileGenericObject(
channel=self._parse_channel(line),
values=self._parse(line)
)
class ValueParser(object): class ValueParser(object):
@ -335,7 +363,7 @@ class ValueParser(object):
Parses a single value from DSMRObject's. Parses a single value from DSMRObject's.
Example with coerce_type being int: Example with coerce_type being int:
(002*A) becomes {'value': 1, 'unit': 'A'} (002*A) becomes {'value': 2, 'unit': 'A'}
Example with coerce_type being str: Example with coerce_type being str:
(42) becomes {'value': '42', 'unit': None} (42) becomes {'value': '42', 'unit': None}