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
52 lines
1.7 KiB
Python
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())
|