Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c78ebe3e2d | ||
|
|
d6e28db116 | ||
|
|
5f1afeb1f8 | ||
|
|
dcb59fddb1 | ||
|
|
9b0a85e84b | ||
|
|
1dca6ab396 | ||
|
|
efc09df71f | ||
|
|
de5c884d8d | ||
|
|
d2f57a8926 | ||
|
|
3d64fea247 | ||
|
|
46860e04c1 | ||
|
|
148bdabc12 | ||
|
|
9d20bb8ad5 | ||
|
|
24ab9aa712 |
@@ -1,6 +1,16 @@
|
||||
Change Log
|
||||
----------
|
||||
|
||||
**0.10** (2017-06-05)
|
||||
|
||||
- bugix: don't force full telegram signatures (`pull request #25 <https://github.com/ndokter/dsmr_parser/pull/25>`_)
|
||||
- removed unused code for automatic telegram detection as this needs reworking after the fix mentioned above
|
||||
- InvalidChecksumError's are logged as warning instead of error
|
||||
|
||||
**0.9** (2017-05-12)
|
||||
|
||||
- added DSMR v5 serial settings
|
||||
|
||||
**0.8** (2017-01-26)
|
||||
|
||||
- added support for DSMR v3
|
||||
|
||||
+111
-26
@@ -8,7 +8,7 @@ DSMR Parser
|
||||
:target: https://travis-ci.org/ndokter/dsmr_parser
|
||||
|
||||
A library for parsing Dutch Smart Meter Requirements (DSMR) telegram data. It
|
||||
also includes a serial client to directly read and parse smart meter data.
|
||||
also includes client implementation to directly read and parse smart meter data.
|
||||
|
||||
|
||||
Features
|
||||
@@ -17,41 +17,126 @@ Features
|
||||
DSMR Parser supports DSMR versions 2, 3, 4 and 5. It has been tested with Python 3.4, 3.5 and 3.6.
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
Client module usage
|
||||
-------------------
|
||||
|
||||
Using the serial reader to connect to your smart meter and parse it's telegrams:
|
||||
**Serial client**
|
||||
|
||||
Read the serial port and work with the parsed telegrams. It should be run in a separate
|
||||
process because the code is blocking (not asynchronous):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dsmr_parser import telegram_specifications
|
||||
from dsmr_parser.clients import SerialReader, SERIAL_SETTINGS_V4
|
||||
|
||||
serial_reader = SerialReader(
|
||||
device='/dev/ttyUSB0',
|
||||
serial_settings=SERIAL_SETTINGS_V4,
|
||||
telegram_specification=telegram_specifications.V4
|
||||
)
|
||||
|
||||
for telegram in serial_reader.read():
|
||||
print(telegram) # see 'Telegram object' docs below
|
||||
|
||||
**AsyncIO client**
|
||||
|
||||
To be documented.
|
||||
|
||||
|
||||
Parsing module usage
|
||||
--------------------
|
||||
The parsing module accepts complete unaltered telegram strings and parses these
|
||||
into a dictionary.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dsmr_parser import telegram_specifications
|
||||
from dsmr_parser.parsers import TelegramParser
|
||||
|
||||
# String is formatted in separate lines for readability.
|
||||
telegram_str = (
|
||||
'/ISk5\2MT382-1000\r\n'
|
||||
'\r\n'
|
||||
'0-0:96.1.1(4B384547303034303436333935353037)\r\n'
|
||||
'1-0:1.8.1(12345.678*kWh)\r\n'
|
||||
'1-0:1.8.2(12345.678*kWh)\r\n'
|
||||
'1-0:2.8.1(12345.678*kWh)\r\n'
|
||||
'1-0:2.8.2(12345.678*kWh)\r\n'
|
||||
'0-0:96.14.0(0002)\r\n'
|
||||
'1-0:1.7.0(001.19*kW)\r\n'
|
||||
'1-0:2.7.0(000.00*kW)\r\n'
|
||||
'0-0:17.0.0(016*A)\r\n'
|
||||
'0-0:96.3.10(1)\r\n'
|
||||
'0-0:96.13.1(303132333435363738)\r\n'
|
||||
'0-0:96.13.0(303132333435363738393A3B3C3D3E3F303132333435363738393A3B3C3D3E'
|
||||
'3F303132333435363738393A3B3C3D3E3F303132333435363738393A3B3C3D3E3F30313233'
|
||||
'3435363738393A3B3C3D3E3F)\r\n'
|
||||
'0-1:96.1.0(3232323241424344313233343536373839)\r\n'
|
||||
'0-1:24.1.0(03)\r\n'
|
||||
'0-1:24.3.0(090212160000)(00)(60)(1)(0-1:24.2.1)(m3)\r\n'
|
||||
'(00001.001)\r\n'
|
||||
'0-1:24.4.0(1)\r\n'
|
||||
'!\r\n'
|
||||
)
|
||||
|
||||
parser = TelegramParser(telegram_specifications.V3)
|
||||
|
||||
telegram = parser.parse(telegram_str)
|
||||
print(telegram) # see 'Telegram object' docs below
|
||||
|
||||
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 telegram_specifications
|
||||
from dsmr_parser import obis_references
|
||||
from dsmr_parser.clients import SerialReader, SERIAL_SETTINGS_V4
|
||||
|
||||
serial_reader = SerialReader(
|
||||
device='/dev/ttyUSB0',
|
||||
serial_settings=SERIAL_SETTINGS_V4,
|
||||
telegram_specification=telegram_specifications.V4
|
||||
)
|
||||
# The telegram message timestamp.
|
||||
message_datetime = telegram[obis_references.P1_MESSAGE_TIMESTAMP]
|
||||
|
||||
for telegram in serial_reader.read():
|
||||
# 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)
|
||||
|
||||
# The telegram message timestamp.
|
||||
message_datetime = telegram[obis_references.P1_MESSAGE_TIMESTAMP]
|
||||
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]]
|
||||
|
||||
# Using the active tariff to determine the electricity being used and
|
||||
# delivered for the right tariff.
|
||||
tariff = telegram[obis_references.ELECTRICITY_ACTIVE_TARIFF]
|
||||
tariff = int(tariff.value)
|
||||
gas_reading = telegram[obis_references.HOURLY_GAS_METER_READING]
|
||||
|
||||
electricity_used_total \
|
||||
= telegram[obis_references.ELECTRICITY_USED_TARIFF_ALL[tariff - 1]]
|
||||
electricity_delivered_total = \
|
||||
telegram[obis_referencesELECTRICITY_DELIVERED_TARIFF_ALL[tariff - 1]]
|
||||
|
||||
gas_reading = telegram[obis_references.HOURLY_GAS_METER_READING]
|
||||
|
||||
# See dsmr_reader.obis_references for all readable telegram values.
|
||||
# See dsmr_reader.obis_references for all readable telegram values.
|
||||
# Note that the avilable values differ per DSMR version.
|
||||
|
||||
|
||||
Installation
|
||||
|
||||
@@ -8,7 +8,7 @@ from serial_asyncio import create_serial_connection
|
||||
|
||||
from dsmr_parser import telegram_specifications
|
||||
from dsmr_parser.clients.telegram_buffer import TelegramBuffer
|
||||
from dsmr_parser.exceptions import ParseError
|
||||
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
|
||||
from dsmr_parser.parsers import TelegramParser
|
||||
from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \
|
||||
SERIAL_SETTINGS_V4
|
||||
@@ -23,6 +23,9 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None):
|
||||
elif dsmr_version == '4':
|
||||
specification = telegram_specifications.V4
|
||||
serial_settings = SERIAL_SETTINGS_V4
|
||||
elif dsmr_version == '5':
|
||||
specification = telegram_specifications.V5
|
||||
serial_settings = SERIAL_SETTINGS_V4
|
||||
else:
|
||||
raise NotImplementedError("No telegram parser found for version: %s",
|
||||
dsmr_version)
|
||||
@@ -98,6 +101,8 @@ class DSMRProtocol(asyncio.Protocol):
|
||||
|
||||
try:
|
||||
parsed_telegram = self.telegram_parser.parse(telegram)
|
||||
except InvalidChecksumError as e:
|
||||
self.log.warning(str(e))
|
||||
except ParseError:
|
||||
self.log.exception("failed to parse telegram")
|
||||
else:
|
||||
|
||||
@@ -4,7 +4,7 @@ import serial
|
||||
import serial_asyncio
|
||||
|
||||
from dsmr_parser.clients.telegram_buffer import TelegramBuffer
|
||||
from dsmr_parser.exceptions import ParseError
|
||||
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
|
||||
from dsmr_parser.parsers import TelegramParser
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ class SerialReader(object):
|
||||
for telegram in self.telegram_buffer.get_all():
|
||||
try:
|
||||
yield self.telegram_parser.parse(telegram)
|
||||
except InvalidChecksumError as e:
|
||||
logger.warning(str(e))
|
||||
except ParseError as e:
|
||||
logger.error('Failed to parse telegram: %s', e)
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
class DSMRObject(object):
|
||||
"""
|
||||
Represents all data from a single telegram line.
|
||||
"""
|
||||
|
||||
def __init__(self, values):
|
||||
self.values = values
|
||||
|
||||
@@ -27,8 +27,8 @@ class TelegramParser(object):
|
||||
|
||||
The telegram str type makes python 2.x integration easier.
|
||||
|
||||
:param str telegram: full telegram from start ('/') to checksum
|
||||
('!ABCD') including line endings inbetween the telegram's lines
|
||||
:param str telegram_data: full telegram from start ('/') to checksum
|
||||
('!ABCD') including line endings in between the telegram's lines
|
||||
:rtype: dict
|
||||
:returns: Shortened example:
|
||||
{
|
||||
@@ -51,6 +51,8 @@ class TelegramParser(object):
|
||||
for signature, parser in self.telegram_specification['objects'].items():
|
||||
match = re.search(signature, telegram_data, re.DOTALL)
|
||||
|
||||
# Some signatures are optional and may not be present,
|
||||
# so only parse lines that match
|
||||
if match:
|
||||
telegram[signature] = parser.parse(match.group(0))
|
||||
|
||||
@@ -147,10 +149,11 @@ class CosemParser(DSMRObjectParser):
|
||||
1 23 45
|
||||
|
||||
1) OBIS Reduced ID-code
|
||||
2) Separator “(“, ASCII 28h
|
||||
2) Separator "(", ASCII 28h
|
||||
3) COSEM object attribute value
|
||||
4) Unit of measurement values (Unit of capture objects attribute) – only if applicable
|
||||
5) Separator “)”, ASCII 29h
|
||||
4) Unit of measurement values (Unit of capture objects attribute) - only if
|
||||
applicable
|
||||
5) Separator ")", ASCII 29h
|
||||
"""
|
||||
|
||||
def parse(self, line):
|
||||
|
||||
@@ -42,34 +42,7 @@ V2_2 = {
|
||||
}
|
||||
}
|
||||
|
||||
V3 = {
|
||||
'checksum_support': False,
|
||||
'objects': {
|
||||
obis.EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)),
|
||||
obis.ELECTRICITY_USED_TARIFF_1: CosemParser(ValueParser(Decimal)),
|
||||
obis.ELECTRICITY_USED_TARIFF_2: CosemParser(ValueParser(Decimal)),
|
||||
obis.ELECTRICITY_DELIVERED_TARIFF_1: CosemParser(ValueParser(Decimal)),
|
||||
obis.ELECTRICITY_DELIVERED_TARIFF_2: CosemParser(ValueParser(Decimal)),
|
||||
obis.ELECTRICITY_ACTIVE_TARIFF: CosemParser(ValueParser(str)),
|
||||
obis.CURRENT_ELECTRICITY_USAGE: CosemParser(ValueParser(Decimal)),
|
||||
obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)),
|
||||
obis.ACTUAL_TRESHOLD_ELECTRICITY: CosemParser(ValueParser(Decimal)),
|
||||
obis.ACTUAL_SWITCH_POSITION: CosemParser(ValueParser(str)),
|
||||
obis.TEXT_MESSAGE_CODE: CosemParser(ValueParser(int)),
|
||||
obis.TEXT_MESSAGE: CosemParser(ValueParser(str)),
|
||||
obis.EQUIPMENT_IDENTIFIER_GAS: CosemParser(ValueParser(str)),
|
||||
obis.DEVICE_TYPE: CosemParser(ValueParser(str)),
|
||||
obis.VALVE_POSITION_GAS: CosemParser(ValueParser(str)),
|
||||
obis.GAS_METER_READING: MBusParser(
|
||||
ValueParser(timestamp),
|
||||
ValueParser(int),
|
||||
ValueParser(int),
|
||||
ValueParser(int),
|
||||
ValueParser(str),
|
||||
ValueParser(Decimal),
|
||||
),
|
||||
}
|
||||
}
|
||||
V3 = V2_2
|
||||
|
||||
V4 = {
|
||||
'checksum_support': True,
|
||||
@@ -145,3 +118,5 @@ V5 = {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ALL = (V2_2, V3, V4, V5)
|
||||
|
||||
Reference in New Issue
Block a user