Files
obdash/obdcore/registry.py
T
justin f3f0bf2a77 Make app vehicle-agnostic: JSON vehicle profiles + menu bar
Vehicle data is now DATA, not code. PIDs/scaling/DTCs/presets live in
profiles/*.json; the app loads them at runtime, so it works across vehicles
and others can contribute profiles (open source).

Core:
- obdcore/formula.py: safe AST evaluator for scaling formulas (A/B/... byte
  vars, Torque/FORScan convention). Only arithmetic/bitwise + min/max/abs/
  round/int/float; names/attrs/arbitrary calls rejected at load -> a community
  profile CANNOT execute code.
- obdcore/profile.py: load/save/list profiles; compiles each formula into a
  decode callable. registry.py now profile-backed (PidRegistry/DtcDatabase
  take a Profile); hardcoded Ford table removed.
- store.py: clear()/snapshot()/export_csv() for capture management.

Profiles:
- profiles/ford-6.0-powerstroke.json (27 PIDs, verified formulas, DTCs)
- profiles/generic-obd2.json (standard SAE Mode-01 base, any vehicle)
- profiles/README.md (schema + formula language + contributing)

GUI:
- Menu bar: File (new/record/export/replay capture, quit), Profile (switch/
  load/import/reload/edit-JSON/export, live profile list), View (Graph/Table
  views, gauges P2, toggle PID dock, normalize, light/dark theme), Help
  (about/confidence legend/profile info).
- PID browser + presets rebuild on profile switch; added Table view; raw-JSON
  profile editor dialog (validates schema+formulas before saving).

Tests: profiles load+compile, formula sandbox rejects hostile input, decoders
still match real truck bytes, crank/derived/dead-PID/replay -- all pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yT89n4zR4qbrySoSiEyZs
2026-06-30 14:34:33 -04:00

73 lines
2.2 KiB
Python

"""PID + DTC data model and registry, backed by a vehicle Profile.
The actual PID numbers, scaling formulas, and DTC meanings live in JSON
vehicle profiles under profiles/ (data, not code) so the app is vehicle-
agnostic and others can contribute profiles. This module is the in-memory
model + lookups; profile.py loads/saves the JSON.
"""
from dataclasses import dataclass, field
from typing import Callable, Tuple
@dataclass
class Pid:
key: str
name: str
mode: str = "22" # "01" | "22" | "atrv" | "derived"
pid: str = "" # hex: "1446" (m22) or "0C" (m01)
nbytes: int = 2
formula: str = "" # scaling expr in A/B/... (raw) or dep keys (derived)
decode: Callable = None # built from formula by profile loader
unit: str = ""
group: str = "misc" # fuel | ficm | air | engine | driveline | power | misc
vmin: float = 0.0
vmax: float = 100.0
confidence: str = "verified" # verified | doc | tentative
round: int = None # display rounding (None=raw float, 0=int)
deps: Tuple[str, ...] = ()
notes: str = ""
@dataclass
class Dtc:
code: str
desc: str
system: str = "powertrain"
no_start: bool = False
causes: str = ""
class PidRegistry:
"""In-memory PID set + presets for the active vehicle profile."""
def __init__(self, profile):
self.profile = profile
self._by_key = {p.key: p for p in profile.pids}
self.presets = dict(profile.presets)
def get(self, key):
return self._by_key.get(key)
def all(self):
return list(self._by_key.values())
def group(self, g):
return [p for p in self._by_key.values() if p.group == g]
def preset(self, name):
return [self._by_key[k] for k in self.presets.get(name, []) if k in self._by_key]
def preset_names(self):
return list(self.presets.keys())
class DtcDatabase:
def __init__(self, profile):
self._db = {d.code: d for d in profile.dtcs}
def get(self, code):
return self._db.get(code) or Dtc(code, "(unknown - look up this code)")
def all(self):
return list(self._db.values())