# ford-obd — Architecture & Roadmap Plan for growing the terminal `obd_reader.py` into a graphical Windows scan tool for the 6.0L Power Stroke, on a shared headless core. ## Vision A desktop app with: - a **PID browser** (searchable list, live values) on the side, - a flexible **graph workspace** — overlay many metrics on one plot, split into a grid of single-metric plots, or show gauges (one metric each), - purpose-built **perspectives**: Cranking, Driving, Diagnostics (DTCs), Logging, - a **Ford DTC database** behind the codes page, - **session record + playback** so intermittent faults can be reviewed offline. ## The constraint that shapes everything: ELM327 bandwidth The ELM327 is a one-request-at-a-time link: each Mode-22 round-trip is ~40–150 ms over the CH340, so total throughput is **~7–15 PID reads/sec**, shared across the whole UI. Consequences baked into the design: - A **prioritized polling scheduler** owns the link. The active view subscribes the PIDs it needs at the rates it needs (cranking = ICP fast, ignore the rest). - Acquisition runs **off the UI thread**; the GUI only reads the store. - Per-PID rates and dead-PID parking keep the sample rate from collapsing. - A faster adapter (**OBDLink SX/MX+**, STN chip — batched PIDs, faster protocols) multiplies throughput; the link layer is abstracted so either works. ## Layers ``` +------------------- GUI (PySide6 + pyqtgraph) -------------------+ | PID browser | Graph workspace | Perspectives | DTC page | Log | +--------------------------------|-------------------------------+ reads only | (Qt thread) +--------------------------------v-------------------------------+ | obdcore (headless) | | PollScheduler --reads--> ElmLink / MockLink --serial--> PCM | | | pushes samples | | v | | TimeSeriesStore <--- CsvRecorder / replay_csv | | PidRegistry (verified PIDs) DtcDatabase | +-----------------------------------------------------------------+ ``` `obdcore` is pure data/IO — no Qt, no curses — so it's shared by the terminal tool, the GUI, and tests. ### obdcore modules (built, tested) | Module | Responsibility | |---|---| | `link.py` `ElmLink` | ELM327 serial: init, protocol negotiate, Mode-01/22 reads, ATRV, DTC read/clear. Returns raw bytes. | | `mock.py` `MockLink` | Synthetic crank (ICP ramp, FICM ~48V, batt sag) — same interface; powers tests + GUI dev with no truck. | | `registry.py` `PidRegistry` | Verified Ford 6.0 PID table (corrected addresses + scaling + confidence) and subscription presets. `DtcDatabase` seeds the code DB. | | `scheduler.py` `PollScheduler` | Prioritized round-robin polling; per-PID Hz; derived channels; dead-PID park/revive. `tick()` is test-drivable with a fake clock. | | `store.py` `TimeSeriesStore` | Per-PID ring buffers + min/max; `CsvRecorder` (long format) + `replay_csv` for record/playback. | Tests: `tests/test_obdcore.py` — decoders vs real truck bytes, crank ramp + peak capture, derived BOOST, dead-PID parking, record/replay round-trip. ## Data model - **Pid**: key, name, mode (`01`/`22`/`atrv`/`derived`), pid hex, nbytes, decode fn, unit, group, vmin/vmax, **confidence** (`verified|doc|tentative`), deps (for derived), notes. - **Channel**: rolling `(t, value)` + session min/max. - **Derived/virtual channels**: e.g. `BOOST = MAP − BARO`; later `ICP_error = ICP_DES − ICP`, `FICM_sag = FICM_M − FICM_V`. ## GUI plan (PySide6 + pyqtgraph) - **Left dock**: `QTreeView` PID browser grouped by system (fuel/ficm/air/…), live value + confidence badge, checkbox to add to the focused panel. - **Central workspace**: dockable/tabbed panels. Panel types: - *Overlay plot* — many PIDs, multiple Y-axes (pyqtgraph `ViewBox` linking). - *Split grid* — one plot per PID. - *Gauge* — radial/linear single metric with warn/crit bands. Drag a PID from the browser onto a panel to add it. - **Perspectives** (saved layouts): - **Cranking** — ICP big readout + 500-psi firing line + peak-hold + trace (port of terminal `--crank`). - **Driving** — boost (MGP), EOT, ECT, EBP, load, RPM, IPR, FICM, trans temp. - **Diagnostics** — DTC read/clear (guarded) + freeze frame, joined to the Ford DTC DB (description, causes, no-start relevance). - **Logging/Playback** — record a session; scrub/replay through the graphs. - **Settings** — COM port, baud, protocol, per-PID rates, units (psi/°F vs kPa/°C), dark/night theme. - **Bottom status** — adapter, protocol, port voltage, dropped-response rate. ### Honest 6.0 data-stream limits Two commonly-wanted gauges are **not in the stock 6.0 PCM stream** from the OBD port and need aftermarket sensors: - **Engine oil PRESSURE** — only ICP (injection oil) + EOT (oil temp) exist; lube pressure is an idiot-light switch, not a PID. - **EGT** — only EBP (exhaust *back pressure*) exists; exhaust *gas temperature* is an add-on pyrometer. Plan: present what the PCM exposes; design an **aux-input** path so external sensors (e.g. a serial/analog EGT/oil-PSI module) can feed extra channels later. ## Additional features (backlog) - Session record + **playback/scrub** (highest value; foundation already in `store`). - Bi-directional tests: **KOEO/KOER self-tests, injector buzz, cylinder contribution/balance**. - **Alarms** + min/max hold per PID (EOT>230°F, ICP<500 cranking). - Timeline **annotations** ("started cranking", "stabbed throttle"). - **Computed channels** (ICP error, FICM sag). - **Multi-vehicle profiles** + per-truck DTC history. - **Export/report** (CSV/PDF, graph screenshots, one-click "share state"). - **PID discovery scan** in GUI (the brute-scan, auto-add hits). - Units toggle, night theme, big-touch cab mode. ## Roadmap - **P0 — core (this commit):** `obdcore` package + tests + this doc. *Next:* migrate `obd_reader.py` to import `obdcore` (remove the duplicated ELM/PID logic) so terminal + GUI share one source of truth. - **P1 — GUI shell:** PySide6 window, connect dialog, PID browser, one overlay plot fed by the scheduler/store. Validate against `MockLink` first. - **P2 — panels + perspectives:** split plots, gauges, Cranking + Driving views. - **P3 — diagnostics:** DTC read/clear page + Ford DTC DB (built by the cross-verified workflow, same method as the PID hunt). - **P4 — record/playback + alarms + computed channels.** - **P5 — packaging:** PyInstaller one-file `.exe` (+ CH340 driver note), optional code-signing; OBDLink/STN fast-path support. ## Dependencies - Runtime (core): `pyserial`. - GUI: `PySide6`, `pyqtgraph`, `numpy`. - Dev: `pytest`, `pyinstaller`.