Compare commits

..
3 Commits
24 changed files with 131 additions and 595 deletions
-4
View File
@@ -6,7 +6,3 @@
/.project /.project
/.pydevproject /.pydevproject
/.coverage /.coverage
build/
dist/
*.*~
*~
+2 -41
View File
@@ -1,48 +1,9 @@
Change Log Change Log
---------- ----------
**0.18** (2020-01-28) **0.9** (2017-03-02)
- PyCRC replacement (`pull request #48 <https://github.com/ndokter/dsmr_parser/pull/48>`_). - allow the telegram specification to optionally be autodetected
**0.17** (2019-12-21)
- Add a true telegram object (`pull request #40 <https://github.com/ndokter/dsmr_parser/pull/40>`_).
**0.16** (2019-12-21)
- Add support for Belgian and Smarty meters (`pull request #44 <https://github.com/ndokter/dsmr_parser/pull/44>`_).
**0.15** (2019-12-12)
- Fixed asyncio loop issue (`pull request #43 <https://github.com/ndokter/dsmr_parser/pull/43>`_).
**0.14** (2019-10-08)
- Changed serial reading to reduce CPU usage (`pull request #37 <https://github.com/ndokter/dsmr_parser/pull/37>`_).
**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>`_).
**0.12** (2018-09-23)
- Add serial settings for DSMR v5.0 (`pull request #31 <https://github.com/ndokter/dsmr_parser/pull/31>`_).
- Lux-creos-obis-1.8.0 (`pull request #32 <https://github.com/ndokter/dsmr_parser/pull/32>`_).
**0.11** (2017-09-18)
- NULL value fix in checksum (`pull request #26 <https://github.com/ndokter/dsmr_parser/pull/26>`_)
**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) **0.8** (2017-01-26)
+2 -112
View File
@@ -54,7 +54,6 @@ into a dictionary.
from dsmr_parser import telegram_specifications from dsmr_parser import telegram_specifications
from dsmr_parser.parsers import TelegramParser from dsmr_parser.parsers import TelegramParser
# String is formatted in separate lines for readability.
telegram_str = ( telegram_str = (
'/ISk5\2MT382-1000\r\n' '/ISk5\2MT382-1000\r\n'
'\r\n' '\r\n'
@@ -85,8 +84,8 @@ into a dictionary.
telegram = parser.parse(telegram_str) telegram = parser.parse(telegram_str)
print(telegram) # see 'Telegram object' docs below print(telegram) # see 'Telegram object' docs below
Telegram dictionary Telegram object
------------------- ---------------
A dictionary of which the key indicates the field type. These regex values A dictionary of which the key indicates the field type. These regex values
correspond to one of dsmr_parser.obis_reference constants. correspond to one of dsmr_parser.obis_reference constants.
@@ -138,115 +137,6 @@ Example to get some of the values:
# 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. # Note that the avilable values differ per DSMR version.
Telegram as an Object
---------------------
An object version of the telegram is available as well.
.. code-block:: python
# DSMR v4.2 p1 using dsmr_parser and telegram objects
from dsmr_parser import telegram_specifications
from dsmr_parser.clients import SerialReader, SERIAL_SETTINGS_V5
from dsmr_parser.objects import CosemObject, MBusObject, Telegram
from dsmr_parser.parsers import TelegramParser
import os
serial_reader = SerialReader(
device='/dev/ttyUSB0',
serial_settings=SERIAL_SETTINGS_V5,
telegram_specification=telegram_specifications.V4
)
# telegram = next(serial_reader.read_as_object())
# print(telegram)
for telegram in serial_reader.read_as_object():
os.system('clear')
print(telegram)
Example of output of print of the telegram object:
.. code-block:: console
P1_MESSAGE_HEADER: 42 [None]
P1_MESSAGE_TIMESTAMP: 2016-11-13 19:57:57+00:00 [None]
EQUIPMENT_IDENTIFIER: 3960221976967177082151037881335713 [None]
ELECTRICITY_USED_TARIFF_1: 1581.123 [kWh]
ELECTRICITY_USED_TARIFF_2: 1435.706 [kWh]
ELECTRICITY_DELIVERED_TARIFF_1: 0.000 [kWh]
ELECTRICITY_DELIVERED_TARIFF_2: 0.000 [kWh]
ELECTRICITY_ACTIVE_TARIFF: 0002 [None]
CURRENT_ELECTRICITY_USAGE: 2.027 [kW]
CURRENT_ELECTRICITY_DELIVERY: 0.000 [kW]
LONG_POWER_FAILURE_COUNT: 7 [None]
VOLTAGE_SAG_L1_COUNT: 0 [None]
VOLTAGE_SAG_L2_COUNT: 0 [None]
VOLTAGE_SAG_L3_COUNT: 0 [None]
VOLTAGE_SWELL_L1_COUNT: 0 [None]
VOLTAGE_SWELL_L2_COUNT: 0 [None]
VOLTAGE_SWELL_L3_COUNT: 0 [None]
TEXT_MESSAGE_CODE: None [None]
TEXT_MESSAGE: None [None]
DEVICE_TYPE: 3 [None]
INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: 0.170 [kW]
INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE: 1.247 [kW]
INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE: 0.209 [kW]
INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE: 0.000 [kW]
INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE: 0.000 [kW]
INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: 0.000 [kW]
EQUIPMENT_IDENTIFIER_GAS: 4819243993373755377509728609491464 [None]
HOURLY_GAS_METER_READING: 981.443 [m3]
Accessing the telegrams information as attributes directly:
.. code-block:: python
telegram
Out[3]: <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:
.. code-block:: python
[attr for attr, value in telegram]
Out[11]:
['P1_MESSAGE_HEADER',
'P1_MESSAGE_TIMESTAMP',
'EQUIPMENT_IDENTIFIER',
'ELECTRICITY_USED_TARIFF_1',
'ELECTRICITY_USED_TARIFF_2',
'ELECTRICITY_DELIVERED_TARIFF_1',
'ELECTRICITY_DELIVERED_TARIFF_2',
'ELECTRICITY_ACTIVE_TARIFF',
'CURRENT_ELECTRICITY_USAGE',
'CURRENT_ELECTRICITY_DELIVERY',
'LONG_POWER_FAILURE_COUNT',
'VOLTAGE_SAG_L1_COUNT',
'VOLTAGE_SAG_L2_COUNT',
'VOLTAGE_SAG_L3_COUNT',
'VOLTAGE_SWELL_L1_COUNT',
'VOLTAGE_SWELL_L2_COUNT',
'VOLTAGE_SWELL_L3_COUNT',
'TEXT_MESSAGE_CODE',
'TEXT_MESSAGE',
'DEVICE_TYPE',
'INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE',
'INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE',
'INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE',
'INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE',
'INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE',
'INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE',
'EQUIPMENT_IDENTIFIER_GAS',
'HOURLY_GAS_METER_READING']
Installation Installation
------------ ------------
+1 -1
View File
@@ -1,5 +1,5 @@
from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \ from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \
SERIAL_SETTINGS_V4, SERIAL_SETTINGS_V5 SERIAL_SETTINGS_V4
from dsmr_parser.clients.serial_ import SerialReader, AsyncSerialReader from dsmr_parser.clients.serial_ import SerialReader, AsyncSerialReader
from dsmr_parser.clients.protocol import create_dsmr_protocol, \ from dsmr_parser.clients.protocol import create_dsmr_protocol, \
create_dsmr_reader, create_tcp_dsmr_reader create_dsmr_reader, create_tcp_dsmr_reader
-122
View File
@@ -1,122 +0,0 @@
import logging
import fileinput
from dsmr_parser.clients.telegram_buffer import TelegramBuffer
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
from dsmr_parser.objects import Telegram
from dsmr_parser.parsers import TelegramParser
logger = logging.getLogger(__name__)
class FileReader(object):
"""
Filereader to read and parse raw telegram strings from a file and instantiate Telegram objects
for each read telegram.
Usage:
from dsmr_parser import telegram_specifications
from dsmr_parser.clients.filereader import FileReader
if __name__== "__main__":
infile = '/data/smartmeter/readings.txt'
file_reader = FileReader(
file = infile,
telegram_specification = telegram_specifications.V4
)
for telegram in file_reader.read_as_object():
print(telegram)
The file can be created like:
from dsmr_parser import telegram_specifications
from dsmr_parser.clients import SerialReader, SERIAL_SETTINGS_V5
if __name__== "__main__":
outfile = '/data/smartmeter/readings.txt'
serial_reader = SerialReader(
device='/dev/ttyUSB0',
serial_settings=SERIAL_SETTINGS_V5,
telegram_specification=telegram_specifications.V4
)
for telegram in serial_reader.read_as_object():
f=open(outfile,"ab+")
f.write(telegram._telegram_data.encode())
f.close()
"""
def __init__(self, file, telegram_specification):
self._file = file
self.telegram_parser = TelegramParser(telegram_specification)
self.telegram_buffer = TelegramBuffer()
self.telegram_specification = telegram_specification
def read_as_object(self):
"""
Read complete DSMR telegram's from a file and return a Telegram object.
:rtype: generator
"""
with open(self._file,"rb") as file_handle:
while True:
data = file_handle.readline()
str = data.decode()
self.telegram_buffer.append(str)
for telegram in self.telegram_buffer.get_all():
try:
yield Telegram(telegram, self.telegram_parser, self.telegram_specification)
except InvalidChecksumError as e:
logger.warning(str(e))
except ParseError as e:
logger.error('Failed to parse telegram: %s', e)
class FileInputReader(object):
"""
Filereader to read and parse raw telegram strings from stdin or files specified at the commandline
and instantiate Telegram objects for each read telegram.
Usage python script "syphon_smartmeter_readings_stdin.py":
from dsmr_parser import telegram_specifications
from dsmr_parser.clients.filereader import FileInputReader
if __name__== "__main__":
fileinput_reader = FileReader(
file = infile,
telegram_specification = telegram_specifications.V4
)
for telegram in fileinput_reader.read_as_object():
print(telegram)
Command line:
tail -f /data/smartmeter/readings.txt | python3 syphon_smartmeter_readings_stdin.py
"""
def __init__(self, telegram_specification):
self.telegram_parser = TelegramParser(telegram_specification)
self.telegram_buffer = TelegramBuffer()
self.telegram_specification = telegram_specification
def read_as_object(self):
"""
Read complete DSMR telegram's from stdin of filearguments specified on teh command line
and return a Telegram object.
:rtype: generator
"""
with fileinput.input(mode='rb') as file_handle:
while True:
data = file_handle.readline()
str = data.decode()
self.telegram_buffer.append(str)
for telegram in self.telegram_buffer.get_all():
try:
yield Telegram(telegram, self.telegram_parser, self.telegram_specification)
except InvalidChecksumError as e:
logger.warning(str(e))
except ParseError as e:
logger.error('Failed to parse telegram: %s', e)
+4 -13
View File
@@ -8,10 +8,10 @@ from serial_asyncio import create_serial_connection
from dsmr_parser import telegram_specifications from dsmr_parser import telegram_specifications
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
from dsmr_parser.parsers import TelegramParser from dsmr_parser.parsers import TelegramParser
from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \ from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \
SERIAL_SETTINGS_V4, SERIAL_SETTINGS_V5 SERIAL_SETTINGS_V4
def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None): def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None):
@@ -23,12 +23,6 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None):
elif dsmr_version == '4': elif dsmr_version == '4':
specification = telegram_specifications.V4 specification = telegram_specifications.V4
serial_settings = SERIAL_SETTINGS_V4 serial_settings = SERIAL_SETTINGS_V4
elif dsmr_version == '5':
specification = telegram_specifications.V5
serial_settings = SERIAL_SETTINGS_V5
elif dsmr_version == '5B':
specification = telegram_specifications.BELGIUM_FLUVIUS
serial_settings = SERIAL_SETTINGS_V5
else: else:
raise NotImplementedError("No telegram parser found for version: %s", raise NotImplementedError("No telegram parser found for version: %s",
dsmr_version) dsmr_version)
@@ -52,13 +46,12 @@ def create_dsmr_reader(port, dsmr_version, telegram_callback, loop=None):
def create_tcp_dsmr_reader(host, port, dsmr_version, def create_tcp_dsmr_reader(host, port, dsmr_version,
telegram_callback, loop=None): telegram_callback, loop=None):
"""Creates a DSMR asyncio protocol coroutine using TCP connection.""" """Creates a DSMR asyncio protocol coroutine using TCP connection."""
if not loop:
loop = asyncio.get_event_loop()
protocol, _ = create_dsmr_protocol( protocol, _ = create_dsmr_protocol(
dsmr_version, telegram_callback, loop=loop) dsmr_version, telegram_callback, loop=None)
conn = loop.create_connection(protocol, host, port) conn = loop.create_connection(protocol, host, port)
return conn return conn
class DSMRProtocol(asyncio.Protocol): class DSMRProtocol(asyncio.Protocol):
"""Assemble and handle incoming data into complete DSM telegrams.""" """Assemble and handle incoming data into complete DSM telegrams."""
@@ -105,8 +98,6 @@ class DSMRProtocol(asyncio.Protocol):
try: try:
parsed_telegram = self.telegram_parser.parse(telegram) parsed_telegram = self.telegram_parser.parse(telegram)
except InvalidChecksumError as e:
self.log.warning(str(e))
except ParseError: except ParseError:
self.log.exception("failed to parse telegram") self.log.exception("failed to parse telegram")
else: else:
+2 -25
View File
@@ -4,9 +4,8 @@ import serial
import serial_asyncio 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
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__)
@@ -21,32 +20,12 @@ class SerialReader(object):
self.telegram_parser = TelegramParser(telegram_specification) self.telegram_parser = TelegramParser(telegram_specification)
self.telegram_buffer = TelegramBuffer() self.telegram_buffer = TelegramBuffer()
self.telegram_specification = telegram_specification
def read(self): def read(self):
""" """
Read complete DSMR telegram's from the serial interface and parse it Read complete DSMR telegram's from the serial interface and parse it
into CosemObject's and MbusObject's into CosemObject's and MbusObject's
:rtype: generator
"""
with serial.Serial(**self.serial_settings) as serial_handle:
while True:
data = serial_handle.read(max(1, min(1024, serial_handle.in_waiting)))
self.telegram_buffer.append(data.decode('ascii'))
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)
def read_as_object(self):
"""
Read complete DSMR telegram's from the serial interface and return a Telegram object.
:rtype: generator :rtype: generator
""" """
with serial.Serial(**self.serial_settings) as serial_handle: with serial.Serial(**self.serial_settings) as serial_handle:
@@ -56,9 +35,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:
logger.warning(str(e))
except ParseError as e: except ParseError as e:
logger.error('Failed to parse telegram: %s', e) logger.error('Failed to parse telegram: %s', e)
-10
View File
@@ -20,13 +20,3 @@ SERIAL_SETTINGS_V4 = {
'rtscts': 0, 'rtscts': 0,
'timeout': 20 'timeout': 20
} }
SERIAL_SETTINGS_V5 = {
'baudrate': 115200,
'bytesize': serial.EIGHTBITS,
'parity': serial.PARITY_NONE,
'stopbits': serial.STOPBITS_ONE,
'xonxoff': 0,
'rtscts': 0,
'timeout': 20
}
+1 -1
View File
@@ -51,7 +51,7 @@ class TelegramBuffer(object):
# - The checksum is optional '{0,4}' because not all telegram versions # - The checksum is optional '{0,4}' because not all telegram versions
# support it. # support it.
return re.findall( return re.findall(
r'\/[^\/]+?\![A-F0-9]{0,4}\0?\r\n', r'\/[^\/]+?\![A-F0-9]{0,4}\r\n',
self._buffer, self._buffer,
re.DOTALL re.DOTALL
) )
+4
View File
@@ -4,3 +4,7 @@ class ParseError(Exception):
class InvalidChecksumError(ParseError): class InvalidChecksumError(ParseError):
pass pass
class TelegramSpecificationMatchError(ParseError):
pass
-54
View File
@@ -1,54 +0,0 @@
from dsmr_parser import obis_references as obis
"""
dsmr_parser.obis_name_mapping
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This module contains a mapping of obis references to names.
"""
EN = {
obis.P1_MESSAGE_HEADER: 'P1_MESSAGE_HEADER',
obis.P1_MESSAGE_TIMESTAMP: 'P1_MESSAGE_TIMESTAMP',
obis.ELECTRICITY_IMPORTED_TOTAL : 'ELECTRICITY_IMPORTED_TOTAL',
obis.ELECTRICITY_USED_TARIFF_1 : 'ELECTRICITY_USED_TARIFF_1',
obis.ELECTRICITY_USED_TARIFF_2 : 'ELECTRICITY_USED_TARIFF_2',
obis.ELECTRICITY_DELIVERED_TARIFF_1 : 'ELECTRICITY_DELIVERED_TARIFF_1',
obis.ELECTRICITY_DELIVERED_TARIFF_2 : 'ELECTRICITY_DELIVERED_TARIFF_2',
obis.ELECTRICITY_ACTIVE_TARIFF : 'ELECTRICITY_ACTIVE_TARIFF',
obis.EQUIPMENT_IDENTIFIER : 'EQUIPMENT_IDENTIFIER',
obis.CURRENT_ELECTRICITY_USAGE : 'CURRENT_ELECTRICITY_USAGE',
obis.CURRENT_ELECTRICITY_DELIVERY : 'CURRENT_ELECTRICITY_DELIVERY',
obis.LONG_POWER_FAILURE_COUNT : 'LONG_POWER_FAILURE_COUNT',
obis.SHORT_POWER_FAILURE_COUNT : 'SHORT_POWER_FAILURE_COUNT',
obis.POWER_EVENT_FAILURE_LOG : 'POWER_EVENT_FAILURE_LOG',
obis.VOLTAGE_SAG_L1_COUNT : 'VOLTAGE_SAG_L1_COUNT',
obis.VOLTAGE_SAG_L2_COUNT : 'VOLTAGE_SAG_L2_COUNT',
obis.VOLTAGE_SAG_L3_COUNT : 'VOLTAGE_SAG_L3_COUNT',
obis.VOLTAGE_SWELL_L1_COUNT : 'VOLTAGE_SWELL_L1_COUNT',
obis.VOLTAGE_SWELL_L2_COUNT : 'VOLTAGE_SWELL_L2_COUNT',
obis.VOLTAGE_SWELL_L3_COUNT : 'VOLTAGE_SWELL_L3_COUNT',
obis.INSTANTANEOUS_VOLTAGE_L1 : 'INSTANTANEOUS_VOLTAGE_L1',
obis.INSTANTANEOUS_VOLTAGE_L2 : 'INSTANTANEOUS_VOLTAGE_L2',
obis.INSTANTANEOUS_VOLTAGE_L3 : 'INSTANTANEOUS_VOLTAGE_L3',
obis.INSTANTANEOUS_CURRENT_L1 : 'INSTANTANEOUS_CURRENT_L1',
obis.INSTANTANEOUS_CURRENT_L2 : 'INSTANTANEOUS_CURRENT_L2',
obis.INSTANTANEOUS_CURRENT_L3 : 'INSTANTANEOUS_CURRENT_L3',
obis.TEXT_MESSAGE_CODE : 'TEXT_MESSAGE_CODE',
obis.TEXT_MESSAGE : 'TEXT_MESSAGE',
obis.DEVICE_TYPE : 'DEVICE_TYPE',
obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE : 'INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE',
obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE : 'INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE',
obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE : 'INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE',
obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE : 'INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE',
obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE : 'INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE',
obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE : 'INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE',
obis.EQUIPMENT_IDENTIFIER_GAS : 'EQUIPMENT_IDENTIFIER_GAS',
obis.HOURLY_GAS_METER_READING : 'HOURLY_GAS_METER_READING',
obis.GAS_METER_READING : 'GAS_METER_READING',
obis.ACTUAL_TRESHOLD_ELECTRICITY : 'ACTUAL_TRESHOLD_ELECTRICITY',
obis.ACTUAL_SWITCH_POSITION : 'ACTUAL_SWITCH_POSITION',
obis.VALVE_POSITION_GAS : 'VALVE_POSITION_GAS'
}
REVERSE_EN = dict([ (v,k) for k,v in EN.items()])
-13
View File
@@ -8,7 +8,6 @@ objects are introduced.
""" """
P1_MESSAGE_HEADER = r'\d-\d:0\.2\.8.+?\r\n' P1_MESSAGE_HEADER = r'\d-\d:0\.2\.8.+?\r\n'
P1_MESSAGE_TIMESTAMP = r'\d-\d:1\.0\.0.+?\r\n' P1_MESSAGE_TIMESTAMP = r'\d-\d:1\.0\.0.+?\r\n'
ELECTRICITY_IMPORTED_TOTAL = r'\d-\d:1\.8\.0.+?\r\n'
ELECTRICITY_USED_TARIFF_1 = r'\d-\d:1\.8\.1.+?\r\n' ELECTRICITY_USED_TARIFF_1 = r'\d-\d:1\.8\.1.+?\r\n'
ELECTRICITY_USED_TARIFF_2 = r'\d-\d:1\.8\.2.+?\r\n' ELECTRICITY_USED_TARIFF_2 = r'\d-\d:1\.8\.2.+?\r\n'
ELECTRICITY_DELIVERED_TARIFF_1 = r'\d-\d:2\.8\.1.+?\r\n' ELECTRICITY_DELIVERED_TARIFF_1 = r'\d-\d:2\.8\.1.+?\r\n'
@@ -18,7 +17,6 @@ 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'96\.7\.9.+?\r\n'
SHORT_POWER_FAILURE_COUNT = r'96\.7\.21.+?\r\n'
POWER_EVENT_FAILURE_LOG = r'99\.97\.0.+?\r\n' POWER_EVENT_FAILURE_LOG = r'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'
@@ -26,12 +24,6 @@ VOLTAGE_SAG_L3_COUNT = r'\d-\d:72\.32\.0.+?\r\n'
VOLTAGE_SWELL_L1_COUNT = r'\d-\d:32\.36\.0.+?\r\n' VOLTAGE_SWELL_L1_COUNT = r'\d-\d:32\.36\.0.+?\r\n'
VOLTAGE_SWELL_L2_COUNT = r'\d-\d:52\.36\.0.+?\r\n' VOLTAGE_SWELL_L2_COUNT = r'\d-\d:52\.36\.0.+?\r\n'
VOLTAGE_SWELL_L3_COUNT = r'\d-\d:72\.36\.0.+?\r\n' VOLTAGE_SWELL_L3_COUNT = r'\d-\d:72\.36\.0.+?\r\n'
INSTANTANEOUS_VOLTAGE_L1 = r'\d-\d:32\.7\.0.+?\r\n'
INSTANTANEOUS_VOLTAGE_L2 = r'\d-\d:52\.7\.0.+?\r\n'
INSTANTANEOUS_VOLTAGE_L3 = r'\d-\d:72\.7\.0.+?\r\n'
INSTANTANEOUS_CURRENT_L1 = r'\d-\d:31\.7\.0.+?\r\n'
INSTANTANEOUS_CURRENT_L2 = r'\d-\d:51\.7\.0.+?\r\n'
INSTANTANEOUS_CURRENT_L3 = r'\d-\d:71\.7\.0.+?\r\n'
TEXT_MESSAGE_CODE = r'\d-\d:96\.13\.1.+?\r\n' TEXT_MESSAGE_CODE = r'\d-\d:96\.13\.1.+?\r\n'
TEXT_MESSAGE = r'\d-\d:96\.13\.0.+?\r\n' TEXT_MESSAGE = r'\d-\d:96\.13\.0.+?\r\n'
DEVICE_TYPE = r'\d-\d:24\.1\.0.+?\r\n' DEVICE_TYPE = r'\d-\d:24\.1\.0.+?\r\n'
@@ -60,8 +52,3 @@ ELECTRICITY_DELIVERED_TARIFF_ALL = (
ELECTRICITY_DELIVERED_TARIFF_1, ELECTRICITY_DELIVERED_TARIFF_1,
ELECTRICITY_DELIVERED_TARIFF_2 ELECTRICITY_DELIVERED_TARIFF_2
) )
# Alternate codes for foreign countries.
BELGIUM_HOURLY_GAS_METER_READING = r'\d-\d:24\.2\.3.+?\r\n' # Different code, same format.
LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL = r'\d-\d:1\.8\.0.+?\r\n' # Total imported energy register (P+)
LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL = r'\d-\d:2\.8\.0.+?\r\n' # Total exported energy register (P-)
-53
View File
@@ -1,56 +1,3 @@
import dsmr_parser.obis_name_mapping
class Telegram(object):
"""
Container for raw and parsed telegram data.
Initializing:
from dsmr_parser import telegram_specifications
from dsmr_parser.exceptions import InvalidChecksumError, ParseError
from dsmr_parser.objects import CosemObject, MBusObject, Telegram
from dsmr_parser.parsers import TelegramParser
from test.example_telegrams import TELEGRAM_V4_2
parser = TelegramParser(telegram_specifications.V4)
telegram = Telegram(TELEGRAM_V4_2, parser, telegram_specifications.V4)
Attributes can be accessed on a telegram object by addressing by their english name, for example:
telegram.ELECTRICITY_USED_TARIFF_1
All attributes in a telegram can be iterated over, for example:
[k for k,v in telegram]
yields:
['P1_MESSAGE_HEADER', 'P1_MESSAGE_TIMESTAMP', 'EQUIPMENT_IDENTIFIER', ...]
"""
def __init__(self, telegram_data, telegram_parser, telegram_specification):
self._telegram_data = telegram_data
self._telegram_specification = telegram_specification
self._telegram_parser = telegram_parser
self._obis_name_mapping = dsmr_parser.obis_name_mapping.EN
self._reverse_obis_name_mapping = dsmr_parser.obis_name_mapping.REVERSE_EN
self._dictionary = self._telegram_parser.parse(telegram_data)
self._item_names = self._get_item_names()
def __getattr__(self, name):
''' will only get called for undefined attributes '''
obis_reference = self._reverse_obis_name_mapping[name]
value = self._dictionary[obis_reference]
setattr(self, name, value)
return value
def _get_item_names(self):
return [self._obis_name_mapping[k] for k, v in self._dictionary.items()]
def __iter__(self):
for attr in self._item_names:
value = getattr(self, attr)
yield attr, value
def __str__(self):
output = ""
for attr, value in self:
output += "{}: \t {} \t[{}]\n".format(attr,str(value.value),str(value.unit))
return output
class DSMRObject(object): class DSMRObject(object):
""" """
Represents all data from a single telegram line. Represents all data from a single telegram line.
+45 -33
View File
@@ -1,19 +1,21 @@
import logging import logging
import re import re
from ctypes import c_ushort from PyCRC.CRC16 import CRC16
from dsmr_parser.objects import MBusObject, CosemObject, Telegram from dsmr_parser.objects import MBusObject, CosemObject
from dsmr_parser.exceptions import ParseError, InvalidChecksumError from dsmr_parser.exceptions import ParseError, InvalidChecksumError, \
TelegramSpecificationMatchError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class TelegramParser(object): class TelegramParser(object):
def __init__(self, telegram_specification, apply_checksum_validation=True): def __init__(self, telegram_specification=None, apply_checksum_validation=True):
""" """
:param telegram_specification: determines how the telegram is parsed :param telegram_specification: determines how the telegram is parsed.
Will attempt to autodetect if omitted.
:param apply_checksum_validation: validate checksum if applicable for :param apply_checksum_validation: validate checksum if applicable for
telegram DSMR version (v4 and up). telegram DSMR version (v4 and up).
:type telegram_specification: dict :type telegram_specification: dict
@@ -39,8 +41,10 @@ class TelegramParser(object):
.. ..
} }
:raises ParseError: :raises ParseError:
:raises InvalidChecksumError:
""" """
if not self.telegram_specification:
self.telegram_specification = \
match_telegram_specification(telegram_data)
if self.apply_checksum_validation \ if self.apply_checksum_validation \
and self.telegram_specification['checksum_support']: and self.telegram_specification['checksum_support']:
@@ -51,10 +55,12 @@ class TelegramParser(object):
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) match = re.search(signature, telegram_data, re.DOTALL)
# Some signatures are optional and may not be present, # All telegram specification lines/signatures are expected to be
# so only parse lines that match # present.
if match: if not match:
telegram[signature] = parser.parse(match.group(0)) raise ParseError('Telegram specification does not match '
'telegram data')
telegram[signature] = parser.parse(match.group(0))
return telegram return telegram
@@ -79,7 +85,7 @@ class TelegramParser(object):
'incomplete. The checksum and/or content values are missing.' 'incomplete. The checksum and/or content values are missing.'
) )
calculated_crc = TelegramParser.crc16(checksum_contents.group(0)) calculated_crc = CRC16().calculate(checksum_contents.group(0))
expected_crc = int(checksum_hex.group(0), base=16) expected_crc = int(checksum_hex.group(0), base=16)
if calculated_crc != expected_crc: if calculated_crc != expected_crc:
@@ -91,28 +97,35 @@ class TelegramParser(object):
) )
) )
@staticmethod
def crc16(telegram):
crc16_tab = []
for i in range(0, 256): def match_telegram_specification(telegram_data):
crc = c_ushort(i).value """
for j in range(0, 8): Find telegram specification that matches the telegram data by trying all
if (crc & 0x0001): specifications.
crc = c_ushort(crc >> 1).value ^ 0xA001
else:
crc = c_ushort(crc >> 1).value
crc16_tab.append(hex(crc))
crcValue = 0x0000 Could be further optimized to check the actual 0.2.8 OBIS reference which
is available for DSMR version 4 and up.
for c in telegram: :param str telegram_data: full telegram from start ('/') to checksum
d = ord(c) ('!ABCD') including line endings in between the telegram's lines
tmp = crcValue ^ d :return: telegram specification
rotated = c_ushort(crcValue >> 8).value :rtype: dict
crcValue = rotated ^ int(crc16_tab[(tmp & 0x00ff)], 0) """
# Prevent circular import
from dsmr_parser import telegram_specifications
return crcValue for specification in telegram_specifications.ALL:
try:
TelegramParser(specification).parse(telegram_data)
except ParseError:
pass
else:
return specification
raise TelegramSpecificationMatchError(
'Could automatically match telegram specification. Make sure the data'
'is not corrupt. Alternatively manually specify one.'
)
class DSMRObjectParser(object): class DSMRObjectParser(object):
@@ -172,11 +185,10 @@ class CosemParser(DSMRObjectParser):
1 23 45 1 23 45
1) OBIS Reduced ID-code 1) OBIS Reduced ID-code
2) Separator "(", ASCII 28h 2) Separator “(“, ASCII 28h
3) COSEM object attribute value 3) COSEM object attribute value
4) Unit of measurement values (Unit of capture objects attribute) - only if 4) Unit of measurement values (Unit of capture objects attribute) – only if applicable
applicable 5) Separator “)”, ASCII 29h
5) Separator ")", ASCII 29h
""" """
def parse(self, line): def parse(self, line):
-24
View File
@@ -1,5 +1,4 @@
from decimal import Decimal from decimal import Decimal
from copy import deepcopy
from dsmr_parser import obis_references as obis from dsmr_parser import obis_references as obis
from dsmr_parser.parsers import CosemParser, ValueParser, MBusParser from dsmr_parser.parsers import CosemParser, ValueParser, MBusParser
@@ -89,7 +88,6 @@ V5 = {
obis.P1_MESSAGE_HEADER: CosemParser(ValueParser(str)), obis.P1_MESSAGE_HEADER: CosemParser(ValueParser(str)),
obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)), obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)),
obis.EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), obis.EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)),
obis.ELECTRICITY_IMPORTED_TOTAL: CosemParser(ValueParser(Decimal)),
obis.ELECTRICITY_USED_TARIFF_1: CosemParser(ValueParser(Decimal)), obis.ELECTRICITY_USED_TARIFF_1: CosemParser(ValueParser(Decimal)),
obis.ELECTRICITY_USED_TARIFF_2: CosemParser(ValueParser(Decimal)), obis.ELECTRICITY_USED_TARIFF_2: CosemParser(ValueParser(Decimal)),
obis.ELECTRICITY_DELIVERED_TARIFF_1: CosemParser(ValueParser(Decimal)), obis.ELECTRICITY_DELIVERED_TARIFF_1: CosemParser(ValueParser(Decimal)),
@@ -98,7 +96,6 @@ V5 = {
obis.CURRENT_ELECTRICITY_USAGE: CosemParser(ValueParser(Decimal)), obis.CURRENT_ELECTRICITY_USAGE: CosemParser(ValueParser(Decimal)),
obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)),
obis.LONG_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), obis.LONG_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)),
obis.SHORT_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)),
# POWER_EVENT_FAILURE_LOG: ProfileGenericParser(), TODO # POWER_EVENT_FAILURE_LOG: ProfileGenericParser(), TODO
obis.VOLTAGE_SAG_L1_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SAG_L1_COUNT: CosemParser(ValueParser(int)),
obis.VOLTAGE_SAG_L2_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SAG_L2_COUNT: CosemParser(ValueParser(int)),
@@ -106,12 +103,6 @@ V5 = {
obis.VOLTAGE_SWELL_L1_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SWELL_L1_COUNT: CosemParser(ValueParser(int)),
obis.VOLTAGE_SWELL_L2_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SWELL_L2_COUNT: CosemParser(ValueParser(int)),
obis.VOLTAGE_SWELL_L3_COUNT: CosemParser(ValueParser(int)), obis.VOLTAGE_SWELL_L3_COUNT: CosemParser(ValueParser(int)),
obis.INSTANTANEOUS_VOLTAGE_L1: CosemParser(ValueParser(Decimal)),
obis.INSTANTANEOUS_VOLTAGE_L2: CosemParser(ValueParser(Decimal)),
obis.INSTANTANEOUS_VOLTAGE_L3: CosemParser(ValueParser(Decimal)),
obis.INSTANTANEOUS_CURRENT_L1: CosemParser(ValueParser(Decimal)),
obis.INSTANTANEOUS_CURRENT_L2: CosemParser(ValueParser(Decimal)),
obis.INSTANTANEOUS_CURRENT_L3: CosemParser(ValueParser(Decimal)),
obis.TEXT_MESSAGE: CosemParser(ValueParser(str)), obis.TEXT_MESSAGE: CosemParser(ValueParser(str)),
obis.DEVICE_TYPE: CosemParser(ValueParser(int)), obis.DEVICE_TYPE: CosemParser(ValueParser(int)),
obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: CosemParser(ValueParser(Decimal)), obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: CosemParser(ValueParser(Decimal)),
@@ -129,18 +120,3 @@ V5 = {
} }
ALL = (V2_2, V3, V4, V5) ALL = (V2_2, V3, V4, V5)
BELGIUM_FLUVIUS = deepcopy(V5)
BELGIUM_FLUVIUS['objects'].update({
obis.BELGIUM_HOURLY_GAS_METER_READING: MBusParser(
ValueParser(timestamp),
ValueParser(Decimal)
)
})
LUXEMBOURG_SMARTY = deepcopy(V5)
LUXEMBOURG_SMARTY['objects'].update({
obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)),
obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)),
})
+3 -2
View File
@@ -4,14 +4,15 @@ setup(
name='dsmr-parser', name='dsmr-parser',
description='Library to parse Dutch Smart Meter Requirements (DSMR)', description='Library to parse Dutch Smart Meter Requirements (DSMR)',
author='Nigel Dokter', author='Nigel Dokter',
author_email='nigel@nldr.net', author_email='nigeldokter@gmail.com',
url='https://github.com/ndokter/dsmr_parser', url='https://github.com/ndokter/dsmr_parser',
version='0.18', version='0.9',
packages=find_packages(), packages=find_packages(),
install_requires=[ install_requires=[
'pyserial>=3,<4', 'pyserial>=3,<4',
'pyserial-asyncio<1', 'pyserial-asyncio<1',
'pytz', 'pytz',
'PyCRC>=1.2,<2'
], ],
entry_points={ entry_points={
'console_scripts': ['dsmr_console=dsmr_parser.__main__:console'] 'console_scripts': ['dsmr_console=dsmr_parser.__main__:console']
-14
View File
@@ -1,14 +0,0 @@
from decimal import Decimal
import datetime
import unittest
import pytz
from dsmr_parser import obis_references as obis
from dsmr_parser import telegram_specifications
from dsmr_parser.exceptions import InvalidChecksumError, ParseError
from dsmr_parser.objects import CosemObject, MBusObject, Telegram
from dsmr_parser.parsers import TelegramParser
from example_telegrams import TELEGRAM_V4_2
parser = TelegramParser(telegram_specifications.V4)
telegram = Telegram(TELEGRAM_V4_2, parser, telegram_specifications.V4)
print(telegram)
+29
View File
@@ -0,0 +1,29 @@
import unittest
from dsmr_parser.exceptions import TelegramSpecificationMatchError
from dsmr_parser.parsers import match_telegram_specification
from dsmr_parser import telegram_specifications
from test import example_telegrams
class MatchTelegramSpecificationTest(unittest.TestCase):
def test_v2_2(self):
assert match_telegram_specification(example_telegrams.TELEGRAM_V2_2) \
== telegram_specifications.V2_2
def test_v3(self):
assert match_telegram_specification(example_telegrams.TELEGRAM_V3) \
== telegram_specifications.V3
def test_v4_2(self):
assert match_telegram_specification(example_telegrams.TELEGRAM_V4_2) \
== telegram_specifications.V4
def test_v5(self):
assert match_telegram_specification(example_telegrams.TELEGRAM_V5) \
== telegram_specifications.V5
def test_malformed_telegram(self):
with self.assertRaises(TelegramSpecificationMatchError):
match_telegram_specification(example_telegrams.TELEGRAM_V5[:-4])
+9
View File
@@ -12,6 +12,15 @@ from test.example_telegrams import TELEGRAM_V2_2
class TelegramParserV2_2Test(unittest.TestCase): class TelegramParserV2_2Test(unittest.TestCase):
""" Test parsing of a DSMR v2.2 telegram. """ """ Test parsing of a DSMR v2.2 telegram. """
def test_telegram_specification_matching(self):
parser = TelegramParser()
parser.parse(TELEGRAM_V2_2)
self.assertEqual(
parser.telegram_specification,
telegram_specifications.V2_2
)
def test_parse(self): def test_parse(self):
parser = TelegramParser(telegram_specifications.V2_2) parser = TelegramParser(telegram_specifications.V2_2)
result = parser.parse(TELEGRAM_V2_2) result = parser.parse(TELEGRAM_V2_2)
+9
View File
@@ -12,6 +12,15 @@ from test.example_telegrams import TELEGRAM_V3
class TelegramParserV3Test(unittest.TestCase): class TelegramParserV3Test(unittest.TestCase):
""" Test parsing of a DSMR v3 telegram. """ """ Test parsing of a DSMR v3 telegram. """
def test_telegram_specification_matching(self):
parser = TelegramParser()
parser.parse(TELEGRAM_V3)
self.assertEqual(
parser.telegram_specification,
telegram_specifications.V3
)
def test_parse(self): def test_parse(self):
parser = TelegramParser(telegram_specifications.V3) parser = TelegramParser(telegram_specifications.V3)
result = parser.parse(TELEGRAM_V3) result = parser.parse(TELEGRAM_V3)
+9
View File
@@ -15,6 +15,15 @@ from test.example_telegrams import TELEGRAM_V4_2
class TelegramParserV4_2Test(unittest.TestCase): class TelegramParserV4_2Test(unittest.TestCase):
""" Test parsing of a DSMR v4.2 telegram. """ """ Test parsing of a DSMR v4.2 telegram. """
def test_telegram_specification_matching(self):
parser = TelegramParser()
parser.parse(TELEGRAM_V4_2)
self.assertEqual(
parser.telegram_specification,
telegram_specifications.V4
)
def test_parse(self): def test_parse(self):
parser = TelegramParser(telegram_specifications.V4) parser = TelegramParser(telegram_specifications.V4)
result = parser.parse(TELEGRAM_V4_2) result = parser.parse(TELEGRAM_V4_2)
+9 -42
View File
@@ -16,6 +16,15 @@ from test.example_telegrams import TELEGRAM_V5
class TelegramParserV5Test(unittest.TestCase): class TelegramParserV5Test(unittest.TestCase):
""" Test parsing of a DSMR v5.x telegram. """ """ Test parsing of a DSMR v5.x telegram. """
def test_telegram_specification_matching(self):
parser = TelegramParser()
parser.parse(TELEGRAM_V5)
self.assertEqual(
parser.telegram_specification,
telegram_specifications.V5
)
def test_parse(self): def test_parse(self):
parser = TelegramParser(telegram_specifications.V5) parser = TelegramParser(telegram_specifications.V5)
result = parser.parse(TELEGRAM_V5) result = parser.parse(TELEGRAM_V5)
@@ -87,12 +96,6 @@ class TelegramParserV5Test(unittest.TestCase):
assert isinstance(result[obis.LONG_POWER_FAILURE_COUNT].value, int) assert isinstance(result[obis.LONG_POWER_FAILURE_COUNT].value, int)
assert result[obis.LONG_POWER_FAILURE_COUNT].value == 0 assert result[obis.LONG_POWER_FAILURE_COUNT].value == 0
# SHORT_POWER_FAILURE_COUNT (1-0:96.7.21)
assert isinstance(result[obis.SHORT_POWER_FAILURE_COUNT], CosemObject)
assert result[obis.SHORT_POWER_FAILURE_COUNT].unit is None
assert isinstance(result[obis.SHORT_POWER_FAILURE_COUNT].value, int)
assert result[obis.SHORT_POWER_FAILURE_COUNT].value == 13
# 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(result[obis.VOLTAGE_SAG_L1_COUNT], CosemObject)
assert result[obis.VOLTAGE_SAG_L1_COUNT].unit is None assert result[obis.VOLTAGE_SAG_L1_COUNT].unit is None
@@ -129,42 +132,6 @@ class TelegramParserV5Test(unittest.TestCase):
assert isinstance(result[obis.VOLTAGE_SWELL_L3_COUNT].value, int) assert isinstance(result[obis.VOLTAGE_SWELL_L3_COUNT].value, int)
assert result[obis.VOLTAGE_SWELL_L3_COUNT].value == 0 assert result[obis.VOLTAGE_SWELL_L3_COUNT].value == 0
# INSTANTANEOUS_VOLTAGE_L1 (1-0:32.7.0)
assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L1], CosemObject)
assert result[obis.INSTANTANEOUS_VOLTAGE_L1].unit == 'V'
assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L1].value, Decimal)
assert result[obis.INSTANTANEOUS_VOLTAGE_L1].value == Decimal('230.0')
# INSTANTANEOUS_VOLTAGE_L2 (1-0:52.7.0)
assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L2], CosemObject)
assert result[obis.INSTANTANEOUS_VOLTAGE_L2].unit == 'V'
assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L2].value, Decimal)
assert result[obis.INSTANTANEOUS_VOLTAGE_L2].value == Decimal('230.0')
# INSTANTANEOUS_VOLTAGE_L3 (1-0:72.7.0)
assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L3], CosemObject)
assert result[obis.INSTANTANEOUS_VOLTAGE_L3].unit == 'V'
assert isinstance(result[obis.INSTANTANEOUS_VOLTAGE_L3].value, Decimal)
assert result[obis.INSTANTANEOUS_VOLTAGE_L3].value == Decimal('229.0')
# INSTANTANEOUS_CURRENT_L1 (1-0:31.7.0)
assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L1], CosemObject)
assert result[obis.INSTANTANEOUS_CURRENT_L1].unit == 'A'
assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L1].value, Decimal)
assert result[obis.INSTANTANEOUS_CURRENT_L1].value == Decimal('0.48')
# INSTANTANEOUS_CURRENT_L2 (1-0:51.7.0)
assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L2], CosemObject)
assert result[obis.INSTANTANEOUS_CURRENT_L2].unit == 'A'
assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L2].value, Decimal)
assert result[obis.INSTANTANEOUS_CURRENT_L2].value == Decimal('0.44')
# INSTANTANEOUS_CURRENT_L3 (1-0:71.7.0)
assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L3], CosemObject)
assert result[obis.INSTANTANEOUS_CURRENT_L3].unit == 'A'
assert isinstance(result[obis.INSTANTANEOUS_CURRENT_L3].value, Decimal)
assert result[obis.INSTANTANEOUS_CURRENT_L3].value == Decimal('0.86')
# TEXT_MESSAGE (0-0:96.13.0) # TEXT_MESSAGE (0-0:96.13.0)
assert isinstance(result[obis.TEXT_MESSAGE], CosemObject) assert isinstance(result[obis.TEXT_MESSAGE], CosemObject)
assert result[obis.TEXT_MESSAGE].unit is None assert result[obis.TEXT_MESSAGE].unit is None
-30
View File
@@ -1,30 +0,0 @@
from decimal import Decimal
import datetime
import unittest
import pytz
from dsmr_parser import obis_references as obis
from dsmr_parser import telegram_specifications
from dsmr_parser.exceptions import InvalidChecksumError, ParseError
from dsmr_parser.objects import CosemObject, MBusObject, Telegram
from dsmr_parser.parsers import TelegramParser
from test.example_telegrams import TELEGRAM_V4_2
class TelegramTest(unittest.TestCase):
""" Test instantiation of Telegram object """
def test_instantiate(self):
parser = TelegramParser(telegram_specifications.V4)
#result = parser.parse(TELEGRAM_V4_2)
telegram = Telegram(TELEGRAM_V4_2, parser, telegram_specifications.V4)
# P1_MESSAGE_HEADER (1-3:0.2.8)
#assert isinstance(result[obis.P1_MESSAGE_HEADER], CosemObject)
#assert result[obis.P1_MESSAGE_HEADER].unit is None
#assert isinstance(result[obis.P1_MESSAGE_HEADER].value, str)
#assert result[obis.P1_MESSAGE_HEADER].value == '50'
+2 -1
View File
@@ -9,12 +9,13 @@ deps=
pytest-asyncio pytest-asyncio
pytest-catchlog pytest-catchlog
pytest-mock pytest-mock
PyCRC
commands= commands=
py.test --cov=dsmr_parser test {posargs} py.test --cov=dsmr_parser test {posargs}
pylama dsmr_parser test pylama dsmr_parser test
[pylama:dsmr_parser/clients/__init__.py] [pylama:dsmr_parser/clients/__init__.py]
ignore = W0611,W0605 ignore = W0611
[pylama:pylint] [pylama:pylint]
max_line_length = 100 max_line_length = 100