Compare commits

...
41 Commits
Author SHA1 Message Date
Nigel f806cc01d3 Preparing release 0.28 2021-02-12 17:56:33 +01:00
Nigel 399532f244 Merge pull request #73 from hogend/keep-alive
Optional keep alive monitoring for TCP/IP connections
2021-02-12 17:38:48 +01:00
Rene Hogendoorn b901b3f74e Add unit test for keep-alive 2021-02-11 11:08:19 +01:00
Nigel a255380953 Merge pull request #74 from lowdef/issue72_robustness_fix
catch parse errors in TelegramParser, ignore lines that can not be parsed
2021-02-07 14:52:45 +01:00
Hans Erik van Elburg 1cdda2eaba catch parse errors in TelegramParser, ignore lines that can not be parsed 2021-02-07 13:40:20 +01:00
Rene Hogendoorn 7453534927 Raised log level to warning for failed keep-alive check 2021-01-19 10:55:20 +01:00
Rene Hogendoorn bbd73897a0 Optional keep alive monitoring for TCP/IP connections
* Since dsmr-parser is listen-only, it will not notice interrupted connections
  and DSMR device restarts. The connection will be reset after an (optional)
  keep-alive interval if no messages were received from the device.
2021-01-19 08:26:28 +01:00
Nigel 3dc77a8231 Prepare for release 0.27 2020-12-24 21:46:41 +01:00
Nigel d61f2229c8 Merge pull request #69 from lowdef/issue57_revisited
Issue57 revisited
2020-12-24 21:42:08 +01:00
Hans Erik van Elburg 3ddf0366e6 small fix 2020-12-24 12:44:49 +01:00
Hans Erik van Elburg 629767590b finished tox tests for issue 57 fix 2020-12-24 12:37:45 +01:00
Hans Erik van Elburg 81cccbd228 fix tox tests 2020-12-24 01:52:17 +01:00
Hans Erik van Elburg 5b1e830018 make sure that for the special case (actually invalid syntax) where a ProfileGeneric line only contains (); an empty ProfileGenericObject is created 2020-12-24 00:22:29 +01:00
albert 2d712b506d referring to parent method from _parse_values in ProfileGenericParser 2020-12-23 18:47:34 +01:00
albert 1318204d0c tempfix for empty profileGenericParser 2020-12-23 18:03:49 +01:00
Nigel feb0f88ddc Preparing release 0.26 2020-12-15 15:03:22 +01:00
Nigel 602129a665 Merge pull request #68 from ndokter/revert-57-master
Revert "fix for empty parentheses in ProfileGenericParser"
2020-12-15 15:00:07 +01:00
Nigel af9a99d995 Revert "fix for empty parentheses in ProfileGenericParser" 2020-12-15 14:59:35 +01:00
Nigel a7b4929eab preparation for v0.25 2020-12-14 17:21:16 +01:00
Nigel 8999b1c924 Merge branch 'master' of github.com:ndokter/dsmr_parser 2020-12-14 17:16:55 +01:00
Nigel 2391f38c4a Merge pull request #57 from avandermeer/master
fix for empty parentheses in ProfileGenericParser
2020-12-14 16:55:38 +01:00
Nigel d1a6e15c68 Merge pull request #66 from GuyFoetz/foreign_name_mapping
adding the # Alternate codes for foreign countries.
2020-12-13 13:22:15 +01:00
Guy Foetz b7c8626d0c adding the # Alternate codes for foreign countries. 2020-12-13 12:15:51 +00:00
Nigel 684023d0b6 Preparing for release 0.24 2020-11-27 21:18:59 +01:00
Nigel 1a886aa03b Merge pull request #62 from RobBie1221/add_lux_eq
Add Luxembourg equipment identifier
2020-11-12 17:10:54 +01:00
Nigel 1c7535448e Updated changelog and version 2020-11-07 08:56:24 +01:00
Rob Bierbooms 2a95d33c02 Add Luxembourg equipment identifier 2020-11-04 20:42:38 +01:00
Nigel de6725dff8 Merge pull request #58 from dunajski/master
Corrects example telegrams.
2020-11-03 21:29:35 +01:00
Nigel 1b1a3f505e Merge pull request #59 from scop/dont-install-tests
Do not install tests
2020-11-03 21:27:21 +01:00
Nigel ebea385753 Merge pull request #61 from lowdef/issue60
issue#60 resolved issue with x-x:24.3.0 where it contains non-integer…
2020-11-03 21:25:39 +01:00
Hans Erik van Elburg 700cf6a2b3 issue#60 resolved issue with x-x:24.3.0 where it contains non-integer characters, parse 2nd value as string instead of integer 2020-11-02 00:29:03 +01:00
Ville Skyttä 7a687a99a6 Do not install tests 2020-10-18 10:49:09 +03:00
dunajski 86d665df39 Change README.md according to change in ec1d37e
In ec1d37ea08 changed example telegrams,
so example telegram from README.md should be corrected too.
2020-09-28 11:35:47 +02:00
dunajski ec1d37ea08 Corrects example telegrams.
In example telegrams there a few mistakes in header strings. Using '\'
in strings have special feature to add to string some non-printable
characters, for example to add new line we can add \n for tab \t etc. So
whenever we want to just print backslash as normal character we have to
add another '\' before. In short string "On\\Off" prints "On\Off" ascii
string.

