Make app vehicle-agnostic: JSON vehicle profiles + menu bar

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
This commit is contained in:
2026-06-30 14:34:33 -04:00
parent 45691334e1
commit f3f0bf2a77
12 changed files with 966 additions and 295 deletions
+79
View File
@@ -0,0 +1,79 @@
# Vehicle Profiles
Each `*.json` file here is a **vehicle profile** — pure data that makes the
ford-obd app vehicle-agnostic. A profile defines a vehicle's PIDs (with safe
scaling formulas), DTC meanings, and named presets. Load one in the app via
**Profile → Load**, or drop a new file in this folder and it appears in the list.
**Contributions welcome** — add a profile for your vehicle and open a PR.
## Current profiles
| File | Vehicle | Notes |
|---|---|---|
| `ford-6.0-powerstroke.json` | Ford 6.0L Power Stroke (20032007) | Verified Mode-22 PIDs (ICP, FICM, EBP, MAP/BARO, EOT, …) + DTCs |
| `generic-obd2.json` | Any OBD-II vehicle (1996+) | Standard SAE Mode-01 PIDs only — a base to fork from |
## Schema (`schema: 1`)
```jsonc
{
"schema": 1,
"meta": {
"name": "Ford 6.0L Power Stroke", // shown in the Profile menu
"make": "Ford", "model": "...", "years": "2003-2007",
"engine": "6.0L Power Stroke diesel",
"author": "you", "version": "1.0.0",
"protocol": "auto", // ELM ATSP target, or "auto"
"notes": "provenance / confidence policy / caveats"
},
"presets": { "crank": ["ICP","FICM_M","BATT","RPM"], "...": [] },
"pids": [ /* see below */ ],
"dtcs": [ {"code":"P0087","desc":"...","system":"fuel","no_start":true,"causes":""} ]
}
```
### PID fields
| Field | Meaning |
|---|---|
| `key` | short unique id used in presets/derived (e.g. `ICP`) |
| `name` | display name |
| `mode` | `01` (generic SAE), `22` (manufacturer-enhanced), `atrv` (adapter pin voltage), `derived` (computed from other PIDs) |
| `pid` | request id hex — `0C` (mode 01) or `1446` (mode 22) |
| `nbytes` | expected data bytes in the response |
| `formula` | scaling expression (see below) |
| `round` | display rounding: omit = raw, `0` = integer, `2` = 2 dp |
| `unit`, `group` | display unit; group = `fuel\|ficm\|air\|engine\|driveline\|power\|misc` |
| `vmin`,`vmax` | range (used for gauges + the Normalize overlay) |
| `confidence` | `verified` (multi-source / read on a real vehicle), `doc` (sourced, unconfirmed), `tentative` (single-source / disputed) |
| `deps` | for `derived`: the PID keys the formula references |
| `notes` | freeform; surfaced as a tooltip |
### Formula language
Arithmetic over **data-byte variables** `A, B, C, …` (byte 0, 1, 2, …) — the
same convention as Torque/FORScan/ScanGauge:
```
(A*256+B)*0.57 # 16-bit * scale (ICP psi)
A-40 # 8-bit temp
(A>>1)&1 # a status bit
A//2 # integer divide (gear)
```
For `derived` PIDs the variables are **other PID keys**: `"MAP - BARO"` with
`"deps": ["MAP","BARO"]`.
Formulas are evaluated by a **safe AST evaluator** (`obdcore/formula.py`):
only numbers, the declared variables, arithmetic/bitwise operators, and
`min/max/abs/round/int/float` are allowed. Anything else (names, attribute
access, arbitrary calls) is rejected at load — so a community profile **cannot
execute code**.
## Caveats worth recording in `notes`
- Manufacturer-enhanced (`22`) PIDs vary by model year and PCM strategy.
- Some signals aren't on the OBD stream at all (e.g. the 6.0 has no EGT or
lube-oil-pressure PID — only ICP and EOT). Don't invent them.
- Mark single-source numbers `tentative` and say so in `notes`.