From 445eda9d5de2540a6d9e46d6f7ac34557d5692a5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 5 Jun 2025 23:41:04 -0400 Subject: [PATCH 01/10] Bump submodules --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 4ff297f230..f2b2b9e50d 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 4ff297f230a55ab052be0f047d7c3049b1b317be +Subproject commit f2b2b9e50ded6c9024715cbb1f5aa7ef911c792d From 835d6a656e77917d69253dafe206a9b03577fc8e Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 6 Jun 2025 00:05:20 -0400 Subject: [PATCH 02/10] Bump submodules --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index f2b2b9e50d..202a26d943 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit f2b2b9e50ded6c9024715cbb1f5aa7ef911c792d +Subproject commit 202a26d94315edaa981ce80ed73c6022d2d66997 From 8849f44e4190f55673af9256df89a937f5c7a6dc Mon Sep 17 00:00:00 2001 From: Nayan Date: Fri, 6 Jun 2025 14:33:33 -0400 Subject: [PATCH 03/10] ui: sp spinner (#984) * sp spinner * fine!! --------- Co-authored-by: DevTekVE --- .../assets/images/spinner_sunnypilot.png | 3 +++ system/ui/spinner.py | 4 +++- system/ui/sunnypilot/lib/application.py | 21 +++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 sunnypilot/selfdrive/assets/images/spinner_sunnypilot.png create mode 100644 system/ui/sunnypilot/lib/application.py diff --git a/sunnypilot/selfdrive/assets/images/spinner_sunnypilot.png b/sunnypilot/selfdrive/assets/images/spinner_sunnypilot.png new file mode 100644 index 0000000000..edc5c6e5c2 --- /dev/null +++ b/sunnypilot/selfdrive/assets/images/spinner_sunnypilot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9dbacb55581a5ea8cbcd5cea0560ec21d96ac7185463a40fff0f81bebf044ffa +size 23187 diff --git a/system/ui/spinner.py b/system/ui/spinner.py index 3825f1300d..ed605885b0 100755 --- a/system/ui/spinner.py +++ b/system/ui/spinner.py @@ -8,6 +8,8 @@ from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.window import BaseWindow from openpilot.system.ui.text import wrap_text +from openpilot.system.ui.sunnypilot.lib.application import gui_app_sp + # Constants PROGRESS_BAR_WIDTH = 1000 PROGRESS_BAR_HEIGHT = 20 @@ -25,7 +27,7 @@ def clamp(value, min_value, max_value): class SpinnerRenderer: def __init__(self): - self._comma_texture = gui_app.texture("images/spinner_comma.png", TEXTURE_SIZE, TEXTURE_SIZE) + self._comma_texture = gui_app_sp.sp_texture("images/spinner_sunnypilot.png", TEXTURE_SIZE, TEXTURE_SIZE) self._spinner_texture = gui_app.texture("images/spinner_track.png", TEXTURE_SIZE, TEXTURE_SIZE, alpha_premultiply=True) self._rotation = 0.0 self._progress: int | None = None diff --git a/system/ui/sunnypilot/lib/application.py b/system/ui/sunnypilot/lib/application.py new file mode 100644 index 0000000000..10227ce9fd --- /dev/null +++ b/system/ui/sunnypilot/lib/application.py @@ -0,0 +1,21 @@ +from openpilot.system.ui.lib.application import GuiApplication +from importlib.resources import as_file, files + +ASSETS_DIR_SP = files("openpilot.sunnypilot.selfdrive").joinpath("assets") + +class GuiApplicationSP(GuiApplication): + + def __init__(self, width: int, height: int): + super().__init__(width, height) + + def sp_texture(self, asset_path: str, width: int, height: int, alpha_premultiply=False, keep_aspect_ratio=True): + cache_key = f"{asset_path}_{width}_{height}_{alpha_premultiply}{keep_aspect_ratio}" + if cache_key in self._textures: + return self._textures[cache_key] + + with as_file(ASSETS_DIR_SP.joinpath(asset_path)) as fspath: + texture_obj = self._load_texture_from_image(fspath.as_posix(), width, height, alpha_premultiply, keep_aspect_ratio) + self._textures[cache_key] = texture_obj + return texture_obj + +gui_app_sp = GuiApplicationSP(2160, 1080) From 65c512acc00f9c4b6c485ef7b08fe26ec35ec279 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Fri, 6 Jun 2025 11:43:10 -0700 Subject: [PATCH 04/10] ui/models panel: liveDelay Toggle (#971) * Model panel * Get this outta here and use sunnylink instead * Remove space * 'not' because we want gas gating to use e2e/mpc blend * Add toggles to models_panel.cc * "keep enabled for stock behavior" * Add this here * Cloudlog result * change cloudlog to debug, and add latsmooth to steer actuator delay. Need to edit json. Will do locally so when this is merged its a simple ready to go push. * Cleanup * Update longitudinal_planner.py * Remove gasgating for now.. may need to be placed in model --------- Co-authored-by: DevTekVE --- common/params_keys.h | 5 +++- .../qt/offroad/settings/models_panel.cc | 7 +++++ sunnypilot/modeld/modeld.py | 6 ++++- sunnypilot/modeld_v2/modeld.py | 6 ++++- sunnypilot/models/modeld_lagd.py | 26 +++++++++++++++++++ system/manager/manager.py | 1 + 6 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 sunnypilot/models/modeld_lagd.py diff --git a/common/params_keys.h b/common/params_keys.h index 6474fb5840..4fd98c1b38 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -71,7 +71,7 @@ inline static std::unordered_map keys = { {"LastPowerDropDetected", CLEAR_ON_MANAGER_START}, {"LastUpdateException", CLEAR_ON_MANAGER_START}, {"LastUpdateTime", PERSISTENT}, - {"LiveDelay", PERSISTENT}, + {"LiveDelay", PERSISTENT | BACKUP}, {"LiveParameters", PERSISTENT}, {"LiveParametersV2", PERSISTENT}, {"LiveTorqueParameters", PERSISTENT | DONT_LOG}, @@ -170,4 +170,7 @@ inline static std::unordered_map keys = { {"HyundaiLongitudinalTuning", PERSISTENT}, {"DynamicExperimentalControl", PERSISTENT}, + + // model panel params + {"LagdToggle", PERSISTENT | BACKUP}, }; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc index d33cb550ce..3c8d034c5a 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc @@ -31,6 +31,13 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) { }); connect(uiStateSP(), &UIStateSP::uiUpdate, this, &ModelsPanel::updateLabels); list->addItem(currentModelLblBtn); + + // LiveDelay toggle + list->addItem(new ParamControlSP("LagdToggle", + tr("Live Learning Steer Delay"), + tr("Enable this for the car to learn and adapt its steering response time. " + "Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience."), + "../assets/offroad/icon_shell.png")); } diff --git a/sunnypilot/modeld/modeld.py b/sunnypilot/modeld/modeld.py index 2ac3f5700e..d4bd96ff66 100755 --- a/sunnypilot/modeld/modeld.py +++ b/sunnypilot/modeld/modeld.py @@ -25,6 +25,7 @@ from openpilot.sunnypilot.modeld.parse_model_outputs import Parser from openpilot.sunnypilot.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState from openpilot.sunnypilot.modeld.constants import ModelConstants, Plan from openpilot.sunnypilot.models.helpers import get_active_bundle, get_model_path, load_metadata, prepare_inputs, load_meta_constants +from openpilot.sunnypilot.models.modeld_lagd import ModeldLagd from openpilot.sunnypilot.modeld.models.commonmodel_pyx import ModelFrame, CLContext @@ -201,8 +202,9 @@ def main(demo=False): cloudlog.info("modeld got CarParams: %s", CP.brand) + modeld_lagd = ModeldLagd() + # Enable lagd support for sunnypilot modeld - steer_delay = sm["liveDelay"].lateralDelay + model.LAT_SMOOTH_SECONDS long_delay = CP.longitudinalActuatorDelay + model.LONG_SMOOTH_SECONDS prev_action = log.ModelDataV2.Action() @@ -246,6 +248,8 @@ def main(demo=False): v_ego = sm["carState"].vEgo is_rhd = sm["driverMonitoringState"].isRHD frame_id = sm["roadCameraState"].frameId + steer_delay = modeld_lagd.lagd_main(CP, sm, model) + if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']: device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))] diff --git a/sunnypilot/modeld_v2/modeld.py b/sunnypilot/modeld_v2/modeld.py index 4c8acafcad..fe322f462e 100755 --- a/sunnypilot/modeld_v2/modeld.py +++ b/sunnypilot/modeld_v2/modeld.py @@ -23,6 +23,7 @@ from openpilot.sunnypilot.modeld_v2.models.commonmodel_pyx import DrivingModelFr from openpilot.sunnypilot.modeld_v2.meta_helper import load_meta_constants from openpilot.sunnypilot.models.helpers import get_active_bundle +from openpilot.sunnypilot.models.modeld_lagd import ModeldLagd from openpilot.sunnypilot.models.runners.helpers import get_model_runner PROCESS_NAME = "selfdrive.modeld.modeld" @@ -238,6 +239,9 @@ def main(demo=False): CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) cloudlog.info("modeld got CarParams: %s", CP.brand) + + modeld_lagd = ModeldLagd() + # TODO Move smooth seconds to action function long_delay = CP.longitudinalActuatorDelay + model.LONG_SMOOTH_SECONDS prev_action = log.ModelDataV2.Action() @@ -282,7 +286,7 @@ def main(demo=False): is_rhd = sm["driverMonitoringState"].isRHD frame_id = sm["roadCameraState"].frameId v_ego = max(sm["carState"].vEgo, 0.) - steer_delay = sm["liveDelay"].lateralDelay + model.LAT_SMOOTH_SECONDS + steer_delay = modeld_lagd.lagd_main(CP, sm, model) if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']: device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))] diff --git a/sunnypilot/models/modeld_lagd.py b/sunnypilot/models/modeld_lagd.py new file mode 100644 index 0000000000..9e538123a8 --- /dev/null +++ b/sunnypilot/models/modeld_lagd.py @@ -0,0 +1,26 @@ +""" +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 openpilot.common.params import Params +from openpilot.common.swaglog import cloudlog + + +class ModeldLagd: + def __init__(self): + self.params = Params() + + def lagd_main(self, CP, sm, model): + if self.params.get_bool("LagdToggle"): + lateral_delay = sm["liveDelay"].lateralDelay + lat_smooth = model.LAT_SMOOTH_SECONDS + result = lateral_delay + lat_smooth + cloudlog.debug(f"LAGD USING LIVE DELAY: {lateral_delay:.3f} + {lat_smooth:.3f} = {result:.3f}") + return result + steer_actuator_delay = CP.steerActuatorDelay + lat_smooth = model.LAT_SMOOTH_SECONDS + result = (steer_actuator_delay + 0.2) + lat_smooth + cloudlog.debug(f"LAGD USING STEER ACTUATOR: {steer_actuator_delay:.3f} + 0.2 + {lat_smooth:.3f} = {result:.3f}") + return result diff --git a/system/manager/manager.py b/system/manager/manager.py index 46b849108c..13048a8851 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -48,6 +48,7 @@ def manager_init() -> None: ("BlinkerPauseLateralControl", "0"), ("DynamicExperimentalControl", "0"), ("HyundaiLongitudinalTuning", "0"), + ("LagdToggle", "1"), ("Mads", "1"), ("MadsMainCruiseAllowed", "1"), ("MadsSteeringMode", "0"), From ab68ce99bada7edae852a264131aebeb486616ea Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 6 Jun 2025 20:53:58 +0200 Subject: [PATCH 05/10] chore: Update CODEOWNERS with new ownership rules (#982) Update CODEOWNERS with new ownership rules Expand ownership to additional paths for better code coverage and clarity. Newly added owners include @Discountchubbs and @devtekve for specified directories and files. This ensures proper oversight and maintenance of critical areas. --- .github/CODEOWNERS | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 65d2f5588e..e8f1b7eb8b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,11 @@ * @sunnypilot/dev-internal /.github/ @devtekve @sunnyhaibin -/release/ci/ @devtekve @sunnyhaibin \ No newline at end of file +/release/ci/ @devtekve @sunnyhaibin +/tinygrad_repo @devtekve @Discountchubbs +/tinygrad/ @devtekve @Discountchubbs +/selfdrive/controls/lib/longitudinal_planner.py @devtekve @Discountchubbs +/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @devtekve @Discountchubbs +/selfdrive/modeld/ @devtekve @Discountchubbs +/sunnypilot/model* @devtekve @Discountchubbs +/sunnypilot/sunnylink/ @devtekve +/system/athena/ @devtekve \ No newline at end of file From 539b5aab75e7f35624799cd9983dd8eadaa46d62 Mon Sep 17 00:00:00 2001 From: royjr Date: Fri, 6 Jun 2025 15:38:16 -0400 Subject: [PATCH 06/10] ui: display different border color for long control without lat (#981) Update ui.cc Co-authored-by: Jason Wen Co-authored-by: DevTekVE --- selfdrive/ui/ui.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 06208638db..a521902f89 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -80,8 +80,12 @@ void UIState::updateStatus() { status = STATUS_OVERRIDE; } else { if (mads.getAvailable()) { - if (mads.getEnabled()) { - status = ss.getEnabled() ? STATUS_ENGAGED : STATUS_LAT_ONLY; + if (mads.getEnabled() && ss.getEnabled()) { + status = STATUS_ENGAGED; + } else if (mads.getEnabled()) { + status = STATUS_LAT_ONLY; + } else if (ss.getEnabled()) { + status = STATUS_LONG_ONLY; } else { status = STATUS_DISENGAGED; } From debba0c9f9dcc74dfc0c902aa92286860c5c7d73 Mon Sep 17 00:00:00 2001 From: royjr Date: Fri, 6 Jun 2025 15:51:05 -0400 Subject: [PATCH 07/10] ui: add brightness controls (#974) * brightness * better brightness * cleanup --------- Co-authored-by: DevTekVE --- common/params_keys.h | 1 + selfdrive/ui/sunnypilot/SConscript | 1 + .../qt/offroad/settings/brightness.cc | 50 +++++++++++++++++++ .../qt/offroad/settings/brightness.h | 25 ++++++++++ .../qt/offroad/settings/device_panel.cc | 5 ++ .../qt/offroad/settings/device_panel.h | 2 + selfdrive/ui/ui.cc | 16 ++++-- system/manager/manager.py | 1 + 8 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/offroad/settings/brightness.cc create mode 100644 selfdrive/ui/sunnypilot/qt/offroad/settings/brightness.h diff --git a/common/params_keys.h b/common/params_keys.h index 4fd98c1b38..a74f6061d0 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -133,6 +133,7 @@ inline static std::unordered_map keys = { {"CarPlatformBundle", PERSISTENT}, {"EnableGithubRunner", PERSISTENT | BACKUP}, {"MaxTimeOffroad", PERSISTENT | BACKUP}, + {"Brightness", PERSISTENT | BACKUP}, {"ModelRunnerTypeCache", CLEAR_ON_ONROAD_TRANSITION}, {"OffroadMode", CLEAR_ON_MANAGER_START}, {"OffroadMode_Status", CLEAR_ON_MANAGER_START}, diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 796107da77..9a9e395d92 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -24,6 +24,7 @@ qt_src = [ "sunnypilot/qt/offroad/settings/lateral_panel.cc", "sunnypilot/qt/offroad/settings/longitudinal_panel.cc", "sunnypilot/qt/offroad/settings/max_time_offroad.cc", + "sunnypilot/qt/offroad/settings/brightness.cc", "sunnypilot/qt/offroad/settings/models_panel.cc", "sunnypilot/qt/offroad/settings/settings.cc", "sunnypilot/qt/offroad/settings/software_panel.cc", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/brightness.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/brightness.cc new file mode 100644 index 0000000000..ffd3ce1d8b --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/brightness.cc @@ -0,0 +1,50 @@ +/** + * 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. + */ + +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/brightness.h" + +// Map of Brightness Options +const QMap Brightness::brightness_options = { + {"0", "1"}, // Auto (Dark) + {"1", "0"}, // Auto + {"2", "10"}, + {"3", "20"}, + {"4", "30"}, + {"5", "40"}, + {"6", "50"}, + {"7", "60"}, + {"8", "70"}, + {"9", "80"}, + {"10", "90"}, + {"11", "100"} +}; + +Brightness::Brightness() : OptionControlSP( + "Brightness", + tr("Brightness"), + tr("Overrides the brightness of the device."), + "../assets/offroad/icon_blank.png", + {0, 11}, 1, true, &brightness_options) { + + refresh(); +} + +void Brightness::refresh() { + const int brightness = QString::fromStdString(params.get("Brightness")).toInt(); + + QString label; + if (brightness == 1) { + label = tr("Auto (Dark)"); + } else if (brightness == 0) { + label = tr("Auto"); + } else { + const int value = brightness; + label = QString("%1").arg(value); + } + + setLabel(label); +} diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/brightness.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/brightness.h new file mode 100644 index 0000000000..20d0a291d9 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/brightness.h @@ -0,0 +1,25 @@ +/** + * 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. + */ + +#pragma once + +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" + +class Brightness : public OptionControlSP { + Q_OBJECT + +public: + static const QMap brightness_options; + + Brightness(); + void refresh(); + +private: + Params params; +}; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc index e6855c4b4e..a2c7414460 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc @@ -80,6 +80,11 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) { connect(maxTimeOffroad, &OptionControlSP::updateLabels, maxTimeOffroad, &MaxTimeOffroad::refresh); addItem(maxTimeOffroad); + // Brightness + brightness = new Brightness(); + connect(brightness, &OptionControlSP::updateLabels, brightness, &Brightness::refresh); + addItem(brightness); + addItem(device_grid_layout); // offroad mode and power buttons diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h index 7b7412a739..e83801dd29 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h @@ -8,6 +8,7 @@ #pragma once #include "selfdrive/ui/sunnypilot/qt/offroad/settings/max_time_offroad.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/brightness.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h" #include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" @@ -25,6 +26,7 @@ private: std::map buttons; PushButtonSP *offroadBtn; MaxTimeOffroad *maxTimeOffroad; + Brightness *brightness; const QString alwaysOffroadStyle = R"( PushButtonSP { diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index a521902f89..c9dc70cdff 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -171,6 +171,8 @@ void Device::resetInteractiveTimeout(int timeout) { } void Device::updateBrightness(const UIState &s) { + int brightness; + int brightness_override = QString::fromStdString(Params().get("Brightness")).toInt(); float clipped_brightness = offroad_brightness; if (s.scene.started && s.scene.light_sensor >= 0) { clipped_brightness = s.scene.light_sensor; @@ -182,11 +184,19 @@ void Device::updateBrightness(const UIState &s) { clipped_brightness = std::pow((clipped_brightness + 16.0) / 116.0, 3.0); } - // Scale back to 10% to 100% - clipped_brightness = std::clamp(100.0f * clipped_brightness, 10.0f, 100.0f); + if (brightness_override == 1) { + clipped_brightness = std::clamp(100.0f * clipped_brightness, 1.0f, 100.0f); // Scale back to 1% to 100% + } else if (brightness_override == 0) { + clipped_brightness = std::clamp(100.0f * clipped_brightness, 10.0f, 100.0f); // Scale back to 10% to 100% + } + } + + if (brightness_override == 0 || brightness_override == 1) { + brightness = brightness_filter.update(clipped_brightness); + } else { + brightness = brightness_override; } - int brightness = brightness_filter.update(clipped_brightness); if (!awake) { brightness = 0; } diff --git a/system/manager/manager.py b/system/manager/manager.py index 13048a8851..1d72beeda1 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -54,6 +54,7 @@ def manager_init() -> None: ("MadsSteeringMode", "0"), ("MadsUnifiedEngagementMode", "1"), ("MaxTimeOffroad", "1800"), + ("Brightness", "0"), ("ModelManager_LastSyncTime", "0"), ("ModelManager_ModelsCache", ""), ("NeuralNetworkLateralControl", "0"), From 1979b00cc075bbe2980bf0b52f53905cb2362bec Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Fri, 6 Jun 2025 13:39:26 -0700 Subject: [PATCH 08/10] UI: Visual indicator for blind spot (#834) * Refactor and extend ModelRenderer for custom Sunnypilot logic Refactored `ModelRenderer` to `ModelRendererSP` with enhanced features such as lane line updates, path drawing, and lead management for Sunnypilot. Introduced new methods for model updates, lead drawing, and improved path rendering with experimental mode support. Ensured compatibility by integrating with Sunnypilot-specific HUD and camera components. * Update selfdrive/ui/sunnypilot/qt/onroad/model.cc * Refactor `ModelRenderer` for modularity Moved constants and `get_path_length_idx` function to the header file for reuse and clarity. Updated `drawPath` and related methods to better handle surface dimensions, improving rendering flexibility. Made key functions virtual to allow further customization in derived classes. * Cleaning logic on ModelRenderSP Given that we've refactored slightly the original ModelRender, we no longer need to duplicate the logic on our own implementation * Enable blind spot detection and visualization. Added support for blind spot warnings, including gradient-colored visualizations for left and right blind spots on the on-road UI. Introduced a new "BlindSpot" parameter with related logic for detection and rendering, as well as a settings option for user toggling. * Cleanup format Clean Cleanup and fixes * Let's backup the BlindSpot setting * add false for restart-needed * Add blind spot warning toggle to VisualsPanel Moved blind spot warning toggle from settings.cc to VisualsPanel and implemented support for dynamic parameter updates. This change introduces a dedicated layout for managing visual settings and improves modularity in the settings interface. * Update Blind Spot Warnings setting description Clarified the description to specify that warnings are displayed only if the car supports Blind Spot Monitoring (BSM). This ensures better user understanding of the feature's requirements. * Avoid diff on settings.cc * More cleanup --------- Co-authored-by: DevTekVE Co-authored-by: Jason Wen Co-authored-by: Nayan --- common/params_keys.h | 1 + .../qt/offroad/settings/visuals_panel.cc | 58 +++++++++++++++++++ .../qt/offroad/settings/visuals_panel.h | 12 ++++ selfdrive/ui/sunnypilot/qt/onroad/model.cc | 44 ++++++++++++++ selfdrive/ui/sunnypilot/qt/onroad/model.h | 7 +++ system/manager/manager.py | 1 + 6 files changed, 123 insertions(+) diff --git a/common/params_keys.h b/common/params_keys.h index a74f6061d0..c87894a959 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -171,6 +171,7 @@ inline static std::unordered_map keys = { {"HyundaiLongitudinalTuning", PERSISTENT}, {"DynamicExperimentalControl", PERSISTENT}, + {"BlindSpot", PERSISTENT | BACKUP}, // model panel params {"LagdToggle", PERSISTENT | BACKUP}, diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc index cf9729be18..0d42bd74f4 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc @@ -8,5 +8,63 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h" VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) { + param_watcher = new ParamWatcher(this); + connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) { + paramsRefresh(); + }); + + main_layout = new QStackedLayout(this); + ListWidgetSP *list = new ListWidgetSP(this, false); + sunnypilotScreen = new QWidget(this); + QVBoxLayout* vlayout = new QVBoxLayout(sunnypilotScreen); + vlayout->setContentsMargins(50, 20, 50, 20); + + std::vector > toggle_defs{ + { + "BlindSpot", + tr("Show Blind Spot Warnings"), + tr("Enabling this will display warnings when a vehicle is detected in your blind spot as long as your car has BSM supported."), + "../assets/offroad/icon_monitoring.png", + false, + }, + }; + + for (auto &[param, title, desc, icon, needs_restart] : toggle_defs) { + auto toggle = new ParamControlSP(param, title, desc, icon, this); + + bool locked = params.getBool((param + "Lock").toStdString()); + toggle->setEnabled(!locked); + + if (needs_restart && !locked) { + toggle->setDescription(toggle->getDescription() + tr(" Changing this setting will restart openpilot if the car is powered on.")); + + QObject::connect(uiState(), &UIState::engagedChanged, [toggle](bool engaged) { + toggle->setEnabled(!engaged); + }); + + QObject::connect(toggle, &ParamControlSP::toggleFlipped, [=](bool state) { + params.putBool("OnroadCycleRequested", true); + }); + } + + list->addItem(toggle); + toggles[param.toStdString()] = toggle; + param_watcher->addParam(param); + } + + sunnypilotScroller = new ScrollViewSP(list, this); + vlayout->addWidget(sunnypilotScroller); + + main_layout->addWidget(sunnypilotScreen); +} + +void VisualsPanel::paramsRefresh() { + if (!isVisible()) { + return; + } + + for (auto toggle : toggles) { + toggle.second->refresh(); + } } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h index 9f58104c4e..42e0688957 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h @@ -8,6 +8,9 @@ #pragma once #include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" + +class ScrollViewSP; class VisualsPanel : public QWidget { Q_OBJECT @@ -15,4 +18,13 @@ class VisualsPanel : public QWidget { public: explicit VisualsPanel(QWidget *parent = nullptr); + void paramsRefresh(); + +protected: + QStackedLayout* main_layout = nullptr; + QWidget* sunnypilotScreen = nullptr; + ScrollViewSP *sunnypilotScroller = nullptr; + Params params; + std::map toggles; + ParamWatcher * param_watcher; }; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/model.cc b/selfdrive/ui/sunnypilot/qt/onroad/model.cc index 617b64f58e..af0177c344 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/model.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/model.cc @@ -6,3 +6,47 @@ */ #include "selfdrive/ui/sunnypilot/qt/onroad/model.h" + + +void ModelRendererSP::update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead) { + ModelRenderer::update_model(model, lead); + const auto &model_position = model.getPosition(); + const auto &lane_lines = model.getLaneLines(); + float max_distance = std::clamp(*(model_position.getX().end() - 1), MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE); + int max_idx = get_path_length_idx(lane_lines[0], max_distance); + // update blindspot vertices + float max_distance_barrier = 100; + int max_idx_barrier = std::min(max_idx, get_path_length_idx(lane_lines[0], max_distance_barrier)); + mapLineToPolygon(model.getLaneLines()[1], 0.2, -0.05, &left_blindspot_vertices, max_idx_barrier); + mapLineToPolygon(model.getLaneLines()[2], 0.2, -0.05, &right_blindspot_vertices, max_idx_barrier); +} + +void ModelRendererSP::drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &surface_rect) { + auto *s = uiState(); + auto &sm = *(s->sm); + bool blindspot = Params().getBool("BlindSpot"); + + if (blindspot) { + bool left_blindspot = sm["carState"].getCarState().getLeftBlindspot(); + bool right_blindspot = sm["carState"].getCarState().getRightBlindspot(); + + //painter.setBrush(QColor::fromRgbF(1.0, 0.0, 0.0, 0.4)); // Red with alpha for blind spot + + if (left_blindspot && !left_blindspot_vertices.isEmpty()) { + QLinearGradient gradient(0, 0, surface_rect.width(), 0); // Horizontal gradient from left to right + gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha + gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha + painter.setBrush(gradient); + painter.drawPolygon(left_blindspot_vertices); + } + + if (right_blindspot && !right_blindspot_vertices.isEmpty()) { + QLinearGradient gradient(surface_rect.width(), 0, 0, 0); // Horizontal gradient from right to left + gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha + gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha + painter.setBrush(gradient); + painter.drawPolygon(right_blindspot_vertices); + } + } + ModelRenderer::drawPath(painter, model, surface_rect.height()); +} diff --git a/selfdrive/ui/sunnypilot/qt/onroad/model.h b/selfdrive/ui/sunnypilot/qt/onroad/model.h index 8569e58f66..24404f32f0 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/model.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/model.h @@ -12,4 +12,11 @@ class ModelRendererSP : public ModelRenderer { public: ModelRendererSP() = default; + +private: + void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead) override; + void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &rect) override; + + QPolygonF left_blindspot_vertices; + QPolygonF right_blindspot_vertices; }; diff --git a/system/manager/manager.py b/system/manager/manager.py index 1d72beeda1..9afe1a4f26 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -44,6 +44,7 @@ def manager_init() -> None: sunnypilot_default_params: list[tuple[str, str | bytes]] = [ ("AutoLaneChangeTimer", "0"), ("AutoLaneChangeBsmDelay", "0"), + ("BlindSpot", "0"), ("BlinkerMinLateralControlSpeed", "20"), # MPH or km/h ("BlinkerPauseLateralControl", "0"), ("DynamicExperimentalControl", "0"), From 489d9164f8c8aba20b898c55e29e1c98f4598d09 Mon Sep 17 00:00:00 2001 From: Nayan Date: Fri, 6 Jun 2025 16:50:10 -0400 Subject: [PATCH 09/10] ui: model fuzzy search (#958) * models panel * models panel * fix ui report * stupid scroll * move model selector to models panel * cleanup whitespaces * cleanup bad merge * model search * support searching with short name --------- Co-authored-by: DevTekVE --- .../qt/offroad/settings/models_panel.cc | 33 ++++++++++++++++--- .../qt/offroad/settings/models_panel.h | 3 +- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc index 3c8d034c5a..1ce1b1b991 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc @@ -24,7 +24,17 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) { currentModelLblBtn = new ButtonControlSP(tr("Current Model"), tr("SELECT"), current_model); currentModelLblBtn->setValue(current_model); - connect(currentModelLblBtn, &ButtonControlSP::clicked, this, &ModelsPanel::handleCurrentModelLblBtnClicked); + connect(currentModelLblBtn, &ButtonControlSP::clicked, [=]() { + + InputDialog d(tr("Search Model"), this, tr("Enter search keywords, or leave blank to list all models."), false); + d.setMinLength(0); + const int ret = d.exec(); + if (ret) { + handleCurrentModelLblBtnClicked(d.text()); + } + + }); + connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { is_onroad = !offroad; updateLabels(); @@ -134,7 +144,7 @@ void ModelsPanel::updateModelManagerState() { * @brief Handles the model bundle selection button click * Displays available bundles, allows selection, and initiates download */ -void ModelsPanel::handleCurrentModelLblBtnClicked() { +void ModelsPanel::handleCurrentModelLblBtnClicked(const QString &query) { currentModelLblBtn->setEnabled(false); currentModelLblBtn->setValue(tr("Fetching models...")); @@ -142,7 +152,8 @@ void ModelsPanel::handleCurrentModelLblBtnClicked() { QMap index_to_bundle; const auto bundles = model_manager.getAvailableBundles(); for (const auto &bundle: bundles) { - index_to_bundle.insert(bundle.getIndex(), QString::fromStdString(bundle.getDisplayName())); + std::string bundleStr = std::string(bundle.getDisplayName()) + " $SNAME$ " + std::string(bundle.getInternalName()); + index_to_bundle.insert(bundle.getIndex(), QString::fromStdString(bundleStr)); } // Sort bundles by index in descending order @@ -156,10 +167,24 @@ void ModelsPanel::handleCurrentModelLblBtnClicked() { bundleNames.append(index_to_bundle[index]); } + QStringList filteredBundleNames = searchFromList(query, bundleNames); + currentModelLblBtn->setValue(GetActiveModelName()); + if (filteredBundleNames.isEmpty()) { + ConfirmationDialog::alert(tr("No model found for keywords: %1").arg(query), this); + return; + } + + for (const QString &bundleName: filteredBundleNames) { + int index = bundleName.indexOf("$SNAME$"); + if (index != -1) { + filteredBundleNames.replace(filteredBundleNames.indexOf(bundleName), bundleName.left(index).trimmed()); + } + } + const QString selectedBundleName = MultiOptionDialog::getSelection( - tr("Select a Model"), bundleNames, GetActiveModelName(), this); + tr("Select a Model"), filteredBundleNames, GetActiveModelName(), this); if (selectedBundleName.isEmpty() || !canContinueOnMeteredDialog()) { return; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h index 4ba526651e..ee6a74a666 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h @@ -7,6 +7,7 @@ #pragma once +#include "selfdrive/ui/sunnypilot/qt/util.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h" class ModelsPanel : public QWidget { @@ -30,7 +31,7 @@ private: // UI update related methods void updateLabels(); - void handleCurrentModelLblBtnClicked(); + void handleCurrentModelLblBtnClicked(const QString &query); void handleBundleDownloadProgress(); void showResetParamsDialog(); cereal::ModelManagerSP::Reader model_manager; From 5be2cd9b61a8fca813f2a17d3601791ff9dfd3e3 Mon Sep 17 00:00:00 2001 From: Nayan Date: Fri, 6 Jun 2025 17:06:13 -0400 Subject: [PATCH 10/10] bug: fix default model selection ui (#977) fix default model selection ui Co-authored-by: DevTekVE --- selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc index 1ce1b1b991..2d7367de1c 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc @@ -159,7 +159,7 @@ void ModelsPanel::handleCurrentModelLblBtnClicked(const QString &query) { // Sort bundles by index in descending order QStringList bundleNames; // Add "Default" as the first option - bundleNames.append(tr("Use Default")); + bundleNames.append(DEFAULT_MODEL); auto indices = index_to_bundle.keys(); std::sort(indices.begin(), indices.end(), std::greater()); @@ -191,7 +191,7 @@ void ModelsPanel::handleCurrentModelLblBtnClicked(const QString &query) { } // Handle "Stock" selection differently - if (selectedBundleName == tr("Use Default")) { + if (selectedBundleName == DEFAULT_MODEL) { params.remove("ModelManager_ActiveBundle"); currentModelLblBtn->setValue(tr("Default")); showResetParamsDialog();