Section 1 backend: VIN/Mode-09, readiness monitors, freeze-frame, trip/perf

obdcore additions (all standard SAE J1979, vehicle-agnostic, hardware-free
tested):
- obdservices.py: decode_vin (Mode 09), decode_readiness (Mode 01 PID 01 I-M
  monitors + MIL + DTC count, spark/diesel monitor sets), freeze-frame PID set.
- link.py: ElmLink.read_vehicle_info (VIN/cal/ECU), read_readiness, read_freeze_frame.
- trip.py: TripComputer (MAF-based MPG + trip totals) and PerformanceMeter
  (0-60 / 1/4-mile with launch detection).
- mock.py: speed/MAF/readiness + service stubs for GUI mock mode.
- tests/test_services.py: VIN, readiness bit decode, trip math, 0-60/quarter.

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:
2026-06-30 19:37:48 -04:00
parent 310d5a3497
commit 6c1ee0c81d
5 changed files with 341 additions and 2 deletions
+23 -2
View File
@@ -42,10 +42,18 @@ class MockLink:
return None # everything else: no response
def read_m01(self, pid, nbytes, timeout=0.6):
if pid == "0C": # RPM 0 at rest
return [0x00, 0x00]
if pid == "0C": # RPM ~750 idle
v = 750 * 4
return [(v >> 8) & 0xFF, v & 0xFF]
if pid == "05": # ECT 82C
return [122]
if pid == "0D": # speed 48 km/h
return [48]
if pid == "10": # MAF 12.0 g/s
v = 1200
return [(v >> 8) & 0xFF, v & 0xFF]
if pid == "01": # readiness: MIL off, 0 DTCs, mixed monitors
return [0x00, 0x07, 0x61, 0x20]
return None
def read_atrv(self, timeout=0.8):
@@ -58,5 +66,18 @@ class MockLink:
def clear_dtcs(self):
return True
def read_vehicle_info(self, timeout=2.0):
return {"vin": "1FMZU73E12ZA12345", "calibration": "JR3A-12A650-BCD",
"ecu_name": "ECM-EngineControl"}
def read_readiness(self, timeout=1.0):
from . import obdservices as svc
return svc.decode_readiness([0x00, 0x07, 0x61, 0x20])
def read_freeze_frame(self, timeout=0.6):
return {"dtc": "P0148",
"values": [("Engine RPM", 240, "rpm"), ("Coolant Temp", 33, "C"),
("Engine Load", 18, "%"), ("Vehicle Speed", 0, "km/h")]}
def close(self):
pass