#10 transport (obdcore/transport.py):
- TcpTransport.read raises IOError on a real socket error or peer-close instead
of swallowing it as a timeout, so a dead WiFi link surfaces (via the #8 poll
handler) as 'connection lost' rather than a frozen dashboard.
- TcpTransport.reset_input_buffer drains at most 64 chunks — never spins forever.
- BleTransport closes the client + stops the event-loop thread on connect
timeout (no leak), caps the notification buffer at 64 KiB, and close() is
robust when only partially initialised.
#11 controller (gui/controller.py, obdcore/store.py):
- connect() closes the transport and nulls the link if init()/connect() raises,
so a failed/retried connect doesn't orphan sockets/threads.
- stop_record() unhooks store.recorder BEFORE closing it, and CsvRecorder now
has a 'closed' guard so a poll-thread write racing close() is a no-op instead
of an I/O-on-closed-file crash.
Closes#10Closes#11
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yT89n4zR4qbrySoSiEyZs
Vehicle data is now DATA, not code. PIDs/scaling/DTCs/presets live in
profiles/*.json; the app loads them at runtime, so it works across vehicles
and others can contribute profiles (open source).
Core:
- obdcore/formula.py: safe AST evaluator for scaling formulas (A/B/... byte
vars, Torque/FORScan convention). Only arithmetic/bitwise + min/max/abs/
round/int/float; names/attrs/arbitrary calls rejected at load -> a community
profile CANNOT execute code.
- obdcore/profile.py: load/save/list profiles; compiles each formula into a
decode callable. registry.py now profile-backed (PidRegistry/DtcDatabase
take a Profile); hardcoded Ford table removed.
- store.py: clear()/snapshot()/export_csv() for capture management.
Profiles:
- profiles/ford-6.0-powerstroke.json (27 PIDs, verified formulas, DTCs)
- profiles/generic-obd2.json (standard SAE Mode-01 base, any vehicle)
- profiles/README.md (schema + formula language + contributing)
GUI:
- Menu bar: File (new/record/export/replay capture, quit), Profile (switch/
load/import/reload/edit-JSON/export, live profile list), View (Graph/Table
views, gauges P2, toggle PID dock, normalize, light/dark theme), Help
(about/confidence legend/profile info).
- PID browser + presets rebuild on profile switch; added Table view; raw-JSON
profile editor dialog (validates schema+formulas before saving).
Tests: profiles load+compile, formula sandbox rejects hostile input, decoders
still match real truck bytes, crank/derived/dead-PID/replay -- all pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yT89n4zR4qbrySoSiEyZs
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