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
10 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.
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.
"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/2Ecommand can mis-actuate or misconfigure a module. - Mark anything that writes/actuates
cautionordangerand write a clearwarning(e.g. "engine off", "wheels chocked"). kind:"write"(module config / As-Built) is the highest-risk — reservedanger.
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}
]
}