Posts: 115
Threads: 10
Joined: Nov 2019
Apr-27-2020, 12:08 PM
(This post was last modified: Apr-27-2020, 12:08 PM by Makada.)
Ive done a little research and tried different versions of python.
I found out up to and including version 3.7.4 the error is gone.
But the code doesnt show data when i run it.
Posts: 115
Threads: 10
Joined: Nov 2019
Hi, i still cant get it to work.
Maybe someone is prepared to install the package on their windows 10 and test/recognize where to find the fault and have a solution hopefully
The package is:
pycampbellcr1000
https://pycampbellcr1000.readthedocs.io/en/latest/
Posts: 115
Threads: 10
Joined: Nov 2019
Nov-11-2020, 06:04 PM
(This post was last modified: Nov-11-2020, 06:04 PM by Makada.)
Hi,
I managed to get rid of the buffer error.
But now im facing a unpack error.
See error and code which generate the error.
Python 3.8.6 (tags/v3.8.6:db45529, Sep 23 2020, 15:52:53) [MSC v.1927 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license()" for more information.
>>>
=============== RESTART: C:\Users\Makada\Desktop\cr1000 modbus.py ==============
Device is connected: True
2020-11-11 18:56:05
Traceback (most recent call last):
File "C:\Users\Makada\Desktop\cr1000 modbus.py", line 5, in <module>
print(device.list_tables())
File "C:\Python38\lib\site-packages\pycampbellcr1000\device.py", line 182, in list_tables
return [item['Header']['TableName'] for item in self.table_def]
File "C:\Python38\lib\site-packages\pycampbellcr1000\utils.py", line 65, in __get__
value = self.func(obj)
File "C:\Python38\lib\site-packages\pycampbellcr1000\device.py", line 177, in table_def
tabledef = self.pakbus.parse_tabledef(data)
File "C:\Python38\lib\site-packages\pycampbellcr1000\pakbus.py", line 700, in parse_tabledef
values, size = self.decode_bin(types, raw[offset:])
File "C:\Python38\lib\site-packages\pycampbellcr1000\pakbus.py", line 318, in decode_bin
value = struct.unpack(str(fmt), buff[offset:offset + size])
struct.error: unpack requires a buffer of 4 bytes
>>> # -*- coding: utf-8 -*-
'''
PyCampbellCR1000.pakbus
-----------------------
PakBus protocol Implementation.
Original Author: Dietrich Feist, Max Planck Institute for Biogeochemistry,
Jena Germany (PyPak)
:copyright: Copyright 2012 Salem Harrache and contributors, see AUTHORS.
:license: GNU GPL v3.
'''
from __future__ import division, unicode_literals
import struct
import time
from .compat import ord, chr, is_text, is_py3, bytes
from .logger import LOGGER
from .utils import Singleton
from .exceptions import DeliveryFailureException
from .utils import bytes_to_hex, nsec_to_time
class Transaction(Singleton):
id = 0
def next_id(self):
self.id += 1
self.id &= 0xFF
return self.id
class PakBus(object):
'''Interface for a pakbus client.
:param link: A `PyLink` connection.
:param dest_addr: Destination physical address (12-bit int) (default dest)
:param dest: Destination node ID (12-bit int) (default 0x001)
:param src_addr: Source physical address (12-bit int) (default src)
:param src: Source node ID (12-bit int) (default 0x802)
:param security_code: 16-bit security code (default 0x0000)
'''
DATATYPE = {
'Byte': {'code': 1, 'fmt': 'B', 'size': 1},
'UInt2': {'code': 2, 'fmt': '>H', 'size': 2},
'UInt4': {'code': 3, 'fmt': '>L', 'size': 4},
'Int1': {'code': 4, 'fmt': 'b', 'size': 1},
'Int2': {'code': 5, 'fmt': '>h', 'size': 2},
'Int4': {'code': 6, 'fmt': '>l', 'size': 4},
'FP2': {'code': 7, 'fmt': '>H', 'size': 2},
'FP3': {'code': 15, 'fmt': '3c', 'size': 3},
'FP4': {'code': 8, 'fmt': '4c', 'size': 4},
'IEEE4B': {'code': 9, 'fmt': '>f', 'size': 4},
'IEEE8B': {'code': 18, 'fmt': '>d', 'size': 8},
'Bool8': {'code': 17, 'fmt': 'B', 'size': 1},
'Bool': {'code': 10, 'fmt': 'B', 'size': 1},
'Bool2': {'code': 27, 'fmt': '>H', 'size': 2},
'Bool4': {'code': 28, 'fmt': '>L', 'size': 4},
'Sec': {'code': 12, 'fmt': '>l', 'size': 4},
'USec': {'code': 13, 'fmt': '6c', 'size': 6},
'NSec': {'code': 14, 'fmt': '>2l', 'size': 8},
'ASCII': {'code': 11, 'fmt': 's', 'size': None},
'ASCIIZ': {'code': 16, 'fmt': 's', 'size': None},
'Short': {'code': 19, 'fmt': '<h', 'size': 2},
'Long': {'code': 20, 'fmt': '<l', 'size': 4},
'UShort': {'code': 21, 'fmt': '<H', 'size': 2},
'ULong': {'code': 22, 'fmt': '<L', 'size': 4},
'IEEE4L': {'code': 24, 'fmt': '<f', 'size': 4},
'IEEE8L': {'code': 25, 'fmt': '<d', 'size': 8},
'SecNano': {'code': 23, 'fmt': '<2l', 'size': 8},
}
# link state
RING = 0x9
READY = 0xA
FINISHED = 0xB
def __init__(self, link, dest_addr=None, dest=0x001,
src_addr=None, src=0x802, security_code=0x0000):
self.link = link
self.src = src
if (src_addr != None):
self.src_addr = src_addr
else:
self.src_addr = src
self.dest = dest
if (dest_addr != None):
self.dest_addr = dest_addr
else:
self.dest_addr = dest
self.security_code = security_code
self.transaction = Transaction()
LOGGER.info('Get the node attention')
self.link.write(b'\xBD\xBD\xBD\xBD\xBD\xBD')
def write(self, packet):
'''Send packet over PakBus.'''
LOGGER.info('Packet data: %s' % bytes_to_hex(packet))
LOGGER.info('Calculate signature for packet')
sign = self.compute_signature(packet)
LOGGER.info('Calculate signature nullifier to create packet')
nullifier = self.compute_signature_nullifier(sign)
frame = self.quote(b''.join((packet, nullifier)))
packet = b''.join((b'\xBD', frame, b'\xBD'))
LOGGER.info('Write: %s' % bytes_to_hex(packet))
self.link.write(packet)
def read(self):
'''Receive packet over PakBus.'''
all_bytes = []
byte = None
begin = time.time()
while byte != b'\xBD':
if (byte != None):
LOGGER.info('Read byte: %s' % bytes_to_hex(byte))
if time.time() - begin > self.link.timeout:
return None
# Read until first \xBD frame character
byte = self._read_one_byte()
while byte == b'\xBD':
# Read unitl first character other than \xBD
byte = self._read_one_byte()
while byte != b'\xBD':
# Read until next occurence of \xBD character
all_bytes.append(byte)
byte = self._read_one_byte()
# Unquote quoted characters
packet = b''.join(all_bytes)
LOGGER.info('Read packet: %s' % bytes_to_hex(packet))
packet = self.unquote(packet)
# Calculate signature (should be zero)
if self.compute_signature(packet):
LOGGER.error('Check signature : Error')
return None
else:
LOGGER.info('Check signature : OK')
# Strip last 2 signature bytes and return packet
return packet[:-2]
def _read_one_byte(self):
'''Read only one byte.'''
data = self.link.read(1)
if is_text(data):
return bytes(data.encode('utf-8'))
else:
return data
def wait_packet(self, transac_id=None):
'''Wait for an incoming packet.
:param transac_id: Expected transaction number.
'''
LOGGER.info('Wait packet with transaction %s' % transac_id)
data = self.read()
if data is None or data == b'':
return {}, {}
hdr, msg = self.decode_packet(data)
if hdr == {} or msg == {}:
return hdr, msg
# ignore packets that are not for us
LOGGER.info('src, SrcNodeId = <%x, %x>' %
(self.src, hdr['SrcNodeId']))
LOGGER.info('dest, DstNodeId = <%x, %x>' %
(self.dest, hdr['DstNodeId']))
if (hdr['DstNodeId'] != self.src):
return {}, {}
# Handle 'please wait' packets
if msg['TranNbr'] == transac_id and msg['MsgType'] == 0xa1:
timewait = msg['WaitSec']
LOGGER.info('Please Wait Message packet <%s sec>' % timewait)
time.sleep(timewait)
return self.wait_packet(transac_id)
# Handle failure message packets and raise exception
if msg['MsgType'] == 0x81:
raise DeliveryFailureException()
# This should be the packet we are waiting for
if msg['TranNbr'] == transac_id:
return hdr, msg
def pack_header(self, hi_proto, exp_more=0x2, link_state=None,
hops=0x0):
'''Generate PakBus header.
:param hi_proto: Higher level protocol code (4 bits). 0x0: PakCtrl,
0x1: BMP5
:param exp_more: Whether client should expect another packet (2 bits)
:param link_state: Link state (4 bits)
:param hops: Number of hops to destination (4 bits)
'''
LOGGER.info('Create header')
priority = 0x1
link_state = link_state or self.READY
# bitwise encoding of header fields
hdr = struct.pack(str('>4H'),
(link_state & 0xF) << 12 | (self.dest_addr & 0xFFF),
(exp_more & 0x3) << 14 | (priority & 0x3) << 12
| (self.src_addr & 0xFFF),
(hi_proto & 0xF) << 12 | (self.dest & 0xFFF),
(hops & 0xF) << 12 | (self.src & 0xFFF))
return hdr
def compute_signature(self, buff, seed=0xAAAA):
'''Compute signature for PakBus packets.'''
sig = seed
for x in buff:
x = ord(x)
j = sig
sig = (sig << 1) & 0x1FF
if sig >= 0x100:
sig += 1
sig = ((((sig + (j >> 8) + x) & 0xFF) | (j << 8))) & 0xFFFF
return sig
def compute_signature_nullifier(self, sig):
'''Compute signature nullifier needed to create valid PakBus
packets.'''
nulb = nullif = b''
for i in (1, 2):
sig = self.compute_signature(nulb, sig)
sig2 = (sig << 1) & 0x1FF
if sig2 >= 0x100:
sig2 += 1
nulb = chr((0x100 - (sig2 + (sig >> 8))) & 0xFF)
nullif += nulb
return nullif
def quote(self, packet):
'''Quote the PakBus packet.'''
LOGGER.info('Quote packet')
packet = packet.replace(b'\xBC', b'\xBC\xDC')
packet = packet.replace(b'\xBD', b'\xBC\xDD')
return packet
def unquote(self, packet):
'''Unquote the PakBus packet.'''
LOGGER.info('Unquote packet')
packet = packet.replace(b'\xBC\xDD', b'\xBD')
packet = packet.replace(b'\xBC\xDC', b'\xBC')
return packet
def encode_bin(self, types, values):
'''Encode binary data according to data type.'''
buff = [] # buffer for binary data
for i, type_ in enumerate(types):
fmt = self.DATATYPE[type_]['fmt'] # get default format for type_
value = values[i]
if type_ == 'ASCIIZ':
# special handling: nul-terminated string
value += '\0'
# Add nul to end of string
fmt_ = str('%d%s' % (len(value), fmt))
if is_py3:
enc = struct.pack(fmt_, bytes(value, encoding='utf8'))
else:
enc = struct.pack(fmt_, str(value))
elif type_ == 'ASCII':
# special handling: fixed-length string
fmt_ = str('%d%s' % (len(value), fmt))
enc = struct.pack(fmt_, str(value))
elif type_ == 'NSec':
# special handling: NSec time
enc = struct.pack(str(fmt), value[0], value[1])
else:
# default encoding scheme
enc = struct.pack(str(fmt), value)
buff.append(enc)
return b''.join(buff)
def decode_bin(self, types, buff, length=1):
'''Decode binary data according to data type.'''
offset = 0 # offset into buffer
values = [] # list of values to return
for type_ in types:
# get default format and size for Type
fmt = self.DATATYPE[type_]['fmt']
size = self.DATATYPE[type_]['size']
if type_ == 'ASCIIZ':
# special handling: nul-terminated string
nul = buff.index(b'\0', offset)
# find first '\0' after offset
if nul == -1:
value = buff[offset:]
else:
value = buff[offset:nul]
# return string without trailing '\0'
size = len(value) + 1
elif type_ == 'ASCII':
# special handling: fixed-length string
size = length
value = buff[offset:offset + size]
# return fixed-length string
elif type_ == 'FP2':
# special handling: FP2 floating point number
fp2 = struct.unpack(str(fmt), buff[offset:offset + size])
mant = fp2[0] & 0x1FFF # mantissa is in bits 1-13
exp = fp2[0] >> 13 & 0x3 # exponent is in bits 14-15
sign = fp2[0] >> 15 # sign is in bit 16
value = ((-1) ** sign * float(mant) / 10 ** exp, )
else:
# default decoding scheme
if buff[offset:offset + size]:
value = struct.unpack(str(fmt), buff[offset:offset + size])
else:
value = ''
# un-tuple single values
if len(value) == 1:
value = value[0]
values.append(value)
offset += size
# Return decoded values and current offset into buffer (size)
return values, offset
def decode_packet(self, data):
'''Decode packet from raw data.'''
LOGGER.info('Decode packet')
# pkt: buffer containing unquoted packet, signature nullifier stripped
# Initialize output variables
hdr = {'LinkState': None, 'DstPhyAddr': None, 'ExpMoreCode': None,
'Priority': None, 'SrcPhyAddr': None, 'HiProtoCode': None,
'DstNodeId': None, 'HopCnt': None, 'SrcNodeId': None}
msg = {'MsgType': None, 'TranNbr': None, 'raw': None}
# decode PakBus header
rawhdr = struct.unpack(str('>4H'), data[0:8]) # raw header bits
hdr['LinkState'] = rawhdr[0] >> 12
hdr['DstPhyAddr'] = rawhdr[0] & 0x0FFF
hdr['ExpMoreCode'] = (rawhdr[1] & 0xC000) >> 14
hdr['Priority'] = (rawhdr[1] & 0x3000) >> 12
hdr['SrcPhyAddr'] = rawhdr[1] & 0x0FFF
hdr['HiProtoCode'] = rawhdr[2] >> 12
hdr['DstNodeId'] = rawhdr[2] & 0x0FFF
hdr['HopCnt'] = rawhdr[3] >> 12
hdr['SrcNodeId'] = rawhdr[3] & 0x0FFF
# decode default message fields:
# raw message, message type and transaction number
msg['raw'] = data[8:]
values, size = self.decode_bin(('Byte', 'Byte'), msg['raw'][:2])
msg['MsgType'], msg['TranNbr'] = values
LOGGER.info('HiProtoCode, MsgType = <%x, %x>' %
(hdr['HiProtoCode'], msg['MsgType']))
# PakBus Control Packets
if hdr['HiProtoCode'] == 0 and msg['MsgType'] in (0x09, 0x89, 0xe):
msg = self.unpack_hello_response(msg)
elif hdr['HiProtoCode'] == 0 and msg['MsgType'] == 0x81:
msg = self.unpack_failure_response(msg)
elif hdr['HiProtoCode'] == 0 and msg['MsgType'] == 0x8f:
msg = self.unpack_getsettings_response(msg)
# BMP5 Application Packets
elif hdr['HiProtoCode'] == 1 and msg['MsgType'] == 0x89:
msg = self.unpack_collectdata_response(msg)
elif hdr['HiProtoCode'] == 1 and msg['MsgType'] == 0x97:
msg = self.unpack_clock_response(msg)
elif hdr['HiProtoCode'] == 1 and msg['MsgType'] == 0x98:
msg = self.unpack_getprogstat_response(msg)
elif hdr['HiProtoCode'] == 1 and msg['MsgType'] == 0x9c:
msg = self.unpack_filedownload_response(msg)
elif hdr['HiProtoCode'] == 1 and msg['MsgType'] == 0x9d:
msg = self.unpack_fileupload_response(msg)
elif hdr['HiProtoCode'] == 1 and msg['MsgType'] == 0xa1:
msg = self.unpack_pleasewait_response(msg)
else:
LOGGER.error('No implementation for <(%r, %r)> packet type'
% (hdr['HiProtoCode'], msg['MsgType']))
return hdr, msg
def get_hello_cmd(self):
'''Create Hello Command packet.'''
transac_id = self.transaction.next_id()
hdr = self.pack_header(0x0, 0x1, self.RING)
msg = self.encode_bin(['Byte', 'Byte', 'Byte', 'Byte', 'UInt2'],
[0x09, transac_id, 0x00, 0x02, 1800])
return b''.join((hdr, msg)), transac_id
def get_hello_response(self, transac_id):
'''Create Hello Response packet.'''
hdr = self.pack_header(0x0)
msg = self.encode_bin(['Byte', 'Byte', 'Byte', 'Byte', 'UInt2'],
[0x89, transac_id, 0x00, 0x02, 1800])
return b''.join([hdr, msg])
def unpack_hello_response(self, msg):
'''Create Hello Response packet.'''
raw = msg['raw'][2:]
values, size = self.decode_bin(['Byte', 'Byte', 'UInt2'], raw)
msg['IsRouter'], msg['HopMetric'], msg['VerifyIntv'] = values
return msg
def unpack_failure_response(self, msg):
'''Unpack Failure Response packet.'''
(msg['ErrCode'],), size = self.decode_bin(['Byte'], msg['raw'][2:])
return msg
def get_getsettings_cmd(self):
'''Create Getsettings Command packet.'''
transac_id = self.transaction.next_id()
hdr = self.pack_header(0x0)
msg = self.encode_bin(['Byte', 'Byte'], [0x0f, transac_id])
return b''.join([hdr, msg]), transac_id
def unpack_getsettings_response(self, msg):
'''Unpack Getsettings Response packet.'''
(msg['Outcome'],), size = self.decode_bin(['Byte'], msg['raw'][2:])
offset = size + 2
# Generate dictionary of all settings
msg['Settings'] = []
if msg['Outcome'] == 0x01:
values, size = self.decode_bin(['UInt2', 'Byte', 'Byte', 'Byte'],
msg['raw'][offset:])
msg['DeviceType'] = values[0]
msg['MajorVersion'] = values[1]
msg['MinorVersion'] = values[2]
msg['MoreSettings'] = values[3]
offset += size
while offset < len(msg['raw']):
# Get setting ID
[SettingId], size = self.decode_bin(['UInt2'],
msg['raw'][offset:])
offset += size
if not msg['raw'][offset:]:
break
# Get flags and length
[bit16], size = self.decode_bin(['UInt2'], msg['raw'][offset:])
LargeValue = (bit16 & 0x8000) >> 15
ReadOnly = (bit16 & 0x4000) >> 14
SettingLen = bit16 & 0x3FFF
offset += size
# Get value
SettingValue = msg['raw'][offset:offset + SettingLen]
offset += SettingLen
item = {'SettingId': SettingId,
'SettingValue': SettingValue,
'LargeValue': LargeValue,
'ReadOnly': ReadOnly}
msg['Settings'].append(item)
return msg
def get_collectdata_cmd(self, tablenbr, tabledefsig, mode=0x04, p1=0,
p2=0):
'''Create Collect Data Command packet
:param tablenbr: Table number that contain data.
:param tabledefsig: Table definition signature.
:param mode: Collection mode code (p1 and p2 will be used depending on
value).
:param p1: 1st parameter used to specify what to collect (optional)
:param p2: 2nd parameter used to specify what to collect (optional)
'''
transac_id = self.transaction.next_id()
# BMP5 Application Packet
hdr = self.pack_header(0x1)
msg = self.encode_bin(['Byte', 'Byte', 'UInt2', 'Byte'],
[0x09, transac_id, self.security_code,
mode])
# encode table number and signature
msg += self.encode_bin(['UInt2', 'UInt2'], [tablenbr, tabledefsig])
# add p1 and p2 according to collect mode
if (mode == 0x04) | (mode == 0x05):
# only P1 used (type UInt4)
msg += self.encode_bin(['UInt4'], [p1])
elif (mode == 0x06) | (mode == 0x08):
# P1 and P2 used (type UInt4)
msg += self.encode_bin(['UInt4', 'UInt4'], [p1, p2])
elif mode == 0x07:
# P1 and P2 used (type NSec)
msg += self.encode_bin(['NSec', 'NSec'], [p1, p2])
# add field list = all fields
msg += self.encode_bin(['UInt2'], [0])
return b''.join((hdr, msg)), transac_id
def unpack_collectdata_response(self, msg):
'''Unpack Collect Data Response body.'''
(msg['RespCode'],), size = self.decode_bin(['Byte'], msg['raw'][2:])
# return raw record data for later parsing
msg['RecData'] = msg['raw'][size + 2:]
return msg
def get_clock_cmd(self, adjustment=(0, 0)):
'''Create Clock Command packet.
:param adjustment: Clock adjustment (seconds, nanoseconds).
'''
transac_id = self.transaction.next_id()
# BMP5 Application Packet
hdr = self.pack_header(0x1)
msg = self.encode_bin(['Byte', 'Byte', 'UInt2', 'NSec'],
[0x17, transac_id, self.security_code,
adjustment])
return b''.join((hdr, msg)), transac_id
def unpack_clock_response(self, msg):
'''Unpack Clock Response packet.'''
values, size = self.decode_bin(['Byte', 'NSec'], msg['raw'][2:])
msg['RespCode'], msg['Time'] = values
return msg
def get_getprogstat_cmd(self):
'''Create Get Programming Statistics Transaction packet.'''
transac_id = self.transaction.next_id()
# BMP5 Application Packet
hdr = self.pack_header(0x1)
msg = self.encode_bin(['Byte', 'Byte', 'UInt2'],
[0x18, transac_id, self.security_code])
return b''.join((hdr, msg)), transac_id
def unpack_getprogstat_response(self, msg):
'''Unpack Get Programming Statistics Response packet.'''
# Get response code
(msg['RespCode'], ), size = self.decode_bin(['Byte'], msg['raw'][2:])
# Get report data if RespCode == 0
if msg['RespCode'] == 0:
types = ['ASCIIZ', 'UInt2', 'ASCIIZ', 'ASCIIZ', 'Byte', 'ASCIIZ',
'UInt2', 'NSec', 'ASCIIZ']
values, size = self.decode_bin(types, msg['raw'][3:])
item = {'OSVer': values[0], 'OSSig': values[1],
'SerialNbr': values[2], 'PowUpProg': values[3],
'CompState': values[4], 'ProgName': values[5],
'ProgSig': values[6], 'CompTime': values[7],
'CompResult': values[8]}
msg['Stats'] = item
return msg
def get_filedownload_cmd(self, filename, data, offset=0x00000000,
closeflag=0x00, transac_id=None):
'''Create Filedownload Command packet.
:param filename: File name as string
:param offset: Byte offset into the file or fragment
:param closeflag: Flag if file should be closed after this transaction
:param transac_id: Transaction number for continuing partial reads
'''
raise NotImplementedError('Filedownload transaction is not implemented'
' yet')
def unpack_filedownload_response(self, msg):
'''Unpack Filedownload Response packet.'''
raise NotImplementedError('Filedownload transaction is not implemented'
' yet')
def get_fileupload_cmd(self, filename, offset=0x00000000, swath=0x0200,
closeflag=0x01, transac_id=None):
'''Create Fileupload Command packet.
:param filename: File name as string
:param offset: Byte offset into the file or fragment
:param swath: Number of bytes to read
:param closeflag: Flag if file should be closed after this transaction
:param transac_id: Transaction number for continuing partial reads
(required by OS>=17!)
'''
if transac_id is None:
transac_id = self.transaction.next_id()
# BMP5 Application Packet
hdr = self.pack_header(0x1)
types = ['Byte', 'Byte', 'UInt2', 'ASCIIZ', 'Byte', 'UInt4', 'UInt2']
values = [0x1d, transac_id, self.security_code, filename, closeflag,
offset, swath]
msg = self.encode_bin(types, values)
return b''.join((hdr, msg)), transac_id
def unpack_fileupload_response(self, msg):
'''Unpack Fileupload Response packet.'''
values, size = self.decode_bin(['Byte', 'UInt4'], msg['raw'][2:7])
msg['RespCode'], msg['FileOffset'] = values
msg['FileData'] = msg['raw'][7:]
return msg
def unpack_pleasewait_response(self, msg):
'''Unpack PeaseWait Response packet.'''
values, size = self.decode_bin(['Byte', 'UInt2'], msg['raw'][2:])
msg['CmdMsgType'], msg['WaitSec'] = values
return msg
def get_bye_cmd(self):
'''Create Bye Command packet.'''
transac_id = self.transaction.next_id()
# PakBus Control Packet
hdr = self.pack_header(0x0, 0x0, self.FINISHED)
msg = self.encode_bin(['Byte', 'Byte'], [0x0d, 0x0])
return b''.join((hdr, msg)), transac_id
def parse_filedir(self, data):
'''Parse file directory format.'''
offset = 0 # offset into raw buffer
fd = {'files': []} # initialize file directory structure
[fd['DirVersion']], size = self.decode_bin(['Byte'], data[offset:])
offset += size
# Extract file entries
while len(data) > offset:
file_ = {} # file description
[filename], size = self.decode_bin(['ASCIIZ'], data[offset:])
offset += size
# end loop when file attribute list terminator reached
if filename == '':
break
file_['FileName'] = filename
values, size = self.decode_bin(['UInt4', 'ASCIIZ'], data[offset:])
file_['FileSize'], file_['LastUpdate'] = values
offset += size
# Read file attribute list
file_['Attribute'] = []
# initialize file attribute list (up to 12)
for i in range(12):
[attribute], size = self.decode_bin(['Byte'], data[offset:])
offset += size
if attribute:
# append file attribute to list
file_['Attribute'].append(attribute)
else:
break # End of attribute list reached
fd['files'].append(file_) # add file entry to list
return fd
def parse_tabledef(self, raw):
'''Parse table definition.'''
tabledef = [] # List of table definitions
offset = 0 # offset into raw buffer
fslversion, size = self.decode_bin(['Byte'], raw[offset:])
offset += size
# Parse list of table definitions
while offset < len(raw):
tblhdr = {} # table header
tblfld = [] # table field definitions
start = offset # start of table definition
# Extract table header data
types = ['ASCIIZ', 'UInt4', 'Byte', 'NSec', 'NSec']
values, size = self.decode_bin(types, raw[offset:])
tblhdr['TableName'] = values[0]
tblhdr['TableSize'] = values[1]
tblhdr['TimeType'] = values[2]
tblhdr['TblTimeInto'] = values[3]
tblhdr['TblInterval'] = values[4]
offset += size
# Extract field definitions
(fieldtype,), size = self.decode_bin(['Byte'], raw[offset:])
offset += size
while fieldtype != 0:
fld = {}
# Extract bits from fieldtype
fld['ReadOnly'] = fieldtype >> 7 # only Bit 7
# Convert fieldtype to ASCII FieldType (e.g. 'FP4') if possible
# else return numerical value
fld['FieldType'] = fieldtype & 0x7F # only Bits 0..6
for Type in self.DATATYPE.keys():
if fld['FieldType'] == self.DATATYPE[Type]['code']:
fld['FieldType'] = Type
break
# Extract field name
values, size = self.decode_bin(['ASCIIZ'], raw[offset:])
fld['FieldName'] = values[0]
offset += size
# Extract AliasName list
fld['AliasName'] = []
aliasname = b'00'
# Alias names list terminator reached
while aliasname != b'':
values, size = self.decode_bin(['ASCIIZ'], raw[offset:])
aliasname = values[0]
offset += size
if aliasname != b'':
fld['AliasName'].append(aliasname)
# Extract other mandatory field definition items
types = ['ASCIIZ', 'ASCIIZ', 'ASCIIZ', 'UInt4', 'UInt4']
values, size = self.decode_bin(types, raw[offset:])
fld['Processing'] = values[0]
fld['Units'] = values[1]
fld['Description'] = values[2]
fld['BegIdx'] = values[3]
fld['Dimension'] = values[4]
offset += size
# Extract sub dimension (if any)
fld['SubDim'] = []
subdim = 1
# sub-dimension list terminator reached
while subdim != 0:
(subdim,), size = self.decode_bin(['UInt4'], raw[offset:])
offset += size
if subdim != 0:
fld['SubDim'].append(subdim)
# append current field definition to list
tblfld.append(fld)
(fieldtype,), size = self.decode_bin(['Byte'], raw[offset:])
offset += size
# calculate table signature
tblsig = self.compute_signature(raw[start:offset])
# Append header, field list and signature to table definition list
item = {'Header': tblhdr, 'Fields': tblfld, 'Signature': tblsig}
tabledef.append(item)
return tabledef
def parse_collectdata(self, raw, tabledef, fieldnbr=[]):
'''Parse data returned by Collectdata Response.'''
offset = 0
recdata = [] # output structure
while offset < len(raw) - 1:
frag = {} # record fragment
values, size = self.decode_bin(['UInt2', 'UInt4'], raw[offset:])
frag['TableNbr'], frag['BegRecNbr'] = values
offset += size
# Provide table name
t_frag = tabledef[frag['TableNbr'] - 1]
tablename = t_frag['Header']['TableName']
frag['TableName'] = tablename
# Decode number of records (16 bits) or ByteOffset (32 Bits)
(isoffset,), size = self.decode_bin(['Byte'], raw[offset:])
frag['IsOffset'] = isoffset >> 7
# Handle fragmented records
if frag['IsOffset']:
(byteoffset,), size = self.decode_bin(['UInt4'], raw[offset:])
offset += size
frag['ByteOffset'] = byteoffset & 0x7FFFFFFF
frag['NbrOfRecs'] = None
# Copy remaining raw data into RecFrag
frag['RecFrag'] = raw[offset:-1]
offset += len(frag['RecFrag'])
# Handle complete records (standard case)
else:
(nbrofrecs,), size = self.decode_bin(['UInt2'], raw[offset:])
offset += size
frag['NbrOfRecs'] = nbrofrecs & 0x7FFF
frag['ByteOffset'] = None
# Get time of first record and time interval information
interval = t_frag['Header']['TblInterval']
if interval == (0, 0): # event-driven table
timeofrec = None
else:
# interval data, read time of first record
[timeofrec], size = self.decode_bin(['NSec'], raw[offset:])
offset += size
# Loop over all records
frag['RecFrag'] = []
for n in range(frag['NbrOfRecs']):
record = {}
# Calculate current record number
record['RecNbr'] = frag['BegRecNbr'] + n
# Get TimeOfRec for interval data or event-driven tables
if timeofrec: # interval data
next_timeofrec = (timeofrec[0] + n * interval[0],
timeofrec[1] + n * interval[1])
record['TimeOfRec'] = nsec_to_time(next_timeofrec)
else:
# event-driven, time data precedes each record
values, size = self.decode_bin(['NSec'], raw[offset:])
record['TimeOfRec'] = values[0]
record['TimeOfRec'] = nsec_to_time(record['TimeOfRec'])
offset += size
# Loop over all field indices
record['Fields'] = {}
if fieldnbr:
# explicit field numbers provided
fields = fieldnbr
else:
# default: generate list of all fields in table
fields = t_frag['Fields']
fields = range(1, len(fields) + 1)
for field in fields:
fieldname = t_frag['Fields'][field - 1]['FieldName']
fieldtype = t_frag['Fields'][field - 1]['FieldType']
dimension = t_frag['Fields'][field - 1]['Dimension']
if fieldtype == 'ASCII':
values, size = self.decode_bin([fieldtype],
raw[offset:],
dimension)
record['Fields'][fieldname] = values[0]
else:
values, size = \
self.decode_bin(dimension * [fieldtype],
raw[offset:])
record['Fields'][fieldname] = values[0]
offset += size
frag['RecFrag'].append(record)
recdata.append(frag)
# Get flag if more records exist
(more_rec,), size = self.decode_bin(['Bool'], raw[offset:])
return recdata, more_rec
def __del__(self):
self.link.close()
def __str__(self):
name = self.__class__.__name__
return '<%s %s>' % (name, self.link)
def __repr__(self):
return '%s' % self.__str__() With kindest regards.
Posts: 1,583
Threads: 3
Joined: Mar 2020
Nov-11-2020, 06:43 PM
(This post was last modified: Nov-11-2020, 06:44 PM by bowlofred.)
You may need to add some debugging output to see what the values are when run. The error message is telling you that the format passed into the struct is a 4-byte element. But the buffer you passed in is not (exactly) 4 bytes. You'll need to see what size is set to there, and why it doesn't match the needed size of 4.
>>> struct.unpack("i", buf[:3]) # size of 3 is to small for "int"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
struct.error: unpack requires a buffer of 4 bytes
>>> struct.unpack("i", buf[:5]) # size of 5 is too big for "int"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
struct.error: unpack requires a buffer of 4 bytes
>>> struct.unpack("i", buf[:4]) # size of 4 is just right....
(1869357153,)
Posts: 115
Threads: 10
Joined: Nov 2019
Feb-05-2022, 07:22 PM
(This post was last modified: Feb-05-2022, 07:22 PM by Makada.)
Hi all,
It has been some time ago i posted the buffer error with the pycampbellcr1000 package.
It still getting the error above.
I have installed pycharm which has a debug function, but i dont have a clue how to interpret it
Maybe someone is able to install the package or has a solution to it?
The designer of the package isnt responsive anymore...
With the kindest regards,
Mark
https://pypi.org/project/PyCampbellCR1000/
I ran the debug function in pycharm and this is the outcome:
Here is the screen from when i run the code:
Posts: 6,822
Threads: 20
Joined: Feb 2020
decode_bin() was told to unpack buffer b'\x00\x00\x00\x00\x00\x00\x01\x00' into 4 values of types ASCIIZ, ASCIIZ, ASCIIZ, ASCIIZ, Uint4, Unit4.
According to this code, ASCIIZ is a null terminated string and it will pull of all the bytes up to the null character (\x00).
if type_ == 'ASCIIZ':
# special handling: nul-terminated string
nul = buff.index(b'\0', offset)
# find first '\0' after offset
if nul == -1:
value = buff[offset:]
else:
value = buff[offset:nul]
# return string without trailing '\0'
size = len(value) + 1 Since your buffer starts with a bunch of null characters, unpacking the 4 ASCIIZ values essentially removes the 4 leading null characters (actually it increments an offset. Now your remaing buffer is b'x\00x\00x\00x\01x\00'.
The next type is Uint4. According to the DATATYPE dictionary in the PakBus class, this code grabs the next 4 bytes and interprets them as an unsigned int using format >L.
DATATYPE = {
'Byte': {'code': 1, 'fmt': 'B', 'size': 1},
'UInt2': {'code': 2, 'fmt': '>H', 'size': 2},
'UInt4': {'code': 3, 'fmt': '>L', 'size': 4}, This will grab b'x\00x\00x\00x\01' and convert it to 1.
The only thing remaining in the buffer is 0x00 (offset = 7). The next type is another Unit4 which requires 4 bytes. Buffer only has 1 byte left, not 4. The code fails.
decode_bin() is not your error, the buffer being passed to decode_bin is the problem. You can trace this back to device.table_def() calling device.getfile() and asking pakbus.parse_table to parse the data.
@cached_property
def table_def(self):
'''Return table definition.'''
data = self.getfile('.TDF')
# List tables
tabledef = self.pakbus.parse_tabledef(data)
return tabledef So there could be a problem with device.getfile(). Maybe the command it sends is wrong. I would print the cmd after doing this:
cmd = self.pakbus.get_fileupload_cmd(filename,
offset=offset,
closeflag=0x00,
transac_id=transac_id) Does the filename make sense? What about the offset?
The command is sent to your device and a response read back.
hdr, msg, send_time = self.send_wait(cmd) The parsing "msg" is where your program eventually crashes. Is the command sent incorrectly? Is the response read incorrectly?
This is device.send_wait()
def send_wait(self, cmd):
'''Send command and wait for response packet.'''
packet, transac_id = cmd
begin = time.time()
self.pakbus.write(packet)
# wait response packet
response = self.pakbus.wait_packet(transac_id)
end = time.time()
send_time = timedelta(seconds=int((end - begin) / 2))
return response[0], response[1], send_time Pretty simple. Write the packet and read response. Looks like we are headed back to pakbus again. pakbus.write(), pakbus.read() and pakbus.wait_packet(). This is where I would set my breakpoints. What is the buffer I am going to write? What is the buffer I just read? What is the link?
Posts: 115
Threads: 10
Joined: Nov 2019
Hi deanhystad,
Thank you for the effort looking into this.
But unfortunately i dont know what to do next
In which python file from the pycampbellcr1000 package do i have to add which code?
With the kindest regards,
Mark
Posts: 6,822
Threads: 20
Joined: Feb 2020
Set breakpoints in pakbus.write() and pakbus.wait_packet(), or add some print statments. Look at what you are sending and what you are getting back. Do they make sense to you? If not, record the info and post here again. I can't do much debugging without a device to talk to.
Posts: 115
Threads: 10
Joined: Nov 2019
I will try
Posts: 115
Threads: 10
Joined: Nov 2019
Feb-07-2022, 12:57 PM
(This post was last modified: Feb-07-2022, 12:57 PM by Makada.)
Hi, i made some print functions in the pakbus package in line 114, 176 and 202, as can be seen here:
# -*- coding: utf-8 -*-
'''
PyCampbellCR1000.pakbus
-----------------------
PakBus protocol Implementation.
Original Author: Dietrich Feist, Max Planck Institute for Biogeochemistry,
Jena Germany (PyPak)
:copyright: Copyright 2012 Salem Harrache and contributors, see AUTHORS.
:license: GNU GPL v3.
'''
from __future__ import division, unicode_literals
import struct
import time
from .compat import ord, chr, is_text, is_py3, bytes
from .logger import LOGGER
from .utils import Singleton
from .exceptions import DeliveryFailureException
from .utils import bytes_to_hex, nsec_to_time
class Transaction(Singleton):
id = 0
def next_id(self):
self.id += 1
self.id &= 0xFF
return self.id
class PakBus(object):
'''Interface for a pakbus client.
:param link: A `PyLink` connection.
:param dest_addr: Destination physical address (12-bit int) (default dest)
:param dest: Destination node ID (12-bit int) (default 0x001)
:param src_addr: Source physical address (12-bit int) (default src)
:param src: Source node ID (12-bit int) (default 0x802)
:param security_code: 16-bit security code (default 0x0000)
'''
DATATYPE = {
'Byte': {'code': 1, 'fmt': 'B', 'size': 1},
'UInt2': {'code': 2, 'fmt': '>H', 'size': 2},
'UInt4': {'code': 3, 'fmt': '>L', 'size': 4},
'Int1': {'code': 4, 'fmt': 'b', 'size': 1},
'Int2': {'code': 5, 'fmt': '>h', 'size': 2},
'Int4': {'code': 6, 'fmt': '>l', 'size': 4},
'FP2': {'code': 7, 'fmt': '>H', 'size': 2},
'FP3': {'code': 15, 'fmt': '3c', 'size': 3},
'FP4': {'code': 8, 'fmt': '4c', 'size': 4},
'IEEE4B': {'code': 9, 'fmt': '>f', 'size': 4},
'IEEE8B': {'code': 18, 'fmt': '>d', 'size': 8},
'Bool8': {'code': 17, 'fmt': 'B', 'size': 1},
'Bool': {'code': 10, 'fmt': 'B', 'size': 1},
'Bool2': {'code': 27, 'fmt': '>H', 'size': 2},
'Bool4': {'code': 28, 'fmt': '>L', 'size': 4},
'Sec': {'code': 12, 'fmt': '>l', 'size': 4},
'USec': {'code': 13, 'fmt': '6c', 'size': 6},
'NSec': {'code': 14, 'fmt': '>2l', 'size': 8},
'ASCII': {'code': 11, 'fmt': 's', 'size': None},
'ASCIIZ': {'code': 16, 'fmt': 's', 'size': None},
'Short': {'code': 19, 'fmt': '<h', 'size': 2},
'Long': {'code': 20, 'fmt': '<l', 'size': 4},
'UShort': {'code': 21, 'fmt': '<H', 'size': 2},
'ULong': {'code': 22, 'fmt': '<L', 'size': 4},
'IEEE4L': {'code': 24, 'fmt': '<f', 'size': 4},
'IEEE8L': {'code': 25, 'fmt': '<d', 'size': 8},
'SecNano': {'code': 23, 'fmt': '<2l', 'size': 8},
}
# link state
RING = 0x9
READY = 0xA
FINISHED = 0xB
def __init__(self, link, dest_addr=None, dest=0x001,
src_addr=None, src=0x802, security_code=0x0000):
self.link = link
self.src = src
if (src_addr != None):
self.src_addr = src_addr
else:
self.src_addr = src
self.dest = dest
if (dest_addr != None):
self.dest_addr = dest_addr
else:
self.dest_addr = dest
self.security_code = security_code
self.transaction = Transaction()
LOGGER.info('Get the node attention')
self.link.write(b'\xBD\xBD\xBD\xBD\xBD\xBD')
def write(self, packet):
'''Send packet over PakBus.'''
LOGGER.info('Packet data: %s' % bytes_to_hex(packet))
LOGGER.info('Calculate signature for packet')
sign = self.compute_signature(packet)
LOGGER.info('Calculate signature nullifier to create packet')
nullifier = self.compute_signature_nullifier(sign)
frame = self.quote(b''.join((packet, nullifier)))
packet = b''.join((b'\xBD', frame, b'\xBD'))
LOGGER.info('Write: %s' % bytes_to_hex(packet))
self.link.write(packet)
print('Write: %s' % bytes_to_hex(packet))
def read(self):
'''Receive packet over PakBus.'''
all_bytes = []
byte = None
begin = time.time()
while byte != b'\xBD':
if (byte != None):
LOGGER.info('Read byte: %s' % bytes_to_hex(byte))
if time.time() - begin > self.link.timeout:
return None
# Read until first \xBD frame character
byte = self._read_one_byte()
while byte == b'\xBD':
# Read unitl first character other than \xBD
byte = self._read_one_byte()
while byte != b'\xBD':
# Read until next occurence of \xBD character
all_bytes.append(byte)
byte = self._read_one_byte()
# Unquote quoted characters
packet = b''.join(all_bytes)
LOGGER.info('Read packet: %s' % bytes_to_hex(packet))
packet = self.unquote(packet)
# Calculate signature (should be zero)
if self.compute_signature(packet):
LOGGER.error('Check signature : Error')
return None
else:
LOGGER.info('Check signature : OK')
# Strip last 2 signature bytes and return packet
return packet[:-2]
def _read_one_byte(self):
'''Read only one byte.'''
data = self.link.read(1)
if is_text(data):
return bytes(data.encode('utf-8'))
else:
return data
def wait_packet(self, transac_id=None):
'''Wait for an incoming packet.
:param transac_id: Expected transaction number.
'''
LOGGER.info('Wait packet with transaction %s' % transac_id)
data = self.read()
if data is None or data == b'':
return {}, {}
hdr, msg = self.decode_packet(data)
if hdr == {} or msg == {}:
return hdr, msg
print(data)
# ignore packets that are not for us
LOGGER.info('src, SrcNodeId = <%x, %x>' %
(self.src, hdr['SrcNodeId']))
LOGGER.info('dest, DstNodeId = <%x, %x>' %
(self.dest, hdr['DstNodeId']))
if (hdr['DstNodeId'] != self.src):
return {}, {}
# Handle 'please wait' packets
if msg['TranNbr'] == transac_id and msg['MsgType'] == 0xa1:
timewait = msg['WaitSec']
LOGGER.info('Please Wait Message packet <%s sec>' % timewait)
time.sleep(timewait)
return self.wait_packet(transac_id)
# Handle failure message packets and raise exception
if msg['MsgType'] == 0x81:
raise DeliveryFailureException()
Print(DeliveryFailureException())
# This should be the packet we are waiting for
if msg['TranNbr'] == transac_id:
return hdr, msg
def pack_header(self, hi_proto, exp_more=0x2, link_state=None,
hops=0x0):
'''Generate PakBus header.
:param hi_proto: Higher level protocol code (4 bits). 0x0: PakCtrl,
0x1: BMP5
:param exp_more: Whether client should expect another packet (2 bits)
:param link_state: Link state (4 bits)
:param hops: Number of hops to destination (4 bits)
'''
LOGGER.info('Create header')
priority = 0x1
link_state = link_state or self.READY
# bitwise encoding of header fields
hdr = struct.pack(str('>4H'),
(link_state & 0xF) << 12 | (self.dest_addr & 0xFFF),
(exp_more & 0x3) << 14 | (priority & 0x3) << 12
| (self.src_addr & 0xFFF),
(hi_proto & 0xF) << 12 | (self.dest & 0xFFF),
(hops & 0xF) << 12 | (self.src & 0xFFF))
return hdr
def compute_signature(self, buff, seed=0xAAAA):
'''Compute signature for PakBus packets.'''
sig = seed
for x in buff:
x = ord(x)
j = sig
sig = (sig << 1) & 0x1FF
if sig >= 0x100:
sig += 1
sig = ((((sig + (j >> 8) + x) & 0xFF) | (j << 8))) & 0xFFFF
return sig
def compute_signature_nullifier(self, sig):
'''Compute signature nullifier needed to create valid PakBus
packets.'''
nulb = nullif = b''
for i in (1, 2):
sig = self.compute_signature(nulb, sig)
sig2 = (sig << 1) & 0x1FF
if sig2 >= 0x100:
sig2 += 1
nulb = chr((0x100 - (sig2 + (sig >> 8))) & 0xFF)
nullif += nulb
return nullif
def quote(self, packet):
'''Quote the PakBus packet.'''
LOGGER.info('Quote packet')
packet = packet.replace(b'\xBC', b'\xBC\xDC')
packet = packet.replace(b'\xBD', b'\xBC\xDD')
return packet
def unquote(self, packet):
'''Unquote the PakBus packet.'''
LOGGER.info('Unquote packet')
packet = packet.replace(b'\xBC\xDD', b'\xBD')
packet = packet.replace(b'\xBC\xDC', b'\xBC')
return packet
def encode_bin(self, types, values):
'''Encode binary data according to data type.'''
buff = [] # buffer for binary data
for i, type_ in enumerate(types):
fmt = self.DATATYPE[type_]['fmt'] # get default format for type_
value = values[i]
if type_ == 'ASCIIZ':
# special handling: nul-terminated string
value += '\0'
# Add nul to end of string
fmt_ = str('%d%s' % (len(value), fmt))
if is_py3:
enc = struct.pack(fmt_, bytes(value, encoding='utf8'))
else:
enc = struct.pack(fmt_, str(value))
elif type_ == 'ASCII':
# special handling: fixed-length string
fmt_ = str('%d%s' % (len(value), fmt))
enc = struct.pack(fmt_, str(value))
elif type_ == 'NSec':
# special handling: NSec time
enc = struct.pack(str(fmt), value[0], value[1])
else:
# default encoding scheme
enc = struct.pack(str(fmt), value)
buff.append(enc)
return b''.join(buff)
def decode_bin(self, types, buff, length=1):
'''Decode binary data according to data type.'''
offset = 0 # offset into buffer
values = [] # list of values to return
for type_ in types:
# get default format and size for Type
fmt = self.DATATYPE[type_]['fmt']
size = self.DATATYPE[type_]['size']
if type_ == 'ASCIIZ':
# special handling: nul-terminated string
nul = buff.index(b'\0', offset)
# find first '\0' after offset
if nul == -1:
value = buff[offset:]
else:
value = buff[offset:nul]
# return string without trailing '\0'
size = len(value) + 1
elif type_ == 'ASCII':
# special handling: fixed-length string
size = length
value = buff[offset:offset + size]
# return fixed-length string
elif type_ == 'FP2':
# special handling: FP2 floating point number
fp2 = struct.unpack(str(fmt), buff[offset:offset + size])
mant = fp2[0] & 0x1FFF # mantissa is in bits 1-13
exp = fp2[0] >> 13 & 0x3 # exponent is in bits 14-15
sign = fp2[0] >> 15 # sign is in bit 16
value = ((-1) ** sign * float(mant) / 10 ** exp, )
else:
# default decoding scheme
if buff[offset:offset + size]:
value = struct.unpack(str(fmt), buff[offset:offset + size])
else:
value = ''
# un-tuple single values
if len(value) == 1:
value = value[0]
values.append(value)
offset += size
# Return decoded values and current offset into buffer (size)
return values, offset
def decode_packet(self, data):
'''Decode packet from raw data.'''
LOGGER.info('Decode packet')
# pkt: buffer containing unquoted packet, signature nullifier stripped
# Initialize output variables
hdr = {'LinkState': None, 'DstPhyAddr': None, 'ExpMoreCode': None,
'Priority': None, 'SrcPhyAddr': None, 'HiProtoCode': None,
'DstNodeId': None, 'HopCnt': None, 'SrcNodeId': None}
msg = {'MsgType': None, 'TranNbr': None, 'raw': None}
# decode PakBus header
rawhdr = struct.unpack(str('>4H'), data[0:8]) # raw header bits
hdr['LinkState'] = rawhdr[0] >> 12
hdr['DstPhyAddr'] = rawhdr[0] & 0x0FFF
hdr['ExpMoreCode'] = (rawhdr[1] & 0xC000) >> 14
hdr['Priority'] = (rawhdr[1] & 0x3000) >> 12
hdr['SrcPhyAddr'] = rawhdr[1] & 0x0FFF
hdr['HiProtoCode'] = rawhdr[2] >> 12
hdr['DstNodeId'] = rawhdr[2] & 0x0FFF
hdr['HopCnt'] = rawhdr[3] >> 12
hdr['SrcNodeId'] = rawhdr[3] & 0x0FFF
# decode default message fields:
# raw message, message type and transaction number
msg['raw'] = data[8:]
values, size = self.decode_bin(('Byte', 'Byte'), msg['raw'][:2])
msg['MsgType'], msg['TranNbr'] = values
LOGGER.info('HiProtoCode, MsgType = <%x, %x>' %
(hdr['HiProtoCode'], msg['MsgType']))
# PakBus Control Packets
if hdr['HiProtoCode'] == 0 and msg['MsgType'] in (0x09, 0x89, 0xe):
msg = self.unpack_hello_response(msg)
elif hdr['HiProtoCode'] == 0 and msg['MsgType'] == 0x81:
msg = self.unpack_failure_response(msg)
elif hdr['HiProtoCode'] == 0 and msg['MsgType'] == 0x8f:
msg = self.unpack_getsettings_response(msg)
# BMP5 Application Packets
elif hdr['HiProtoCode'] == 1 and msg['MsgType'] == 0x89:
msg = self.unpack_collectdata_response(msg)
elif hdr['HiProtoCode'] == 1 and msg['MsgType'] == 0x97:
msg = self.unpack_clock_response(msg)
elif hdr['HiProtoCode'] == 1 and msg['MsgType'] == 0x98:
msg = self.unpack_getprogstat_response(msg)
elif hdr['HiProtoCode'] == 1 and msg['MsgType'] == 0x9c:
msg = self.unpack_filedownload_response(msg)
elif hdr['HiProtoCode'] == 1 and msg['MsgType'] == 0x9d:
msg = self.unpack_fileupload_response(msg)
elif hdr['HiProtoCode'] == 1 and msg['MsgType'] == 0xa1:
msg = self.unpack_pleasewait_response(msg)
else:
LOGGER.error('No implementation for <(%r, %r)> packet type'
% (hdr['HiProtoCode'], msg['MsgType']))
return hdr, msg
def get_hello_cmd(self):
'''Create Hello Command packet.'''
transac_id = self.transaction.next_id()
hdr = self.pack_header(0x0, 0x1, self.RING)
msg = self.encode_bin(['Byte', 'Byte', 'Byte', 'Byte', 'UInt2'],
[0x09, transac_id, 0x00, 0x02, 1800])
return b''.join((hdr, msg)), transac_id
def get_hello_response(self, transac_id):
'''Create Hello Response packet.'''
hdr = self.pack_header(0x0)
msg = self.encode_bin(['Byte', 'Byte', 'Byte', 'Byte', 'UInt2'],
[0x89, transac_id, 0x00, 0x02, 1800])
return b''.join([hdr, msg])
def unpack_hello_response(self, msg):
'''Create Hello Response packet.'''
raw = msg['raw'][2:]
values, size = self.decode_bin(['Byte', 'Byte', 'UInt2'], raw)
msg['IsRouter'], msg['HopMetric'], msg['VerifyIntv'] = values
return msg
def unpack_failure_response(self, msg):
'''Unpack Failure Response packet.'''
(msg['ErrCode'],), size = self.decode_bin(['Byte'], msg['raw'][2:])
return msg
def get_getsettings_cmd(self):
'''Create Getsettings Command packet.'''
transac_id = self.transaction.next_id()
hdr = self.pack_header(0x0)
msg = self.encode_bin(['Byte', 'Byte'], [0x0f, transac_id])
return b''.join([hdr, msg]), transac_id
def unpack_getsettings_response(self, msg):
'''Unpack Getsettings Response packet.'''
(msg['Outcome'],), size = self.decode_bin(['Byte'], msg['raw'][2:])
offset = size + 2
# Generate dictionary of all settings
msg['Settings'] = []
if msg['Outcome'] == 0x01:
values, size = self.decode_bin(['UInt2', 'Byte', 'Byte', 'Byte'],
msg['raw'][offset:])
msg['DeviceType'] = values[0]
msg['MajorVersion'] = values[1]
msg['MinorVersion'] = values[2]
msg['MoreSettings'] = values[3]
offset += size
while offset < len(msg['raw']):
# Get setting ID
[SettingId], size = self.decode_bin(['UInt2'],
msg['raw'][offset:])
offset += size
if not msg['raw'][offset:]:
break
# Get flags and length
[bit16], size = self.decode_bin(['UInt2'], msg['raw'][offset:])
LargeValue = (bit16 & 0x8000) >> 15
ReadOnly = (bit16 & 0x4000) >> 14
SettingLen = bit16 & 0x3FFF
offset += size
# Get value
SettingValue = msg['raw'][offset:offset + SettingLen]
offset += SettingLen
item = {'SettingId': SettingId,
'SettingValue': SettingValue,
'LargeValue': LargeValue,
'ReadOnly': ReadOnly}
msg['Settings'].append(item)
return msg
def get_collectdata_cmd(self, tablenbr, tabledefsig, mode=0x04, p1=0,
p2=0):
'''Create Collect Data Command packet
:param tablenbr: Table number that contain data.
:param tabledefsig: Table definition signature.
:param mode: Collection mode code (p1 and p2 will be used depending on
value).
:param p1: 1st parameter used to specify what to collect (optional)
:param p2: 2nd parameter used to specify what to collect (optional)
'''
transac_id = self.transaction.next_id()
# BMP5 Application Packet
hdr = self.pack_header(0x1)
msg = self.encode_bin(['Byte', 'Byte', 'UInt2', 'Byte'],
[0x09, transac_id, self.security_code,
mode])
# encode table number and signature
msg += self.encode_bin(['UInt2', 'UInt2'], [tablenbr, tabledefsig])
# add p1 and p2 according to collect mode
if (mode == 0x04) | (mode == 0x05):
# only P1 used (type UInt4)
msg += self.encode_bin(['UInt4'], [p1])
elif (mode == 0x06) | (mode == 0x08):
# P1 and P2 used (type UInt4)
msg += self.encode_bin(['UInt4', 'UInt4'], [p1, p2])
elif mode == 0x07:
# P1 and P2 used (type NSec)
msg += self.encode_bin(['NSec', 'NSec'], [p1, p2])
# add field list = all fields
msg += self.encode_bin(['UInt2'], [0])
return b''.join((hdr, msg)), transac_id
def unpack_collectdata_response(self, msg):
'''Unpack Collect Data Response body.'''
(msg['RespCode'],), size = self.decode_bin(['Byte'], msg['raw'][2:])
# return raw record data for later parsing
msg['RecData'] = msg['raw'][size + 2:]
return msg
def get_clock_cmd(self, adjustment=(0, 0)):
'''Create Clock Command packet.
:param adjustment: Clock adjustment (seconds, nanoseconds).
'''
transac_id = self.transaction.next_id()
# BMP5 Application Packet
hdr = self.pack_header(0x1)
msg = self.encode_bin(['Byte', 'Byte', 'UInt2', 'NSec'],
[0x17, transac_id, self.security_code,
adjustment])
return b''.join((hdr, msg)), transac_id
def unpack_clock_response(self, msg):
'''Unpack Clock Response packet.'''
values, size = self.decode_bin(['Byte', 'NSec'], msg['raw'][2:])
msg['RespCode'], msg['Time'] = values
return msg
def get_getprogstat_cmd(self):
'''Create Get Programming Statistics Transaction packet.'''
transac_id = self.transaction.next_id()
# BMP5 Application Packet
hdr = self.pack_header(0x1)
msg = self.encode_bin(['Byte', 'Byte', 'UInt2'],
[0x18, transac_id, self.security_code])
return b''.join((hdr, msg)), transac_id
def unpack_getprogstat_response(self, msg):
'''Unpack Get Programming Statistics Response packet.'''
# Get response code
(msg['RespCode'], ), size = self.decode_bin(['Byte'], msg['raw'][2:])
# Get report data if RespCode == 0
if msg['RespCode'] == 0:
types = ['ASCIIZ', 'UInt2', 'ASCIIZ', 'ASCIIZ', 'Byte', 'ASCIIZ',
'UInt2', 'NSec', 'ASCIIZ']
values, size = self.decode_bin(types, msg['raw'][3:])
item = {'OSVer': values[0], 'OSSig': values[1],
'SerialNbr': values[2], 'PowUpProg': values[3],
'CompState': values[4], 'ProgName': values[5],
'ProgSig': values[6], 'CompTime': values[7],
'CompResult': values[8]}
msg['Stats'] = item
return msg
def get_filedownload_cmd(self, filename, data, offset=0x00000000,
closeflag=0x00, transac_id=None):
'''Create Filedownload Command packet.
:param filename: File name as string
:param offset: Byte offset into the file or fragment
:param closeflag: Flag if file should be closed after this transaction
:param transac_id: Transaction number for continuing partial reads
'''
raise NotImplementedError('Filedownload transaction is not implemented'
' yet')
def unpack_filedownload_response(self, msg):
'''Unpack Filedownload Response packet.'''
raise NotImplementedError('Filedownload transaction is not implemented'
' yet')
def get_fileupload_cmd(self, filename, offset=0x00000000, swath=0x0200,
closeflag=0x01, transac_id=None):
'''Create Fileupload Command packet.
:param filename: File name as string
:param offset: Byte offset into the file or fragment
:param swath: Number of bytes to read
:param closeflag: Flag if file should be closed after this transaction
:param transac_id: Transaction number for continuing partial reads
(required by OS>=17!)
'''
if transac_id is None:
transac_id = self.transaction.next_id()
# BMP5 Application Packet
hdr = self.pack_header(0x1)
types = ['Byte', 'Byte', 'UInt2', 'ASCIIZ', 'Byte', 'UInt4', 'UInt2']
values = [0x1d, transac_id, self.security_code, filename, closeflag,
offset, swath]
msg = self.encode_bin(types, values)
return b''.join((hdr, msg)), transac_id
def unpack_fileupload_response(self, msg):
'''Unpack Fileupload Response packet.'''
values, size = self.decode_bin(['Byte', 'UInt4'], msg['raw'][2:7])
msg['RespCode'], msg['FileOffset'] = values
msg['FileData'] = msg['raw'][7:]
return msg
def unpack_pleasewait_response(self, msg):
'''Unpack PeaseWait Response packet.'''
values, size = self.decode_bin(['Byte', 'UInt2'], msg['raw'][2:])
msg['CmdMsgType'], msg['WaitSec'] = values
return msg
def get_bye_cmd(self):
'''Create Bye Command packet.'''
transac_id = self.transaction.next_id()
# PakBus Control Packet
hdr = self.pack_header(0x0, 0x0, self.FINISHED)
msg = self.encode_bin(['Byte', 'Byte'], [0x0d, 0x0])
return b''.join((hdr, msg)), transac_id
def parse_filedir(self, data):
'''Parse file directory format.'''
offset = 0 # offset into raw buffer
fd = {'files': []} # initialize file directory structure
[fd['DirVersion']], size = self.decode_bin(['Byte'], data[offset:])
offset += size
# Extract file entries
while len(data) > offset:
file_ = {} # file description
[filename], size = self.decode_bin(['ASCIIZ'], data[offset:])
offset += size
# end loop when file attribute list terminator reached
if filename == '':
break
file_['FileName'] = filename
values, size = self.decode_bin(['UInt4', 'ASCIIZ'], data[offset:])
file_['FileSize'], file_['LastUpdate'] = values
offset += size
# Read file attribute list
file_['Attribute'] = []
# initialize file attribute list (up to 12)
for i in range(12):
[attribute], size = self.decode_bin(['Byte'], data[offset:])
offset += size
if attribute:
# append file attribute to list
file_['Attribute'].append(attribute)
else:
break # End of attribute list reached
fd['files'].append(file_) # add file entry to list
return fd
def parse_tabledef(self, raw):
'''Parse table definition.'''
tabledef = [] # List of table definitions
offset = 0 # offset into raw buffer
fslversion, size = self.decode_bin(['Byte'], raw[offset:])
offset += size
# Parse list of table definitions
while offset < len(raw):
tblhdr = {} # table header
tblfld = [] # table field definitions
start = offset # start of table definition
# Extract table header data
types = ['ASCIIZ', 'UInt4', 'Byte', 'NSec', 'NSec']
values, size = self.decode_bin(types, raw[offset:])
tblhdr['TableName'] = values[0]
tblhdr['TableSize'] = values[1]
tblhdr['TimeType'] = values[2]
tblhdr['TblTimeInto'] = values[3]
tblhdr['TblInterval'] = values[4]
offset += size
# Extract field definitions
(fieldtype,), size = self.decode_bin(['Byte'], raw[offset:])
offset += size
while fieldtype != 0:
fld = {}
# Extract bits from fieldtype
fld['ReadOnly'] = fieldtype >> 7 # only Bit 7
# Convert fieldtype to ASCII FieldType (e.g. 'FP4') if possible
# else return numerical value
fld['FieldType'] = fieldtype & 0x7F # only Bits 0..6
for Type in self.DATATYPE.keys():
if fld['FieldType'] == self.DATATYPE[Type]['code']:
fld['FieldType'] = Type
break
# Extract field name
values, size = self.decode_bin(['ASCIIZ'], raw[offset:])
fld['FieldName'] = values[0]
offset += size
# Extract AliasName list
fld['AliasName'] = []
aliasname = b'00'
# Alias names list terminator reached
while aliasname != b'':
values, size = self.decode_bin(['ASCIIZ'], raw[offset:])
aliasname = values[0]
offset += size
if aliasname != b'':
fld['AliasName'].append(aliasname)
# Extract other mandatory field definition items
types = ['ASCIIZ', 'ASCIIZ', 'ASCIIZ', 'UInt4', 'UInt4']
values, size = self.decode_bin(types, raw[offset:])
fld['Processing'] = values[0]
fld['Units'] = values[1]
fld['Description'] = values[2]
fld['BegIdx'] = values[3]
fld['Dimension'] = values[4]
offset += size
# Extract sub dimension (if any)
fld['SubDim'] = []
subdim = 1
# sub-dimension list terminator reached
while subdim != 0:
(subdim,), size = self.decode_bin(['UInt4'], raw[offset:])
offset += size
if subdim != 0:
fld['SubDim'].append(subdim)
# append current field definition to list
tblfld.append(fld)
(fieldtype,), size = self.decode_bin(['Byte'], raw[offset:])
offset += size
# calculate table signature
tblsig = self.compute_signature(raw[start:offset])
# Append header, field list and signature to table definition list
item = {'Header': tblhdr, 'Fields': tblfld, 'Signature': tblsig}
tabledef.append(item)
return tabledef
def parse_collectdata(self, raw, tabledef, fieldnbr=[]):
'''Parse data returned by Collectdata Response.'''
offset = 0
recdata = [] # output structure
while offset < len(raw) - 1:
frag = {} # record fragment
values, size = self.decode_bin(['UInt2', 'UInt4'], raw[offset:])
frag['TableNbr'], frag['BegRecNbr'] = values
offset += size
# Provide table name
t_frag = tabledef[frag['TableNbr'] - 1]
tablename = t_frag['Header']['TableName']
frag['TableName'] = tablename
# Decode number of records (16 bits) or ByteOffset (32 Bits)
(isoffset,), size = self.decode_bin(['Byte'], raw[offset:])
frag['IsOffset'] = isoffset >> 7
# Handle fragmented records
if frag['IsOffset']:
(byteoffset,), size = self.decode_bin(['UInt4'], raw[offset:])
offset += size
frag['ByteOffset'] = byteoffset & 0x7FFFFFFF
frag['NbrOfRecs'] = None
# Copy remaining raw data into RecFrag
frag['RecFrag'] = raw[offset:-1]
offset += len(frag['RecFrag'])
# Handle complete records (standard case)
else:
(nbrofrecs,), size = self.decode_bin(['UInt2'], raw[offset:])
offset += size
frag['NbrOfRecs'] = nbrofrecs & 0x7FFF
frag['ByteOffset'] = None
# Get time of first record and time interval information
interval = t_frag['Header']['TblInterval']
if interval == (0, 0): # event-driven table
timeofrec = None
else:
# interval data, read time of first record
[timeofrec], size = self.decode_bin(['NSec'], raw[offset:])
offset += size
# Loop over all records
frag['RecFrag'] = []
for n in range(frag['NbrOfRecs']):
record = {}
# Calculate current record number
record['RecNbr'] = frag['BegRecNbr'] + n
# Get TimeOfRec for interval data or event-driven tables
if timeofrec: # interval data
next_timeofrec = (timeofrec[0] + n * interval[0],
timeofrec[1] + n * interval[1])
record['TimeOfRec'] = nsec_to_time(next_timeofrec)
else:
# event-driven, time data precedes each record
values, size = self.decode_bin(['NSec'], raw[offset:])
record['TimeOfRec'] = values[0]
record['TimeOfRec'] = nsec_to_time(record['TimeOfRec'])
offset += size
# Loop over all field indices
record['Fields'] = {}
if fieldnbr:
# explicit field numbers provided
fields = fieldnbr
else:
# default: generate list of all fields in table
fields = t_frag['Fields']
fields = range(1, len(fields) + 1)
for field in fields:
fieldname = t_frag['Fields'][field - 1]['FieldName']
fieldtype = t_frag['Fields'][field - 1]['FieldType']
dimension = t_frag['Fields'][field - 1]['Dimension']
if fieldtype == 'ASCII':
values, size = self.decode_bin([fieldtype],
raw[offset:],
dimension)
record['Fields'][fieldname] = values[0]
else:
values, size = \
self.decode_bin(dimension * [fieldtype],
raw[offset:])
record['Fields'][fieldname] = values[0]
offset += size
frag['RecFrag'].append(record)
recdata.append(frag)
# Get flag if more records exist
(more_rec,), size = self.decode_bin(['Bool'], raw[offset:])
return recdata, more_rec
def __del__(self):
self.link.close()
def __str__(self):
name = self.__class__.__name__
return '<%s %s>' % (name, self.link)
def __repr__(self):
return '%s' % self.__str__() The outcome of it is here from pycharm:
"C:\Program Files\Python310\python.exe" "C:\Program Files\JetBrains\PyCharm 2021.3\plugins\python\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 50247 --file C:/Users/Makada/Desktop/cr1000..py
Connected to pydev debugger (build 213.5744.248)
Write: BD 90 01 58 02 00 01 08 02 09 01 00 02 07 08 F6 86 BD
b'\xa8\x02\x10\x01\x08\x02\x00\x01\x89\x01\x00\x01\xff\xff'
Delivery failure.
Write: BD B0 01 18 02 00 01 08 02 0D 00 50 6D BD
Process finished with exit code 0
|