DataInformationBlock calculations

This commit is contained in:
hg 2015-06-12 12:13:34 +02:00
parent 53789088f2
commit d64e8bff98
4 changed files with 253 additions and 48 deletions

View File

@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/MeterbusLib.py=iso-8859-15

View File

@ -1,3 +1,6 @@
# -*- coding: iso-8859-15 -*-
'''
Created on 11.06.2015
@ -7,6 +10,7 @@ Created on 11.06.2015
import MeterbusTypeConversion
from MeterbusLibExceptions import *
import struct
class Frame(object):
def __init__(self, startCharacter, frameLength, firstPayload, telegram):
@ -46,6 +50,10 @@ class SingleCharacter(Frame):
def parse2(self):
pass
def getJSON(self):
j = {}
return j
class ShortFrame(Frame):
@ -55,6 +63,10 @@ class ShortFrame(Frame):
def parse2(self):
self.cField = self.telegram[1]
self.address = self.telegram[2]
def getJSON(self):
j = {'c': self.cField, 'a': self.address}
return j
class ControlFrame(Frame):
@ -69,6 +81,10 @@ class ControlFrame(Frame):
self.cField = self.telegram[4]
self.address = self.telegram[5]
self.ciField = self.telegram[6]
def getJSON(self):
j = {'c': self.cField, 'a': self.address, 'ci': self.ciField}
return j
class LongFrame(ControlFrame):
@ -83,9 +99,14 @@ class LongFrame(ControlFrame):
self.medium = MeterbusTypeConversion.mediumCode(self.data[7])
self.accessNo = self.data[8]
self.status = self.data[9]
self.signature = self.data[10:]
self.signature = [s for s in self.data[10:]]
if self.signature[0] != 0 or self.signature[1] != 0:
raise InvalidFrameException("signature should be zero")
def getJSON(self):
j = {'identNr': self.identNr, 'manufacturer': self.manufacturer, 'version': self.version, 'medium': self.medium,
'accessNo': self.accessNo, 'status': self.status, 'signature': self.signature}
return j
class DataInformationElement(object):
def __init__(self, data):
@ -109,8 +130,200 @@ class LongFrame(ControlFrame):
return j
class DataInformationBlock(DataInformationElement):
VALUE_LENGTH_MAP = [0, 1, 2, 3, 4, 4, 6, 8, 0, 1, 2, 3, 4, -1, 6, 0 ]
DATA_FIELD_CODES = (
('No Data', 0, None),
('8 Bit Integer', 1, lambda x: x[0]),
('16 Bit Integer', 2, lambda x: (x[1] << 8) + x[0]),
('24 Bit Integer', 3, lambda x: (x[2] << 16) + (x[1] << 8) + x[0]),
('32 Bit Integer', 4, lambda x: (x[3] << 24) + (x[2] << 16) + (x[1] << 8) + x[0]),
('32 Bit Real', 4, lambda x: struct.unpack('f', x.getAll())[0]),
('48 Bit Integer', 6, lambda x: (x[5] << 40) + (x[4] << 32) + (x[3] << 24) + (x[2] << 16) + (x[1] << 8) + x[0]),
('64 Bit Integer', 8, lambda x: (x[7] << 56) + (x[6] << 48) + (x[5] << 40) + (x[4] << 32) + (x[3] << 24) + (x[2] << 16) + (x[1] << 8) + x[0]),
('Selection for Readout', 0, None),
('2 Digit BCD', 1, lambda x: MeterbusTypeConversion.bcd(x)),
('4 Digit BCD', 2, lambda x: MeterbusTypeConversion.bcd(x)),
('6 Digit BCD', 3, lambda x: MeterbusTypeConversion.bcd(x)),
('8 Digit BCD', 4, lambda x: MeterbusTypeConversion.bcd(x)),
('variable length', -1, None),
('12 Digit BCD', 6, lambda x: MeterbusTypeConversion.bcd(x)),
('Special Function', 0, None),
)
VIF_CODES = (
(0b01111000, 0b00000000, 'Energy', 'Wh', 0b00000111, lambda d: 10**(d-3)),
(0b01111000, 0b00001000, 'Energy', 'J', 0b00000111, lambda d: 10**d),
(0b01111000, 0b00010000, 'Volume', 'm**3', 0b00000111, lambda d: 10**(d-6)),
(0b01111000, 0b00011000, 'Mass', 'kg', 0b00000111, lambda d: 10**(d-3)),
(0b01111100, 0b00100000, 'On Time', 'TimeCalc', 0b00000011, lambda d: d),
(0b01111100, 0b00100100, 'Operating Time', 'TimeCalc', 0b00000011, lambda d: d),
(0b01111000, 0b00101000, 'Power', 'W', 0b00000111, lambda d: 10**(d-3)),
(0b01111000, 0b00110000, 'Power', 'J/h', 0b00000111, lambda d: 10**d),
(0b01111000, 0b00111000, 'Volume Flow', 'm**3/h', 0b00000111, lambda d: 10**(d-6)),
(0b01111000, 0b01000000, 'Volume Flow ext.', 'm**3/min', 0b00000111, lambda d: 10**(d-7)),
(0b01111000, 0b01001000, 'Volume Flow ext.', 'm**3/s', 0b00000111, lambda d: 10**(d-9)),
(0b01111000, 0b01010000, 'Mass Flow', 'kg/h', 0b00000111, lambda d: 10**(d-3)),
(0b01111100, 0b01011000, 'Flow Temperature', '°C', 0b00000011, lambda d: 10**(d-3)),
(0b01111100, 0b01011100, 'Return Temperature', '°C', 0b00000011, lambda d: 10**(d-3)),
(0b01111100, 0b01100000, 'Temperature Diff.', 'K', 0b00000011, lambda d: 10**(d-3)),
(0b01111100, 0b01100100, 'Extern. Temp', '°C', 0b00000011, lambda d: 10**(d-3)),
(0b01111100, 0b01101000, 'Pressure', 'bar', 0b00000011, lambda d: 10**(d-3)),
(0b01111110, 0b01101100, 'Time Point', 'TimePointCalc', 0b00000001,lambda d: d),
(0b01111111, 0b01101110, 'Units for H.C.A.', '', 0, None),
(0b01111111, 0b01101111, 'Reserved', '', 0, None),
(0b01111100, 0b01110000, 'Averaging Duration', 'TimeCalc', 0b00000011, lambda d: d),
(0b01111100, 0b01110100, 'Actuality Duration', 'TimeCalc', 0b00000011, lambda d: d),
(0b01111111, 0b01111000, 'Fabrication Number', '', 0, None),
(0b01111111, 0b01111001, 'Enhanced Ident.', '', 0, None),
(0b01111111, 0b01111010, 'Bus Address', '', 0, None),
(0b11111111, 0b11111011, 'Extensions 1', 'Table8.4.4b', 0, None),
(0b11111111, 0b11111101, 'Extensions 2', 'Table8.4.4a', 0, None),
)
TABLE844a = (
(0b01111100, 0b00000000, 'Credit', 'Currency Unit', 0b00000011, lambda d: 10*(d-3)),
(0b01111100, 0b00000100, 'Debit', 'Currency Unit', 0b00000011, lambda d: 10*(d-3)),
(0b01111111, 0b00001000, 'Access Number', '', 0, None),
(0b01111111, 0b00001001, 'Medium', '', 0, None),
(0b01111111, 0b00001010, 'Manufacturer', '', 0, None),
(0b01111111, 0b00001011, 'Parameter set ident','', 0, None),
(0b01111111, 0b00001100, 'Model / Version', '', 0, None),
(0b01111111, 0b00001101, 'Hardware Version', '', 0, None),
(0b01111111, 0b00001110, 'Firmware Version', '', 0, None),
(0b01111111, 0b00001111, 'Software Version', '', 0, None),
(0b01111111, 0b00010000, 'Customer Location', '', 0, None),
(0b01111111, 0b00010001, 'Customer', '', 0, None),
(0b01111111, 0b00010010, 'Access Code User', '', 0, None),
(0b01111111, 0b00010011, 'Access Code Operator','', 0, None),
(0b01111111, 0b00010100, 'Access Code System Operator','', 0, None),
(0b01111111, 0b00010101, 'Access Code Developer','', 0, None),
(0b01111111, 0b00010110, 'Password', '', 0, None),
(0b01111111, 0b00010111, 'Error Flags', '', 0, None),
(0b01111111, 0b00011000, 'Error Mask', '', 0, None),
(0b01111111, 0b00011001, 'Reserved', '', 0, None),
(0b01111111, 0b00011010, 'Digital Output', '', 0, None),
(0b01111111, 0b00011011, 'Digital Input', '', 0, None),
(0b01111111, 0b00011100, 'Baudrate', 'Baud', 0, None),
(0b01111111, 0b00011101, 'response delay time', '', 0, None),
(0b01111111, 0b00011110, 'retry', '', 0, None),
(0b01111111, 0b00011111, 'Reserved', '', 0, None),
(0b01111111, 0b00100000, 'First storage', '', 0, None),
(0b01111111, 0b00100001, 'Last storage', '', 0, None),
(0b01111111, 0b00100010, 'Size of storage blk', '', 0, None),
(0b01111111, 0b00100011, 'Reserved', '', 0, None),
(0b01111100, 0b00100100, 'Storage interval', '', 0b00000011, lambda d: d),
(0b01111111, 0b00101000, 'Storage interval month', '', 0, None),
(0b01111111, 0b00101001, 'Storage interval years', '', 0, None),
(0b01111111, 0b00101010, 'Reserved', '', 0, None),
(0b01111111, 0b00101011, 'Reserved', '', 0, None),
(0b01111111, 0b00101100, 'Duration since last read', '', 0b00000011, lambda d: d),
(0b01111111, 0b00110000, 'Start of tariff', '', 0, None),
(0b01111111, 0b00110000, 'Duration of tariff', '', 0b00000011, lambda d: d),
(0b01111111, 0b00110100, 'Period of tariff', '', 0b00000011, lambda d: d),
(0b01111111, 0b00111010, 'dimensionless', '', 0, None),
(0b01111111, 0b00111011, 'Reserved', '', 0, None),
(0b01111100, 0b00111100, 'Reserved', '', 0, None),
(0b01110000, 0b01000000, 'Voltage', 'V', 0b00001111, lambda d: 10**(d-9)),
(0b01110000, 0b01010000, 'Current', 'A', 0b00001111, lambda d: 10**(d-12)),
(0b01111111, 0b01100000, 'Reset Counter', '', 0, None),
(0b01111111, 0b01100001, 'Cumulation Counter', '', 0, None),
(0b01111111, 0b01100010, 'Control Signal', '', 0, None),
(0b01111111, 0b01100011, 'Day of week', '', 0, None),
(0b01111111, 0b01100100, 'Week number', '', 0, None),
(0b01111111, 0b01100101, 'Time point of day change', '', 0, None),
(0b01111111, 0b01100110, 'State of parameter activation', '', 0, None),
(0b01111111, 0b01100111, 'Special supplier info', '', 0, None),
(0b01111100, 0b01101000, 'Duration since last cumulation', '', 0b00000011, lambda d: d),
(0b01111100, 0b01101100, 'Operating time battery', '', 0b00000011, lambda d: d),
(0b01111111, 0b01110000, 'Date/time of bat. change', '', 0, None)
)
TABLE844b = (
(0b01111110, 0b00000000, 'Energy', 'MWh', 0b00000001, lambda d: 10**(d-1)),
(0b01111110, 0b00000010, 'Reserved', '', 0, None),
(0b01111100, 0b00000100, 'Reserved', '', 0, None),
(0b01111110, 0b00001000, 'Energy', 'GJ', 0b00000001, lambda d: 10**(d-1)),
(0b01111110, 0b00001010, 'Reserved', '', 0, None),
(0b01111100, 0b00001100, 'Reserved', '', 0, None),
(0b01111110, 0b00010000, 'Volume', 'm**3', 0b00000001, lambda d: 10**(d+2)),
(0b01111110, 0b00010010, 'Reserved', '', 0, None),
(0b01111100, 0b00010100, 'Reserved', '', 0, None),
(0b01111110, 0b00011000, 'Mass', 't', 0b00000001, lambda d: 10**(d+2)),
(0b01111111, 0b00100001, 'Volume', 'feet**3', 0, lambda d: 0.1),
(0b01111111, 0b00100010, 'Volume', 'am. gallon', 0, lambda d: 0.1),
(0b01111111, 0b00100011, 'Volume', 'am. gallon', 0, lambda d: 1),
(0b01111111, 0b00100100, 'Volume flow', 'am. gallon/min', 0, lambda d: 0.001),
(0b01111111, 0b00100101, 'Volume flow', 'am. gallon/min', 0, lambda d: 1),
(0b01111111, 0b00100110, 'Volume flow', 'am. gallon/h', 0, lambda d: 1),
(0b01111111, 0b00100111, 'Reserved', '', 0, None),
(0b01111110, 0b00101000, 'Power', 'MW', 0b00000001, lambda d: 10**(d-1)),
(0b01111110, 0b00101010, 'Reserved', '', 0, None),
(0b01111100, 0b00101100, 'Reserved', '', 0, None),
(0b01111110, 0b00110000, 'Power', 'GJ/h', 0b00000001, lambda d: 10**(d-1)),
(0b01111100, 0b01011000, 'Flow Temperature', '°F', 0b00000011, lambda d: 10**(d-3)),
(0b01111100, 0b01011100, 'Return Temperature', '°F', 0b00000011, lambda d: 10**(d-3)),
(0b01111100, 0b01100000, 'Temperature Diff.', '°F', 0b00000011, lambda d: 10**(d-3)),
(0b01111100, 0b01100100, 'Extern. Temp.', '°F', 0b00000011, lambda d: 10**(d-3)),
(0b01111000, 0b01101000, 'Reserved', '', 0, None),
(0b01111100, 0b01110000, 'Cold/Warm Temp.Limit', '°F', 0b00000011, lambda d: 10**(d-3)),
(0b01111100, 0b01110100, 'Cold/Warm Temp.Limit', '°C', 0b00000011, lambda d: 10**(d-3)),
(0b01111000, 0b01111000, 'cumul. count max power', 'W', 0b00000111, lambda d: 10**(d-3))
)
TIME_UNITS = ('seconds', 'minutes', 'hours', 'days')
TIME_POINT_UNITS = ('date', 'time & date')
@classmethod
def extVifCalc(cls, table, vife):
retDescr = 'unknown'
retUnit = 'unknown'
retRange = 'unknown'
for vifeCode in table:
# print vifeCode
(mask, value, descr, unit, rangeMask, rangeFunc) = vifeCode
if (vife & mask) == value:
if rangeFunc == None:
retRange = 1
else:
rangeHelper = vife & rangeMask
retRange = rangeFunc(rangeHelper)
retDescr = descr
retUnit = unit
break
return (retDescr, retUnit, retRange)
@classmethod
def vifCalc(cls, vif, vife):
retDescr = 'unknown'
retUnit = 'unknown'
retRange = 1
for vifCode in cls.VIF_CODES:
(mask, value, descr, unit, rangeMask, rangeFunc) = vifCode
if (vif & mask) == value:
if unit == 'TimeCalc':
retDescr = descr
retUnit = cls.TIME_UNITS[vif & rangeMask]
retRange = None
elif unit == 'Table8.4.4a':
(retDescr, retUnit, retRange) = cls.extVifCalc(cls.TABLE844a, vife[0])
elif unit == 'Table8.4.4b':
(retDescr, retUnit, retRange) = cls.extVifCalc(cls.TABLE844b, vife[0])
elif unit == 'TimePointCalc':
retDescr = descr
retUnit = cls.TIME_POINT_UNITS[vif & rangeMask]
retRange = None
else:
retDescr = descr
retUnit = unit
if rangeFunc != None:
rangeHelper = vif & rangeMask
retRange = rangeFunc(rangeHelper)
break
# print("vifCalc: %s %s %s %s" % (retDescr, retRange, retUnit, rangeHelper))
return (retDescr, retUnit, retRange)
def parse(self):
self.dif = self.data[self.consumed]
self.consumed += 1
@ -134,51 +347,27 @@ class LongFrame(ControlFrame):
break
self.vifData = []
if self.vif & 0x7f == 0x7c:
dataLength = self.data[self.consumed]
vifDataLength = 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]]]
self.vifData = [v for v in self.data[self.consumed:self.consumed+vifDataLength]]
self.consumed += vifDataLength
userDataLength = LongFrame.DataInformationBlock.DATA_FIELD_CODES[self.dif&0x0f][1]
self.userData = [u for u in self.data[self.consumed:self.consumed+userDataLength]]
self.consumed += userDataLength
self.value = LongFrame.DataInformationBlock.DATA_FIELD_CODES[self.dif & 0x0f][2](self.userData)
self.dataType = LongFrame.DataInformationBlock.DATA_FIELD_CODES[self.dif & 0x0f][0]
(self.quantity, self.unit, self.factor) = LongFrame.DataInformationBlock.vifCalc(self.vif, self.vife)
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()}
'vifData': self.vifData, 'userData':self.userData,
'dataType': self.dataType, 'value':self.value,
'quantity': self.quantity, 'factor': self.factor, 'unit': self.unit,
}
return j
@ -193,14 +382,20 @@ class LongFrame(ControlFrame):
consumed = 0
#print("Telegram length: %d" % len(self.telegram))
while True:
curDib = LongFrame.DataInformationElement.create(self.telegram[(19 + consumed):])
curDib = LongFrame.DataInformationElement.create(self.telegram[(19 + consumed):-2])
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:
#print("DIB: %s" % str(curDib.getJSON()))
# 3 for the fields, 12 for the fixed data header, 2 for checksum and stop code, 1 off since consumed is an index and no length
if (consumed + 3 + 12 + 2 - 1) >= self.payloadLength:
break
def getJSON(self):
superJSON = super(LongFrame, self).getJSON()
j = {'header': self.fixedDataHeader.getJSON(), 'dib': [dib.getJSON() for dib in self.dib]}
j.update(superJSON)
return j
class Telegram(object):
def __init__(self):
@ -228,5 +423,9 @@ class Telegram(object):
self.frame.parse()
def getJSON(self):
j = {'frameType': self.frame.__class__.__name__, 'frame': self.frame.getJSON()}
return j

View File

@ -10,7 +10,7 @@ from MeterbusLibExceptions import MediumConversionException
def bcd(data):
v = ""
for c in data:
for c in reversed(data):
v += str((c & 0xf0) >> 4)
v += str(c & 0x0f)
return v

View File

@ -6,9 +6,13 @@ Created on 11.06.2015
import MeterbusLib
import json
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"
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"
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"
telegram = MeterbusLib.Telegram()
telegram.fromHexString(inputOkLongFrame_1phase_electric)
telegram.fromHexString(inputOkLongFrame_3phase_electric)
telegram.parse()
print(json.dumps(telegram.getJSON(), indent=2))