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:
@@ -0,0 +1,70 @@
|
||||
"""Tests for the standard OBD services + trip/performance (no hardware)."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from obdcore import obdservices as svc
|
||||
from obdcore.trip import TripComputer, PerformanceMeter, KMH_PER_MPH
|
||||
|
||||
|
||||
def test_decode_vin():
|
||||
vin = "1FMZU73E12ZA12345"
|
||||
data = [0x49, 0x02, 0x01] + [ord(c) for c in vin]
|
||||
assert svc.decode_vin(data) == vin
|
||||
assert svc.decode_vin([0x41, 0x00]) is None # not a mode-09 response
|
||||
print(" VIN decode: OK")
|
||||
|
||||
|
||||
def test_decode_readiness():
|
||||
# A=MIL off/0 DTCs, B=3 continuous supported+ready, C=Cat/O2/O2htr supported,
|
||||
# D=O2 sensor incomplete
|
||||
r = svc.decode_readiness([0x00, 0x07, 0x61, 0x20])
|
||||
assert r["mil"] is False and r["dtc_count"] == 0 and r["ignition"] == "spark"
|
||||
by = {m["name"]: m["ready"] for m in r["monitors"]}
|
||||
assert by["Misfire"] and by["Fuel System"] and by["Components"]
|
||||
assert by["Catalyst"] is True and by["O2 Sensor"] is False
|
||||
assert r["total"] == 6 and r["ready_count"] == 5
|
||||
# diesel flag
|
||||
rc = svc.decode_readiness([0x80, 0x0F, 0x00, 0x00])
|
||||
assert rc["mil"] is True and rc["ignition"] == "compression"
|
||||
print(f" readiness decode: {r['ready_count']}/{r['total']} ready, O2 not ready: OK")
|
||||
|
||||
|
||||
def test_trip_computer():
|
||||
tc = TripComputer()
|
||||
t = 0.0
|
||||
for _ in range(360): # 6 min at 1 Hz, 96.56 km/h (60 mph), 12 g/s
|
||||
tc.update(t, 96.56, 12.0)
|
||||
t += 1.0
|
||||
s = tc.stats()
|
||||
assert 5.5 < s["distance_mi"] < 6.5, s # 60mph * 0.1h = 6 mi
|
||||
assert s["avg_mpg"] > 5 and s["avg_mpg"] < 60, s
|
||||
inst = tc.instant_mpg(96.56, 12.0)
|
||||
assert inst > 0
|
||||
print(f" trip: {s['distance_mi']}mi, {s['avg_mpg']} avg mpg, "
|
||||
f"{inst:.1f} inst: OK")
|
||||
|
||||
|
||||
def test_performance_meter():
|
||||
pm = PerformanceMeter()
|
||||
t = 0.0
|
||||
# parked
|
||||
for _ in range(3):
|
||||
pm.update(t, 0.0); t += 0.5
|
||||
# accelerate 0 -> 70 mph over 7s (mph), then cruise to cover 1/4 mile
|
||||
for i in range(1, 200):
|
||||
t = 1.5 + i * 0.1
|
||||
mph = min(70.0, (t - 1.5) * 10.0) # 10 mph/s
|
||||
pm.update(t, mph * KMH_PER_MPH)
|
||||
assert pm.best_0_60 is not None, "should have timed 0-60"
|
||||
assert 5.0 < pm.best_0_60 < 8.0, pm.best_0_60 # ~6s at 10mph/s
|
||||
assert pm.best_quarter is not None, "should have timed 1/4 mile"
|
||||
print(f" performance: 0-60 {pm.best_0_60}s, 1/4mi {pm.best_quarter}s: OK")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
for fn in [test_decode_vin, test_decode_readiness, test_trip_computer,
|
||||
test_performance_meter]:
|
||||
fn()
|
||||
print("\nALL SERVICE TESTS PASS")
|
||||
Reference in New Issue
Block a user