diff --git a/meterbus/__init__.py b/meterbus/__init__.py index ee15bc0..a71845b 100644 --- a/meterbus/__init__.py +++ b/meterbus/__init__.py @@ -47,7 +47,35 @@ def load(data): if not data: raise MBusFrameDecodeError("empty frame", data) - if isinstance(data, str): + data = convert_data(data) + + for Frame in (TelegramACK, TelegramShort, TelegramControl, + TelegramLong, WTelegramSndNr): + try: + return Frame.parse(data) + + except FrameMismatch as e: + pass + + raise MBusFrameDecodeError("unable to decode frame") + + +def load_all(data): + if not data: + raise MBusFrameDecodeError("empty frame", data) + + dats = split_frames(convert_data(data)) + + return [load(d) for d in dats] + + +def convert_data(data): + + if isinstance(data, list): + # assume that the data is already processed and quickly return it + return data + + elif isinstance(data, str): data = list(map(ord, data)) elif isinstance(data, bytes): @@ -56,18 +84,52 @@ def load(data): elif isinstance(data, bytearray): data = list(data) - elif isinstance(data, list): - pass + return data - for Frame in [WTelegramSndNr, TelegramACK, TelegramShort, TelegramControl, - TelegramLong]: - try: - return Frame.parse(data) - except FrameMismatch as e: - pass +def split_frames(data): + """ + try to extract more then one frame from data and return a list + of frames + + there are user cases in tcp connection when more then one frame + is received from the slave in one batch. These are long frames + in all the user cases, but the split function is designed to + be more generic and able to split all kinds of mbus frames + """ + + data = convert_data(data) + # or assume the data is already converted and skip this step + + if data is None or len(data) == 0: + raise MBusFrameDecodeError("Data is None") + + while len(data) > 0: + # ack + if data[0] == 0xE5: + yield data[:1] + data = data[1:] + continue + + # short frame + elif len(data) >= 5 and data[0] == 0x10 and data[4] == 0x16: + yield data[:5] + data = data[5:] + continue + + # long/control frame (+6 is for the header) + elif data[0] == 0x68 and data[3] == 0x68 and data[1] == data[2] \ + and len(data) >= data[1]+6 and data[data[1]+6-1] == 0x16: + yield data[:data[1] + 6] + data = data[data[1] + 6:] + continue + + else: + raise MBusFrameDecodeError("invalid data") + # or could consume data by bytes + # data = data[1:] + - raise MBusFrameDecodeError("unable to decode frame") def debug(state): g.debug = state diff --git a/meterbus/telegram_ack.py b/meterbus/telegram_ack.py index d4b4845..d9dbdef 100644 --- a/meterbus/telegram_ack.py +++ b/meterbus/telegram_ack.py @@ -1,3 +1,5 @@ +import simplejson as json + from .exceptions import MBusFrameDecodeError, MBusFrameCRCError, FrameMismatch @@ -24,3 +26,12 @@ def __len__(self): def __iter__(self): yield 0xE5 + + @property + def interpreted(self): + return { + 'ack': hex(0xE5) + } + + def to_JSON(self): + return json.dumps(self.interpreted, sort_keys=True, indent=4, use_decimal=True) diff --git a/meterbus/telegram_body.py b/meterbus/telegram_body.py index b64c188..b1a93e0 100644 --- a/meterbus/telegram_body.py +++ b/meterbus/telegram_body.py @@ -225,7 +225,7 @@ def isLSBOrder(self): @property def noDataHeader(self): - return (self._ci_field.parts and self._ci_field.parts[0] == 0x78) + return (self._ci_field.parts and self._ci_field.parts[0] in (0x78, 0x51) ) @property def isVariableData(self): diff --git a/meterbus/telegram_control.py b/meterbus/telegram_control.py index 86fc104..16e2108 100644 --- a/meterbus/telegram_control.py +++ b/meterbus/telegram_control.py @@ -1,3 +1,5 @@ +import simplejson as json + from .telegram_body import TelegramBody from .telegram_header import TelegramHeader from .exceptions import MBusFrameCRCError, MBusFrameDecodeError, FrameMismatch @@ -61,6 +63,12 @@ def body(self): def body(self, value): self._body = value + @property + def interpreted(self): + return { + 'head': self.header.interpreted + } + def compute_crc(self): return (self.header.cField.parts[0] + self.header.aField.parts[0] + @@ -69,6 +77,9 @@ def compute_crc(self): def check_crc(self): return self.compute_crc() == self.header.crcField.parts[0] + def to_JSON(self): + return json.dumps(self.interpreted, sort_keys=True, indent=4, use_decimal=True) + def __len__(self): return 0x09 diff --git a/meterbus/telegram_header.py b/meterbus/telegram_header.py index 21d8eb3..9340fa6 100644 --- a/meterbus/telegram_header.py +++ b/meterbus/telegram_header.py @@ -91,6 +91,7 @@ def load(self, hat): self.lField = header[1] self.lField = header[2] # Re-set self.startField = header[3] # Re-set + # TODO check equality of re-set bytes self.cField = header[4] self.aField = header[5] self.crcField = header[-2] diff --git a/meterbus/telegram_long.py b/meterbus/telegram_long.py index 5df4eba..8e2f839 100644 --- a/meterbus/telegram_long.py +++ b/meterbus/telegram_long.py @@ -11,12 +11,16 @@ def parse(data): if data is None: raise MBusFrameDecodeError("Data is None") - if data is not None and len(data) < 9: + if len(data) < 9: raise MBusFrameDecodeError("Invalid M-Bus length") if data[0] != 0x68: raise FrameMismatch() + if data[1] != data[2] or len(data) != data[1] + 6: + print('not a single long', len(data), data[1]) + raise FrameMismatch() # to be able to parse multiple frames + return TelegramLong(data) def __init__(self, dbuf=None): diff --git a/meterbus/telegram_short.py b/meterbus/telegram_short.py index c537ca9..266cf47 100644 --- a/meterbus/telegram_short.py +++ b/meterbus/telegram_short.py @@ -1,3 +1,5 @@ +import simplejson as json + from .defines import * from .telegram_header import TelegramHeader from .exceptions import MBusFrameDecodeError, MBusFrameCRCError, FrameMismatch