Skip to content

Commit

Permalink
Load multiple frames at once and universal support for to_JSON in frames
Browse files Browse the repository at this point in the history
  • Loading branch information
Szabolcs committed Jun 20, 2019
1 parent f248942 commit f6b0585
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 12 deletions.
82 changes: 72 additions & 10 deletions meterbus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
11 changes: 11 additions & 0 deletions meterbus/telegram_ack.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import simplejson as json

from .exceptions import MBusFrameDecodeError, MBusFrameCRCError, FrameMismatch


Expand All @@ -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)
2 changes: 1 addition & 1 deletion meterbus/telegram_body.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
11 changes: 11 additions & 0 deletions meterbus/telegram_control.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import simplejson as json

from .telegram_body import TelegramBody
from .telegram_header import TelegramHeader
from .exceptions import MBusFrameCRCError, MBusFrameDecodeError, FrameMismatch
Expand Down Expand Up @@ -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] +
Expand All @@ -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

Expand Down
1 change: 1 addition & 0 deletions meterbus/telegram_header.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
6 changes: 5 additions & 1 deletion meterbus/telegram_long.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 2 additions & 0 deletions meterbus/telegram_short.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import simplejson as json

from .defines import *
from .telegram_header import TelegramHeader
from .exceptions import MBusFrameDecodeError, MBusFrameCRCError, FrameMismatch
Expand Down

0 comments on commit f6b0585

Please sign in to comment.