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:
parent
6fb2ad3495
commit
de167c89b6
@ -1,71 +1,74 @@
|
|||||||
Change Log
|
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)
|
**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)
|
**1.0.0** (2022-12-22)
|
||||||
|
|
||||||
- switched to new numbering scheme https://semver.org/
|
- 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>`_)
|
- 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 (`pull request #110 <https://github.com/ndokter/dsmr_parser/pull/113>`_)
|
- 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)
|
**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)
|
**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>`_).
|
- Test Python 3.10 in CI + legacy badge fix (`PR #105 <https://github.com/ndokter/dsmr_parser/pull/105>`_).
|
||||||
- Update telegram_specifications.py (`pull request #106 <https://github.com/ndokter/dsmr_parser/pull/106>`_).
|
- Update telegram_specifications.py (`PR #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 compatiblity with Belgian standard (`PR #107 <https://github.com/ndokter/dsmr_parser/pull/107>`_).
|
||||||
- Improve documentation asyncio (`pull request #63 <https://github.com/ndokter/dsmr_parser/pull/63>`_).
|
- Improve documentation asyncio (`PR #63 <https://github.com/ndokter/dsmr_parser/pull/63>`_).
|
||||||
|
|
||||||
**0.32** (2022-01-04)
|
**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)
|
**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)
|
**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)
|
**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>`_).
|
- 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 (`pull request #76 <https://github.com/ndokter/dsmr_parser/pull/76>`_).
|
Remove deprecated asyncio coroutine decorator (`PR #76 <https://github.com/ndokter/dsmr_parser/pull/76>`_).
|
||||||
|
|
||||||
**0.28** (2021-02-21)
|
**0.28** (2021-02-21)
|
||||||
|
|
||||||
- Optional keep alive monitoring for TCP/IP connections (`pull request #73 <https://github.com/ndokter/dsmr_parser/pull/73>`_).
|
- 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 (`pull request #74 <https://github.com/ndokter/dsmr_parser/pull/74>`_).
|
- 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)
|
**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)
|
**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)
|
**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)
|
**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)
|
**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>`_).
|
- 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 (`pull request #59 <https://github.com/ndokter/dsmr_parser/pull/59>`_).
|
- Tests are not installed anymore (`PR #59 <https://github.com/ndokter/dsmr_parser/pull/59>`_).
|
||||||
- Example telegram improvement (`pull request #58 <https://github.com/ndokter/dsmr_parser/pull/58>`_).
|
- Example telegram improvement (`PR #58 <https://github.com/ndokter/dsmr_parser/pull/58>`_).
|
||||||
|
|
||||||
**0.22** (2020-08-23)
|
**0.22** (2020-08-23)
|
||||||
|
|
||||||
@ -93,40 +96,40 @@ Remove deprecated asyncio coroutine decorator (`pull request #76 <https://github
|
|||||||
|
|
||||||
**0.18** (2020-01-28)
|
**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)
|
**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)
|
**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)
|
**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)
|
**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)
|
**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)
|
**0.12** (2018-09-23)
|
||||||
|
|
||||||
- Add serial settings for DSMR v5.0 (`pull request #31 <https://github.com/ndokter/dsmr_parser/pull/31>`_).
|
- Add serial settings for DSMR v5.0 (`PR #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>`_).
|
- Lux-creos-obis-1.8.0 (`PR #32 <https://github.com/ndokter/dsmr_parser/pull/32>`_).
|
||||||
|
|
||||||
**0.11** (2017-09-18)
|
**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)
|
**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
|
- 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
|
- 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)
|
**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:**
|
**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)
|
**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>`_)
|
- 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 (`pull request #12 <https://github.com/ndokter/dsmr_parser/pull/12/>`_)
|
- Support added for TCP connections using the asyncio client (`PR #12 <https://github.com/ndokter/dsmr_parser/pull/12/>`_)
|
||||||
|
|
||||||
**0.5** (2016-12-29)
|
**0.5** (2016-12-29)
|
||||||
|
|
||||||
@ -165,16 +168,16 @@ Remove deprecated asyncio coroutine decorator (`pull request #76 <https://github
|
|||||||
|
|
||||||
**0.4** (2016-11-21)
|
**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>`_)
|
- 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 (`pull request #8 <https://github.com/ndokter/dsmr_parser/pull/8>`_)
|
- improved asyncio reader and improve it's error handling (`PR #8 <https://github.com/ndokter/dsmr_parser/pull/8>`_)
|
||||||
|
|
||||||
**0.3** (2016-11-12)
|
**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)
|
**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)
|
**0.1** (2016-08-22)
|
||||||
|
|
||||||
|
174
README.rst
174
README.rst
@ -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>`_.
|
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
|
Client module usage
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
@ -113,10 +112,8 @@ However, if we construct a mock TelegramParser that just returns the already par
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
#from dsmr_parser import obis_references
|
from dsmr_parser import telegram_specifications
|
||||||
#from dsmr_parser import telegram_specifications
|
from dsmr_parser.clients.protocol import create_tcp_dsmr_reader
|
||||||
#from dsmr_parser.clients.protocol import create_dsmr_reader, create_tcp_dsmr_reader
|
|
||||||
#from dsmr_parser.objects import Telegram
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO, format='%(message)s')
|
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:
|
except ParseError as e:
|
||||||
logger.error('Failed to parse telegram: %s', e)
|
logger.error('Failed to parse telegram: %s', e)
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
try:
|
try:
|
||||||
logger.debug("Getting loop")
|
logger.debug("Getting loop")
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
logger.debug("Creating reader")
|
logger.debug("Creating reader")
|
||||||
await create_tcp_dsmr_reader(
|
await create_tcp_dsmr_reader(
|
||||||
HOST,
|
HOST,
|
||||||
PORT,
|
PORT,
|
||||||
DSMR_VERSION,
|
DSMR_VERSION,
|
||||||
printTelegram,
|
printTelegram,
|
||||||
loop
|
loop
|
||||||
)
|
)
|
||||||
logger.debug("Reader created going to sleep now")
|
logger.debug("Reader created going to sleep now")
|
||||||
while True:
|
while True:
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
@ -173,7 +169,9 @@ However, if we construct a mock TelegramParser that just returns the already par
|
|||||||
Parsing module usage
|
Parsing module usage
|
||||||
--------------------
|
--------------------
|
||||||
The parsing module accepts complete unaltered telegram strings and parses these
|
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
|
.. code-block:: python
|
||||||
|
|
||||||
@ -208,135 +206,48 @@ into a dictionary.
|
|||||||
|
|
||||||
parser = TelegramParser(telegram_specifications.V3)
|
parser = TelegramParser(telegram_specifications.V3)
|
||||||
|
|
||||||
|
# see 'Telegram object' docs below
|
||||||
telegram = parser.parse(telegram_str)
|
telegram = parser.parse(telegram_str)
|
||||||
print(telegram) # see 'Telegram object' docs below
|
|
||||||
|
|
||||||
Telegram dictionary
|
Telegram object
|
||||||
-------------------
|
|
||||||
|
|
||||||
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
|
|
||||||
---------------------
|
---------------------
|
||||||
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
|
.. 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
|
# Example to get current electricity usage
|
||||||
from dsmr_parser.clients import SerialReader, SERIAL_SETTINGS_V5
|
print(telegram.CURRENT_ELECTRICITY_USAGE) # <dsmr_parser.objects.CosemObject at 0x7f5e98ae5ac8>
|
||||||
from dsmr_parser.objects import CosemObject, MBusObject, Telegram
|
print(telegram.CURRENT_ELECTRICITY_USAGE.value) # Decimal('2.027')
|
||||||
from dsmr_parser.parsers import TelegramParser
|
print(telegram.CURRENT_ELECTRICITY_USAGE.unit) # 'kW'
|
||||||
import os
|
|
||||||
|
|
||||||
serial_reader = SerialReader(
|
# All Mbus device readings like gas meters and water meters can be retrieved as follows. This
|
||||||
device='/dev/ttyUSB0',
|
# returns a list of MbusDevice objects:
|
||||||
serial_settings=SERIAL_SETTINGS_V5,
|
mbus_devices = telegram.MBUS_DEVICES
|
||||||
telegram_specification=telegram_specifications.V4
|
|
||||||
)
|
|
||||||
|
|
||||||
# telegram = next(serial_reader.read_as_object())
|
# A specific MbusDevice based on the channel it's connected to, can be retrieved as follows:
|
||||||
# print(telegram)
|
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():
|
# DEPRECATED: the dictionary approach of getting the values by key or `.items()' or '.get() is deprecated
|
||||||
os.system('clear')
|
telegram[obis_references.CURRENT_ELECTRICITY_USAGE]
|
||||||
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]: <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:
|
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',
|
'EQUIPMENT_IDENTIFIER_GAS',
|
||||||
'HOURLY_GAS_METER_READING']
|
'HOURLY_GAS_METER_READING']
|
||||||
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import tailer
|
|||||||
|
|
||||||
from dsmr_parser.clients.telegram_buffer import TelegramBuffer
|
from dsmr_parser.clients.telegram_buffer import TelegramBuffer
|
||||||
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
|
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
|
||||||
from dsmr_parser.objects import Telegram
|
|
||||||
from dsmr_parser.parsers import TelegramParser
|
from dsmr_parser.parsers import TelegramParser
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -72,7 +71,7 @@ class FileReader(object):
|
|||||||
|
|
||||||
for telegram in self.telegram_buffer.get_all():
|
for telegram in self.telegram_buffer.get_all():
|
||||||
try:
|
try:
|
||||||
yield Telegram(telegram, self.telegram_parser, self.telegram_specification)
|
yield self.telegram_parser.parse(telegram)
|
||||||
except InvalidChecksumError as e:
|
except InvalidChecksumError as e:
|
||||||
logger.warning(str(e))
|
logger.warning(str(e))
|
||||||
except ParseError as e:
|
except ParseError as e:
|
||||||
@ -121,7 +120,7 @@ class FileInputReader(object):
|
|||||||
|
|
||||||
for telegram in self.telegram_buffer.get_all():
|
for telegram in self.telegram_buffer.get_all():
|
||||||
try:
|
try:
|
||||||
yield Telegram(telegram, self.telegram_parser, self.telegram_specification)
|
yield self.telegram_parser.parse(telegram)
|
||||||
except InvalidChecksumError as e:
|
except InvalidChecksumError as e:
|
||||||
logger.warning(str(e))
|
logger.warning(str(e))
|
||||||
except ParseError as e:
|
except ParseError as e:
|
||||||
@ -167,7 +166,7 @@ class FileTailReader(object):
|
|||||||
|
|
||||||
for telegram in self.telegram_buffer.get_all():
|
for telegram in self.telegram_buffer.get_all():
|
||||||
try:
|
try:
|
||||||
yield Telegram(telegram, self.telegram_parser, self.telegram_specification)
|
yield self.telegram_parser.parse(telegram)
|
||||||
except InvalidChecksumError as e:
|
except InvalidChecksumError as e:
|
||||||
logger.warning(str(e))
|
logger.warning(str(e))
|
||||||
except ParseError as e:
|
except ParseError as e:
|
||||||
|
@ -5,7 +5,6 @@ import serial_asyncio
|
|||||||
from dsmr_parser.clients.telegram_buffer import TelegramBuffer
|
from dsmr_parser.clients.telegram_buffer import TelegramBuffer
|
||||||
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
|
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
|
||||||
from dsmr_parser.parsers import TelegramParser
|
from dsmr_parser.parsers import TelegramParser
|
||||||
from dsmr_parser.objects import Telegram
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -55,7 +54,7 @@ class SerialReader(object):
|
|||||||
|
|
||||||
for telegram in self.telegram_buffer.get_all():
|
for telegram in self.telegram_buffer.get_all():
|
||||||
try:
|
try:
|
||||||
yield Telegram(telegram, self.telegram_parser, self.telegram_specification)
|
yield self.telegram_parser.parse(telegram)
|
||||||
except InvalidChecksumError as e:
|
except InvalidChecksumError as e:
|
||||||
logger.warning(str(e))
|
logger.warning(str(e))
|
||||||
except ParseError as e:
|
except ParseError as e:
|
||||||
@ -121,7 +120,7 @@ class AsyncSerialReader(SerialReader):
|
|||||||
for telegram in self.telegram_buffer.get_all():
|
for telegram in self.telegram_buffer.get_all():
|
||||||
try:
|
try:
|
||||||
queue.put_nowait(
|
queue.put_nowait(
|
||||||
Telegram(telegram, self.telegram_parser, self.telegram_specification)
|
self.telegram_parser.parse(telegram)
|
||||||
)
|
)
|
||||||
except InvalidChecksumError as e:
|
except InvalidChecksumError as e:
|
||||||
logger.warning(str(e))
|
logger.warning(str(e))
|
||||||
|
@ -4,7 +4,6 @@ import socket
|
|||||||
from dsmr_parser.clients.telegram_buffer import TelegramBuffer
|
from dsmr_parser.clients.telegram_buffer import TelegramBuffer
|
||||||
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
|
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
|
||||||
from dsmr_parser.parsers import TelegramParser
|
from dsmr_parser.parsers import TelegramParser
|
||||||
from dsmr_parser.objects import Telegram
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -81,7 +80,7 @@ class SocketReader(object):
|
|||||||
|
|
||||||
for telegram in self.telegram_buffer.get_all():
|
for telegram in self.telegram_buffer.get_all():
|
||||||
try:
|
try:
|
||||||
yield Telegram(telegram, self.telegram_parser, self.telegram_specification)
|
yield self.telegram_parser.parse(telegram)
|
||||||
except InvalidChecksumError as e:
|
except InvalidChecksumError as e:
|
||||||
logger.warning(str(e))
|
logger.warning(str(e))
|
||||||
except ParseError as e:
|
except ParseError as e:
|
||||||
|
@ -19,6 +19,12 @@ EN = {
|
|||||||
obis.ELECTRICITY_DELIVERED_TARIFF_1: 'ELECTRICITY_DELIVERED_TARIFF_1',
|
obis.ELECTRICITY_DELIVERED_TARIFF_1: 'ELECTRICITY_DELIVERED_TARIFF_1',
|
||||||
obis.ELECTRICITY_DELIVERED_TARIFF_2: 'ELECTRICITY_DELIVERED_TARIFF_2',
|
obis.ELECTRICITY_DELIVERED_TARIFF_2: 'ELECTRICITY_DELIVERED_TARIFF_2',
|
||||||
obis.ELECTRICITY_ACTIVE_TARIFF: 'ELECTRICITY_ACTIVE_TARIFF',
|
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.EQUIPMENT_IDENTIFIER: 'EQUIPMENT_IDENTIFIER',
|
||||||
obis.CURRENT_ELECTRICITY_USAGE: 'CURRENT_ELECTRICITY_USAGE',
|
obis.CURRENT_ELECTRICITY_USAGE: 'CURRENT_ELECTRICITY_USAGE',
|
||||||
obis.CURRENT_ELECTRICITY_DELIVERY: 'CURRENT_ELECTRICITY_DELIVERY',
|
obis.CURRENT_ELECTRICITY_DELIVERY: 'CURRENT_ELECTRICITY_DELIVERY',
|
||||||
@ -86,6 +92,7 @@ EN = {
|
|||||||
obis.Q3D_EQUIPMENT_IDENTIFIER: 'Q3D_EQUIPMENT_IDENTIFIER',
|
obis.Q3D_EQUIPMENT_IDENTIFIER: 'Q3D_EQUIPMENT_IDENTIFIER',
|
||||||
obis.Q3D_EQUIPMENT_STATE: 'Q3D_EQUIPMENT_STATE',
|
obis.Q3D_EQUIPMENT_STATE: 'Q3D_EQUIPMENT_STATE',
|
||||||
obis.Q3D_EQUIPMENT_SERIALNUMBER: 'Q3D_EQUIPMENT_SERIALNUMBER',
|
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()])
|
REVERSE_EN = dict([(v, k) for k, v in EN.items()])
|
||||||
|
@ -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'
|
EQUIPMENT_IDENTIFIER = r'\d-\d:96\.1\.1.+?\r\n'
|
||||||
CURRENT_ELECTRICITY_USAGE = r'\d-\d:1\.7\.0.+?\r\n'
|
CURRENT_ELECTRICITY_USAGE = r'\d-\d:1\.7\.0.+?\r\n'
|
||||||
CURRENT_ELECTRICITY_DELIVERY = r'\d-\d:2\.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'
|
LONG_POWER_FAILURE_COUNT = r'\d-\d:96\.7\.9.+?\r\n'
|
||||||
SHORT_POWER_FAILURE_COUNT = r'96\.7\.21.+?\r\n'
|
SHORT_POWER_FAILURE_COUNT = r'\d-\d:96\.7\.21.+?\r\n'
|
||||||
POWER_EVENT_FAILURE_LOG = r'99\.97\.0.+?\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_L1_COUNT = r'\d-\d:32\.32\.0.+?\r\n'
|
||||||
VOLTAGE_SAG_L2_COUNT = r'\d-\d:52\.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'
|
VOLTAGE_SAG_L3_COUNT = r'\d-\d:72\.32\.0.+?\r\n'
|
||||||
|
@ -1,20 +1,16 @@
|
|||||||
import dsmr_parser.obis_name_mapping
|
|
||||||
import datetime
|
|
||||||
import json
|
|
||||||
from decimal import Decimal
|
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.
|
Container for 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:
|
Attributes can be accessed on a telegram object by addressing by their english name, for example:
|
||||||
telegram.ELECTRICITY_USED_TARIFF_1
|
telegram.ELECTRICITY_USED_TARIFF_1
|
||||||
@ -23,25 +19,55 @@ class Telegram(object):
|
|||||||
[k for k,v in telegram]
|
[k for k,v in telegram]
|
||||||
yields:
|
yields:
|
||||||
['P1_MESSAGE_HEADER', 'P1_MESSAGE_TIMESTAMP', 'EQUIPMENT_IDENTIFIER', ...]
|
['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):
|
def __init__(self, *args, **kwargs):
|
||||||
self._telegram_data = telegram_data
|
self._item_names = []
|
||||||
self._telegram_specification = telegram_specification
|
self._mbus_devices = []
|
||||||
self._telegram_parser = telegram_parser
|
super().__init__(*args, **kwargs)
|
||||||
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):
|
def add(self, obis_reference, dsmr_object):
|
||||||
''' will only get called for undefined attributes '''
|
# Update name mapping used to get value by attribute. Example: telegram.P1_MESSAGE_HEADER
|
||||||
obis_reference = self._reverse_obis_name_mapping[name]
|
obis_name = obis_name_mapping.EN[obis_reference]
|
||||||
value = self._dictionary[obis_reference]
|
setattr(self, obis_name, dsmr_object)
|
||||||
setattr(self, name, value)
|
if obis_name not in self._item_names: # TODO repeating obis references
|
||||||
return value
|
self._item_names.append(obis_name)
|
||||||
|
|
||||||
def _get_item_names(self):
|
# TODO isinstance check: MaxDemandParser (BELGIUM_MAXIMUM_DEMAND_13_MONTHS) returns a list
|
||||||
return [self._obis_name_mapping[k] for k, v in self._dictionary.items()]
|
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):
|
def __iter__(self):
|
||||||
for attr in self._item_names:
|
for attr in self._item_names:
|
||||||
@ -51,21 +77,44 @@ class Telegram(object):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
output = ""
|
output = ""
|
||||||
for attr, value in self:
|
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
|
return output
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
return json.dumps(dict([[attr, json.loads(value.to_json())] for attr, value in self]))
|
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):
|
class DSMRObject(object):
|
||||||
"""
|
"""
|
||||||
Represents all data from a single telegram line.
|
Represents all data from a single telegram line.
|
||||||
"""
|
"""
|
||||||
|
def __init__(self, obis_id_code, values):
|
||||||
def __init__(self, values):
|
self.obis_id_code = obis_id_code
|
||||||
self.values = values
|
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):
|
class MBusObject(DSMRObject):
|
||||||
|
|
||||||
@ -94,16 +143,20 @@ class MBusObject(DSMRObject):
|
|||||||
return self.values[1]['unit']
|
return self.values[1]['unit']
|
||||||
|
|
||||||
def __str__(self):
|
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
|
return output
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
timestamp = self.datetime
|
timestamp = self.datetime
|
||||||
if isinstance(self.datetime, datetime.datetime):
|
if isinstance(self.datetime, datetime.datetime):
|
||||||
timestamp = self.datetime.astimezone().isoformat()
|
timestamp = self.datetime.astimezone().astimezone(pytz.utc).isoformat()
|
||||||
value = self.value
|
value = self.value
|
||||||
if isinstance(self.value, datetime.datetime):
|
if isinstance(self.value, datetime.datetime):
|
||||||
value = self.value.astimezone().isoformat()
|
value = self.value.astimezone().astimezone(pytz.utc).isoformat()
|
||||||
if isinstance(self.value, Decimal):
|
if isinstance(self.value, Decimal):
|
||||||
value = float(self.value)
|
value = float(self.value)
|
||||||
output = {
|
output = {
|
||||||
@ -134,20 +187,20 @@ class MBusObjectPeak(DSMRObject):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
output = "{}\t[{}] at {} occurred {}"\
|
output = "{}\t[{}] at {} occurred {}"\
|
||||||
.format(str(self.value), str(self.unit), str(self.datetime.astimezone().isoformat()),
|
.format(str(self.value), str(self.unit), str(self.datetime.astimezone().astimezone(pytz.utc).isoformat()),
|
||||||
str(self.occurred.astimezone().isoformat()))
|
str(self.occurred.astimezone().astimezone(pytz.utc).isoformat()))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
timestamp = self.datetime
|
timestamp = self.datetime
|
||||||
if isinstance(self.datetime, datetime.datetime):
|
if isinstance(self.datetime, datetime.datetime):
|
||||||
timestamp = self.datetime.astimezone().isoformat()
|
timestamp = self.datetime.astimezone().astimezone(pytz.utc).isoformat()
|
||||||
timestamp_occurred = self.occurred
|
timestamp_occurred = self.occurred
|
||||||
if isinstance(self.occurred, datetime.datetime):
|
if isinstance(self.occurred, datetime.datetime):
|
||||||
timestamp_occurred = self.occurred.astimezone().isoformat()
|
timestamp_occurred = self.occurred.astimezone().astimezone(pytz.utc).isoformat()
|
||||||
value = self.value
|
value = self.value
|
||||||
if isinstance(self.value, datetime.datetime):
|
if isinstance(self.value, datetime.datetime):
|
||||||
value = self.value.astimezone().isoformat()
|
value = self.value.astimezone().astimezone(pytz.utc).isoformat()
|
||||||
if isinstance(self.value, Decimal):
|
if isinstance(self.value, Decimal):
|
||||||
value = float(self.value)
|
value = float(self.value)
|
||||||
output = {
|
output = {
|
||||||
@ -172,14 +225,14 @@ class CosemObject(DSMRObject):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
print_value = self.value
|
print_value = self.value
|
||||||
if isinstance(self.value, datetime.datetime):
|
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))
|
output = "{}\t[{}]".format(str(print_value), str(self.unit))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
json_value = self.value
|
json_value = self.value
|
||||||
if isinstance(self.value, datetime.datetime):
|
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):
|
if isinstance(self.value, Decimal):
|
||||||
json_value = float(self.value)
|
json_value = float(self.value)
|
||||||
output = {
|
output = {
|
||||||
@ -196,8 +249,8 @@ class ProfileGenericObject(DSMRObject):
|
|||||||
containing the datetime (timestamp) and the value.
|
containing the datetime (timestamp) and the value.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, values):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(values)
|
super().__init__(*args, **kwargs)
|
||||||
self._buffer_list = None
|
self._buffer_list = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -223,9 +276,16 @@ class ProfileGenericObject(DSMRObject):
|
|||||||
if self._buffer_list is None:
|
if self._buffer_list is None:
|
||||||
self._buffer_list = []
|
self._buffer_list = []
|
||||||
values_offset = 2
|
values_offset = 2
|
||||||
|
|
||||||
for i in range(self.buffer_length):
|
for i in range(self.buffer_length):
|
||||||
offset = values_offset + i * 2
|
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
|
return self._buffer_list
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -234,7 +294,7 @@ class ProfileGenericObject(DSMRObject):
|
|||||||
for buffer_value in self.buffer:
|
for buffer_value in self.buffer:
|
||||||
timestamp = buffer_value.datetime
|
timestamp = buffer_value.datetime
|
||||||
if isinstance(timestamp, datetime.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 += "\n\t event occured at: {}".format(timestamp)
|
||||||
output += "\t for: {} [{}]".format(buffer_value.value, buffer_value.unit)
|
output += "\t for: {} [{}]".format(buffer_value.value, buffer_value.unit)
|
||||||
return output
|
return output
|
||||||
@ -260,3 +320,40 @@ class ProfileGenericObject(DSMRObject):
|
|||||||
list.append(['buffer', buffer_repr])
|
list.append(['buffer', buffer_repr])
|
||||||
output = dict(list)
|
output = dict(list)
|
||||||
return json.dumps(output)
|
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)
|
||||||
|
@ -8,7 +8,7 @@ from decimal import Decimal
|
|||||||
from dlms_cosem.connection import XDlmsApduFactory
|
from dlms_cosem.connection import XDlmsApduFactory
|
||||||
from dlms_cosem.protocol.xdlms import GeneralGlobalCipher
|
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.exceptions import ParseError, InvalidChecksumError
|
||||||
from dsmr_parser.value_types import timestamp
|
from dsmr_parser.value_types import timestamp
|
||||||
|
|
||||||
@ -37,15 +37,7 @@ class TelegramParser(object):
|
|||||||
('!ABCD') including line endings in between the telegram's lines
|
('!ABCD') including line endings in between the telegram's lines
|
||||||
:param str encryption_key: encryption key
|
:param str encryption_key: encryption key
|
||||||
:param str authentication_key: authentication key
|
:param str authentication_key: authentication key
|
||||||
:rtype: dict
|
:rtype: Telegram
|
||||||
: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
|
|
||||||
..
|
|
||||||
}
|
|
||||||
:raises ParseError:
|
:raises ParseError:
|
||||||
:raises InvalidChecksumError:
|
:raises InvalidChecksumError:
|
||||||
"""
|
"""
|
||||||
@ -82,23 +74,25 @@ class TelegramParser(object):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if self.apply_checksum_validation \
|
if self.apply_checksum_validation and self.telegram_specification['checksum_support']:
|
||||||
and self.telegram_specification['checksum_support']:
|
|
||||||
self.validate_checksum(telegram_data)
|
self.validate_checksum(telegram_data)
|
||||||
|
|
||||||
telegram = {}
|
telegram = Telegram()
|
||||||
|
|
||||||
for signature, parser in self.telegram_specification['objects'].items():
|
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,
|
# Some signatures are optional and may not be present,
|
||||||
# so only parse lines that match
|
# so only parse lines that match
|
||||||
if match:
|
for match in matches:
|
||||||
try:
|
try:
|
||||||
telegram[signature] = parser.parse(match.group(0))
|
dsmr_object = parser.parse(match)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error("ignore line with signature {}, because parsing failed.".format(signature),
|
logger.error("ignore line with signature {}, because parsing failed.".format(signature),
|
||||||
exc_info=True)
|
exc_info=True)
|
||||||
|
else:
|
||||||
|
telegram.add(obis_reference=signature, dsmr_object=dsmr_object)
|
||||||
|
|
||||||
return telegram
|
return telegram
|
||||||
|
|
||||||
@ -180,6 +174,20 @@ class DSMRObjectParser(object):
|
|||||||
return [self.value_formats[i].parse(value)
|
return [self.value_formats[i].parse(value)
|
||||||
for i, value in enumerate(values)]
|
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):
|
def _parse(self, line):
|
||||||
# Match value groups, but exclude the parentheses
|
# Match value groups, but exclude the parentheses
|
||||||
pattern = re.compile(r'((?<=\()[0-9a-zA-Z\.\*\-\:]{0,}(?=\)))')
|
pattern = re.compile(r'((?<=\()[0-9a-zA-Z\.\*\-\:]{0,}(?=\)))')
|
||||||
@ -213,7 +221,10 @@ class MBusParser(DSMRObjectParser):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def parse(self, line):
|
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):
|
class MaxDemandParser(DSMRObjectParser):
|
||||||
@ -241,6 +252,8 @@ class MaxDemandParser(DSMRObjectParser):
|
|||||||
pattern = re.compile(r'((?<=\()[0-9a-zA-Z\.\*\-\:]{0,}(?=\)))')
|
pattern = re.compile(r'((?<=\()[0-9a-zA-Z\.\*\-\:]{0,}(?=\)))')
|
||||||
values = re.findall(pattern, line)
|
values = re.findall(pattern, line)
|
||||||
|
|
||||||
|
obis_id_code = self._parse_obis_id_code(line)
|
||||||
|
|
||||||
objects = []
|
objects = []
|
||||||
|
|
||||||
count = int(values[0])
|
count = int(values[0])
|
||||||
@ -248,7 +261,10 @@ class MaxDemandParser(DSMRObjectParser):
|
|||||||
timestamp_month = ValueParser(timestamp).parse(values[i * 3 + 1])
|
timestamp_month = ValueParser(timestamp).parse(values[i * 3 + 1])
|
||||||
timestamp_occurred = ValueParser(timestamp).parse(values[i * 3 + 1])
|
timestamp_occurred = ValueParser(timestamp).parse(values[i * 3 + 1])
|
||||||
value = ValueParser(Decimal).parse(values[i * 3 + 2])
|
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
|
return objects
|
||||||
|
|
||||||
@ -274,7 +290,10 @@ class CosemParser(DSMRObjectParser):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def parse(self, line):
|
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):
|
class ProfileGenericParser(DSMRObjectParser):
|
||||||
@ -333,7 +352,10 @@ class ProfileGenericParser(DSMRObjectParser):
|
|||||||
return [self.value_formats[i].parse(value) for i, value in enumerate(values)]
|
return [self.value_formats[i].parse(value) for i, value in enumerate(values)]
|
||||||
|
|
||||||
def parse(self, line):
|
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):
|
class ValueParser(object):
|
||||||
@ -341,7 +363,7 @@ class ValueParser(object):
|
|||||||
Parses a single value from DSMRObject's.
|
Parses a single value from DSMRObject's.
|
||||||
|
|
||||||
Example with coerce_type being int:
|
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:
|
Example with coerce_type being str:
|
||||||
(42) becomes {'value': '42', 'unit': None}
|
(42) becomes {'value': '42', 'unit': None}
|
||||||
|
2
setup.py
2
setup.py
@ -7,7 +7,7 @@ setup(
|
|||||||
author_email='mail@nldr.net',
|
author_email='mail@nldr.net',
|
||||||
license='MIT',
|
license='MIT',
|
||||||
url='https://github.com/ndokter/dsmr_parser',
|
url='https://github.com/ndokter/dsmr_parser',
|
||||||
version='1.1.0',
|
version='1.2.0',
|
||||||
packages=find_packages(exclude=('test', 'test.*')),
|
packages=find_packages(exclude=('test', 'test.*')),
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'pyserial>=3,<4',
|
'pyserial>=3,<4',
|
||||||
|
@ -129,6 +129,51 @@ TELEGRAM_V5 = (
|
|||||||
'!6EEE\r\n'
|
'!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 = (
|
TELEGRAM_FLUVIUS_V171 = (
|
||||||
'/FLU5\253769484_A\r\n'
|
'/FLU5\253769484_A\r\n'
|
||||||
'\r\n'
|
'\r\n'
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
from dsmr_parser import telegram_specifications
|
from dsmr_parser import telegram_specifications
|
||||||
from dsmr_parser.objects import Telegram
|
|
||||||
from dsmr_parser.parsers import TelegramParser
|
from dsmr_parser.parsers import TelegramParser
|
||||||
from test.example_telegrams import TELEGRAM_V4_2
|
from test.example_telegrams import TELEGRAM_V4_2
|
||||||
parser = TelegramParser(telegram_specifications.V4)
|
parser = TelegramParser(telegram_specifications.V4)
|
||||||
telegram = Telegram(TELEGRAM_V4_2, parser, telegram_specifications.V4)
|
telegram = parser.parse(TELEGRAM_V4_2)
|
||||||
|
|
||||||
print(telegram)
|
print(telegram)
|
||||||
|
0
test/objects/__init__.py
Normal file
0
test/objects/__init__.py
Normal file
61
test/objects/test_mbusdevice.py
Normal file
61
test/objects/test_mbusdevice.py
Normal 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'
|
||||||
|
)
|
||||||
|
)
|
@ -2,7 +2,6 @@ import unittest
|
|||||||
|
|
||||||
from dsmr_parser import telegram_specifications
|
from dsmr_parser import telegram_specifications
|
||||||
|
|
||||||
from dsmr_parser.objects import Telegram
|
|
||||||
from dsmr_parser.objects import ProfileGenericObject
|
from dsmr_parser.objects import ProfileGenericObject
|
||||||
from dsmr_parser.parsers import TelegramParser
|
from dsmr_parser.parsers import TelegramParser
|
||||||
from dsmr_parser.parsers import ProfileGenericParser
|
from dsmr_parser.parsers import ProfileGenericParser
|
||||||
@ -18,7 +17,7 @@ class TestParserCornerCases(unittest.TestCase):
|
|||||||
def test_power_event_log_empty_1(self):
|
def test_power_event_log_empty_1(self):
|
||||||
# POWER_EVENT_FAILURE_LOG (1-0:99.97.0)
|
# POWER_EVENT_FAILURE_LOG (1-0:99.97.0)
|
||||||
parser = TelegramParser(telegram_specifications.V5)
|
parser = TelegramParser(telegram_specifications.V5)
|
||||||
telegram = Telegram(TELEGRAM_V5, parser, telegram_specifications.V5)
|
telegram = parser.parse(TELEGRAM_V5)
|
||||||
|
|
||||||
object_type = ProfileGenericObject
|
object_type = ProfileGenericObject
|
||||||
testitem = telegram.POWER_EVENT_FAILURE_LOG
|
testitem = telegram.POWER_EVENT_FAILURE_LOG
|
@ -1,15 +1,15 @@
|
|||||||
|
import json
|
||||||
import unittest
|
import unittest
|
||||||
import datetime
|
import datetime
|
||||||
import pytz
|
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 import obis_name_mapping
|
||||||
from dsmr_parser.objects import CosemObject
|
from dsmr_parser.objects import CosemObject
|
||||||
from dsmr_parser.objects import MBusObject
|
from dsmr_parser.objects import MBusObject
|
||||||
from dsmr_parser.objects import Telegram
|
|
||||||
from dsmr_parser.objects import ProfileGenericObject
|
from dsmr_parser.objects import ProfileGenericObject
|
||||||
from dsmr_parser.parsers import TelegramParser
|
from dsmr_parser.parsers import TelegramParser
|
||||||
from test.example_telegrams import TELEGRAM_V4_2
|
from test.example_telegrams import TELEGRAM_V4_2, TELEGRAM_V5_TWO_MBUS, TELEGRAM_V5
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ class TelegramTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_instantiate(self):
|
def test_instantiate(self):
|
||||||
parser = TelegramParser(telegram_specifications.V4)
|
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)
|
# P1_MESSAGE_HEADER (1-3:0.2.8)
|
||||||
self.verify_telegram_item(telegram,
|
self.verify_telegram_item(telegram,
|
||||||
@ -320,3 +320,170 @@ class TelegramTest(unittest.TestCase):
|
|||||||
item_names_tested_set = set(self.item_names_tested)
|
item_names_tested_set = set(self.item_names_tested)
|
||||||
|
|
||||||
assert item_names_tested_set == V4_name_set
|
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')
|
@ -5,7 +5,6 @@ import unittest
|
|||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from dsmr_parser import obis_references as obis
|
|
||||||
from dsmr_parser import telegram_specifications
|
from dsmr_parser import telegram_specifications
|
||||||
from dsmr_parser.exceptions import InvalidChecksumError, ParseError
|
from dsmr_parser.exceptions import InvalidChecksumError, ParseError
|
||||||
from dsmr_parser.objects import CosemObject, MBusObject
|
from dsmr_parser.objects import CosemObject, MBusObject
|
||||||
@ -18,211 +17,222 @@ class TelegramParserV5Test(unittest.TestCase):
|
|||||||
|
|
||||||
def test_parse(self):
|
def test_parse(self):
|
||||||
parser = TelegramParser(telegram_specifications.V5)
|
parser = TelegramParser(telegram_specifications.V5)
|
||||||
result = parser.parse(TELEGRAM_V5)
|
telegram = parser.parse(TELEGRAM_V5)
|
||||||
|
|
||||||
# P1_MESSAGE_HEADER (1-3:0.2.8)
|
# P1_MESSAGE_HEADER (1-3:0.2.8)
|
||||||
assert isinstance(result[obis.P1_MESSAGE_HEADER], CosemObject)
|
assert isinstance(telegram.P1_MESSAGE_HEADER, CosemObject)
|
||||||
assert result[obis.P1_MESSAGE_HEADER].unit is None
|
assert telegram.P1_MESSAGE_HEADER.unit is None
|
||||||
assert isinstance(result[obis.P1_MESSAGE_HEADER].value, str)
|
assert isinstance(telegram.P1_MESSAGE_HEADER.value, str)
|
||||||
assert result[obis.P1_MESSAGE_HEADER].value == '50'
|
assert telegram.P1_MESSAGE_HEADER.value == '50'
|
||||||
|
|
||||||
# P1_MESSAGE_TIMESTAMP (0-0:1.0.0)
|
# P1_MESSAGE_TIMESTAMP (0-0:1.0.0)
|
||||||
assert isinstance(result[obis.P1_MESSAGE_TIMESTAMP], CosemObject)
|
assert isinstance(telegram.P1_MESSAGE_TIMESTAMP, CosemObject)
|
||||||
assert result[obis.P1_MESSAGE_TIMESTAMP].unit is None
|
assert telegram.P1_MESSAGE_TIMESTAMP.unit is None
|
||||||
assert isinstance(result[obis.P1_MESSAGE_TIMESTAMP].value, datetime.datetime)
|
assert isinstance(telegram.P1_MESSAGE_TIMESTAMP.value, datetime.datetime)
|
||||||
assert result[obis.P1_MESSAGE_TIMESTAMP].value == \
|
assert telegram.P1_MESSAGE_TIMESTAMP.value == \
|
||||||
datetime.datetime(2017, 1, 2, 18, 20, 2, tzinfo=pytz.UTC)
|
datetime.datetime(2017, 1, 2, 18, 20, 2, tzinfo=pytz.UTC)
|
||||||
|
|
||||||
# ELECTRICITY_USED_TARIFF_1 (1-0:1.8.1)
|
# ELECTRICITY_USED_TARIFF_1 (1-0:1.8.1)
|
||||||
assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_1], CosemObject)
|
assert isinstance(telegram.ELECTRICITY_USED_TARIFF_1, CosemObject)
|
||||||
assert result[obis.ELECTRICITY_USED_TARIFF_1].unit == 'kWh'
|
assert telegram.ELECTRICITY_USED_TARIFF_1.unit == 'kWh'
|
||||||
assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_1].value, Decimal)
|
assert isinstance(telegram.ELECTRICITY_USED_TARIFF_1.value, Decimal)
|
||||||
assert result[obis.ELECTRICITY_USED_TARIFF_1].value == Decimal('4.426')
|
assert telegram.ELECTRICITY_USED_TARIFF_1.value == Decimal('4.426')
|
||||||
|
|
||||||
# ELECTRICITY_USED_TARIFF_2 (1-0:1.8.2)
|
# ELECTRICITY_USED_TARIFF_2 (1-0:1.8.2)
|
||||||
assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_2], CosemObject)
|
assert isinstance(telegram.ELECTRICITY_USED_TARIFF_2, CosemObject)
|
||||||
assert result[obis.ELECTRICITY_USED_TARIFF_2].unit == 'kWh'
|
assert telegram.ELECTRICITY_USED_TARIFF_2.unit == 'kWh'
|
||||||
assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_2].value, Decimal)
|
assert isinstance(telegram.ELECTRICITY_USED_TARIFF_2.value, Decimal)
|
||||||
assert result[obis.ELECTRICITY_USED_TARIFF_2].value == Decimal('2.399')
|
assert telegram.ELECTRICITY_USED_TARIFF_2.value == Decimal('2.399')
|
||||||
|
|
||||||
# ELECTRICITY_DELIVERED_TARIFF_1 (1-0:2.8.1)
|
# ELECTRICITY_DELIVERED_TARIFF_1 (1-0:2.8.1)
|
||||||
assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_1], CosemObject)
|
assert isinstance(telegram.ELECTRICITY_DELIVERED_TARIFF_1, CosemObject)
|
||||||
assert result[obis.ELECTRICITY_DELIVERED_TARIFF_1].unit == 'kWh'
|
assert telegram.ELECTRICITY_DELIVERED_TARIFF_1.unit == 'kWh'
|
||||||
assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_1].value, Decimal)
|
assert isinstance(telegram.ELECTRICITY_DELIVERED_TARIFF_1.value, Decimal)
|
||||||
assert result[obis.ELECTRICITY_DELIVERED_TARIFF_1].value == Decimal('2.444')
|
assert telegram.ELECTRICITY_DELIVERED_TARIFF_1.value == Decimal('2.444')
|
||||||
|
|
||||||
# ELECTRICITY_DELIVERED_TARIFF_2 (1-0:2.8.2)
|
# ELECTRICITY_DELIVERED_TARIFF_2 (1-0:2.8.2)
|
||||||
assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_2], CosemObject)
|
assert isinstance(telegram.ELECTRICITY_DELIVERED_TARIFF_2, CosemObject)
|
||||||
assert result[obis.ELECTRICITY_DELIVERED_TARIFF_2].unit == 'kWh'
|
assert telegram.ELECTRICITY_DELIVERED_TARIFF_2.unit == 'kWh'
|
||||||
assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_2].value, Decimal)
|
assert isinstance(telegram.ELECTRICITY_DELIVERED_TARIFF_2.value, Decimal)
|
||||||
assert result[obis.ELECTRICITY_DELIVERED_TARIFF_2].value == Decimal('0')
|
assert telegram.ELECTRICITY_DELIVERED_TARIFF_2.value == Decimal('0')
|
||||||
|
|
||||||
# ELECTRICITY_ACTIVE_TARIFF (0-0:96.14.0)
|
# ELECTRICITY_ACTIVE_TARIFF (0-0:96.14.0)
|
||||||
assert isinstance(result[obis.ELECTRICITY_ACTIVE_TARIFF], CosemObject)
|
assert isinstance(telegram.ELECTRICITY_ACTIVE_TARIFF, CosemObject)
|
||||||
assert result[obis.ELECTRICITY_ACTIVE_TARIFF].unit is None
|
assert telegram.ELECTRICITY_ACTIVE_TARIFF.unit is None
|
||||||
assert isinstance(result[obis.ELECTRICITY_ACTIVE_TARIFF].value, str)
|
assert isinstance(telegram.ELECTRICITY_ACTIVE_TARIFF.value, str)
|
||||||
assert result[obis.ELECTRICITY_ACTIVE_TARIFF].value == '0002'
|
assert telegram.ELECTRICITY_ACTIVE_TARIFF.value == '0002'
|
||||||
|
|
||||||
# EQUIPMENT_IDENTIFIER (0-0:96.1.1)
|
# EQUIPMENT_IDENTIFIER (0-0:96.1.1)
|
||||||
assert isinstance(result[obis.EQUIPMENT_IDENTIFIER], CosemObject)
|
assert isinstance(telegram.EQUIPMENT_IDENTIFIER, CosemObject)
|
||||||
assert result[obis.EQUIPMENT_IDENTIFIER].unit is None
|
assert telegram.EQUIPMENT_IDENTIFIER.unit is None
|
||||||
assert isinstance(result[obis.EQUIPMENT_IDENTIFIER].value, str)
|
assert isinstance(telegram.EQUIPMENT_IDENTIFIER.value, str)
|
||||||
assert result[obis.EQUIPMENT_IDENTIFIER].value == '4B384547303034303436333935353037'
|
assert telegram.EQUIPMENT_IDENTIFIER.value == '4B384547303034303436333935353037'
|
||||||
|
|
||||||
# CURRENT_ELECTRICITY_USAGE (1-0:1.7.0)
|
# CURRENT_ELECTRICITY_USAGE (1-0:1.7.0)
|
||||||
assert isinstance(result[obis.CURRENT_ELECTRICITY_USAGE], CosemObject)
|
assert isinstance(telegram.CURRENT_ELECTRICITY_USAGE, CosemObject)
|
||||||
assert result[obis.CURRENT_ELECTRICITY_USAGE].unit == 'kW'
|
assert telegram.CURRENT_ELECTRICITY_USAGE.unit == 'kW'
|
||||||
assert isinstance(result[obis.CURRENT_ELECTRICITY_USAGE].value, Decimal)
|
assert isinstance(telegram.CURRENT_ELECTRICITY_USAGE.value, Decimal)
|
||||||
assert result[obis.CURRENT_ELECTRICITY_USAGE].value == Decimal('0.244')
|
assert telegram.CURRENT_ELECTRICITY_USAGE.value == Decimal('0.244')
|
||||||
|
|
||||||
# CURRENT_ELECTRICITY_DELIVERY (1-0:2.7.0)
|
# CURRENT_ELECTRICITY_DELIVERY (1-0:2.7.0)
|
||||||
assert isinstance(result[obis.CURRENT_ELECTRICITY_DELIVERY], CosemObject)
|
assert isinstance(telegram.CURRENT_ELECTRICITY_DELIVERY, CosemObject)
|
||||||
assert result[obis.CURRENT_ELECTRICITY_DELIVERY].unit == 'kW'
|
assert telegram.CURRENT_ELECTRICITY_DELIVERY.unit == 'kW'
|
||||||
assert isinstance(result[obis.CURRENT_ELECTRICITY_DELIVERY].value, Decimal)
|
assert isinstance(telegram.CURRENT_ELECTRICITY_DELIVERY.value, Decimal)
|
||||||
assert result[obis.CURRENT_ELECTRICITY_DELIVERY].value == Decimal('0')
|
assert telegram.CURRENT_ELECTRICITY_DELIVERY.value == Decimal('0')
|
||||||
|
|
||||||
# LONG_POWER_FAILURE_COUNT (96.7.9)
|
# LONG_POWER_FAILURE_COUNT (96.7.9)
|
||||||
assert isinstance(result[obis.LONG_POWER_FAILURE_COUNT], CosemObject)
|
assert isinstance(telegram.LONG_POWER_FAILURE_COUNT, CosemObject)
|
||||||
assert result[obis.LONG_POWER_FAILURE_COUNT].unit is None
|
assert telegram.LONG_POWER_FAILURE_COUNT.unit is None
|
||||||
assert isinstance(result[obis.LONG_POWER_FAILURE_COUNT].value, int)
|
assert isinstance(telegram.LONG_POWER_FAILURE_COUNT.value, int)
|
||||||
assert result[obis.LONG_POWER_FAILURE_COUNT].value == 0
|
assert telegram.LONG_POWER_FAILURE_COUNT.value == 0
|
||||||
|
|
||||||
# SHORT_POWER_FAILURE_COUNT (1-0:96.7.21)
|
# SHORT_POWER_FAILURE_COUNT (1-0:96.7.21)
|
||||||
assert isinstance(result[obis.SHORT_POWER_FAILURE_COUNT], CosemObject)
|
assert isinstance(telegram.SHORT_POWER_FAILURE_COUNT, CosemObject)
|
||||||
assert result[obis.SHORT_POWER_FAILURE_COUNT].unit is None
|
assert telegram.SHORT_POWER_FAILURE_COUNT.unit is None
|
||||||
assert isinstance(result[obis.SHORT_POWER_FAILURE_COUNT].value, int)
|
assert isinstance(telegram.SHORT_POWER_FAILURE_COUNT.value, int)
|
||||||
assert result[obis.SHORT_POWER_FAILURE_COUNT].value == 13
|
assert telegram.SHORT_POWER_FAILURE_COUNT.value == 13
|
||||||
|
|
||||||
# VOLTAGE_SAG_L1_COUNT (1-0:32.32.0)
|
# VOLTAGE_SAG_L1_COUNT (1-0:32.32.0)
|
||||||
assert isinstance(result[obis.VOLTAGE_SAG_L1_COUNT], CosemObject)
|
assert isinstance(telegram.VOLTAGE_SAG_L1_COUNT, CosemObject)
|
||||||
assert result[obis.VOLTAGE_SAG_L1_COUNT].unit is None
|
assert telegram.VOLTAGE_SAG_L1_COUNT.unit is None
|
||||||
assert isinstance(result[obis.VOLTAGE_SAG_L1_COUNT].value, int)
|
assert isinstance(telegram.VOLTAGE_SAG_L1_COUNT.value, int)
|
||||||
assert result[obis.VOLTAGE_SAG_L1_COUNT].value == 0
|
assert telegram.VOLTAGE_SAG_L1_COUNT.value == 0
|
||||||
|
|
||||||
# VOLTAGE_SAG_L2_COUNT (1-0:52.32.0)
|
# VOLTAGE_SAG_L2_COUNT (1-0:52.32.0)
|
||||||
assert isinstance(result[obis.VOLTAGE_SAG_L2_COUNT], CosemObject)
|
assert isinstance(telegram.VOLTAGE_SAG_L2_COUNT, CosemObject)
|
||||||
assert result[obis.VOLTAGE_SAG_L2_COUNT].unit is None
|
assert telegram.VOLTAGE_SAG_L2_COUNT.unit is None
|
||||||
assert isinstance(result[obis.VOLTAGE_SAG_L2_COUNT].value, int)
|
assert isinstance(telegram.VOLTAGE_SAG_L2_COUNT.value, int)
|
||||||
assert result[obis.VOLTAGE_SAG_L2_COUNT].value == 0
|
assert telegram.VOLTAGE_SAG_L2_COUNT.value == 0
|
||||||
|
|
||||||
# VOLTAGE_SAG_L3_COUNT (1-0:72.32.0)
|
# VOLTAGE_SAG_L3_COUNT (1-0:72.32.0)
|
||||||
assert isinstance(result[obis.VOLTAGE_SAG_L3_COUNT], CosemObject)
|
assert isinstance(telegram.VOLTAGE_SAG_L3_COUNT, CosemObject)
|
||||||
assert result[obis.VOLTAGE_SAG_L3_COUNT].unit is None
|
assert telegram.VOLTAGE_SAG_L3_COUNT.unit is None
|
||||||
assert isinstance(result[obis.VOLTAGE_SAG_L3_COUNT].value, int)
|
assert isinstance(telegram.VOLTAGE_SAG_L3_COUNT.value, int)
|
||||||
assert result[obis.VOLTAGE_SAG_L3_COUNT].value == 0
|
assert telegram.VOLTAGE_SAG_L3_COUNT.value == 0
|
||||||
|
|
||||||
# VOLTAGE_SWELL_L1_COUNT (1-0:32.36.0)
|
# VOLTAGE_SWELL_L1_COUNT (1-0:32.36.0)
|
||||||
assert isinstance(result[obis.VOLTAGE_SWELL_L1_COUNT], CosemObject)
|
assert isinstance(telegram.VOLTAGE_SWELL_L1_COUNT, CosemObject)
|
||||||
assert result[obis.VOLTAGE_SWELL_L1_COUNT].unit is None
|
assert telegram.VOLTAGE_SWELL_L1_COUNT.unit is None
|
||||||
assert isinstance(result[obis.VOLTAGE_SWELL_L1_COUNT].value, int)
|
assert isinstance(telegram.VOLTAGE_SWELL_L1_COUNT.value, int)
|
||||||
assert result[obis.VOLTAGE_SWELL_L1_COUNT].value == 0
|
assert telegram.VOLTAGE_SWELL_L1_COUNT.value == 0
|
||||||
|
|
||||||
# VOLTAGE_SWELL_L2_COUNT (1-0:52.36.0)
|
# VOLTAGE_SWELL_L2_COUNT (1-0:52.36.0)
|
||||||
assert isinstance(result[obis.VOLTAGE_SWELL_L2_COUNT], CosemObject)
|
assert isinstance(telegram.VOLTAGE_SWELL_L2_COUNT, CosemObject)
|
||||||
assert result[obis.VOLTAGE_SWELL_L2_COUNT].unit is None
|
assert telegram.VOLTAGE_SWELL_L2_COUNT.unit is None
|
||||||
assert isinstance(result[obis.VOLTAGE_SWELL_L2_COUNT].value, int)
|
assert isinstance(telegram.VOLTAGE_SWELL_L2_COUNT.value, int)
|
||||||
assert result[obis.VOLTAGE_SWELL_L2_COUNT].value == 0
|
assert telegram.VOLTAGE_SWELL_L2_COUNT.value == 0
|
||||||
|
|
||||||
# VOLTAGE_SWELL_L3_COUNT (1-0:72.36.0)
|
# VOLTAGE_SWELL_L3_COUNT (1-0:72.36.0)
|
||||||
assert isinstance(result[obis.VOLTAGE_SWELL_L3_COUNT], CosemObject)
|
assert isinstance(telegram.VOLTAGE_SWELL_L3_COUNT, CosemObject)
|
||||||
assert result[obis.VOLTAGE_SWELL_L3_COUNT].unit is None
|
assert telegram.VOLTAGE_SWELL_L3_COUNT.unit is None
|
||||||
assert isinstance(result[obis.VOLTAGE_SWELL_L3_COUNT].value, int)
|
assert isinstance(telegram.VOLTAGE_SWELL_L3_COUNT.value, int)
|
||||||
assert result[obis.VOLTAGE_SWELL_L3_COUNT].value == 0
|
assert telegram.VOLTAGE_SWELL_L3_COUNT.value == 0
|
||||||
|
|
||||||
# INSTANTANEOUS_VOLTAGE_L1 (1-0:32.7.0)
|
# INSTANTANEOUS_VOLTAGE_L1 (1-0:32.7.0)
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L1], CosemObject)
|
assert isinstance(telegram.INSTANTANEOUS_VOLTAGE_L1, CosemObject)
|
||||||
assert result[obis.INSTANTANEOUS_VOLTAGE_L1].unit == 'V'
|
assert telegram.INSTANTANEOUS_VOLTAGE_L1.unit == 'V'
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L1].value, Decimal)
|
assert isinstance(telegram.INSTANTANEOUS_VOLTAGE_L1.value, Decimal)
|
||||||
assert result[obis.INSTANTANEOUS_VOLTAGE_L1].value == Decimal('230.0')
|
assert telegram.INSTANTANEOUS_VOLTAGE_L1.value == Decimal('230.0')
|
||||||
|
|
||||||
# INSTANTANEOUS_VOLTAGE_L2 (1-0:52.7.0)
|
# INSTANTANEOUS_VOLTAGE_L2 (1-0:52.7.0)
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L2], CosemObject)
|
assert isinstance(telegram.INSTANTANEOUS_VOLTAGE_L2, CosemObject)
|
||||||
assert result[obis.INSTANTANEOUS_VOLTAGE_L2].unit == 'V'
|
assert telegram.INSTANTANEOUS_VOLTAGE_L2.unit == 'V'
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L2].value, Decimal)
|
assert isinstance(telegram.INSTANTANEOUS_VOLTAGE_L2.value, Decimal)
|
||||||
assert result[obis.INSTANTANEOUS_VOLTAGE_L2].value == Decimal('230.0')
|
assert telegram.INSTANTANEOUS_VOLTAGE_L2.value == Decimal('230.0')
|
||||||
|
|
||||||
# INSTANTANEOUS_VOLTAGE_L3 (1-0:72.7.0)
|
# INSTANTANEOUS_VOLTAGE_L3 (1-0:72.7.0)
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L3], CosemObject)
|
assert isinstance(telegram.INSTANTANEOUS_VOLTAGE_L3, CosemObject)
|
||||||
assert result[obis.INSTANTANEOUS_VOLTAGE_L3].unit == 'V'
|
assert telegram.INSTANTANEOUS_VOLTAGE_L3.unit == 'V'
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L3].value, Decimal)
|
assert isinstance(telegram.INSTANTANEOUS_VOLTAGE_L3.value, Decimal)
|
||||||
assert result[obis.INSTANTANEOUS_VOLTAGE_L3].value == Decimal('229.0')
|
assert telegram.INSTANTANEOUS_VOLTAGE_L3.value == Decimal('229.0')
|
||||||
|
|
||||||
# INSTANTANEOUS_CURRENT_L1 (1-0:31.7.0)
|
# INSTANTANEOUS_CURRENT_L1 (1-0:31.7.0)
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L1], CosemObject)
|
assert isinstance(telegram.INSTANTANEOUS_CURRENT_L1, CosemObject)
|
||||||
assert result[obis.INSTANTANEOUS_CURRENT_L1].unit == 'A'
|
assert telegram.INSTANTANEOUS_CURRENT_L1.unit == 'A'
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L1].value, Decimal)
|
assert isinstance(telegram.INSTANTANEOUS_CURRENT_L1.value, Decimal)
|
||||||
assert result[obis.INSTANTANEOUS_CURRENT_L1].value == Decimal('0.48')
|
assert telegram.INSTANTANEOUS_CURRENT_L1.value == Decimal('0.48')
|
||||||
|
|
||||||
# INSTANTANEOUS_CURRENT_L2 (1-0:51.7.0)
|
# INSTANTANEOUS_CURRENT_L2 (1-0:51.7.0)
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L2], CosemObject)
|
assert isinstance(telegram.INSTANTANEOUS_CURRENT_L2, CosemObject)
|
||||||
assert result[obis.INSTANTANEOUS_CURRENT_L2].unit == 'A'
|
assert telegram.INSTANTANEOUS_CURRENT_L2.unit == 'A'
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L2].value, Decimal)
|
assert isinstance(telegram.INSTANTANEOUS_CURRENT_L2.value, Decimal)
|
||||||
assert result[obis.INSTANTANEOUS_CURRENT_L2].value == Decimal('0.44')
|
assert telegram.INSTANTANEOUS_CURRENT_L2.value == Decimal('0.44')
|
||||||
|
|
||||||
# INSTANTANEOUS_CURRENT_L3 (1-0:71.7.0)
|
# INSTANTANEOUS_CURRENT_L3 (1-0:71.7.0)
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L3], CosemObject)
|
assert isinstance(telegram.INSTANTANEOUS_CURRENT_L3, CosemObject)
|
||||||
assert result[obis.INSTANTANEOUS_CURRENT_L3].unit == 'A'
|
assert telegram.INSTANTANEOUS_CURRENT_L3.unit == 'A'
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L3].value, Decimal)
|
assert isinstance(telegram.INSTANTANEOUS_CURRENT_L3.value, Decimal)
|
||||||
assert result[obis.INSTANTANEOUS_CURRENT_L3].value == Decimal('0.86')
|
assert telegram.INSTANTANEOUS_CURRENT_L3.value == Decimal('0.86')
|
||||||
|
|
||||||
# TEXT_MESSAGE (0-0:96.13.0)
|
# TEXT_MESSAGE (0-0:96.13.0)
|
||||||
assert isinstance(result[obis.TEXT_MESSAGE], CosemObject)
|
assert isinstance(telegram.TEXT_MESSAGE, CosemObject)
|
||||||
assert result[obis.TEXT_MESSAGE].unit is None
|
assert telegram.TEXT_MESSAGE.unit is None
|
||||||
assert result[obis.TEXT_MESSAGE].value is None
|
assert telegram.TEXT_MESSAGE.value is None
|
||||||
|
|
||||||
# DEVICE_TYPE (0-x:24.1.0)
|
# DEVICE_TYPE (0-x:24.1.0)
|
||||||
assert isinstance(result[obis.DEVICE_TYPE], CosemObject)
|
assert isinstance(telegram.DEVICE_TYPE, CosemObject)
|
||||||
assert result[obis.DEVICE_TYPE].unit is None
|
assert telegram.DEVICE_TYPE.unit is None
|
||||||
assert isinstance(result[obis.DEVICE_TYPE].value, int)
|
assert isinstance(telegram.DEVICE_TYPE.value, int)
|
||||||
assert result[obis.DEVICE_TYPE].value == 3
|
assert telegram.DEVICE_TYPE.value == 3
|
||||||
|
|
||||||
# INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE (1-0:21.7.0)
|
# INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE (1-0:21.7.0)
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE], CosemObject)
|
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE, CosemObject)
|
||||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE].unit == 'kW'
|
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE.unit == 'kW'
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE].value, Decimal)
|
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE.value, Decimal)
|
||||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE].value == Decimal('0.070')
|
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE.value == Decimal('0.070')
|
||||||
|
|
||||||
# INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE (1-0:41.7.0)
|
# INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE (1-0:41.7.0)
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE], CosemObject)
|
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE, CosemObject)
|
||||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE].unit == 'kW'
|
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE.unit == 'kW'
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE].value, Decimal)
|
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE.value, Decimal)
|
||||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE].value == Decimal('0.032')
|
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE.value == Decimal('0.032')
|
||||||
|
|
||||||
# INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE (1-0:61.7.0)
|
# INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE (1-0:61.7.0)
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE], CosemObject)
|
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE, CosemObject)
|
||||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE].unit == 'kW'
|
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE.unit == 'kW'
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE].value, Decimal)
|
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE.value, Decimal)
|
||||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE].value == Decimal('0.142')
|
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE.value == Decimal('0.142')
|
||||||
|
|
||||||
# INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE (1-0:22.7.0)
|
# INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE (1-0:22.7.0)
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE], CosemObject)
|
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE, CosemObject)
|
||||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE].unit == 'kW'
|
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE.unit == 'kW'
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE].value, Decimal)
|
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE.value, Decimal)
|
||||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE].value == Decimal('0')
|
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE.value == Decimal('0')
|
||||||
|
|
||||||
# INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE (1-0:42.7.0)
|
# INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE (1-0:42.7.0)
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE], CosemObject)
|
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE, CosemObject)
|
||||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE].unit == 'kW'
|
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE.unit == 'kW'
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE].value, Decimal)
|
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE.value, Decimal)
|
||||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE].value == Decimal('0')
|
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE.value == Decimal('0')
|
||||||
|
|
||||||
# INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE (1-0:62.7.0)
|
# INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE (1-0:62.7.0)
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE], CosemObject)
|
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE, CosemObject)
|
||||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE].unit == 'kW'
|
assert telegram.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE.unit == 'kW'
|
||||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE].value, Decimal)
|
assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE.value, Decimal)
|
||||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE].value == Decimal('0')
|
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)
|
# EQUIPMENT_IDENTIFIER_GAS (0-x:96.1.0)
|
||||||
assert isinstance(result[obis.EQUIPMENT_IDENTIFIER_GAS], CosemObject)
|
assert isinstance(gas_meter_device.DEVICE_TYPE, CosemObject)
|
||||||
assert result[obis.EQUIPMENT_IDENTIFIER_GAS].unit is None
|
assert gas_meter_device.DEVICE_TYPE.unit is None
|
||||||
assert isinstance(result[obis.EQUIPMENT_IDENTIFIER_GAS].value, str)
|
assert isinstance(gas_meter_device.DEVICE_TYPE.value, int)
|
||||||
assert result[obis.EQUIPMENT_IDENTIFIER_GAS].value == '3232323241424344313233343536373839'
|
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)
|
# HOURLY_GAS_METER_READING (0-1:24.2.1)
|
||||||
assert isinstance(result[obis.HOURLY_GAS_METER_READING], MBusObject)
|
assert isinstance(gas_meter_device.HOURLY_GAS_METER_READING, MBusObject)
|
||||||
assert result[obis.HOURLY_GAS_METER_READING].unit == 'm3'
|
assert gas_meter_device.HOURLY_GAS_METER_READING.unit == 'm3'
|
||||||
assert isinstance(result[obis.HOURLY_GAS_METER_READING].value, Decimal)
|
assert isinstance(telegram.HOURLY_GAS_METER_READING.value, Decimal)
|
||||||
assert result[obis.HOURLY_GAS_METER_READING].value == Decimal('0.107')
|
assert gas_meter_device.HOURLY_GAS_METER_READING.value == Decimal('0.107')
|
||||||
|
|
||||||
def test_checksum_valid(self):
|
def test_checksum_valid(self):
|
||||||
# No exception is raised.
|
# 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')
|
invalid_date_telegram = invalid_date_telegram.replace('!6EEE\r\n', '!90C2\r\n')
|
||||||
parser = TelegramParser(telegram_specifications.V5)
|
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)
|
# HOURLY_GAS_METER_READING (0-1:24.2.1)
|
||||||
assert isinstance(result[obis.HOURLY_GAS_METER_READING], MBusObject)
|
assert isinstance(telegram.HOURLY_GAS_METER_READING, MBusObject)
|
||||||
assert result[obis.HOURLY_GAS_METER_READING].unit is None
|
assert telegram.HOURLY_GAS_METER_READING.unit is None
|
||||||
assert isinstance(result[obis.HOURLY_GAS_METER_READING].value, Decimal)
|
assert isinstance(telegram.HOURLY_GAS_METER_READING.value, Decimal)
|
||||||
assert result[obis.HOURLY_GAS_METER_READING].value == Decimal('0.000')
|
assert telegram.HOURLY_GAS_METER_READING.value == Decimal('0.000')
|
||||||
|
@ -4,7 +4,7 @@ import unittest
|
|||||||
|
|
||||||
from dsmr_parser import obis_references as obis
|
from dsmr_parser import obis_references as obis
|
||||||
from dsmr_parser.clients.protocol import create_dsmr_protocol
|
from dsmr_parser.clients.protocol import create_dsmr_protocol
|
||||||
|
from dsmr_parser.objects import Telegram
|
||||||
|
|
||||||
TELEGRAM_V2_2 = (
|
TELEGRAM_V2_2 = (
|
||||||
'/ISk5\2MT382-1004\r\n'
|
'/ISk5\2MT382-1004\r\n'
|
||||||
@ -44,7 +44,7 @@ class ProtocolTest(unittest.TestCase):
|
|||||||
self.protocol.data_received(TELEGRAM_V2_2.encode('ascii'))
|
self.protocol.data_received(TELEGRAM_V2_2.encode('ascii'))
|
||||||
|
|
||||||
telegram = self.protocol.telegram_callback.call_args_list[0][0][0]
|
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 float(telegram[obis.CURRENT_ELECTRICITY_USAGE].value) == 1.01
|
||||||
assert telegram[obis.CURRENT_ELECTRICITY_USAGE].unit == 'kW'
|
assert telegram[obis.CURRENT_ELECTRICITY_USAGE].unit == 'kW'
|
||||||
|
@ -4,7 +4,7 @@ import unittest
|
|||||||
|
|
||||||
from dsmr_parser import obis_references as obis
|
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.clients.rfxtrx_protocol import create_rfxtrx_dsmr_protocol, PACKETTYPE_DSMR, SUBTYPE_P1
|
||||||
|
from dsmr_parser.objects import Telegram
|
||||||
|
|
||||||
TELEGRAM_V2_2 = (
|
TELEGRAM_V2_2 = (
|
||||||
'/ISk5\2MT382-1004\r\n'
|
'/ISk5\2MT382-1004\r\n'
|
||||||
@ -68,7 +68,7 @@ class RFXtrxProtocolTest(unittest.TestCase):
|
|||||||
self.protocol.data_received(data[200:])
|
self.protocol.data_received(data[200:])
|
||||||
|
|
||||||
telegram = self.protocol.telegram_callback.call_args_list[0][0][0]
|
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 float(telegram[obis.CURRENT_ELECTRICITY_USAGE].value) == 1.01
|
||||||
assert telegram[obis.CURRENT_ELECTRICITY_USAGE].unit == 'kW'
|
assert telegram[obis.CURRENT_ELECTRICITY_USAGE].unit == 'kW'
|
||||||
|
Loading…
Reference in New Issue
Block a user