From f2f8cfffa89825dc3f1f35d832a3cdaae4e82bde Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Sun, 1 Nov 2026 21:01:08 -0600 Subject: [PATCH] Stop Auto Learn when learning is switched off --- .../tests/test_automatic_learning_source.py | 52 +++++++++++++++++++ openpilot/system/manager/process_config.py | 9 +++- prebuilt.json | 2 +- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/openpilot/starpilot/lateral/tests/test_automatic_learning_source.py b/openpilot/starpilot/lateral/tests/test_automatic_learning_source.py index d46c9d41d8..558a904230 100644 --- a/openpilot/starpilot/lateral/tests/test_automatic_learning_source.py +++ b/openpilot/starpilot/lateral/tests/test_automatic_learning_source.py @@ -7,6 +7,9 @@ import struct import unittest from opendbc.car.car_helpers import interfaces +from opendbc.car.structs import car +from openpilot.starpilot.lateral.learning_preferences import KEY as LEARNING_KEY, LEGACY_KEY as LEARNING_LEGACY_KEY, replace_preference +from openpilot.system.manager.process_config import managed_processes from opendbc.car.gm.values import CAR as GM from opendbc.car.hyundai.values import CAR as HKG from opendbc.car.toyota.values import CAR as TOYOTA @@ -236,3 +239,52 @@ class TestAutomaticLearningSource(unittest.TestCase): self.assertIsNone(conversion_identity(cp, ci, ControllerMode.STANDARD)) cp, ci, _, _ = context(HKG.HYUNDAI_IONIQ_6) self.assertIsNone(conversion_identity(cp, ci, ControllerMode.STARPILOT)) + + +class TestDisabledLearnerProcess(unittest.TestCase): + def setUp(self): + self.prefix = OpenpilotPrefix() + self.prefix.__enter__() + self.addCleanup(self.prefix.__exit__, None, None, None) + self.params = Params() + self.cp = car.CarParams.new_message(brand='hyundai', carFingerprint='HYUNDAI_IONIQ_6') + self.process = managed_processes['steerlearnerd'] + + def test_default_still_runs_onroad(self): + assert self.process.should_run(True, self.params, self.cp) + assert not self.process.should_run(False, self.params, self.cp) + assert self.process.restart_on_exit + + def test_explicit_off_does_not_start_learner(self): + self.params.put(LEARNING_KEY, replace_preference(None, self.cp, False), block=True) + assert not self.process.should_run(True, self.params, self.cp) + + def test_toggle_can_stop_and_restart_learner(self): + for enabled in (False, True, False, True): + self.params.put(LEARNING_KEY, replace_preference(self.params.get(LEARNING_KEY), self.cp, enabled), block=True) + assert self.process.should_run(True, self.params, self.cp) is enabled + + def test_legacy_off_does_not_start_learner(self): + self.params.put_bool(LEARNING_LEGACY_KEY, True, block=True) + assert not self.process.should_run(True, self.params, self.cp) + + def test_scoped_on_retains_precedence_over_legacy_off(self): + self.params.put_bool(LEARNING_LEGACY_KEY, True, block=True) + self.params.put(LEARNING_KEY, replace_preference(None, self.cp, True), block=True) + assert self.process.should_run(True, self.params, self.cp) + + def test_invalid_preference_does_not_start_learner(self): + for raw in (b'', b'not json', b'{"version":1,"vehicles":{"hyundai":{"HYUNDAI_IONIQ_6":1}}}'): + self.params.put(LEARNING_KEY, raw, block=True) + assert not self.process.should_run(True, self.params, self.cp) + + def test_other_vehicle_preference_is_not_reused(self): + self.params.put(LEARNING_KEY, replace_preference(None, self.cp, False), block=True) + other = car.CarParams.new_message(brand='ford', carFingerprint='FORD_MUSTANG_MACH_E_MK1') + assert self.process.should_run(True, self.params, other) + + def test_offroad_does_not_read_preferences(self): + class NoRead: + def get(self, *args, **kwargs): + raise AssertionError('Offroad learner should not read settings') + assert not self.process.should_run(False, NoRead(), self.cp) diff --git a/openpilot/system/manager/process_config.py b/openpilot/system/manager/process_config.py index 52983d54a3..f331d85caf 100644 --- a/openpilot/system/manager/process_config.py +++ b/openpilot/system/manager/process_config.py @@ -62,6 +62,13 @@ def always_run(started: bool, params: Params, CP: car.CarParams) -> bool: def only_onroad(started: bool, params: Params, CP: car.CarParams) -> bool: return started +def steering_learning(started: bool, params: Params, CP: car.CarParams) -> bool: + if not started: + return False + from openpilot.starpilot.lateral.learning_preferences import read_preference + preference = read_preference(params, CP) + return preference.valid and preference.enabled + def vision_slc_development(started: bool, params: Params, CP: car.CarParams) -> bool: from openpilot.starpilot.feature_runtime import enabled as feature_enabled return started and feature_enabled(params, CP, 'slc', os.environ) and feature_enabled(params, CP, 'vision', os.environ) @@ -162,7 +169,7 @@ procs = [ NativeProcess("_pandad", "openpilot/selfdrive/pandad", ["./pandad"], always_run, enabled=False), PythonProcess("calibrationd", "openpilot.selfdrive.locationd.calibrationd", only_onroad), PythonProcess("torqued", "openpilot.selfdrive.locationd.torqued", only_onroad), - PythonProcess("steerlearnerd", "openpilot.starpilot.flm.steerlearnerd", only_onroad, restart_on_exit=True), + PythonProcess("steerlearnerd", "openpilot.starpilot.flm.steerlearnerd", steering_learning, restart_on_exit=True), PythonProcess("controlsd", "openpilot.selfdrive.controls.controlsd", and_(not_joystick, iscar)), PythonProcess("joystickd", "openpilot.tools.joystick.joystickd", or_(joystick, notcar)), PythonProcess("selfdrived", "openpilot.selfdrive.selfdrived.selfdrived", only_onroad), diff --git a/prebuilt.json b/prebuilt.json index 746122e7a5..c624505ece 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "079e92a64c345ad09f03d5a7ede05b13deea46058bbdcc4a29011799cf17a50d", "schema_version": 1} +{"protected_inventory_sha256": "02da6162febbf11d54a5478dc5fb06e44bf60f07217aa39a141e8f181b9d5b0b", "schema_version": 1}