Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca8392504d | ||
|
|
026b91cd49 | ||
|
|
02eb3a34dd | ||
|
|
142b7359a1 | ||
|
|
21b8547655 | ||
|
|
a2b9fe5e3b | ||
|
|
0a259a3c9d | ||
|
|
f851f48225 | ||
|
|
3a3054324a | ||
|
|
041ac758f4 | ||
|
|
d50d775e38 | ||
|
|
10613acbd9 | ||
|
|
5f58bc8f72 | ||
|
|
371e1f3c5e | ||
|
|
0eae28e183 | ||
|
|
431ddadb5f | ||
|
|
9e01dc32c0 | ||
|
|
650d16862b |
+6
-1
@@ -1,10 +1,15 @@
|
||||
Change Log
|
||||
----------
|
||||
|
||||
**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>`_)
|
||||
|
||||
|
||||
**1.0.0** (2022-12-22)
|
||||
|
||||
- switched to new numbering scheme https://semver.org/
|
||||
- Added support for Python 3.11 and dropped support for Python 3.6 (`pull request #110 <https://github.com/ndokter/dsmr_parser/pull/112>`_)
|
||||
- Added support for Python 3.11 and dropped support for Python 3.6 (`pull request #112 <https://github.com/ndokter/dsmr_parser/pull/112>`_)
|
||||
- Add support for Fluvius V1.7.1 DSMR messages (`pull request #110 <https://github.com/ndokter/dsmr_parser/pull/113>`_)
|
||||
|
||||
**0.34** (2022-10-19)
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ DSMR Parser
|
||||
.. image:: https://img.shields.io/pypi/v/dsmr-parser.svg
|
||||
:target: https://pypi.python.org/pypi/dsmr-parser
|
||||
|
||||
.. image:: https://img.shields.io/github/workflow/status/ndokter/dsmr_parser/Tests/master
|
||||
.. image:: https://img.shields.io/github/actions/workflow/status/ndokter/dsmr_parser/tests.yml?branch=master
|
||||
:target: https://github.com/ndokter/dsmr_parser/actions/workflows/tests.yml
|
||||
|
||||
A library for parsing Dutch Smart Meter Requirements (DSMR) telegram data. It
|
||||
|
||||
@@ -95,3 +95,35 @@ class AsyncSerialReader(SerialReader):
|
||||
)
|
||||
except ParseError as e:
|
||||
logger.warning('Failed to parse telegram: %s', e)
|
||||
|
||||
async def read_as_object(self, queue):
|
||||
"""
|
||||
Read complete DSMR telegram's from the serial interface
|
||||
and return a Telegram object.
|
||||
|
||||
Instead of being a generator, Telegram objects are pushed
|
||||
to provided queue for asynchronous processing.
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
# create Serial StreamReader
|
||||
conn = serial_asyncio.open_serial_connection(**self.serial_settings)
|
||||
reader, _ = await conn
|
||||
|
||||
while True:
|
||||
|
||||
# Read line if available or give control back to loop until new
|
||||
# data has arrived.
|
||||
data = await reader.readline()
|
||||
self.telegram_buffer.append(data.decode('ascii'))
|
||||
|
||||
for telegram in self.telegram_buffer.get_all():
|
||||
try:
|
||||
queue.put_nowait(
|
||||
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)
|
||||
|
||||
@@ -11,9 +11,11 @@ EN = {
|
||||
obis.P1_MESSAGE_HEADER: 'P1_MESSAGE_HEADER',
|
||||
obis.P1_MESSAGE_TIMESTAMP: 'P1_MESSAGE_TIMESTAMP',
|
||||
obis.ELECTRICITY_IMPORTED_TOTAL: 'ELECTRICITY_IMPORTED_TOTAL',
|
||||
obis.ELECTRICITY_REACTIVE_IMPORTED_TOTAL: 'ELECTRICITY_REACTIVE_IMPORTED_TOTAL',
|
||||
obis.ELECTRICITY_USED_TARIFF_1: 'ELECTRICITY_USED_TARIFF_1',
|
||||
obis.ELECTRICITY_USED_TARIFF_2: 'ELECTRICITY_USED_TARIFF_2',
|
||||
obis.ELECTRICITY_EXPORTED_TOTAL: 'ELECTRICITY_EXPORTED_TOTAL',
|
||||
obis.ELECTRICITY_REACTIVE_EXPORTED_TOTAL: 'ELECTRICITY_REACTIVE_EXPORTED_TOTAL',
|
||||
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',
|
||||
@@ -44,12 +46,19 @@ EN = {
|
||||
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.INSTANTANEOUS_REACTIVE_POWER_L1_POSITIVE: 'INSTANTANEOUS_REACTIVE_POWER_L1_POSITIVE',
|
||||
obis.INSTANTANEOUS_REACTIVE_POWER_L1_NEGATIVE: 'INSTANTANEOUS_REACTIVE_POWER_L1_NEGATIVE',
|
||||
obis.INSTANTANEOUS_REACTIVE_POWER_L2_POSITIVE: 'INSTANTANEOUS_REACTIVE_POWER_L2_POSITIVE',
|
||||
obis.INSTANTANEOUS_REACTIVE_POWER_L2_NEGATIVE: 'INSTANTANEOUS_REACTIVE_POWER_L2_NEGATIVE',
|
||||
obis.INSTANTANEOUS_REACTIVE_POWER_L3_POSITIVE: 'INSTANTANEOUS_REACTIVE_POWER_L3_POSITIVE',
|
||||
obis.INSTANTANEOUS_REACTIVE_POWER_L3_NEGATIVE: 'INSTANTANEOUS_REACTIVE_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',
|
||||
obis.BELGIUM_VERSION_INFORMATION: 'BELGIUM_VERSION_INFORMATION',
|
||||
obis.BELGIUM_EQUIPMENT_IDENTIFIER: 'BELGIUM_EQUIPMENT_IDENTIFIER',
|
||||
obis.BELGIUM_CURRENT_AVERAGE_DEMAND: 'BELGIUM_CURRENT_AVERAGE_DEMAND',
|
||||
obis.BELGIUM_MAXIMUM_DEMAND_MONTH: 'BELGIUM_MAXIMUM_DEMAND_MONTH',
|
||||
|
||||
@@ -12,11 +12,11 @@ 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_DELIVERED_TARIFF_1 = r'\d-\d:2\.8\.1.+?\r\n'
|
||||
ELECTRICITY_DELIVERED_TARIFF_2 = r'\d-\d:2\.8\.2.+?\r\n'
|
||||
CURRENT_REACTIVE_EXPORTED = r'\d-\d:3\.7\.0.+?\r\n'
|
||||
CURRENT_REACTIVE_IMPORTED = r'\d-\d:3\.7\.0.+?\r\n'
|
||||
ELECTRICITY_REACTIVE_IMPORTED_TOTAL = r'\d-\d:3\.8\.0.+?\r\n'
|
||||
ELECTRICITY_REACTIVE_IMPORTED_TARIFF_1 = r'\d-\d:3\.8\.1.+?\r\n'
|
||||
ELECTRICITY_REACTIVE_IMPORTED_TARIFF_2 = r'\d-\d:3\.8\.2.+?\r\n'
|
||||
CURRENT_REACTIVE_IMPORTED = r'\d-\d:4\.7\.0.+?\r\n'
|
||||
CURRENT_REACTIVE_EXPORTED = r'\d-\d:4\.7\.0.+?\r\n'
|
||||
ELECTRICITY_REACTIVE_EXPORTED_TOTAL = r'\d-\d:4\.8\.0.+?\r\n'
|
||||
ELECTRICITY_REACTIVE_EXPORTED_TARIFF_1 = r'\d-\d:4\.8\.1.+?\r\n'
|
||||
ELECTRICITY_REACTIVE_EXPORTED_TARIFF_2 = r'\d-\d:4\.8\.2.+?\r\n'
|
||||
@@ -48,6 +48,12 @@ INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE = r'\d-\d:61\.7\.0.+?\r\n'
|
||||
INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE = r'\d-\d:22\.7\.0.+?\r\n'
|
||||
INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE = r'\d-\d:42\.7\.0.+?\r\n'
|
||||
INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE = r'\d-\d:62\.7\.0.+?\r\n'
|
||||
INSTANTANEOUS_REACTIVE_POWER_L1_POSITIVE = r'\d-\d:23\.7\.0.+?\r\n'
|
||||
INSTANTANEOUS_REACTIVE_POWER_L1_NEGATIVE = r'\d-\d:24\.7\.0.+?\r\n'
|
||||
INSTANTANEOUS_REACTIVE_POWER_L2_POSITIVE = r'\d-\d:43\.7\.0.+?\r\n'
|
||||
INSTANTANEOUS_REACTIVE_POWER_L2_NEGATIVE = r'\d-\d:44\.7\.0.+?\r\n'
|
||||
INSTANTANEOUS_REACTIVE_POWER_L3_POSITIVE = r'\d-\d:63\.7\.0.+?\r\n'
|
||||
INSTANTANEOUS_REACTIVE_POWER_L3_NEGATIVE = r'\d-\d:64\.7\.0.+?\r\n'
|
||||
EQUIPMENT_IDENTIFIER_GAS = r'\d-\d:96\.1\.0.+?\r\n'
|
||||
# TODO differences between gas meter readings in v3 and lower and v4 and up
|
||||
HOURLY_GAS_METER_READING = r'\d-\d:24\.2\.1.+?\r\n'
|
||||
|
||||
@@ -113,6 +113,7 @@ class MBusObject(DSMRObject):
|
||||
}
|
||||
return json.dumps(output)
|
||||
|
||||
|
||||
class MBusObjectPeak(DSMRObject):
|
||||
|
||||
@property
|
||||
@@ -132,7 +133,9 @@ class MBusObjectPeak(DSMRObject):
|
||||
return self.values[2]['unit']
|
||||
|
||||
def __str__(self):
|
||||
output = "{}\t[{}] at {} occurred {}".format(str(self.value), str(self.unit), str(self.datetime.astimezone().isoformat()), str(self.occurred.astimezone().isoformat()))
|
||||
output = "{}\t[{}] at {} occurred {}"\
|
||||
.format(str(self.value), str(self.unit), str(self.datetime.astimezone().isoformat()),
|
||||
str(self.occurred.astimezone().isoformat()))
|
||||
return output
|
||||
|
||||
def to_json(self):
|
||||
|
||||
@@ -244,10 +244,10 @@ class MaxDemandParser(DSMRObjectParser):
|
||||
objects = []
|
||||
|
||||
count = int(values[0])
|
||||
for i in range(1, count+1):
|
||||
timestamp_month = ValueParser(timestamp).parse(values[i*3+1])
|
||||
timestamp_occurred = ValueParser(timestamp).parse(values[i*3+1])
|
||||
value = ValueParser(Decimal).parse(values[i*3+2])
|
||||
for i in range(1, count + 1):
|
||||
timestamp_month = ValueParser(timestamp).parse(values[i * 3 + 1])
|
||||
timestamp_occurred = ValueParser(timestamp).parse(values[i * 3 + 1])
|
||||
value = ValueParser(Decimal).parse(values[i * 3 + 2])
|
||||
objects.append(MBusObjectPeak([timestamp_month, timestamp_occurred, value]))
|
||||
|
||||
return objects
|
||||
|
||||
@@ -240,6 +240,8 @@ SWEDEN = {
|
||||
obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)),
|
||||
obis.ELECTRICITY_IMPORTED_TOTAL: CosemParser(ValueParser(Decimal)),
|
||||
obis.ELECTRICITY_EXPORTED_TOTAL: CosemParser(ValueParser(Decimal)),
|
||||
obis.ELECTRICITY_REACTIVE_IMPORTED_TOTAL: CosemParser(ValueParser(Decimal)),
|
||||
obis.ELECTRICITY_REACTIVE_EXPORTED_TOTAL: CosemParser(ValueParser(Decimal)),
|
||||
obis.CURRENT_ELECTRICITY_USAGE: CosemParser(ValueParser(Decimal)),
|
||||
obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)),
|
||||
obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: CosemParser(ValueParser(Decimal)),
|
||||
@@ -248,6 +250,12 @@ SWEDEN = {
|
||||
obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE: CosemParser(ValueParser(Decimal)),
|
||||
obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE: CosemParser(ValueParser(Decimal)),
|
||||
obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: CosemParser(ValueParser(Decimal)),
|
||||
obis.INSTANTANEOUS_REACTIVE_POWER_L1_POSITIVE: CosemParser(ValueParser(Decimal)),
|
||||
obis.INSTANTANEOUS_REACTIVE_POWER_L1_NEGATIVE: CosemParser(ValueParser(Decimal)),
|
||||
obis.INSTANTANEOUS_REACTIVE_POWER_L2_POSITIVE: CosemParser(ValueParser(Decimal)),
|
||||
obis.INSTANTANEOUS_REACTIVE_POWER_L2_NEGATIVE: CosemParser(ValueParser(Decimal)),
|
||||
obis.INSTANTANEOUS_REACTIVE_POWER_L3_POSITIVE: CosemParser(ValueParser(Decimal)),
|
||||
obis.INSTANTANEOUS_REACTIVE_POWER_L3_NEGATIVE: CosemParser(ValueParser(Decimal)),
|
||||
obis.INSTANTANEOUS_VOLTAGE_L1: CosemParser(ValueParser(Decimal)),
|
||||
obis.INSTANTANEOUS_VOLTAGE_L2: CosemParser(ValueParser(Decimal)),
|
||||
obis.INSTANTANEOUS_VOLTAGE_L3: CosemParser(ValueParser(Decimal)),
|
||||
|
||||
@@ -4,10 +4,10 @@ setup(
|
||||
name='dsmr-parser',
|
||||
description='Library to parse Dutch Smart Meter Requirements (DSMR)',
|
||||
author='Nigel Dokter and many others',
|
||||
author_email='nigel@nldr.net',
|
||||
author_email='mail@nldr.net',
|
||||
license='MIT',
|
||||
url='https://github.com/ndokter/dsmr_parser',
|
||||
version='1.0.0',
|
||||
version='1.1.0',
|
||||
packages=find_packages(exclude=('test', 'test.*')),
|
||||
install_requires=[
|
||||
'pyserial>=3,<4',
|
||||
|
||||
@@ -246,7 +246,6 @@ class TelegramParserFluviusTest(unittest.TestCase):
|
||||
assert isinstance(result[obis.BELGIUM_MBUS2_METER_READING1].value, Decimal)
|
||||
assert result[obis.BELGIUM_MBUS2_METER_READING1].value == Decimal('872.234')
|
||||
|
||||
|
||||
def test_checksum_valid(self):
|
||||
# No exception is raised.
|
||||
TelegramParser.validate_checksum(TELEGRAM_FLUVIUS_V171)
|
||||
|
||||
@@ -2,8 +2,25 @@
|
||||
deps=
|
||||
pytest
|
||||
pytest-cov
|
||||
pylama
|
||||
pytest-asyncio
|
||||
pytest-mock
|
||||
dlms_cosem
|
||||
commands=
|
||||
py.test --cov=dsmr_parser test {posargs}
|
||||
pylama dsmr_parser test
|
||||
|
||||
[pylama:dsmr_parser/clients/__init__.py]
|
||||
ignore = W0611
|
||||
|
||||
[pylama:dsmr_parser/parsers.py]
|
||||
ignore = W605
|
||||
|
||||
[pylama:test/example_telegrams.py]
|
||||
ignore = E501
|
||||
|
||||
[pylama:pylint]
|
||||
max_line_length = 120
|
||||
|
||||
[pylama:pycodestyle]
|
||||
max_line_length = 120
|
||||
Reference in New Issue
Block a user