Files
AMI/scripts/send_drivetrain_can.py
T

80 lines
3.5 KiB
Python

"""Send AMI 0x182 display values through a python-can adapter (no motor actuation)."""
import argparse
from decimal import Decimal, InvalidOperation
import os
import struct
import time
def temperature_raw(value):
try:
scaled = Decimal(value) * 10
if not scaled.is_finite() or scaled != scaled.to_integral_value():
raise ValueError('temperature must have at most one decimal place')
raw = int(scaled)
except (InvalidOperation, OverflowError) as exc:
raise ValueError('invalid temperature') from exc
if not -32768 <= raw <= 32767:
raise ValueError('temperature must be -3276.8..3276.7 degC')
return raw
def build_payload(torque, motor_temp, mcu_temp, valid=7):
if not -1000 <= torque <= 1000:
raise ValueError('torque must be -1000..1000 permille')
if not 0 <= valid <= 7:
raise ValueError('valid must be 0..7 (bit0 torque, bit1 motor, bit2 MCU)')
return struct.pack('<hhhBB', torque, temperature_raw(motor_temp),
temperature_raw(mcu_temp), valid, 1)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--torque', type=int, required=True)
parser.add_argument('--motor-temp', required=True, help='degC, up to one decimal place')
parser.add_argument('--mcu-temp', required=True, help='motor-controller temperature, degC')
parser.add_argument('--valid', type=int, default=7, help='validity mask 0..7, default all valid')
parser.add_argument('--interface', default='pcan')
parser.add_argument('--channel', default='PCAN_USBBUS1')
parser.add_argument('--bitrate', type=int, choices=(250000, 500000), default=250000)
parser.add_argument('--period', type=float, default=0.1, help='seconds, default 100 ms')
parser.add_argument('--duration', type=float, default=10, help='seconds; finite transmission')
parser.add_argument('--dll-dir', help='Windows adapter DLL directory, if required')
parser.add_argument('--dry-run', action='store_true', help='print payload without opening CAN')
args = parser.parse_args()
try:
payload = build_payload(args.torque, args.motor_temp, args.mcu_temp, args.valid)
except ValueError as exc:
parser.error(str(exc))
if not (0 < args.period <= 2 and 0 < args.duration < float('inf')):
parser.error('period must be >0 and <=2 seconds; duration must be positive and finite')
print(f'Standard ID 0x182, DLC 8, data {payload.hex(" ").upper()}, bitrate {args.bitrate}', flush=True)
if args.dry_run:
return
dll_handle = None
if args.dll_dir:
path = os.path.abspath(args.dll_dir)
os.environ['PATH'] = path + os.pathsep + os.environ['PATH']
if hasattr(os, 'add_dll_directory'):
dll_handle = os.add_dll_directory(path)
import can
count = 0
try:
with can.Bus(interface=args.interface, channel=args.channel, bitrate=args.bitrate) as bus:
message = can.Message(arbitration_id=0x182, is_extended_id=False, data=payload)
started = time.monotonic()
deadline = started
while time.monotonic() < started + args.duration:
bus.send(message, timeout=0.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. Values expire after 2 seconds without valid input.')
if __name__ == '__main__':
main()