From 8bdf77c78d938281bbe29b6c4d785877435dcdf6 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Sat, 6 Apr 2019 12:56:27 +0200 Subject: [PATCH 01/17] ensure build and dist directories are not synced --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 1da5fee..20de282 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ /.project /.pydevproject /.coverage +build/ +dist/ From c36f68a88423e9e656658db0afdd46fd7b350ff6 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Thu, 6 Jun 2019 05:41:55 +0200 Subject: [PATCH 02/17] working version of the Telegram object --- dsmr_parser/clients/serial_.py | 20 ++++++++++++ dsmr_parser/obis_name_mapping.py | 54 ++++++++++++++++++++++++++++++++ dsmr_parser/objects.py | 54 ++++++++++++++++++++++++++++++++ dsmr_parser/parsers.py | 2 +- test/test_telegram.py | 30 ++++++++++++++++++ tox.ini | 2 +- 6 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 dsmr_parser/obis_name_mapping.py create mode 100644 test/test_telegram.py diff --git a/dsmr_parser/clients/serial_.py b/dsmr_parser/clients/serial_.py index 1d7be89..c252f6f 100644 --- a/dsmr_parser/clients/serial_.py +++ b/dsmr_parser/clients/serial_.py @@ -6,6 +6,7 @@ import serial_asyncio from dsmr_parser.clients.telegram_buffer import TelegramBuffer from dsmr_parser.exceptions import ParseError, InvalidChecksumError from dsmr_parser.parsers import TelegramParser +from dsmr_parser.objects import Telegram logger = logging.getLogger(__name__) @@ -41,6 +42,25 @@ class SerialReader(object): except ParseError as e: logger.error('Failed to parse telegram: %s', e) + def read_as_object(self): + """ + Read complete DSMR telegram's from the serial interface and return a Telegram object. + + :rtype: generator + """ + with serial.Serial(**self.serial_settings) as serial_handle: + while True: + data = serial_handle.readline() + self.telegram_buffer.append(data.decode('ascii')) + + for telegram in self.telegram_buffer.get_all(): + try: + yield Telegram(telegram, telegram_parser, telegram_specification) + except InvalidChecksumError as e: + logger.warning(str(e)) + except ParseError as e: + logger.error('Failed to parse telegram: %s', e) + class AsyncSerialReader(SerialReader): """Serial reader using asyncio pyserial.""" diff --git a/dsmr_parser/obis_name_mapping.py b/dsmr_parser/obis_name_mapping.py new file mode 100644 index 0000000..8f72654 --- /dev/null +++ b/dsmr_parser/obis_name_mapping.py @@ -0,0 +1,54 @@ +from dsmr_parser import obis_references as obis + +""" +dsmr_parser.obis_name_mapping +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains a mapping of obis references to names. +""" + +EN = { + obis.P1_MESSAGE_HEADER: 'P1_MESSAGE_HEADER', + obis.P1_MESSAGE_TIMESTAMP: 'P1_MESSAGE_TIMESTAMP', + obis.ELECTRICITY_IMPORTED_TOTAL : 'ELECTRICITY_IMPORTED_TOTAL', + obis.ELECTRICITY_USED_TARIFF_1 : 'ELECTRICITY_USED_TARIFF_1', + obis.ELECTRICITY_USED_TARIFF_2 : 'ELECTRICITY_USED_TARIFF_2', + obis.ELECTRICITY_DELIVERED_TARIFF_1 : 'ELECTRICITY_DELIVERED_TARIFF_1', + obis.ELECTRICITY_DELIVERED_TARIFF_2 : 'ELECTRICITY_DELIVERED_TARIFF_2', + obis.ELECTRICITY_ACTIVE_TARIFF : 'ELECTRICITY_ACTIVE_TARIFF', + obis.EQUIPMENT_IDENTIFIER : 'EQUIPMENT_IDENTIFIER', + obis.CURRENT_ELECTRICITY_USAGE : 'CURRENT_ELECTRICITY_USAGE', + obis.CURRENT_ELECTRICITY_DELIVERY : 'CURRENT_ELECTRICITY_DELIVERY', + obis.LONG_POWER_FAILURE_COUNT : 'LONG_POWER_FAILURE_COUNT', + obis.SHORT_POWER_FAILURE_COUNT : 'SHORT_POWER_FAILURE_COUNT', + obis.POWER_EVENT_FAILURE_LOG : 'POWER_EVENT_FAILURE_LOG', + obis.VOLTAGE_SAG_L1_COUNT : 'VOLTAGE_SAG_L1_COUNT', + obis.VOLTAGE_SAG_L2_COUNT : 'VOLTAGE_SAG_L2_COUNT', + obis.VOLTAGE_SAG_L3_COUNT : 'VOLTAGE_SAG_L3_COUNT', + obis.VOLTAGE_SWELL_L1_COUNT : 'VOLTAGE_SWELL_L1_COUNT', + obis.VOLTAGE_SWELL_L2_COUNT : 'VOLTAGE_SWELL_L2_COUNT', + obis.VOLTAGE_SWELL_L3_COUNT : 'VOLTAGE_SWELL_L3_COUNT', + obis.INSTANTANEOUS_VOLTAGE_L1 : 'INSTANTANEOUS_VOLTAGE_L1', + obis.INSTANTANEOUS_VOLTAGE_L2 : 'INSTANTANEOUS_VOLTAGE_L2', + obis.INSTANTANEOUS_VOLTAGE_L3 : 'INSTANTANEOUS_VOLTAGE_L3', + obis.INSTANTANEOUS_CURRENT_L1 : 'INSTANTANEOUS_CURRENT_L1', + obis.INSTANTANEOUS_CURRENT_L2 : 'INSTANTANEOUS_CURRENT_L2', + obis.INSTANTANEOUS_CURRENT_L3 : 'INSTANTANEOUS_CURRENT_L3', + obis.TEXT_MESSAGE_CODE : 'TEXT_MESSAGE_CODE', + obis.TEXT_MESSAGE : 'TEXT_MESSAGE', + obis.DEVICE_TYPE : 'DEVICE_TYPE', + obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE : 'INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE', + obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE : 'INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE', + obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE : 'INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE', + obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE : 'INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE', + obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE : 'INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE', + obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE : 'INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE', + obis.EQUIPMENT_IDENTIFIER_GAS : 'EQUIPMENT_IDENTIFIER_GAS', + obis.HOURLY_GAS_METER_READING : 'HOURLY_GAS_METER_READING', + obis.GAS_METER_READING : 'GAS_METER_READING', + obis.ACTUAL_TRESHOLD_ELECTRICITY : 'ACTUAL_TRESHOLD_ELECTRICITY', + obis.ACTUAL_SWITCH_POSITION : 'ACTUAL_SWITCH_POSITION', + obis.VALVE_POSITION_GAS : 'VALVE_POSITION_GAS' +} + +REVERSE_EN = dict([ (v,k) for k,v in EN.items()]) \ No newline at end of file diff --git a/dsmr_parser/objects.py b/dsmr_parser/objects.py index e6706c4..940318a 100644 --- a/dsmr_parser/objects.py +++ b/dsmr_parser/objects.py @@ -1,3 +1,57 @@ +import dsmr_parser.obis_name_mapping + +class Telegram(object): + """ + Container for raw and parsed telegram data. + Initializing: + from dsmr_parser import telegram_specifications + from dsmr_parser.exceptions import InvalidChecksumError, ParseError + from dsmr_parser.objects import CosemObject, MBusObject, Telegram + from dsmr_parser.parsers import TelegramParser + from test.example_telegrams import TELEGRAM_V4_2 + parser = TelegramParser(telegram_specifications.V4) + telegram = Telegram(TELEGRAM_V4_2, parser, telegram_specifications.V4) + + Attributes can be accessed on a telegram object by addressing by their english name, for example: + telegram.ELECTRICITY_USED_TARIFF_1 + + All attributes in a telegram can be iterated over, for example: + [k for k,v in telegram] + yields: + ['P1_MESSAGE_HEADER', 'P1_MESSAGE_TIMESTAMP', 'EQUIPMENT_IDENTIFIER', ...] + """ + def __init__(self, telegram_data, telegram_parser, telegram_specification): + self._telegram_data = telegram_data + self._telegram_specification = telegram_specification + self._telegram_parser = telegram_parser + self._obis_name_mapping = dsmr_parser.obis_name_mapping.EN + self._reverse_obis_name_mapping = dsmr_parser.obis_name_mapping.REVERSE_EN + self._dictionary = self._telegram_parser.parse(telegram_data) + self._item_names = self._get_item_names() + + def __getattr__(self, name): + ''' will only get called for undefined attributes ''' + obis_reference = self._reverse_obis_name_mapping[name] + value = self._dictionary[obis_reference] + setattr(self, name, value) + return value + + def _get_item_names(self): + return [self._obis_name_mapping[k] for k, v in self._dictionary.items()] + + 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 += " " if not output == "" else "" + output += "{}: \t {} \t[{}]\n".format(attr,str(value.value),str(value.unit)) + return output + + class DSMRObject(object): """ Represents all data from a single telegram line. diff --git a/dsmr_parser/parsers.py b/dsmr_parser/parsers.py index 087d9e0..4b415f6 100644 --- a/dsmr_parser/parsers.py +++ b/dsmr_parser/parsers.py @@ -3,7 +3,7 @@ import re from PyCRC.CRC16 import CRC16 -from dsmr_parser.objects import MBusObject, CosemObject +from dsmr_parser.objects import MBusObject, CosemObject, Telegram from dsmr_parser.exceptions import ParseError, InvalidChecksumError logger = logging.getLogger(__name__) diff --git a/test/test_telegram.py b/test/test_telegram.py new file mode 100644 index 0000000..d0e1042 --- /dev/null +++ b/test/test_telegram.py @@ -0,0 +1,30 @@ +from decimal import Decimal + +import datetime +import unittest + +import pytz + +from dsmr_parser import obis_references as obis +from dsmr_parser import telegram_specifications +from dsmr_parser.exceptions import InvalidChecksumError, ParseError +from dsmr_parser.objects import CosemObject, MBusObject, Telegram +from dsmr_parser.parsers import TelegramParser +from test.example_telegrams import TELEGRAM_V4_2 + +class TelegramTest(unittest.TestCase): + """ Test instantiation of Telegram object """ + + def test_instantiate(self): + parser = TelegramParser(telegram_specifications.V4) + #result = parser.parse(TELEGRAM_V4_2) + telegram = Telegram(TELEGRAM_V4_2, parser, telegram_specifications.V4) + + + + + # P1_MESSAGE_HEADER (1-3:0.2.8) + #assert isinstance(result[obis.P1_MESSAGE_HEADER], CosemObject) + #assert result[obis.P1_MESSAGE_HEADER].unit is None + #assert isinstance(result[obis.P1_MESSAGE_HEADER].value, str) + #assert result[obis.P1_MESSAGE_HEADER].value == '50' diff --git a/tox.ini b/tox.ini index 95660fe..23fe214 100644 --- a/tox.ini +++ b/tox.ini @@ -15,7 +15,7 @@ commands= pylama dsmr_parser test [pylama:dsmr_parser/clients/__init__.py] -ignore = W0611 +ignore = W0611,W0605 [pylama:pylint] max_line_length = 100 From f9e9e70771598f4316e6b64b6d6016925f50c3e4 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Thu, 3 Oct 2019 21:33:11 +0200 Subject: [PATCH 03/17] experimentation file --- test/experiment_telegram.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 test/experiment_telegram.py diff --git a/test/experiment_telegram.py b/test/experiment_telegram.py new file mode 100644 index 0000000..3450a05 --- /dev/null +++ b/test/experiment_telegram.py @@ -0,0 +1,14 @@ +from decimal import Decimal +import datetime +import unittest +import pytz +from dsmr_parser import obis_references as obis +from dsmr_parser import telegram_specifications +from dsmr_parser.exceptions import InvalidChecksumError, ParseError +from dsmr_parser.objects import CosemObject, MBusObject, Telegram +from dsmr_parser.parsers import TelegramParser +from test.example_telegrams import TELEGRAM_V4_2 +parser = TelegramParser(telegram_specifications.V4) +telegram = Telegram(TELEGRAM_V4_2, parser, telegram_specifications.V4) + +print(telegram) \ No newline at end of file From f7ba363b93598c98ce46230994c4fcfb619a559d Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Mon, 25 Nov 2019 01:37:48 +0100 Subject: [PATCH 04/17] small fixes --- dsmr_parser/clients/serial_.py | 2 +- test/experiment_telegram.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dsmr_parser/clients/serial_.py b/dsmr_parser/clients/serial_.py index c252f6f..319becc 100644 --- a/dsmr_parser/clients/serial_.py +++ b/dsmr_parser/clients/serial_.py @@ -55,7 +55,7 @@ class SerialReader(object): for telegram in self.telegram_buffer.get_all(): try: - yield Telegram(telegram, telegram_parser, telegram_specification) + yield Telegram(telegram, self.telegram_parser, self.telegram_specification) except InvalidChecksumError as e: logger.warning(str(e)) except ParseError as e: diff --git a/test/experiment_telegram.py b/test/experiment_telegram.py index 3450a05..2649f51 100644 --- a/test/experiment_telegram.py +++ b/test/experiment_telegram.py @@ -7,7 +7,7 @@ from dsmr_parser import telegram_specifications from dsmr_parser.exceptions import InvalidChecksumError, ParseError from dsmr_parser.objects import CosemObject, MBusObject, Telegram from dsmr_parser.parsers import TelegramParser -from test.example_telegrams import TELEGRAM_V4_2 +from example_telegrams import TELEGRAM_V4_2 parser = TelegramParser(telegram_specifications.V4) telegram = Telegram(TELEGRAM_V4_2, parser, telegram_specifications.V4) From 8ba400800b7f5b663e000919219f506b30ca8cc0 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Mon, 25 Nov 2019 01:53:54 +0100 Subject: [PATCH 05/17] small fixes --- dsmr_parser/clients/serial_.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dsmr_parser/clients/serial_.py b/dsmr_parser/clients/serial_.py index 319becc..265cedd 100644 --- a/dsmr_parser/clients/serial_.py +++ b/dsmr_parser/clients/serial_.py @@ -21,6 +21,7 @@ class SerialReader(object): self.telegram_parser = TelegramParser(telegram_specification) self.telegram_buffer = TelegramBuffer() + self.telegram_specification = telegram_specification def read(self): """ From d712d468ac5c3eee28d03e3fa893b0a2fa00fc75 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Mon, 25 Nov 2019 20:44:25 +0100 Subject: [PATCH 06/17] remove useless space --- dsmr_parser/objects.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dsmr_parser/objects.py b/dsmr_parser/objects.py index 940318a..e7374ab 100644 --- a/dsmr_parser/objects.py +++ b/dsmr_parser/objects.py @@ -47,7 +47,6 @@ class Telegram(object): def __str__(self): output = "" for attr,value in self: - output += " " if not output == "" else "" output += "{}: \t {} \t[{}]\n".format(attr,str(value.value),str(value.unit)) return output From a137ef0e02fa97dd11a1e28a25a0318cc9a4dc1b Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Sun, 1 Dec 2019 17:47:22 +0100 Subject: [PATCH 07/17] add some documentation for the use of the telegram as an object --- README.rst | 114 ++++++++++++++++++++++++++++++++++++++++- dsmr_parser/objects.py | 2 +- 2 files changed, 113 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 7b53ee4..5f0d7d6 100644 --- a/README.rst +++ b/README.rst @@ -85,8 +85,8 @@ into a dictionary. telegram = parser.parse(telegram_str) print(telegram) # see 'Telegram object' docs below -Telegram object ---------------- +Telegram dictionary +------------------- A dictionary of which the key indicates the field type. These regex values correspond to one of dsmr_parser.obis_reference constants. @@ -138,6 +138,116 @@ Example to get some of the values: # See dsmr_reader.obis_references for all readable telegram values. # Note that the avilable values differ per DSMR version. +Telegram as an Object +--------------------- +An object version of the telegram is available as well. + + +.. code-block:: python + + # DSMR v4.2 p1 using dsmr_parser and telegram objects + + from dsmr_parser import telegram_specifications + from dsmr_parser.clients import SerialReader, SERIAL_SETTINGS_V5 + from dsmr_parser.objects import CosemObject, MBusObject, Telegram + from dsmr_parser.parsers import TelegramParser + import os + + serial_reader = SerialReader( + device='/dev/ttyUSB0', + serial_settings=SERIAL_SETTINGS_V5, + telegram_specification=telegram_specifications.V4 + ) + + # telegram = next(serial_reader.read_as_object()) + # print(telegram) + + for telegram in serial_reader.read_as_object(): + os.system('clear') + print(telegram) + +Example of output of print of the telegram object: + +.. code-block:: console + + P1_MESSAGE_HEADER: 42 [None] + P1_MESSAGE_TIMESTAMP: 2016-11-13 19:57:57+00:00 [None] + EQUIPMENT_IDENTIFIER: 3960221976967177082151037881335713 [None] + ELECTRICITY_USED_TARIFF_1: 1581.123 [kWh] + ELECTRICITY_USED_TARIFF_2: 1435.706 [kWh] + ELECTRICITY_DELIVERED_TARIFF_1: 0.000 [kWh] + ELECTRICITY_DELIVERED_TARIFF_2: 0.000 [kWh] + ELECTRICITY_ACTIVE_TARIFF: 0002 [None] + CURRENT_ELECTRICITY_USAGE: 2.027 [kW] + CURRENT_ELECTRICITY_DELIVERY: 0.000 [kW] + LONG_POWER_FAILURE_COUNT: 7 [None] + VOLTAGE_SAG_L1_COUNT: 0 [None] + VOLTAGE_SAG_L2_COUNT: 0 [None] + VOLTAGE_SAG_L3_COUNT: 0 [None] + VOLTAGE_SWELL_L1_COUNT: 0 [None] + VOLTAGE_SWELL_L2_COUNT: 0 [None] + VOLTAGE_SWELL_L3_COUNT: 0 [None] + TEXT_MESSAGE_CODE: None [None] + TEXT_MESSAGE: None [None] + DEVICE_TYPE: 3 [None] + INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: 0.170 [kW] + INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE: 1.247 [kW] + INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE: 0.209 [kW] + INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE: 0.000 [kW] + INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE: 0.000 [kW] + INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: 0.000 [kW] + EQUIPMENT_IDENTIFIER_GAS: 4819243993373755377509728609491464 [None] + HOURLY_GAS_METER_READING: 981.443 [m3] + +Accessing the telegrams information as attributes directly: + +.. code-block:: python + + telegram + Out[3]: + telegram.CURRENT_ELECTRICITY_USAGE.value + Out[4]: Decimal('2.027') + telegram.CURRENT_ELECTRICITY_USAGE.unit + Out[5]: 'kW' + +The telegram object has an iterator, can be used to find all the elements in the current telegram: + +.. code-block:: python + + for attr, value in telegram: + print(attr) + + Out[7]: + P1_MESSAGE_HEADER + P1_MESSAGE_TIMESTAMP + EQUIPMENT_IDENTIFIER + ELECTRICITY_USED_TARIFF_1 + ELECTRICITY_USED_TARIFF_2 + ELECTRICITY_DELIVERED_TARIFF_1 + ELECTRICITY_DELIVERED_TARIFF_2 + ELECTRICITY_ACTIVE_TARIFF + CURRENT_ELECTRICITY_USAGE + CURRENT_ELECTRICITY_DELIVERY + LONG_POWER_FAILURE_COUNT + VOLTAGE_SAG_L1_COUNT + VOLTAGE_SAG_L2_COUNT + VOLTAGE_SAG_L3_COUNT + VOLTAGE_SWELL_L1_COUNT + VOLTAGE_SWELL_L2_COUNT + VOLTAGE_SWELL_L3_COUNT + TEXT_MESSAGE_CODE + TEXT_MESSAGE + DEVICE_TYPE + INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE + INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE + INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE + INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE + INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE + INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE + EQUIPMENT_IDENTIFIER_GAS + HOURLY_GAS_METER_READING + + Installation ------------ diff --git a/dsmr_parser/objects.py b/dsmr_parser/objects.py index e7374ab..07d576d 100644 --- a/dsmr_parser/objects.py +++ b/dsmr_parser/objects.py @@ -46,7 +46,7 @@ class Telegram(object): def __str__(self): output = "" - for attr,value in self: + for attr, value in self: output += "{}: \t {} \t[{}]\n".format(attr,str(value.value),str(value.unit)) return output From 1b522fc7f088f22a8b54df5605dada8fc79cb5f7 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Sun, 1 Dec 2019 18:34:21 +0100 Subject: [PATCH 08/17] add some documentation for the use of the telegram as an object --- README.rst | 65 ++++++++++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/README.rst b/README.rst index 5f0d7d6..11ce600 100644 --- a/README.rst +++ b/README.rst @@ -210,43 +210,40 @@ Accessing the telegrams information as attributes directly: telegram.CURRENT_ELECTRICITY_USAGE.unit Out[5]: 'kW' -The telegram object has an iterator, can be used to find all the elements in the current telegram: +The telegram object has an iterator, can be used to find all the information elements in the current telegram: .. code-block:: python - for attr, value in telegram: - print(attr) - - Out[7]: - P1_MESSAGE_HEADER - P1_MESSAGE_TIMESTAMP - EQUIPMENT_IDENTIFIER - ELECTRICITY_USED_TARIFF_1 - ELECTRICITY_USED_TARIFF_2 - ELECTRICITY_DELIVERED_TARIFF_1 - ELECTRICITY_DELIVERED_TARIFF_2 - ELECTRICITY_ACTIVE_TARIFF - CURRENT_ELECTRICITY_USAGE - CURRENT_ELECTRICITY_DELIVERY - LONG_POWER_FAILURE_COUNT - VOLTAGE_SAG_L1_COUNT - VOLTAGE_SAG_L2_COUNT - VOLTAGE_SAG_L3_COUNT - VOLTAGE_SWELL_L1_COUNT - VOLTAGE_SWELL_L2_COUNT - VOLTAGE_SWELL_L3_COUNT - TEXT_MESSAGE_CODE - TEXT_MESSAGE - DEVICE_TYPE - INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE - INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE - INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE - INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE - INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE - INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE - EQUIPMENT_IDENTIFIER_GAS - HOURLY_GAS_METER_READING - + [attr for attr, value in telegram] + Out[11]: + ['P1_MESSAGE_HEADER', + 'P1_MESSAGE_TIMESTAMP', + 'EQUIPMENT_IDENTIFIER', + 'ELECTRICITY_USED_TARIFF_1', + 'ELECTRICITY_USED_TARIFF_2', + 'ELECTRICITY_DELIVERED_TARIFF_1', + 'ELECTRICITY_DELIVERED_TARIFF_2', + 'ELECTRICITY_ACTIVE_TARIFF', + 'CURRENT_ELECTRICITY_USAGE', + 'CURRENT_ELECTRICITY_DELIVERY', + 'LONG_POWER_FAILURE_COUNT', + 'VOLTAGE_SAG_L1_COUNT', + 'VOLTAGE_SAG_L2_COUNT', + 'VOLTAGE_SAG_L3_COUNT', + 'VOLTAGE_SWELL_L1_COUNT', + 'VOLTAGE_SWELL_L2_COUNT', + 'VOLTAGE_SWELL_L3_COUNT', + 'TEXT_MESSAGE_CODE', + 'TEXT_MESSAGE', + 'DEVICE_TYPE', + 'INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE', + 'INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE', + 'INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE', + 'INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE', + 'INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE', + 'INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE', + 'EQUIPMENT_IDENTIFIER_GAS', + 'HOURLY_GAS_METER_READING'] Installation From ea804d485ebbf5f8bb98b5902e2a32ae69f21040 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Sun, 1 Dec 2019 18:39:21 +0100 Subject: [PATCH 09/17] add some documentation for the use of the telegram as an object --- README.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 11ce600..47fc884 100644 --- a/README.rst +++ b/README.rst @@ -205,10 +205,12 @@ Accessing the telegrams information as attributes directly: telegram Out[3]: + telegram.CURRENT_ELECTRICITY_USAGE + Out[4]: telegram.CURRENT_ELECTRICITY_USAGE.value - Out[4]: Decimal('2.027') + Out[5]: Decimal('2.027') telegram.CURRENT_ELECTRICITY_USAGE.unit - Out[5]: 'kW' + Out[6]: 'kW' The telegram object has an iterator, can be used to find all the information elements in the current telegram: From aa4f9fb96751bcd2d210e347376ad71498afb6da Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Sun, 1 Dec 2019 18:55:53 +0100 Subject: [PATCH 10/17] update version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b20a2a8..673267f 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( author='Nigel Dokter', author_email='nigel@nldr.net', url='https://github.com/ndokter/dsmr_parser', - version='0.14', + version='0.15', packages=find_packages(), install_requires=[ 'pyserial>=3,<4', From 5313edd6cb84ace9a2549a0e482103d738338ace Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Mon, 9 Dec 2019 22:31:40 +0100 Subject: [PATCH 11/17] add file reader --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 20de282..4dfc343 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ /.coverage build/ dist/ +*.*~ +*~ \ No newline at end of file From 3c78b0b6c4f17246fa4a6a1d0e9db095ce987cbc Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Mon, 9 Dec 2019 22:36:46 +0100 Subject: [PATCH 12/17] add file reader --- dsmr_parser/clients/filereader.py | 35 +++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 dsmr_parser/clients/filereader.py diff --git a/dsmr_parser/clients/filereader.py b/dsmr_parser/clients/filereader.py new file mode 100644 index 0000000..ee1fc90 --- /dev/null +++ b/dsmr_parser/clients/filereader.py @@ -0,0 +1,35 @@ +from dsmr_parser import telegram_specifications +from dsmr_parser.clients.telegram_buffer import TelegramBuffer +from dsmr_parser.exceptions import ParseError, InvalidChecksumError +from dsmr_parser.objects import CosemObject, MBusObject, Telegram +from dsmr_parser.parsers import TelegramParser +import os + +logger = logging.getLogger(__name__) + +class FileReader(object): + + def __init__(self, file, telegram_specification): + self._file = file + self.telegram_parser = TelegramParser(telegram_specification) + self.telegram_buffer = TelegramBuffer() + self.telegram_specification = telegram_specification + + def read_as_object(self): + """ + Read complete DSMR telegram's from a file and return a Telegram object. + :rtype: generator + """ + with open(self._file,"rb") as file_handle: + while True: + data = file_handle.readline() + str = data.decode() + self.telegram_buffer.append(str) + + for telegram in self.telegram_buffer.get_all(): + try: + yield Telegram(telegram, self.telegram_parser, self.telegram_specification) + except InvalidChecksumError as e: + logger.warning(str(e)) + except ParseError as e: + logger.error('Failed to parse telegram: %s', e) From a65949823d9303055ff31dfe6cd4083c146721d7 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Mon, 9 Dec 2019 23:27:57 +0100 Subject: [PATCH 13/17] fix filereader --- dsmr_parser/clients/filereader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dsmr_parser/clients/filereader.py b/dsmr_parser/clients/filereader.py index ee1fc90..30396d9 100644 --- a/dsmr_parser/clients/filereader.py +++ b/dsmr_parser/clients/filereader.py @@ -1,9 +1,9 @@ -from dsmr_parser import telegram_specifications +import logging + from dsmr_parser.clients.telegram_buffer import TelegramBuffer from dsmr_parser.exceptions import ParseError, InvalidChecksumError -from dsmr_parser.objects import CosemObject, MBusObject, Telegram +from dsmr_parser.objects import Telegram from dsmr_parser.parsers import TelegramParser -import os logger = logging.getLogger(__name__) From c1d7ba151d7bb7f3a58b38d44f8f0c880da0f365 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Mon, 9 Dec 2019 23:48:48 +0100 Subject: [PATCH 14/17] add some documentation for the file reader --- dsmr_parser/clients/filereader.py | 38 +++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/dsmr_parser/clients/filereader.py b/dsmr_parser/clients/filereader.py index 30396d9..dae492d 100644 --- a/dsmr_parser/clients/filereader.py +++ b/dsmr_parser/clients/filereader.py @@ -8,6 +8,44 @@ from dsmr_parser.parsers import TelegramParser logger = logging.getLogger(__name__) class FileReader(object): + """ + Filereader to read and parse raw telegram strings from a file and instantiate Telegram objects + for each read telegram. + Usage: + from dsmr_parser import telegram_specifications + from dsmr_parser.clients.filereader import FileReader + + if __name__== "__main__": + + infile = '/data/smartmeter/readings.txt' + + file_reader = FileReader( + file = infile, + telegram_specification = telegram_specifications.V4 + ) + + for telegram in file_reader.read_as_object(): + print(telegram) + + The file can be created like: + from dsmr_parser import telegram_specifications + from dsmr_parser.clients import SerialReader, SERIAL_SETTINGS_V5 + + if __name__== "__main__": + + outfile = '/data/smartmeter/readings.txt' + + serial_reader = SerialReader( + device='/dev/ttyUSB0', + serial_settings=SERIAL_SETTINGS_V5, + telegram_specification=telegram_specifications.V4 + ) + + for telegram in serial_reader.read_as_object(): + f=open(outfile,"ab+") + f.write(telegram._telegram_data.encode()) + f.close() + """ def __init__(self, file, telegram_specification): self._file = file From 50cef2646b18171ec6b50e4abde75438813815b5 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Mon, 16 Dec 2019 15:30:55 +0100 Subject: [PATCH 15/17] add fileinputreader --- dsmr_parser/clients/filereader.py | 44 +++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/dsmr_parser/clients/filereader.py b/dsmr_parser/clients/filereader.py index dae492d..c2a35cb 100644 --- a/dsmr_parser/clients/filereader.py +++ b/dsmr_parser/clients/filereader.py @@ -71,3 +71,47 @@ class FileReader(object): logger.warning(str(e)) except ParseError as e: logger.error('Failed to parse telegram: %s', e) + +class FileInputReader(object): + """ + Filereader to read and parse raw telegram strings from stdin or files specified at the commandline + and instantiate Telegram objects for each read telegram. + Usage: + from dsmr_parser import telegram_specifications + from dsmr_parser.clients.filereader import FileInputReader + + if __name__== "__main__": + + fileinput_reader = FileReader( + file = infile, + telegram_specification = telegram_specifications.V4 + ) + + for telegram in fileinput_reader.read_as_object(): + print(telegram) + """ + + def __init__(self, telegram_specification): + self.telegram_parser = TelegramParser(telegram_specification) + self.telegram_buffer = TelegramBuffer() + self.telegram_specification = telegram_specification + + def read_as_object(self): + """ + Read complete DSMR telegram's from stdin of filearguments specified on teh command line + and return a Telegram object. + :rtype: generator + """ + with fileinput.input(mode='rb') as file_handle: + while True: + data = file_handle.readline() + str = data.decode() + self.telegram_buffer.append(str) + + for telegram in self.telegram_buffer.get_all(): + try: + yield Telegram(telegram, self.telegram_parser, self.telegram_specification) + except InvalidChecksumError as e: + logger.warning(str(e)) + except ParseError as e: + logger.error('Failed to parse telegram: %s', e) From 43500e6bc25e596ef985b39481a86adedcfa541d Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Mon, 16 Dec 2019 15:36:13 +0100 Subject: [PATCH 16/17] fix failing import --- dsmr_parser/clients/filereader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dsmr_parser/clients/filereader.py b/dsmr_parser/clients/filereader.py index c2a35cb..f4ed6fd 100644 --- a/dsmr_parser/clients/filereader.py +++ b/dsmr_parser/clients/filereader.py @@ -1,4 +1,5 @@ import logging +import fileinput from dsmr_parser.clients.telegram_buffer import TelegramBuffer from dsmr_parser.exceptions import ParseError, InvalidChecksumError From 2d4b0d8e7266acf4f5c938157d1cb203ebe6a426 Mon Sep 17 00:00:00 2001 From: Hans Erik van Elburg Date: Mon, 16 Dec 2019 15:41:24 +0100 Subject: [PATCH 17/17] fix documentation FileInputReader --- dsmr_parser/clients/filereader.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dsmr_parser/clients/filereader.py b/dsmr_parser/clients/filereader.py index f4ed6fd..e6eeb59 100644 --- a/dsmr_parser/clients/filereader.py +++ b/dsmr_parser/clients/filereader.py @@ -77,7 +77,7 @@ class FileInputReader(object): """ Filereader to read and parse raw telegram strings from stdin or files specified at the commandline and instantiate Telegram objects for each read telegram. - Usage: + Usage python script "syphon_smartmeter_readings_stdin.py": from dsmr_parser import telegram_specifications from dsmr_parser.clients.filereader import FileInputReader @@ -90,6 +90,10 @@ class FileInputReader(object): for telegram in fileinput_reader.read_as_object(): print(telegram) + + Command line: + tail -f /data/smartmeter/readings.txt | python3 syphon_smartmeter_readings_stdin.py + """ def __init__(self, telegram_specification):