diff --git a/dsmr_parser/obis_references.py b/dsmr_parser/obis_references.py index b09252b..c350202 100644 --- a/dsmr_parser/obis_references.py +++ b/dsmr_parser/obis_references.py @@ -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' 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' -SHORT_POWER_FAILURE_COUNT = r'96\.7\.21.+?\r\n' -POWER_EVENT_FAILURE_LOG = r'99\.97\.0.+?\r\n' +LONG_POWER_FAILURE_COUNT = r'\d-\d:96\.7\.9.+?\r\n' +SHORT_POWER_FAILURE_COUNT = r'\d-\d:96\.7\.21.+?\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_L2_COUNT = r'\d-\d:52\.32\.0.+?\r\n' VOLTAGE_SAG_L3_COUNT = r'\d-\d:72\.32\.0.+?\r\n' diff --git a/dsmr_parser/objects.py b/dsmr_parser/objects.py index 15ed27e..7237b6c 100644 --- a/dsmr_parser/objects.py +++ b/dsmr_parser/objects.py @@ -24,11 +24,18 @@ class Telegram(object): self._reverse_obis_name_mapping = dsmr_parser.obis_name_mapping.REVERSE_EN self._item_names = self._get_item_names() - def add(self, obis_reference, value): - self._telegram_data[obis_reference].append(value) + def add(self, obis_reference, dsmr_object): + self._telegram_data[obis_reference].append(dsmr_object) - def get(self, obis_reference, channel): - return next(filter(lambda x: x.channel == channel, self._telegram_data[obis_reference])) + # TODO experiment with api to see what is nice + 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): """ will only get called for undefined attributes """ @@ -66,13 +73,10 @@ class DSMRObject(object): 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 - @property - def channel(self): - return 0 # TODO - class MBusObject(DSMRObject): @@ -203,8 +207,8 @@ class ProfileGenericObject(DSMRObject): containing the datetime (timestamp) and the value. """ - def __init__(self, values): - super().__init__(values) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self._buffer_list = None @property @@ -230,9 +234,16 @@ class ProfileGenericObject(DSMRObject): if self._buffer_list is None: self._buffer_list = [] values_offset = 2 + for i in range(self.buffer_length): 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 def __str__(self): diff --git a/dsmr_parser/parsers.py b/dsmr_parser/parsers.py index 80d9a91..bff6ed3 100644 --- a/dsmr_parser/parsers.py +++ b/dsmr_parser/parsers.py @@ -18,7 +18,7 @@ logger = logging.getLogger(__name__) class TelegramParser(object): 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 apply_checksum_validation: validate checksum if applicable for @@ -87,12 +87,12 @@ class TelegramParser(object): # so only parse lines that match for match in matches: try: - value = parser.parse(match) + dsmr_object = parser.parse(match) except Exception: logger.error("ignore line with signature {}, because parsing failed.".format(signature), exc_info=True) else: - telegram.add(obis_reference=signature, value=value) + telegram.add(obis_reference=signature, dsmr_object=dsmr_object) return telegram @@ -174,6 +174,20 @@ class DSMRObjectParser(object): return [self.value_formats[i].parse(value) 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): # Match value groups, but exclude the parentheses pattern = re.compile(r'((?<=\()[0-9a-zA-Z\.\*\-\:]{0,}(?=\)))') @@ -207,7 +221,10 @@ class MBusParser(DSMRObjectParser): """ def parse(self, line): - return MBusObject(self._parse(line)) + return MBusObject( + channel=self._parse_channel(line), + values=self._parse(line) + ) class MaxDemandParser(DSMRObjectParser): @@ -235,6 +252,8 @@ class MaxDemandParser(DSMRObjectParser): pattern = re.compile(r'((?<=\()[0-9a-zA-Z\.\*\-\:]{0,}(?=\)))') values = re.findall(pattern, line) + channel = self._parse_channel(line) + objects = [] count = int(values[0]) @@ -242,7 +261,10 @@ class MaxDemandParser(DSMRObjectParser): timestamp_month = ValueParser(timestamp).parse(values[i * 3 + 1]) timestamp_occurred = ValueParser(timestamp).parse(values[i * 3 + 1]) 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 @@ -268,7 +290,10 @@ class CosemParser(DSMRObjectParser): """ def parse(self, line): - return CosemObject(self._parse(line)) + return CosemObject( + channel=self._parse_channel(line), + values=self._parse(line) + ) class ProfileGenericParser(DSMRObjectParser): @@ -327,7 +352,10 @@ class ProfileGenericParser(DSMRObjectParser): return [self.value_formats[i].parse(value) for i, value in enumerate(values)] def parse(self, line): - return ProfileGenericObject(self._parse(line)) + return ProfileGenericObject( + channel=self._parse_channel(line), + values=self._parse(line) + ) class ValueParser(object): @@ -335,7 +363,7 @@ class ValueParser(object): Parses a single value from DSMRObject's. 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: (42) becomes {'value': '42', 'unit': None}