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
61 lines
1.7 KiB
Python
61 lines
1.7 KiB
Python
"""Dump whatever raw bytes the CANdapter sends after opening a channel.
|
|
|
|
Useful when frames aren't parsing as expected - shows the literal serial
|
|
stream so we can see if it's real (but differently-shaped) CAN frames,
|
|
error/status chatter, or nothing at all.
|
|
|
|
Usage:
|
|
python raw_dump.py [--bitrate 500000] [--seconds 5]
|
|
"""
|
|
import argparse
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import serial
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
from adapters.candapter import BITRATE_CODES # noqa: E402
|
|
|
|
DEFAULT_PORT = "/dev/cu.usbserial-DNBJV4F5"
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--port", default=DEFAULT_PORT)
|
|
parser.add_argument("--bitrate", type=int, default=500_000)
|
|
parser.add_argument("--seconds", type=float, default=5.0)
|
|
args = parser.parse_args()
|
|
|
|
ser = serial.Serial(args.port, 115200, timeout=0.2)
|
|
with ser:
|
|
ser.write(b"C\r")
|
|
time.sleep(0.1)
|
|
ser.read(ser.in_waiting or 1)
|
|
|
|
ser.write(f"S{BITRATE_CODES[args.bitrate]}\r".encode())
|
|
time.sleep(0.1)
|
|
print("S reply:", ser.read(ser.in_waiting or 1))
|
|
|
|
ser.write(b"O\r")
|
|
time.sleep(0.1)
|
|
print("O reply:", ser.read(ser.in_waiting or 1))
|
|
|
|
print(f"\nRaw stream for {args.seconds}s:")
|
|
deadline = time.monotonic() + args.seconds
|
|
got_any = False
|
|
while time.monotonic() < deadline:
|
|
chunk = ser.read(256)
|
|
if chunk:
|
|
got_any = True
|
|
print(repr(chunk))
|
|
if not got_any:
|
|
print("(nothing received)")
|
|
|
|
ser.write(b"C\r")
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|