diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index 2a6b59cc3..df18e44b1 100644 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -122,7 +122,8 @@ class Car: with car.CarParams.from_bytes(cached_params_raw) as _cached_params: cached_params = _cached_params - self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, self.params, num_pandas, cached_params, get_starpilot_toggles()) + self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, self.params, num_pandas, cached_params, + get_starpilot_toggles(read_persisted_force_params=True)) self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP) self.CP = self.CI.CP @@ -200,7 +201,7 @@ class Car: self.resume_prev_button = False - self.starpilot_toggles = get_starpilot_toggles() + self.starpilot_toggles = get_starpilot_toggles(read_persisted_force_params=True) self.FPCP.alternativeExperience |= interface_alternative_experience diff --git a/starpilot/common/starpilot_variables.py b/starpilot/common/starpilot_variables.py index 4c5954d30..9473a529f 100644 --- a/starpilot/common/starpilot_variables.py +++ b/starpilot/common/starpilot_variables.py @@ -336,7 +336,7 @@ def default_ev_tuning_enabled(CP): ev_vehicle |= getattr(CP, "transmissionType", None) == car.CarParams.TransmissionType.direct return bool(ev_vehicle) -def get_starpilot_toggles(sm=messaging.SubMaster(["starpilotPlan"])): +def get_starpilot_toggles(sm=messaging.SubMaster(["starpilotPlan"]), *, read_persisted_force_params=False): toggles_text = sm["starpilotPlan"].starpilotToggles if toggles_text: get_starpilot_toggles._last_toggles_text = toggles_text @@ -345,16 +345,18 @@ def get_starpilot_toggles(sm=messaging.SubMaster(["starpilotPlan"])): toggles = process_starpilot_toggles(toggles_text) - # Force drive-state controls must be authoritative from params so they - # apply immediately even if starpilotPlan publication is temporarily stale. - if not hasattr(get_starpilot_toggles, "_params"): - get_starpilot_toggles._params = Params(return_defaults=True) + # Realtime callers consume these values from the serialized toggle broadcast. + # Only startup/state-management callers should synchronously read the backing + # files; doing so from every 100 Hz control loop can stall critical processes. + if read_persisted_force_params: + if not hasattr(get_starpilot_toggles, "_params"): + get_starpilot_toggles._params = Params(return_defaults=True) - toggles.force_offroad = get_starpilot_toggles._params.get_bool("ForceOffroad") - toggles.force_onroad = get_starpilot_toggles._params.get_bool("ForceOnroad") - # Controller selection happens before the first live StarPilot broadcast. Do - # not let a cached CarParams/controller type hide the persisted user request. - toggles.force_torque_controller = get_starpilot_toggles._params.get_bool("ForceTorqueController") + toggles.force_offroad = get_starpilot_toggles._params.get_bool("ForceOffroad") + toggles.force_onroad = get_starpilot_toggles._params.get_bool("ForceOnroad") + # Controller selection happens before the first live StarPilot broadcast. Do + # not let a cached CarParams/controller type hide the persisted user request. + toggles.force_torque_controller = get_starpilot_toggles._params.get_bool("ForceTorqueController") return toggles @cache diff --git a/starpilot/common/tests/test_starpilot_variables.py b/starpilot/common/tests/test_starpilot_variables.py index 6a2e5aa84..23e263eae 100644 --- a/starpilot/common/tests/test_starpilot_variables.py +++ b/starpilot/common/tests/test_starpilot_variables.py @@ -41,11 +41,29 @@ def test_get_starpilot_toggles_uses_persisted_force_torque_request(monkeypatch): monkeypatch.setattr(spv.get_starpilot_toggles, "_params", params, raising=False) payload = '{"force_torque_controller": false}' - toggles = spv.get_starpilot_toggles({"starpilotPlan": SimpleNamespace(starpilotToggles=payload)}) + toggles = spv.get_starpilot_toggles( + {"starpilotPlan": SimpleNamespace(starpilotToggles=payload)}, + read_persisted_force_params=True, + ) assert toggles.force_torque_controller is True +def test_get_starpilot_toggles_realtime_path_does_not_read_persisted_force_params(monkeypatch): + class UnexpectedParamsRead: + def get_bool(self, key): + raise AssertionError(f"unexpected persisted param read: {key}") + + monkeypatch.setattr(spv.get_starpilot_toggles, "_params", UnexpectedParamsRead(), raising=False) + + payload = '{"force_offroad": false, "force_onroad": true, "force_torque_controller": false}' + toggles = spv.get_starpilot_toggles({"starpilotPlan": SimpleNamespace(starpilotToggles=payload)}) + + assert toggles.force_offroad is False + assert toggles.force_onroad is True + assert toggles.force_torque_controller is False + + class _FakeParams: def __init__(self, floats=None, ints=None, bools=None): self.floats = dict(floats or {}) diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py index a2d0f45a7..2a6bec5b1 100644 --- a/system/hardware/hardwared.py +++ b/system/hardware/hardwared.py @@ -218,7 +218,7 @@ def hardware_thread(end_event, hw_queue) -> None: sm = sm.extend(['starpilotPlan']) pm = pm.extend(['starpilotDeviceState']) - starpilot_toggles = get_starpilot_toggles() + starpilot_toggles = get_starpilot_toggles(read_persisted_force_params=True) while not end_event.is_set(): sm.update(PANDA_STATES_TIMEOUT) @@ -488,7 +488,7 @@ def hardware_thread(end_event, hw_queue) -> None: should_start_prev = should_start # StarPilot variables - starpilot_toggles = get_starpilot_toggles(sm) + starpilot_toggles = get_starpilot_toggles(sm, read_persisted_force_params=True) def main(): diff --git a/system/manager/manager.py b/system/manager/manager.py index f61de3020..2e460b12e 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -1043,7 +1043,7 @@ def manager_thread() -> None: last_timing = _log_boot_timing("manager_thread", "messaging", manager_thread_start, last_timing) write_onroad_params(False, params) - initial_toggles = get_starpilot_toggles() + initial_toggles = get_starpilot_toggles(read_persisted_force_params=True) last_timing = _log_boot_timing("manager_thread", "initial_toggles", manager_thread_start, last_timing) ensure_running(managed_processes.values(), False, params=params, CP=sm['carParams'], not_run=ignore, starpilot_toggles=initial_toggles) last_timing = _log_boot_timing("manager_thread", "initial_ensure_running", manager_thread_start, last_timing) @@ -1059,7 +1059,7 @@ def manager_thread() -> None: params_memory = Params(memory=True) - starpilot_toggles = get_starpilot_toggles() + starpilot_toggles = get_starpilot_toggles(read_persisted_force_params=True) last_timing = _log_boot_timing("manager_thread", "loop_toggles", manager_thread_start, last_timing) _log_boot_timing("manager_thread", "loop_ready", manager_thread_start, last_timing) @@ -1133,7 +1133,7 @@ def manager_thread() -> None: break # StarPilot variables - starpilot_toggles = get_starpilot_toggles(sm) + starpilot_toggles = get_starpilot_toggles(sm, read_persisted_force_params=True) def main() -> None: