Files
templates/python/logic_analyzer/decoders/pm35_uart.py

96 lines
4.0 KiB
Python

"""PM35 MODBUS 03/06, including the legacy 128-word/zero-byte-count reply."""
from .set_uart import crc16, number
def parse_pm35(raw, role):
raw = bytes(raw)
if len(raw) < 5:
raise ValueError('Incomplete PM35 frame')
if crc16(raw[:-2], True) != number(raw[-2:]):
raise ValueError('PM35 CRC mismatch')
address, function = raw[:2]
result = dict(protocol='PM35', controller=address, function=function, raw=raw)
if function in (0x83, 0x86):
if len(raw) != 5 or role == 'request':
raise ValueError('Invalid exception response')
result['summary'] = 'PM35 node=%d exception fn=%02X code=%d' % (address, function, raw[2])
elif function == 6 or function == 3 and role == 'request':
if len(raw) != 8:
raise ValueError('PM35 request/06 must contain 8 bytes')
register, value = number(raw[2:4], 'big'), number(raw[4:6], 'big')
if register >= 128 or function == 3 and not 1 <= value <= 128 - register:
raise ValueError('PM35 register range outside 0..127')
result.update(register=register, value=value)
result['summary'] = 'PM35 node=%d %s R%d %s=%d' % (address,
'READ' if function == 3 else 'WRITE/echo', register,
'count' if function == 3 else 'value', value)
if function == 6 and register == 127:
result['summary'] += ' command_bits=%04X' % value
elif function == 3:
count = raw[2] or 256
if count % 2 or len(raw) != count + 5:
raise ValueError('Invalid PM35 response byte count')
result['values'] = [number(raw[i:i+2], 'big') for i in range(3, len(raw)-2, 2)]
result['summary'] = 'PM35 node=%d READ response %d words: %s' % (address,
len(result['values']), ' '.join('%04X' % v for v in result['values']))
else:
raise ValueError('Unsupported PM35 function')
return result
class PM35Parser:
def __init__(self, role):
self.role = role
self.buffer = []
def flush(self):
if not self.buffer:
return []
ss, es = self.buffer[0][1], self.buffer[-1][2]
self.buffer = []
return [(ss, es, None, 'Incomplete PM35 frame')]
def feed(self, value, ss, es):
self.buffer.append((value, ss, es))
events = []
while len(self.buffer) >= 3:
raw = bytes(item[0] for item in self.buffer)
fn = raw[1]
if fn not in (3, 6, 0x83, 0x86):
del self.buffer[0]
continue
role = self.role
total = 5 if fn & 128 else 8
if role == 'auto' and fn == 3:
candidates = [(8, 'request'), ((raw[2] or 256) + 5, 'response')]
found = None
for length, candidate_role in candidates:
if len(raw) >= length:
try:
found = (length, candidate_role, parse_pm35(raw[:length], candidate_role))
break
except ValueError:
pass
if found:
length, candidate_role, result = found
events.append((self.buffer[0][1], self.buffer[length-1][2], result, None))
del self.buffer[:length]
continue
if len(raw) < max(length for length, _ in candidates):
break
events.append((self.buffer[0][1], self.buffer[-1][2], None, 'Invalid PM35 frame/CRC'))
del self.buffer[0]
continue
if fn == 3 and role == 'response':
total = (raw[2] or 256) + 5
if len(raw) < total:
break
result, error = None, None
try:
result = parse_pm35(raw[:total], role)
except ValueError as exc:
error = str(exc)
events.append((self.buffer[0][1], self.buffer[total-1][2], result, error))
del self.buffer[:1 if error else total]
return events