In enquired telegrams there are few strings with \2 sign in header,
'/ISk5\2MT382-1000\r\n' and when we calculate CRC for whole given
telegram part of "..\2.." is interpreted as one character (value 0x02)
instead of two characters ('\', '2').

I think in a header in the example enlosed in P1 DSMR Companion Standard
/ISk5\2.. stand for printable '\' and '2' character instead of
"converted" to special '\2' character.
2020-09-28 10:22:16 +02:00
albert dc902a83e9 referring to parent method from _parse_values in ProfileGenericParser 2020-09-08 12:30:51 +02:00
albert be4386bc59 tempfix for empty profileGenericParser 2020-09-07 18:15:49 +02:00
Nigel 0427ace079 Updated changelog 2020-08-23 09:36:31 +02:00
Nigel addf9b590e Merge pull request #55 from aequitas/exception
Pass exception to log.exception as it is not called from within an ex…
2020-08-23 09:32:44 +02:00
Nigel 51f821a7fc Merge pull request #49 from dupondje/fix_pycrc
Improve CRC speed
2020-08-23 09:30:22 +02:00
Johan Bloemberg 5630520771 Pass exception to log.exception as it is not called from within an except: clause. Add Python 3.8 test support, drop Python 3.4 test support. 2020-07-21 10:31:40 +02:00
Jean-Louis Dupond 78419f6cc7 Improve CRC speed 2020-02-16 09:31:06 +01:00
15 changed files with 220 additions and 39 deletions
+1 -1
View File
@@ -2,9 +2,9 @@ language: python
python:
- 2.7
- 3.4
- 3.5
- 3.6
- 3.8
install: pip install tox-travis codecov
+27
View File
@@ -1,5 +1,32 @@
Change Log
----------
**0.28** (2021-02-21)
- Optional keep alive monitoring for TCP/IP connections (`pull request #73 <https://github.com/ndokter/dsmr_parser/pull/73>`_).
- Catch parse errors in TelegramParser, ignore lines that can not be parsed (`pull request #74 <https://github.com/ndokter/dsmr_parser/pull/74>`_).
**0.27** (2020-12-24)
- fix for empty parentheses in ProfileGenericParser (redone) (`pull request #69 <https://github.com/ndokter/dsmr_parser/pull/69>`_).
**0.26** (2020-12-15)
- reverted fix for empty parentheses in ProfileGenericParser (`pull request #68 <https://github.com/ndokter/dsmr_parser/pull/68>`_).
**0.25** (2020-12-14)
- fix for empty parentheses in ProfileGenericParser (`pull request #57 <https://github.com/ndokter/dsmr_parser/pull/57>`_).
**0.24** (2020-11-27)
- Add Luxembourg equipment identifier (`pull request #62 <https://github.com/ndokter/dsmr_parser/pull/62>`_).
**0.23** (2020-11-07)
- Resolved issue with x-x:24.3.0 where it contains non-integer character (`pull request #61 <https://github.com/ndokter/dsmr_parser/pull/61>`_).
- Tests are not installed anymore (`pull request #59 <https://github.com/ndokter/dsmr_parser/pull/59>`_).
- Example telegram improvement (`pull request #58 <https://github.com/ndokter/dsmr_parser/pull/58>`_).
**0.22** (2020-08-23)
- CRC check speed is improved
- Exception info improvement
**0.21** (2020-05-25)
- All objects can produce a json serialization of their state.
+1 -1
View File
@@ -56,7 +56,7 @@ into a dictionary.
# String is formatted in separate lines for readability.
telegram_str = (
'/ISk5\2MT382-1000\r\n'
'/ISk5\\2MT382-1000\r\n'
'\r\n'
'0-0:96.1.1(4B384547303034303436333935353037)\r\n'
'1-0:1.8.1(12345.678*kWh)\r\n'
+2 -2
View File
@@ -16,8 +16,8 @@ def console():
help='alternatively connect using TCP host.')
parser.add_argument('--port', default=None,
help='TCP port to use for connection')
parser.add_argument('--version', default='2.2', choices=['2.2', '4'],
help='DSMR version (2.2, 4)')
parser.add_argument('--version', default='2.2', choices=['2.2', '4', '5', '5B', '5L'],
help='DSMR version (2.2, 4, 5, 5B, 5L)')
parser.add_argument('--verbose', '-v', action='count')
args = parser.parse_args()
+29 -6
View File
@@ -14,7 +14,7 @@ from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \
SERIAL_SETTINGS_V4, SERIAL_SETTINGS_V5
def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None):
def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs):
"""Creates a DSMR asyncio protocol."""
if dsmr_version == '2.2':
@@ -29,12 +29,15 @@ def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None):
elif dsmr_version == '5B':
specification = telegram_specifications.BELGIUM_FLUVIUS
serial_settings = SERIAL_SETTINGS_V5
elif dsmr_version == "5L":
specification = telegram_specifications.LUXEMBOURG_SMARTY
serial_settings = SERIAL_SETTINGS_V5
else:
raise NotImplementedError("No telegram parser found for version: %s",
dsmr_version)
protocol = partial(DSMRProtocol, loop, TelegramParser(specification),
telegram_callback=telegram_callback)
telegram_callback=telegram_callback, **kwargs)
return protocol, serial_settings
@@ -50,12 +53,14 @@ def create_dsmr_reader(port, dsmr_version, telegram_callback, loop=None):
def create_tcp_dsmr_reader(host, port, dsmr_version,
telegram_callback, loop=None):
telegram_callback, loop=None,
keep_alive_interval=None):
"""Creates a DSMR asyncio protocol coroutine using TCP connection."""
if not loop:
loop = asyncio.get_event_loop()
protocol, _ = create_dsmr_protocol(
dsmr_version, telegram_callback, loop=loop)
dsmr_version, telegram_callback, loop=loop,
keep_alive_interval=keep_alive_interval)
conn = loop.create_connection(protocol, host, port)
return conn
@@ -66,7 +71,8 @@ class DSMRProtocol(asyncio.Protocol):
transport = None
telegram_callback = None
def __init__(self, loop, telegram_parser, telegram_callback=None):
def __init__(self, loop, telegram_parser,
telegram_callback=None, keep_alive_interval=None):
"""Initialize class."""
self.loop = loop
self.log = logging.getLogger(__name__)
@@ -77,25 +83,42 @@ class DSMRProtocol(asyncio.Protocol):
self.telegram_buffer = TelegramBuffer()
# keep a lock until the connection is closed
self._closed = asyncio.Event()
self._keep_alive_interval = keep_alive_interval
self._active = True
def connection_made(self, transport):
"""Just logging for now."""
self.transport = transport
self.log.debug('connected')
self._active = False
if self.loop and self._keep_alive_interval:
self.loop.call_later(self._keep_alive_interval, self.keep_alive)
def data_received(self, data):
"""Add incoming data to buffer."""
data = data.decode('ascii')
self._active = True
self.log.debug('received data: %s', data)
self.telegram_buffer.append(data)
for telegram in self.telegram_buffer.get_all():
self.handle_telegram(telegram)
def keep_alive(self):
if self._active:
self.log.debug('keep-alive checked')
self._active = False
if self.loop:
self.loop.call_later(self._keep_alive_interval, self.keep_alive)
else:
self.log.warning('keep-alive check failed')
if self.transport:
self.transport.close()
def connection_lost(self, exc):
"""Stop when connection is lost."""
if exc:
self.log.exception('disconnected due to exception')
self.log.exception('disconnected due to exception', exc_info=exc)
else:
self.log.info('disconnected because of close/abort.')
self._closed.set()
+5 -1
View File
@@ -48,7 +48,11 @@ EN = {
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.VALVE_POSITION_GAS: 'VALVE_POSITION_GAS',
obis.BELGIUM_HOURLY_GAS_METER_READING: 'BELGIUM_HOURLY_GAS_METER_READING',
obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: 'LUXEMBOURG_EQUIPMENT_IDENTIFIER',
obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: 'LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL',
obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: 'LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL'
}
REVERSE_EN = dict([(v, k) for k, v in EN.items()])
+1
View File
@@ -63,5 +63,6 @@ ELECTRICITY_DELIVERED_TARIFF_ALL = (
# Alternate codes for foreign countries.
BELGIUM_HOURLY_GAS_METER_READING = r'\d-\d:24\.2\.3.+?\r\n' # Different code, same format.
LUXEMBOURG_EQUIPMENT_IDENTIFIER = r'\d-\d:42\.0\.0.+?\r\n' # Logical device name
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-)
+29 -13
View File
@@ -10,6 +10,7 @@ logger = logging.getLogger(__name__)
class TelegramParser(object):
crc16_tab = []
def __init__(self, telegram_specification, apply_checksum_validation=True):
"""
@@ -54,7 +55,11 @@ class TelegramParser(object):
# Some signatures are optional and may not be present,
# so only parse lines that match
if match:
telegram[signature] = parser.parse(match.group(0))
try:
telegram[signature] = parser.parse(match.group(0))
except Exception:
logger.error("ignore line with signature {}, because parsing failed.".format(signature),
exc_info=True)
return telegram
@@ -93,24 +98,28 @@ 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))
"""
Calculate the CRC16 value for the given telegram
:param str telegram:
"""
crcValue = 0x0000
if len(TelegramParser.crc16_tab) == 0:
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
TelegramParser.crc16_tab.append(hex(crc))
for c in telegram:
d = ord(c)
tmp = crcValue ^ d
rotated = c_ushort(crcValue >> 8).value
crcValue = rotated ^ int(crc16_tab[(tmp & 0x00ff)], 0)
crcValue = rotated ^ int(TelegramParser.crc16_tab[(tmp & 0x00ff)], 0)
return crcValue
@@ -213,12 +222,17 @@ class ProfileGenericParser(DSMRObjectParser):
8) Buffer value 2 (oldest entry of buffer attribute without unit)
9) Unit of buffer values (Unit of capture objects attribute)
"""
def __init__(self, buffer_types, head_parsers, parsers_for_unidentified):
self.value_formats = head_parsers
self.buffer_types = buffer_types
self.parsers_for_unidentified = parsers_for_unidentified
def _is_line_wellformed(self, line, values):
if values and (len(values) == 1) and (values[0] == ''):
# special case: single empty parentheses (indicated by empty string)
return True
if values and (len(values) >= 2) and (values[0].isdigit()):
buffer_length = int(values[0])
return (buffer_length <= 10) and (len(values) == (buffer_length * 2 + 2))
@@ -226,6 +240,9 @@ class ProfileGenericParser(DSMRObjectParser):
return False
def _parse_values(self, values):
if values and (len(values) == 1) and (values[0] is None):
# special case: single empty parentheses; make sure empty ProfileGenericObject is created
values = [0, None] # buffer_length=0, buffer_value_obis_ID=None
buffer_length = int(values[0])
buffer_value_obis_ID = values[1]
if (buffer_length > 0):
@@ -258,7 +275,6 @@ class ValueParser(object):
self.coerce_type = coerce_type
def parse(self, value):
unit_of_measurement = None
if value and '*' in value:
+2 -1
View File
@@ -34,7 +34,7 @@ V2_2 = {
obis.VALVE_POSITION_GAS: CosemParser(ValueParser(str)),
obis.GAS_METER_READING: MBusParser(
ValueParser(timestamp),
ValueParser(int),
ValueParser(str), # changed to str see issue60
ValueParser(int),
ValueParser(int),
ValueParser(str), # obis ref
@@ -152,6 +152,7 @@ BELGIUM_FLUVIUS['objects'].update({
LUXEMBOURG_SMARTY = deepcopy(V5)
LUXEMBOURG_SMARTY['objects'].update({
obis.LUXEMBOURG_EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)),
obis.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)),
obis.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemParser(ValueParser(Decimal)),
})
+3 -3
View File
@@ -3,11 +3,11 @@ from setuptools import setup, find_packages
setup(
name='dsmr-parser',
description='Library to parse Dutch Smart Meter Requirements (DSMR)',
author='Nigel Dokter',
author='Nigel Dokter and many others',
author_email='nigel@nldr.net',
url='https://github.com/ndokter/dsmr_parser',
version='0.21',
packages=find_packages(),
version='0.28',
packages=find_packages(exclude=('test', 'test.*')),
install_requires=[
'pyserial>=3,<4',
'pyserial-asyncio<1',
+4 -4
View File
@@ -1,5 +1,5 @@
TELEGRAM_V2_2 = (
'/ISk5\2MT382-1004\r\n'
'/ISk5\\2MT382-1004\r\n'
'\r\n'
'0-0:96.1.1(00000000000000)\r\n'
'1-0:1.8.1(00001.001*kWh)\r\n'
@@ -22,7 +22,7 @@ TELEGRAM_V2_2 = (
)
TELEGRAM_V3 = (
'/ISk5\2MT382-1000\r\n'
'/ISk5\\2MT382-1000\r\n'
'\r\n'
'0-0:96.1.1(4B384547303034303436333935353037)\r\n'
'1-0:1.8.1(12345.678*kWh)\r\n'
@@ -87,7 +87,7 @@ TELEGRAM_V4_2 = (
)
TELEGRAM_V5 = (
'/ISk5\2MT382-1000\r\n'
'/ISk5\\2MT382-1000\r\n'
'\r\n'
'1-3:0.2.8(50)\r\n'
'0-0:1.0.0(170102192002W)\r\n'
@@ -126,5 +126,5 @@ TELEGRAM_V5 = (
'0-1:24.2.1(170102161005W)(00000.107*m3)\r\n'
'0-2:24.1.0(003)\r\n'
'0-2:96.1.0()\r\n'
'!87B3\r\n'
'!6EEE\r\n'
)
+1 -2
View File
@@ -241,7 +241,6 @@ class TelegramParserV5Test(unittest.TestCase):
def test_checksum_missing(self):
# Remove the checksum value causing a ParseError.
corrupted_telegram = TELEGRAM_V5.replace('!87B3\r\n', '')
corrupted_telegram = TELEGRAM_V5.replace('!6EEE\r\n', '')
with self.assertRaises(ParseError):
TelegramParser.validate_checksum(corrupted_telegram)
+89
View File
@@ -0,0 +1,89 @@
import unittest
from dsmr_parser import telegram_specifications
from dsmr_parser.objects import Telegram
from dsmr_parser.objects import ProfileGenericObject
from dsmr_parser.parsers import TelegramParser
from dsmr_parser.parsers import ProfileGenericParser
from dsmr_parser.profile_generic_specifications import BUFFER_TYPES
from dsmr_parser.profile_generic_specifications import PG_HEAD_PARSERS
from dsmr_parser.profile_generic_specifications import PG_UNIDENTIFIED_BUFFERTYPE_PARSERS
from test.example_telegrams import TELEGRAM_V5
class TestParserCornerCases(unittest.TestCase):
""" Test instantiation of Telegram object """
def test_power_event_log_empty_1(self):
# POWER_EVENT_FAILURE_LOG (1-0:99.97.0)
parser = TelegramParser(telegram_specifications.V5)
telegram = Telegram(TELEGRAM_V5, parser, telegram_specifications.V5)
object_type = ProfileGenericObject
testitem = telegram.POWER_EVENT_FAILURE_LOG
assert isinstance(testitem, object_type)
assert testitem.buffer_length == 0
assert testitem.buffer_type == '0-0:96.7.19'
buffer = testitem.buffer
assert isinstance(testitem.buffer, list)
assert len(buffer) == 0
def test_power_event_log_empty_2(self):
pef_parser = ProfileGenericParser(BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS)
object_type = ProfileGenericObject
# Power Event Log with 0 items and no object type
pefl_line = r'1-0:99.97.0(0)()\r\n'
testitem = pef_parser.parse(pefl_line)
assert isinstance(testitem, object_type)
assert testitem.buffer_length == 0
assert testitem.buffer_type is None
buffer = testitem.buffer
assert isinstance(testitem.buffer, list)
assert len(buffer) == 0
assert testitem.values == [{'value': 0, 'unit': None}, {'value': None, 'unit': None}]
json = testitem.to_json()
assert json == '{"buffer_length": 0, "buffer_type": null, "buffer": []}'
def test_power_event_log_null_values(self):
pef_parser = ProfileGenericParser(BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS)
object_type = ProfileGenericObject
# Power Event Log with 1 item and no object type and nno values for the item
pefl_line = r'1-0:99.97.0(1)()()()\r\n'
testitem = pef_parser.parse(pefl_line)
assert isinstance(testitem, object_type)
assert testitem.buffer_length == 1
assert testitem.buffer_type is None
buffer = testitem.buffer
assert isinstance(testitem.buffer, list)
assert len(buffer) == 1
assert testitem.values == [{'value': 1, 'unit': None}, {'value': None, 'unit': None},
{'value': None, 'unit': None}, {'value': None, 'unit': None}]
json = testitem.to_json()
assert json == \
'{"buffer_length": 1, "buffer_type": null, "buffer": [{"datetime": null, "value": null, "unit": null}]}'
def test_power_event_log_brackets_only(self):
# POWER_EVENT_FAILURE_LOG (1-0:99.97.0)
# Issue 57
# Test of an ill formatted empty POWER_EVENT_FAILURE_LOG, observed on some smartmeters
# The idea is that instead of failing, the parser converts it to an empty POWER_EVENT_FAILURE_LOG
pef_parser = ProfileGenericParser(BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS)
object_type = ProfileGenericObject
pefl_line = r'1-0:99.97.0()\r\n'
testitem = pef_parser.parse(pefl_line)
assert isinstance(testitem, object_type)
assert testitem.buffer_length == 0
assert testitem.buffer_type is None
buffer = testitem.buffer
assert isinstance(testitem.buffer, list)
assert len(buffer) == 0
assert testitem.values == [{'value': 0, 'unit': None}, {'value': None, 'unit': None}]
json = testitem.to_json()
assert json == '{"buffer_length": 0, "buffer_type": null, "buffer": []}'
+25 -4
View File
@@ -5,7 +5,7 @@ import unittest
from dsmr_parser import obis_references as obis
from dsmr_parser import telegram_specifications
from dsmr_parser.parsers import TelegramParser
from dsmr_parser.clients.protocol import DSMRProtocol
from dsmr_parser.clients.protocol import create_dsmr_protocol
TELEGRAM_V2_2 = (
@@ -35,9 +35,10 @@ TELEGRAM_V2_2 = (
class ProtocolTest(unittest.TestCase):
def setUp(self):
telegram_parser = TelegramParser(telegram_specifications.V2_2)
self.protocol = DSMRProtocol(None, telegram_parser,
telegram_callback=Mock())
new_protocol, _ = create_dsmr_protocol('2.2',
telegram_callback=Mock(),
keep_alive_interval=1)
self.protocol = new_protocol()
def test_complete_packet(self):
"""Protocol should assemble incoming lines into complete packet."""
@@ -52,3 +53,23 @@ class ProtocolTest(unittest.TestCase):
assert float(telegram[obis.GAS_METER_READING].value) == 1.001
assert telegram[obis.GAS_METER_READING].unit == 'm3'
def test_receive_packet(self):
"""Protocol packet reception."""
mock_transport = Mock()
self.protocol.connection_made(mock_transport)
assert not self.protocol._active
self.protocol.data_received(TELEGRAM_V2_2.encode('ascii'))
assert self.protocol._active
# 1st call of keep_alive resets 'active' flag
self.protocol.keep_alive()
assert not self.protocol._active
# 2nd call of keep_alive should close the transport
self.protocol.keep_alive()
assert mock_transport.close.called_once()
self.protocol.connection_lost(None)
+1 -1
View File
@@ -1,5 +1,5 @@
[tox]
envlist = py34,py35,py36,py37
envlist = py35,py36,py37,py38
[testenv]
deps=