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
101 lines
5.3 KiB
Markdown
101 lines
5.3 KiB
Markdown
# EPS communication notes
|
|
|
|
Findings about what the Electric Power Steering (EPS) unit needs on the bus,
|
|
built up from gateway sessions between the car's PT-CAN bus and the EPS's
|
|
(isolated) bus via the CAN-IO board.
|
|
|
|
- **[EPS_PROTOCOL.md](EPS_PROTOCOL.md)** — the consolidated result: how to
|
|
power up, control and monitor the EPS standalone (e.g. in a custom EV).
|
|
Start here.
|
|
- **[findings.md](findings.md)** — chronological working log: what was
|
|
tested, what broke, and why.
|
|
|
|
**Headline result:** the EPS needs only two CAN messages (`0x130` terminal
|
|
status, `0x1A0` road speed) plus a 12V enable wire — not the 69 messages the
|
|
car puts on the bus. Total required rate: 60 frames/s.
|
|
|
|
## Setup
|
|
|
|
```
|
|
car PT-CAN --[CANdapter]-- gateway (this repo) --[CAN-IO board USB bridge]-- EPS bus -- EPS unit
|
|
```
|
|
|
|
- Car side: CANdapter, port varies (`/dev/cu.usbserial-DNBJV4F5` as of writing).
|
|
- EPS side: CAN-IO board (XIAO ESP32-S3, `can-io/`), port varies (`/dev/cu.usbmodemXXXX` -
|
|
changes on replug, always re-check with `ls /dev/cu.*`).
|
|
- Both sides run at 500 kbit/s.
|
|
- Tool: `gateway/gateway_app.py` (live relay + per-ID filter + IO test) or
|
|
`gateway/replay_to_bus.py` (replay a capture onto one side, no car needed).
|
|
- Every gateway session logs every frame seen (relayed or not) to
|
|
`captures/gateway_<timestamp>.csv` - columns: `t, direction, arbitration_id,
|
|
is_extended, dlc, data_hex, relayed`.
|
|
|
|
## Workflow
|
|
|
|
1. **Transparent relay** (current stage): everything allowed both ways,
|
|
confirm EPS + car both still behave normally through the gateway.
|
|
2. **Filter down**: block IDs one at a time (or in groups) via the "Allow"
|
|
checkboxes in `gateway_app.py`, re-test after each cut, to find the
|
|
minimal set of car-bus messages the EPS actually needs.
|
|
3. **Replay-only**: once step 2 gives a candidate minimal set, use
|
|
`gateway/replay_to_bus.py` to feed just those messages to the EPS with
|
|
the car fully disconnected, to confirm it's really enough to bring the
|
|
EPS up on its own.
|
|
4. **Synthesize**: once replay works, write a generator that produces those
|
|
messages from scratch (no capture needed) - not started yet.
|
|
|
|
## Tools
|
|
|
|
- `gateway/gateway_app.py` - live relay + per-ID filter + IO test (Streamlit).
|
|
- `gateway/replay_to_bus.py` - replay a capture CSV onto one adapter, with
|
|
the same FilterRules filtering, `--dry-run` needs no hardware.
|
|
- `tools/gateway_log_to_capture.py` - turns a gateway session log (which has
|
|
both directions + relay/block status) into a plain capture CSV for
|
|
`replay_to_bus.py`, e.g. to replay exactly what a working live session
|
|
sent, filtered to one direction and optionally only the frames that were
|
|
actually relayed.
|
|
- `tools/extract_dio_timeline.py` - pulls the CAN-IO board's own digital
|
|
IN1-4/OUT1-2 timeline out of a gateway log (decoded from its 0x100 status
|
|
frames) - answers "when was the 12V signal high/low", and feeds
|
|
`replay_to_bus.py --dio-log` so a replay reproduces the digital signal
|
|
(e.g. the 12V repeat to the EPS) in sync with the CAN traffic, not just
|
|
the CAN frames alone. Resolution is limited to whenever the board sent a
|
|
status frame (on change, plus a 1s heartbeat) - a change undone within a
|
|
couple of scheduling ticks might not show up as its own sample.
|
|
- `gateway/rules.suggested.json` / `rules.suggested_car_to_eps.json` -
|
|
current best-guess filter (see findings.md for what's blocked and why):
|
|
the combined form loads in `gateway_app.py`'s sidebar, the plain form
|
|
loads via `replay_to_bus.py --rules`.
|
|
|
|
## Synthesis plan (not started)
|
|
|
|
Once a minimal, replay-confirmed message set is known:
|
|
|
|
1. For each required ID, write an encoder (the mirror image of
|
|
`files/decode_ptcan.py`'s `SIGNALS` decoders) that produces the raw bytes
|
|
from a target value (angle, speed, rpm, terminal state, ...), including
|
|
the alive-counter and checksum schemes already documented in
|
|
`files/PTCAN_protocol.md`.
|
|
2. Start with static/slowly-changing values (terminal state, a fixed
|
|
"engine running" RPM, zero speed) to prove the EPS accepts synthetic
|
|
frames at all, before worrying about realistic dynamic values.
|
|
3. Time the generator against `gateway/replay_to_bus.py`'s measured periods
|
|
per ID (10ms for steering angle, 20ms for road speed, etc.) - this is
|
|
also where the jitter hypothesis from findings.md gets tested: a
|
|
dedicated generator (ideally on the CAN-IO board itself, not the PC) can
|
|
hit those periods far more precisely than the PC-mediated relay.
|
|
4. Longer term: move the generator onto the CAN-IO board's firmware itself,
|
|
so the final setup needs no PC in the loop at all.
|
|
|
|
## Known pitfalls (see findings.md for detail)
|
|
|
|
- **TX-echo feedback loop (fixed 2026-08-29)**: the CAN-IO board's firmware
|
|
used to mirror every frame it successfully transmitted back over USB,
|
|
including frames the gateway had just asked it to relay from the car bus -
|
|
not just its own status/heartbeat. This made every relayed ID falsely
|
|
reappear as "new EPS-bus traffic" a moment later, which is what session
|
|
`gateway_20260829_162235.csv` shows (64 of 69 "eps->car" IDs were things
|
|
just relayed `car->eps` seconds earlier). Fixed in `can_bus.cpp`/`usb_bridge.cpp`
|
|
by only mirroring board-originated transmits, not bridge-injected ones.
|
|
**Any gateway log captured before this fix should be treated as unreliable
|
|
for "what does the EPS bus actually carry" purposes.**
|