diff --git a/starpilot/common/favorite_slots.py b/starpilot/common/favorite_slots.py index 04d0f62a8..9cf0a3cc5 100644 --- a/starpilot/common/favorite_slots.py +++ b/starpilot/common/favorite_slots.py @@ -13,8 +13,10 @@ FAVORITE_SLOT_COUNT = 3 FAVORITE_ACTION_PREFIX = "__starpilot_favorite_action__:" FAVORITE_ACTION_DISTANCE_DECREASE = f"{FAVORITE_ACTION_PREFIX}distance_decrease" FAVORITE_ACTION_DISTANCE_INCREASE = f"{FAVORITE_ACTION_PREFIX}distance_increase" +FAVORITE_ACTION_TOGGLE_TRAFFIC_MODE = f"{FAVORITE_ACTION_PREFIX}toggle_traffic_mode" FAVORITE_ACTION_DECEL_COUNTER = "FavoriteVirtualDecelCruiseCounter" FAVORITE_ACTION_ACCEL_COUNTER = "FavoriteVirtualAccelCruiseCounter" +FAVORITE_ACTION_TRAFFIC_MODE_COUNTER = "FavoriteTrafficModeCounter" FAVORITE_ACTION_OPTIONS = ( { "key": FAVORITE_ACTION_DISTANCE_DECREASE, @@ -30,6 +32,13 @@ FAVORITE_ACTION_OPTIONS = ( "section": "Actions", "action": "accelCruise", }, + { + "key": FAVORITE_ACTION_TOGGLE_TRAFFIC_MODE, + "label": "Toggle Traffic Mode", + "description": "Engages or disengages Traffic Mode while openpilot is actively controlling.", + "section": "Actions", + "action": "trafficMode", + }, ) FAVORITE_ACTION_KEYS = {option["key"] for option in FAVORITE_ACTION_OPTIONS} FAVORITE_ACTION_LABELS = {option["key"]: option["label"] for option in FAVORITE_ACTION_OPTIONS} @@ -161,11 +170,11 @@ def trigger_favorite_action(key: str | None, params_memory: Params | None = None return False params_memory = params_memory or Params(memory=True) - counter_key = ( - FAVORITE_ACTION_ACCEL_COUNTER - if key == FAVORITE_ACTION_DISTANCE_INCREASE - else FAVORITE_ACTION_DECEL_COUNTER - ) + counter_key = { + FAVORITE_ACTION_DISTANCE_DECREASE: FAVORITE_ACTION_DECEL_COUNTER, + FAVORITE_ACTION_DISTANCE_INCREASE: FAVORITE_ACTION_ACCEL_COUNTER, + FAVORITE_ACTION_TOGGLE_TRAFFIC_MODE: FAVORITE_ACTION_TRAFFIC_MODE_COUNTER, + }[key] params_memory.put_int(counter_key, params_memory.get_int(counter_key) + 1) return True diff --git a/starpilot/common/tests/test_favorite_slots.py b/starpilot/common/tests/test_favorite_slots.py index 517455e7a..85da1c324 100644 --- a/starpilot/common/tests/test_favorite_slots.py +++ b/starpilot/common/tests/test_favorite_slots.py @@ -2,6 +2,8 @@ from openpilot.common.params import ParamKeyType from openpilot.starpilot.common.favorite_slots import ( FAVORITE_ACTION_ACCEL_COUNTER, FAVORITE_ACTION_DISTANCE_INCREASE, + FAVORITE_ACTION_TRAFFIC_MODE_COUNTER, + FAVORITE_ACTION_TOGGLE_TRAFFIC_MODE, FAVORITE_SLOTS_PARAM, default_favorite_slots, load_favorite_slots, @@ -97,3 +99,14 @@ def test_toggle_favorite_slot_action_increments_virtual_button_counter(): assert toggle_favorite_slot(0, params, memory) is True assert memory.get_int(FAVORITE_ACTION_ACCEL_COUNTER) == 1 + + +def test_toggle_favorite_slot_action_increments_traffic_mode_counter(): + params = FakeParams() + memory = FakeParams() + params.put(FAVORITE_SLOTS_PARAM, [ + {"enabled": True, "show_onroad": True, "key": FAVORITE_ACTION_TOGGLE_TRAFFIC_MODE, "label": "Toggle Traffic Mode"}, + ]) + + assert toggle_favorite_slot(0, params, memory) is True + assert memory.get_int(FAVORITE_ACTION_TRAFFIC_MODE_COUNTER) == 1 diff --git a/starpilot/controls/starpilot_card.py b/starpilot/controls/starpilot_card.py index 1b7375fd0..106acd30b 100644 --- a/starpilot/controls/starpilot_card.py +++ b/starpilot/controls/starpilot_card.py @@ -14,7 +14,7 @@ from openpilot.starpilot.common.experimental_state import ( sync_manual_cc_state, sync_manual_ce_state, ) -from openpilot.starpilot.common.favorite_slots import toggle_favorite_slot +from openpilot.starpilot.common.favorite_slots import FAVORITE_ACTION_TRAFFIC_MODE_COUNTER, toggle_favorite_slot from openpilot.starpilot.common.starpilot_utilities import is_FrogsGoMoo from openpilot.starpilot.common.starpilot_variables import ERROR_LOGS_PATH, GearShifter, NON_DRIVING_GEARS @@ -58,6 +58,7 @@ class StarPilotCard: self.pause_longitudinal = False self.switchback_mode_enabled = self.params_memory.get_bool("SwitchbackModeEnabled") self.traffic_mode_enabled = False + self._favorite_traffic_mode_counter = self.params_memory.get_int(FAVORITE_ACTION_TRAFFIC_MODE_COUNTER) self.gap_counter = 0 self.cancel_counter = 0 @@ -98,6 +99,14 @@ class StarPilotCard: counter = self.params_memory.get_int("WheelButtonBookmarkCounter") self.params_memory.put_int("WheelButtonBookmarkCounter", counter + 1) + def _handle_favorite_traffic_mode_action(self, sm): + counter = self.params_memory.get_int(FAVORITE_ACTION_TRAFFIC_MODE_COUNTER) + pending = counter - self._favorite_traffic_mode_counter + self._favorite_traffic_mode_counter = counter + + if pending > 0 and sm["carControl"].longActive and pending % 2: + self.traffic_mode_enabled = not self.traffic_mode_enabled + def handle_experimental_mode(self, sm, starpilot_toggles): if getattr(starpilot_toggles, "safe_mode", False): return @@ -117,6 +126,7 @@ class StarPilotCard: def update(self, carState, starpilotCarState, sm, starpilot_toggles): self.switchback_mode_enabled = self.params_memory.get_bool("SwitchbackModeEnabled") + self._handle_favorite_traffic_mode_action(sm) button_event_types = [self._button_type_raw(be) for be in carState.buttonEvents] button_aol_supported = self.CP.brand == "hyundai" or starpilot_toggles.lkas_allowed_for_aol button_managed_aol = starpilot_toggles.always_on_lateral_lkas or (button_aol_supported and starpilot_toggles.main_cruise_aol_toggle) diff --git a/starpilot/controls/tests/test_starpilot_card.py b/starpilot/controls/tests/test_starpilot_card.py index e60db227a..46d4e2659 100644 --- a/starpilot/controls/tests/test_starpilot_card.py +++ b/starpilot/controls/tests/test_starpilot_card.py @@ -8,6 +8,8 @@ from openpilot.common.params import ParamKeyType from openpilot.starpilot.common.favorite_slots import ( FAVORITE_ACTION_ACCEL_COUNTER, FAVORITE_ACTION_DISTANCE_INCREASE, + FAVORITE_ACTION_TRAFFIC_MODE_COUNTER, + FAVORITE_ACTION_TOGGLE_TRAFFIC_MODE, FAVORITE_SLOTS_PARAM, ) from openpilot.starpilot.controls import starpilot_card as spc @@ -780,3 +782,41 @@ def test_favorite_wheel_action_can_press_virtual_resume(monkeypatch, tmp_path): card.handle_button_event("lkas", make_sm(), make_toggles(favorite_1_via_lkas=True)) assert card.params_memory.get_int(FAVORITE_ACTION_ACCEL_COUNTER) == 1 + + +def test_favorite_action_toggles_traffic_mode_when_longitudinal_control_is_active(monkeypatch, tmp_path): + monkeypatch.setattr(spc, "Params", FakeParams) + monkeypatch.setattr(spc, "is_FrogsGoMoo", lambda: False) + monkeypatch.setattr(spc, "ERROR_LOGS_PATH", tmp_path) + + card = spc.StarPilotCard(SimpleNamespace(brand="gm"), SimpleNamespace(alternativeExperience=0)) + card.params.put(FAVORITE_SLOTS_PARAM, [ + {"enabled": True, "show_onroad": True, "key": FAVORITE_ACTION_TOGGLE_TRAFFIC_MODE, "label": "Toggle Traffic Mode"}, + ]) + + sm = make_sm() + sm["carControl"].longActive = True + card.handle_button_event("lkas", sm, make_toggles(favorite_1_via_lkas=True)) + + card.update(make_car_state(), SimpleNamespace(distancePressed=False), sm, make_toggles()) + + assert card.traffic_mode_enabled is True + assert card.params_memory.get_int(FAVORITE_ACTION_TRAFFIC_MODE_COUNTER) == 1 + + +def test_favorite_traffic_mode_action_is_consumed_when_not_active(monkeypatch, tmp_path): + monkeypatch.setattr(spc, "Params", FakeParams) + monkeypatch.setattr(spc, "is_FrogsGoMoo", lambda: False) + monkeypatch.setattr(spc, "ERROR_LOGS_PATH", tmp_path) + + card = spc.StarPilotCard(SimpleNamespace(brand="gm"), SimpleNamespace(alternativeExperience=0)) + card.params.put(FAVORITE_SLOTS_PARAM, [ + {"enabled": True, "show_onroad": True, "key": FAVORITE_ACTION_TOGGLE_TRAFFIC_MODE, "label": "Toggle Traffic Mode"}, + ]) + + sm = make_sm() + card.handle_button_event("lkas", sm, make_toggles(favorite_1_via_lkas=True)) + card.update(make_car_state(), SimpleNamespace(distancePressed=False), sm, make_toggles()) + + assert card.traffic_mode_enabled is False + assert card._favorite_traffic_mode_counter == 1