ProfileGeneric parser working, TODO complete ProfileGenericObject + Test
This commit is contained in:
parent
a0ce89054a
commit
b6278a8991
@ -74,7 +74,7 @@ class MBusObject(DSMRObject):
|
|||||||
# TODO object, but let the parse set them differently? So don't use
|
# TODO object, but let the parse set them differently? So don't use
|
||||||
# TODO hardcoded indexes here.
|
# TODO hardcoded indexes here.
|
||||||
if len(self.values) != 2: # v2
|
if len(self.values) != 2: # v2
|
||||||
return self.values[5]['value']
|
return self.values[6]['value']
|
||||||
else:
|
else:
|
||||||
return self.values[1]['value']
|
return self.values[1]['value']
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ class MBusObject(DSMRObject):
|
|||||||
# TODO object, but let the parse set them differently? So don't use
|
# TODO object, but let the parse set them differently? So don't use
|
||||||
# TODO hardcoded indexes here.
|
# TODO hardcoded indexes here.
|
||||||
if len(self.values) != 2: # v2
|
if len(self.values) != 2: # v2
|
||||||
return self.values[4]['value']
|
return self.values[5]['value']
|
||||||
else:
|
else:
|
||||||
return self.values[1]['unit']
|
return self.values[1]['unit']
|
||||||
|
|
||||||
@ -111,5 +111,7 @@ class CosemObject(DSMRObject):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
class ProfileGeneric(DSMRObject):
|
class ProfileGenericObject(DSMRObject):
|
||||||
pass # TODO implement
|
def __str__(self):
|
||||||
|
output = "{}".format(self.values)
|
||||||
|
return output
|
||||||
|
@ -3,7 +3,7 @@ import re
|
|||||||
|
|
||||||
from ctypes import c_ushort
|
from ctypes import c_ushort
|
||||||
|
|
||||||
from dsmr_parser.objects import MBusObject, CosemObject
|
from dsmr_parser.objects import MBusObject, CosemObject, ProfileGenericObject
|
||||||
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
|
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -123,19 +123,28 @@ class DSMRObjectParser(object):
|
|||||||
def __init__(self, *value_formats):
|
def __init__(self, *value_formats):
|
||||||
self.value_formats = value_formats
|
self.value_formats = value_formats
|
||||||
|
|
||||||
|
def _is_line_wellformed(self, line, values):
|
||||||
|
# allows overriding by child class
|
||||||
|
return (values and (len(values) == len(self.value_formats)))
|
||||||
|
|
||||||
|
def _parse_values(self, values):
|
||||||
|
# allows overriding by child class
|
||||||
|
return [self.value_formats[i].parse(value)
|
||||||
|
for i, value in enumerate(values)]
|
||||||
|
|
||||||
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,}(?=\)))')
|
||||||
|
|
||||||
values = re.findall(pattern, line)
|
values = re.findall(pattern, line)
|
||||||
|
|
||||||
|
if not self._is_line_wellformed(line, values):
|
||||||
|
raise ParseError("Invalid '%s' line for '%s'", line, self)
|
||||||
|
|
||||||
# Convert empty value groups to None for clarity.
|
# Convert empty value groups to None for clarity.
|
||||||
values = [None if value == '' else value for value in values]
|
values = [None if value == '' else value for value in values]
|
||||||
|
|
||||||
if not values or len(values) != len(self.value_formats):
|
return self._parse_values(values)
|
||||||
raise ParseError("Invalid '%s' line for '%s'", line, self)
|
|
||||||
|
|
||||||
return [self.value_formats[i].parse(value)
|
|
||||||
for i, value in enumerate(values)]
|
|
||||||
|
|
||||||
|
|
||||||
class MBusParser(DSMRObjectParser):
|
class MBusParser(DSMRObjectParser):
|
||||||
@ -183,7 +192,7 @@ class CosemParser(DSMRObjectParser):
|
|||||||
return CosemObject(self._parse(line))
|
return CosemObject(self._parse(line))
|
||||||
|
|
||||||
|
|
||||||
class ProfileGenericParser(object):
|
class ProfileGenericParser(DSMRObjectParser):
|
||||||
"""
|
"""
|
||||||
Power failure log parser.
|
Power failure log parser.
|
||||||
|
|
||||||
@ -204,25 +213,34 @@ class ProfileGenericParser(object):
|
|||||||
8) Buffer value 2 (oldest entry of buffer attribute without unit)
|
8) Buffer value 2 (oldest entry of buffer attribute without unit)
|
||||||
9) Unit of buffer values (Unit of capture objects attribute)
|
9) Unit of buffer values (Unit of capture objects attribute)
|
||||||
"""
|
"""
|
||||||
|
def __init__(self, buffer_types, head_parsers, parsers_for_unidentified):
|
||||||
|
self.value_formats = head_parsers
|
||||||
|
self.buffer_types = buffer_types
|
||||||
|
self.parsers_for_unidentified = parsers_for_unidentified
|
||||||
|
|
||||||
def _parse(self, line):
|
def _is_line_wellformed(self, line, values):
|
||||||
# Match value groups, but exclude the parentheses. Adapted to also match OBIS code in 3rd position.
|
if values and (len(values) >= 2) and (values[0].isdigit()):
|
||||||
pattern = re.compile(r'((?<=\()[0-9a-zA-Z\.\*\-\:]{0,}(?=\)))')
|
buffer_length = int(values[0])
|
||||||
values = re.findall(pattern, line)
|
return (buffer_length <= 10) and (len(values) == (buffer_length * 2 + 2))
|
||||||
|
else:
|
||||||
# Convert empty value groups to None for clarity.
|
return False
|
||||||
values = [None if value == '' else value for value in values]
|
|
||||||
|
|
||||||
|
def _parse_values(self, values):
|
||||||
buffer_length = int(values[0])
|
buffer_length = int(values[0])
|
||||||
|
buffer_value_obis_ID = values[1]
|
||||||
|
if (buffer_length > 0):
|
||||||
|
if buffer_value_obis_ID in self.buffer_types:
|
||||||
|
bufferValueParsers = self.buffer_types[buffer_value_obis_ID]
|
||||||
|
else:
|
||||||
|
bufferValueParsers = self.parsers_for_unidentified
|
||||||
|
# add the parsers for the encountered value type z times
|
||||||
|
for _ in range(buffer_length):
|
||||||
|
self.value_formats.extend(bufferValueParsers)
|
||||||
|
|
||||||
if (not values) or (len(values) != (buffer_length * 2 + 2)):
|
return [self.value_formats[i].parse(value) for i, value in enumerate(values)]
|
||||||
raise ParseError("Invalid '%s' line for '%s'", line, self)
|
|
||||||
|
|
||||||
return [self.value_formats[i].parse(value)
|
|
||||||
for i, value in enumerate(values)]
|
|
||||||
|
|
||||||
def parse(self, line):
|
def parse(self, line):
|
||||||
raise NotImplementedError()
|
return ProfileGenericObject(self._parse(line))
|
||||||
|
|
||||||
|
|
||||||
class ValueParser(object):
|
class ValueParser(object):
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
from dsmr_parser.parsers import ValueParser, MBusParser
|
from dsmr_parser.parsers import ValueParser
|
||||||
from dsmr_parser.value_types import timestamp
|
from dsmr_parser.value_types import timestamp
|
||||||
|
|
||||||
FAILURE_EVENT = r'0-0\:96\.7\.19'
|
PG_FAILURE_EVENT = r'0-0:96.7.19'
|
||||||
|
|
||||||
V4 = {
|
PG_HEAD_PARSERS = [ValueParser(int), ValueParser(str)]
|
||||||
'objects': {
|
PG_UNIDENTIFIED_BUFFERTYPE_PARSERS = [ValueParser(str), ValueParser(str)]
|
||||||
FAILURE_EVENT: MBusParser(
|
BUFFER_TYPES = {
|
||||||
ValueParser(timestamp),
|
PG_FAILURE_EVENT: [ValueParser(timestamp), ValueParser(int)]
|
||||||
ValueParser(int)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -2,9 +2,9 @@ from decimal import Decimal
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
from dsmr_parser import obis_references as obis
|
from dsmr_parser import obis_references as obis
|
||||||
from dsmr_parser.parsers import CosemParser, ValueParser, MBusParser
|
from dsmr_parser.parsers import CosemParser, ValueParser, MBusParser, ProfileGenericParser
|
||||||
from dsmr_parser.value_types import timestamp
|
from dsmr_parser.value_types import timestamp
|
||||||
|
from dsmr_parser.profile_generic_specifications import BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS
|
||||||
|
|
||||||
"""
|
"""
|
||||||
dsmr_parser.telegram_specifications
|
dsmr_parser.telegram_specifications
|
||||||
@ -37,8 +37,9 @@ V2_2 = {
|
|||||||
ValueParser(int),
|
ValueParser(int),
|
||||||
ValueParser(int),
|
ValueParser(int),
|
||||||
ValueParser(int),
|
ValueParser(int),
|
||||||
ValueParser(str),
|
ValueParser(str), # obis ref
|
||||||
ValueParser(Decimal),
|
ValueParser(str), # unit, position 5
|
||||||
|
ValueParser(Decimal), # meter reading, position 6
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,7 +61,10 @@ V4 = {
|
|||||||
obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)),
|
obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)),
|
||||||
obis.SHORT_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)),
|
obis.SHORT_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)),
|
||||||
obis.LONG_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)),
|
obis.LONG_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)),
|
||||||
# POWER_EVENT_FAILURE_LOG: ProfileGenericParser(), TODO
|
obis.POWER_EVENT_FAILURE_LOG:
|
||||||
|
ProfileGenericParser(BUFFER_TYPES,
|
||||||
|
PG_HEAD_PARSERS,
|
||||||
|
PG_UNIDENTIFIED_BUFFERTYPE_PARSERS),
|
||||||
obis.VOLTAGE_SAG_L1_COUNT: CosemParser(ValueParser(int)),
|
obis.VOLTAGE_SAG_L1_COUNT: CosemParser(ValueParser(int)),
|
||||||
obis.VOLTAGE_SAG_L2_COUNT: CosemParser(ValueParser(int)),
|
obis.VOLTAGE_SAG_L2_COUNT: CosemParser(ValueParser(int)),
|
||||||
obis.VOLTAGE_SAG_L3_COUNT: CosemParser(ValueParser(int)),
|
obis.VOLTAGE_SAG_L3_COUNT: CosemParser(ValueParser(int)),
|
||||||
|
@ -7,6 +7,7 @@ from dsmr_parser import obis_name_mapping
|
|||||||
from dsmr_parser.objects import CosemObject
|
from dsmr_parser.objects import CosemObject
|
||||||
from dsmr_parser.objects import MBusObject
|
from dsmr_parser.objects import MBusObject
|
||||||
from dsmr_parser.objects import Telegram
|
from dsmr_parser.objects import Telegram
|
||||||
|
from dsmr_parser.objects import ProfileGenericObject
|
||||||
from dsmr_parser.parsers import TelegramParser
|
from dsmr_parser.parsers import TelegramParser
|
||||||
from test.example_telegrams import TELEGRAM_V4_2
|
from test.example_telegrams import TELEGRAM_V4_2
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
@ -286,6 +287,15 @@ class TelegramTest(unittest.TestCase):
|
|||||||
unit_val='m3',
|
unit_val='m3',
|
||||||
value_type=Decimal,
|
value_type=Decimal,
|
||||||
value_val=Decimal('981.443'))
|
value_val=Decimal('981.443'))
|
||||||
|
# POWER_EVENT_FAILURE_LOG (1-0:99.97.0)
|
||||||
|
testitem_name = 'POWER_EVENT_FAILURE_LOG'
|
||||||
|
object_type = ProfileGenericObject
|
||||||
|
testitem = eval("telegram.{}".format(testitem_name))
|
||||||
|
assert isinstance(testitem, object_type)
|
||||||
|
# assert testitem.unit == unit_val
|
||||||
|
# assert isinstance(testitem.value, value_type)
|
||||||
|
# assert testitem.value == value_val
|
||||||
|
self.item_names_tested.append(testitem_name)
|
||||||
|
|
||||||
# check if all items in telegram V4 specification are covered
|
# check if all items in telegram V4 specification are covered
|
||||||
V4_name_list = [obis_name_mapping.EN[signature] for signature, parser in
|
V4_name_list = [obis_name_mapping.EN[signature] for signature, parser in
|
||||||
|
Loading…
Reference in New Issue
Block a user