BMW_E8x_EPS/tools/probe_adapter.py
Luca c32c4645b5 Reverse-engineer BMW E8x EPS for standalone operation
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
2026-08-29 19:34:43 +02:00

52 lines
1.7 KiB
Python

"""Probe the USB-CAN adapter to confirm it speaks a Lawicel-style ASCII protocol.
The adapter is a CANdapter (Ewert Energy Systems / candapter.com), which
enumerates as an FTDI FT240X USB-serial chip and shares its bring-up commands
(V/N/S/O/C) with SLCAN, though its frame format diverges - see candapter.py.
This script only exercises the shared commands (no CAN bus needs to be
connected yet) to read the firmware version and serial number.
"""
import sys
import time
import serial
PORT = "/dev/cu.usbserial-DNBJV4F5"
BAUD = 115200 # standard SLCAN control baud rate (independent of CAN bitrate)
def query(ser: serial.Serial, cmd: str, wait: float = 0.3) -> str:
ser.reset_input_buffer()
ser.write((cmd + "\r").encode("ascii"))
time.sleep(wait)
return ser.read(ser.in_waiting or 1).decode("ascii", errors="replace")
def main() -> int:
try:
ser = serial.Serial(PORT, BAUD, timeout=1)
except serial.SerialException as exc:
print(f"Could not open {PORT}: {exc}", file=sys.stderr)
return 1
with ser:
# Close channel first in case it was left open, ignore any response.
query(ser, "C")
version = query(ser, "V")
serial_no = query(ser, "N")
print(f"Port: {PORT}")
print(f"Raw version reply: {version!r}")
print(f"Raw serial# reply: {serial_no!r}")
if version.startswith("V") or serial_no.startswith("N"):
print("\nAdapter responded to SLCAN commands - looks like a Lawicel/CANUSB-compatible device.")
else:
print("\nNo recognizable SLCAN response. The adapter may use a different protocol.")
return 0
if __name__ == "__main__":
raise SystemExit(main())