Extract reusable device protocols, ports and logic analyzers from SETGUI
This commit is contained in:
95
python/logic_analyzer/decoders/pm35_uart.py
Normal file
95
python/logic_analyzer/decoders/pm35_uart.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user