From 74b2d519b95ba814e13f77e2733fbd08ea14afd9 Mon Sep 17 00:00:00 2001 From: nayan Date: Tue, 5 Aug 2025 15:52:46 -0400 Subject: [PATCH 01/37] move custom button to cs_sp, add bookmark functionality --- cereal/custom.capnp | 11 +++++++ common/params_keys.h | 1 + opendbc_repo | 2 +- selfdrive/selfdrived/selfdrived.py | 29 +++++++++++++++---- .../settings/vehicle/hyundai_settings.cc | 27 +++++++++++++++++ .../settings/vehicle/hyundai_settings.h | 2 ++ 6 files changed, 66 insertions(+), 6 deletions(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index fdb89da84c..cfd20a398f 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -247,6 +247,17 @@ struct BackupManagerSP @0xf98d843bfd7004a3 { } struct CarStateSP @0xb86e6369214c01c8 { + buttonEvents @0 :List(ButtonEvent); + + struct ButtonEvent { + pressed @0 :Bool; + type @1 :Type; + + enum Type { + unknown @0; + customButton @1; + } + } } struct LiveMapDataSP @0xf416ec09499d9d19 { diff --git a/common/params_keys.h b/common/params_keys.h index 23e938eb61..60c5a3b6e0 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -154,6 +154,7 @@ inline static std::unordered_map keys = { {"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}}, {"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}}, {"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}}, + {"SteeringCustomButtonMapping", {PERSISTENT | BACKUP, INT, "0"}}, // MADS params {"Mads", {PERSISTENT | BACKUP, BOOL, "1"}}, diff --git a/opendbc_repo b/opendbc_repo index 5509df5eb3..6676b7cb33 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 5509df5eb3e965335c04c61d4039e8378bf18718 +Subproject commit 6676b7cb3384fa582c73ad1b0a7e34da9288ee21 diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index e203270579..533802c62b 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -45,6 +45,8 @@ EventName = log.OnroadEvent.EventName ButtonType = car.CarState.ButtonEvent.Type SafetyModel = car.CarParams.SafetyModel +ButtonTypeSP = custom.CarStateSP.ButtonEvent.Type + IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput) @@ -88,7 +90,7 @@ class SelfdriveD(CruiseHelper): self.calibrator = PoseCalibrator() # Setup sockets - self.pm = messaging.PubMaster(['selfdriveState', 'onroadEvents'] + ['selfdriveStateSP', 'onroadEventsSP']) + self.pm = messaging.PubMaster(['selfdriveState', 'onroadEvents'] + ['selfdriveStateSP', 'onroadEventsSP', 'userFlag']) self.gps_location_service = get_gps_location_service(self.params) self.gps_packets = [self.gps_location_service] @@ -97,6 +99,7 @@ class SelfdriveD(CruiseHelper): # TODO: de-couple selfdrived with card/conflate on carState without introducing controls mismatches self.car_state_sock = messaging.sub_sock('carState', timeout=20) + self.car_state_sp_sock = messaging.sub_sock('carStateSP', timeout=20) ignore = self.sensor_packets + self.gps_packets + ['alertDebug'] if SIMULATION: @@ -117,6 +120,8 @@ class SelfdriveD(CruiseHelper): self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled") self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") + self.custom_button_mapping = self.params.get("SteeringCustomButtonMapping") + car_recognized = self.CP.brand != 'mock' # cleanup old params @@ -126,6 +131,7 @@ class SelfdriveD(CruiseHelper): self.params.remove("ExperimentalMode") self.CS_prev = car.CarState.new_message() + self.CS_SP_prev = custom.CarStateSP.new_message() self.AM = AlertManager() self.events = Events() @@ -447,7 +453,9 @@ class SelfdriveD(CruiseHelper): def data_sample(self): _car_state = messaging.recv_one(self.car_state_sock) + _car_state_sp = messaging.recv_one(self.car_state_sp_sock) CS = _car_state.carState if _car_state else self.CS_prev + CS_SP = _car_state_sp.carStateSP if _car_state_sp else self.CS_SP_prev self.sm.update(0) @@ -490,7 +498,7 @@ class SelfdriveD(CruiseHelper): if ps.safetyModel not in IGNORED_SAFETY_MODES): self.mismatch_counter += 1 - return CS + return CS, CS_SP def update_alerts(self, CS): clear_event_types = set() @@ -509,7 +517,7 @@ class SelfdriveD(CruiseHelper): self.AM.add_many(self.sm.frame, alerts + alerts_sp) self.AM.process_alerts(self.sm.frame, clear_event_types) - def publish_selfdriveState(self, CS): + def publish_selfdriveState(self, CS, CS_SP): # selfdriveState ss_msg = messaging.new_message('selfdriveState') ss_msg.valid = True @@ -559,8 +567,17 @@ class SelfdriveD(CruiseHelper): self.pm.send('onroadEventsSP', ce_send_sp) self.events_sp_prev = self.events_sp.names.copy() + # custom button handling for bookmark + custom_pressed = any(be.type == ButtonTypeSP.customButton for be in CS_SP.buttonEvents) + if custom_pressed: + # 0 = Off + # 1 = bookmark + if self.custom_button_mapping == 1: + uf_msg = messaging.new_message('userFlag', valid=True) + self.pm.send('userFlag', uf_msg) + def step(self): - CS = self.data_sample() + CS, CS_SP = self.data_sample() self.update_events(CS) if not self.CP.passive and self.initialized: self.enabled, self.active = self.state_machine.update(self.events) @@ -568,9 +585,10 @@ class SelfdriveD(CruiseHelper): self.mads.update(CS) self.update_alerts(CS) - self.publish_selfdriveState(CS) + self.publish_selfdriveState(CS, CS_SP) self.CS_prev = CS + self.CS_SP_prev = CS_SP def params_thread(self, evt): while not evt.is_set(): @@ -579,6 +597,7 @@ class SelfdriveD(CruiseHelper): self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl self.personality = self.params.get("LongitudinalPersonality", return_default=True) + self.custom_button_mapping = self.params.get("SteeringCustomButtonMapping") self.mads.read_params() time.sleep(0.1) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.cc index 01ea64f0cb..74483ffc41 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.cc @@ -8,6 +8,19 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.h" HyundaiSettings::HyundaiSettings(QWidget *parent) : BrandSettingsInterface(parent) { + + std::vector custom_btn_texts{ tr("Off"), tr("Bookmark") }; + customButtonMapping = new ButtonParamControlSP( + "SteeringCustomButtonMapping", + tr("Steering Custom ☆ Button"), + tr("Customize the steering wheel custom/star button for openpilot control.\n" + "This will not disable OEM functionality."), + "", + custom_btn_texts, + 300 + ); + list->addItem(customButtonMapping); + std::vector tuning_texts{ tr("Off"), tr("Dynamic"), tr("Predictive") }; longitudinalTuningToggle = new ButtonParamControl( "HyundaiLongitudinalTuning", @@ -36,6 +49,18 @@ void HyundaiSettings::updateSettings() { has_longitudinal_control = false; } + auto cp_sp_bytes = params.get("CarParamsSPPersistent"); + if (!cp_sp_bytes.empty()) { + AlignedBuffer aligned_buf; + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_sp_bytes.data(), cp_sp_bytes.size())); + cereal::CarParamsSP::Reader CP_SP = cmsg.getRoot(); + + // TODO-SP: Better way to get the flag value in qt? + has_custom_button = CP_SP.getFlags() & 64; + } else { + has_custom_button = false; + } + LongitudinalTuningOption longitudinal_tuning_option; if (longitudinal_tuning_param == int(LongitudinalTuningOption::PREDICTIVE)) { longitudinal_tuning_option = LongitudinalTuningOption::PREDICTIVE; @@ -54,4 +79,6 @@ void HyundaiSettings::updateSettings() { longitudinalTuningToggle->setEnabled(!longitudinal_tuning_disabled); longitudinalTuningToggle->setDescription(longitudinal_tuning_description); longitudinalTuningToggle->showDescription(); + + customButtonMapping->setVisible(has_custom_button); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.h index c94d40cfde..55bc4da5a2 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.h @@ -29,7 +29,9 @@ public: private: bool has_longitudinal_control = false; + bool has_custom_button = false; ButtonParamControl *longitudinalTuningToggle = nullptr; + ButtonParamControlSP *customButtonMapping = nullptr; static QString toggleDisableMsg(bool _offroad, bool _has_longitudinal_control) { if (!_has_longitudinal_control) { From 5f1f34fa7f8b9d66373aa7507549ace92d90355c Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 10 Aug 2025 09:31:11 -0400 Subject: [PATCH 02/37] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 6676b7cb33..1e76f67930 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 6676b7cb3384fa582c73ad1b0a7e34da9288ee21 +Subproject commit 1e76f6793079ac2649a4e0ba5f43e13cc2cc05c4 From f2949e1dc2a488afe568c00afb5d5af8ba15afcf Mon Sep 17 00:00:00 2001 From: nayan Date: Sun, 10 Aug 2025 11:49:35 -0400 Subject: [PATCH 03/37] use custom button in feedbackd --- RELEASES.md | 1 + selfdrive/selfdrived/selfdrived.py | 16 +------- selfdrive/ui/feedback/feedbackd.py | 64 ++++++++++++++++++++---------- 3 files changed, 44 insertions(+), 37 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 584be9d67e..aa7b296f2a 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -9,6 +9,7 @@ Version 0.10.0 (2025-08-05) * Enable live-learned steering actuation delay * Record driving feedback using LKAS button when MADS is disabled +* Allow Bookmark/Record driving feedback using Custom ☆ button for Hyundai/Kia/Genesis vehicles * Opt-in audio recording for dashcam video Version 0.9.9 (2025-05-23) diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index e9a1613e7b..b7d4bab332 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -44,8 +44,6 @@ EventName = log.OnroadEvent.EventName ButtonType = car.CarState.ButtonEvent.Type SafetyModel = car.CarParams.SafetyModel -ButtonTypeSP = custom.CarStateSP.ButtonEvent.Type - IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput) @@ -78,7 +76,7 @@ class SelfdriveD(CruiseHelper): self.excessive_actuation = self.params.get("Offroad_ExcessiveActuation") is not None # Setup sockets - self.pm = messaging.PubMaster(['selfdriveState', 'onroadEvents'] + ['selfdriveStateSP', 'onroadEventsSP', 'userFlag']) + self.pm = messaging.PubMaster(['selfdriveState', 'onroadEvents'] + ['selfdriveStateSP', 'onroadEventsSP']) self.gps_location_service = get_gps_location_service(self.params) self.gps_packets = [self.gps_location_service] @@ -108,8 +106,6 @@ class SelfdriveD(CruiseHelper): self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled") self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") - self.custom_button_mapping = self.params.get("SteeringCustomButtonMapping") - car_recognized = self.CP.brand != 'mock' # cleanup old params @@ -559,15 +555,6 @@ class SelfdriveD(CruiseHelper): self.pm.send('onroadEventsSP', ce_send_sp) self.events_sp_prev = self.events_sp.names.copy() - # custom button handling for bookmark - custom_pressed = any(be.type == ButtonTypeSP.customButton for be in CS_SP.buttonEvents) - if custom_pressed: - # 0 = Off - # 1 = bookmark - if self.custom_button_mapping == 1: - uf_msg = messaging.new_message('userFlag', valid=True) - self.pm.send('userFlag', uf_msg) - def step(self): CS, CS_SP = self.data_sample() self.update_events(CS) @@ -589,7 +576,6 @@ class SelfdriveD(CruiseHelper): self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl self.personality = self.params.get("LongitudinalPersonality", return_default=True) - self.custom_button_mapping = self.params.get("SteeringCustomButtonMapping") self.mads.read_params() time.sleep(0.1) diff --git a/selfdrive/ui/feedback/feedbackd.py b/selfdrive/ui/feedback/feedbackd.py index e814106304..df8cad26fc 100755 --- a/selfdrive/ui/feedback/feedbackd.py +++ b/selfdrive/ui/feedback/feedbackd.py @@ -2,47 +2,67 @@ import cereal.messaging as messaging from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog -from cereal import car +from cereal import car, custom from openpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER FEEDBACK_MAX_DURATION = 10.0 ButtonType = car.CarState.ButtonEvent.Type +ButtonTypeSP = custom.CarStateSP.ButtonEvent.Type + +CUSTOM_MAPPING_BOOKMARK = 1 # Custom button mapping value for bookmark action def main(): params = Params() pm = messaging.PubMaster(['userBookmark', 'audioFeedback']) - sm = messaging.SubMaster(['rawAudioData', 'bookmarkButton', 'carState', 'selfdriveStateSP']) + sm = messaging.SubMaster(['rawAudioData', 'bookmarkButton', 'carState', 'selfdriveStateSP', 'carStateSP']) should_record_audio = False block_num = 0 waiting_for_release = False early_stop_triggered = False + custom_button_mapping = params.get("SteeringCustomButtonMapping") while True: sm.update() - should_send_bookmark = False - # only allow the LKAS button to record feedback when MADS is disabled - if sm.updated['carState'] and sm['carState'].canValid and not sm['selfdriveStateSP'].mads.available: + if sm.frame % 60 == 0: # update params once every 60 frames + custom_button_mapping = params.get("SteeringCustomButtonMapping") + + custom_mapped = custom_button_mapping == CUSTOM_MAPPING_BOOKMARK + should_send_bookmark = False + btn_pressed = False + + # use custom button mapping if available + use_custom = custom_mapped and sm.updated['carStateSP'] + # only allow the LKAS button to record feedback when MADS is disabled & custom button mapping is not set + use_lkas = sm.updated['carState'] and sm['carState'].canValid and not sm['selfdriveStateSP'].mads.available and not custom_mapped + + if use_custom: + for be in sm['carStateSP'].buttonEvents: + if be.type == ButtonTypeSP.customButton: + btn_pressed = be.pressed + elif use_lkas: for be in sm['carState'].buttonEvents: if be.type == ButtonType.lkas: - if be.pressed: - if not should_record_audio: - if params.get_bool("RecordAudioFeedback"): # Start recording on first press if toggle set - should_record_audio = True - block_num = 0 - waiting_for_release = False - early_stop_triggered = False - cloudlog.info("LKAS button pressed - starting 10-second audio feedback") - else: - should_send_bookmark = True # immediately send bookmark if toggle false - cloudlog.info("LKAS button pressed - bookmarking") - elif should_record_audio and not waiting_for_release: # Wait for release of second press to stop recording early - waiting_for_release = True - elif waiting_for_release: # Second press released - waiting_for_release = False - early_stop_triggered = True - cloudlog.info("LKAS button released - ending recording early") + btn_pressed = be.pressed + + if btn_pressed: + if not should_record_audio: + if params.get_bool("RecordAudioFeedback"): # Start recording on first press if toggle set + should_record_audio = True + block_num = 0 + waiting_for_release = False + early_stop_triggered = False + cloudlog.info(f"{'LKAS' if use_lkas else 'CUSTOM'} button pressed - starting 10-second audio feedback") + else: + should_send_bookmark = True # immediately send bookmark if toggle false + cloudlog.info(f"{'LKAS' if use_lkas else 'CUSTOM'} button pressed - bookmarking") + elif should_record_audio and not waiting_for_release: # Wait for release of second press to stop recording early + waiting_for_release = True + elif waiting_for_release: # Second press released + waiting_for_release = False + early_stop_triggered = True + cloudlog.info(f"{'LKAS' if use_lkas else 'CUSTOM'} button released - ending recording early") if should_record_audio and sm.updated['rawAudioData']: raw_audio = sm['rawAudioData'] From cea00a6c14920ab4adffdd425e122ea711489212 Mon Sep 17 00:00:00 2001 From: nayan Date: Tue, 12 Aug 2025 12:00:53 -0400 Subject: [PATCH 04/37] todo --- selfdrive/ui/feedback/feedbackd.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/ui/feedback/feedbackd.py b/selfdrive/ui/feedback/feedbackd.py index df8cad26fc..90f77ad3aa 100755 --- a/selfdrive/ui/feedback/feedbackd.py +++ b/selfdrive/ui/feedback/feedbackd.py @@ -9,6 +9,7 @@ FEEDBACK_MAX_DURATION = 10.0 ButtonType = car.CarState.ButtonEvent.Type ButtonTypeSP = custom.CarStateSP.ButtonEvent.Type +# TODO-SP: Use common python enum when we move to raylib? CUSTOM_MAPPING_BOOKMARK = 1 # Custom button mapping value for bookmark action From f23cc408e35f18a07cabaaa1b932c91ab36d0953 Mon Sep 17 00:00:00 2001 From: nayan Date: Tue, 12 Aug 2025 12:48:49 -0400 Subject: [PATCH 05/37] not needed anymore --- selfdrive/selfdrived/selfdrived.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index b7d4bab332..342c78b8d3 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -85,7 +85,6 @@ class SelfdriveD(CruiseHelper): # TODO: de-couple selfdrived with card/conflate on carState without introducing controls mismatches self.car_state_sock = messaging.sub_sock('carState', timeout=20) - self.car_state_sp_sock = messaging.sub_sock('carStateSP', timeout=20) ignore = self.sensor_packets + self.gps_packets + ['alertDebug'] if SIMULATION: @@ -115,7 +114,6 @@ class SelfdriveD(CruiseHelper): self.params.remove("ExperimentalMode") self.CS_prev = car.CarState.new_message() - self.CS_SP_prev = custom.CarStateSP.new_message() self.AM = AlertManager() self.events = Events() @@ -441,9 +439,7 @@ class SelfdriveD(CruiseHelper): def data_sample(self): _car_state = messaging.recv_one(self.car_state_sock) - _car_state_sp = messaging.recv_one(self.car_state_sp_sock) CS = _car_state.carState if _car_state else self.CS_prev - CS_SP = _car_state_sp.carStateSP if _car_state_sp else self.CS_SP_prev self.sm.update(0) @@ -486,7 +482,7 @@ class SelfdriveD(CruiseHelper): if ps.safetyModel not in IGNORED_SAFETY_MODES): self.mismatch_counter += 1 - return CS, CS_SP + return CS def update_alerts(self, CS): clear_event_types = set() @@ -505,7 +501,7 @@ class SelfdriveD(CruiseHelper): self.AM.add_many(self.sm.frame, alerts + alerts_sp) self.AM.process_alerts(self.sm.frame, clear_event_types) - def publish_selfdriveState(self, CS, CS_SP): + def publish_selfdriveState(self, CS): # selfdriveState ss_msg = messaging.new_message('selfdriveState') ss_msg.valid = True @@ -556,7 +552,7 @@ class SelfdriveD(CruiseHelper): self.events_sp_prev = self.events_sp.names.copy() def step(self): - CS, CS_SP = self.data_sample() + CS = self.data_sample() self.update_events(CS) if not self.CP.passive and self.initialized: self.enabled, self.active = self.state_machine.update(self.events) @@ -564,10 +560,9 @@ class SelfdriveD(CruiseHelper): self.mads.update(CS) self.update_alerts(CS) - self.publish_selfdriveState(CS, CS_SP) + self.publish_selfdriveState(CS) self.CS_prev = CS - self.CS_SP_prev = CS_SP def params_thread(self, evt): while not evt.is_set(): From 9685b0aa9bbf26d86a50f02067705d7321da62d1 Mon Sep 17 00:00:00 2001 From: nayan Date: Tue, 12 Aug 2025 12:49:17 -0400 Subject: [PATCH 06/37] lint --- selfdrive/ui/feedback/feedbackd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/feedback/feedbackd.py b/selfdrive/ui/feedback/feedbackd.py index 90f77ad3aa..826baba297 100755 --- a/selfdrive/ui/feedback/feedbackd.py +++ b/selfdrive/ui/feedback/feedbackd.py @@ -46,7 +46,7 @@ def main(): for be in sm['carState'].buttonEvents: if be.type == ButtonType.lkas: btn_pressed = be.pressed - + if btn_pressed: if not should_record_audio: if params.get_bool("RecordAudioFeedback"): # Start recording on first press if toggle set From dc86f3595778831186aeae40d92ba5d42e2fa127 Mon Sep 17 00:00:00 2001 From: nayan Date: Tue, 12 Aug 2025 13:29:06 -0400 Subject: [PATCH 07/37] refactor --- selfdrive/ui/feedback/feedbackd.py | 58 +++++++++++++++++------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/selfdrive/ui/feedback/feedbackd.py b/selfdrive/ui/feedback/feedbackd.py index 826baba297..3d36755595 100755 --- a/selfdrive/ui/feedback/feedbackd.py +++ b/selfdrive/ui/feedback/feedbackd.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import cereal.messaging as messaging +from enum import Enum from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog from cereal import car, custom @@ -12,6 +13,11 @@ ButtonTypeSP = custom.CarStateSP.ButtonEvent.Type # TODO-SP: Use common python enum when we move to raylib? CUSTOM_MAPPING_BOOKMARK = 1 # Custom button mapping value for bookmark action +class ButtonPressType(Enum): + NONE = 'NONE' + LKAS = 'LKAS' + CUSTOM = 'CUSTOM' + def main(): params = Params() @@ -21,49 +27,29 @@ def main(): block_num = 0 waiting_for_release = False early_stop_triggered = False - custom_button_mapping = params.get("SteeringCustomButtonMapping") while True: sm.update() - - if sm.frame % 60 == 0: # update params once every 60 frames - custom_button_mapping = params.get("SteeringCustomButtonMapping") - - custom_mapped = custom_button_mapping == CUSTOM_MAPPING_BOOKMARK should_send_bookmark = False - btn_pressed = False + btn_pressed = get_button_event(sm, params) - # use custom button mapping if available - use_custom = custom_mapped and sm.updated['carStateSP'] - # only allow the LKAS button to record feedback when MADS is disabled & custom button mapping is not set - use_lkas = sm.updated['carState'] and sm['carState'].canValid and not sm['selfdriveStateSP'].mads.available and not custom_mapped - - if use_custom: - for be in sm['carStateSP'].buttonEvents: - if be.type == ButtonTypeSP.customButton: - btn_pressed = be.pressed - elif use_lkas: - for be in sm['carState'].buttonEvents: - if be.type == ButtonType.lkas: - btn_pressed = be.pressed - - if btn_pressed: + if btn_pressed is not ButtonPressType.NONE: if not should_record_audio: if params.get_bool("RecordAudioFeedback"): # Start recording on first press if toggle set should_record_audio = True block_num = 0 waiting_for_release = False early_stop_triggered = False - cloudlog.info(f"{'LKAS' if use_lkas else 'CUSTOM'} button pressed - starting 10-second audio feedback") + cloudlog.info(f"{btn_pressed.value} button pressed - starting 10-second audio feedback") else: should_send_bookmark = True # immediately send bookmark if toggle false - cloudlog.info(f"{'LKAS' if use_lkas else 'CUSTOM'} button pressed - bookmarking") + cloudlog.info(f"{btn_pressed.value} button pressed - bookmarking") elif should_record_audio and not waiting_for_release: # Wait for release of second press to stop recording early waiting_for_release = True elif waiting_for_release: # Second press released waiting_for_release = False early_stop_triggered = True - cloudlog.info(f"{'LKAS' if use_lkas else 'CUSTOM'} button released - ending recording early") + cloudlog.info(f"{btn_pressed.value} button released - ending recording early") if should_record_audio and sm.updated['rawAudioData']: raw_audio = sm['rawAudioData'] @@ -88,5 +74,27 @@ def main(): pm.send('userBookmark', msg) +def get_button_event(sm, params): + + custom_button_mapping = params.get("SteeringCustomButtonMapping") + custom_mapped = custom_button_mapping == CUSTOM_MAPPING_BOOKMARK + btn_pressed = ButtonPressType.NONE + + # use custom button mapping if available + use_custom = custom_mapped and sm.updated['carStateSP'] + # only allow the LKAS button to record feedback when MADS is disabled & custom button mapping is not set + use_lkas = sm.updated['carState'] and sm['carState'].canValid and not sm['selfdriveStateSP'].mads.available and not custom_mapped + + if use_custom: + for be in sm['carStateSP'].buttonEvents: + if be.type == ButtonTypeSP.customButton: + btn_pressed = ButtonPressType.CUSTOM if be.pressed else ButtonPressType.NONE + elif use_lkas: + for be in sm['carState'].buttonEvents: + if be.type == ButtonType.lkas: + btn_pressed = ButtonPressType.LKAS if be.pressed else ButtonPressType.NONE + + return btn_pressed + if __name__ == '__main__': main() From 0c7d7df2ec3f490c1e916b97a062c1de84abcac7 Mon Sep 17 00:00:00 2001 From: nayan Date: Tue, 12 Aug 2025 14:40:11 -0400 Subject: [PATCH 08/37] keep both --- selfdrive/ui/feedback/feedbackd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/feedback/feedbackd.py b/selfdrive/ui/feedback/feedbackd.py index 3d36755595..078170ac16 100755 --- a/selfdrive/ui/feedback/feedbackd.py +++ b/selfdrive/ui/feedback/feedbackd.py @@ -83,13 +83,13 @@ def get_button_event(sm, params): # use custom button mapping if available use_custom = custom_mapped and sm.updated['carStateSP'] # only allow the LKAS button to record feedback when MADS is disabled & custom button mapping is not set - use_lkas = sm.updated['carState'] and sm['carState'].canValid and not sm['selfdriveStateSP'].mads.available and not custom_mapped + use_lkas = sm.updated['carState'] and sm['carState'].canValid and not sm['selfdriveStateSP'].mads.available if use_custom: for be in sm['carStateSP'].buttonEvents: if be.type == ButtonTypeSP.customButton: btn_pressed = ButtonPressType.CUSTOM if be.pressed else ButtonPressType.NONE - elif use_lkas: + if use_lkas: for be in sm['carState'].buttonEvents: if be.type == ButtonType.lkas: btn_pressed = ButtonPressType.LKAS if be.pressed else ButtonPressType.NONE From a0eed058d13498596c06334a15c129a04c2ceeac Mon Sep 17 00:00:00 2001 From: nayan Date: Wed, 13 Aug 2025 17:28:11 -0400 Subject: [PATCH 09/37] bump opendbc --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index f1e871beeb..2ee4f19c2a 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit f1e871beeb5257ceb31470851f5906f8cd7b0bdb +Subproject commit 2ee4f19c2a340bb19993ab49449325af0ac6a467 From d9a690dac3be6a1c0f244e9353dac3e61b36f7f2 Mon Sep 17 00:00:00 2001 From: royjr Date: Fri, 22 Aug 2025 10:30:28 -0400 Subject: [PATCH 10/37] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 2ee4f19c2a..72b0179e05 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 2ee4f19c2a340bb19993ab49449325af0ac6a467 +Subproject commit 72b0179e054181e833bd550cecb77db7e2c75c47 From f1e11e3f065fd26fed0881d0b63607232c388ac5 Mon Sep 17 00:00:00 2001 From: nayan Date: Sat, 23 Aug 2025 22:00:50 -0400 Subject: [PATCH 11/37] bump opendbc --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 72b0179e05..c3e42debb6 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 72b0179e054181e833bd550cecb77db7e2c75c47 +Subproject commit c3e42debb643df97fd91c7be7cf57244ad70fed8 From 33324e590b7fda899132961724fb2520e08b340a Mon Sep 17 00:00:00 2001 From: nayan Date: Sat, 23 Aug 2025 22:24:39 -0400 Subject: [PATCH 12/37] use altButton2 event --- cereal/custom.capnp | 11 ----------- opendbc_repo | 2 +- selfdrive/ui/feedback/feedbackd.py | 17 +++++++---------- 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index cfd20a398f..fdb89da84c 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -247,17 +247,6 @@ struct BackupManagerSP @0xf98d843bfd7004a3 { } struct CarStateSP @0xb86e6369214c01c8 { - buttonEvents @0 :List(ButtonEvent); - - struct ButtonEvent { - pressed @0 :Bool; - type @1 :Type; - - enum Type { - unknown @0; - customButton @1; - } - } } struct LiveMapDataSP @0xf416ec09499d9d19 { diff --git a/opendbc_repo b/opendbc_repo index c3e42debb6..530c7ea12f 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit c3e42debb643df97fd91c7be7cf57244ad70fed8 +Subproject commit 530c7ea12f4f94a07c170f32e4019b66a40f5726 diff --git a/selfdrive/ui/feedback/feedbackd.py b/selfdrive/ui/feedback/feedbackd.py index 078170ac16..959d65faed 100755 --- a/selfdrive/ui/feedback/feedbackd.py +++ b/selfdrive/ui/feedback/feedbackd.py @@ -3,12 +3,11 @@ import cereal.messaging as messaging from enum import Enum from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog -from cereal import car, custom +from cereal import car from openpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER FEEDBACK_MAX_DURATION = 10.0 ButtonType = car.CarState.ButtonEvent.Type -ButtonTypeSP = custom.CarStateSP.ButtonEvent.Type # TODO-SP: Use common python enum when we move to raylib? CUSTOM_MAPPING_BOOKMARK = 1 # Custom button mapping value for bookmark action @@ -22,7 +21,7 @@ class ButtonPressType(Enum): def main(): params = Params() pm = messaging.PubMaster(['userBookmark', 'audioFeedback']) - sm = messaging.SubMaster(['rawAudioData', 'bookmarkButton', 'carState', 'selfdriveStateSP', 'carStateSP']) + sm = messaging.SubMaster(['rawAudioData', 'bookmarkButton', 'carState', 'selfdriveStateSP']) should_record_audio = False block_num = 0 waiting_for_release = False @@ -81,17 +80,15 @@ def get_button_event(sm, params): btn_pressed = ButtonPressType.NONE # use custom button mapping if available - use_custom = custom_mapped and sm.updated['carStateSP'] + use_custom = custom_mapped and sm.updated['carState'] # only allow the LKAS button to record feedback when MADS is disabled & custom button mapping is not set use_lkas = sm.updated['carState'] and sm['carState'].canValid and not sm['selfdriveStateSP'].mads.available - if use_custom: - for be in sm['carStateSP'].buttonEvents: - if be.type == ButtonTypeSP.customButton: - btn_pressed = ButtonPressType.CUSTOM if be.pressed else ButtonPressType.NONE - if use_lkas: + if use_custom or use_lkas: for be in sm['carState'].buttonEvents: - if be.type == ButtonType.lkas: + if be.type == ButtonType.altButton2: + btn_pressed = ButtonPressType.CUSTOM if be.pressed else ButtonPressType.NONE + elif be.type == ButtonType.lkas: btn_pressed = ButtonPressType.LKAS if be.pressed else ButtonPressType.NONE return btn_pressed From d251ae4976e8dd16f4d2d1144d2a0a7aadac9605 Mon Sep 17 00:00:00 2001 From: nayan Date: Sun, 24 Aug 2025 16:40:48 -0400 Subject: [PATCH 13/37] pr be messing with pr --- .../sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.cc index 74483ffc41..e342252cb8 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.cc @@ -56,7 +56,7 @@ void HyundaiSettings::updateSettings() { cereal::CarParamsSP::Reader CP_SP = cmsg.getRoot(); // TODO-SP: Better way to get the flag value in qt? - has_custom_button = CP_SP.getFlags() & 64; + has_custom_button = CP_SP.getFlags() & 512; // 512 = HAS_CUSTOM_BUTTON (2 ** 9) } else { has_custom_button = false; } From 95ad932efbcdc4f5e578f30d47cbfe6d0638116d Mon Sep 17 00:00:00 2001 From: nayan Date: Mon, 25 Aug 2025 09:15:20 -0400 Subject: [PATCH 14/37] disable lkas per upstream & fix mixed events --- selfdrive/ui/feedback/feedbackd.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/feedback/feedbackd.py b/selfdrive/ui/feedback/feedbackd.py index 959d65faed..3018fbdb5c 100755 --- a/selfdrive/ui/feedback/feedbackd.py +++ b/selfdrive/ui/feedback/feedbackd.py @@ -82,13 +82,14 @@ def get_button_event(sm, params): # use custom button mapping if available use_custom = custom_mapped and sm.updated['carState'] # only allow the LKAS button to record feedback when MADS is disabled & custom button mapping is not set - use_lkas = sm.updated['carState'] and sm['carState'].canValid and not sm['selfdriveStateSP'].mads.available + # TODO: https://github.com/commaai/openpilot/issues/36015 + use_lkas = False #sm.updated['carState'] and sm['carState'].canValid and not sm['selfdriveStateSP'].mads.available if use_custom or use_lkas: for be in sm['carState'].buttonEvents: - if be.type == ButtonType.altButton2: + if use_custom and be.type == ButtonType.altButton2: btn_pressed = ButtonPressType.CUSTOM if be.pressed else ButtonPressType.NONE - elif be.type == ButtonType.lkas: + elif use_lkas and be.type == ButtonType.lkas: btn_pressed = ButtonPressType.LKAS if be.pressed else ButtonPressType.NONE return btn_pressed From 132cc156f40ccc914db111ab986e228b2fa8dc51 Mon Sep 17 00:00:00 2001 From: royjr Date: Wed, 3 Sep 2025 09:57:27 -0400 Subject: [PATCH 15/37] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index e4b2dba198..746a754108 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit e4b2dba1985b88821c83ac425610f6929fbe7627 +Subproject commit 746a7541081982d8585645c5a677c248223da1f0 From 40f24cc0b68213a0454d71e8396cf9c177a8f762 Mon Sep 17 00:00:00 2001 From: royjr Date: Wed, 3 Sep 2025 10:08:01 -0400 Subject: [PATCH 16/37] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 746a754108..f403762b0e 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 746a7541081982d8585645c5a677c248223da1f0 +Subproject commit f403762b0e5464dd2a460256561e883dcb14888b From bd0578cbf99b42428ecae63f4277c2202d8df3be Mon Sep 17 00:00:00 2001 From: royjr Date: Wed, 8 Oct 2025 17:17:28 -0400 Subject: [PATCH 17/37] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index f403762b0e..d127f2432b 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit f403762b0e5464dd2a460256561e883dcb14888b +Subproject commit d127f2432bc8f03ef8c462d705dad936135e2d7e From 2ecc26b623e228f1b82c2b87be3d62b6482711d7 Mon Sep 17 00:00:00 2001 From: royjr Date: Wed, 8 Oct 2025 18:27:19 -0400 Subject: [PATCH 18/37] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index d127f2432b..ce8381fd9d 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit d127f2432bc8f03ef8c462d705dad936135e2d7e +Subproject commit ce8381fd9dde0491fc8613789fd607e1ff537875 From 43522c49223a9815df3d25575ad99e4fecfa3ef6 Mon Sep 17 00:00:00 2001 From: royjr Date: Wed, 4 Feb 2026 19:14:28 -0500 Subject: [PATCH 19/37] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index ce8381fd9d..052ef84aff 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit ce8381fd9dde0491fc8613789fd607e1ff537875 +Subproject commit 052ef84aff144eddf7c26cbb287bf181c40ce132 From cf2010389a6e6422c6e1807fba429bee3e7f4729 Mon Sep 17 00:00:00 2001 From: royjr Date: Wed, 4 Feb 2026 19:14:43 -0500 Subject: [PATCH 20/37] Revert "Update opendbc_repo" This reverts commit 43522c49223a9815df3d25575ad99e4fecfa3ef6. --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 052ef84aff..ce8381fd9d 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 052ef84aff144eddf7c26cbb287bf181c40ce132 +Subproject commit ce8381fd9dde0491fc8613789fd607e1ff537875 From ba36a28f2a5dfc5b3f9c6be359317f529264a564 Mon Sep 17 00:00:00 2001 From: royjr Date: Wed, 4 Feb 2026 21:43:53 -0500 Subject: [PATCH 21/37] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index ce8381fd9d..dd80a5ced7 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit ce8381fd9dde0491fc8613789fd607e1ff537875 +Subproject commit dd80a5ced70662e9b8b182d12cddf2fa09cafa62 From e1cfea49246e56465ae8466be4426d90705064aa Mon Sep 17 00:00:00 2001 From: royjr Date: Wed, 4 Feb 2026 22:04:29 -0500 Subject: [PATCH 22/37] Update RELEASES.md --- RELEASES.md | 1 - 1 file changed, 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 243547f8bd..6191c6ba3d 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -39,7 +39,6 @@ Version 0.10.0 (2025-08-05) * Action from lateral MPC as training objective replaced by E2E planning from World Model * Low-speed lead car ground-truth fixes * Enable live-learned steering actuation delay -* Allow Bookmark/Record driving feedback using Custom ☆ button for Hyundai/Kia/Genesis vehicles * Opt-in audio recording for dashcam video * Acura MDX 2025 support thanks to vanillagorillaa and MVL! * Honda Accord 2023-25 support thanks to vanillagorillaa and MVL! From ceb466578af78e1cee458517b558b9143efab6a4 Mon Sep 17 00:00:00 2001 From: royjr Date: Wed, 4 Feb 2026 22:07:19 -0500 Subject: [PATCH 23/37] Update params_metadata.json --- sunnypilot/sunnylink/params_metadata.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index 7f597a9621..7250196e76 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -1199,6 +1199,20 @@ "title": "Standstill Timer", "description": "" }, + "SteeringCustomButtonMapping": { + "title": "SteeringCustomButtonMapping", + "description": "Customize the steering wheel custom/star button for openpilot control.", + "options": [ + { + "value": 0, + "label": "Off" + }, + { + "value": 1, + "label": "Bookmark" + } + ] + }, "SubaruStopAndGo": { "title": "Subaru Stop and Go", "description": "" From b9020e00030dc165d9fa8737d00d8c2bc12e3ba3 Mon Sep 17 00:00:00 2001 From: royjr Date: Wed, 4 Feb 2026 22:07:48 -0500 Subject: [PATCH 24/37] Update params_metadata.json --- sunnypilot/sunnylink/params_metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index 7250196e76..adc708f8ff 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -1200,7 +1200,7 @@ "description": "" }, "SteeringCustomButtonMapping": { - "title": "SteeringCustomButtonMapping", + "title": "Steering Custom Button Mapping", "description": "Customize the steering wheel custom/star button for openpilot control.", "options": [ { From 2321f9d8f51f512a1ee21b63fd9a429e960d93c0 Mon Sep 17 00:00:00 2001 From: royjr Date: Fri, 13 Feb 2026 23:02:06 -0500 Subject: [PATCH 25/37] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index dd80a5ced7..bb1471dd99 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit dd80a5ced70662e9b8b182d12cddf2fa09cafa62 +Subproject commit bb1471dd99a66e03f4f4b78b2ddac0629b982bcb From 5b58f9c1f5d6720f621d583901387ef9a5533d8c Mon Sep 17 00:00:00 2001 From: royjr Date: Fri, 13 Feb 2026 23:34:16 -0500 Subject: [PATCH 26/37] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index bb1471dd99..ef7fe65545 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit bb1471dd99a66e03f4f4b78b2ddac0629b982bcb +Subproject commit ef7fe6554504830b2aa4e1c9d622ed9251dc4478 From cce4810904fe2c96e322f368e049528cfabdb009 Mon Sep 17 00:00:00 2001 From: royjr Date: Mon, 16 Feb 2026 22:43:09 -0500 Subject: [PATCH 27/37] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index ef7fe65545..a9e627dfb1 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit ef7fe6554504830b2aa4e1c9d622ed9251dc4478 +Subproject commit a9e627dfb129f0f94168bd9ea9ba970970166f7e From 73fc740831e3ee8efad459de8311fda233c5ebef Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 15 Aug 2026 19:34:50 -0400 Subject: [PATCH 28/37] [TIZI/TICI] ui: fix developer UI crash on renamed field (#1910) ui: fix developer UI crash on renamed lateralTorqueParameters valid field --- .../selfdrive/ui/sunnypilot/onroad/developer_ui/elements.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpilot/selfdrive/ui/sunnypilot/onroad/developer_ui/elements.py b/openpilot/selfdrive/ui/sunnypilot/onroad/developer_ui/elements.py index f89edef48b..a8ecb5f8ab 100644 --- a/openpilot/selfdrive/ui/sunnypilot/onroad/developer_ui/elements.py +++ b/openpilot/selfdrive/ui/sunnypilot/onroad/developer_ui/elements.py @@ -252,7 +252,7 @@ class FrictionCoefficientElement: ltp = sm['lateralTorqueParameters'] value = f"{ltp.frictionCoefficientFiltered:.3f}" - color = rl.Color(0, 255, 0, 255) if ltp.liveValid else rl.WHITE + color = rl.Color(0, 255, 0, 255) if ltp.valid else rl.WHITE return UiElement(value, "FRIC.", self.unit, color) @@ -266,7 +266,7 @@ class LatAccelFactorElement: ltp = sm['lateralTorqueParameters'] value = f"{ltp.latAccelFactorFiltered:.3f}" - color = rl.Color(0, 255, 0, 255) if ltp.liveValid else rl.WHITE + color = rl.Color(0, 255, 0, 255) if ltp.valid else rl.WHITE return UiElement(value, "L.A.F.", self.unit, color) From 91d0f3309c8b0d470b5a6faf9af744cdb42301ff Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 15 Aug 2026 19:56:19 -0400 Subject: [PATCH 29/37] DEC: restore gate on longitudinal E2E output (#1911) dec: restore Dynamic Experimental Control gate on longitudinal e2e output --- .../controls/lib/longitudinal_planner.py | 13 +- .../lib/dec/tests/test_dec_planner_gate.py | 112 ++++++++++++++++++ 2 files changed, 115 insertions(+), 10 deletions(-) create mode 100644 openpilot/sunnypilot/selfdrive/controls/lib/dec/tests/test_dec_planner_gate.py diff --git a/openpilot/selfdrive/controls/lib/longitudinal_planner.py b/openpilot/selfdrive/controls/lib/longitudinal_planner.py index 12b4f9da61..8b62808dc0 100755 --- a/openpilot/selfdrive/controls/lib/longitudinal_planner.py +++ b/openpilot/selfdrive/controls/lib/longitudinal_planner.py @@ -139,23 +139,16 @@ class LongitudinalPlanner(LongitudinalPlannerSP): output_a_target_e2e = sm['modelV2'].action.desiredAcceleration output_should_stop_e2e = sm['modelV2'].action.shouldStop - if self.is_e2e(sm): - output_a_target = min(output_a_target_e2e, output_a_target_mpc) - self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc - if output_a_target < output_a_target_mpc: - self.mpc.source = LongitudinalPlanSource.e2e - else: - output_a_target = output_a_target_mpc - self.output_should_stop = output_should_stop_mpc + is_e2e = self.is_e2e(sm) - self.a_cruise = get_cruise_accel(sm['selfdriveState'].experimentalMode, v_cruise, v_ego, + self.a_cruise = get_cruise_accel(is_e2e, v_cruise, v_ego, self.a_cruise, steer_angle_without_offset, self.CP, self.dt, accel_coast, self.allow_throttle) cruise_should_stop = should_stop(v_ego, self.a_cruise) candidates = [(output_a_target_mpc, self.mpc.source, output_should_stop_mpc), (self.a_cruise, LongitudinalPlanSource.cruise, cruise_should_stop)] - if sm['selfdriveState'].experimentalMode: + if is_e2e: candidates.append((output_a_target_e2e, LongitudinalPlanSource.e2e, output_should_stop_e2e)) output_a_target, self.mpc.source, _ = min(candidates, key=lambda c: c[0]) diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/dec/tests/test_dec_planner_gate.py b/openpilot/sunnypilot/selfdrive/controls/lib/dec/tests/test_dec_planner_gate.py new file mode 100644 index 0000000000..1f5c577028 --- /dev/null +++ b/openpilot/sunnypilot/selfdrive/controls/lib/dec/tests/test_dec_planner_gate.py @@ -0,0 +1,112 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from typing import cast + +from openpilot.cereal import custom, messaging +from opendbc.car import structs +from openpilot.common.test import OpenpilotTestCase +from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner, LongitudinalPlanSource +from openpilot.sunnypilot.selfdrive.controls.lib.dec.dec import DynamicExperimentalController + +V_EGO = 20.0 +E2E_ACCEL = -3.0 # low enough that e2e wins the min() whenever it is a candidate + + +class MockDec: + def __init__(self, active: bool, mode: str): + self._active = active + self._mode = mode + + def update(self, sm): + pass + + def active(self) -> bool: + return self._active + + def mode(self) -> str: + return self._mode + + def enabled(self) -> bool: + return True + + +class MockSubMaster(dict): + def __init__(self, services: dict): + super().__init__(services) + self.valid = dict.fromkeys(services, True) + self.logMonoTime = dict.fromkeys(services, 0) + self.updated = dict.fromkeys(services, True) + self.recv_frame = dict.fromkeys(services, 1) + + def all_checks(self, service_list=None) -> bool: + return True + + +def build_sm(experimental_mode: bool) -> MockSubMaster: + services = {} + for service in ("radarState", "controlsState", "vehicleParameters", "carStateSP", + "liveMapDataSP", "gpsLocationExternal", "gpsLocation"): + services[service] = getattr(messaging.new_message(service), service) + + car_state = messaging.new_message('carState') + car_state.carState.vEgo = V_EGO + car_state.carState.vCruise = 100.0 + car_state.carState.vCruiseCluster = 100.0 + services['carState'] = car_state.carState.as_reader() + + selfdrive_state = messaging.new_message('selfdriveState') + selfdrive_state.selfdriveState.experimentalMode = experimental_mode + selfdrive_state.selfdriveState.enabled = True + services['selfdriveState'] = selfdrive_state.selfdriveState.as_reader() + + car_control = messaging.new_message('carControl') + car_control.carControl.enabled = True + services['carControl'] = car_control.carControl.as_reader() + + model = messaging.new_message('modelV2') + model.modelV2.orientationRate.z = [0.01] * 33 # nonzero: a straight path divides by zero in SCC vision + model.modelV2.velocity.x = [V_EGO] * 33 + model.modelV2.position.x = [float(i) for i in range(33)] + model.modelV2.action.desiredAcceleration = E2E_ACCEL + services['modelV2'] = model.modelV2.as_reader() + + return MockSubMaster(services) + + +def build_planner(dec_active: bool, dec_mode: str) -> LongitudinalPlanner: + CP = structs.CarParams() + CP.steerRatio = 15.0 + CP.wheelbase = 2.7 + CP.longitudinalActuatorDelay = 0.2 + CP_SP = custom.CarParamsSP.new_message().as_reader() + + planner = LongitudinalPlanner(CP, CP_SP, init_v=V_EGO) + planner.dec = cast(DynamicExperimentalController, MockDec(dec_active, dec_mode)) + return planner + + +class TestDecPlannerGate(OpenpilotTestCase): + """The e2e candidate must be gated on is_e2e(), not raw experimentalMode.""" + + def _source(self, experimental_mode: bool, dec_active: bool, dec_mode: str) -> LongitudinalPlanSource: + planner = build_planner(dec_active, dec_mode) + planner.update(build_sm(experimental_mode)) + return planner.mpc.source + + def test_no_e2e_when_experimental_mode_off(self): + assert self._source(False, False, 'acc') != LongitudinalPlanSource.e2e + + def test_e2e_when_dec_inactive(self): + # DEC off: behavior must match upstream + assert self._source(True, False, 'acc') == LongitudinalPlanSource.e2e + + def test_e2e_when_dec_blended(self): + assert self._source(True, True, 'blended') == LongitudinalPlanSource.e2e + + def test_no_e2e_when_dec_holds_acc(self): + # the regression + assert self._source(True, True, 'acc') != LongitudinalPlanSource.e2e From 138353adb4ab096da6930db4481f7cbafe66522a Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 16 Aug 2026 15:39:52 -0400 Subject: [PATCH 30/37] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index a9e627dfb1..b90e239f2f 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit a9e627dfb129f0f94168bd9ea9ba970970166f7e +Subproject commit b90e239f2f37492ab9809393552fa27a2a3d905f From 1a75c53ea4fa1b743b6d268c8c181efb35c19c28 Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 16 Aug 2026 16:18:09 -0400 Subject: [PATCH 31/37] ui: handle custom steering button on comma 4 --- openpilot/selfdrive/ui/custom_button.py | 25 +++++++++++++++++++ openpilot/selfdrive/ui/layouts/main.py | 25 ++----------------- openpilot/selfdrive/ui/mici/layouts/main.py | 3 +++ .../selfdrive/ui/tests/test_custom_button.py | 19 ++++++-------- 4 files changed, 38 insertions(+), 34 deletions(-) create mode 100644 openpilot/selfdrive/ui/custom_button.py diff --git a/openpilot/selfdrive/ui/custom_button.py b/openpilot/selfdrive/ui/custom_button.py new file mode 100644 index 0000000000..20aadff5b1 --- /dev/null +++ b/openpilot/selfdrive/ui/custom_button.py @@ -0,0 +1,25 @@ +from enum import IntEnum + +from opendbc.car.structs import car + + +class CustomButtonAction(IntEnum): + NONE = 0 + BOOKMARK = 1 + QUIET_MODE = 2 + + +def handle_custom_button(sm, params, bookmark_callback): + if not sm.updated['carState']: + return + + custom_pressed = any(be.type == car.CarState.ButtonEvent.Type.altButton2 and be.pressed + for be in sm['carState'].buttonEvents) + if not custom_pressed: + return + + action = CustomButtonAction(params.get('SteeringCustomButtonMapping', return_default=True)) + if action == CustomButtonAction.BOOKMARK: + bookmark_callback() + elif action == CustomButtonAction.QUIET_MODE: + params.put_bool('QuietMode', not params.get_bool('QuietMode')) diff --git a/openpilot/selfdrive/ui/layouts/main.py b/openpilot/selfdrive/ui/layouts/main.py index 5b2b9bc9f5..b56c8136c2 100644 --- a/openpilot/selfdrive/ui/layouts/main.py +++ b/openpilot/selfdrive/ui/layouts/main.py @@ -1,9 +1,9 @@ import pyray as rl from enum import IntEnum import openpilot.cereal.messaging as messaging -from opendbc.car.structs import car from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import Widget +from openpilot.selfdrive.ui.custom_button import handle_custom_button from openpilot.selfdrive.ui.layouts.sidebar import Sidebar, SIDEBAR_WIDTH from openpilot.selfdrive.ui.layouts.home import HomeLayout from openpilot.selfdrive.ui.layouts.settings.settings import SettingsLayout, PanelType @@ -23,12 +23,6 @@ class MainState(IntEnum): ONROAD = 2 -class CustomButtonAction(IntEnum): - NONE = 0 - BOOKMARK = 1 - QUIET_MODE = 2 - - class MainLayout(Widget): def __init__(self): super().__init__() @@ -62,7 +56,7 @@ class MainLayout(Widget): gui_app.push_widget(self._onboarding_window) def _render(self, _): - self._handle_custom_button() + handle_custom_button(ui_state.sm, ui_state.params, self._on_bookmark_clicked) self._handle_onroad_transition() self._render_main_content() @@ -127,21 +121,6 @@ class MainLayout(Widget): msg = messaging.new_message(service, valid=True) self._pm.send(service, msg) - def _handle_custom_button(self): - if not ui_state.sm.updated['carState']: - return - - custom_pressed = any(be.type == car.CarState.ButtonEvent.Type.altButton2 and be.pressed - for be in ui_state.sm['carState'].buttonEvents) - if not custom_pressed: - return - - action = CustomButtonAction(ui_state.params.get('SteeringCustomButtonMapping', return_default=True)) - if action == CustomButtonAction.BOOKMARK: - self._on_bookmark_clicked() - elif action == CustomButtonAction.QUIET_MODE: - ui_state.params.put_bool('QuietMode', not ui_state.params.get_bool('QuietMode')) - def _on_onroad_clicked(self): self._sidebar.set_visible(not self._sidebar.is_visible) diff --git a/openpilot/selfdrive/ui/mici/layouts/main.py b/openpilot/selfdrive/ui/mici/layouts/main.py index 7b96366894..4766c8c976 100644 --- a/openpilot/selfdrive/ui/mici/layouts/main.py +++ b/openpilot/selfdrive/ui/mici/layouts/main.py @@ -4,6 +4,7 @@ from openpilot.selfdrive.ui.mici.layouts.home import MiciHomeLayout from openpilot.selfdrive.ui.mici.layouts.settings.settings import SettingsLayout from openpilot.selfdrive.ui.mici.layouts.offroad_alerts import MiciOffroadAlerts from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView +from openpilot.selfdrive.ui.custom_button import handle_custom_button from openpilot.selfdrive.ui.ui_state import device, ui_state from openpilot.selfdrive.ui.mici.layouts.onboarding import OnboardingWindow from openpilot.selfdrive.ui.body.layouts.onroad import BodyLayout @@ -95,6 +96,8 @@ class MiciMainLayout(Scroller): self._alerts_layout._update_state() def _render(self, _): + handle_custom_button(ui_state.sm, ui_state.params, self._on_bookmark_clicked) + if not self._setup: if self._alerts_layout.active_alerts() > 0: self._scroller.scroll_to(self._alerts_layout.rect.x) diff --git a/openpilot/selfdrive/ui/tests/test_custom_button.py b/openpilot/selfdrive/ui/tests/test_custom_button.py index efaa90e0f0..6a8ed3abf1 100644 --- a/openpilot/selfdrive/ui/tests/test_custom_button.py +++ b/openpilot/selfdrive/ui/tests/test_custom_button.py @@ -3,7 +3,7 @@ from unittest.mock import Mock from opendbc.car.structs import car -from openpilot.selfdrive.ui.layouts import main +from openpilot.selfdrive.ui.custom_button import CustomButtonAction, handle_custom_button class FakeSubMaster: @@ -15,7 +15,7 @@ class FakeSubMaster: return self.messages[key] -def test_custom_button_actions(monkeypatch): +def test_custom_button_actions(): params = Mock() params.get_bool.return_value = False sm = FakeSubMaster({ @@ -24,15 +24,12 @@ def test_custom_button_actions(monkeypatch): pressed=True, )]), }) - monkeypatch.setattr(main, 'ui_state', SimpleNamespace(sm=sm, params=params)) + bookmark_callback = Mock() - layout = main.MainLayout.__new__(main.MainLayout) - layout._on_bookmark_clicked = Mock() + params.get.return_value = CustomButtonAction.BOOKMARK + handle_custom_button(sm, params, bookmark_callback) + bookmark_callback.assert_called_once() - params.get.return_value = main.CustomButtonAction.BOOKMARK - layout._handle_custom_button() - layout._on_bookmark_clicked.assert_called_once() - - params.get.return_value = main.CustomButtonAction.QUIET_MODE - layout._handle_custom_button() + params.get.return_value = CustomButtonAction.QUIET_MODE + handle_custom_button(sm, params, bookmark_callback) params.put_bool.assert_called_once_with('QuietMode', True) From 9af59caf9c036afc651368bb589f82610778e5eb Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 16 Aug 2026 16:21:05 -0400 Subject: [PATCH 32/37] ui: compartmentalize custom button handling --- openpilot/selfdrive/ui/layouts/main.py | 2 +- openpilot/selfdrive/ui/mici/layouts/main.py | 2 +- openpilot/selfdrive/ui/{ => sunnypilot}/custom_button.py | 0 openpilot/selfdrive/ui/tests/test_custom_button.py | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename openpilot/selfdrive/ui/{ => sunnypilot}/custom_button.py (100%) diff --git a/openpilot/selfdrive/ui/layouts/main.py b/openpilot/selfdrive/ui/layouts/main.py index b56c8136c2..fd26b129e6 100644 --- a/openpilot/selfdrive/ui/layouts/main.py +++ b/openpilot/selfdrive/ui/layouts/main.py @@ -3,7 +3,7 @@ from enum import IntEnum import openpilot.cereal.messaging as messaging from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import Widget -from openpilot.selfdrive.ui.custom_button import handle_custom_button +from openpilot.selfdrive.ui.sunnypilot.custom_button import handle_custom_button from openpilot.selfdrive.ui.layouts.sidebar import Sidebar, SIDEBAR_WIDTH from openpilot.selfdrive.ui.layouts.home import HomeLayout from openpilot.selfdrive.ui.layouts.settings.settings import SettingsLayout, PanelType diff --git a/openpilot/selfdrive/ui/mici/layouts/main.py b/openpilot/selfdrive/ui/mici/layouts/main.py index 4766c8c976..84a3f345a4 100644 --- a/openpilot/selfdrive/ui/mici/layouts/main.py +++ b/openpilot/selfdrive/ui/mici/layouts/main.py @@ -4,7 +4,7 @@ from openpilot.selfdrive.ui.mici.layouts.home import MiciHomeLayout from openpilot.selfdrive.ui.mici.layouts.settings.settings import SettingsLayout from openpilot.selfdrive.ui.mici.layouts.offroad_alerts import MiciOffroadAlerts from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView -from openpilot.selfdrive.ui.custom_button import handle_custom_button +from openpilot.selfdrive.ui.sunnypilot.custom_button import handle_custom_button from openpilot.selfdrive.ui.ui_state import device, ui_state from openpilot.selfdrive.ui.mici.layouts.onboarding import OnboardingWindow from openpilot.selfdrive.ui.body.layouts.onroad import BodyLayout diff --git a/openpilot/selfdrive/ui/custom_button.py b/openpilot/selfdrive/ui/sunnypilot/custom_button.py similarity index 100% rename from openpilot/selfdrive/ui/custom_button.py rename to openpilot/selfdrive/ui/sunnypilot/custom_button.py diff --git a/openpilot/selfdrive/ui/tests/test_custom_button.py b/openpilot/selfdrive/ui/tests/test_custom_button.py index 6a8ed3abf1..a608d4cb23 100644 --- a/openpilot/selfdrive/ui/tests/test_custom_button.py +++ b/openpilot/selfdrive/ui/tests/test_custom_button.py @@ -3,7 +3,7 @@ from unittest.mock import Mock from opendbc.car.structs import car -from openpilot.selfdrive.ui.custom_button import CustomButtonAction, handle_custom_button +from openpilot.selfdrive.ui.sunnypilot.custom_button import CustomButtonAction, handle_custom_button class FakeSubMaster: From bf0cdd667ba460ebd1301de7e79b73a360a7a68e Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 16 Aug 2026 16:24:42 -0400 Subject: [PATCH 33/37] ui: add custom button navigation actions --- openpilot/common/params_keys.h | 2 +- openpilot/selfdrive/ui/layouts/main.py | 23 ++++++++++++++++-- openpilot/selfdrive/ui/mici/layouts/main.py | 24 +++++++++++++++++-- .../selfdrive/ui/sunnypilot/custom_button.py | 13 +++++----- .../selfdrive/ui/tests/test_custom_button.py | 14 ++++------- .../sunnypilot/sunnylink/settings_ui.json | 14 ++++++++++- .../settings_ui_src/pages/vehicle.yaml | 8 ++++++- 7 files changed, 76 insertions(+), 22 deletions(-) diff --git a/openpilot/common/params_keys.h b/openpilot/common/params_keys.h index c325d4443f..44f21b9761 100644 --- a/openpilot/common/params_keys.h +++ b/openpilot/common/params_keys.h @@ -186,7 +186,7 @@ inline static std::unordered_map keys = { {"ShowTurnSignals", {PERSISTENT | BACKUP, BOOL, "0"}}, {"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}}, {"TrueVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}}, - {"SteeringCustomButtonMapping", {PERSISTENT | BACKUP, INT, "0"}}, + {"CustomButtonAction", {PERSISTENT | BACKUP, INT, "0"}}, // MADS params {"Mads", {PERSISTENT | BACKUP, BOOL, "1"}}, diff --git a/openpilot/selfdrive/ui/layouts/main.py b/openpilot/selfdrive/ui/layouts/main.py index fd26b129e6..89f40af851 100644 --- a/openpilot/selfdrive/ui/layouts/main.py +++ b/openpilot/selfdrive/ui/layouts/main.py @@ -3,7 +3,7 @@ from enum import IntEnum import openpilot.cereal.messaging as messaging from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import Widget -from openpilot.selfdrive.ui.sunnypilot.custom_button import handle_custom_button +from openpilot.selfdrive.ui.sunnypilot.custom_button import CustomButtonAction, handle_custom_button from openpilot.selfdrive.ui.layouts.sidebar import Sidebar, SIDEBAR_WIDTH from openpilot.selfdrive.ui.layouts.home import HomeLayout from openpilot.selfdrive.ui.layouts.settings.settings import SettingsLayout, PanelType @@ -41,6 +41,13 @@ class MainLayout(Widget): MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: AugmentedRoadView(), } + self._custom_button_callbacks = { + CustomButtonAction.BOOKMARK: self._on_bookmark_clicked, + CustomButtonAction.QUIET_MODE: self._toggle_quiet_mode, + CustomButtonAction.ONROAD: self._show_onroad, + CustomButtonAction.HOME: self._show_home, + CustomButtonAction.SETTINGS: self._on_settings_clicked, + } self._sidebar_rect = rl.Rectangle(0, 0, 0, 0) self._content_rect = rl.Rectangle(0, 0, 0, 0) @@ -56,7 +63,7 @@ class MainLayout(Widget): gui_app.push_widget(self._onboarding_window) def _render(self, _): - handle_custom_button(ui_state.sm, ui_state.params, self._on_bookmark_clicked) + handle_custom_button(ui_state.sm, ui_state.params, self._custom_button_callbacks) self._handle_onroad_transition() self._render_main_content() @@ -116,6 +123,18 @@ class MainLayout(Widget): def _on_settings_clicked(self): self.open_settings(PanelType.DEVICE) + @staticmethod + def _toggle_quiet_mode(): + ui_state.params.put_bool('QuietMode', not ui_state.params.get_bool('QuietMode')) + + def _show_onroad(self): + self._set_current_layout(MainState.ONROAD) + self._sidebar.set_visible(False) + + def _show_home(self): + self._set_current_layout(MainState.HOME) + self._sidebar.set_visible(True) + def _on_bookmark_clicked(self): for service in ('bookmarkButton', 'userBookmark'): msg = messaging.new_message(service, valid=True) diff --git a/openpilot/selfdrive/ui/mici/layouts/main.py b/openpilot/selfdrive/ui/mici/layouts/main.py index 84a3f345a4..2e3121df19 100644 --- a/openpilot/selfdrive/ui/mici/layouts/main.py +++ b/openpilot/selfdrive/ui/mici/layouts/main.py @@ -4,7 +4,7 @@ from openpilot.selfdrive.ui.mici.layouts.home import MiciHomeLayout from openpilot.selfdrive.ui.mici.layouts.settings.settings import SettingsLayout from openpilot.selfdrive.ui.mici.layouts.offroad_alerts import MiciOffroadAlerts from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView -from openpilot.selfdrive.ui.sunnypilot.custom_button import handle_custom_button +from openpilot.selfdrive.ui.sunnypilot.custom_button import CustomButtonAction, handle_custom_button from openpilot.selfdrive.ui.ui_state import device, ui_state from openpilot.selfdrive.ui.mici.layouts.onboarding import OnboardingWindow from openpilot.selfdrive.ui.body.layouts.onroad import BodyLayout @@ -36,6 +36,13 @@ class MiciMainLayout(Scroller): self._settings_layout = SettingsLayout() self._car_onroad_layout = AugmentedRoadView(bookmark_callback=self._on_bookmark_clicked) self._body_onroad_layout = BodyLayout() + self._custom_button_callbacks = { + CustomButtonAction.BOOKMARK: self._on_bookmark_clicked, + CustomButtonAction.QUIET_MODE: self._toggle_quiet_mode, + CustomButtonAction.ONROAD: lambda: self._show_layout(self._onroad_layout), + CustomButtonAction.HOME: lambda: self._show_layout(self._home_layout), + CustomButtonAction.SETTINGS: self._show_settings, + } # Initialize widget rects for widget in (self._home_layout, self._alerts_layout, self._settings_layout, @@ -96,7 +103,7 @@ class MiciMainLayout(Scroller): self._alerts_layout._update_state() def _render(self, _): - handle_custom_button(ui_state.sm, ui_state.params, self._on_bookmark_clicked) + handle_custom_button(ui_state.sm, ui_state.params, self._custom_button_callbacks) if not self._setup: if self._alerts_layout.active_alerts() > 0: @@ -153,6 +160,19 @@ class MiciMainLayout(Scroller): msg = messaging.new_message(service, valid=True) self._pm.send(service, msg) + @staticmethod + def _toggle_quiet_mode(): + ui_state.params.put_bool('QuietMode', not ui_state.params.get_bool('QuietMode')) + + def _show_layout(self, layout: Widget): + if gui_app.widget_in_stack(self._onboarding_window): + return + gui_app.pop_widgets_to(self, lambda: self._scroll_to(layout)) + + def _show_settings(self): + if not gui_app.widget_in_stack(self._onboarding_window): + gui_app.push_widget(self._settings_layout) + def _on_body_changed(self): self._car_onroad_layout.set_visible(not ui_state.is_body) self._body_onroad_layout.set_visible(bool(ui_state.is_body)) diff --git a/openpilot/selfdrive/ui/sunnypilot/custom_button.py b/openpilot/selfdrive/ui/sunnypilot/custom_button.py index 20aadff5b1..470b876224 100644 --- a/openpilot/selfdrive/ui/sunnypilot/custom_button.py +++ b/openpilot/selfdrive/ui/sunnypilot/custom_button.py @@ -7,9 +7,12 @@ class CustomButtonAction(IntEnum): NONE = 0 BOOKMARK = 1 QUIET_MODE = 2 + ONROAD = 3 + HOME = 4 + SETTINGS = 5 -def handle_custom_button(sm, params, bookmark_callback): +def handle_custom_button(sm, params, callbacks): if not sm.updated['carState']: return @@ -18,8 +21,6 @@ def handle_custom_button(sm, params, bookmark_callback): if not custom_pressed: return - action = CustomButtonAction(params.get('SteeringCustomButtonMapping', return_default=True)) - if action == CustomButtonAction.BOOKMARK: - bookmark_callback() - elif action == CustomButtonAction.QUIET_MODE: - params.put_bool('QuietMode', not params.get_bool('QuietMode')) + action = CustomButtonAction(params.get('CustomButtonAction', return_default=True)) + if callback := callbacks.get(action): + callback() diff --git a/openpilot/selfdrive/ui/tests/test_custom_button.py b/openpilot/selfdrive/ui/tests/test_custom_button.py index a608d4cb23..9398d18039 100644 --- a/openpilot/selfdrive/ui/tests/test_custom_button.py +++ b/openpilot/selfdrive/ui/tests/test_custom_button.py @@ -17,19 +17,15 @@ class FakeSubMaster: def test_custom_button_actions(): params = Mock() - params.get_bool.return_value = False sm = FakeSubMaster({ 'carState': SimpleNamespace(buttonEvents=[SimpleNamespace( type=car.CarState.ButtonEvent.Type.altButton2, pressed=True, )]), }) - bookmark_callback = Mock() + callbacks = {action: Mock() for action in CustomButtonAction if action != CustomButtonAction.NONE} - params.get.return_value = CustomButtonAction.BOOKMARK - handle_custom_button(sm, params, bookmark_callback) - bookmark_callback.assert_called_once() - - params.get.return_value = CustomButtonAction.QUIET_MODE - handle_custom_button(sm, params, bookmark_callback) - params.put_bool.assert_called_once_with('QuietMode', True) + for action, callback in callbacks.items(): + params.get.return_value = action + handle_custom_button(sm, params, callbacks) + callback.assert_called_once() diff --git a/openpilot/sunnypilot/sunnylink/settings_ui.json b/openpilot/sunnypilot/sunnylink/settings_ui.json index d5fe3f5936..9fc80892fa 100644 --- a/openpilot/sunnypilot/sunnylink/settings_ui.json +++ b/openpilot/sunnypilot/sunnylink/settings_ui.json @@ -2173,7 +2173,7 @@ "description": "", "items": [ { - "key": "SteeringCustomButtonMapping", + "key": "CustomButtonAction", "widget": "multiple_button", "title": "Steering Custom Button", "description": "Choose the openpilot action for the steering wheel custom/star button. OEM functionality is unchanged.", @@ -2189,6 +2189,18 @@ { "value": 2, "label": "Quiet Mode" + }, + { + "value": 3, + "label": "Onroad" + }, + { + "value": 4, + "label": "Home" + }, + { + "value": 5, + "label": "Settings" } ] }, diff --git a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml index e402afe4f6..dcc2cab221 100644 --- a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml +++ b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml @@ -10,7 +10,7 @@ sections: title: Hyundai / Kia / Genesis Settings description: '' items: - - key: SteeringCustomButtonMapping + - key: CustomButtonAction widget: multiple_button title: Steering Custom Button description: Choose the openpilot action for the steering wheel custom/star button. OEM functionality is unchanged. @@ -21,6 +21,12 @@ sections: label: Bookmark - value: 2 label: Quiet Mode + - value: 3 + label: Onroad + - value: 4 + label: Home + - value: 5 + label: Settings - key: HyundaiLongitudinalTuning widget: multiple_button title: Custom Longitudinal Tuning From 5426b6af86acada596843070fc00959182e4ae79 Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 16 Aug 2026 16:26:44 -0400 Subject: [PATCH 34/37] ui: toggle custom button navigation targets --- openpilot/selfdrive/ui/layouts/main.py | 20 +++++++++++++------ openpilot/selfdrive/ui/mici/layouts/main.py | 20 ++++++++++++++----- .../selfdrive/ui/sunnypilot/custom_button.py | 5 ++--- .../sunnypilot/sunnylink/settings_ui.json | 8 ++------ .../settings_ui_src/pages/vehicle.yaml | 6 ++---- 5 files changed, 35 insertions(+), 24 deletions(-) diff --git a/openpilot/selfdrive/ui/layouts/main.py b/openpilot/selfdrive/ui/layouts/main.py index 89f40af851..e20b59d97d 100644 --- a/openpilot/selfdrive/ui/layouts/main.py +++ b/openpilot/selfdrive/ui/layouts/main.py @@ -44,9 +44,8 @@ class MainLayout(Widget): self._custom_button_callbacks = { CustomButtonAction.BOOKMARK: self._on_bookmark_clicked, CustomButtonAction.QUIET_MODE: self._toggle_quiet_mode, - CustomButtonAction.ONROAD: self._show_onroad, - CustomButtonAction.HOME: self._show_home, - CustomButtonAction.SETTINGS: self._on_settings_clicked, + CustomButtonAction.ONROAD_HOME: self._toggle_home, + CustomButtonAction.ONROAD_SETTINGS: self._toggle_settings, } self._sidebar_rect = rl.Rectangle(0, 0, 0, 0) @@ -131,9 +130,18 @@ class MainLayout(Widget): self._set_current_layout(MainState.ONROAD) self._sidebar.set_visible(False) - def _show_home(self): - self._set_current_layout(MainState.HOME) - self._sidebar.set_visible(True) + def _toggle_home(self): + if self._current_mode == MainState.HOME: + self._show_onroad() + else: + self._set_current_layout(MainState.HOME) + self._sidebar.set_visible(True) + + def _toggle_settings(self): + if self._current_mode == MainState.SETTINGS: + self._show_onroad() + else: + self._on_settings_clicked() def _on_bookmark_clicked(self): for service in ('bookmarkButton', 'userBookmark'): diff --git a/openpilot/selfdrive/ui/mici/layouts/main.py b/openpilot/selfdrive/ui/mici/layouts/main.py index 2e3121df19..b20520cf06 100644 --- a/openpilot/selfdrive/ui/mici/layouts/main.py +++ b/openpilot/selfdrive/ui/mici/layouts/main.py @@ -39,9 +39,8 @@ class MiciMainLayout(Scroller): self._custom_button_callbacks = { CustomButtonAction.BOOKMARK: self._on_bookmark_clicked, CustomButtonAction.QUIET_MODE: self._toggle_quiet_mode, - CustomButtonAction.ONROAD: lambda: self._show_layout(self._onroad_layout), - CustomButtonAction.HOME: lambda: self._show_layout(self._home_layout), - CustomButtonAction.SETTINGS: self._show_settings, + CustomButtonAction.ONROAD_HOME: self._toggle_home, + CustomButtonAction.ONROAD_SETTINGS: self._toggle_settings, } # Initialize widget rects @@ -169,8 +168,19 @@ class MiciMainLayout(Scroller): return gui_app.pop_widgets_to(self, lambda: self._scroll_to(layout)) - def _show_settings(self): - if not gui_app.widget_in_stack(self._onboarding_window): + def _layout_visible(self, layout: Widget) -> bool: + return abs(layout.rect.x - self._rect.x) < self._rect.width / 2 + + def _toggle_home(self): + if gui_app.get_active_widget() is self and self._layout_visible(self._home_layout): + self._show_layout(self._onroad_layout) + else: + self._show_layout(self._home_layout) + + def _toggle_settings(self): + if gui_app.widget_in_stack(self._settings_layout): + self._show_layout(self._onroad_layout) + elif not gui_app.widget_in_stack(self._onboarding_window): gui_app.push_widget(self._settings_layout) def _on_body_changed(self): diff --git a/openpilot/selfdrive/ui/sunnypilot/custom_button.py b/openpilot/selfdrive/ui/sunnypilot/custom_button.py index 470b876224..97d2f36c13 100644 --- a/openpilot/selfdrive/ui/sunnypilot/custom_button.py +++ b/openpilot/selfdrive/ui/sunnypilot/custom_button.py @@ -7,9 +7,8 @@ class CustomButtonAction(IntEnum): NONE = 0 BOOKMARK = 1 QUIET_MODE = 2 - ONROAD = 3 - HOME = 4 - SETTINGS = 5 + ONROAD_HOME = 3 + ONROAD_SETTINGS = 4 def handle_custom_button(sm, params, callbacks): diff --git a/openpilot/sunnypilot/sunnylink/settings_ui.json b/openpilot/sunnypilot/sunnylink/settings_ui.json index 9fc80892fa..c4485ac4a7 100644 --- a/openpilot/sunnypilot/sunnylink/settings_ui.json +++ b/openpilot/sunnypilot/sunnylink/settings_ui.json @@ -2192,15 +2192,11 @@ }, { "value": 3, - "label": "Onroad" + "label": "Onroad / Home" }, { "value": 4, - "label": "Home" - }, - { - "value": 5, - "label": "Settings" + "label": "Onroad / Settings" } ] }, diff --git a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml index dcc2cab221..869da36d21 100644 --- a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml +++ b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml @@ -22,11 +22,9 @@ sections: - value: 2 label: Quiet Mode - value: 3 - label: Onroad + label: Onroad / Home - value: 4 - label: Home - - value: 5 - label: Settings + label: Onroad / Settings - key: HyundaiLongitudinalTuning widget: multiple_button title: Custom Longitudinal Tuning From c36db99c2d48a578bebe0bb47c2bc118a636209b Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 16 Aug 2026 16:27:59 -0400 Subject: [PATCH 35/37] ui: cycle custom button navigation --- openpilot/selfdrive/ui/layouts/main.py | 17 ++++++----------- openpilot/selfdrive/ui/mici/layouts/main.py | 18 +++++++----------- .../selfdrive/ui/sunnypilot/custom_button.py | 3 +-- .../sunnypilot/sunnylink/settings_ui.json | 6 +----- .../settings_ui_src/pages/vehicle.yaml | 4 +--- 5 files changed, 16 insertions(+), 32 deletions(-) diff --git a/openpilot/selfdrive/ui/layouts/main.py b/openpilot/selfdrive/ui/layouts/main.py index e20b59d97d..59d4433cf5 100644 --- a/openpilot/selfdrive/ui/layouts/main.py +++ b/openpilot/selfdrive/ui/layouts/main.py @@ -44,8 +44,7 @@ class MainLayout(Widget): self._custom_button_callbacks = { CustomButtonAction.BOOKMARK: self._on_bookmark_clicked, CustomButtonAction.QUIET_MODE: self._toggle_quiet_mode, - CustomButtonAction.ONROAD_HOME: self._toggle_home, - CustomButtonAction.ONROAD_SETTINGS: self._toggle_settings, + CustomButtonAction.CYCLE_UI: self._cycle_ui, } self._sidebar_rect = rl.Rectangle(0, 0, 0, 0) @@ -130,18 +129,14 @@ class MainLayout(Widget): self._set_current_layout(MainState.ONROAD) self._sidebar.set_visible(False) - def _toggle_home(self): - if self._current_mode == MainState.HOME: - self._show_onroad() - else: + def _cycle_ui(self): + if self._current_mode == MainState.ONROAD: self._set_current_layout(MainState.HOME) self._sidebar.set_visible(True) - - def _toggle_settings(self): - if self._current_mode == MainState.SETTINGS: - self._show_onroad() - else: + elif self._current_mode == MainState.HOME: self._on_settings_clicked() + else: + self._show_onroad() def _on_bookmark_clicked(self): for service in ('bookmarkButton', 'userBookmark'): diff --git a/openpilot/selfdrive/ui/mici/layouts/main.py b/openpilot/selfdrive/ui/mici/layouts/main.py index b20520cf06..1ff2819cb4 100644 --- a/openpilot/selfdrive/ui/mici/layouts/main.py +++ b/openpilot/selfdrive/ui/mici/layouts/main.py @@ -39,8 +39,7 @@ class MiciMainLayout(Scroller): self._custom_button_callbacks = { CustomButtonAction.BOOKMARK: self._on_bookmark_clicked, CustomButtonAction.QUIET_MODE: self._toggle_quiet_mode, - CustomButtonAction.ONROAD_HOME: self._toggle_home, - CustomButtonAction.ONROAD_SETTINGS: self._toggle_settings, + CustomButtonAction.CYCLE_UI: self._cycle_ui, } # Initialize widget rects @@ -171,17 +170,14 @@ class MiciMainLayout(Scroller): def _layout_visible(self, layout: Widget) -> bool: return abs(layout.rect.x - self._rect.x) < self._rect.width / 2 - def _toggle_home(self): - if gui_app.get_active_widget() is self and self._layout_visible(self._home_layout): - self._show_layout(self._onroad_layout) - else: - self._show_layout(self._home_layout) - - def _toggle_settings(self): + def _cycle_ui(self): if gui_app.widget_in_stack(self._settings_layout): self._show_layout(self._onroad_layout) - elif not gui_app.widget_in_stack(self._onboarding_window): - gui_app.push_widget(self._settings_layout) + elif gui_app.get_active_widget() is self and self._layout_visible(self._home_layout): + if not gui_app.widget_in_stack(self._onboarding_window): + gui_app.push_widget(self._settings_layout) + else: + self._show_layout(self._home_layout) def _on_body_changed(self): self._car_onroad_layout.set_visible(not ui_state.is_body) diff --git a/openpilot/selfdrive/ui/sunnypilot/custom_button.py b/openpilot/selfdrive/ui/sunnypilot/custom_button.py index 97d2f36c13..53dad90ce8 100644 --- a/openpilot/selfdrive/ui/sunnypilot/custom_button.py +++ b/openpilot/selfdrive/ui/sunnypilot/custom_button.py @@ -7,8 +7,7 @@ class CustomButtonAction(IntEnum): NONE = 0 BOOKMARK = 1 QUIET_MODE = 2 - ONROAD_HOME = 3 - ONROAD_SETTINGS = 4 + CYCLE_UI = 3 def handle_custom_button(sm, params, callbacks): diff --git a/openpilot/sunnypilot/sunnylink/settings_ui.json b/openpilot/sunnypilot/sunnylink/settings_ui.json index c4485ac4a7..7386e6af47 100644 --- a/openpilot/sunnypilot/sunnylink/settings_ui.json +++ b/openpilot/sunnypilot/sunnylink/settings_ui.json @@ -2192,11 +2192,7 @@ }, { "value": 3, - "label": "Onroad / Home" - }, - { - "value": 4, - "label": "Onroad / Settings" + "label": "Cycle Onroad / Home / Settings" } ] }, diff --git a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml index 869da36d21..930f894322 100644 --- a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml +++ b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml @@ -22,9 +22,7 @@ sections: - value: 2 label: Quiet Mode - value: 3 - label: Onroad / Home - - value: 4 - label: Onroad / Settings + label: Cycle Onroad / Home / Settings - key: HyundaiLongitudinalTuning widget: multiple_button title: Custom Longitudinal Tuning From 823b447fff76c533fc5e0f748c98ab4033ae2259 Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 16 Aug 2026 16:29:12 -0400 Subject: [PATCH 36/37] ui: adapt custom button cycle by device --- openpilot/selfdrive/ui/layouts/main.py | 9 ++++----- openpilot/sunnypilot/sunnylink/settings_ui.json | 2 +- .../sunnylink/settings_ui_src/pages/vehicle.yaml | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/openpilot/selfdrive/ui/layouts/main.py b/openpilot/selfdrive/ui/layouts/main.py index 59d4433cf5..a0c6186f09 100644 --- a/openpilot/selfdrive/ui/layouts/main.py +++ b/openpilot/selfdrive/ui/layouts/main.py @@ -130,13 +130,12 @@ class MainLayout(Widget): self._sidebar.set_visible(False) def _cycle_ui(self): - if self._current_mode == MainState.ONROAD: - self._set_current_layout(MainState.HOME) + if self._current_mode == MainState.ONROAD and not self._sidebar.is_visible: self._sidebar.set_visible(True) - elif self._current_mode == MainState.HOME: - self._on_settings_clicked() - else: + elif self._current_mode == MainState.SETTINGS: self._show_onroad() + else: + self._on_settings_clicked() def _on_bookmark_clicked(self): for service in ('bookmarkButton', 'userBookmark'): diff --git a/openpilot/sunnypilot/sunnylink/settings_ui.json b/openpilot/sunnypilot/sunnylink/settings_ui.json index 7386e6af47..867b55de63 100644 --- a/openpilot/sunnypilot/sunnylink/settings_ui.json +++ b/openpilot/sunnypilot/sunnylink/settings_ui.json @@ -2192,7 +2192,7 @@ }, { "value": 3, - "label": "Cycle Onroad / Home / Settings" + "label": "Cycle UI" } ] }, diff --git a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml index 930f894322..48612f1c97 100644 --- a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml +++ b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml @@ -22,7 +22,7 @@ sections: - value: 2 label: Quiet Mode - value: 3 - label: Cycle Onroad / Home / Settings + label: Cycle UI - key: HyundaiLongitudinalTuning widget: multiple_button title: Custom Longitudinal Tuning From a8795674b8d207e0446bf9d960b749318a01d332 Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 16 Aug 2026 16:33:39 -0400 Subject: [PATCH 37/37] ui: remove custom button quiet mode --- openpilot/selfdrive/ui/layouts/main.py | 5 ----- openpilot/selfdrive/ui/mici/layouts/main.py | 5 ----- openpilot/selfdrive/ui/sunnypilot/custom_button.py | 1 - openpilot/sunnypilot/sunnylink/settings_ui.json | 4 ---- .../sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml | 2 -- 5 files changed, 17 deletions(-) diff --git a/openpilot/selfdrive/ui/layouts/main.py b/openpilot/selfdrive/ui/layouts/main.py index a0c6186f09..52de91d906 100644 --- a/openpilot/selfdrive/ui/layouts/main.py +++ b/openpilot/selfdrive/ui/layouts/main.py @@ -43,7 +43,6 @@ class MainLayout(Widget): } self._custom_button_callbacks = { CustomButtonAction.BOOKMARK: self._on_bookmark_clicked, - CustomButtonAction.QUIET_MODE: self._toggle_quiet_mode, CustomButtonAction.CYCLE_UI: self._cycle_ui, } @@ -121,10 +120,6 @@ class MainLayout(Widget): def _on_settings_clicked(self): self.open_settings(PanelType.DEVICE) - @staticmethod - def _toggle_quiet_mode(): - ui_state.params.put_bool('QuietMode', not ui_state.params.get_bool('QuietMode')) - def _show_onroad(self): self._set_current_layout(MainState.ONROAD) self._sidebar.set_visible(False) diff --git a/openpilot/selfdrive/ui/mici/layouts/main.py b/openpilot/selfdrive/ui/mici/layouts/main.py index 1ff2819cb4..9f1168f103 100644 --- a/openpilot/selfdrive/ui/mici/layouts/main.py +++ b/openpilot/selfdrive/ui/mici/layouts/main.py @@ -38,7 +38,6 @@ class MiciMainLayout(Scroller): self._body_onroad_layout = BodyLayout() self._custom_button_callbacks = { CustomButtonAction.BOOKMARK: self._on_bookmark_clicked, - CustomButtonAction.QUIET_MODE: self._toggle_quiet_mode, CustomButtonAction.CYCLE_UI: self._cycle_ui, } @@ -158,10 +157,6 @@ class MiciMainLayout(Scroller): msg = messaging.new_message(service, valid=True) self._pm.send(service, msg) - @staticmethod - def _toggle_quiet_mode(): - ui_state.params.put_bool('QuietMode', not ui_state.params.get_bool('QuietMode')) - def _show_layout(self, layout: Widget): if gui_app.widget_in_stack(self._onboarding_window): return diff --git a/openpilot/selfdrive/ui/sunnypilot/custom_button.py b/openpilot/selfdrive/ui/sunnypilot/custom_button.py index 53dad90ce8..9f8374cb75 100644 --- a/openpilot/selfdrive/ui/sunnypilot/custom_button.py +++ b/openpilot/selfdrive/ui/sunnypilot/custom_button.py @@ -6,7 +6,6 @@ from opendbc.car.structs import car class CustomButtonAction(IntEnum): NONE = 0 BOOKMARK = 1 - QUIET_MODE = 2 CYCLE_UI = 3 diff --git a/openpilot/sunnypilot/sunnylink/settings_ui.json b/openpilot/sunnypilot/sunnylink/settings_ui.json index 867b55de63..c2d8a9b695 100644 --- a/openpilot/sunnypilot/sunnylink/settings_ui.json +++ b/openpilot/sunnypilot/sunnylink/settings_ui.json @@ -2186,10 +2186,6 @@ "value": 1, "label": "Bookmark" }, - { - "value": 2, - "label": "Quiet Mode" - }, { "value": 3, "label": "Cycle UI" diff --git a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml index 48612f1c97..31eaed5675 100644 --- a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml +++ b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml @@ -19,8 +19,6 @@ sections: label: None - value: 1 label: Bookmark - - value: 2 - label: Quiet Mode - value: 3 label: Cycle UI - key: HyundaiLongitudinalTuning