5 Commits

Author SHA1 Message Date
justin fa7225d6dc Fix #9: DTC/freeze-frame parsing (phantom codes, Mode 02, hex frame index)
- parse_dtcs CAN branch is now message-aware: each ECU reply '<svc> <count>
  <pairs>' has its header stripped per-message, instead of flattening all lines
  and stripping svc+count once. With multiple ECUs the old code ate the second
  header as a DTC pair -> phantom codes. Critically, it does NOT blind-scan for
  svc (0x43 is a legal DTC first byte: C03xx) — a numbered ISO-TP continuation
  is distinguished by its 'N:' frame-index prefix, not by value.
- _line_bytes strips hex frame indices A:-F: (ISO-TP index cycles 0-F), not just
  0-9, so consecutive frames past the 10th aren't dropped.
- read_freeze_frame sends the correct '020200' (svc 02, PID 02, frame 00) and
  skips SID+PID+frame (+3), fixing the off-by-one that mis-read the freeze DTC.
- tests/test_dtc_parse.py: single-frame, multi-ECU (no phantom), numbered
  multiframe with a real C03xx continuation, hex index, non-CAN legacy.

Closes #9

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yT89n4zR4qbrySoSiEyZs
2026-07-01 19:36:35 -04:00
justin d435384b58 #2 (framework): bi-directional / service-function engine
Profile-defined UDS action sequences, run safely -- the framework for #2 (real
per-vehicle actuator tests/resets are follow-on, added as verified profile data).

- obdcore/actions.py: Action model + run_action() executing session (Mode 10) ->
  security (Mode 27 seed->key) -> command steps (2F/31/11/3E/... any hex) with
  positive/negative response checks. Security KEY algorithms are per-vehicle
  secrets and NOT bundled -- only trivial transforms (xor-ff/invert/add-ff)
  known; an action naming an unknown algorithm is BLOCKED (fails safe). Never
  synthesizes bytes -- runs only what the profile defines. validate_action()
  rejects malformed hex at load.
- profile.py: load/save an actions[] block; ElmLink/MockLink read_raw(hex).
- GUI: Diagnostics -> Service & Bi-directional dialog -- lists the profile's
  actions with risk badges; caution/danger gated behind a warning confirmation.
- generic-obd2: two safe STANDARD actions (Tester-Present ping; ECU-Reset,
  caution + engine-off warning). PROFILE_SPEC.md documents the actions schema
  + safety rules.
- tests/test_actions.py: runner, session+reset, security handshake, unknown-algo
  block, hex validation, profile load. All 5 suites pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yT89n4zR4qbrySoSiEyZs
2026-07-01 16:33:51 -04:00
justin 7bda758f88 Tier 2: WiFi + Bluetooth ELM327 transports
- obdcore/transport.py: pluggable byte transports -- SerialTransport,
  TcpTransport (WiFi ELM327, stdlib socket), BleTransport (experimental, via
  optional 'bleak'; background asyncio loop buffering notifications). ble_scan().
- ElmLink refactored onto a transport with .serial()/.tcp()/.ble() factories
  (close/cmd now go through self.io); no behavior change for serial.
- Controller.connect(conn={kind:serial|wifi|ble,...}); GUI connection bar gains
  a transport selector (Serial/USB/BT-SPP | WiFi host:port | Bluetooth LE + Scan).
- Classic-Bluetooth needs no new code (pairs as a serial port); WiFi needs no
  extra deps; BLE is opt-in (bleak not bundled, so CI binaries keep building).
- tests/test_transport.py: drives ElmLink over a fake ELM TCP server end-to-end
  (connect, RPM, readiness, VIN). All suites pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yT89n4zR4qbrySoSiEyZs
2026-07-01 08:24:51 -04:00
justin 6c1ee0c81d Section 1 backend: VIN/Mode-09, readiness monitors, freeze-frame, trip/perf
obdcore additions (all standard SAE J1979, vehicle-agnostic, hardware-free
tested):
- obdservices.py: decode_vin (Mode 09), decode_readiness (Mode 01 PID 01 I-M
  monitors + MIL + DTC count, spark/diesel monitor sets), freeze-frame PID set.
- link.py: ElmLink.read_vehicle_info (VIN/cal/ECU), read_readiness, read_freeze_frame.
- trip.py: TripComputer (MAF-based MPG + trip totals) and PerformanceMeter
  (0-60 / 1/4-mile with launch detection).
- mock.py: speed/MAF/readiness + service stubs for GUI mock mode.
- tests/test_services.py: VIN, readiness bit decode, trip math, 0-60/quarter.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yT89n4zR4qbrySoSiEyZs
2026-06-30 19:37:48 -04:00
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