Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc6c35a0b6 | ||
|
|
47c20e763f | ||
|
|
e7164d9af7 | ||
|
|
d714528c5a | ||
|
|
eac9681e07 | ||
|
|
659560222a | ||
|
|
74fe1f2d1e | ||
|
|
a01e673646 | ||
|
|
09614860a6 | ||
|
|
3bfb555d0e | ||
|
|
2d4b0d8e72 | ||
|
|
43500e6bc2 | ||
|
|
50cef2646b | ||
|
|
3a8b4d2458 | ||
|
|
c1d7ba151d | ||
|
|
a65949823d | ||
|
|
3c78b0b6c4 | ||
|
|
5313edd6cb | ||
|
|
aa4f9fb967 | ||
|
|
ea804d485e | ||
|
|
1b522fc7f0 | ||
|
|
a137ef0e02 | ||
|
|
d712d468ac | ||
|
|
95bab6a51a | ||
|
|
49999618ea | ||
|
|
8ba400800b | ||
|
|
f7ba363b93 | ||
|
|
f9e9e70771 | ||
|
|
c36f68a884 | ||
|
|
8bdf77c78d |
@@ -6,3 +6,7 @@
|
||||
/.project
|
||||
/.pydevproject
|
||||
/.coverage
|
||||
build/
|
||||
dist/
|
||||
*.*~
|
||||
*~
|
||||
+13
-1
@@ -1,9 +1,21 @@
|
||||
Change Log
|
||||
----------
|
||||
|
||||
**0.18** (2020-01-28)
|
||||
|
||||
- PyCRC replacement (`pull request #48 <https://github.com/ndokter/dsmr_parser/pull/48>`_).
|
||||
|
||||
**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 #37 <https://github.com/ndokter/dsmr_parser/pull/36>`_).
|
||||
- Fixed asyncio loop issue (`pull request #43 <https://github.com/ndokter/dsmr_parser/pull/43>`_).
|
||||
|
||||
**0.14** (2019-10-08)
|
||||
|
||||
|
||||
+111
-2
@@ -85,8 +85,8 @@ into a dictionary.
|
||||
telegram = parser.parse(telegram_str)
|
||||
print(telegram) # see 'Telegram object' docs below
|
||||
|
||||
Telegram object
|
||||
---------------
|
||||
Telegram dictionary
|
||||
-------------------
|
||||
|
||||
A dictionary of which the key indicates the field type. These regex values
|
||||
correspond to one of dsmr_parser.obis_reference constants.
|
||||
@@ -138,6 +138,115 @@ Example to get some of the values:
|
||||
# See dsmr_reader.obis_references for all readable telegram values.
|
||||
# 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
|
||||
------------
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
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)
|
||||
@@ -26,6 +26,9 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None):
|
||||
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:
|
||||
raise NotImplementedError("No telegram parser found for version: %s",
|
||||
dsmr_version)
|
||||
|
||||
@@ -6,6 +6,7 @@ import serial_asyncio
|
||||
from dsmr_parser.clients.telegram_buffer import TelegramBuffer
|
||||
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
|
||||
from dsmr_parser.parsers import TelegramParser
|
||||
from dsmr_parser.objects import Telegram
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -20,6 +21,7 @@ class SerialReader(object):
|
||||
|
||||
self.telegram_parser = TelegramParser(telegram_specification)
|
||||
self.telegram_buffer = TelegramBuffer()
|
||||
self.telegram_specification = telegram_specification
|
||||
|
||||
def read(self):
|
||||
"""
|
||||
@@ -41,6 +43,25 @@ class SerialReader(object):
|
||||
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
|
||||
"""
|
||||
with serial.Serial(**self.serial_settings) as serial_handle:
|
||||
while True:
|
||||
data = serial_handle.readline()
|
||||
self.telegram_buffer.append(data.decode('ascii'))
|
||||
|
||||
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 AsyncSerialReader(SerialReader):
|
||||
"""Serial reader using asyncio pyserial."""
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
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()])
|
||||
@@ -60,3 +60,8 @@ ELECTRICITY_DELIVERED_TARIFF_ALL = (
|
||||
ELECTRICITY_DELIVERED_TARIFF_1,
|
||||
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-)
|
||||
|
||||
@@ -1,3 +1,56 @@
|
||||
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):
|
||||
"""
|
||||
Represents all data from a single telegram line.
|
||||
|
||||
+26
-3
@@ -1,9 +1,9 @@
|
||||
import logging
|
||||
import re
|
||||
|
||||
from PyCRC.CRC16 import CRC16
|
||||
from ctypes import c_ushort
|
||||
|
||||
from dsmr_parser.objects import MBusObject, CosemObject
|
||||
from dsmr_parser.objects import MBusObject, CosemObject, Telegram
|
||||
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -79,7 +79,7 @@ class TelegramParser(object):
|
||||
'incomplete. The checksum and/or content values are missing.'
|
||||
)
|
||||
|
||||
calculated_crc = CRC16().calculate(checksum_contents.group(0))
|
||||
calculated_crc = TelegramParser.crc16(checksum_contents.group(0))
|
||||
expected_crc = int(checksum_hex.group(0), base=16)
|
||||
|
||||
if calculated_crc != expected_crc:
|
||||
@@ -91,6 +91,29 @@ class TelegramParser(object):
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def crc16(telegram):
|
||||
crc16_tab = []
|
||||
|
||||
for i in range(0, 256):
|
||||
crc = c_ushort(i).value
|
||||
for j in range(0, 8):
|
||||
if (crc & 0x0001):
|
||||
crc = c_ushort(crc >> 1).value ^ 0xA001
|
||||
else:
|
||||
crc = c_ushort(crc >> 1).value
|
||||
crc16_tab.append(hex(crc))
|
||||
|
||||
crcValue = 0x0000
|
||||
|
||||
for c in telegram:
|
||||
d = ord(c)
|
||||
tmp = crcValue ^ d
|
||||
rotated = c_ushort(crcValue >> 8).value
|
||||
crcValue = rotated ^ int(crc16_tab[(tmp & 0x00ff)], 0)
|
||||
|
||||
return crcValue
|
||||
|
||||
|
||||
class DSMRObjectParser(object):
|
||||
"""
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from decimal import Decimal
|
||||
from copy import deepcopy
|
||||
|
||||
from dsmr_parser import obis_references as obis
|
||||
from dsmr_parser.parsers import CosemParser, ValueParser, MBusParser
|
||||
@@ -128,3 +129,18 @@ 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)),
|
||||
})
|
||||
|
||||
@@ -6,13 +6,12 @@ setup(
|
||||
author='Nigel Dokter',
|
||||
author_email='nigel@nldr.net',
|
||||
url='https://github.com/ndokter/dsmr_parser',
|
||||
version='0.15',
|
||||
version='0.18',
|
||||
packages=find_packages(),
|
||||
install_requires=[
|
||||
'pyserial>=3,<4',
|
||||
'pyserial-asyncio<1',
|
||||
'pytz',
|
||||
'PyCRC>=1.2,<2'
|
||||
],
|
||||
entry_points={
|
||||
'console_scripts': ['dsmr_console=dsmr_parser.__main__:console']
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
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)
|
||||
@@ -0,0 +1,30 @@
|
||||
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'
|
||||
Reference in New Issue
Block a user