Gauge redline zones + C/F units toggle + cleaner dials
Gauges: - Optional per-metric warning zones (warn_hi/redline_hi/warn_lo/redline_lo) in the profile schema; gauges draw colored redline/warn bands and color the needle + readout by zone. Default neutral when unset (no false redline). - Removed the value progress-arc fill (it dominated the dial / looked wrong) -> clean tach face: bezel, ticks, numeric scale, needle, redline band, readout. - Auto-derivation rejected: bad direction/threshold vary per metric, so zones are config (with a sensible neutral default). Units: - New Units menu: Temperature C / F. Converts gauges, graph, table, and PID browser (values, scale, zones, unit labels) at display time; data stays C. Ford 6.0 profile: zones for ICP (red<500), FICM (red<40/amber40-48/green48+), ECT/EOT (high redline), RPM (redline 3800), boost, battery; tightened FICM (38-52) and battery (9-15) ranges so redline bands land sensibly. Docs: profiles/PROFILE_SPEC.md -- canonical, AI-agent-ready profile spec (schema, formula language, zones, confidence, rules); README points to it. Validated headless: zones parse/classify, F conversion (112C->233.6F, zones converted), gauges render; obdcore + diagnostics tests 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:
@@ -0,0 +1,179 @@
|
||||
# OBDash Vehicle Profile Specification (v1)
|
||||
|
||||
This is the **canonical, self-contained spec** for an OBDash vehicle profile. A
|
||||
profile is a single JSON file that teaches OBDash how to read one vehicle: its
|
||||
PIDs (with scaling), trouble-code meanings, dashboards (presets), and gauge
|
||||
warning zones. Profiles are **pure data** — they cannot run code.
|
||||
|
||||
> **Using an AI agent to build a profile?** Paste this whole file into your
|
||||
> agent and say: *"Research <year make model engine> and produce an OBDash
|
||||
> vehicle profile JSON that conforms exactly to this spec."* Then drop the
|
||||
> result in `profiles/` and open a PR. See "Rules for authors / agents" below.
|
||||
|
||||
Spec version: **1** (matches the top-level `"schema": 1`). This document is kept
|
||||
in sync with the loader (`obdcore/profile.py`) and the PID model
|
||||
(`obdcore/registry.py`) — if they disagree, the loader wins; file an issue.
|
||||
|
||||
---
|
||||
|
||||
## 1. Top-level shape
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"schema": 1, // required, must be 1
|
||||
"meta": { ... }, // vehicle identity (object)
|
||||
"presets": { "name": ["KEY", ...], ... }, // named dashboards (object)
|
||||
"pids": [ { ...pid... }, ... ], // signals (array)
|
||||
"dtcs": [ { ...dtc... }, ... ] // trouble-code dictionary (array)
|
||||
}
|
||||
```
|
||||
|
||||
## 2. `meta`
|
||||
|
||||
| Field | Req | Meaning |
|
||||
|---|---|---|
|
||||
| `name` | ✓ | Display name, shown in the Profile menu (e.g. `"Ford 6.0L Power Stroke"`) |
|
||||
| `make`, `model`, `years`, `engine` | ✓ | Vehicle identity strings |
|
||||
| `protocol` | ✓ | One EXACT value (see below), or `"auto"` |
|
||||
| `author` | — | Your name / handle |
|
||||
| `version` | — | Profile semver, e.g. `"1.0.0"` |
|
||||
| `notes` | — | Provenance, caveats, confidence policy |
|
||||
|
||||
`protocol` must be one of:
|
||||
`"SAE J1850 PWM"`, `"SAE J1850 VPW"`, `"ISO 9141-2"`, `"ISO 14230 KWP2000"`,
|
||||
`"ISO 15765 CAN"`, or `"auto"`. The ELM327 auto-negotiates regardless, so this
|
||||
is a hint/record — but get it right when you can.
|
||||
|
||||
## 3. `pids` — the signal definitions
|
||||
|
||||
Each PID object:
|
||||
|
||||
| Field | Req | Type | Meaning |
|
||||
|---|---|---|---|
|
||||
| `key` | ✓ | string | Unique id, `UPPER_SNAKE` (e.g. `ICP`, `STFT1`). Used in presets + derived `deps`. |
|
||||
| `name` | ✓ | string | Display name |
|
||||
| `mode` | ✓ | string | `"01"` generic SAE · `"22"` manufacturer-enhanced · `"atrv"` adapter pin voltage · `"derived"` computed from other PIDs |
|
||||
| `pid` | mode 01/22 | string | Request id hex — `"0C"` (mode 01) or `"1446"` (mode 22). Omit for `atrv`/`derived`. |
|
||||
| `nbytes` | mode 01/22 | int | Number of data bytes in the response the formula uses |
|
||||
| `formula` | mode 01/22/derived | string | Scaling expression (see §4). Omit for `atrv`. |
|
||||
| `unit` | ✓ | string | `"rpm"`, `"C"`, `"kPa"`, `"psi"`, `"%"`, `"V"`, `"km/h"`, … |
|
||||
| `group` | ✓ | string | One of: `fuel` `air` `engine` `driveline` `power` `ficm` `misc` |
|
||||
| `vmin`, `vmax` | ✓ | number | Display range (used by gauges + the Normalize overlay) |
|
||||
| `confidence` | ✓ | string | `verified` · `doc` · `tentative` (see §6) |
|
||||
| `round` | — | int | Display rounding: omit = raw float, `0` = integer, `2` = 2 dp |
|
||||
| `deps` | derived | string[] | PID keys the `derived` formula references |
|
||||
| `notes` | — | string | Gotchas / provenance; shown as a tooltip |
|
||||
| `warn_hi` `redline_hi` `warn_lo` `redline_lo` | — | number | Gauge warning zones (see §5) |
|
||||
|
||||
Always include this adapter-voltage pseudo-PID:
|
||||
```json
|
||||
{"key":"BATT","name":"Battery (OBD port)","mode":"atrv","unit":"V","group":"power","vmin":0,"vmax":16,"confidence":"verified"}
|
||||
```
|
||||
|
||||
## 4. Formula language
|
||||
|
||||
Arithmetic over **data-byte variables** `A, B, C, …` (= response byte 0, 1, 2 …) —
|
||||
the same convention as Torque / FORScan / ScanGauge. For `derived` PIDs the
|
||||
variables are **other PID keys** instead.
|
||||
|
||||
A safe AST evaluator (`obdcore/formula.py`) runs formulas. **Allowed:**
|
||||
- Numbers and the declared variables (`A`/`B`/… or dep keys)
|
||||
- Operators: `+ - * / // % **` and bitwise `& | ^ << >>` and unary `- ~`, parentheses
|
||||
- Functions: `min`, `max`, `abs`, `round`, `int`, `float`
|
||||
|
||||
**Rejected at load** (so a hostile profile can't run code): any other name,
|
||||
attribute access (`x.y`), subscripts (`x[0]`), or any other function call.
|
||||
|
||||
Canonical standard SAE J1979 Mode-01 formulas:
|
||||
```
|
||||
RPM 010C: (A*256+B)/4 Speed 010D: A ECT 0105: A-40
|
||||
IAT 010F: A-40 MAP 010B: A (kPa) MAF 0110: (A*256+B)/100 (g/s)
|
||||
TPS 0111: A*100/255 Load 0104: A*100/255 Timing 010E: A/2-64
|
||||
STFT/LTFT 0106-0109: A*100/128-100 Fuel pressure 010A: A*3
|
||||
O2 voltage 0114-011B: A/200 Runtime 011F: A*256+B Module V 0142: (A*256+B)/1000
|
||||
Ambient 0146: A-40 Fuel level 012F: A*100/255 Baro 0133: A
|
||||
```
|
||||
Examples (enhanced): `(A*256+B)*0.57` (ICP psi), `(A>>1)&1` (a status bit),
|
||||
`A//2` (gear), `"MAP-BARO"` with `"deps":["MAP","BARO"]` (boost).
|
||||
|
||||
## 5. Gauge warning zones (optional)
|
||||
|
||||
Make a gauge color-code like a real tach. All optional; omit for a neutral gauge.
|
||||
|
||||
| Field | Meaning |
|
||||
|---|---|
|
||||
| `redline_hi` | value `>=` this → RED (high redline) |
|
||||
| `warn_hi` | value `>=` this → AMBER |
|
||||
| `redline_lo` | value `<=` this → RED (low redline) |
|
||||
| `warn_lo` | value `<=` this → AMBER |
|
||||
|
||||
Use **high** zones where *high is bad* (ECT/EOT/RPM/boost) and **low** zones
|
||||
where *low is bad* (ICP/FICM/oil pressure; both for battery). The gauge draws a
|
||||
colored band on the dial and turns the needle + readout amber/red in-zone.
|
||||
|
||||
Examples (Ford 6.0): `"ICP": redline_lo 500, warn_lo 600` (must make ~500 psi to
|
||||
fire); `"ECT": warn_hi 105, redline_hi 110`; `"RPM": warn_hi 3500, redline_hi 3800`.
|
||||
|
||||
## 6. `confidence` tiers
|
||||
|
||||
| Value | Meaning |
|
||||
|---|---|
|
||||
| `verified` | SAE-standard PID, OR multi-source AND confirmed on a real vehicle |
|
||||
| `doc` | Documented in sources, **not** yet read on this vehicle |
|
||||
| `tentative` | Single-source, or disputed scaling — sanity-check before trusting |
|
||||
|
||||
Standard Mode-01 PIDs are `verified` (they're SAE-mandated). Manufacturer-enhanced
|
||||
PIDs you found in one community list are `doc` or `tentative`.
|
||||
|
||||
## 7. `presets` and `dtcs`
|
||||
|
||||
`presets`: named dashboards → a list of PID keys, e.g.
|
||||
`"basic": ["RPM","SPEED","ECT","MAP","TPS","BATT"]`, `"fuel": ["STFT1","LTFT1","O2B1S1"]`.
|
||||
Reference only keys you define. Provide at least `basic` (and `fuel` if the
|
||||
vehicle reports trims/O2).
|
||||
|
||||
`dtcs`: array of `{"code","desc","system","no_start","causes"}`. `code` like
|
||||
`"P0301"`; `system` is freeform (`engine`/`fuel`/`emissions`/…); `no_start: true`
|
||||
flags drive-disabling faults (shown bold red). Include generic `P0xxx` plus
|
||||
manufacturer-specific `P1xxx` you can source.
|
||||
|
||||
## 8. Rules for authors / agents
|
||||
|
||||
- **Standard Mode-01 PIDs are the reliable backbone** — include the ones this
|
||||
engine actually supports (MAF *vs* MAP by induction type; the O2/trim banks
|
||||
it really has). Mark them `verified`.
|
||||
- **Never invent a PID number or formula.** Enhanced PIDs need a documented id
|
||||
AND scaling; mark `doc`/`tentative` and cite in `notes`. If you can't verify,
|
||||
leave it out.
|
||||
- **Don't fabricate signals the stock stream lacks.** Many vehicles have no EGT
|
||||
or engine-oil-*pressure* PID (e.g. the 6.0 reports ICP + EOT only). Don't add them.
|
||||
- **Every formula must obey §4** (only `A`/`B`/… or dep keys + allowed ops/funcs).
|
||||
- **Validate before PR:** the loader compiles every formula and rejects bad ones.
|
||||
Quick check: `python -c "from obdcore import load_profile; load_profile('profiles/<file>.json')"`
|
||||
(no exception = it's valid). The app's **Profile → Edit JSON** dialog also
|
||||
validates on save.
|
||||
|
||||
## 9. Minimal valid example
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": 1,
|
||||
"meta": {"name":"Example 2.0L","make":"Example","model":"Demo","years":"1999",
|
||||
"engine":"2.0L I4","protocol":"auto","author":"you","version":"0.1.0"},
|
||||
"presets": {"basic": ["RPM","ECT","BATT"]},
|
||||
"pids": [
|
||||
{"key":"RPM","name":"Engine RPM","mode":"01","pid":"0C","nbytes":2,
|
||||
"formula":"(A*256+B)/4","round":0,"unit":"rpm","group":"engine",
|
||||
"vmin":0,"vmax":8000,"confidence":"verified","warn_hi":6000,"redline_hi":6800},
|
||||
{"key":"ECT","name":"Engine Coolant Temp","mode":"01","pid":"05","nbytes":1,
|
||||
"formula":"A-40","round":0,"unit":"C","group":"engine","vmin":-40,"vmax":215,
|
||||
"confidence":"verified","warn_hi":110,"redline_hi":118},
|
||||
{"key":"BATT","name":"Battery (OBD port)","mode":"atrv","unit":"V",
|
||||
"group":"power","vmin":0,"vmax":16,"confidence":"verified",
|
||||
"warn_lo":12.0,"redline_lo":11.0}
|
||||
],
|
||||
"dtcs": [
|
||||
{"code":"P0301","desc":"Cylinder 1 misfire","system":"engine","no_start":false}
|
||||
]
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user