From d4a49cb8c102153492545bb8ea8a17f5b670a7cc Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Thu, 22 Oct 2026 17:50:08 -0500 Subject: [PATCH] Allow tuning preparation to be turned off --- openpilot/starpilot/ui/controller_feature.py | 6 ++-- .../starpilot/ui/tests/test_lateral_setup.py | 31 +++++++++++++++++++ openpilot/starpilot/ui/tuning_preparation.py | 8 +++-- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/openpilot/starpilot/ui/controller_feature.py b/openpilot/starpilot/ui/controller_feature.py index ad2a3c7b94..040751f9c5 100644 --- a/openpilot/starpilot/ui/controller_feature.py +++ b/openpilot/starpilot/ui/controller_feature.py @@ -141,12 +141,12 @@ class ControllerFeature: any(raw not in (journal['prior'][key], journal['prepared'][key]) for key, raw in (sources or {}).items())) return FeatureRow(SETUP_ACTION, 'Prep My Vehicle for Tuning', 'On' if active else 'Off', journal_raw, choices=('Off', 'On'), default_value='Off', - available=valid and readable and journal is not False and not conflict and + available=(active or valid and not conflict) and sources is not None and readable and journal is not False and self.authority('parked_preferences') and self.authority('preferences'), reason=('Enable before recording a route for a tuning request on the StarPilot Discord. ' + 'Turn off to restore your previous tune.' if not conflict and (not active or complete) else 'Preparation was interrupted. Turn off to restore your previous tune.' if not conflict else - 'Settings changed during preparation; your saved tune has been kept.'), + 'Turn off to restore previous settings while keeping changes made after preparation.'), capability=capability, dependencies=tuple((key, raw) for key, raw in (sources or {}).items())) def apply_setup(self, request: FeatureSettingsRequest) -> bool: @@ -159,7 +159,7 @@ class ControllerFeature: CP = self.vehicle_params() sources = dict(row.dependencies) try: - desired = self._setup_values(CP, sources) + desired = self._setup_values(CP, sources) if request.value == 'On' else {} except (ValueError, TypeError, UnicodeError, OverflowError): return False capability = row.capability diff --git a/openpilot/starpilot/ui/tests/test_lateral_setup.py b/openpilot/starpilot/ui/tests/test_lateral_setup.py index bcfd0329ed..fcc9bd952b 100644 --- a/openpilot/starpilot/ui/tests/test_lateral_setup.py +++ b/openpilot/starpilot/ui/tests/test_lateral_setup.py @@ -77,6 +77,37 @@ class TestLateralSetup(unittest.TestCase): lane = LaneChangePolicy(False, 1.0, True, True, 3.0, 3.0, True, 0.9) self.params.put(LANE_KEY, to_value(lane), block=True) + def test_active_prep_can_turn_off_after_user_edits_without_overwriting_them(self): + self.seed() + prior = {key: Path(self.params.get_param_path(key)).read_bytes() + if Path(self.params.get_param_path(key)).exists() else None for key in controller_keys()} + self.assertTrue(self.owner.apply(self.request())) + changed = {'LaneCenterOffset': b'0.37', LANE_KEY: b'broken'} + for key, value in changed.items(): + Path(self.params.get_param_path(key)).write_bytes(value) + self.assertEqual(self.row().value, 'On') + self.assertTrue(self.row().available) + self.assertTrue(self.owner.apply(self.request('Off'))) + self.assertIsNone(self.params.get(preparation.KEY)) + for key, value in prior.items(): + path = Path(self.params.get_param_path(key)) + self.assertEqual(path.read_bytes() if path.exists() else None, changed.get(key, value)) + + def test_galaxy_active_prep_can_turn_off_after_new_lane_preference(self): + self.seed() + self.assertTrue(self.owner.apply(self.request())) + self.params.put('LaneCenterOffset', 0.37, block=True) + current = SimpleNamespace(parked=True, cp=self.cp, raw=b'current-cp') + gateway = SettingsGateway(self.params, Mock(spec=ContextSource, sample=lambda: AuthorityContext(current.parked, current.cp, current.raw)), + clock=lambda: 10) + page = gateway.page('torque', 'session', b'generation') + index = next(i for i, row in enumerate(page['rows']) if row['label'] == 'Prep My Vehicle for Tuning') + intent = gateway.preview(page['view'], index, 0, 'session', b'generation') + self.assertEqual(intent['proposed'], 'Off') + self.assertTrue(gateway.confirm(intent['intent'], 'session', b'generation')) + self.assertEqual(Path(self.params.get_param_path('LaneCenterOffset')).read_bytes(), b'0.37') + self.assertIsNone(self.params.get(preparation.KEY)) + def test_preparation_journals_scoped_choice_and_restores_other_vehicles_exactly(self): self.seed() other = cp_for(TOYOTA.TOYOTA_COROLLA_TSS2) diff --git a/openpilot/starpilot/ui/tuning_preparation.py b/openpilot/starpilot/ui/tuning_preparation.py index 645918b5c3..76b3bf3b05 100644 --- a/openpilot/starpilot/ui/tuning_preparation.py +++ b/openpilot/starpilot/ui/tuning_preparation.py @@ -79,15 +79,19 @@ def transition(params, vehicle, sources, desired, journal_raw, enabled, authoriz if journal['vehicle'] != vehicle: return False for key, current in sources.items(): - if current not in (journal['prior'][key], journal['prepared'][key]): + if enabled and current not in (journal['prior'][key], journal['prepared'][key]): return False target = journal['prepared'] if enabled else journal['prior'] for key, value in target.items(): if not authorized(): return False current, readable = read_saved(params, key, LIMIT) - if not readable or current not in (journal['prior'][key], journal['prepared'][key]): + if not readable or current != sources[key]: return False + if current not in (journal['prior'][key], journal['prepared'][key]): + if enabled: + return False + continue if current != value: replace_saved(params, key, value) if not authorized():