"""Send custom AMI 0x183 VCU display feedback; no vehicle actuation.""" import argparse import math import os import time def build_payload(task, locked, ebs_normal, valid=7, sequence=0, as_state=0, version=2): if task not in range(7) or locked not in (0, 1) or ebs_normal not in (0, 1): raise ValueError('task must be 0..6; locked and ebs-normal must be 0 or 1') if version not in (1, 2) or as_state not in range(6): raise ValueError('version must be 1 or 2; as-state must be 0..5') if valid not in range(8 if version == 1 else 16) or sequence not in range(256): raise ValueError('valid must be 0..7 (v1) or 0..15 (v2); sequence must be 0..255') if version == 1 and as_state != 0: raise ValueError('v1 has no AS state; byte 5 must be zero') return bytes([task, locked, ebs_normal, valid, sequence, as_state, 0, version]) def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--task', type=int, required=True, choices=range(7)) parser.add_argument('--locked', type=int, required=True, choices=(0, 1)) parser.add_argument('--ebs-normal', type=int, required=True, choices=(0, 1)) parser.add_argument('--valid', type=int, help='default 15 with --as-state, otherwise 7') parser.add_argument('--as-state', type=int, choices=range(6), help='0 OFF, 1 Ready, 2 Driving, 3 Finished, 4 Emergency, 5 Manual') parser.add_argument('--version', type=int, choices=(1, 2), default=2) parser.add_argument('--sequence', type=int, default=0) parser.add_argument('--interface', default='pcan') parser.add_argument('--channel', default='PCAN_USBBUS1') parser.add_argument('--bitrate', type=int, choices=(250000, 500000), default=500000) parser.add_argument('--period', type=float, default=.1) parser.add_argument('--duration', type=float, default=10) parser.add_argument('--dll-dir') parser.add_argument('--dry-run', action='store_true') args = parser.parse_args() try: if args.version == 1 and args.as_state is not None: raise ValueError('--as-state requires --version 2') valid = args.valid if args.valid is not None else (15 if args.as_state is not None else 7) as_state = args.as_state if args.as_state is not None else 0 payload = build_payload(args.task, args.locked, args.ebs_normal, valid, args.sequence, as_state, args.version) except ValueError as exc: parser.error(str(exc)) if not (0 < args.period <= .5 and 0 < args.duration and math.isfinite(args.duration)): parser.error('period must be >0 and <=0.5 seconds; duration must be finite and positive') print(f'Standard ID 0x183, DLC 8, data {payload.hex(" ").upper()}, bitrate {args.bitrate}', flush=True) if args.dry_run: return dll_handle = None if args.dll_dir: directory = os.path.abspath(args.dll_dir) os.environ['PATH'] = directory + os.pathsep + os.environ['PATH'] dll_handle = os.add_dll_directory(directory) import can count = 0 try: with can.Bus(interface=args.interface, channel=args.channel, bitrate=args.bitrate) as bus: started = time.monotonic() deadline = started while time.monotonic() < started + args.duration: payload = build_payload(args.task, args.locked, args.ebs_normal, valid, (args.sequence + count) & 255, as_state, args.version) bus.send(can.Message(arbitration_id=0x183, is_extended_id=False, data=payload), timeout=.2) count += 1 deadline += args.period time.sleep(max(0, min(deadline, started + args.duration) - time.monotonic())) finally: if dll_handle is not None: dll_handle.close() print(f'Sent {count} frames. Display becomes unknown over 500 ms after feedback stops.') if __name__ == '__main__': main()