Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
819d0d0696 | ||
|
|
a3685f0310 | ||
|
|
5ae6ad4156 | ||
|
|
e330e9db21 | ||
|
|
0ac2990df2 | ||
|
|
e0d91f1ec4 | ||
|
|
c058feca5f | ||
|
|
6f3c74ce7c | ||
|
|
43a3cb7a96 | ||
|
|
5e88182301 | ||
|
|
e3569e0719 | ||
|
|
f8a3c76c68 | ||
|
|
a1d077d6f2 | ||
|
|
616db8b1cc | ||
|
|
927a4bc8e7 | ||
|
|
e7ff8f2444 | ||
|
|
f41fe0894c | ||
|
|
59dd0893a4 | ||
|
|
e3c8c92198 | ||
|
|
9363840042 | ||
|
|
4afab70dc0 |
+12
-2
@@ -1,9 +1,19 @@
|
||||
Change Log
|
||||
----------
|
||||
|
||||
**0.4** (2016-11-21)
|
||||
|
||||
- DSMR v2.2 serial settings now uses parity serial.EVEN by default (`pull request #5 <https://github.com/ndokter/dsmr_parser/pull/5>`_)
|
||||
- improved asyncio reader and improve it's error handling (`pull request #8 <https://github.com/ndokter/dsmr_parser/pull/8>`_)
|
||||
|
||||
**0.3** (2016-11-12)
|
||||
|
||||
- asyncio reader for non-blocking reads. (`pull request #3 <https://github.com/ndokter/dsmr_parser/pull/3>`_)
|
||||
|
||||
**0.2** (2016-11-08)
|
||||
|
||||
- User https://github.com/aequitas added support for DMSR version 2.2
|
||||
- support for DMSR version 2.2 (`pull request #2 <https://github.com/ndokter/dsmr_parser/pull/2>`_)
|
||||
|
||||
**0.1** (2016-08-22)
|
||||
|
||||
- Initial version with a serial reader and support for DSMR version 4.x
|
||||
- initial version with a serial reader and support for DSMR version 4.x
|
||||
|
||||
+17
-8
@@ -4,6 +4,9 @@ DSMR Parser
|
||||
.. image:: https://img.shields.io/pypi/v/dsmr-parser.svg
|
||||
:target: https://pypi.python.org/pypi/dsmr-parser
|
||||
|
||||
.. image:: https://travis-ci.org/ndokter/dsmr_parser.svg?branch=master
|
||||
: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.
|
||||
|
||||
@@ -11,7 +14,7 @@ also includes a serial client to directly read and parse smart meter data.
|
||||
Features
|
||||
--------
|
||||
|
||||
DSMR Parser currently supports DSMR versions 2.2 and 4.x. It has been tested with Python 3.5
|
||||
DSMR Parser currently supports DSMR versions 2.2 and 4.x. It has been tested with Python 3.4 and 3.5.
|
||||
|
||||
|
||||
Examples
|
||||
@@ -22,7 +25,7 @@ Using the serial reader to connect to your smart meter and parse it's telegrams:
|
||||
.. code-block:: python
|
||||
|
||||
from dsmr_parser import telegram_specifications
|
||||
from dsmr_parser.obis_references import P1_MESSAGE_TIMESTAMP
|
||||
from dsmr_parser import obis_references
|
||||
from dsmr_parser.serial import SerialReader, SERIAL_SETTINGS_V4
|
||||
|
||||
serial_reader = SerialReader(
|
||||
@@ -34,19 +37,19 @@ Using the serial reader to connect to your smart meter and parse it's telegrams:
|
||||
for telegram in serial_reader.read():
|
||||
|
||||
# The telegram message timestamp.
|
||||
message_datetime = telegram[P1_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.
|
||||
tariff = telegram[ELECTRICITY_ACTIVE_TARIFF]
|
||||
tariff = telegram[obis_references.ELECTRICITY_ACTIVE_TARIFF]
|
||||
tariff = int(tariff.value)
|
||||
|
||||
electricity_used_total \
|
||||
= telegram[ELECTRICITY_USED_TARIFF_ALL[tariff - 1]]
|
||||
= telegram[obis_references.ELECTRICITY_USED_TARIFF_ALL[tariff - 1]]
|
||||
electricity_delivered_total = \
|
||||
telegram[ELECTRICITY_DELIVERED_TARIFF_ALL[tariff - 1]]
|
||||
telegram[obis_referencesELECTRICITY_DELIVERED_TARIFF_ALL[tariff - 1]]
|
||||
|
||||
gas_reading = telegram[HOURLY_GAS_METER_READING]
|
||||
gas_reading = telegram[obis_references.HOURLY_GAS_METER_READING]
|
||||
|
||||
# See dsmr_reader.obis_references for all readable telegram values.
|
||||
|
||||
@@ -60,10 +63,16 @@ To install DSMR Parser:
|
||||
|
||||
$ pip install dsmr-parser
|
||||
|
||||
Known issues
|
||||
------------
|
||||
|
||||
If the serial settings SERIAL_SETTINGS_V2_2 or SERIAL_SETTINGS_V4 don't work.
|
||||
Make sure to try and replace the parity settings to EVEN or NONE.
|
||||
It's possible that alternative settings will be added in the future if these
|
||||
settings don't work for the majority of meters.
|
||||
|
||||
TODO
|
||||
----
|
||||
|
||||
- add unit tests
|
||||
- verify telegram checksum
|
||||
- improve ease of use
|
||||
|
||||
+18
-12
@@ -1,6 +1,8 @@
|
||||
import argparse
|
||||
from dsmr_parser.serial import SERIAL_SETTINGS_V2_2, SERIAL_SETTINGS_V4, SerialReader
|
||||
from dsmr_parser import telegram_specifications
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from .protocol import create_dsmr_reader
|
||||
|
||||
|
||||
def console():
|
||||
@@ -11,22 +13,26 @@ def console():
|
||||
help='port to read DSMR data from')
|
||||
parser.add_argument('--version', default='2.2', choices=['2.2', '4'],
|
||||
help='DSMR version (2.2, 4)')
|
||||
parser.add_argument('--verbose', '-v', action='count')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
settings = {
|
||||
'2.2': (SERIAL_SETTINGS_V2_2, telegram_specifications.V2_2),
|
||||
'4': (SERIAL_SETTINGS_V4, telegram_specifications.V4),
|
||||
}
|
||||
if args.verbose:
|
||||
level = logging.DEBUG
|
||||
else:
|
||||
level = logging.ERROR
|
||||
logging.basicConfig(level=level)
|
||||
|
||||
serial_reader = SerialReader(
|
||||
device=args.device,
|
||||
serial_settings=settings[args.version][0],
|
||||
telegram_specification=settings[args.version][1],
|
||||
)
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
for telegram in serial_reader.read():
|
||||
def print_callback(telegram):
|
||||
"""Callback that prints telegram values."""
|
||||
for obiref, obj in telegram.items():
|
||||
if obj:
|
||||
print(obj.value, obj.unit)
|
||||
print()
|
||||
|
||||
conn = create_dsmr_reader(args.device, args.version, print_callback, loop=loop)
|
||||
|
||||
loop.create_task(conn)
|
||||
loop.run_forever()
|
||||
|
||||
@@ -36,7 +36,7 @@ class TelegramParser(object):
|
||||
return telegram
|
||||
|
||||
def parse_line(self, line_value):
|
||||
logger.debug('Parsing line\'%s\'', line_value)
|
||||
logger.debug('Parsing line \'%s\'', line_value)
|
||||
|
||||
obis_reference, parser = self._find_line_parser(line_value)
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
"""Asyncio protocol implementation for handling telegrams."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from functools import partial
|
||||
|
||||
from serial_asyncio import create_serial_connection
|
||||
|
||||
from . import telegram_specifications
|
||||
from .exceptions import ParseError
|
||||
from .parsers import TelegramParser, TelegramParserV2_2
|
||||
from .serial import (SERIAL_SETTINGS_V2_2, SERIAL_SETTINGS_V4,
|
||||
is_end_of_telegram, is_start_of_telegram)
|
||||
|
||||
|
||||
def create_dsmr_reader(port, dsmr_version, telegram_callback, loop=None):
|
||||
"""Creates a DSMR asyncio protocol coroutine."""
|
||||
|
||||
if dsmr_version == '2.2':
|
||||
specifications = telegram_specifications.V2_2
|
||||
telegram_parser = TelegramParserV2_2
|
||||
serial_settings = SERIAL_SETTINGS_V2_2
|
||||
elif dsmr_version == '4':
|
||||
specifications = telegram_specifications.V4
|
||||
telegram_parser = TelegramParser
|
||||
serial_settings = SERIAL_SETTINGS_V4
|
||||
|
||||
serial_settings['url'] = port
|
||||
|
||||
protocol = partial(DSMRProtocol, loop, telegram_parser(specifications),
|
||||
telegram_callback=telegram_callback)
|
||||
|
||||
conn = create_serial_connection(loop, protocol, **serial_settings)
|
||||
|
||||
return conn
|
||||
|
||||
|
||||
class DSMRProtocol(asyncio.Protocol):
|
||||
"""Assemble and handle incoming data into complete DSM telegrams."""
|
||||
|
||||
transport = None
|
||||
telegram_callback = None
|
||||
|
||||
def __init__(self, loop, telegram_parser, telegram_callback=None):
|
||||
"""Initialize class."""
|
||||
self.loop = loop
|
||||
self.log = logging.getLogger(__name__)
|
||||
self.telegram_parser = telegram_parser
|
||||
# callback to call on complete telegram
|
||||
self.telegram_callback = telegram_callback
|
||||
# buffer to keep incoming telegram lines
|
||||
self.telegram = []
|
||||
# buffer to keep incomplete incoming data
|
||||
self.buffer = ''
|
||||
|
||||
def connection_made(self, transport):
|
||||
"""Just logging for now."""
|
||||
self.transport = transport
|
||||
self.log.debug('connected')
|
||||
|
||||
def data_received(self, data):
|
||||
"""Add incoming data to buffer."""
|
||||
data = data.decode()
|
||||
self.log.debug('received data: %s', data.strip())
|
||||
self.buffer += data
|
||||
self.handle_lines()
|
||||
|
||||
def handle_lines(self):
|
||||
"""Assemble incoming data into single lines."""
|
||||
while "\r\n" in self.buffer:
|
||||
line, self.buffer = self.buffer.split("\r\n", 1)
|
||||
self.log.debug('got line: %s', line)
|
||||
|
||||
# Telegrams need to be complete because the values belong to a
|
||||
# particular reading and can also be related to eachother.
|
||||
if not self.telegram and not is_start_of_telegram(line):
|
||||
continue
|
||||
|
||||
self.telegram.append(line)
|
||||
if is_end_of_telegram(line):
|
||||
try:
|
||||
parsed_telegram = self.telegram_parser.parse(self.telegram)
|
||||
self.handle_telegram(parsed_telegram)
|
||||
except ParseError:
|
||||
self.log.exception("failed to parse telegram")
|
||||
self.telegram = []
|
||||
|
||||
def connection_lost(self, exc):
|
||||
"""Stop when connection is lost."""
|
||||
self.log.error('disconnected')
|
||||
|
||||
def handle_telegram(self, telegram):
|
||||
"""Send off parsed telegram to handling callback."""
|
||||
self.log.debug('got telegram: %s', telegram)
|
||||
|
||||
if self.telegram_callback:
|
||||
self.telegram_callback(telegram)
|
||||
+58
-2
@@ -1,11 +1,19 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import serial
|
||||
|
||||
import serial_asyncio
|
||||
from dsmr_parser.exceptions import ParseError
|
||||
from dsmr_parser.parsers import TelegramParser, TelegramParserV2_2
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
SERIAL_SETTINGS_V2_2 = {
|
||||
'baudrate': 9600,
|
||||
'bytesize': serial.SEVENBITS,
|
||||
'parity': serial.PARITY_NONE,
|
||||
'parity': serial.PARITY_EVEN,
|
||||
'stopbits': serial.STOPBITS_ONE,
|
||||
'xonxoff': 0,
|
||||
'rtscts': 0,
|
||||
@@ -33,9 +41,11 @@ def is_end_of_telegram(line):
|
||||
|
||||
class SerialReader(object):
|
||||
|
||||
PORT_KEY = 'port'
|
||||
|
||||
def __init__(self, device, serial_settings, telegram_specification):
|
||||
self.serial_settings = serial_settings
|
||||
self.serial_settings['port'] = device
|
||||
self.serial_settings[self.PORT_KEY] = device
|
||||
|
||||
if serial_settings is SERIAL_SETTINGS_V2_2:
|
||||
telegram_parser = TelegramParserV2_2
|
||||
@@ -67,3 +77,49 @@ class SerialReader(object):
|
||||
if is_end_of_telegram(line):
|
||||
yield self.telegram_parser.parse(telegram)
|
||||
telegram = []
|
||||
|
||||
|
||||
class AsyncSerialReader(SerialReader):
|
||||
"""Serial reader using asyncio pyserial."""
|
||||
|
||||
PORT_KEY = 'url'
|
||||
|
||||
@asyncio.coroutine
|
||||
def read(self, queue):
|
||||
"""
|
||||
Read complete DSMR telegram's from the serial interface and parse it
|
||||
into CosemObject's and MbusObject's.
|
||||
|
||||
Instead of being a generator, values are pushed to provided queue for
|
||||
asynchronous processing.
|
||||
|
||||
:rtype Generator/Async
|
||||
"""
|
||||
# create Serial StreamReader
|
||||
conn = serial_asyncio.open_serial_connection(**self.serial_settings)
|
||||
reader, _ = yield from conn
|
||||
|
||||
telegram = []
|
||||
|
||||
while True:
|
||||
# read line if available or give control back to loop until
|
||||
# new data has arrived
|
||||
line = yield from reader.readline()
|
||||
line = line.decode('ascii')
|
||||
|
||||
# Telegrams need to be complete because the values belong to a
|
||||
# particular reading and can also be related to eachother.
|
||||
if not telegram and not is_start_of_telegram(line):
|
||||
continue
|
||||
|
||||
telegram.append(line)
|
||||
|
||||
if is_end_of_telegram(line):
|
||||
try:
|
||||
parsed_telegram = self.telegram_parser.parse(telegram)
|
||||
# push new parsed telegram onto queue
|
||||
queue.put_nowait(parsed_telegram)
|
||||
except ParseError:
|
||||
logger.exception("failed to parse telegram")
|
||||
|
||||
telegram = []
|
||||
|
||||
@@ -6,10 +6,11 @@ setup(
|
||||
author='Nigel Dokter',
|
||||
author_email='nigeldokter@gmail.com',
|
||||
url='https://github.com/ndokter/dsmr_parser',
|
||||
version='0.2',
|
||||
version='0.4',
|
||||
packages=find_packages(),
|
||||
install_requires=[
|
||||
'pyserial>=3,<4',
|
||||
'pyserial-asyncio<1',
|
||||
'pytz'
|
||||
],
|
||||
entry_points={
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""Test telegram parsing."""
|
||||
"""Test parsing of a DSMR v2.2 telegram."""
|
||||
|
||||
from dsmr_parser.parsers import TelegramParserV2_2
|
||||
from dsmr_parser import telegram_specifications
|
||||
from dsmr_parser.obis_references import CURRENT_ELECTRICITY_USAGE, GAS_METER_READING
|
||||
from dsmr_parser import obis_references as obis
|
||||
|
||||
TELEGRAM_V2_2 = [
|
||||
"/ISk5\2MT382-1004",
|
||||
@@ -28,13 +28,14 @@ TELEGRAM_V2_2 = [
|
||||
]
|
||||
|
||||
|
||||
def test_parse_v2_2():
|
||||
def test_parse():
|
||||
"""Test if telegram parsing results in correct results."""
|
||||
|
||||
parser = TelegramParserV2_2(telegram_specifications.V2_2)
|
||||
result = parser.parse(TELEGRAM_V2_2)
|
||||
|
||||
assert float(result[CURRENT_ELECTRICITY_USAGE].value) == 1.01
|
||||
assert result[CURRENT_ELECTRICITY_USAGE].unit == 'kW'
|
||||
assert float(result[GAS_METER_READING].value) == 1.001
|
||||
assert result[GAS_METER_READING].unit == 'm3'
|
||||
assert float(result[obis.CURRENT_ELECTRICITY_USAGE].value) == 1.01
|
||||
assert result[obis.CURRENT_ELECTRICITY_USAGE].unit == 'kW'
|
||||
|
||||
assert float(result[obis.GAS_METER_READING].value) == 1.001
|
||||
assert result[obis.GAS_METER_READING].unit == 'm3'
|
||||
@@ -0,0 +1,232 @@
|
||||
"""Test parsing of a DSMR v4.2 telegram."""
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
import pytz
|
||||
|
||||
from dsmr_parser import obis_references as obis
|
||||
from dsmr_parser import telegram_specifications
|
||||
from dsmr_parser.objects import CosemObject, MBusObject
|
||||
from dsmr_parser.parsers import TelegramParser
|
||||
|
||||
TELEGRAM_V4_2 = [
|
||||
'1-3:0.2.8(42)',
|
||||
'0-0:1.0.0(161113205757W)',
|
||||
'0-0:96.1.1(1231231231231231231231231231231231)',
|
||||
'1-0:1.8.1(001511.267*kWh)',
|
||||
'1-0:1.8.2(001265.173*kWh)',
|
||||
'1-0:2.8.1(000000.000*kWh)',
|
||||
'1-0:2.8.2(000000.000*kWh)',
|
||||
'0-0:96.14.0(0001)',
|
||||
'1-0:1.7.0(00.235*kW)',
|
||||
'1-0:2.7.0(00.000*kW)',
|
||||
'0-0:96.7.21(00015)',
|
||||
'0-0:96.7.9(00007)',
|
||||
('1-0:99.97.0(3)(0-0:96.7.19)(000103180420W)(0000237126*s)'
|
||||
'(000101000001W)(2147483647*s)(000101000001W)(2147483647*s)'),
|
||||
'1-0:32.32.0(00000)',
|
||||
'1-0:52.32.0(00000)',
|
||||
'1-0:72.32.0(00000)',
|
||||
'1-0:32.36.0(00000)',
|
||||
'1-0:52.36.0(00000)',
|
||||
'1-0:72.36.0(00000)',
|
||||
'0-0:96.13.1()',
|
||||
'0-0:96.13.0()',
|
||||
'1-0:31.7.0(000*A)',
|
||||
'1-0:51.7.0(000*A)',
|
||||
'1-0:71.7.0(000*A)',
|
||||
'1-0:21.7.0(00.095*kW)',
|
||||
'1-0:22.7.0(00.000*kW)',
|
||||
'1-0:41.7.0(00.025*kW)',
|
||||
'1-0:42.7.0(00.000*kW)',
|
||||
'1-0:61.7.0(00.115*kW)',
|
||||
'1-0:62.7.0(00.000*kW)',
|
||||
'0-1:24.1.0(003)',
|
||||
'0-1:96.1.0(3404856892390357246729543587524029)',
|
||||
'0-1:24.2.1(161113200000W)(00915.219*m3)',
|
||||
'!5D83',
|
||||
]
|
||||
|
||||
|
||||
def test_parse():
|
||||
parser = TelegramParser(telegram_specifications.V4)
|
||||
result = parser.parse(TELEGRAM_V4_2)
|
||||
|
||||
# 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 == '42'
|
||||
|
||||
# P1_MESSAGE_TIMESTAMP (0-0:1.0.0)
|
||||
assert isinstance(result[obis.P1_MESSAGE_TIMESTAMP], CosemObject)
|
||||
assert result[obis.P1_MESSAGE_TIMESTAMP].unit is None
|
||||
assert isinstance(result[obis.P1_MESSAGE_TIMESTAMP].value, datetime.datetime)
|
||||
assert result[obis.P1_MESSAGE_TIMESTAMP].value == \
|
||||
datetime.datetime(2016, 11, 13, 19, 57, 57, tzinfo=pytz.UTC)
|
||||
|
||||
# ELECTRICITY_USED_TARIFF_1 (1-0:1.8.1)
|
||||
assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_1], CosemObject)
|
||||
assert result[obis.ELECTRICITY_USED_TARIFF_1].unit == 'kWh'
|
||||
assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_1].value, Decimal)
|
||||
assert result[obis.ELECTRICITY_USED_TARIFF_1].value == Decimal('1511.267')
|
||||
|
||||
# ELECTRICITY_USED_TARIFF_2 (1-0:1.8.2)
|
||||
assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_2], CosemObject)
|
||||
assert result[obis.ELECTRICITY_USED_TARIFF_2].unit == 'kWh'
|
||||
assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_2].value, Decimal)
|
||||
assert result[obis.ELECTRICITY_USED_TARIFF_2].value == Decimal('1265.173')
|
||||
|
||||
# ELECTRICITY_DELIVERED_TARIFF_1 (1-0:2.8.1)
|
||||
assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_1], CosemObject)
|
||||
assert result[obis.ELECTRICITY_DELIVERED_TARIFF_1].unit == 'kWh'
|
||||
assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_1].value, Decimal)
|
||||
assert result[obis.ELECTRICITY_DELIVERED_TARIFF_1].value == Decimal('0')
|
||||
|
||||
# ELECTRICITY_DELIVERED_TARIFF_2 (1-0:2.8.2)
|
||||
assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_2], CosemObject)
|
||||
assert result[obis.ELECTRICITY_DELIVERED_TARIFF_2].unit == 'kWh'
|
||||
assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_2].value, Decimal)
|
||||
assert result[obis.ELECTRICITY_DELIVERED_TARIFF_2].value == Decimal('0')
|
||||
|
||||
# ELECTRICITY_ACTIVE_TARIFF (0-0:96.14.0)
|
||||
assert isinstance(result[obis.ELECTRICITY_ACTIVE_TARIFF], CosemObject)
|
||||
assert result[obis.ELECTRICITY_ACTIVE_TARIFF].unit is None
|
||||
assert isinstance(result[obis.ELECTRICITY_ACTIVE_TARIFF].value, str)
|
||||
assert result[obis.ELECTRICITY_ACTIVE_TARIFF].value == '0001'
|
||||
|
||||
# EQUIPMENT_IDENTIFIER (0-0:96.1.1)
|
||||
assert isinstance(result[obis.EQUIPMENT_IDENTIFIER], CosemObject)
|
||||
assert result[obis.EQUIPMENT_IDENTIFIER].unit is None
|
||||
assert isinstance(result[obis.EQUIPMENT_IDENTIFIER].value, str)
|
||||
assert result[obis.EQUIPMENT_IDENTIFIER].value == '1231231231231231231231231231231231'
|
||||
|
||||
# CURRENT_ELECTRICITY_USAGE (1-0:1.7.0)
|
||||
assert isinstance(result[obis.CURRENT_ELECTRICITY_USAGE], CosemObject)
|
||||
assert result[obis.CURRENT_ELECTRICITY_USAGE].unit == 'kW'
|
||||
assert isinstance(result[obis.CURRENT_ELECTRICITY_USAGE].value, Decimal)
|
||||
assert result[obis.CURRENT_ELECTRICITY_USAGE].value == Decimal('0.235')
|
||||
|
||||
# CURRENT_ELECTRICITY_DELIVERY (1-0:2.7.0)
|
||||
assert isinstance(result[obis.CURRENT_ELECTRICITY_DELIVERY], CosemObject)
|
||||
assert result[obis.CURRENT_ELECTRICITY_DELIVERY].unit == 'kW'
|
||||
assert isinstance(result[obis.CURRENT_ELECTRICITY_DELIVERY].value, Decimal)
|
||||
assert result[obis.CURRENT_ELECTRICITY_DELIVERY].value == Decimal('0')
|
||||
|
||||
# LONG_POWER_FAILURE_COUNT (96.7.9)
|
||||
assert isinstance(result[obis.LONG_POWER_FAILURE_COUNT], CosemObject)
|
||||
assert result[obis.LONG_POWER_FAILURE_COUNT].unit is None
|
||||
assert isinstance(result[obis.LONG_POWER_FAILURE_COUNT].value, int)
|
||||
assert result[obis.LONG_POWER_FAILURE_COUNT].value == 7
|
||||
|
||||
# VOLTAGE_SAG_L1_COUNT (1-0:32.32.0)
|
||||
assert isinstance(result[obis.VOLTAGE_SAG_L1_COUNT], CosemObject)
|
||||
assert result[obis.VOLTAGE_SAG_L1_COUNT].unit is None
|
||||
assert isinstance(result[obis.VOLTAGE_SAG_L1_COUNT].value, int)
|
||||
assert result[obis.VOLTAGE_SAG_L1_COUNT].value == 0
|
||||
|
||||
# VOLTAGE_SAG_L2_COUNT (1-0:52.32.0)
|
||||
assert isinstance(result[obis.VOLTAGE_SAG_L2_COUNT], CosemObject)
|
||||
assert result[obis.VOLTAGE_SAG_L2_COUNT].unit is None
|
||||
assert isinstance(result[obis.VOLTAGE_SAG_L2_COUNT].value, int)
|
||||
assert result[obis.VOLTAGE_SAG_L2_COUNT].value == 0
|
||||
|
||||
# VOLTAGE_SAG_L3_COUNT (1-0:72.32.0)
|
||||
assert isinstance(result[obis.VOLTAGE_SAG_L3_COUNT], CosemObject)
|
||||
assert result[obis.VOLTAGE_SAG_L3_COUNT].unit is None
|
||||
assert isinstance(result[obis.VOLTAGE_SAG_L3_COUNT].value, int)
|
||||
assert result[obis.VOLTAGE_SAG_L3_COUNT].value == 0
|
||||
|
||||
# VOLTAGE_SWELL_L1_COUNT (1-0:32.36.0)
|
||||
assert isinstance(result[obis.VOLTAGE_SWELL_L1_COUNT], CosemObject)
|
||||
assert result[obis.VOLTAGE_SWELL_L1_COUNT].unit is None
|
||||
assert isinstance(result[obis.VOLTAGE_SWELL_L1_COUNT].value, int)
|
||||
assert result[obis.VOLTAGE_SWELL_L1_COUNT].value == 0
|
||||
|
||||
# VOLTAGE_SWELL_L2_COUNT (1-0:52.36.0)
|
||||
assert isinstance(result[obis.VOLTAGE_SWELL_L2_COUNT], CosemObject)
|
||||
assert result[obis.VOLTAGE_SWELL_L2_COUNT].unit is None
|
||||
assert isinstance(result[obis.VOLTAGE_SWELL_L2_COUNT].value, int)
|
||||
assert result[obis.VOLTAGE_SWELL_L2_COUNT].value == 0
|
||||
|
||||
# VOLTAGE_SWELL_L3_COUNT (1-0:72.36.0)
|
||||
assert isinstance(result[obis.VOLTAGE_SWELL_L3_COUNT], CosemObject)
|
||||
assert result[obis.VOLTAGE_SWELL_L3_COUNT].unit is None
|
||||
assert isinstance(result[obis.VOLTAGE_SWELL_L3_COUNT].value, int)
|
||||
assert result[obis.VOLTAGE_SWELL_L3_COUNT].value == 0
|
||||
|
||||
# TEXT_MESSAGE_CODE (0-0:96.13.1)
|
||||
assert isinstance(result[obis.TEXT_MESSAGE_CODE], CosemObject)
|
||||
assert result[obis.TEXT_MESSAGE_CODE].unit is None
|
||||
assert result[obis.TEXT_MESSAGE_CODE].value is None
|
||||
|
||||
# TEXT_MESSAGE (0-0:96.13.0)
|
||||
assert isinstance(result[obis.TEXT_MESSAGE], CosemObject)
|
||||
assert result[obis.TEXT_MESSAGE].unit is None
|
||||
assert result[obis.TEXT_MESSAGE].value is None
|
||||
|
||||
# DEVICE_TYPE (0-x:24.1.0)
|
||||
assert isinstance(result[obis.TEXT_MESSAGE], CosemObject)
|
||||
assert result[obis.DEVICE_TYPE].unit is None
|
||||
assert isinstance(result[obis.DEVICE_TYPE].value, int)
|
||||
assert result[obis.DEVICE_TYPE].value == 3
|
||||
|
||||
# INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE (1-0:21.7.0)
|
||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE], CosemObject)
|
||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE].unit == 'kW'
|
||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE].value, Decimal)
|
||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE].value == Decimal('0.095')
|
||||
|
||||
# INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE (1-0:41.7.0)
|
||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE], CosemObject)
|
||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE].unit == 'kW'
|
||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE].value, Decimal)
|
||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE].value == Decimal('0.025')
|
||||
|
||||
# INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE (1-0:61.7.0)
|
||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE], CosemObject)
|
||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE].unit == 'kW'
|
||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE].value, Decimal)
|
||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE].value == Decimal('0.115')
|
||||
|
||||
# INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE (1-0:22.7.0)
|
||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE], CosemObject)
|
||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE].unit == 'kW'
|
||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE].value, Decimal)
|
||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE].value == Decimal('0')
|
||||
|
||||
# INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE (1-0:42.7.0)
|
||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE], CosemObject)
|
||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE].unit == 'kW'
|
||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE].value, Decimal)
|
||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE].value == Decimal('0')
|
||||
|
||||
# INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE (1-0:62.7.0)
|
||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE], CosemObject)
|
||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE].unit == 'kW'
|
||||
assert isinstance(result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE].value, Decimal)
|
||||
assert result[obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE].value == Decimal('0')
|
||||
|
||||
# EQUIPMENT_IDENTIFIER_GAS (0-x:96.1.0)
|
||||
assert isinstance(result[obis.EQUIPMENT_IDENTIFIER_GAS], CosemObject)
|
||||
assert result[obis.EQUIPMENT_IDENTIFIER_GAS].unit is None
|
||||
assert isinstance(result[obis.EQUIPMENT_IDENTIFIER_GAS].value, str)
|
||||
assert result[obis.EQUIPMENT_IDENTIFIER_GAS].value == '3404856892390357246729543587524029'
|
||||
|
||||
# HOURLY_GAS_METER_READING (0-1:24.2.1)
|
||||
assert isinstance(result[obis.HOURLY_GAS_METER_READING], MBusObject)
|
||||
assert result[obis.HOURLY_GAS_METER_READING].unit == 'm3'
|
||||
assert isinstance(result[obis.HOURLY_GAS_METER_READING].value, Decimal)
|
||||
assert result[obis.HOURLY_GAS_METER_READING].value == Decimal('915.219')
|
||||
|
||||
# POWER_EVENT_FAILURE_LOG (99.97.0)
|
||||
# TODO to be implemented
|
||||
|
||||
# ACTUAL_TRESHOLD_ELECTRICITY (0-0:17.0.0)
|
||||
# TODO to be implemented
|
||||
|
||||
# ACTUAL_SWITCH_POSITION (0-0:96.3.10)
|
||||
# TODO to be implemented
|
||||
|
||||
# VALVE_POSITION_GAS (0-x:24.4.0)
|
||||
# TODO to be implemented
|
||||
@@ -0,0 +1,39 @@
|
||||
"""Test DSMR serial protocol."""
|
||||
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
from dsmr_parser import obis_references as obis
|
||||
from dsmr_parser import telegram_specifications
|
||||
from dsmr_parser.parsers import TelegramParserV2_2
|
||||
from dsmr_parser.protocol import DSMRProtocol
|
||||
|
||||
from .test_parse_v2_2 import TELEGRAM_V2_2
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def protocol():
|
||||
"""DSMRprotocol instance with mocked telegram_callback."""
|
||||
|
||||
parser = TelegramParserV2_2
|
||||
specification = telegram_specifications.V2_2
|
||||
|
||||
telegram_parser = parser(specification)
|
||||
return DSMRProtocol(None, telegram_parser,
|
||||
telegram_callback=Mock())
|
||||
|
||||
|
||||
def test_complete_packet(protocol):
|
||||
"""Protocol should assemble incoming lines into complete packet."""
|
||||
|
||||
for line in TELEGRAM_V2_2:
|
||||
protocol.data_received(bytes(line + '\r\n', 'ascii'))
|
||||
|
||||
telegram = protocol.telegram_callback.call_args_list[0][0][0]
|
||||
assert isinstance(telegram, dict)
|
||||
|
||||
assert float(telegram[obis.CURRENT_ELECTRICITY_USAGE].value) == 1.01
|
||||
assert telegram[obis.CURRENT_ELECTRICITY_USAGE].unit == 'kW'
|
||||
|
||||
assert float(telegram[obis.GAS_METER_READING].value) == 1.001
|
||||
assert telegram[obis.GAS_METER_READING].unit == 'm3'
|
||||
Reference in New Issue
Block a user