6c1ee0c81d
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
84 lines
2.9 KiB
Python
84 lines
2.9 KiB
Python
"""MockLink -- a synthetic ElmLink for tests and GUI development without a
|
|
truck. Simulates a cranking 6.0: ICP ramps toward ~540 psi, FICM holds ~48V,
|
|
battery sags, MAP/BARO sit at atmospheric. Same read interface as ElmLink.
|
|
"""
|
|
|
|
|
|
class MockLink:
|
|
def __init__(self, clock):
|
|
self.clock = clock # callable -> float seconds
|
|
self.t0 = clock()
|
|
self.protocol = "A6"
|
|
|
|
def init(self):
|
|
pass
|
|
|
|
def fast_timing(self, on=True):
|
|
pass
|
|
|
|
def connect(self):
|
|
return True
|
|
|
|
def is_can(self):
|
|
return True
|
|
|
|
def _u16le(self, raw16):
|
|
return [(raw16 >> 8) & 0xFF, raw16 & 0xFF]
|
|
|
|
def read_m22(self, pid, timeout=0.5):
|
|
el = self.clock() - self.t0
|
|
if pid == "1446": # ICP: ramps 0 -> 540 over ~2.7s
|
|
return self._u16le(int(min(540, el * 200) / 0.57))
|
|
if pid == "09D0": # FICM main ~48V (0x3000)
|
|
return self._u16le(0x3000)
|
|
if pid == "1440": # MAP atmospheric
|
|
return [0x01, 0x89]
|
|
if pid == "1442": # BARO atmospheric
|
|
return [0x01, 0x88]
|
|
if pid == "1445": # EBP atmospheric
|
|
return [0x01, 0x8F]
|
|
if pid == "1310": # EOT ~33C
|
|
return [0x1C, 0x92]
|
|
return None # everything else: no response
|
|
|
|
def read_m01(self, pid, nbytes, timeout=0.6):
|
|
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):
|
|
el = self.clock() - self.t0
|
|
return 10.6 if el < 2.5 else 12.5 # crank sag then recover
|
|
|
|
def read_dtcs(self, mode, svc, timeout=5.0):
|
|
return ["P0148"] if mode == "03" else []
|
|
|
|
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
|