Tooling and findings for running an E8x/E9x electric power steering unit
outside its donor car, e.g. in an EV conversion.
Headline result: the EPS needs only two CAN messages plus a 12V enable
wire, not the 69-message set the car puts on the bus:
0x130 CAS terminal status (100ms) brings the unit up
0x1A0 DSC road speed (20ms) sets the assist level
Total required rate is 60 frames/s. Protocol write-up, including what is
proven vs. inferred and the open questions, is in eps-comms/.
Contents:
adapters/ CANdapter (its SLCAN dialect differs) and generic SLCAN
gateway/ car<->EPS relay, replay, message bench, EPS controller,
4-tab Streamlit UI
decoder/ PT-CAN frame decoding and live/replay sources
can-io/ XIAO ESP32-S3 firmware: CAN IO board + USB-CAN bridge with
a CAN-independent digital IO channel
tools/ capture, bitrate scan, startup-order and session analysis,
checksum solver
captures/ reference working session + the replay set eps_control reads
66 lines
2.4 KiB
Python
66 lines
2.4 KiB
Python
"""Read and decode CAN frames from the CANdapter (candapter.com) USB adapter.
|
|
|
|
Usage:
|
|
python read_can.py [--bitrate 500000] [--port /dev/cu.usbserial-DNBJV4F5]
|
|
|
|
Connect the adapter to the CAN bus first, then run this script. It opens the
|
|
channel at the given bitrate and prints every frame it receives: timestamp,
|
|
arbitration ID (with an 'x' suffix for 29-bit extended IDs), DLC, raw bytes
|
|
(hex), and an ASCII preview.
|
|
|
|
If you don't know the bus's bitrate, common values are 125000, 250000,
|
|
500000 (most likely - the adapter is rated for automotive use at 500 kbps),
|
|
and 1000000. Wrong bitrate usually means zero frames or garbled data --
|
|
try another value.
|
|
"""
|
|
import argparse
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
from adapters.candapter import Candapter # noqa: E402
|
|
|
|
DEFAULT_PORT = "/dev/cu.usbserial-DNBJV4F5"
|
|
DEFAULT_BITRATE = 500000
|
|
|
|
|
|
def ascii_preview(data: bytes) -> str:
|
|
return "".join(chr(b) if 32 <= b < 127 else "." for b in data)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--port", default=DEFAULT_PORT, help="Serial device path")
|
|
parser.add_argument("--bitrate", type=int, default=DEFAULT_BITRATE, help="CAN bus bitrate")
|
|
args = parser.parse_args()
|
|
|
|
try:
|
|
adapter = Candapter(args.port, args.bitrate, timestamps=True)
|
|
except (OSError, ValueError) as exc:
|
|
print(f"Failed to open adapter on {args.port}: {exc}", file=sys.stderr)
|
|
return 1
|
|
|
|
print(f"Listening on {args.port} at {args.bitrate} bps (Ctrl+C to stop)...\n")
|
|
print(f"{'timestamp':>14} {'id':>10} {'dlc':>3} data (hex) ascii")
|
|
|
|
try:
|
|
with adapter:
|
|
while True:
|
|
frame = adapter.read_frame(timeout=None)
|
|
if frame is None:
|
|
continue
|
|
id_str = f"{frame.arbitration_id:08X}x" if frame.is_extended else f"{frame.arbitration_id:03X}"
|
|
data_hex = " ".join(f"{b:02X}" for b in frame.data)
|
|
ts = frame.timestamp_ms if frame.timestamp_ms is not None else frame.recv_time
|
|
print(
|
|
f"{ts:14.3f} {id_str:>10} {len(frame.data):>3} "
|
|
f"{data_hex:<24} {ascii_preview(frame.data)}"
|
|
)
|
|
except KeyboardInterrupt:
|
|
print("\nStopped.")
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|