# 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_.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.**