mirror of
https://github.com/intrepidcs/libicsneo.git
synced 2026-11-04 21:09:06 +01:00
265 lines
8.4 KiB
Python
265 lines
8.4 KiB
Python
"""
|
|
Two-device ISO-TP loopback on CAN1.
|
|
|
|
Firmware segments the payload. This script sends it once and prints CAN1
|
|
frames with the ISO-TP PCI nibble decoded (SF/FF/CF/FC).
|
|
|
|
Wire CAN1 of two devices together (CANH/CANL, terminated).
|
|
|
|
python iso15765_loopback.py --raw
|
|
python iso15765_loopback.py --sf
|
|
python iso15765_loopback.py --both
|
|
"""
|
|
|
|
import argparse
|
|
import threading
|
|
import time
|
|
from datetime import timedelta
|
|
|
|
import icsneopy
|
|
|
|
|
|
CAN1 = icsneopy.Network.NetID.DWCAN_01
|
|
|
|
|
|
def pci_describe(data):
|
|
if not data:
|
|
return ""
|
|
pci_type = data[0] >> 4
|
|
pci_low = data[0] & 0x0F
|
|
if pci_type == 0x0:
|
|
if pci_low > 7 or len(data) < 1 + pci_low:
|
|
return ""
|
|
return f"SF len={pci_low}"
|
|
if pci_type == 0x1:
|
|
if len(data) < 8:
|
|
return ""
|
|
length = (pci_low << 8) | data[1]
|
|
if length < 8:
|
|
return ""
|
|
return f"FF len={length}"
|
|
if pci_type == 0x2:
|
|
if len(data) < 2:
|
|
return ""
|
|
return f"CF SN={pci_low}"
|
|
if pci_type == 0x3:
|
|
if len(data) < 3 or pci_low > 2:
|
|
return ""
|
|
fs_name = ("CTS", "WAIT", "OVFLW")[pci_low]
|
|
return f"FC FS={fs_name} BS={data[1]} STmin=0x{data[2]:02X}"
|
|
return ""
|
|
|
|
|
|
def print_api_result(what, ok):
|
|
line = f" {what}: {'OK' if ok else 'FAIL'}"
|
|
if not ok:
|
|
err = icsneopy.get_last_error()
|
|
if err.get_type() != icsneopy.APIEvent.Type.NoErrorFound:
|
|
line += f" [{err.describe()}]"
|
|
print(line)
|
|
|
|
|
|
def is_extended(arb_id):
|
|
return arb_id > 0x7FF
|
|
|
|
|
|
def make_args(tx_id, fc_id, payload, rx):
|
|
msg = icsneopy.Iso15765MessageArgs()
|
|
msg.tx_index = 0
|
|
msg.arb_id = icsneopy.Iso15765MessageArgs.ArbId(tx_id, is_extended(tx_id))
|
|
msg.flow_control_arb_id = icsneopy.Iso15765MessageArgs.ArbId(fc_id, is_extended(fc_id))
|
|
msg.flow_control_arb_id_mask = 0x1FFFFFFF if is_extended(tx_id) else 0x7FF
|
|
msg.is_can_fd = False
|
|
msg.is_brs_enabled = False
|
|
msg.tx_dl = 8
|
|
msg.padding_value = 0x00
|
|
msg.fs_timeout = 75
|
|
msg.fs_wait_timeout = 150
|
|
msg.cf_timeout = 150
|
|
msg.is_flow_control_enabled = rx
|
|
if payload:
|
|
msg.data = payload
|
|
return msg
|
|
|
|
|
|
def transmit_can(device, arb_id, data):
|
|
frame = icsneopy.CANMessage()
|
|
frame.network = icsneopy.Network(CAN1)
|
|
frame.arbid = arb_id
|
|
# RawMessage.data is vector<uint8_t>: a list of ints, not bytes.
|
|
frame.data = list(data)
|
|
frame.isExtended = is_extended(arb_id)
|
|
frame.isCANFD = False
|
|
ok = device.transmit(frame)
|
|
if not ok:
|
|
print_api_result("transmit CAN", ok)
|
|
return ok
|
|
|
|
|
|
class Sniffer:
|
|
def __init__(self, tag):
|
|
self.tag = tag
|
|
self.t0 = None
|
|
self.lock = threading.Lock()
|
|
|
|
def on_message(self, message):
|
|
if not isinstance(message, icsneopy.CANMessage):
|
|
return
|
|
if message.network.get_type() != icsneopy.Network.Type.CAN:
|
|
return
|
|
with self.lock:
|
|
if self.t0 is None:
|
|
self.t0 = message.timestamp
|
|
rel_ms = 0.0
|
|
if message.timestamp >= self.t0:
|
|
rel_ms = (message.timestamp - self.t0) / 1e6
|
|
direction = "TX" if message.transmitted else "RX"
|
|
width = 8 if is_extended(message.arbid) else 3
|
|
data = " ".join(f"{b:02X}" for b in message.data)
|
|
line = (
|
|
f" [{self.tag} {direction}] {rel_ms:8.3f} ms "
|
|
f"{message.network} 0x{message.arbid:0{width}X} "
|
|
f"[{len(message.data)}] {data}"
|
|
)
|
|
pci = pci_describe(message.data)
|
|
if pci:
|
|
line += f" {pci}"
|
|
print(line)
|
|
|
|
|
|
def open_and_online(device, baud, keep_baud):
|
|
print(f"Opening {device.describe()} ...")
|
|
if not device.open():
|
|
print_api_result("open", False)
|
|
return False
|
|
if not keep_baud and device.settings is not None:
|
|
net = icsneopy.Network(CAN1)
|
|
if not device.settings.set_baudrate(net, baud):
|
|
print_api_result("set_baudrate", False)
|
|
elif not device.settings.apply(True):
|
|
print_api_result("settings.apply(temporary)", False)
|
|
if not device.go_online():
|
|
print_api_result("go_online", False)
|
|
device.close()
|
|
return False
|
|
return True
|
|
|
|
|
|
def close_device(device):
|
|
if device is None:
|
|
return
|
|
device.iso15765_disable_all()
|
|
if device.is_online():
|
|
device.go_offline()
|
|
if device.is_open():
|
|
device.close()
|
|
|
|
|
|
def find_by_serial(devices, serial):
|
|
for device in devices:
|
|
if device.get_serial().lower() == serial.lower():
|
|
return device
|
|
return None
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(
|
|
description="Two Intrepid devices with CAN1 wired together (CANH/CANL, terminated)."
|
|
)
|
|
mode = parser.add_mutually_exclusive_group(required=True)
|
|
mode.add_argument("--raw", action="store_true", help="Raw CAN ping-pong (cable check)")
|
|
mode.add_argument("--sf", action="store_true", help="Firmware ISO-TP single-frame TX from tester")
|
|
mode.add_argument("--both", action="store_true", help="Firmware ISO-TP TX on tester, firmware flow-control on ECU")
|
|
parser.add_argument("--tester", default="", help="Tester (sender) serial. Optional if exactly two devices are found.")
|
|
parser.add_argument("--ecu", default="", help="ECU / peer serial. Optional if exactly two devices are found.")
|
|
parser.add_argument("--baud", type=int, default=500000, help="CAN1 baud, applied temporarily (default 500000)")
|
|
parser.add_argument("--keep-baud", action="store_true", help="Do not change device baud")
|
|
parser.add_argument("--tx-id", type=lambda s: int(s, 0), default=0x7E0, help="Tester ISO-TP CAN ID (default 0x7E0)")
|
|
parser.add_argument("--fc-id", type=lambda s: int(s, 0), default=0x7E8, help="Flow-control CAN ID (default 0x7E8)")
|
|
parser.add_argument("--bytes", type=int, default=None, help="ISO-TP payload size (sf default 5, both default 20)")
|
|
parser.add_argument("--listen-ms", type=int, default=2000, help="Time to listen after the action (default 2000)")
|
|
return parser.parse_args()
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
print(f"libicsneo {icsneopy.get_version()}")
|
|
|
|
devices = icsneopy.find_all_devices()
|
|
print(f"Found {len(devices)} device(s)")
|
|
for device in devices:
|
|
print(f" {device.describe()} serial={device.get_serial()}")
|
|
if len(devices) < 2:
|
|
raise SystemExit("Need two devices connected.")
|
|
|
|
if not args.tester and not args.ecu:
|
|
if len(devices) != 2:
|
|
raise SystemExit("More than two devices found; pass --tester and --ecu serials.")
|
|
tester, ecu = devices[0], devices[1]
|
|
print("No serials given; using first as tester, second as ECU.")
|
|
else:
|
|
if not args.tester or not args.ecu:
|
|
raise SystemExit("Pass both --tester and --ecu, or neither.")
|
|
tester = find_by_serial(devices, args.tester)
|
|
ecu = find_by_serial(devices, args.ecu)
|
|
if tester is None or ecu is None:
|
|
raise SystemExit("Could not match tester/ECU serials.")
|
|
|
|
payload_size = args.bytes if args.bytes is not None else (5 if args.sf else 20)
|
|
payload = [i & 0xFF for i in range(payload_size)]
|
|
|
|
print(f"Tester: {tester.describe()}")
|
|
print(f"ECU: {ecu.describe()}")
|
|
if not args.raw:
|
|
print(f"CAN1 TX 0x{args.tx_id:X} FC 0x{args.fc_id:X} payload {payload_size} bytes")
|
|
print()
|
|
|
|
network = icsneopy.Network(CAN1)
|
|
try:
|
|
if not open_and_online(tester, args.baud, args.keep_baud):
|
|
return 1
|
|
if not open_and_online(ecu, args.baud, args.keep_baud):
|
|
return 1
|
|
|
|
for device, sniffer in ((tester, Sniffer("TESTER")), (ecu, Sniffer("ECU "))):
|
|
callback = icsneopy.MessageCallback(sniffer.on_message, icsneopy.MessageFilter(CAN1))
|
|
device.add_message_callback(callback)
|
|
|
|
if args.raw:
|
|
print("Mode --raw: tester 0x123, then ECU 0x456")
|
|
transmit_can(tester, 0x123, [0x11, 0x22, 0x33, 0x44])
|
|
time.sleep(0.2)
|
|
transmit_can(ecu, 0x456, [0x55, 0x66, 0x77, 0x88])
|
|
elif args.sf:
|
|
print("Mode --sf: firmware ISO-TP single frame from tester")
|
|
if len(payload) > 7:
|
|
print(" warning: payload > 7 bytes will be a First Frame (no flow control in this mode)")
|
|
ok = tester.iso15765_enable(network)
|
|
print_api_result("tester iso15765_enable", ok)
|
|
msg = make_args(args.tx_id, args.fc_id, payload, rx=False)
|
|
ok = tester.iso15765_transmit_message(network, msg, timedelta(0))
|
|
print_api_result("tester iso15765_transmit_message", ok)
|
|
else:
|
|
print("Mode --both: firmware TX on tester, firmware flow-control on ECU")
|
|
ok = tester.iso15765_enable(network)
|
|
print_api_result("tester iso15765_enable", ok)
|
|
ok = ecu.iso15765_enable(network)
|
|
print_api_result("ECU iso15765_enable", ok)
|
|
msg = make_args(args.tx_id, args.fc_id, [], rx=True)
|
|
ok = ecu.iso15765_setup_rx_flow_control(network, msg)
|
|
print_api_result("ECU iso15765_setup_rx_flow_control", ok)
|
|
time.sleep(0.1)
|
|
msg = make_args(args.tx_id, args.fc_id, payload, rx=False)
|
|
ok = tester.iso15765_transmit_message(network, msg, timedelta(0))
|
|
print_api_result("tester iso15765_transmit_message", ok)
|
|
|
|
time.sleep(args.listen_ms / 1000.0)
|
|
finally:
|
|
close_device(tester)
|
|
close_device(ecu)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|