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

125 lines
6.6 KiB
Python

"""ProtoCAN, Balsam and SET v2 classic CAN binding."""
from .set_uart import number, parse_frame
TYPES = {0: 'BROADCAST', 1: 'DISCRETE', 2: 'ANALOG', 3: 'GAS',
4: 'MODBUS_COIL', 5: 'MODBUS_DISCRETE', 6: 'MODBUS_HOLDING',
7: 'MODBUS_INPUT', 8: 'ERROR', 9: 'BOOT_CONTROL', 10: 'BOOT_DATA_A',
11: 'BOOT_DATA_B', 12: 'BOOT_STATUS', 13: 'BOOT_DISCOVERY', 15: 'PULSE'}
BOOT = dict(enumerate(('IDENTIFY ENTER_BOOT BEGIN_IMAGE BEGIN_COMPAT ERASE VERIFY '
'COMMIT CONFIRM REBOOT ABORT QUERY_PROGRESS').split(), 1))
def legacy(ident, data, profile='protocan'):
if not 0 <= ident <= 0x1fffffff or len(data) > 8:
raise ValueError('Invalid classic CAN frame')
relative = ident - 0xba0000
if profile == 'balsam':
if not (0 <= relative < 13 or 16 <= relative < 29):
return None
if len(data) != 8:
raise ValueError('Balsam requires DLC=8')
header = number(data[:2], 'big')
address, mask = header & 0x1fff, header >> 13
registers = [(address + i, number(data[2+i*2:4+i*2], 'big'))
for i in range(3) if mask & (4 >> i)]
return dict(protocol='Balsam', registers=registers, summary=
'Balsam node=%d %s mask=%X %s' % ((relative & 15) + 1,
'data' if relative >= 16 else 'command', mask,
' '.join('R%04X=%04X' % pair for pair in registers)))
msg, body = (ident >> 16) & 15, ident & 0xffff
result = dict(protocol='ProtoCAN', priority=(ident >> 28) & 1,
route=(ident >> 27) & 1, device_type=(ident >> 24) & 7,
device_id=(ident >> 20) & 15, message_type=msg, body=body)
text = 'ProtoCAN %s dev=%d:%d route=%d priority=%d body=%04X' % (
TYPES.get(msg, 'TYPE_%X' % msg), result['device_type'], result['device_id'],
result['route'], result['priority'], body)
if msg == 3:
if len(data) % 2 or body + len(data)//2 > 65536:
raise ValueError('Invalid GAS register payload/range')
result['registers'] = [(body + i//2, number(data[i:i+2])) for i in range(0, len(data), 2)]
text += ' ' + ('READ' if not data else ' '.join('R%04X=%04X' % pair for pair in result['registers']))
elif 4 <= msg <= 7:
text += ' address=%03X count=%d' % (body >> 4, body & 15)
elif msg == 8:
text += ' info=%02X code=%02X' % (body >> 8, body & 255)
elif msg in (9, 12):
command = body & 255
text += ' session=%d command=%s' % (body >> 8, BOOT.get(command, '%02X' % command))
if msg == 12:
if len(data) != 8:
raise ValueError('BOOT_STATUS requires DLC=8')
text += ' status=%d slot=%d next_block=%d CRC32=%08X' % (data[0], data[1], number(data[2:4]), number(data[4:8]))
elif command in (3, 4):
if len(data) != 8:
raise ValueError('BOOT metadata requires DLC=8')
if command == 3:
text += ' image_size=%d CRC32=%08X' % (number(data[:4]), number(data[4:]))
else:
text += ' product=%d hw=%d..%d version=%08X' % (number(data[:2]), data[2], data[3], number(data[4:]))
elif command in BOOT and data:
raise ValueError('BOOT control requires DLC=0')
elif msg in (10, 11):
if len(data) != 8:
raise ValueError('BOOT_DATA requires DLC=8')
text += ' block=%d offset=0x%X' % (body, body * 8)
elif msg in (0, 1, 2):
shift = 4 if msg == 0 else 12
text += ' type=%X value=%X' % (body >> shift, body & ((1 << shift) - 1))
result['summary'] = text
return result
class Reassembler:
def __init__(self):
self.pending = {}
def feed(self, ident, data, ss, es, now_ms):
events = []
for key, state in list(self.pending.items()):
if now_ms - state['time'] >= 500:
events.append((state['start'], es, None, 'SET CAN timeout ID=%08X' % key))
del self.pending[key]
if ident >> 24 != 0x12:
return events
try:
if not 1 <= len(data) <= 8:
raise ValueError('Invalid SET CAN DLC')
pci = data[0] & 0xf0
if pci == 0x30:
if len(data) != 3 or data[0] & 15 > 2 or data[2] > 127:
raise ValueError('Invalid FLOW_CONTROL')
events.append((ss, es, dict(protocol='SET CAN', summary='FLOW_CONTROL status=%d block=%d st_min=%d ms' % (data[0] & 15, data[1], data[2])), None))
elif pci == 0x10:
if data[0] != 0x10 or len(data) != 8 or not 18 <= number(data[1:3]) <= 530:
raise ValueError('Invalid FIRST segment')
old = self.pending.pop(ident, None)
if old:
events.append((old['start'], es, None, 'FIRST replaced incomplete packet'))
if len(self.pending) >= 64:
raise ValueError('Too many concurrent SET CAN packets (64)')
self.pending[ident] = dict(start=ss, time=now_ms, total=number(data[1:3]),
data=bytearray(data[3:]), sequence=1)
events.append((ss, es, dict(protocol='SET CAN', summary='FIRST length=%d' % number(data[1:3])), None))
elif pci == 0x20:
state = self.pending.get(ident)
if not state or len(data) < 2 or data[0] & 15 != state['sequence']:
raise ValueError('Unexpected CONSECUTIVE sequence')
state['data'].extend(data[1:])
state['time'], state['sequence'] = now_ms, (state['sequence'] + 1) & 15
if len(state['data']) > state['total']:
raise ValueError('SET CAN packet exceeds advertised length')
if len(state['data']) == state['total']:
packet = parse_frame(state['data'])
if packet['protocol'] != 'SET v2' or packet['source'] != (ident >> 8) & 255 or packet['destination'] != (ident >> 16) & 255 or bool(packet['flags'] & 32) != bool(ident & 128):
raise ValueError('CAN ID and SET header disagree')
events.append((state['start'], es, packet, None))
del self.pending[ident]
else:
events.append((ss, es, dict(protocol='SET CAN', summary='CONSECUTIVE SN=%d' % (data[0] & 15)), None))
else:
raise ValueError('Unknown SET CAN PCI')
except ValueError as exc:
state = self.pending.pop(ident, None)
events.append((state['start'] if state else ss, es, None, str(exc)))
return events