diff --git a/common/params_keys.h b/common/params_keys.h index 64d872b56d..be038f5a2a 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -130,6 +130,7 @@ inline static std::unordered_map keys = { {"ModelRunnerTypeCache", CLEAR_ON_ONROAD_TRANSITION}, {"OffroadMode", CLEAR_ON_MANAGER_START}, {"OffroadMode_Status", CLEAR_ON_MANAGER_START}, + {"QuietMode", PERSISTENT | BACKUP}, // MADS params {"Mads", PERSISTENT | BACKUP}, diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index 8b5fc8bd04..facb398339 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -13,6 +13,8 @@ from openpilot.common.swaglog import cloudlog from openpilot.system import micd +from openpilot.selfdrive.ui.sunnypilot.quiet_mode import QuietMode + SAMPLE_RATE = 48000 SAMPLE_BUFFER = 4096 # (approx 100ms) MAX_VOLUME = 1.0 @@ -50,8 +52,10 @@ def check_selfdrive_timeout_alert(sm): return False -class Soundd: +class Soundd(QuietMode): def __init__(self): + super().__init__() + self.load_sounds() self.current_alert = AudibleAlert.none @@ -81,7 +85,7 @@ class Soundd: ret = np.zeros(frames, dtype=np.float32) - if self.current_alert != AudibleAlert.none: + if self.should_play_sound(self.current_alert): num_loops = sound_list[self.current_alert][1] sound_data = self.loaded_sounds[self.current_alert] written_frames = 0 @@ -144,6 +148,8 @@ class Soundd: while True: sm.update(0) + self.load_param() + if sm.updated['microphone'] and self.current_alert == AudibleAlert.none: # only update volume filter when not playing alert self.spl_filter_weighted.update(sm["microphone"].soundPressureWeightedDb) self.current_volume = self.calculate_volume(float(self.spl_filter_weighted.x)) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc index ca6d6c62fa..f26ed0a198 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc @@ -16,23 +16,26 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) { device_grid_layout->setHorizontalSpacing(5); device_grid_layout->setVerticalSpacing(25); - std::vector> device_btns = { - {"dcamBtn", tr("Driver Camera Preview")}, - {"retrainingBtn", tr("Training Guide")}, - {"regulatoryBtn", tr("Regulatory")}, - {"translateBtn", tr("Language")}, - {"resetParams", tr("Reset Settings")}, + std::vector> device_btns = { + {"quietModeBtn", tr("Quiet Mode"), "QuietMode"}, + {"dcamBtn", tr("Driver Camera Preview"), ""}, + {"retrainingBtn", tr("Training Guide"), ""}, + {"regulatoryBtn", tr("Regulatory"), ""}, + {"translateBtn", tr("Language"), ""}, + {"resetParams", tr("Reset Settings"), ""}, }; int row = 0, col = 0; - for (int i = 0; i < device_btns.size(); i++) { - if (device_btns[i].first == "regulatoryBtn" && !Hardware::TICI()) { + for (const auto &[id, text, param] : device_btns) { + if (id == "regulatoryBtn" && !Hardware::TICI()) { continue; } - auto *btn = new PushButtonSP(device_btns[i].second, 720, this); + auto *btn = new PushButtonSP(text, 720, this, param); + btn->setObjectName(id); + device_grid_layout->addWidget(btn, row, col); - buttons[device_btns[i].first] = btn; + buttons[id] = btn; col++; if (col > 1) { @@ -43,6 +46,8 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) { connect(buttons["dcamBtn"], &PushButtonSP::clicked, [=]() { emit showDriverView(); }); + connect(buttons["quietModeBtn"], &PushButtonSP::clicked, buttons["quietModeBtn"], &PushButtonSP::updateButton); + connect(buttons["retrainingBtn"], &PushButtonSP::clicked, [=]() { if (ConfirmationDialog::confirm(tr("Are you sure you want to review the training guide?"), tr("Review"), this)) { emit reviewTrainingGuide(); diff --git a/selfdrive/ui/sunnypilot/qt/widgets/controls.h b/selfdrive/ui/sunnypilot/qt/widgets/controls.h index a7c60980fa..acbc3ea3c4 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/controls.h @@ -520,7 +520,11 @@ protected: } // Draw the rectangle +#ifdef __APPLE__ + QRect rect(0, !_title.isEmpty() ? (h - 16) : 20, w, h); +#else QRect rect(0, !_title.isEmpty() ? (h - 24) : 20, w, h); +#endif p.setBrush(QColor(button_enabled ? "#b24a4a4a" : "#121212")); // Background color p.setPen(QPen(Qt::NoPen)); p.drawRoundedRect(rect, 20, 20); @@ -550,8 +554,8 @@ class PushButtonSP : public QPushButton { Q_OBJECT public: - PushButtonSP(const QString &text, const int minimum_button_width = 800, QWidget *parent = nullptr) : QPushButton(text, parent) { - const QString buttonStyle = R"( + PushButtonSP(const QString &text, const int minimum_button_width = 800, QWidget *parent = nullptr, const QString ¶m = "") : QPushButton(text, parent) { + buttonStyle = R"( QPushButton { border-radius: 20px; font-size: 50px; @@ -560,21 +564,61 @@ public: padding: 0 25px 0 25px; color: #FFFFFF; } - QPushButton:enabled { - background-color: #393939; - } - QPushButton:pressed { - background-color: #4A4A4A; - } - QPushButton:disabled { - background-color: #121212; - color: #5C5C5C; - } )"; - setStyleSheet(buttonStyle); + if (!param.isEmpty()) { + key = param.toStdString(); + refresh(); + } else { + updateStyle(false); + } + setFixedWidth(minimum_button_width); } + + void refresh() { + if (!key.empty()) { + bool state = params.getBool(key); + if (state != is_enabled) { + is_enabled = state; + } + updateStyle(is_enabled); + } + } + + void updateButton() { + if (!key.empty()) { + params.putBool(key, !is_enabled); + refresh(); + } + } + +protected: + // Override mouse release event to handle style updates smoothly + void mouseReleaseEvent(QMouseEvent *event) override { + if (!key.empty()) { + bool next_state = !params.getBool(key); + updateStyle(next_state); + } + QPushButton::mouseReleaseEvent(event); + } + +private: + std::string key = ""; + Params params; + bool is_enabled; + QString buttonStyle; + + QString btn_enabled_off_style = "QPushButton:enabled { background-color: #393939; }"; + QString btn_enabled_on_style = "QPushButton:enabled { background-color: #1e79e8; }"; + QString btn_pressed_style = "QPushButton:pressed { background-color: #4A4A4A; }"; + QString btn_disabled_stype = "QPushButton:disabled { background-color: #121212; color: #5C5C5C; }"; + + void updateStyle(bool enabled) { + QString enabled_style = enabled ? btn_enabled_on_style : btn_enabled_off_style; + + setStyleSheet(buttonStyle + enabled_style + btn_pressed_style + btn_disabled_stype); + } }; class PanelBackButton : public QPushButton { diff --git a/selfdrive/ui/sunnypilot/quiet_mode.py b/selfdrive/ui/sunnypilot/quiet_mode.py new file mode 100644 index 0000000000..893df95033 --- /dev/null +++ b/selfdrive/ui/sunnypilot/quiet_mode.py @@ -0,0 +1,39 @@ +""" +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 cereal import car + +from openpilot.common.params import Params + +AudibleAlert = car.CarControl.HUDControl.AudibleAlert + +ALERTS_ALWAYS_PLAY = { + AudibleAlert.warningSoft, + AudibleAlert.warningImmediate, + AudibleAlert.promptDistracted, + AudibleAlert.promptRepeat, +} + +class QuietMode: + def __init__(self): + self.params = Params() + self.enabled: bool = self.params.get_bool("QuietMode") + self._frame = 0 + + def load_param(self) -> None: + self._frame += 1 + if self._frame % 50 == 0: # 2.5 seconds + self.enabled = self.params.get_bool("QuietMode") + + def should_play_sound(self, current_alert: int) -> bool: + """ + Check if a sound should be played based on the Quiet Mode setting + and the current alert. + """ + if not self.enabled: + return bool(current_alert != AudibleAlert.none) + + return current_alert in ALERTS_ALWAYS_PLAY diff --git a/selfdrive/ui/tests/test_ui/run.py b/selfdrive/ui/tests/test_ui/run.py index 507b9db2f5..a4569b9156 100755 --- a/selfdrive/ui/tests/test_ui/run.py +++ b/selfdrive/ui/tests/test_ui/run.py @@ -151,7 +151,7 @@ def setup_keyboard_uppercase(click, pm: PubMaster, scroll=None): def setup_driver_camera(click, pm: PubMaster, scroll=None): setup_settings_device(click, pm) - click(950, 620) + click(1720, 620) DATA['deviceState'].deviceState.started = False setup_onroad(click, pm) DATA['deviceState'].deviceState.started = True diff --git a/sunnypilot/selfdrive/controls/lib/nnlc/helpers.py b/sunnypilot/selfdrive/controls/lib/nnlc/helpers.py index 0ea920f198..8c33fec023 100644 --- a/sunnypilot/selfdrive/controls/lib/nnlc/helpers.py +++ b/sunnypilot/selfdrive/controls/lib/nnlc/helpers.py @@ -46,10 +46,10 @@ def get_nn_model_path(CP: structs.CarParams) -> tuple[str, str, bool]: exact_match = max_similarity >= 0.99 - if car_fingerprint not in model_path or 0.0 <= max_similarity < 0.8: + if car_fingerprint not in model_path or 0.0 <= max_similarity < 0.9: nn_candidate = car_fingerprint model_path, max_similarity = check_nn_path(nn_candidate) - if 0.0 <= max_similarity < 0.8: + if 0.0 <= max_similarity < 0.9: with open(TORQUE_NN_MODEL_SUBSTITUTE_PATH, 'rb') as f: sub = tomllib.load(f) sub_candidate = sub.get(car_fingerprint, car_fingerprint) diff --git a/system/manager/manager.py b/system/manager/manager.py index fe403de364..eb6c60c9ee 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -53,6 +53,7 @@ def manager_init() -> None: ("ModelManager_LastSyncTime", "0"), ("ModelManager_ModelsCache", ""), ("NeuralNetworkLateralControl", "0"), + ("QuietMode", "0"), ] if params.get_bool("RecordFrontLock"):