# 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 ```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. ## 7b. `actions` — bi-directional / service functions (optional) Manufacturer service functions (actuator tests, service resets, module writes) are UDS (ISO 14229) sequences, so they live in the profile as **data**. OBDash runs ONLY the hex bytes you define — it never synthesizes commands. ```jsonc "actions": [ { "key": "ECU_RESET", "name": "Reset ECU (soft reboot)", "kind": "reset", // test | actuator | reset | write "risk": "caution", // safe | caution | danger (caution/danger prompt to confirm) "description": "shown in the list", "warning": "shown in the confirmation for caution/danger actions", "session": "03", // OPTIONAL Mode 10 subfunction hex (enter extended session) "security": {"level":"01","algorithm":"xor-ff"}, // OPTIONAL seed->key unlock "steps": [ {"send":"1101", "expect":"51"} ], // send hex; expect = hex the reply must contain "success_msg": "ECU reset acknowledged." } ] ``` Execution order: `session` (Mode 10) → `security` (Mode 27 seed→key) → each `step` in order. A step succeeds if the reply contains `expect`, or (when `expect` is omitted) the UDS positive-response byte (`send` SID + 0x40). Any negative response (`7F …`) aborts. **Security access:** real per-vehicle seed→key algorithms are proprietary and are **not** bundled. Only trivial/standard transforms are known (`xor-ff`, `invert`, `add-ff`); an action naming any other `algorithm` is **blocked** (fails safe) — don't put a real secret algorithm name and expect it to work. Most simple functions need no security block. **Safety rules for authors:** - Only include commands with **verified** bytes (service manual / bench-confirmed). A wrong `2F`/`31`/`2E` command can mis-actuate or misconfigure a module. - Mark anything that writes/actuates `caution` or `danger` and write a clear `warning` (e.g. "engine off", "wheels chocked"). - `kind:"write"` (module config / As-Built) is the highest-risk — reserve `danger`. ## 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/.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} ] } ```