frame parsing
This commit is contained in:
146
MeterbusLib.py
146
MeterbusLib.py
@ -6,32 +6,7 @@ Created on 11.06.2015
|
||||
|
||||
import MeterbusTypeConversion
|
||||
|
||||
class MeterbusLibException(Exception):
|
||||
pass
|
||||
|
||||
class InvalidFrameCodeException(MeterbusLibException):
|
||||
pass
|
||||
|
||||
class InvalidFrameException(MeterbusLibException):
|
||||
pass
|
||||
|
||||
class InvalidChecksumException(MeterbusLibException):
|
||||
pass
|
||||
|
||||
class InvalidStopCharException(MeterbusLibException):
|
||||
pass
|
||||
|
||||
class InvalidStartCharException(MeterbusLibException):
|
||||
pass
|
||||
|
||||
class InvalidLengthException(MeterbusLibException):
|
||||
pass
|
||||
|
||||
class InvalidSecondLengthException(MeterbusLibException):
|
||||
pass
|
||||
|
||||
class PayloadTooShortException(MeterbusLibException):
|
||||
pass
|
||||
from MeterbusLibExceptions import *
|
||||
|
||||
class Frame(object):
|
||||
def __init__(self, startCharacter, frameLength, firstPayload, telegram):
|
||||
@ -100,16 +75,113 @@ class LongFrame(ControlFrame):
|
||||
class FixedDataHeader(object):
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
print("Fixed header length: %d" % len(self.data))
|
||||
|
||||
def parse(self):
|
||||
self.identNr = MeterbusTypeConversion.bcd(self.data[:4])
|
||||
self.manufacturer = self.data[4:6]
|
||||
self.manufacturer = MeterbusTypeConversion.manufCode(self.data[4:6])
|
||||
self.version = self.data[6]
|
||||
self.medium = self.data[7]
|
||||
self.medium = MeterbusTypeConversion.mediumCode(self.data[7])
|
||||
self.accessNo = self.data[8]
|
||||
self.status = self.data[9]
|
||||
self.signature = self.data[10:]
|
||||
if self.signature[0] != 0 or self.signature[1] != 0:
|
||||
raise InvalidFrameException("signature should be zero")
|
||||
|
||||
class DataInformationElement(object):
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
self.consumed = 0
|
||||
|
||||
@classmethod
|
||||
def create(cls, data):
|
||||
if data[0] != 0x0f:
|
||||
return LongFrame.DataInformationBlock(data)
|
||||
else:
|
||||
return LongFrame.ManufacturerSpecificDataBlock(data)
|
||||
|
||||
class ManufacturerSpecificDataBlock(DataInformationElement):
|
||||
def parse(self):
|
||||
self.dif = self.data[0]
|
||||
self.mdh = self.data[1:]
|
||||
|
||||
def getJSON(self):
|
||||
j = {'dif': self.dif, 'mdh': self.mdh}
|
||||
return j
|
||||
|
||||
class DataInformationBlock(DataInformationElement):
|
||||
VALUE_LENGTH_MAP = [0, 1, 2, 3, 4, 4, 6, 8, 0, 1, 2, 3, 4, -1, 6, 0 ]
|
||||
|
||||
def parse(self):
|
||||
self.dif = self.data[self.consumed]
|
||||
self.consumed += 1
|
||||
self.dife = []
|
||||
if self.dif & 0x80 != 0:
|
||||
while True:
|
||||
curDife = self.data[self.consumed]
|
||||
self.consumed += 1
|
||||
self.dife.append(curDife)
|
||||
if curDife & 0x80 == 0:
|
||||
break
|
||||
self.vif = self.data[self.consumed]
|
||||
self.consumed += 1
|
||||
self.vife = []
|
||||
if self.vif & 0x80 != 0:
|
||||
while True:
|
||||
curVife = self.data[self.consumed]
|
||||
self.consumed += 1
|
||||
self.vife.append(curVife)
|
||||
if curVife & 0x80 == 0:
|
||||
break
|
||||
self.vifData = []
|
||||
if self.vif & 0x7f == 0x7c:
|
||||
dataLength = self.data[self.consumed]
|
||||
self.consumed += 1
|
||||
self.vifData = [v for v in self.data[self.consumed:self.consumed+dataLength]]
|
||||
self.consumed += dataLength
|
||||
self.userData = [u for u in self.data[self.consumed:self.consumed+LongFrame.DataInformationBlock.VALUE_LENGTH_MAP[self.dif&0x0f]]]
|
||||
|
||||
return self.consumed
|
||||
|
||||
def value(self):
|
||||
res = 0
|
||||
difCode = self.dif & 0x0f
|
||||
if difCode == 0:
|
||||
# 0
|
||||
pass
|
||||
elif difCode == 1:
|
||||
# 8bit int
|
||||
res = self.userData[0]
|
||||
elif difCode == 2:
|
||||
# 16bit int
|
||||
res = (self.userData[1] << 8) + self.userData[0]
|
||||
elif difCode == 3:
|
||||
# 24bit int
|
||||
res = (self.userData[2] << 16) + (self.userData[1] << 8) + self.userData[0]
|
||||
elif difCode == 4:
|
||||
# 32bit int
|
||||
res = (self.userData[3] << 24) + (self.userData[2] << 16) + (self.userData[1] << 8) + self.userData[0]
|
||||
elif difCode == 5:
|
||||
# 32bit float
|
||||
res = 1.0
|
||||
elif difCode == 6:
|
||||
# 48bit int
|
||||
res = (self.userData[5] << 40) + (self.userData[4] << 32) + (self.userData[3] << 24) + (self.userData[2] << 16) + (self.userData[1] << 8) + self.userData[0]
|
||||
elif difCode == 7:
|
||||
# 64bit int
|
||||
res = (self.userData[7] << 56) + (self.userData[6] << 48) + (self.userData[5] << 40) + (self.userData[4] << 32) + (self.userData[3] << 24) + (self.userData[2] << 16) + (self.userData[1] << 8) + self.userData[0]
|
||||
elif difCode == 8:
|
||||
# strange
|
||||
res = 0
|
||||
elif difCode in [9, 10, 11, 12, 14]:
|
||||
res = MeterbusTypeConversion.bcd(self.userData)
|
||||
return res
|
||||
|
||||
def getJSON(self):
|
||||
j = {'dif': self.dif, 'dife': self.dife, 'vif': self.vif, 'vife': self.vife,
|
||||
'vifData': self.vifData, 'userData':self.userData, 'value':self.value()}
|
||||
return j
|
||||
|
||||
|
||||
|
||||
def parse2(self):
|
||||
super(LongFrame, self).parse2()
|
||||
@ -117,6 +189,17 @@ class LongFrame(ControlFrame):
|
||||
raise PayloadTooShortException("too short for fixed data header")
|
||||
self.fixedDataHeader = LongFrame.FixedDataHeader(self.telegram[7:19])
|
||||
self.fixedDataHeader.parse()
|
||||
self.dib = []
|
||||
consumed = 0
|
||||
#print("Telegram length: %d" % len(self.telegram))
|
||||
while True:
|
||||
curDib = LongFrame.DataInformationElement.create(self.telegram[(19 + consumed):])
|
||||
consumed += curDib.parse()
|
||||
self.dib.append(curDib)
|
||||
#print("PayloadLength: %d, Consumed: %d" % (self.payloadLength, consumed))
|
||||
print("DIB: %s" % str(curDib.getJSON()))
|
||||
if (consumed + 12 + 3 - 1) >= self.payloadLength:
|
||||
break
|
||||
|
||||
|
||||
class Telegram(object):
|
||||
@ -128,9 +211,6 @@ class Telegram(object):
|
||||
octetList = [int(o, 16) for o in self.hexString.split(' ')]
|
||||
self.telegram = bytearray(octetList)
|
||||
|
||||
for i in self.telegram:
|
||||
print(int(i))
|
||||
|
||||
def toHexString(self):
|
||||
return str([hex(b) for b in self.telegram])
|
||||
|
||||
@ -148,7 +228,5 @@ class Telegram(object):
|
||||
|
||||
self.frame.parse()
|
||||
|
||||
print("Frame is %s" % self.frame)
|
||||
|
||||
|
||||
|
||||
|
35
MeterbusLibExceptions.py
Normal file
35
MeterbusLibExceptions.py
Normal file
@ -0,0 +1,35 @@
|
||||
'''
|
||||
Created on 11.06.2015
|
||||
|
||||
@author: wn
|
||||
'''
|
||||
|
||||
class MeterbusLibException(Exception):
|
||||
pass
|
||||
|
||||
class InvalidFrameCodeException(MeterbusLibException):
|
||||
pass
|
||||
|
||||
class InvalidFrameException(MeterbusLibException):
|
||||
pass
|
||||
|
||||
class InvalidChecksumException(MeterbusLibException):
|
||||
pass
|
||||
|
||||
class InvalidStopCharException(MeterbusLibException):
|
||||
pass
|
||||
|
||||
class InvalidStartCharException(MeterbusLibException):
|
||||
pass
|
||||
|
||||
class InvalidLengthException(MeterbusLibException):
|
||||
pass
|
||||
|
||||
class InvalidSecondLengthException(MeterbusLibException):
|
||||
pass
|
||||
|
||||
class PayloadTooShortException(MeterbusLibException):
|
||||
pass
|
||||
|
||||
class MediumConversionException(MeterbusLibException):
|
||||
pass
|
@ -4,6 +4,9 @@ Created on 11.06.2015
|
||||
@author: wn
|
||||
'''
|
||||
|
||||
from MeterbusLibExceptions import MediumConversionException
|
||||
|
||||
|
||||
|
||||
def bcd(data):
|
||||
v = ""
|
||||
@ -13,13 +16,43 @@ def bcd(data):
|
||||
return v
|
||||
|
||||
def manufCode(data):
|
||||
print("data: %s %s" % (hex(data[1]), hex(data[0])))
|
||||
v = data[1] * 256 + data[0]
|
||||
print("v: %s" % hex(v))
|
||||
l3 = chr((v & 0x20) + 64)
|
||||
print("l3: %s" % l3)
|
||||
l2 = chr((v >> 5) & 0x20)
|
||||
print("l2: %s" % l2)
|
||||
l1 = chr((v >> 10) & 0x20)
|
||||
print("l1: %s" % l1)
|
||||
v = (data[1] << 8) + data[0]
|
||||
l3 = chr((v & 0x1f) + 64)
|
||||
l2 = chr(((v >> 5) & 0x1f) + 64)
|
||||
l1 = chr(((v >> 10) & 0x1f) + 64)
|
||||
return l1 + l2 + l3
|
||||
|
||||
|
||||
def mediumCode(data):
|
||||
try:
|
||||
res = ('Other',
|
||||
'Oil',
|
||||
'Electrity',
|
||||
'Gas',
|
||||
'Heat (Volume measured at return temperature: outlet',
|
||||
'Steam',
|
||||
'Hot Water',
|
||||
'Water',
|
||||
'Heat Cost Allocator',
|
||||
'Compressed Air',
|
||||
'Cooling Load Meter (Volume measured at return temperature: outlet',
|
||||
'Cooling Load Meter (Volume measured at flow temperature: inlet',
|
||||
'Heat (Volume measured at flow temperature: inlet)',
|
||||
'Heat / Cooling Load Meter',
|
||||
'Bus / System',
|
||||
'Unknown Medium',
|
||||
'Reserved (10)',
|
||||
'Reserved (11)',
|
||||
'Reserved (12)',
|
||||
'Reserved (13)',
|
||||
'Reserved (14)',
|
||||
'Reserved (15)',
|
||||
'Cold Water',
|
||||
'Dual Water',
|
||||
'Pressure',
|
||||
'A/D Converter',
|
||||
)[data]
|
||||
return res
|
||||
except IndexError:
|
||||
raise MediumConversionException
|
||||
|
42
test1.py
42
test1.py
@ -7,12 +7,15 @@ Created on 11.06.2015
|
||||
import unittest
|
||||
import MeterbusLib
|
||||
import MeterbusTypeConversion
|
||||
import MeterbusLibExceptions
|
||||
|
||||
|
||||
class TestFrameParsing(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# dishwasher, electric
|
||||
self.inputOkLongFrame_1phase_electric = "68 38 38 68 08 53 72 17 00 13 00 2E 19 24 02 D6 00 00 00 8C 10 04 01 02 00 00 8C 11 04 01 02 00 00 02 FD C9 FF 01 E4 00 02 FD DB FF 01 03 00 02 AC FF 01 01 00 82 40 AC FF 01 FA FF 20 16"
|
||||
self.inputNOkLongFrame_1phase_electric_wrong_medium = "68 38 38 68 08 53 72 17 00 13 00 2E 19 24 FE D6 00 00 00 8C 10 04 01 02 00 00 8C 11 04 01 02 00 00 02 FD C9 FF 01 E4 00 02 FD DB FF 01 03 00 02 AC FF 01 01 00 82 40 AC FF 01 FA FF 1c 16"
|
||||
self.inputNOkLongFrame_1phase_electric_wrong_signature = "68 38 38 68 08 53 72 17 00 13 00 2E 19 24 02 D6 00 00 01 8C 10 04 01 02 00 00 8C 11 04 01 02 00 00 02 FD C9 FF 01 E4 00 02 FD DB FF 01 03 00 02 AC FF 01 01 00 82 40 AC FF 01 FA FF 21 16"
|
||||
|
||||
# electricity
|
||||
self.inputOkLongFrame_3phase_electric = "68 92 92 68 08 50 72 81 14 01 11 2E 19 16 02 88 00 00 00 8C 10 04 58 43 86 00 8C 11 04 58 43 86 00 8C 20 04 00 00 00 00 8C 21 04 00 00 00 00 02 FD C9 FF 01 E4 00 02 FD DB FF 01 5A 00 02 AC FF 01 D2 00 82 40 AC FF 01 00 00 02 FD C9 FF 02 DF 00 02 FD DB FF 02 0F 00 02 AC FF 02 21 00 82 40 AC FF 02 FD FF 02 FD C9 FF 03 E3 00 02 FD DB FF 03 04 00 02 AC FF 03 02 00 82 40 AC FF 03 F4 FF 02 FF 68 00 00 02 AC FF 00 F5 00 82 40 AC FF 00 F1 FF 01 FF 13 00 F4 16"
|
||||
@ -51,6 +54,22 @@ class TestFrameParsing(unittest.TestCase):
|
||||
self.assertEqual(telegram.frame.address, 0x53);
|
||||
self.assertEqual(telegram.frame.ciField, 0x72);
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.identNr, "17001300")
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.manufacturer, "FIN")
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.version, 36)
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.medium, "Electrity")
|
||||
|
||||
def test_OK_LongFrame_1phase_electric_wrong_medium(self):
|
||||
telegram = MeterbusLib.Telegram()
|
||||
telegram.fromHexString(self.inputNOkLongFrame_1phase_electric_wrong_medium)
|
||||
with self.assertRaises(MeterbusLibExceptions.MediumConversionException):
|
||||
telegram.parse()
|
||||
|
||||
def test_OK_LongFrame_1phase_electric_wrong_signature(self):
|
||||
telegram = MeterbusLib.Telegram()
|
||||
telegram.fromHexString(self.inputNOkLongFrame_1phase_electric_wrong_signature)
|
||||
with self.assertRaises(MeterbusLibExceptions.InvalidFrameException):
|
||||
telegram.parse()
|
||||
|
||||
|
||||
def test_OK_LongFrame_3phase_electric(self):
|
||||
telegram = MeterbusLib.Telegram()
|
||||
@ -61,6 +80,9 @@ class TestFrameParsing(unittest.TestCase):
|
||||
self.assertEqual(telegram.frame.address, 0x50);
|
||||
self.assertEqual(telegram.frame.ciField, 0x72);
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.identNr, "81140111")
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.manufacturer, "FIN")
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.version, 22)
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.medium, "Electrity")
|
||||
|
||||
def test_OK_LongFrame_water(self):
|
||||
telegram = MeterbusLib.Telegram()
|
||||
@ -71,6 +93,10 @@ class TestFrameParsing(unittest.TestCase):
|
||||
self.assertEqual(telegram.frame.address, 0x30);
|
||||
self.assertEqual(telegram.frame.ciField, 0x72);
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.identNr, "45714300")
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.manufacturer, "HYD")
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.version, 37)
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.medium, "Water")
|
||||
|
||||
|
||||
def test_OK_LongFrame_gas(self):
|
||||
telegram = MeterbusLib.Telegram()
|
||||
@ -81,6 +107,9 @@ class TestFrameParsing(unittest.TestCase):
|
||||
self.assertEqual(telegram.frame.address, 0x40);
|
||||
self.assertEqual(telegram.frame.ciField, 0x72);
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.identNr, "43605200")
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.manufacturer, "ACW")
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.version, 20)
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.medium, "Gas")
|
||||
|
||||
def test_OK_LongFrame_thermometer(self):
|
||||
telegram = MeterbusLib.Telegram()
|
||||
@ -91,6 +120,9 @@ class TestFrameParsing(unittest.TestCase):
|
||||
self.assertEqual(telegram.frame.address, 0x21);
|
||||
self.assertEqual(telegram.frame.ciField, 0x72);
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.identNr, "00000000")
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.manufacturer, "@@@")
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.version, 1)
|
||||
self.assertEqual(telegram.frame.fixedDataHeader.medium, "Other")
|
||||
|
||||
def test_OK_SingleCharacter(self):
|
||||
telegram = MeterbusLib.Telegram()
|
||||
@ -118,31 +150,31 @@ class TestFrameParsing(unittest.TestCase):
|
||||
def test_NOk_Shortframe_checksum_failure(self):
|
||||
telegram = MeterbusLib.Telegram()
|
||||
telegram.fromHexString(self.inputNOkShortframe_checksum_failure)
|
||||
with self.assertRaises(MeterbusLib.InvalidChecksumException):
|
||||
with self.assertRaises(MeterbusLibExceptions.InvalidChecksumException):
|
||||
telegram.parse()
|
||||
|
||||
def test_NOk_LongFrame_thermometer_checksum_failure(self):
|
||||
telegram = MeterbusLib.Telegram()
|
||||
telegram.fromHexString(self.inputNOkLongFrame_thermometer_checksum_failure)
|
||||
with self.assertRaises(MeterbusLib.InvalidChecksumException):
|
||||
with self.assertRaises(MeterbusLibExceptions.InvalidChecksumException):
|
||||
telegram.parse()
|
||||
|
||||
def test_NOk_Controlframe_wrong_stopcode(self):
|
||||
telegram = MeterbusLib.Telegram()
|
||||
telegram.fromHexString(self.inputNOKControlframe_wrong_stopcode)
|
||||
with self.assertRaises(MeterbusLib.InvalidStopCharException):
|
||||
with self.assertRaises(MeterbusLibExceptions.InvalidStopCharException):
|
||||
telegram.parse()
|
||||
|
||||
def test_NOk_Controlframe_wrong_secondlength(self):
|
||||
telegram = MeterbusLib.Telegram()
|
||||
telegram.fromHexString(self.inputNOKControlframe_wrong_secondlength)
|
||||
with self.assertRaises(MeterbusLib.InvalidSecondLengthException):
|
||||
with self.assertRaises(MeterbusLibExceptions.InvalidSecondLengthException):
|
||||
telegram.parse()
|
||||
|
||||
def test_NOk_wrong_startcode(self):
|
||||
telegram = MeterbusLib.Telegram()
|
||||
telegram.fromHexString(self.inputNOk_wrong_startcode)
|
||||
with self.assertRaises(MeterbusLib.InvalidStartCharException):
|
||||
with self.assertRaises(MeterbusLibExceptions.InvalidStartCharException):
|
||||
telegram.parse()
|
||||
|
||||
def test_bcd(self):
|
||||
|
18
test2.py
18
test2.py
@ -4,19 +4,11 @@ Created on 11.06.2015
|
||||
@author: dehottgw
|
||||
'''
|
||||
|
||||
class A(object):
|
||||
def __init__(self, x):
|
||||
self.x = x
|
||||
|
||||
def out(self):
|
||||
print("X is %s" % self.x)
|
||||
import MeterbusLib
|
||||
|
||||
class B(A):
|
||||
pass
|
||||
inputOkLongFrame_1phase_electric = "68 38 38 68 08 53 72 17 00 13 00 2E 19 24 02 D6 00 00 00 8C 10 04 01 02 00 00 8C 11 04 01 02 00 00 02 FD C9 FF 01 E4 00 02 FD DB FF 01 03 00 02 AC FF 01 01 00 82 40 AC FF 01 FA FF 20 16"
|
||||
|
||||
|
||||
a = A('abc')
|
||||
a.out()
|
||||
|
||||
b = B('xyz')
|
||||
b.out()
|
||||
telegram = MeterbusLib.Telegram()
|
||||
telegram.fromHexString(inputOkLongFrame_1phase_electric)
|
||||
telegram.parse()
|
||||
|
Reference in New Issue
Block a user