issue-51-telegram refactored TelegramParser.parse to return Telegram … (#121)

* issue-51-telegram improved mbus device parsing; refactored TelegramParser.parse to return Telegram object which is backwards compatible with the dict result
This commit is contained in:
Nigel Dokter 2023-02-19 12:24:44 +01:00 committed by GitHub
parent 6fb2ad3495
commit de167c89b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 724 additions and 407 deletions

View File

@ -1,71 +1,74 @@
Change Log
----------
**1.2.0** (2023-02-18)
- Improved gas meter (mbus devices) support and replaced Telegram dictionary with backwards compatible object (`PR #121 <https://github.com/ndokter/dsmr_parser/pull/121>`_ by `ndokter <https://github.com/ndokter>`_)
**1.1.0** (2023-02-08)
- Add instantaneous reactive power + fixed swapped reactive total import export (`pull request #124 <https://github.com/ndokter/dsmr_parser/pull/124>`_ by `yada75 <https://github.com/yada75>`_)
- Add instantaneous reactive power + fixed swapped reactive total import export (`PR #124 <https://github.com/ndokter/dsmr_parser/pull/124>`_ by `yada75 <https://github.com/yada75>`_)
**1.0.0** (2022-12-22)
- switched to new numbering scheme https://semver.org/
- Added support for Python 3.11 and dropped support for Python 3.6 (`pull request #112 <https://github.com/ndokter/dsmr_parser/pull/112>`_)
- Add support for Fluvius V1.7.1 DSMR messages (`pull request #110 <https://github.com/ndokter/dsmr_parser/pull/113>`_)
- Added support for Python 3.11 and dropped support for Python 3.6 (`PR #112 <https://github.com/ndokter/dsmr_parser/pull/112>`_ by `dennissiemensma <https://github.com/dennissiemensma>`_)
- Add support for Fluvius V1.7.1 DSMR messages (`PR #110 <https://github.com/ndokter/dsmr_parser/pull/113>`_ by `dupondje <https://github.com/dupondje>`_)
**0.34** (2022-10-19)
- Adds support for the Sagemcom T210-D-r smart meter (`pull request #110 <https://github.com/ndokter/dsmr_parser/pull/110>`_).
- Adds support for the Sagemcom T210-D-r smart meter (`PR #110 <https://github.com/ndokter/dsmr_parser/pull/110>`_).
**0.33** (2022-04-20)
- Test Python 3.10 in CI + legacy badge fix (`pull request #105 <https://github.com/ndokter/dsmr_parser/pull/105>`_).
- Update telegram_specifications.py (`pull request #106 <https://github.com/ndokter/dsmr_parser/pull/106>`_).
- Improve compatiblity with Belgian standard (`pull request #107 <https://github.com/ndokter/dsmr_parser/pull/107>`_).
- Improve documentation asyncio (`pull request #63 <https://github.com/ndokter/dsmr_parser/pull/63>`_).
- Test Python 3.10 in CI + legacy badge fix (`PR #105 <https://github.com/ndokter/dsmr_parser/pull/105>`_).
- Update telegram_specifications.py (`PR #106 <https://github.com/ndokter/dsmr_parser/pull/106>`_).
- Improve compatiblity with Belgian standard (`PR #107 <https://github.com/ndokter/dsmr_parser/pull/107>`_).
- Improve documentation asyncio (`PR #63 <https://github.com/ndokter/dsmr_parser/pull/63>`_).
**0.32** (2022-01-04)
- Support DSMR data read via RFXtrx with integrated P1 reader (`pull request #98 <https://github.com/ndokter/dsmr_parser/pull/98>`_).
- Support DSMR data read via RFXtrx with integrated P1 reader (`PR #98 <https://github.com/ndokter/dsmr_parser/pull/98>`_).
**0.31** (2021-11-21)
- Support for (German) EasyMeter Q3D using COM-1 Ethernet Gateway (`pull request #92 <https://github.com/ndokter/dsmr_parser/pull/92>`_).
- Support for (German) EasyMeter Q3D using COM-1 Ethernet Gateway (`PR #92 <https://github.com/ndokter/dsmr_parser/pull/92>`_).
**0.30** (2021-08-18)
- Add support for Swedish smart meters (`pull request #86 <https://github.com/ndokter/dsmr_parser/pull/86>`_).
- Add support for Swedish smart meters (`PR #86 <https://github.com/ndokter/dsmr_parser/pull/86>`_).
**0.29** (2021-04-18)
- Add value and unit properties to ProfileGenericObject to make sure that code like iterators that rely on that do not break (`pull request #71 <https://github.com/ndokter/dsmr_parser/pull/71>`_).
Remove deprecated asyncio coroutine decorator (`pull request #76 <https://github.com/ndokter/dsmr_parser/pull/76>`_).
- Add value and unit properties to ProfileGenericObject to make sure that code like iterators that rely on that do not break (`PR #71 <https://github.com/ndokter/dsmr_parser/pull/71>`_).
Remove deprecated asyncio coroutine decorator (`PR #76 <https://github.com/ndokter/dsmr_parser/pull/76>`_).
**0.28** (2021-02-21)
- Optional keep alive monitoring for TCP/IP connections (`pull request #73 <https://github.com/ndokter/dsmr_parser/pull/73>`_).
- Catch parse errors in TelegramParser, ignore lines that can not be parsed (`pull request #74 <https://github.com/ndokter/dsmr_parser/pull/74>`_).
- Optional keep alive monitoring for TCP/IP connections (`PR #73 <https://github.com/ndokter/dsmr_parser/pull/73>`_).
- Catch parse errors in TelegramParser, ignore lines that can not be parsed (`PR #74 <https://github.com/ndokter/dsmr_parser/pull/74>`_).
**0.27** (2020-12-24)
- fix for empty parentheses in ProfileGenericParser (redone) (`pull request #69 <https://github.com/ndokter/dsmr_parser/pull/69>`_).
- fix for empty parentheses in ProfileGenericParser (redone) (`PR #69 <https://github.com/ndokter/dsmr_parser/pull/69>`_).
**0.26** (2020-12-15)
- reverted fix for empty parentheses in ProfileGenericParser (`pull request #68 <https://github.com/ndokter/dsmr_parser/pull/68>`_).
- reverted fix for empty parentheses in ProfileGenericParser (`PR #68 <https://github.com/ndokter/dsmr_parser/pull/68>`_).
**0.25** (2020-12-14)
- fix for empty parentheses in ProfileGenericParser (`pull request #57 <https://github.com/ndokter/dsmr_parser/pull/57>`_).
- fix for empty parentheses in ProfileGenericParser (`PR #57 <https://github.com/ndokter/dsmr_parser/pull/57>`_).
**0.24** (2020-11-27)
- Add Luxembourg equipment identifier (`pull request #62 <https://github.com/ndokter/dsmr_parser/pull/62>`_).
- Add Luxembourg equipment identifier (`PR #62 <https://github.com/ndokter/dsmr_parser/pull/62>`_).
**0.23** (2020-11-07)
- Resolved issue with x-x:24.3.0 where it contains non-integer character (`pull request #61 <https://github.com/ndokter/dsmr_parser/pull/61>`_).
- Tests are not installed anymore (`pull request #59 <https://github.com/ndokter/dsmr_parser/pull/59>`_).
- Example telegram improvement (`pull request #58 <https://github.com/ndokter/dsmr_parser/pull/58>`_).
- Resolved issue with x-x:24.3.0 where it contains non-integer character (`PR #61 <https://github.com/ndokter/dsmr_parser/pull/61>`_).
- Tests are not installed anymore (`PR #59 <https://github.com/ndokter/dsmr_parser/pull/59>`_).
- Example telegram improvement (`PR #58 <https://github.com/ndokter/dsmr_parser/pull/58>`_).
**0.22** (2020-08-23)
@ -93,40 +96,40 @@ Remove deprecated asyncio coroutine decorator (`pull request #76 <https://github
**0.18** (2020-01-28)
- PyCRC replacement (`pull request #48 <https://github.com/ndokter/dsmr_parser/pull/48>`_).
- PyCRC replacement (`PR #48 <https://github.com/ndokter/dsmr_parser/pull/48>`_).
**0.17** (2019-12-21)
- Add a true telegram object (`pull request #40 <https://github.com/ndokter/dsmr_parser/pull/40>`_).
- Add a true telegram object (`PR #40 <https://github.com/ndokter/dsmr_parser/pull/40>`_).
**0.16** (2019-12-21)
- Add support for Belgian and Smarty meters (`pull request #44 <https://github.com/ndokter/dsmr_parser/pull/44>`_).
- Add support for Belgian and Smarty meters (`PR #44 <https://github.com/ndokter/dsmr_parser/pull/44>`_).
**0.15** (2019-12-12)
- Fixed asyncio loop issue (`pull request #43 <https://github.com/ndokter/dsmr_parser/pull/43>`_).
- Fixed asyncio loop issue (`PR #43 <https://github.com/ndokter/dsmr_parser/pull/43>`_).
**0.14** (2019-10-08)
- Changed serial reading to reduce CPU usage (`pull request #37 <https://github.com/ndokter/dsmr_parser/pull/37>`_).
- Changed serial reading to reduce CPU usage (`PR #37 <https://github.com/ndokter/dsmr_parser/pull/37>`_).
**0.13** (2019-03-04)
- Fix DSMR v5.0 serial settings which were not used (`pull request #33 <https://github.com/ndokter/dsmr_parser/pull/33>`_).
- Fix DSMR v5.0 serial settings which were not used (`PR #33 <https://github.com/ndokter/dsmr_parser/pull/33>`_).
**0.12** (2018-09-23)
- Add serial settings for DSMR v5.0 (`pull request #31 <https://github.com/ndokter/dsmr_parser/pull/31>`_).
- Lux-creos-obis-1.8.0 (`pull request #32 <https://github.com/ndokter/dsmr_parser/pull/32>`_).
- Add serial settings for DSMR v5.0 (`PR #31 <https://github.com/ndokter/dsmr_parser/pull/31>`_).
- Lux-creos-obis-1.8.0 (`PR #32 <https://github.com/ndokter/dsmr_parser/pull/32>`_).
**0.11** (2017-09-18)
- NULL value fix in checksum (`pull request #26 <https://github.com/ndokter/dsmr_parser/pull/26>`_)
- NULL value fix in checksum (`PR #26 <https://github.com/ndokter/dsmr_parser/pull/26>`_)
**0.10** (2017-06-05)
- bugfix: don't force full telegram signatures (`pull request #25 <https://github.com/ndokter/dsmr_parser/pull/25>`_)
- bugfix: don't force full telegram signatures (`PR #25 <https://github.com/ndokter/dsmr_parser/pull/25>`_)
- removed unused code for automatic telegram detection as this needs reworking after the fix mentioned above
- InvalidChecksumError's are logged as warning instead of error
@ -146,7 +149,7 @@ Remove deprecated asyncio coroutine decorator (`pull request #76 <https://github
**0.7** (2017-01-14)
- Internal refactoring related to the way clients feed their data into the parse module. Clients can now supply the telegram data in single characters, lines (which was common) or complete telegram strings. (`pull request #17 <https://github.com/ndokter/dsmr_parser/pull/17>`_)
- Internal refactoring related to the way clients feed their data into the parse module. Clients can now supply the telegram data in single characters, lines (which was common) or complete telegram strings. (`PR #17 <https://github.com/ndokter/dsmr_parser/pull/17>`_)
**IMPORTANT: this release has the following backwards incompatible changes:**
@ -156,8 +159,8 @@ Remove deprecated asyncio coroutine decorator (`pull request #76 <https://github
**0.6** (2017-01-04)
- Fixed bug in CRC checksum verification for the asyncio client (`pull request #15 <https://github.com/ndokter/dsmr_parser/pull/15>`_)
- Support added for TCP connections using the asyncio client (`pull request #12 <https://github.com/ndokter/dsmr_parser/pull/12/>`_)
- Fixed bug in CRC checksum verification for the asyncio client (`PR #15 <https://github.com/ndokter/dsmr_parser/pull/15>`_)
- Support added for TCP connections using the asyncio client (`PR #12 <https://github.com/ndokter/dsmr_parser/pull/12/>`_)
**0.5** (2016-12-29)
@ -165,16 +168,16 @@ Remove deprecated asyncio coroutine decorator (`pull request #76 <https://github
**0.4** (2016-11-21)
- DSMR v2.2 serial settings now uses parity serial.EVEN by default (`pull request #5 <https://github.com/ndokter/dsmr_parser/pull/5>`_)
- improved asyncio reader and improve it's error handling (`pull request #8 <https://github.com/ndokter/dsmr_parser/pull/8>`_)
- DSMR v2.2 serial settings now uses parity serial.EVEN by default (`PR #5 <https://github.com/ndokter/dsmr_parser/pull/5>`_)
- improved asyncio reader and improve it's error handling (`PR #8 <https://github.com/ndokter/dsmr_parser/pull/8>`_)
**0.3** (2016-11-12)
- asyncio reader for non-blocking reads (`pull request #3 <https://github.com/ndokter/dsmr_parser/pull/3>`_)
- asyncio reader for non-blocking reads (`PR #3 <https://github.com/ndokter/dsmr_parser/pull/3>`_)
**0.2** (2016-11-08)
- support for DMSR version 2.2 (`pull request #2 <https://github.com/ndokter/dsmr_parser/pull/2>`_)
- support for DMSR version 2.2 (`PR #2 <https://github.com/ndokter/dsmr_parser/pull/2>`_)
**0.1** (2016-08-22)

View File

@ -16,7 +16,6 @@ Features
DSMR Parser supports DSMR versions 2, 3, 4 and 5. See for the `currently supported/tested Python versions here <https://github.com/ndokter/dsmr_parser/blob/master/.github/workflows/tests.yml#L14>`_.
Client module usage
-------------------
@ -113,10 +112,8 @@ However, if we construct a mock TelegramParser that just returns the already par
import asyncio
import logging
#from dsmr_parser import obis_references
#from dsmr_parser import telegram_specifications
#from dsmr_parser.clients.protocol import create_dsmr_reader, create_tcp_dsmr_reader
#from dsmr_parser.objects import Telegram
from dsmr_parser import telegram_specifications
from dsmr_parser.clients.protocol import create_tcp_dsmr_reader
logging.basicConfig(level=logging.INFO, format='%(message)s')
@ -142,19 +139,18 @@ However, if we construct a mock TelegramParser that just returns the already par
except ParseError as e:
logger.error('Failed to parse telegram: %s', e)
async def main():
try:
logger.debug("Getting loop")
loop = asyncio.get_event_loop()
logger.debug("Creating reader")
await create_tcp_dsmr_reader(
HOST,
PORT,
DSMR_VERSION,
printTelegram,
loop
)
await create_tcp_dsmr_reader(
HOST,
PORT,
DSMR_VERSION,
printTelegram,
loop
)
logger.debug("Reader created going to sleep now")
while True:
await asyncio.sleep(1)
@ -173,7 +169,9 @@ However, if we construct a mock TelegramParser that just returns the already par
Parsing module usage
--------------------
The parsing module accepts complete unaltered telegram strings and parses these
into a dictionary.
into a Telegram object.
Tip: getting full telegrams from a bytestream can be made easier by using the TelegramBuffer helper class.
.. code-block:: python
@ -208,135 +206,48 @@ into a dictionary.
parser = TelegramParser(telegram_specifications.V3)
# see 'Telegram object' docs below
telegram = parser.parse(telegram_str)
print(telegram) # see 'Telegram object' docs below
Telegram dictionary
-------------------
A dictionary of which the key indicates the field type. These regex values
correspond to one of dsmr_parser.obis_reference constants.
The value is either a CosemObject or MBusObject. These have a 'value' and 'unit'
property. MBusObject's additionally have a 'datetime' property. The 'value' can
contain any python type (int, str, Decimal) depending on the field. The 'unit'
contains 'kW', 'A', 'kWh' or 'm3'.
.. code-block:: python
# Contents of a parsed DSMR v3 telegram
{'\\d-\\d:17\\.0\\.0.+?\\r\\n': <dsmr_parser.objects.CosemObject object at 0x10fc39eb8>,
'\\d-\\d:1\\.7\\.0.+?\\r\\n': <dsmr_parser.objects.CosemObject object at 0x10f916390>,
'\\d-\\d:1\\.8\\.1.+?\\r\\n': <dsmr_parser.objects.CosemObject object at 0x10fc39e10>,
'\\d-\\d:1\\.8\\.2.+?\\r\\n': <dsmr_parser.objects.CosemObject object at 0x10fc39ef0>,
'\\d-\\d:24\\.1\\.0.+?\\r\\n': <dsmr_parser.objects.CosemObject object at 0x10fbaef28>,
'\\d-\\d:24\\.3\\.0.+?\\r\\n.+?\\r\\n': <dsmr_parser.objects.MBusObject object at 0x10f9163c8>,
'\\d-\\d:24\\.4\\.0.+?\\r\\n': <dsmr_parser.objects.CosemObject object at 0x10fc39f60>,
'\\d-\\d:2\\.7\\.0.+?\\r\\n': <dsmr_parser.objects.CosemObject object at 0x10fc39fd0>,
'\\d-\\d:2\\.8\\.1.+?\\r\\n': <dsmr_parser.objects.CosemObject object at 0x10fbaee10>,
'\\d-\\d:2\\.8\\.2.+?\\r\\n': <dsmr_parser.objects.CosemObject object at 0x10fc39e80>,
'\\d-\\d:96\\.13\\.0.+?\\r\\n': <dsmr_parser.objects.CosemObject object at 0x10fc39d30>,
'\\d-\\d:96\\.13\\.1.+?\\r\\n': <dsmr_parser.objects.CosemObject object at 0x10fbaeeb8>,
'\\d-\\d:96\\.14\\.0.+?\\r\\n': <dsmr_parser.objects.CosemObject object at 0x10fbaef98>,
'\\d-\\d:96\\.1\\.0.+?\\r\\n': <dsmr_parser.objects.CosemObject object at 0x10fbaef60>,
'\\d-\\d:96\\.1\\.1.+?\\r\\n': <dsmr_parser.objects.CosemObject object at 0x10fc39f98>,
'\\d-\\d:96\\.3\\.10.+?\\r\\n': <dsmr_parser.objects.CosemObject object at 0x10fc39dd8>}
Example to get some of the values:
.. code-block:: python
from dsmr_parser import obis_references
# The telegram message timestamp.
message_datetime = telegram[obis_references.P1_MESSAGE_TIMESTAMP]
# Using the active tariff to determine the electricity being used and
# delivered for the right tariff.
active_tariff = telegram[obis_references.ELECTRICITY_ACTIVE_TARIFF]
active_tariff = int(tariff.value)
electricity_used_total = telegram[obis_references.ELECTRICITY_USED_TARIFF_ALL[active_tariff - 1]]
electricity_delivered_total = telegram[obis_references.ELECTRICITY_DELIVERED_TARIFF_ALL[active_tariff - 1]]
gas_reading = telegram[obis_references.HOURLY_GAS_METER_READING]
# See dsmr_reader.obis_references for all readable telegram values.
# Note that the available values differ per DSMR version.
Telegram as an Object
Telegram object
---------------------
An object version of the telegram is available as well.
A Telegram has attributes for all the parsed values according to the given telegram specification. Each value is a DsmrObject which have a 'value' and 'unit' property. MBusObject's, which are DsmrObject's as well additionally have a 'datetime' property. The 'value' can contain any python type (int, str, Decimal) depending on the field. The 'unit' contains 'kW', 'A', 'kWh' or 'm3'.
Note: Telegram extends dictionary, which done for backwards compatibility. The use of keys (e.g. `telegram[obis_references.CURRENT_ELECTRICITY_USAGE]`) is deprecated.
Below are some examples on how to get the meter data. Alternatively check out the following unit test for a complete example: TelegramParserV5Test.test_parse
.. code-block:: python
# DSMR v4.2 p1 using dsmr_parser and telegram objects
# Print contents of all available values
# See dsmr_parser.obis_name_mapping for all readable telegram values.
# The available values differ per DSMR version and meter.
print(telegram)
# 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]
# etc.
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
# Example to get current electricity usage
print(telegram.CURRENT_ELECTRICITY_USAGE) # <dsmr_parser.objects.CosemObject at 0x7f5e98ae5ac8>
print(telegram.CURRENT_ELECTRICITY_USAGE.value) # Decimal('2.027')
print(telegram.CURRENT_ELECTRICITY_USAGE.unit) # 'kW'
serial_reader = SerialReader(
device='/dev/ttyUSB0',
serial_settings=SERIAL_SETTINGS_V5,
telegram_specification=telegram_specifications.V4
)
# All Mbus device readings like gas meters and water meters can be retrieved as follows. This
# returns a list of MbusDevice objects:
mbus_devices = telegram.MBUS_DEVICES
# telegram = next(serial_reader.read_as_object())
# print(telegram)
# A specific MbusDevice based on the channel it's connected to, can be retrieved as follows:
mbus_device = telegram.get_mbus_device_by_channel(1)
print(mbus_device.DEVICE_TYPE.value) # 3
print(mbus_device.EQUIPMENT_IDENTIFIER_GAS.value) # '4730303339303031393336393930363139'
print(mbus_device.HOURLY_GAS_METER_READING.value) # Decimal('246.138')
for telegram in serial_reader.read_as_object():
os.system('clear')
print(telegram)
# DEPRECATED: the dictionary approach of getting the values by key or `.items()' or '.get() is deprecated
telegram[obis_references.CURRENT_ELECTRICITY_USAGE]
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]: <dsmr_parser.objects.Telegram at 0x7f5e995d9898>
telegram.CURRENT_ELECTRICITY_USAGE
Out[4]: <dsmr_parser.objects.CosemObject at 0x7f5e98ae5ac8>
telegram.CURRENT_ELECTRICITY_USAGE.value
Out[5]: Decimal('2.027')
telegram.CURRENT_ELECTRICITY_USAGE.unit
Out[6]: 'kW'
The telegram object has an iterator, can be used to find all the information elements in the current telegram:
@ -373,7 +284,6 @@ The telegram object has an iterator, can be used to find all the information ele
'EQUIPMENT_IDENTIFIER_GAS',
'HOURLY_GAS_METER_READING']
Installation
------------

View File

@ -4,7 +4,6 @@ import tailer
from dsmr_parser.clients.telegram_buffer import TelegramBuffer
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
from dsmr_parser.objects import Telegram
from dsmr_parser.parsers import TelegramParser
logger = logging.getLogger(__name__)
@ -72,7 +71,7 @@ class FileReader(object):
for telegram in self.telegram_buffer.get_all():
try:
yield Telegram(telegram, self.telegram_parser, self.telegram_specification)
yield self.telegram_parser.parse(telegram)
except InvalidChecksumError as e:
logger.warning(str(e))
except ParseError as e:
@ -121,7 +120,7 @@ class FileInputReader(object):
for telegram in self.telegram_buffer.get_all():
try:
yield Telegram(telegram, self.telegram_parser, self.telegram_specification)
yield self.telegram_parser.parse(telegram)
except InvalidChecksumError as e:
logger.warning(str(e))
except ParseError as e:
@ -167,7 +166,7 @@ class FileTailReader(object):
for telegram in self.telegram_buffer.get_all():
try:
yield Telegram(telegram, self.telegram_parser, self.telegram_specification)
yield self.telegram_parser.parse(telegram)
except InvalidChecksumError as e:
logger.warning(str(e))
except ParseError as e:

View File

@ -5,7 +5,6 @@ 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__)
@ -55,7 +54,7 @@ class SerialReader(object):
for telegram in self.telegram_buffer.get_all():
try:
yield Telegram(telegram, self.telegram_parser, self.telegram_specification)
yield self.telegram_parser.parse(telegram)
except InvalidChecksumError as e:
logger.warning(str(e))
except ParseError as e:
@ -121,7 +120,7 @@ class AsyncSerialReader(SerialReader):
for telegram in self.telegram_buffer.get_all():
try:
queue.put_nowait(
Telegram(telegram, self.telegram_parser, self.telegram_specification)
self.telegram_parser.parse(telegram)
)
except InvalidChecksumError as e:
logger.warning(str(e))

View File

@ -4,7 +4,6 @@ import socket
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__)
@ -81,7 +80,7 @@ class SocketReader(object):
for telegram in self.telegram_buffer.get_all():
try:
yield Telegram(telegram, self.telegram_parser, self.telegram_specification)
yield self.telegram_parser.parse(telegram)
except InvalidChecksumError as e:
logger.warning(str(e))
except ParseError as e:

View File

@ -19,6 +19,12 @@ EN = {
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.CURRENT_REACTIVE_EXPORTED: 'CURRENT_REACTIVE_EXPORTED',
obis.ELECTRICITY_REACTIVE_IMPORTED_TARIFF_1: 'ELECTRICITY_REACTIVE_IMPORTED_TARIFF_1',
obis.ELECTRICITY_REACTIVE_IMPORTED_TARIFF_2: 'ELECTRICITY_REACTIVE_IMPORTED_TARIFF_2',
obis.ELECTRICITY_REACTIVE_EXPORTED_TARIFF_1: 'ELECTRICITY_REACTIVE_EXPORTED_TARIFF_1',
obis.ELECTRICITY_REACTIVE_EXPORTED_TARIFF_2: 'ELECTRICITY_REACTIVE_EXPORTED_TARIFF_2',
obis.CURRENT_REACTIVE_IMPORTED: 'CURRENT_REACTIVE_IMPORTED',
obis.EQUIPMENT_IDENTIFIER: 'EQUIPMENT_IDENTIFIER',
obis.CURRENT_ELECTRICITY_USAGE: 'CURRENT_ELECTRICITY_USAGE',
obis.CURRENT_ELECTRICITY_DELIVERY: 'CURRENT_ELECTRICITY_DELIVERY',
@ -86,6 +92,7 @@ EN = {
obis.Q3D_EQUIPMENT_IDENTIFIER: 'Q3D_EQUIPMENT_IDENTIFIER',
obis.Q3D_EQUIPMENT_STATE: 'Q3D_EQUIPMENT_STATE',
obis.Q3D_EQUIPMENT_SERIALNUMBER: 'Q3D_EQUIPMENT_SERIALNUMBER',
obis.BELGIUM_MBUS2_DEVICE_TYPE: 'BELGIUM_MBUS2_DEVICE_TYPE'
}
REVERSE_EN = dict([(v, k) for k, v in EN.items()])

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'
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'

View File

@ -1,20 +1,16 @@
import dsmr_parser.obis_name_mapping
import datetime
import json
from decimal import Decimal
import datetime
import json
class Telegram(object):
import pytz
from dsmr_parser import obis_name_mapping
class Telegram(dict):
"""
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)
Container for parsed telegram data.
Attributes can be accessed on a telegram object by addressing by their english name, for example:
telegram.ELECTRICITY_USED_TARIFF_1
@ -23,25 +19,55 @@ class Telegram(object):
[k for k,v in telegram]
yields:
['P1_MESSAGE_HEADER', 'P1_MESSAGE_TIMESTAMP', 'EQUIPMENT_IDENTIFIER', ...]
Note: Dict like usage is deprecated. The inheritance from dict is because of backwards compatibility.
"""
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 __init__(self, *args, **kwargs):
self._item_names = []
self._mbus_devices = []
super().__init__(*args, **kwargs)
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 add(self, obis_reference, dsmr_object):
# Update name mapping used to get value by attribute. Example: telegram.P1_MESSAGE_HEADER
obis_name = obis_name_mapping.EN[obis_reference]
setattr(self, obis_name, dsmr_object)
if obis_name not in self._item_names: # TODO repeating obis references
self._item_names.append(obis_name)
def _get_item_names(self):
return [self._obis_name_mapping[k] for k, v in self._dictionary.items()]
# TODO isinstance check: MaxDemandParser (BELGIUM_MAXIMUM_DEMAND_13_MONTHS) returns a list
if isinstance(dsmr_object, DSMRObject) and dsmr_object.is_mbus_reading:
self._add_mbus(obis_reference, dsmr_object)
# Fill dict which is only used for backwards compatibility
if obis_reference not in self:
self[obis_reference] = dsmr_object
def _add_mbus(self, obis_reference, dsmr_object):
"""
The given DsmrObject is assumed to be Mbus related and will be grouped into a MbusDevice.
Grouping is done by the DsmrObject channel ID.
"""
channel_id = dsmr_object.obis_id_code[1]
# Create new MbusDevice or update existing one as it's records are being added one by one.
mbus_device = self.get_mbus_device_by_channel(channel_id)
if not mbus_device:
mbus_device = MbusDevice(channel_id=channel_id)
self._mbus_devices.append(mbus_device)
mbus_device.add(obis_reference, dsmr_object)
if not hasattr(self, 'MBUS_DEVICES'):
setattr(self, 'MBUS_DEVICES', self._mbus_devices)
self._item_names.append('MBUS_DEVICES')
def get_mbus_device_by_channel(self, channel_id):
"""
:rtype: MbusDevice|None
"""
for mbus_device in self._mbus_devices:
if mbus_device.channel_id == channel_id:
return mbus_device
def __iter__(self):
for attr in self._item_names:
@ -51,21 +77,44 @@ class Telegram(object):
def __str__(self):
output = ""
for attr, value in self:
output += "{}: \t {}\n".format(attr, str(value))
if isinstance(value, list):
output += ''.join(map(str, value))
else:
output += "{}: \t {}\n".format(attr, str(value))
return output
def to_json(self):
return json.dumps(dict([[attr, json.loads(value.to_json())] for attr, value in self]))
json_data = {}
for attr, value in self:
if isinstance(value, list):
json_data[attr] = [json.loads(item.to_json() if hasattr(item, 'to_json') else item)
for item in value]
elif hasattr(value, 'to_json'):
json_data[attr] = json.loads(value.to_json())
return json.dumps(json_data)
class DSMRObject(object):
"""
Represents all data from a single telegram line.
"""
def __init__(self, values):
def __init__(self, obis_id_code, values):
self.obis_id_code = obis_id_code
self.values = values
@property
def is_mbus_reading(self):
""" Detect Mbus related readings using obis id + channel. """
obis_id, channel_id = self.obis_id_code
return obis_id == 0 and channel_id != 0
def to_json(self):
raise NotImplementedError
class MBusObject(DSMRObject):
@ -94,16 +143,20 @@ class MBusObject(DSMRObject):
return self.values[1]['unit']
def __str__(self):
output = "{}\t[{}] at {}".format(str(self.value), str(self.unit), str(self.datetime.astimezone().isoformat()))
output = "{}\t[{}] at {}".format(
str(self.value),
str(self.unit),
str(self.datetime.astimezone().astimezone(pytz.utc).isoformat())
)
return output
def to_json(self):
timestamp = self.datetime
if isinstance(self.datetime, datetime.datetime):
timestamp = self.datetime.astimezone().isoformat()
timestamp = self.datetime.astimezone().astimezone(pytz.utc).isoformat()
value = self.value
if isinstance(self.value, datetime.datetime):
value = self.value.astimezone().isoformat()
value = self.value.astimezone().astimezone(pytz.utc).isoformat()
if isinstance(self.value, Decimal):
value = float(self.value)
output = {
@ -134,20 +187,20 @@ class MBusObjectPeak(DSMRObject):
def __str__(self):
output = "{}\t[{}] at {} occurred {}"\
.format(str(self.value), str(self.unit), str(self.datetime.astimezone().isoformat()),
str(self.occurred.astimezone().isoformat()))
.format(str(self.value), str(self.unit), str(self.datetime.astimezone().astimezone(pytz.utc).isoformat()),
str(self.occurred.astimezone().astimezone(pytz.utc).isoformat()))
return output
def to_json(self):
timestamp = self.datetime
if isinstance(self.datetime, datetime.datetime):
timestamp = self.datetime.astimezone().isoformat()
timestamp = self.datetime.astimezone().astimezone(pytz.utc).isoformat()
timestamp_occurred = self.occurred
if isinstance(self.occurred, datetime.datetime):
timestamp_occurred = self.occurred.astimezone().isoformat()
timestamp_occurred = self.occurred.astimezone().astimezone(pytz.utc).isoformat()
value = self.value
if isinstance(self.value, datetime.datetime):
value = self.value.astimezone().isoformat()
value = self.value.astimezone().astimezone(pytz.utc).isoformat()
if isinstance(self.value, Decimal):
value = float(self.value)
output = {
@ -172,14 +225,14 @@ class CosemObject(DSMRObject):
def __str__(self):
print_value = self.value
if isinstance(self.value, datetime.datetime):
print_value = self.value.astimezone().isoformat()
print_value = self.value.astimezone().astimezone(pytz.utc).isoformat()
output = "{}\t[{}]".format(str(print_value), str(self.unit))
return output
def to_json(self):
json_value = self.value
if isinstance(self.value, datetime.datetime):
json_value = self.value.astimezone().isoformat()
json_value = self.value.astimezone().astimezone(pytz.utc).isoformat()
if isinstance(self.value, Decimal):
json_value = float(self.value)
output = {
@ -196,8 +249,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
@ -223,9 +276,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(
obis_id_code=self.obis_id_code,
values=[self.values[offset], self.values[offset + 1]]
)
)
return self._buffer_list
def __str__(self):
@ -234,7 +294,7 @@ class ProfileGenericObject(DSMRObject):
for buffer_value in self.buffer:
timestamp = buffer_value.datetime
if isinstance(timestamp, datetime.datetime):
timestamp = str(timestamp.astimezone().isoformat())
timestamp = str(timestamp.astimezone().astimezone(pytz.utc).isoformat())
output += "\n\t event occured at: {}".format(timestamp)
output += "\t for: {} [{}]".format(buffer_value.value, buffer_value.unit)
return output
@ -260,3 +320,40 @@ class ProfileGenericObject(DSMRObject):
list.append(['buffer', buffer_repr])
output = dict(list)
return json.dumps(output)
class MbusDevice:
"""
This object is similar to the Telegram except that it only contains readings related to the same mbus device.
"""
def __init__(self, channel_id):
self.channel_id = channel_id
self._item_names = []
def add(self, 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)
def __len__(self):
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 = "MBUS DEVICE (channel {})\n".format(self.channel_id)
for attr, value in self:
output += "\t{}: \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)

View File

@ -8,7 +8,7 @@ from decimal import Decimal
from dlms_cosem.connection import XDlmsApduFactory
from dlms_cosem.protocol.xdlms import GeneralGlobalCipher
from dsmr_parser.objects import MBusObject, MBusObjectPeak, CosemObject, ProfileGenericObject
from dsmr_parser.objects import MBusObject, MBusObjectPeak, CosemObject, ProfileGenericObject, Telegram
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
from dsmr_parser.value_types import timestamp
@ -37,15 +37,7 @@ class TelegramParser(object):
('!ABCD') including line endings in between the telegram's lines
:param str encryption_key: encryption key
:param str authentication_key: authentication key
:rtype: dict
:returns: Shortened example:
{
..
r'\d-\d:96\.1\.1.+?\r\n': <CosemObject>, # EQUIPMENT_IDENTIFIER
r'\d-\d:1\.8\.1.+?\r\n': <CosemObject>, # ELECTRICITY_USED_TARIFF_1
r'\d-\d:24\.3\.0.+?\r\n.+?\r\n': <MBusObject>, # GAS_METER_READING
..
}
:rtype: Telegram
:raises ParseError:
:raises InvalidChecksumError:
"""
@ -82,23 +74,25 @@ class TelegramParser(object):
except Exception:
pass
if self.apply_checksum_validation \
and self.telegram_specification['checksum_support']:
if self.apply_checksum_validation and self.telegram_specification['checksum_support']:
self.validate_checksum(telegram_data)
telegram = {}
telegram = Telegram()
for signature, parser in self.telegram_specification['objects'].items():
match = re.search(signature, telegram_data, re.DOTALL)
pattern = re.compile(signature, re.DOTALL)
matches = pattern.findall(telegram_data)
# Some signatures are optional and may not be present,
# so only parse lines that match
if match:
for match in matches:
try:
telegram[signature] = parser.parse(match.group(0))
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, dsmr_object=dsmr_object)
return telegram
@ -180,6 +174,20 @@ class DSMRObjectParser(object):
return [self.value_formats[i].parse(value)
for i, value in enumerate(values)]
def _parse_obis_id_code(self, line):
"""
Get the OBIS ID code
Example line:
'0-2:24.2.1(200426223001S)(00246.138*m3)'
OBIS ID code = 0-2 returned as tuple
"""
try:
return int(line[0]), int(line[2])
except ValueError:
raise ParseError("Invalid OBIS ID code 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,}(?=\)))')
@ -213,7 +221,10 @@ class MBusParser(DSMRObjectParser):
"""
def parse(self, line):
return MBusObject(self._parse(line))
return MBusObject(
obis_id_code=self._parse_obis_id_code(line),
values=self._parse(line)
)
class MaxDemandParser(DSMRObjectParser):
@ -241,6 +252,8 @@ class MaxDemandParser(DSMRObjectParser):
pattern = re.compile(r'((?<=\()[0-9a-zA-Z\.\*\-\:]{0,}(?=\)))')
values = re.findall(pattern, line)
obis_id_code = self._parse_obis_id_code(line)
objects = []
count = int(values[0])
@ -248,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(
obis_id_code=obis_id_code,
values=[timestamp_month, timestamp_occurred, value]
))
return objects
@ -274,7 +290,10 @@ class CosemParser(DSMRObjectParser):
"""
def parse(self, line):
return CosemObject(self._parse(line))
return CosemObject(
obis_id_code=self._parse_obis_id_code(line),
values=self._parse(line)
)
class ProfileGenericParser(DSMRObjectParser):
@ -333,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(
obis_id_code=self._parse_obis_id_code(line),
values=self._parse(line)
)
class ValueParser(object):
@ -341,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}

View File

@ -7,7 +7,7 @@ setup(
author_email='mail@nldr.net',
license='MIT',
url='https://github.com/ndokter/dsmr_parser',
version='1.1.0',
version='1.2.0',
packages=find_packages(exclude=('test', 'test.*')),
install_requires=[
'pyserial>=3,<4',

View File

@ -129,6 +129,51 @@ TELEGRAM_V5 = (
'!6EEE\r\n'
)
# V5 telegram with 2 MBUS devices
TELEGRAM_V5_TWO_MBUS = (
'/ISK5\\2M550T-1012\r\n'
'\r\n'
'1-3:0.2.8(50)\r\n'
'0-0:1.0.0(200426223325S)\r\n'
'0-0:96.1.1(4530303434303037333832323436303139)\r\n'
'1-0:1.8.1(002130.115*kWh)\r\n'
'1-0:1.8.2(000245.467*kWh)\r\n'
'1-0:2.8.1(000000.000*kWh)\r\n'
'1-0:2.8.2(000000.000*kWh)\r\n'
'0-0:96.14.0(0001)\r\n'
'1-0:1.7.0(00.111*kW)\r\n'
'1-0:2.7.0(00.000*kW)\r\n'
'0-0:96.7.21(00005)\r\n'
'0-0:96.7.9(00003)\r\n'
'1-0:99.97.0(1)(0-0:96.7.19)(190326095015W)(0000002014*s)\r\n'
'1-0:32.32.0(00001)\r\n'
'1-0:52.32.0(00001)\r\n'
'1-0:72.32.0(00192)\r\n'
'1-0:32.36.0(00001)\r\n'
'1-0:52.36.0(00001)\r\n'
'1-0:72.36.0(00001)\r\n'
'0-0:96.13.0()\r\n'
'1-0:32.7.0(229.9*V)\r\n'
'1-0:52.7.0(229.2*V)\r\n'
'1-0:72.7.0(222.9*V)\r\n'
'1-0:31.7.0(000*A)\r\n'
'1-0:51.7.0(000*A)\r\n'
'1-0:71.7.0(001*A)\r\n'
'1-0:21.7.0(00.056*kW)\r\n'
'1-0:41.7.0(00.000*kW)\r\n'
'1-0:61.7.0(00.055*kW)\r\n'
'1-0:22.7.0(00.000*kW)\r\n'
'1-0:42.7.0(00.000*kW)\r\n'
'1-0:62.7.0(00.000*kW)\r\n'
'0-1:24.1.0(003)\r\n'
'0-1:96.1.0()\r\n'
'0-1:24.2.1(700101010000W)(00000000)\r\n'
'0-2:24.1.0(003)\r\n'
'0-2:96.1.0(4730303339303031393336393930363139)\r\n'
'0-2:24.2.1(200426223001S)(00246.138*m3)\r\n'
'!56DD\r\n'
)
TELEGRAM_FLUVIUS_V171 = (
'/FLU5\253769484_A\r\n'
'\r\n'

View File

@ -1,8 +1,7 @@
from dsmr_parser import telegram_specifications
from dsmr_parser.objects import 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)
telegram = parser.parse(TELEGRAM_V4_2)
print(telegram)

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

View File

@ -0,0 +1,61 @@
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-26T20:30:01+00:00', 'value': 246.138, 'unit': 'm3'}}
)
def test_str(self):
self.assertEqual(
str(self.mbus_device),
(
'MBUS DEVICE (channel 1)\n'
'\tDEVICE_TYPE: 3 [None]\n'
'\tEQUIPMENT_IDENTIFIER_GAS: 4730303339303031393336393930363139 [None]\n'
'\tHOURLY_GAS_METER_READING: 246.138 [m3] at 2020-04-26T20:30:01+00:00\n'
)
)

View File

@ -2,7 +2,6 @@ import unittest
from dsmr_parser import telegram_specifications
from dsmr_parser.objects import Telegram
from dsmr_parser.objects import ProfileGenericObject
from dsmr_parser.parsers import TelegramParser
from dsmr_parser.parsers import ProfileGenericParser
@ -18,7 +17,7 @@ class TestParserCornerCases(unittest.TestCase):
def test_power_event_log_empty_1(self):
# POWER_EVENT_FAILURE_LOG (1-0:99.97.0)
parser = TelegramParser(telegram_specifications.V5)
telegram = Telegram(TELEGRAM_V5, parser, telegram_specifications.V5)
telegram = parser.parse(TELEGRAM_V5)
object_type = ProfileGenericObject
testitem = telegram.POWER_EVENT_FAILURE_LOG

View File

@ -1,15 +1,15 @@
import json
import unittest
import datetime
import pytz
from dsmr_parser import telegram_specifications
from dsmr_parser import telegram_specifications, obis_references
from dsmr_parser import obis_name_mapping
from dsmr_parser.objects import CosemObject
from dsmr_parser.objects import MBusObject
from dsmr_parser.objects import Telegram
from dsmr_parser.objects import ProfileGenericObject
from dsmr_parser.parsers import TelegramParser
from test.example_telegrams import TELEGRAM_V4_2
from test.example_telegrams import TELEGRAM_V4_2, TELEGRAM_V5_TWO_MBUS, TELEGRAM_V5
from decimal import Decimal
@ -30,7 +30,7 @@ class TelegramTest(unittest.TestCase):
def test_instantiate(self):
parser = TelegramParser(telegram_specifications.V4)
telegram = Telegram(TELEGRAM_V4_2, parser, telegram_specifications.V4)
telegram = parser.parse(TELEGRAM_V4_2)
# P1_MESSAGE_HEADER (1-3:0.2.8)
self.verify_telegram_item(telegram,
@ -320,3 +320,170 @@ class TelegramTest(unittest.TestCase):
item_names_tested_set = set(self.item_names_tested)
assert item_names_tested_set == V4_name_set
def test_iter(self):
parser = TelegramParser(telegram_specifications.V5)
telegram = parser.parse(TELEGRAM_V5)
for obis_name, dsmr_object in telegram:
break
# Verify that the iterator works for at least one value
self.assertEqual(obis_name, obis_name_mapping.EN[obis_references.P1_MESSAGE_HEADER])
self.assertEqual(dsmr_object.value, '50')
def test_mbus_devices(self):
parser = TelegramParser(telegram_specifications.V5)
telegram = parser.parse(TELEGRAM_V5_TWO_MBUS)
mbus_devices = telegram.MBUS_DEVICES
self.assertEqual(len(mbus_devices), 2)
mbus_device_1 = mbus_devices[0]
self.assertEqual(mbus_device_1.DEVICE_TYPE.value, 3)
self.assertEqual(mbus_device_1.EQUIPMENT_IDENTIFIER_GAS.value, None)
self.assertEqual(mbus_device_1.HOURLY_GAS_METER_READING.value, Decimal('0'))
mbus_device_2 = mbus_devices[1]
self.assertEqual(mbus_device_2.DEVICE_TYPE.value, 3)
self.assertEqual(mbus_device_2.EQUIPMENT_IDENTIFIER_GAS.value, '4730303339303031393336393930363139')
self.assertEqual(mbus_device_2.HOURLY_GAS_METER_READING.value, Decimal('246.138'))
def test_get_mbus_device_by_channel(self):
parser = TelegramParser(telegram_specifications.V5)
telegram = parser.parse(TELEGRAM_V5_TWO_MBUS)
mbus_device_1 = telegram.get_mbus_device_by_channel(1)
self.assertEqual(mbus_device_1.DEVICE_TYPE.value, 3)
self.assertEqual(mbus_device_1.EQUIPMENT_IDENTIFIER_GAS.value, None)
self.assertEqual(mbus_device_1.HOURLY_GAS_METER_READING.value, Decimal('0'))
mbus_device_2 = telegram.get_mbus_device_by_channel(2)
self.assertEqual(mbus_device_2.DEVICE_TYPE.value, 3)
self.assertEqual(mbus_device_2.EQUIPMENT_IDENTIFIER_GAS.value, '4730303339303031393336393930363139')
self.assertEqual(mbus_device_2.HOURLY_GAS_METER_READING.value, Decimal('246.138'))
def test_without_mbus_devices(self):
parser = TelegramParser(telegram_specifications.V5, apply_checksum_validation=False)
telegram = parser.parse('')
self.assertFalse(hasattr(telegram, 'MBUS_DEVICES'))
self.assertIsNone(telegram.get_mbus_device_by_channel(1))
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-02T15:10:05+00: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-02T15:10:05+00: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-02T18:20:02+00: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_to_str(self):
parser = TelegramParser(telegram_specifications.V5)
telegram = parser.parse(TELEGRAM_V5)
self.assertEqual(
str(telegram),
(
'P1_MESSAGE_HEADER: 50 [None]\n'
'P1_MESSAGE_TIMESTAMP: 2017-01-02T18:20:02+00:00 [None]\n'
'EQUIPMENT_IDENTIFIER: 4B384547303034303436333935353037 [None]\n'
'ELECTRICITY_USED_TARIFF_1: 4.426 [kWh]\n'
'ELECTRICITY_USED_TARIFF_2: 2.399 [kWh]\n'
'ELECTRICITY_DELIVERED_TARIFF_1: 2.444 [kWh]\n'
'ELECTRICITY_DELIVERED_TARIFF_2: 0.000 [kWh]\n'
'ELECTRICITY_ACTIVE_TARIFF: 0002 [None]\n'
'CURRENT_ELECTRICITY_USAGE: 0.244 [kW]\n'
'CURRENT_ELECTRICITY_DELIVERY: 0.000 [kW]\n'
'LONG_POWER_FAILURE_COUNT: 0 [None]\n'
'SHORT_POWER_FAILURE_COUNT: 13 [None]\n'
'POWER_EVENT_FAILURE_LOG: buffer length: 0\n'
' buffer type: 0-0:96.7.19\n'
'VOLTAGE_SAG_L1_COUNT: 0 [None]\n'
'VOLTAGE_SAG_L2_COUNT: 0 [None]\n'
'VOLTAGE_SAG_L3_COUNT: 0 [None]\n'
'VOLTAGE_SWELL_L1_COUNT: 0 [None]\n'
'VOLTAGE_SWELL_L2_COUNT: 0 [None]\n'
'VOLTAGE_SWELL_L3_COUNT: 0 [None]\n'
'INSTANTANEOUS_VOLTAGE_L1: 230.0 [V]\n'
'INSTANTANEOUS_VOLTAGE_L2: 230.0 [V]\n'
'INSTANTANEOUS_VOLTAGE_L3: 229.0 [V]\n'
'INSTANTANEOUS_CURRENT_L1: 0.48 [A]\n'
'INSTANTANEOUS_CURRENT_L2: 0.44 [A]\n'
'INSTANTANEOUS_CURRENT_L3: 0.86 [A]\n'
'TEXT_MESSAGE: None [None]\n'
'DEVICE_TYPE: 3 [None]\n'
'MBUS DEVICE (channel 1)\n'
' DEVICE_TYPE: 3 [None]\n'
' EQUIPMENT_IDENTIFIER_GAS: 3232323241424344313233343536373839 [None]\n'
' HOURLY_GAS_METER_READING: 0.107 [m3] at 2017-01-02T15:10:05+00:00\n'
'MBUS DEVICE (channel 2)\n'
' DEVICE_TYPE: 3 [None]\n'
' EQUIPMENT_IDENTIFIER_GAS: None [None]\n'
'INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: 0.070 [kW]\n'
'INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE: 0.032 [kW]\n'
'INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE: 0.142 [kW]\n'
'INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE: 0.000 [kW]\n'
'INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE: 0.000 [kW]\n'
'INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: 0.000 [kW]\n'
'EQUIPMENT_IDENTIFIER_GAS: None [None]\n'
'HOURLY_GAS_METER_READING: 0.107 [m3] at 2017-01-02T15:10:05+00:00\n'
)
)
def test_getitem(self):
parser = TelegramParser(telegram_specifications.V5)
telegram = parser.parse(TELEGRAM_V5)
self.assertEqual(telegram[obis_references.P1_MESSAGE_HEADER].value, '50')

View File

@ -5,7 +5,6 @@ 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
@ -18,211 +17,222 @@ class TelegramParserV5Test(unittest.TestCase):
def test_parse(self):
parser = TelegramParser(telegram_specifications.V5)
result = parser.parse(TELEGRAM_V5)
telegram = parser.parse(TELEGRAM_V5)
# 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'
assert isinstance(telegram.P1_MESSAGE_HEADER, CosemObject)
assert telegram.P1_MESSAGE_HEADER.unit is None
assert isinstance(telegram.P1_MESSAGE_HEADER.value, str)
assert telegram.P1_MESSAGE_HEADER.value == '50'
# P1_MESSAGE_TIMESTAMP (0-0:1.0.0)
assert isinstance(result[obis.P1_MESSAGE_TIMESTAMP], CosemObject)
assert result[obis.P1_MESSAGE_TIMESTAMP].unit is None
assert isinstance(result[obis.P1_MESSAGE_TIMESTAMP].value, datetime.datetime)
assert result[obis.P1_MESSAGE_TIMESTAMP].value == \
assert isinstance(telegram.P1_MESSAGE_TIMESTAMP, CosemObject)
assert telegram.P1_MESSAGE_TIMESTAMP.unit is None
assert isinstance(telegram.P1_MESSAGE_TIMESTAMP.value, datetime.datetime)
assert telegram.P1_MESSAGE_TIMESTAMP.value == \
datetime.datetime(2017, 1, 2, 18, 20, 2, tzinfo=pytz.UTC)
# ELECTRICITY_USED_TARIFF_1 (1-0:1.8.1)
assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_1], CosemObject)
assert result[obis.ELECTRICITY_USED_TARIFF_1].unit == 'kWh'
assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_1].value, Decimal)
assert result[obis.ELECTRICITY_USED_TARIFF_1].value == Decimal('4.426')
assert isinstance(telegram.ELECTRICITY_USED_TARIFF_1, CosemObject)
assert telegram.ELECTRICITY_USED_TARIFF_1.unit == 'kWh'
assert isinstance(telegram.ELECTRICITY_USED_TARIFF_1.value, Decimal)
assert telegram.ELECTRICITY_USED_TARIFF_1.value == Decimal('4.426')
# ELECTRICITY_USED_TARIFF_2 (1-0:1.8.2)
assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_2], CosemObject)
assert result[obis.ELECTRICITY_USED_TARIFF_2].unit == 'kWh'
assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_2].value, Decimal)
assert result[obis.ELECTRICITY_USED_TARIFF_2].value == Decimal('2.399')
assert isinstance(telegram.ELECTRICITY_USED_TARIFF_2, CosemObject)
assert telegram.ELECTRICITY_USED_TARIFF_2.unit == 'kWh'
assert isinstance(telegram.ELECTRICITY_USED_TARIFF_2.value, Decimal)
assert telegram.ELECTRICITY_USED_TARIFF_2.value == Decimal('2.399')
# ELECTRICITY_DELIVERED_TARIFF_1 (1-0:2.8.1)
assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_1], CosemObject)
assert result[obis.ELECTRICITY_DELIVERED_TARIFF_1].unit == 'kWh'
assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_1].value, Decimal)
assert result[obis.ELECTRICITY_DELIVERED_TARIFF_1].value == Decimal('2.444')
assert isinstance(telegram.ELECTRICITY_DELIVERED_TARIFF_1, CosemObject)
assert telegram.ELECTRICITY_DELIVERED_TARIFF_1.unit == 'kWh'
assert isinstance(telegram.ELECTRICITY_DELIVERED_TARIFF_1.value, Decimal)
assert telegram.ELECTRICITY_DELIVERED_TARIFF_1.value == Decimal('2.444')
# ELECTRICITY_DELIVERED_TARIFF_2 (1-0:2.8.2)
assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_2], CosemObject)
assert result[obis.ELECTRICITY_DELIVERED_TARIFF_2].unit == 'kWh'
assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_2].value, Decimal)
assert result[obis.ELECTRICITY_DELIVERED_TARIFF_2].value == Decimal('0')
assert isinstance(telegram.ELECTRICITY_DELIVERED_TARIFF_2, CosemObject)
assert telegram.ELECTRICITY_DELIVERED_TARIFF_2.unit == 'kWh'
assert isinstance(telegram.ELECTRICITY_DELIVERED_TARIFF_2.value, Decimal)
assert telegram.ELECTRICITY_DELIVERED_TARIFF_2.value == Decimal('0')
# ELECTRICITY_ACTIVE_TARIFF (0-0:96.14.0)
assert isinstance(result[obis.ELECTRICITY_ACTIVE_TARIFF], CosemObject)
assert result[obis.ELECTRICITY_ACTIVE_TARIFF].unit is None
assert isinstance(result[obis.ELECTRICITY_ACTIVE_TARIFF].value, str)
assert result[obis.ELECTRICITY_ACTIVE_TARIFF].value == '0002'
assert isinstance(telegram.ELECTRICITY_ACTIVE_TARIFF, CosemObject)
assert telegram.ELECTRICITY_ACTIVE_TARIFF.unit is None
assert isinstance(telegram.ELECTRICITY_ACTIVE_TARIFF.value, str)
assert telegram.ELECTRICITY_ACTIVE_TARIFF.value == '0002'
# EQUIPMENT_IDENTIFIER (0-0:96.1.1)
assert isinstance(result[obis.EQUIPMENT_IDENTIFIER], CosemObject)
assert result[obis.EQUIPMENT_IDENTIFIER].unit is None
assert isinstance(result[obis.EQUIPMENT_IDENTIFIER].value, str)
assert result[obis.EQUIPMENT_IDENTIFIER].value == '4B384547303034303436333935353037'
assert isinstance(telegram.EQUIPMENT_IDENTIFIER, CosemObject)
assert telegram.EQUIPMENT_IDENTIFIER.unit is None
assert isinstance(telegram.EQUIPMENT_IDENTIFIER.value, str)
assert telegram.EQUIPMENT_IDENTIFIER.value == '4B384547303034303436333935353037'
# CURRENT_ELECTRICITY_USAGE (1-0:1.7.0)
assert isinstance(result[obis.CURRENT_ELECTRICITY_USAGE], CosemObject)
assert result[obis.CURRENT_ELECTRICITY_USAGE].unit == 'kW'
assert isinstance(result[obis.CURRENT_ELECTRICITY_USAGE].value, Decimal)
assert result[obis.CURRENT_ELECTRICITY_USAGE].value == Decimal('0.244')
assert isinstance(telegram.CURRENT_ELECTRICITY_USAGE, CosemObject)
assert telegram.CURRENT_ELECTRICITY_USAGE.unit == 'kW'
assert isinstance(telegram.CURRENT_ELECTRICITY_USAGE.value, Decimal)
assert telegram.CURRENT_ELECTRICITY_USAGE.value == Decimal('0.244')
# CURRENT_ELECTRICITY_DELIVERY (1-0:2.7.0)
assert isinstance(result[obis.CURRENT_ELECTRICITY_DELIVERY], CosemObject)
assert result[obis.CURRENT_ELECTRICITY_DELIVERY].unit == 'kW'
assert isinstance(result[obis.CURRENT_ELECTRICITY_DELIVERY].value, Decimal)
assert result[obis.CURRENT_ELECTRICITY_DELIVERY].value == Decimal('0')
assert isinstance(telegram.CURRENT_ELECTRICITY_DELIVERY, CosemObject)
assert telegram.CURRENT_ELECTRICITY_DELIVERY.unit == 'kW'
assert isinstance(telegram.CURRENT_ELECTRICITY_DELIVERY.value, Decimal)
assert telegram.CURRENT_ELECTRICITY_DELIVERY.value == Decimal('0')
# LONG_POWER_FAILURE_COUNT (96.7.9)
assert isinstance(result[obis.LONG_POWER_FAILURE_COUNT], CosemObject)
assert result[obis.LONG_POWER_FAILURE_COUNT].unit is None
assert isinstance(result[obis.LONG_POWER_FAILURE_COUNT].value, int)
assert result[obis.LONG_POWER_FAILURE_COUNT].value == 0
assert isinstance(telegram.LONG_POWER_FAILURE_COUNT, CosemObject)
assert telegram.LONG_POWER_FAILURE_COUNT.unit is None
assert isinstance(telegram.LONG_POWER_FAILURE_COUNT.value, int)
assert telegram.LONG_POWER_FAILURE_COUNT.value == 0
# SHORT_POWER_FAILURE_COUNT (1-0:96.7.21)
assert isinstance(result[obis.SHORT_POWER_FAILURE_COUNT], CosemObject)
assert result[obis.SHORT_POWER_FAILURE_COUNT].unit is None
assert isinstance(result[obis.SHORT_POWER_FAILURE_COUNT].value, int)
assert result[obis.SHORT_POWER_FAILURE_COUNT].value == 13
assert isinstance(telegram.SHORT_POWER_FAILURE_COUNT, CosemObject)
assert telegram.SHORT_POWER_FAILURE_COUNT.unit is None
assert isinstance(telegram.SHORT_POWER_FAILURE_COUNT.value, int)
assert telegram.SHORT_POWER_FAILURE_COUNT.value == 13
# VOLTAGE_SAG_L1_COUNT (1-0:32.32.0)
assert isinstance(result[obis.VOLTAGE_SAG_L1_COUNT], CosemObject)
assert result[obis.VOLTAGE_SAG_L1_COUNT].unit is None
assert isinstance(result[obis.VOLTAGE_SAG_L1_COUNT].value, int)
assert result[obis.VOLTAGE_SAG_L1_COUNT].value == 0
assert isinstance(telegram.VOLTAGE_SAG_L1_COUNT, CosemObject)
assert telegram.VOLTAGE_SAG_L1_COUNT.unit is None
assert isinstance(telegram.VOLTAGE_SAG_L1_COUNT.value, int)
assert telegram.VOLTAGE_SAG_L1_COUNT.value == 0
# VOLTAGE_SAG_L2_COUNT (1-0:52.32.0)
assert isinstance(result[obis.VOLTAGE_SAG_L2_COUNT], CosemObject)
assert result[obis.VOLTAGE_SAG_L2_COUNT].unit is None
assert isinstance(result[obis.VOLTAGE_SAG_L2_COUNT].value, int)
assert result[obis.VOLTAGE_SAG_L2_COUNT].value == 0
assert isinstance(telegram.VOLTAGE_SAG_L2_COUNT, CosemObject)
assert telegram.VOLTAGE_SAG_L2_COUNT.unit is None
assert isinstance(telegram.VOLTAGE_SAG_L2_COUNT.value, int)
assert telegram.VOLTAGE_SAG_L2_COUNT.value == 0
# VOLTAGE_SAG_L3_COUNT (1-0:72.32.0)
assert isinstance(result[obis.VOLTAGE_SAG_L3_COUNT], CosemObject)
assert result[obis.VOLTAGE_SAG_L3_COUNT].unit is None
assert isinstance(result[obis.VOLTAGE_SAG_L3_COUNT].value, int)
assert result[obis.VOLTAGE_SAG_L3_COUNT].value == 0
assert isinstance(telegram.VOLTAGE_SAG_L3_COUNT, CosemObject)
assert telegram.VOLTAGE_SAG_L3_COUNT.unit is None
assert isinstance(telegram.VOLTAGE_SAG_L3_COUNT.value, int)
assert telegram.VOLTAGE_SAG_L3_COUNT.value == 0
# VOLTAGE_SWELL_L1_COUNT (1-0:32.36.0)
assert isinstance(result[obis.VOLTAGE_SWELL_L1_COUNT], CosemObject)
assert result[obis.VOLTAGE_SWELL_L1_COUNT].unit is None
assert isinstance(result[obis.VOLTAGE_SWELL_L1_COUNT].value, int)
assert result[obis.VOLTAGE_SWELL_L1_COUNT].value == 0
assert isinstance(telegram.VOLTAGE_SWELL_L1_COUNT, CosemObject)
assert telegram.VOLTAGE_SWELL_L1_COUNT.unit is None
assert isinstance(telegram.VOLTAGE_SWELL_L1_COUNT.value, int)
assert telegram.VOLTAGE_SWELL_L1_COUNT.value == 0
# VOLTAGE_SWELL_L2_COUNT (1-0:52.36.0)
assert isinstance(result[obis.VOLTAGE_SWELL_L2_COUNT], CosemObject)
assert result[obis.VOLTAGE_SWELL_L2_COUNT].unit is None
assert isinstance(result[obis.VOLTAGE_SWELL_L2_COUNT].value, int)
assert result[obis.VOLTAGE_SWELL_L2_COUNT].value == 0
assert isinstance(telegram.VOLTAGE_SWELL_L2_COUNT, CosemObject)
assert telegram.VOLTAGE_SWELL_L2_COUNT.unit is None
assert isinstance(telegram.VOLTAGE_SWELL_L2_COUNT.value, int)
assert telegram.VOLTAGE_SWELL_L2_COUNT.value == 0
# VOLTAGE_SWELL_L3_COUNT (1-0:72.36.0)
assert isinstance(result[obis.VOLTAGE_SWELL_L3_COUNT], CosemObject)
assert result[obis.VOLTAGE_SWELL_L3_COUNT].unit is None
assert isinstance(result[obis.VOLTAGE_SWELL_L3_COUNT].value, int)
assert result[obis.VOLTAGE_SWELL_L3_COUNT].value == 0
assert isinstance(telegram.VOLTAGE_SWELL_L3_COUNT, CosemObject)
assert telegram.VOLTAGE_SWELL_L3_COUNT.unit is None
assert isinstance(telegram.VOLTAGE_SWELL_L3_COUNT.value, int)
assert telegram.VOLTAGE_SWELL_L3_COUNT.value == 0
# INSTANTANEOUS_VOLTAGE_L1 (1-0:32.7.0)
assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L1], CosemObject)
assert result[obis.INSTANTANEOUS_VOLTAGE_L1].unit == 'V'
assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L1].value, Decimal)
assert result[obis.INSTANTANEOUS_VOLTAGE_L1].value == Decimal('230.0')
assert isinstance(telegram.INSTANTANEOUS_VOLTAGE_L1, CosemObject)
assert telegram.INSTANTANEOUS_VOLTAGE_L1.unit == 'V'
assert isinstance(telegram.INSTANTANEOUS_VOLTAGE_L1.value, Decimal)
assert telegram.INSTANTANEOUS_VOLTAGE_L1.value == Decimal('230.0')
# INSTANTANEOUS_VOLTAGE_L2 (1-0:52.7.0)
assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L2], CosemObject)
assert result[obis.INSTANTANEOUS_VOLTAGE_L2].unit == 'V'
assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L2].value, Decimal)
assert result[obis.INSTANTANEOUS_VOLTAGE_L2].value == Decimal('230.0')
assert isinstance(telegram.INSTANTANEOUS_VOLTAGE_L2, CosemObject)
assert telegram.INSTANTANEOUS_VOLTAGE_L2.unit == 'V'
assert isinstance(telegram.INSTANTANEOUS_VOLTAGE_L2.value, Decimal)
assert telegram.INSTANTANEOUS_VOLTAGE_L2.value == Decimal('230.0')
# INSTANTANEOUS_VOLTAGE_L3 (1-0:72.7.0)
assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L3], CosemObject)
assert result[obis.INSTANTANEOUS_VOLTAGE_L3].unit == 'V'
assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L3].value, Decimal)
assert result[obis.INSTANTANEOUS_VOLTAGE_L3].value == Decimal('229.0')
assert isinstance(telegram.INSTANTANEOUS_VOLTAGE_L3, CosemObject)
assert telegram.INSTANTANEOUS_VOLTAGE_L3.unit == 'V'
assert isinstance(telegram.INSTANTANEOUS_VOLTAGE_L3.value, Decimal)
assert telegram.INSTANTANEOUS_VOLTAGE_L3.value == Decimal('229.0')
# INSTANTANEOUS_CURRENT_L1 (1-0:31.7.0)
assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L1], CosemObject)
assert result[obis.INSTANTANEOUS_CURRENT_L1].unit == 'A'
assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L1].value, Decimal)
assert result[obis.INSTANTANEOUS_CURRENT_L1].value == Decimal('0.48')
assert isinstance(telegram.INSTANTANEOUS_CURRENT_L1, CosemObject)
assert telegram.INSTANTANEOUS_CURRENT_L1.unit == 'A'
assert isinstance(telegram.INSTANTANEOUS_CURRENT_L1.value, Decimal)
assert telegram.INSTANTANEOUS_CURRENT_L1.value == Decimal('0.48')
# INSTANTANEOUS_CURRENT_L2 (1-0:51.7.0)
assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L2], CosemObject)
assert result[obis.INSTANTANEOUS_CURRENT_L2].unit == 'A'
assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L2].value, Decimal)
assert result[obis.INSTANTANEOUS_CURRENT_L2].value == Decimal('0.44')
assert isinstance(telegram.INSTANTANEOUS_CURRENT_L2, CosemObject)
assert telegram.INSTANTANEOUS_CURRENT_L2.unit == 'A'
assert isinstance(telegram.INSTANTANEOUS_CURRENT_L2.value, Decimal)
assert telegram.INSTANTANEOUS_CURRENT_L2.value == Decimal('0.44')
# INSTANTANEOUS_CURRENT_L3 (1-0:71.7.0)
assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L3], CosemObject)
assert result[obis.INSTANTANEOUS_CURRENT_L3].unit == 'A'
assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L3].value, Decimal)
assert result[obis.INSTANTANEOUS_CURRENT_L3].value == Decimal('0.86')
assert isinstance(telegram.INSTANTANEOUS_CURRENT_L3, CosemObject)
assert telegram.INSTANTANEOUS_CURRENT_L3.unit == 'A'
assert isinstance(telegram.INSTANTANEOUS_CURRENT_L3.value, Decimal)
assert telegram.INSTANTANEOUS_CURRENT_L3.value == Decimal('0.86')
# TEXT_MESSAGE (0-0:96.13.0)
assert isinstance(result[obis.TEXT_MESSAGE], CosemObject)
assert result[obis.TEXT_MESSAGE].unit is None
assert result[obis.TEXT_MESSAGE].value is None
assert isinstance(telegram.TEXT_MESSAGE, CosemObject)
assert telegram.TEXT_MESSAGE.unit is None
assert telegram.TEXT_MESSAGE.value is None
# DEVICE_TYPE (0-x:24.1.0)
assert isinstance(result[obis.DEVICE_TYPE], CosemObject)
assert result[obis.DEVICE_TYPE].unit is None
assert isinstance(result[obis.DEVICE_TYPE].value, int)
assert result[obis.DEVICE_TYPE].value == 3
assert isinstance(telegram.DEVICE_TYPE, CosemObject)
assert telegram.DEVICE_TYPE.unit is None
assert isinstance(telegram.DEVICE_TYPE.value, int)
assert telegram.DEVICE_TYPE.value == 3
# INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE (1-0:21.7.0)
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE], CosemObject)
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE].unit == 'kW'
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE].value, Decimal)
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE].value == Decimal('0.070')
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE, CosemObject)
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE.unit == 'kW'
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE.value, Decimal)
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE.value == Decimal('0.070')
# INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE (1-0:41.7.0)
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE], CosemObject)
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE].unit == 'kW'
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE].value, Decimal)
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE].value == Decimal('0.032')
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE, CosemObject)
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE.unit == 'kW'
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE.value, Decimal)
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE.value == Decimal('0.032')
# INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE (1-0:61.7.0)
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE], CosemObject)
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE].unit == 'kW'
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE].value, Decimal)
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE].value == Decimal('0.142')
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE, CosemObject)
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE.unit == 'kW'
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE.value, Decimal)
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE.value == Decimal('0.142')
# INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE (1-0:22.7.0)
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE], CosemObject)
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE].unit == 'kW'
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE].value, Decimal)
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE].value == Decimal('0')
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE, CosemObject)
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE.unit == 'kW'
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE.value, Decimal)
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE.value == Decimal('0')
# INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE (1-0:42.7.0)
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE], CosemObject)
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE].unit == 'kW'
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE].value, Decimal)
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE].value == Decimal('0')
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE, CosemObject)
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE.unit == 'kW'
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE.value, Decimal)
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE.value == Decimal('0')
# INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE (1-0:62.7.0)
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE], CosemObject)
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE].unit == 'kW'
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE].value, Decimal)
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE].value == Decimal('0')
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE, CosemObject)
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE.unit == 'kW'
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE.value, Decimal)
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE.value == Decimal('0')
# There's only one Mbus device (gas meter) in this case. Alternatively
# use get_mbget_mbus_device_by_channel
gas_meter_devices = telegram.MBUS_DEVICES
gas_meter_device = gas_meter_devices[0]
# EQUIPMENT_IDENTIFIER_GAS (0-x:96.1.0)
assert isinstance(result[obis.EQUIPMENT_IDENTIFIER_GAS], CosemObject)
assert result[obis.EQUIPMENT_IDENTIFIER_GAS].unit is None
assert isinstance(result[obis.EQUIPMENT_IDENTIFIER_GAS].value, str)
assert result[obis.EQUIPMENT_IDENTIFIER_GAS].value == '3232323241424344313233343536373839'
assert isinstance(gas_meter_device.DEVICE_TYPE, CosemObject)
assert gas_meter_device.DEVICE_TYPE.unit is None
assert isinstance(gas_meter_device.DEVICE_TYPE.value, int)
assert gas_meter_device.DEVICE_TYPE.value == 3
# EQUIPMENT_IDENTIFIER_GAS (0-x:96.1.0)
assert isinstance(gas_meter_device.EQUIPMENT_IDENTIFIER_GAS, CosemObject)
assert gas_meter_device.EQUIPMENT_IDENTIFIER_GAS.unit is None
assert isinstance(gas_meter_device.EQUIPMENT_IDENTIFIER_GAS.value, str)
assert gas_meter_device.EQUIPMENT_IDENTIFIER_GAS.value == '3232323241424344313233343536373839'
# HOURLY_GAS_METER_READING (0-1:24.2.1)
assert isinstance(result[obis.HOURLY_GAS_METER_READING], MBusObject)
assert result[obis.HOURLY_GAS_METER_READING].unit == 'm3'
assert isinstance(result[obis.HOURLY_GAS_METER_READING].value, Decimal)
assert result[obis.HOURLY_GAS_METER_READING].value == Decimal('0.107')
assert isinstance(gas_meter_device.HOURLY_GAS_METER_READING, MBusObject)
assert gas_meter_device.HOURLY_GAS_METER_READING.unit == 'm3'
assert isinstance(telegram.HOURLY_GAS_METER_READING.value, Decimal)
assert gas_meter_device.HOURLY_GAS_METER_READING.value == Decimal('0.107')
def test_checksum_valid(self):
# No exception is raised.
@ -255,10 +265,10 @@ class TelegramParserV5Test(unittest.TestCase):
)
invalid_date_telegram = invalid_date_telegram.replace('!6EEE\r\n', '!90C2\r\n')
parser = TelegramParser(telegram_specifications.V5)
result = parser.parse(invalid_date_telegram)
telegram = parser.parse(invalid_date_telegram)
# HOURLY_GAS_METER_READING (0-1:24.2.1)
assert isinstance(result[obis.HOURLY_GAS_METER_READING], MBusObject)
assert result[obis.HOURLY_GAS_METER_READING].unit is None
assert isinstance(result[obis.HOURLY_GAS_METER_READING].value, Decimal)
assert result[obis.HOURLY_GAS_METER_READING].value == Decimal('0.000')
assert isinstance(telegram.HOURLY_GAS_METER_READING, MBusObject)
assert telegram.HOURLY_GAS_METER_READING.unit is None
assert isinstance(telegram.HOURLY_GAS_METER_READING.value, Decimal)
assert telegram.HOURLY_GAS_METER_READING.value == Decimal('0.000')

View File

@ -4,7 +4,7 @@ import unittest
from dsmr_parser import obis_references as obis
from dsmr_parser.clients.protocol import create_dsmr_protocol
from dsmr_parser.objects import Telegram
TELEGRAM_V2_2 = (
'/ISk5\2MT382-1004\r\n'
@ -44,7 +44,7 @@ class ProtocolTest(unittest.TestCase):
self.protocol.data_received(TELEGRAM_V2_2.encode('ascii'))
telegram = self.protocol.telegram_callback.call_args_list[0][0][0]
assert isinstance(telegram, dict)
assert isinstance(telegram, Telegram)
assert float(telegram[obis.CURRENT_ELECTRICITY_USAGE].value) == 1.01
assert telegram[obis.CURRENT_ELECTRICITY_USAGE].unit == 'kW'

View File

@ -4,7 +4,7 @@ import unittest
from dsmr_parser import obis_references as obis
from dsmr_parser.clients.rfxtrx_protocol import create_rfxtrx_dsmr_protocol, PACKETTYPE_DSMR, SUBTYPE_P1
from dsmr_parser.objects import Telegram
TELEGRAM_V2_2 = (
'/ISk5\2MT382-1004\r\n'
@ -68,7 +68,7 @@ class RFXtrxProtocolTest(unittest.TestCase):
self.protocol.data_received(data[200:])
telegram = self.protocol.telegram_callback.call_args_list[0][0][0]
assert isinstance(telegram, dict)
assert isinstance(telegram, Telegram)
assert float(telegram[obis.CURRENT_ELECTRICITY_USAGE].value) == 1.01
assert telegram[obis.CURRENT_ELECTRICITY_USAGE].unit == 'kW'