From f8f9335ba388261204be0a760199d4880758fd0f Mon Sep 17 00:00:00 2001 From: "IQ.Lvbs CI [bot]" Date: Mon, 31 Aug 2026 22:30:24 -0500 Subject: [PATCH] IQ.Pilot Release Commit @ 83fbdd3 --- iqpilot/selfdrive/car/card.py | 23 +++++++---- .../tests/test_learned_factor_persistence.py | 39 ++++++++++++++++++- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/iqpilot/selfdrive/car/card.py b/iqpilot/selfdrive/car/card.py index 4e6e84a24..69c083b44 100755 --- a/iqpilot/selfdrive/car/card.py +++ b/iqpilot/selfdrive/car/card.py @@ -270,10 +270,14 @@ class Car: return tuple(attr for attr in ("gasfactor", "windfactor") if hasattr(self.CI.CC, attr)) def _stored_learned_factors(self) -> dict: - try: - stored = json.loads(self.params.get("IQLongLearnedFactors") or b"{}") - except ValueError: - stored = {} + # JSON-typed params come back already parsed from params_pyx; only the pure-python fallback + # returns raw bytes + stored = self.params.get("IQLongLearnedFactors") + if isinstance(stored, bytes | str): + try: + stored = json.loads(stored) + except ValueError: + stored = None return stored if isinstance(stored, dict) else {} def _seed_learned_factors(self): @@ -292,14 +296,19 @@ class Car: return stored = self._stored_learned_factors() stored[str(self.CP.carFingerprint)] = {attr: float(getattr(self.CI.CC, attr)) for attr in attrs} - self.params.put_nonblocking("IQLongLearnedFactors", json.dumps(stored)) + # JSON-typed params take the dict itself; params_pyx serializes and rejects pre-dumped strings + self.params.put_nonblocking("IQLongLearnedFactors", stored) def state_publish(self, CS: car.CarState, CS_IQ: custom.IQCarState, RD: structs.RadarDataT | None): """carState and carParams publish loop""" - # persist live-learned longitudinal factors so they survive across drives + # persist live-learned longitudinal factors so they survive across drives; card authors the + # car's CAN stream, so a persistence failure must never take it down mid-drive if self.sm.frame > 0 and self.sm.frame % int(60. / DT_CTRL) == 0: - self._save_learned_factors() + try: + self._save_learned_factors() + except Exception: + cloudlog.exception("failed to persist learned longitudinal factors") # carParams - logged every 50 seconds (> 1 per segment) if self.sm.frame % int(50. / DT_CTRL) == 0: diff --git a/iqpilot/selfdrive/car/tests/test_learned_factor_persistence.py b/iqpilot/selfdrive/car/tests/test_learned_factor_persistence.py index 603f31f8a..ed20244f3 100644 --- a/iqpilot/selfdrive/car/tests/test_learned_factor_persistence.py +++ b/iqpilot/selfdrive/car/tests/test_learned_factor_persistence.py @@ -55,7 +55,7 @@ class TestLearnedFactorPersistence: other.CI.CC.gasfactor = 0.5 other._save_learned_factors() - stored = json.loads(other.params.values["IQLongLearnedFactors"]) + stored = other.params.values["IQLongLearnedFactors"] assert stored["HONDA_CRV_6G"]["gasfactor"] == 2.0 assert stored["HONDA_CIVIC_BOSCH"]["gasfactor"] == 0.5 @@ -80,3 +80,40 @@ class TestLearnedFactorPersistence: car._seed_learned_factors() car._save_learned_factors() assert "IQLongLearnedFactors" not in car.params.values + + +class TestLearnedFactorRealParams: + """Round-trips through the actual params store so the pyx JSON type marshaling is exercised: + JSON params take dicts on put and come back parsed on get.""" + + def make_real_car(self, tmp_path, controller): + from iqpilot.common.params import Params + car = object.__new__(Car) + car.params = Params(str(tmp_path)) + car.CI = SimpleNamespace(CC=controller) + car.CP = SimpleNamespace(carFingerprint="HONDA_CIVIC_2022") + return car + + def test_save_then_seed_through_real_params(self, tmp_path): + import time + car = self.make_real_car(tmp_path, HondaLikeController()) + car.CI.CC.gasfactor = 1.31 + car.CI.CC.windfactor = 0.88 + car._save_learned_factors() + time.sleep(0.3) + + fresh = self.make_real_car(tmp_path, HondaLikeController()) + fresh._seed_learned_factors() + assert fresh.CI.CC.gasfactor == 1.31 + assert fresh.CI.CC.windfactor == 0.88 + + def test_repeated_saves_through_real_params(self, tmp_path): + import time + car = self.make_real_car(tmp_path, HondaLikeController()) + for value in (1.1, 1.2, 1.3): + car.CI.CC.gasfactor = value + car._save_learned_factors() + time.sleep(0.3) + fresh = self.make_real_car(tmp_path, HondaLikeController()) + fresh._seed_learned_factors() + assert fresh.CI.CC.gasfactor == 1.3