Files
obdash/ARCHITECTURE.md
T
justin 6bee9c0d7f Scaffold obdcore (headless acquisition core) + ARCHITECTURE.md
Foundation for the PySide6 + pyqtgraph Windows GUI, shared with the terminal
tool. Pure data/IO -- no Qt, no curses.

obdcore/
  link.py      ElmLink   -- ELM327 serial (Mode-01/22, ATRV, DTC read/clear)
  mock.py      MockLink  -- synthetic crank for tests + GUI dev (no truck)
  registry.py  PidRegistry (verified Ford 6.0 PIDs + confidence) + DtcDatabase
  scheduler.py PollScheduler -- prioritized round-robin polling, dead-PID park,
               derived channels; tick() is fake-clock test-drivable
  store.py     TimeSeriesStore (ring buffers + min/max) + CsvRecorder/replay

Design centers on the ELM327 bandwidth limit (~7-15 reads/sec): the active
view subscribes PIDs at chosen rates; acquisition runs off the UI thread;
the GUI only reads the store. FICM_M (09D0) promoted to verified after the
2026-06-30 on-truck crank read (48.0V, intermittent).

tests/test_obdcore.py: decoders vs real truck bytes, crank ramp + peak,
derived BOOST, dead-PID park/revive, record/replay roundtrip -- all pass.

ARCHITECTURE.md: layers, data model, GUI plan, 6.0 stock-PID limits
(no EGT/oil-PSI), feature backlog, P0-P5 roadmap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yT89n4zR4qbrySoSiEyZs
2026-06-30 13:41:24 -04:00

6.8 KiB
Raw Blame History

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 ~40150 ms over the CH340, so total throughput is ~715 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.