issue-51-telegram work in progress

This commit is contained in:
Nigel Dokter 2023-02-15 11:55:28 +01:00
parent 516850481d
commit ad25fd4182
5 changed files with 167 additions and 19 deletions

View File

@ -23,12 +23,17 @@ class Telegram(object):
def __init__(self): def __init__(self):
self._telegram_data = defaultdict(list) self._telegram_data = defaultdict(list)
self._mbus_channel_devices = {} self._mbus_channel_devices = {}
self._item_names = []
def add(self, obis_reference, dsmr_object): def add(self, obis_reference, dsmr_object):
self._telegram_data[obis_reference].append(dsmr_object) self._telegram_data[obis_reference].append(dsmr_object)
# Update name mapping used to get value by attribute. Example: telegram.P1_MESSAGE_HEADER # Update name mapping used to get value by attribute. Example: telegram.P1_MESSAGE_HEADER
setattr(self, obis_name_mapping.EN[obis_reference], dsmr_object) # Also keep track of the added names internally
obis_name = obis_name_mapping.EN[obis_reference]
setattr(self, obis_name, dsmr_object)
if obis_name not in self._item_names: # TODO solve issue with repeating obis references
self._item_names.append(obis_name)
# Group Mbus related values into a MbusDevice object. # Group Mbus related values into a MbusDevice object.
# TODO MaxDemandParser (BELGIUM_MAXIMUM_DEMAND_13_MONTHS) returns a list # TODO MaxDemandParser (BELGIUM_MAXIMUM_DEMAND_13_MONTHS) returns a list
@ -63,26 +68,26 @@ class Telegram(object):
def __getitem__(self, obis_reference): def __getitem__(self, obis_reference):
""" """
Get value by key. Example: telegram[obis_references.P1_MESSAGE_HEADER] Deprecated method to get obis_reference by key. Exists for backwards compatibility
For Mbus devices like gas and water meters, it's better to use get_mbus_devices and get_mbus_device_by_channel. Example: telegram[obis_references.P1_MESSAGE_HEADER]
This key approach will only fetch the first found value and therefor might not be accurate.
""" """
try: try:
# TODO use _telegram_data here or else TelegramParserFluviusTest.test_parse breaks
return self._telegram_data[obis_reference][0] return self._telegram_data[obis_reference][0]
# obis_name = obis_name_mapping.EN[obis_reference]
# return getattr(self, obis_name)
except IndexError: except IndexError:
# The index error is an internal detail. The KeyError is expected as a user. # The index error is an internal detail. The KeyError is expected as a user.
raise KeyError raise KeyError
def __len__(self): def __len__(self):
return len(self._telegram_data) return len(self._item_names)
def __iter__(self): def __iter__(self):
for obis_reference, values in self._telegram_data.items(): for attr in self._item_names:
reverse_obis_name = obis_name_mapping.EN[obis_reference] value = getattr(self, attr)
value = values[0] # TODO might be considered legacy behavior? yield attr, value
yield reverse_obis_name, value
def __str__(self): def __str__(self):
output = "" output = ""
@ -91,7 +96,13 @@ class Telegram(object):
return output return output
def to_json(self): def to_json(self):
return json.dumps(dict([[attr, json.loads(value.to_json())] for attr, value in self])) telegram_data = {obis_name: json.loads(value.to_json()) for obis_name, value in self}
telegram_data['MBUS_DEVICES'] = [
json.loads(mbus_device.to_json())
for mbus_device in self._mbus_channel_devices.values()
]
return json.dumps(telegram_data)
class DSMRObject(object): class DSMRObject(object):
@ -319,10 +330,31 @@ class MbusDevice:
def __init__(self, channel_id): def __init__(self, channel_id):
self.channel_id = channel_id self.channel_id = channel_id
self._telegram_data = {} self._item_names = []
def add(self, obis_reference, dsmr_object): def add(self, obis_reference, dsmr_object):
self._telegram_data[obis_reference] = dsmr_object # 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)
# Update name mapping used to get value by attribute. Example: device.HOURLY_GAS_METER_READING def __len__(self):
setattr(self, obis_name_mapping.EN[obis_reference], dsmr_object) return len(self._item_names)
def __iter__(self):
for attr in self._item_names:
value = getattr(self, attr)
yield attr, value
def __str__(self):
output = ""
for attr, value in self:
output += "{}: \t {}\n".format(attr, str(value))
return output
def to_json(self):
data = {obis_name: json.loads(value.to_json()) for obis_name, value in self}
data['CHANNEL_ID'] = self.channel_id
return json.dumps(data)

