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
8.3 KiB
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 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
{
"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:
{"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/tentativeand cite innotes. 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
{
"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}
]
}