Files
obdash/profiles/PROFILE_SPEC.md
T
justin d435384b58 #2 (framework): bi-directional / service-function engine
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
2026-07-01 16:33:51 -04:00

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/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/<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}
  ]
}