#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
This commit is contained in:
+27
-1
@@ -24,6 +24,7 @@ from dataclasses import dataclass, field
|
||||
|
||||
from .formula import compile_formula
|
||||
from .registry import Pid, Dtc
|
||||
from .actions import Action, ActionStep, validate_action
|
||||
|
||||
SCHEMA = 1
|
||||
BYTE_VARS = [chr(65 + i) for i in range(8)] # A..H
|
||||
@@ -36,6 +37,7 @@ class Profile:
|
||||
dtcs: list
|
||||
presets: dict
|
||||
path: str = None
|
||||
actions: list = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -100,8 +102,20 @@ def load_profile(path):
|
||||
dtcs = [Dtc(code=x["code"], desc=x.get("desc", ""), system=x.get("system", "powertrain"),
|
||||
no_start=x.get("no_start", False), causes=x.get("causes", ""))
|
||||
for x in raw.get("dtcs", [])]
|
||||
actions = []
|
||||
for a in raw.get("actions", []):
|
||||
act = Action(
|
||||
key=a["key"], name=a.get("name", a["key"]), kind=a.get("kind", "test"),
|
||||
risk=a.get("risk", "safe"), description=a.get("description", ""),
|
||||
warning=a.get("warning", ""), session=a.get("session"),
|
||||
security=a.get("security"),
|
||||
steps=[ActionStep(send=s["send"], expect=s.get("expect", ""))
|
||||
for s in a.get("steps", [])],
|
||||
success_msg=a.get("success_msg", "Done."))
|
||||
validate_action(act) # rejects malformed hex
|
||||
actions.append(act)
|
||||
return Profile(meta=raw.get("meta", {}), pids=pids, dtcs=dtcs,
|
||||
presets=raw.get("presets", {}), path=path)
|
||||
presets=raw.get("presets", {}), path=path, actions=actions)
|
||||
|
||||
|
||||
def _pid_to_dict(p):
|
||||
@@ -136,6 +150,18 @@ def save_profile(profile, path=None):
|
||||
"dtcs": [{"code": d.code, "desc": d.desc, "system": d.system,
|
||||
"no_start": d.no_start, "causes": d.causes} for d in profile.dtcs],
|
||||
}
|
||||
if profile.actions:
|
||||
out["actions"] = []
|
||||
for a in profile.actions:
|
||||
ad = {"key": a.key, "name": a.name, "kind": a.kind, "risk": a.risk}
|
||||
if a.description: ad["description"] = a.description
|
||||
if a.warning: ad["warning"] = a.warning
|
||||
if a.session: ad["session"] = a.session
|
||||
if a.security: ad["security"] = a.security
|
||||
ad["steps"] = [{"send": s.send, **({"expect": s.expect} if s.expect else {})}
|
||||
for s in a.steps]
|
||||
if a.success_msg != "Done.": ad["success_msg"] = a.success_msg
|
||||
out["actions"].append(ad)
|
||||
with open(path, "w") as f:
|
||||
json.dump(out, f, indent=2)
|
||||
return path
|
||||
|
||||
Reference in New Issue
Block a user