From 760c19d3f91f79e404f36b020c5df027a5291e48 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 24 Aug 2026 23:31:52 -0400 Subject: [PATCH 1/3] ui/models: handle missing files during cache size calculation (#1958) --- .../selfdrive/ui/sunnypilot/layouts/settings/models.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpilot/selfdrive/ui/sunnypilot/layouts/settings/models.py b/openpilot/selfdrive/ui/sunnypilot/layouts/settings/models.py index becdceaaf0..e4a6bea6e0 100644 --- a/openpilot/selfdrive/ui/sunnypilot/layouts/settings/models.py +++ b/openpilot/selfdrive/ui/sunnypilot/layouts/settings/models.py @@ -115,8 +115,12 @@ class ModelsLayout(Widget): def calculate_cache_size(): cache_size = 0.0 if os.path.exists(CUSTOM_MODEL_PATH): - cache_size = sum(os.path.getsize(os.path.join(CUSTOM_MODEL_PATH, file)) for file in os.listdir(CUSTOM_MODEL_PATH)) / (1024**2) - return cache_size + for file in os.listdir(CUSTOM_MODEL_PATH): + try: + cache_size += os.path.getsize(os.path.join(CUSTOM_MODEL_PATH, file)) + except OSError: + continue + return cache_size / (1024**2) def _clear_cache(self): def _callback(response): From cefe5737b9b201af26eafc0c4d426e7e15ecdd4c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 25 Aug 2026 00:41:31 -0400 Subject: [PATCH 2/3] models: fix current model not updating on chestnut status (#1959) * models: preserve user model selection across reboots and power cycles * no * again * idk * over --- openpilot/sunnypilot/models/helpers.py | 24 ++++++------------------ openpilot/sunnypilot/models/manager.py | 7 ++++++- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/openpilot/sunnypilot/models/helpers.py b/openpilot/sunnypilot/models/helpers.py index d0fb2e37ec..d97d655d5f 100644 --- a/openpilot/sunnypilot/models/helpers.py +++ b/openpilot/sunnypilot/models/helpers.py @@ -23,7 +23,6 @@ REQUIRED_JSON_VERSION = 18 CUSTOM_MODEL_PATH = Paths.model_root() METADATA_PATH = Path(__file__).parent / '../models/supercombo_metadata.pkl' ModelManager = custom.ModelManagerSP -_LAST_VALIDATED_RAW = None def _compute_hash(file_path: str) -> str | None: @@ -86,11 +85,11 @@ def _bundle_needs_reset(active_bundle: custom.ModelManagerSP.ModelBundle, availa if available_bundles is not None: matching_bundle = None for bundle in available_bundles: - if getattr(active_bundle, 'ref', None) and getattr(bundle, 'ref', None): + if active_bundle.ref and bundle.ref: if active_bundle.ref == bundle.ref: matching_bundle = bundle break - elif getattr(active_bundle, 'internalName', None) == getattr(bundle, 'internalName', None): + elif active_bundle.internalName == bundle.internalName: matching_bundle = bundle break @@ -98,36 +97,25 @@ def _bundle_needs_reset(active_bundle: custom.ModelManagerSP.ModelBundle, availa return True if active_bundle.minimumSelectorVersion != matching_bundle.minimumSelectorVersion: return True - - active_runner = getattr(active_bundle, 'runner', None) - matching_runner = getattr(matching_bundle, 'runner', None) - if active_runner is not None and matching_runner is not None: - if getattr(active_runner, 'raw', active_runner) != getattr(matching_runner, 'raw', matching_runner): - return True + if active_bundle.runner.raw != matching_bundle.runner.raw: + return True if set(_bundle_artifacts(active_bundle)) != set(_bundle_artifacts(matching_bundle)): return True - return not _bundle_is_valid_locally(active_bundle) + # missing files trigger re-download, not selection reset + return False def validate_active_bundle(params: Params, available_bundles: list[custom.ModelManagerSP.ModelBundle] | None = None) -> None: - global _LAST_VALIDATED_RAW - raw_bundle = params.get("ModelManager_ActiveBundle") if not raw_bundle: return - if raw_bundle == _LAST_VALIDATED_RAW: - return - active_bundle = get_active_bundle(params, raw_bundle_dict=raw_bundle) if active_bundle is None or _bundle_needs_reset(active_bundle, available_bundles): cloudlog.warning("Active model bundle invalid; resetting to default") params.remove("ModelManager_ActiveBundle") params.put("ModelRunnerTypeCache", int(custom.ModelManagerSP.Runner.stock), block=True) - _LAST_VALIDATED_RAW = None - else: - _LAST_VALIDATED_RAW = raw_bundle def get_active_bundle(params: Params | None = None, raw_bundle_dict: dict | bytes | None = None) -> "custom.ModelManagerSP.ModelBundle | None": diff --git a/openpilot/sunnypilot/models/manager.py b/openpilot/sunnypilot/models/manager.py index 37bcb781cf..60f01d0c81 100644 --- a/openpilot/sunnypilot/models/manager.py +++ b/openpilot/sunnypilot/models/manager.py @@ -257,15 +257,20 @@ class ModelManagerSP: """Main entry point for downloading a model bundle""" asyncio.run(self._download_bundle(model_bundle, destination_path)) + BOOT_SETTLE_TICKS = 10 # seconds at 1 Hz before validating active bundle + def main_thread(self) -> None: """Main thread for model management""" rk = Ratekeeper(1, print_delay_threshold=None) + boot_ticks = 0 while True: try: self.sm.update(0) self.available_models = self.model_fetcher.get_available_bundles(self.sm['deviceState'].chestnutPresent) - validate_active_bundle(self.params, self.available_models) + if boot_ticks >= self.BOOT_SETTLE_TICKS: + validate_active_bundle(self.params, self.available_models) + boot_ticks = min(boot_ticks + 1, self.BOOT_SETTLE_TICKS) self.active_bundle = get_active_bundle(self.params) if (index_to_download := self.params.get("ModelManager_DownloadIndex")) is not None: From 25c25047b890337059f3a6727c1525c49dca2b10 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 25 Aug 2026 01:12:12 -0400 Subject: [PATCH 3/3] models: persist model selection per catalog across chestnut state changes (#1960) --- openpilot/common/params_keys.h | 2 ++ openpilot/sunnypilot/models/helpers.py | 19 ++++++++++++++++++- openpilot/sunnypilot/models/manager.py | 5 +++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/openpilot/common/params_keys.h b/openpilot/common/params_keys.h index 01c14fb539..111099fce9 100644 --- a/openpilot/common/params_keys.h +++ b/openpilot/common/params_keys.h @@ -196,6 +196,8 @@ inline static std::unordered_map keys = { // Model Manager params {"ModelManager_ActiveBundle", {PERSISTENT, JSON}}, {"ModelManager_ActiveJson", {CLEAR_ON_MANAGER_START, STRING}}, + {"ModelManager_PrevBundle", {PERSISTENT, JSON}}, + {"ModelManager_PrevBundle_USBGPU", {PERSISTENT, JSON}}, {"ModelManager_ClearCache", {CLEAR_ON_MANAGER_START, BOOL}}, {"ModelManager_DownloadIndex", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, INT}}, {"ModelManager_Favs", {PERSISTENT | BACKUP, STRING}}, diff --git a/openpilot/sunnypilot/models/helpers.py b/openpilot/sunnypilot/models/helpers.py index d97d655d5f..e33cc445d1 100644 --- a/openpilot/sunnypilot/models/helpers.py +++ b/openpilot/sunnypilot/models/helpers.py @@ -106,14 +106,31 @@ def _bundle_needs_reset(active_bundle: custom.ModelManagerSP.ModelBundle, availa return False -def validate_active_bundle(params: Params, available_bundles: list[custom.ModelManagerSP.ModelBundle] | None = None) -> None: +def _prev_bundle_key(is_usbgpu: bool) -> str: + return "ModelManager_PrevBundle_USBGPU" if is_usbgpu else "ModelManager_PrevBundle" + + +def validate_active_bundle(params: Params, available_bundles: list[custom.ModelManagerSP.ModelBundle] | None = None, + is_usbgpu: bool = False) -> None: raw_bundle = params.get("ModelManager_ActiveBundle") if not raw_bundle: + prev = params.get(_prev_bundle_key(is_usbgpu)) + if prev and (prev_bundle := get_active_bundle(params, raw_bundle_dict=prev)) is not None: + if not _bundle_needs_reset(prev_bundle, available_bundles): + params.put("ModelManager_ActiveBundle", prev, block=True) return active_bundle = get_active_bundle(params, raw_bundle_dict=raw_bundle) if active_bundle is None or _bundle_needs_reset(active_bundle, available_bundles): cloudlog.warning("Active model bundle invalid; resetting to default") + params.put(_prev_bundle_key(not is_usbgpu), raw_bundle, block=True) + + prev = params.get(_prev_bundle_key(is_usbgpu)) + if prev and (prev_bundle := get_active_bundle(params, raw_bundle_dict=prev)) is not None: + if not _bundle_needs_reset(prev_bundle, available_bundles): + params.put("ModelManager_ActiveBundle", prev, block=True) + return + params.remove("ModelManager_ActiveBundle") params.put("ModelRunnerTypeCache", int(custom.ModelManagerSP.Runner.stock), block=True) diff --git a/openpilot/sunnypilot/models/manager.py b/openpilot/sunnypilot/models/manager.py index 60f01d0c81..e47cf7536c 100644 --- a/openpilot/sunnypilot/models/manager.py +++ b/openpilot/sunnypilot/models/manager.py @@ -267,9 +267,10 @@ class ModelManagerSP: while True: try: self.sm.update(0) - self.available_models = self.model_fetcher.get_available_bundles(self.sm['deviceState'].chestnutPresent) + chestnut_present = self.sm['deviceState'].chestnutPresent + self.available_models = self.model_fetcher.get_available_bundles(chestnut_present) if boot_ticks >= self.BOOT_SETTLE_TICKS: - validate_active_bundle(self.params, self.available_models) + validate_active_bundle(self.params, self.available_models, is_usbgpu=chestnut_present) boot_ticks = min(boot_ticks + 1, self.BOOT_SETTLE_TICKS) self.active_bundle = get_active_bundle(self.params)