0
test/objects/__init__.py Normal file
View File

View File

@ -0,0 +1,50 @@
from decimal import Decimal
import json
import unittest
from dsmr_parser import telegram_specifications, obis_references
from dsmr_parser.objects import MbusDevice
class MbusDeviceTest(unittest.TestCase):
def setUp(self):
v5_objects = telegram_specifications.V5['objects']
device_type_parser = v5_objects[obis_references.DEVICE_TYPE]
device_type = device_type_parser.parse('0-2:24.1.0(003)\r\n')
equipment_parser = v5_objects[obis_references.EQUIPMENT_IDENTIFIER_GAS]
equipment = equipment_parser.parse('0-2:96.1.0(4730303339303031393336393930363139)\r\n')
gas_reading_parser = v5_objects[obis_references.HOURLY_GAS_METER_READING]
gas_reading = gas_reading_parser.parse('0-2:24.2.1(200426223001S)(00246.138*m3)\r\n')
mbus_device = MbusDevice(channel_id=1)
mbus_device.add(obis_references.DEVICE_TYPE, device_type)
mbus_device.add(obis_references.EQUIPMENT_IDENTIFIER_GAS, equipment)
mbus_device.add(obis_references.HOURLY_GAS_METER_READING, gas_reading)
self.mbus_device = mbus_device
def test_attributes(self):
self.assertEqual(self.mbus_device.DEVICE_TYPE.value, 3)
self.assertEqual(self.mbus_device.DEVICE_TYPE.unit, None)
self.assertEqual(self.mbus_device.EQUIPMENT_IDENTIFIER_GAS.value,
'4730303339303031393336393930363139')
self.assertEqual(self.mbus_device.EQUIPMENT_IDENTIFIER_GAS.unit, None)
self.assertEqual(self.mbus_device.HOURLY_GAS_METER_READING.value, Decimal('246.138'))
self.assertEqual(self.mbus_device.HOURLY_GAS_METER_READING.unit, 'm3')
def test_to_json(self):
self.assertEqual(
json.loads(self.mbus_device.to_json()),
{
'CHANNEL_ID': 1,
'DEVICE_TYPE': {'value': 3, 'unit': None},
'EQUIPMENT_IDENTIFIER_GAS': {'value': '4730303339303031393336393930363139', 'unit': None},
'HOURLY_GAS_METER_READING': {'datetime': '2020-04-26T22:30:01+02:00', 'value': 246.138, 'unit': 'm3'}}
)

View File

@ -1,3 +1,4 @@
import json
import unittest import unittest
import datetime import datetime
import pytz import pytz
@ -324,17 +325,17 @@ class TelegramTest(unittest.TestCase):
parser = TelegramParser(telegram_specifications.V5) parser = TelegramParser(telegram_specifications.V5)
telegram = parser.parse(TELEGRAM_V5_TWO_MBUS) telegram = parser.parse(TELEGRAM_V5_TWO_MBUS)
self.assertEqual(len(telegram), 35) self.assertEqual(len(telegram._item_names), 35)
def test_iter(self): def test_iter(self):
parser = TelegramParser(telegram_specifications.V5) parser = TelegramParser(telegram_specifications.V5)
telegram = parser.parse(TELEGRAM_V5) telegram = parser.parse(TELEGRAM_V5)
for obis_reference, dsmr_object in telegram: for obis_name, dsmr_object in telegram:
break break
# Verify that the iterator works for at least on evalue # Verify that the iterator works for at least one value
self.assertEqual(obis_reference, obis_name_mapping.EN[obis_references.P1_MESSAGE_HEADER]) self.assertEqual(obis_name, obis_name_mapping.EN[obis_references.P1_MESSAGE_HEADER])
self.assertEqual(dsmr_object.value, '50') self.assertEqual(dsmr_object.value, '50')
def test_get_mbus_devices(self): def test_get_mbus_devices(self):
@ -378,3 +379,68 @@ class TelegramTest(unittest.TestCase):
# Because of a bug related to incorrect use of defaultdict, # Because of a bug related to incorrect use of defaultdict,
# test again for unwanted side effects # test again for unwanted side effects
self.assertEqual(telegram.get_mbus_devices(), []) self.assertEqual(telegram.get_mbus_devices(), [])
def test_to_json(self):
parser = TelegramParser(telegram_specifications.V5)
telegram = parser.parse(TELEGRAM_V5)
json_data = json.loads(telegram.to_json())
self.assertEqual(
json_data,
{'CURRENT_ELECTRICITY_DELIVERY': {'unit': 'kW', 'value': 0.0},
'CURRENT_ELECTRICITY_USAGE': {'unit': 'kW', 'value': 0.244},
'DEVICE_TYPE': {'unit': None, 'value': 3},
'ELECTRICITY_ACTIVE_TARIFF': {'unit': None, 'value': '0002'},
'ELECTRICITY_DELIVERED_TARIFF_1': {'unit': 'kWh', 'value': 2.444},
'ELECTRICITY_DELIVERED_TARIFF_2': {'unit': 'kWh', 'value': 0.0},
'ELECTRICITY_USED_TARIFF_1': {'unit': 'kWh', 'value': 4.426},
'ELECTRICITY_USED_TARIFF_2': {'unit': 'kWh', 'value': 2.399},
'EQUIPMENT_IDENTIFIER': {'unit': None,
'value': '4B384547303034303436333935353037'},
'EQUIPMENT_IDENTIFIER_GAS': {'unit': None, 'value': None},
'HOURLY_GAS_METER_READING': {'datetime': '2017-01-02T16:10:05+01:00',
'unit': 'm3',
'value': 0.107},
'INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE': {'unit': 'kW', 'value': 0.0},
'INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE': {'unit': 'kW', 'value': 0.07},
'INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE': {'unit': 'kW', 'value': 0.0},
'INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE': {'unit': 'kW', 'value': 0.032},
'INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE': {'unit': 'kW', 'value': 0.0},
'INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE': {'unit': 'kW', 'value': 0.142},
'INSTANTANEOUS_CURRENT_L1': {'unit': 'A', 'value': 0.48},
'INSTANTANEOUS_CURRENT_L2': {'unit': 'A', 'value': 0.44},
'INSTANTANEOUS_CURRENT_L3': {'unit': 'A', 'value': 0.86},
'INSTANTANEOUS_VOLTAGE_L1': {'unit': 'V', 'value': 230.0},
'INSTANTANEOUS_VOLTAGE_L2': {'unit': 'V', 'value': 230.0},
'INSTANTANEOUS_VOLTAGE_L3': {'unit': 'V', 'value': 229.0},
'LONG_POWER_FAILURE_COUNT': {'unit': None, 'value': 0},
'MBUS_DEVICES': [{'CHANNEL_ID': 1,
'DEVICE_TYPE': {'unit': None, 'value': 3},
'EQUIPMENT_IDENTIFIER_GAS': {'unit': None,
'value': '3232323241424344313233343536373839'},
'HOURLY_GAS_METER_READING': {'datetime': '2017-01-02T16:10:05+01:00',
'unit': 'm3',
'value': 0.107}},
{'CHANNEL_ID': 2,
'DEVICE_TYPE': {'unit': None, 'value': 3},
'EQUIPMENT_IDENTIFIER_GAS': {'unit': None, 'value': None}}],
'P1_MESSAGE_HEADER': {'unit': None, 'value': '50'},
'P1_MESSAGE_TIMESTAMP': {'unit': None, 'value': '2017-01-02T19:20:02+01:00'},
'POWER_EVENT_FAILURE_LOG': {'buffer': [],
'buffer_length': 0,
'buffer_type': '0-0:96.7.19'},
'SHORT_POWER_FAILURE_COUNT': {'unit': None, 'value': 13},
'TEXT_MESSAGE': {'unit': None, 'value': None},
'VOLTAGE_SAG_L1_COUNT': {'unit': None, 'value': 0},
'VOLTAGE_SAG_L2_COUNT': {'unit': None, 'value': 0},
'VOLTAGE_SAG_L3_COUNT': {'unit': None, 'value': 0},
'VOLTAGE_SWELL_L1_COUNT': {'unit': None, 'value': 0},
'VOLTAGE_SWELL_L2_COUNT': {'unit': None, 'value': 0},
'VOLTAGE_SWELL_L3_COUNT': {'unit': None, 'value': 0}}
)
def test_getitem(self):
parser = TelegramParser(telegram_specifications.V5)
telegram = parser.parse(TELEGRAM_V5)
self.assertEqual(telegram[obis_references.P1_MESSAGE_HEADER].value, '50')