From 17e25f78b4df38dcf68d5661b33b07b0e0af6829 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 10 Oct 2025 17:00:44 -0400 Subject: [PATCH 01/72] ui: dynamic ICBM status (#1357) * ui: dynamic ICBM status * straight up pls --- selfdrive/ui/sunnypilot/qt/onroad/hud.cc | 18 ++++++++++++++++-- selfdrive/ui/sunnypilot/qt/onroad/hud.h | 4 ++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index 251140ad17..9ce32002b8 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -127,6 +127,9 @@ void HudRendererSP::updateState(const UIState &s) { leftBlindspot = car_state.getLeftBlindspot(); rightBlindspot = car_state.getRightBlindspot(); showTurnSignals = s.scene.turn_signals; + + carControlEnabled = car_control.getEnabled(); + speedCluster = car_state.getCruiseState().getSpeedCluster() * speedConv; } void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) { @@ -685,10 +688,21 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) { } } - // Draw "MAX" text + // Draw "MAX" or carState.cruiseState.speedCluster (when ICBM is active) text + if (carControlEnabled) { + if (std::nearbyint(set_speed) != std::nearbyint(speedCluster)) { + icbm_active_counter = 3 * UI_FREQ; + } else if (icbm_active_counter > 0) { + icbm_active_counter--; + } + } else { + icbm_active_counter = 0; + } + QString max_str = (icbm_active_counter != 0) ? QString::number(std::nearbyint(speedCluster)) : tr("MAX"); + p.setFont(InterFont(40, QFont::DemiBold)); p.setPen(max_color); - p.drawText(set_speed_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("MAX")); + p.drawText(set_speed_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, max_str); // Draw set speed QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(set_speed)) : "–"; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.h b/selfdrive/ui/sunnypilot/qt/onroad/hud.h index c50e2d641d..4a3f90827d 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.h @@ -115,4 +115,8 @@ private: bool rightBlindspot; int blinkerFrameCounter; bool showTurnSignals; + + bool carControlEnabled; + float speedCluster = 0; + int icbm_active_counter = 0; }; From fe6edda23a87f478ec30e3b4cc876c9a625835e4 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 11 Oct 2025 01:52:33 -0400 Subject: [PATCH 02/72] Revert "UI: Make Always Offroad more accessible (#1327)" (#1358) Reverts #1327 --- .../qt/offroad/settings/device_panel.cc | 17 +++++------------ .../qt/offroad/settings/device_panel.h | 2 -- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc index 43e6067451..0abbb5ba34 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc @@ -83,7 +83,7 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) { connect(maxTimeOffroad, &OptionControlSP::updateLabels, maxTimeOffroad, &MaxTimeOffroad::refresh); addItem(maxTimeOffroad); - toggleDeviceBootMode = new ButtonParamControlSP("DeviceBootMode", tr("Wake-Up Behavior"), "", "", {"Default", "Offroad"}, 375, true); + toggleDeviceBootMode = new ButtonParamControlSP("DeviceBootMode", tr("Wake-Up Behavior"), "", "", {"Default", "Offroad"}, 375, true); addItem(toggleDeviceBootMode); connect(toggleDeviceBootMode, &ButtonParamControlSP::buttonClicked, this, [=](int index) { @@ -116,8 +116,9 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) { offroadBtn->setFixedWidth(power_layout->sizeHint().width()); QObject::connect(offroadBtn, &PushButtonSP::clicked, this, &DevicePanelSP::setOffroadMode); - power_group_layout = new QVBoxLayout(); + QVBoxLayout *power_group_layout = new QVBoxLayout(); power_group_layout->setSpacing(25); + power_group_layout->addWidget(offroadBtn, 0, Qt::AlignHCenter); power_group_layout->addLayout(power_layout); addItem(power_group_layout); @@ -130,7 +131,7 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) { buttons["onroadUploadsBtn"], }; - QObject::connect(uiState(), &UIState::offroadTransition, [=](bool _offroad) { + QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { for (auto btn : findChildren()) { bool always_enabled = std::find(always_enabled_btns.begin(), always_enabled_btns.end(), btn) != always_enabled_btns.end(); @@ -138,8 +139,6 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) { btn->setEnabled(offroad); } } - offroad = _offroad; - updateState(); }); } @@ -190,7 +189,7 @@ void DevicePanelSP::updateState() { } bool offroad_mode_param = params.getBool("OffroadMode"); - offroadBtn->setText(offroad_mode_param ? tr("Exit Always Offroad") : tr("Enable Always Offroad")); + offroadBtn->setText(offroad_mode_param ? tr("Exit Always Offroad") : tr("Always Offroad")); offroadBtn->setStyleSheet(offroad_mode_param ? alwaysOffroadStyle : autoOffroadStyle); DeviceSleepModeStatus currStatus = DeviceSleepModeStatus::DEFAULT; @@ -198,10 +197,4 @@ void DevicePanelSP::updateState() { currStatus = DeviceSleepModeStatus::OFFROAD; } toggleDeviceBootMode->setDescription(deviceSleepModeDescription(currStatus)); - - if (offroad and not offroad_mode_param) { - power_group_layout->insertWidget(0, offroadBtn, 0, Qt::AlignHCenter); - } else { - AddWidgetAt(0, offroadBtn); - } } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h index ce4acfc7cf..f6d21699ff 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h @@ -31,8 +31,6 @@ private: PushButtonSP *offroadBtn; MaxTimeOffroad *maxTimeOffroad; ButtonParamControlSP *toggleDeviceBootMode; - QVBoxLayout *power_group_layout; - bool offroad; const QString alwaysOffroadStyle = R"( PushButtonSP { From 974a7a3f7d95253f75ea915fa393000927121eb1 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Fri, 10 Oct 2025 23:47:45 -0700 Subject: [PATCH 03/72] ui: improve toggle states in Developer panel (#1352) ui: quickboot toggle race condition Co-authored-by: Jason Wen --- .../ui/sunnypilot/qt/offroad/settings/developer_panel.cc | 7 +++---- .../ui/sunnypilot/qt/offroad/settings/developer_panel.h | 3 --- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc index 9c36097f1b..021f5b3776 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc @@ -56,14 +56,13 @@ DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(pare addItem(errorLogBtn); QObject::connect(uiState(), &UIState::offroadTransition, this, &DeveloperPanelSP::updateToggles); - - is_release = params.getBool("IsReleaseBranch"); - is_tested = params.getBool("IsTestedBranch"); - is_development = params.getBool("IsDevelopmentBranch"); } void DeveloperPanelSP::updateToggles(bool offroad) { bool disable_updates = params.getBool("DisableUpdates"); + bool is_release = params.getBool("IsReleaseBranch"); + bool is_tested = params.getBool("IsTestedBranch"); + bool is_development = params.getBool("IsDevelopmentBranch"); prebuiltToggle->setVisible(!is_release && !is_tested && !is_development); prebuiltToggle->setEnabled(disable_updates); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h index 7f67512b5d..ed64964a56 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h @@ -22,9 +22,6 @@ private: ParamControlSP *prebuiltToggle; Params params; ParamControlSP *showAdvancedControls; - bool is_development; - bool is_release; - bool is_tested; private slots: void updateToggles(bool offroad); From 1e5758e7126b528741ce1c9d98b36065590bc8e5 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sun, 12 Oct 2025 00:21:38 -0400 Subject: [PATCH 04/72] ui: Better E2E Alert UI Positioning (#1355) Better E2E Alert UI Positioning --- selfdrive/ui/sunnypilot/qt/onroad/hud.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index 9ce32002b8..c00be966a3 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -714,9 +714,8 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) { void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const QString &alert_alt_text) { int size = devUiInfo > 0 ? e2e_alert_small : e2e_alert_large; int x = surface_rect.center().x() + surface_rect.width() / 4; - int y = surface_rect.center().y() + 40; + int y = surface_rect.center().y() + 20; x += devUiInfo > 0 ? 0 : 50; - y += devUiInfo > 0 ? 0 : 80; QRect alertRect(x - size, y - size, size * 2, size * 2); // Alert Circle From b89393a5a214a24fe45f49630029749598996562 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sun, 12 Oct 2025 00:34:15 -0400 Subject: [PATCH 05/72] UI: Blinker Size & State Fix (#1363) Blinker Size & State Fix Co-authored-by: Jason Wen --- selfdrive/ui/sunnypilot/qt/onroad/hud.cc | 22 ++++++++++++++++++---- selfdrive/ui/sunnypilot/qt/onroad/hud.h | 1 + 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index c00be966a3..718bfb514b 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -776,19 +776,33 @@ void HudRendererSP::drawCurrentSpeedSP(QPainter &p, const QRect &surface_rect) { } void HudRendererSP::drawBlinker(QPainter &p, const QRect &surface_rect) { + const bool hazard = leftBlinkerOn && rightBlinkerOn; + int blinkerStatus = hazard ? 2 : (leftBlinkerOn or rightBlinkerOn) ? 1 : 0; + if (!leftBlinkerOn && !rightBlinkerOn) { blinkerFrameCounter = 0; + lastBlinkerStatus = 0; return; } + + if (blinkerStatus != lastBlinkerStatus) { + blinkerFrameCounter = 0; + lastBlinkerStatus = blinkerStatus; + } + ++blinkerFrameCounter; - const int circleRadius = 44; - const int arrowLength = 44; - const int x_gap = 180; + const int BLINKER_COOLDOWN_FRAMES = UI_FREQ / 10; + if (blinkerFrameCounter < BLINKER_COOLDOWN_FRAMES) { + return; + } + + const int circleRadius = 60; + const int arrowLength = 60; + const int x_gap = 160; const int y_offset = 272; const int centerX = surface_rect.center().x(); - const bool hazard = leftBlinkerOn && rightBlinkerOn; const QPen bgBorder(Qt::white, 5); const QPen arrowPen(Qt::NoPen); diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.h b/selfdrive/ui/sunnypilot/qt/onroad/hud.h index 4a3f90827d..554c9c5d0d 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.h @@ -114,6 +114,7 @@ private: bool leftBlindspot; bool rightBlindspot; int blinkerFrameCounter; + int lastBlinkerStatus; bool showTurnSignals; bool carControlEnabled; From 6f42bbab18ae9a35a5ace15a67f995b282c1c333 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sun, 12 Oct 2025 01:59:03 -0400 Subject: [PATCH 06/72] Reapply "UI: Make Always Offroad more accessible" (#1327) (#1361) * UI: Make Always Offroad more accessible * conditional - based on offroad * no need to delete * account for always offroad * fix offroad transition * do this inside updateState on every invoke --------- Co-authored-by: Jason Wen --- .../qt/offroad/settings/device_panel.cc | 43 ++++++++++--------- .../qt/offroad/settings/device_panel.h | 6 ++- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc index 0abbb5ba34..8295789f0a 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc @@ -83,12 +83,12 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) { connect(maxTimeOffroad, &OptionControlSP::updateLabels, maxTimeOffroad, &MaxTimeOffroad::refresh); addItem(maxTimeOffroad); - toggleDeviceBootMode = new ButtonParamControlSP("DeviceBootMode", tr("Wake-Up Behavior"), "", "", {"Default", "Offroad"}, 375, true); + toggleDeviceBootMode = new ButtonParamControlSP("DeviceBootMode", tr("Wake-Up Behavior"), "", "", {"Default", "Offroad"}, 375, true); addItem(toggleDeviceBootMode); connect(toggleDeviceBootMode, &ButtonParamControlSP::buttonClicked, this, [=](int index) { params.put("DeviceBootMode", QString::number(index).toStdString()); - updateState(); + updateState(offroad); }); addItem(device_grid_layout); @@ -116,14 +116,13 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) { offroadBtn->setFixedWidth(power_layout->sizeHint().width()); QObject::connect(offroadBtn, &PushButtonSP::clicked, this, &DevicePanelSP::setOffroadMode); - QVBoxLayout *power_group_layout = new QVBoxLayout(); + power_group_layout = new QVBoxLayout(); power_group_layout->setSpacing(25); - power_group_layout->addWidget(offroadBtn, 0, Qt::AlignHCenter); power_group_layout->addLayout(power_layout); addItem(power_group_layout); - std::vector always_enabled_btns = { + always_enabled_btns = { rebootBtn, poweroffBtn, offroadBtn, @@ -131,15 +130,7 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) { buttons["onroadUploadsBtn"], }; - QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { - for (auto btn : findChildren()) { - bool always_enabled = std::find(always_enabled_btns.begin(), always_enabled_btns.end(), btn) != always_enabled_btns.end(); - - if (!always_enabled) { - btn->setEnabled(offroad); - } - } - }); + QObject::connect(uiState(), &UIState::offroadTransition, this, &DevicePanelSP::updateState); } void DevicePanelSP::setOffroadMode() { @@ -163,7 +154,7 @@ void DevicePanelSP::setOffroadMode() { ConfirmationDialog::alert(tr("Disengage to Enter Always Offroad Mode"), this); } - updateState(); + updateState(offroad); } void DevicePanelSP::resetSettings() { @@ -180,16 +171,20 @@ void DevicePanelSP::resetSettings() { } void DevicePanelSP::showEvent(QShowEvent *event) { - updateState(); + updateState(offroad); } -void DevicePanelSP::updateState() { - if (!isVisible()) { - return; +void DevicePanelSP::updateState(bool _offroad) { + for (auto btn : findChildren()) { + bool always_enabled = std::find(always_enabled_btns.begin(), always_enabled_btns.end(), btn) != always_enabled_btns.end(); + + if (!always_enabled) { + btn->setEnabled(_offroad); + } } bool offroad_mode_param = params.getBool("OffroadMode"); - offroadBtn->setText(offroad_mode_param ? tr("Exit Always Offroad") : tr("Always Offroad")); + offroadBtn->setText(offroad_mode_param ? tr("Exit Always Offroad") : tr("Enable Always Offroad")); offroadBtn->setStyleSheet(offroad_mode_param ? alwaysOffroadStyle : autoOffroadStyle); DeviceSleepModeStatus currStatus = DeviceSleepModeStatus::DEFAULT; @@ -197,4 +192,12 @@ void DevicePanelSP::updateState() { currStatus = DeviceSleepModeStatus::OFFROAD; } toggleDeviceBootMode->setDescription(deviceSleepModeDescription(currStatus)); + + if (offroad and not offroad_mode_param) { + power_group_layout->insertWidget(0, offroadBtn, 0, Qt::AlignHCenter); + } else { + AddWidgetAt(0, offroadBtn); + } + + offroad = _offroad; } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h index f6d21699ff..425c4528a1 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h @@ -23,7 +23,7 @@ public: explicit DevicePanelSP(SettingsWindowSP *parent = 0); void showEvent(QShowEvent *event) override; void setOffroadMode(); - void updateState(); + void updateState(bool _offroad); void resetSettings(); private: @@ -31,6 +31,10 @@ private: PushButtonSP *offroadBtn; MaxTimeOffroad *maxTimeOffroad; ButtonParamControlSP *toggleDeviceBootMode; + QVBoxLayout *power_group_layout; + bool offroad; + + std::vector always_enabled_btns = {}; const QString alwaysOffroadStyle = R"( PushButtonSP { From 5b29fd0f2ce9e52c40f303054990b18a0d276b19 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sun, 12 Oct 2025 12:12:52 -0400 Subject: [PATCH 07/72] UI: Onroad Screen Timeout Fixes (#1364) * Screen Timeout Fixes * rename * auto type --------- Co-authored-by: Jason Wen --- selfdrive/ui/qt/window.cc | 10 ++++++++++ selfdrive/ui/qt/window.h | 4 ++++ .../ui/sunnypilot/qt/onroad/annotated_camera.cc | 6 ++++++ selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h | 1 + selfdrive/ui/sunnypilot/qt/onroad/onroad_home.cc | 2 -- selfdrive/ui/sunnypilot/ui.cc | 12 +++++++----- selfdrive/ui/sunnypilot/ui.h | 8 +++++++- 7 files changed, 35 insertions(+), 8 deletions(-) diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 36f12c266b..d0806e4379 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -92,6 +92,16 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) { // ignore events when device is awakened by resetInteractiveTimeout ignore = !device()->isAwake(); device()->resetInteractiveTimeout(); + +#ifdef SUNNYPILOT + auto *s_sp = uiStateSP(); + bool onroadScreenControl = s_sp->scene.onroadScreenOffControl; + bool started = s_sp->scene.started; + bool timerExpired = (s_sp->scene.onroadScreenOffTimer == 0); + ignore |= (onroadScreenControl and started and timerExpired); + s_sp->reset_onroad_sleep_timer(); +#endif + break; } default: diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h index 8a118b0bb6..63293d4177 100644 --- a/selfdrive/ui/qt/window.h +++ b/selfdrive/ui/qt/window.h @@ -7,6 +7,10 @@ #include "selfdrive/ui/qt/offroad/onboarding.h" #include "selfdrive/ui/qt/offroad/settings.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/ui.h" +#endif + class MainWindow : public QWidget { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc index 1d5567161a..f9b62b0ae7 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc @@ -18,4 +18,10 @@ void AnnotatedCameraWidgetSP::updateState(const UIState &s) { void AnnotatedCameraWidgetSP::showEvent(QShowEvent *event) { AnnotatedCameraWidget::showEvent(event); ui_update_params_sp(uiState()); + uiStateSP()->reset_onroad_sleep_timer(OnroadTimerStatusToggle::RESUME); +} + +void AnnotatedCameraWidgetSP::hideEvent(QHideEvent *event) { + AnnotatedCameraWidget::hideEvent(event); + uiStateSP()->reset_onroad_sleep_timer(OnroadTimerStatusToggle::PAUSE); } diff --git a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h index 8c0a385657..2e7609dae0 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h @@ -18,4 +18,5 @@ public: protected: void showEvent(QShowEvent *event) override; + void hideEvent(QHideEvent* event) override; }; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_home.cc b/selfdrive/ui/sunnypilot/qt/onroad/onroad_home.cc index 3f853e3363..b26d1b828a 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/onroad_home.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_home.cc @@ -25,10 +25,8 @@ void OnroadWindowSP::updateState(const UIStateSP &s) { void OnroadWindowSP::mousePressEvent(QMouseEvent *e) { OnroadWindow::mousePressEvent(e); - uiStateSP()->reset_onroad_sleep_timer(); } void OnroadWindowSP::offroadTransition(bool offroad) { OnroadWindow::offroadTransition(offroad); - uiStateSP()->reset_onroad_sleep_timer(); } diff --git a/selfdrive/ui/sunnypilot/ui.cc b/selfdrive/ui/sunnypilot/ui.cc index c021e0e95b..2244e23199 100644 --- a/selfdrive/ui/sunnypilot/ui.cc +++ b/selfdrive/ui/sunnypilot/ui.cc @@ -71,17 +71,19 @@ void ui_update_params_sp(UIStateSP *s) { s->scene.onroadScreenOffBrightness = std::atoi(params.get("OnroadScreenOffBrightness").c_str()); s->scene.onroadScreenOffControl = params.getBool("OnroadScreenOffControl"); s->scene.onroadScreenOffTimerParam = std::atoi(params.get("OnroadScreenOffTimer").c_str()); - s->reset_onroad_sleep_timer(); s->scene.turn_signals = params.getBool("ShowTurnSignals"); } -void UIStateSP::reset_onroad_sleep_timer() { - if (scene.onroadScreenOffTimerParam >= 0 and scene.onroadScreenOffControl) { - scene.onroadScreenOffTimer = scene.onroadScreenOffTimerParam * UI_FREQ; - } else { +void UIStateSP::reset_onroad_sleep_timer(OnroadTimerStatusToggle toggleTimerStatus) { + // Toggling from active state to inactive + if (toggleTimerStatus == OnroadTimerStatusToggle::PAUSE and scene.onroadScreenOffTimer != -1) { scene.onroadScreenOffTimer = -1; } + // Toggling from a previously inactive state or resetting an active timer + else if ((scene.onroadScreenOffTimerParam >= 0 and scene.onroadScreenOffControl and scene.onroadScreenOffTimer != -1) or toggleTimerStatus == OnroadTimerStatusToggle::RESUME) { + scene.onroadScreenOffTimer = scene.onroadScreenOffTimerParam * UI_FREQ; + } } DeviceSP::DeviceSP(QObject *parent) : Device(parent) { diff --git a/selfdrive/ui/sunnypilot/ui.h b/selfdrive/ui/sunnypilot/ui.h index 55dd43ad52..ba5de83ef6 100644 --- a/selfdrive/ui/sunnypilot/ui.h +++ b/selfdrive/ui/sunnypilot/ui.h @@ -15,6 +15,12 @@ #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" +enum OnroadTimerStatusToggle { + NONE, + PAUSE, + RESUME +}; + class UIStateSP : public UIState { Q_OBJECT @@ -61,7 +67,7 @@ public: return user.user_id.toLower() != "unregisteredsponsor" && user.user_id.toLower() != "temporarysponsor"; }); } - void reset_onroad_sleep_timer(); + void reset_onroad_sleep_timer(OnroadTimerStatusToggle toggleTimerStatus = OnroadTimerStatusToggle::NONE); signals: void sunnylinkRoleChanged(bool subscriber); From cb6fa622ee484a1b51345958c0d9bed0aa8335d8 Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Sun, 12 Oct 2025 20:11:23 -0700 Subject: [PATCH 08/72] Visuals: Move custom chevron info to `sunnypilot/qt` (#1066) * refactor: move to sp ui * pr suggestion * no need to check pcm just oplong * no color, big front, long check * Fix Rainbow Mode & Y Positioning * Move param to uiscene --------- Co-authored-by: Jason Wen Co-authored-by: nayan --- selfdrive/ui/qt/onroad/model.cc | 167 ------------------ selfdrive/ui/qt/onroad/model.h | 10 -- .../qt/offroad/settings/visuals_panel.cc | 34 ++++ .../qt/offroad/settings/visuals_panel.h | 3 + selfdrive/ui/sunnypilot/qt/onroad/model.cc | 135 ++++++++++++++ selfdrive/ui/sunnypilot/qt/onroad/model.h | 11 ++ selfdrive/ui/sunnypilot/ui.cc | 1 + selfdrive/ui/sunnypilot/ui_scene.h | 1 + 8 files changed, 185 insertions(+), 177 deletions(-) diff --git a/selfdrive/ui/qt/onroad/model.cc b/selfdrive/ui/qt/onroad/model.cc index a1e3756cb6..a4165be216 100644 --- a/selfdrive/ui/qt/onroad/model.cc +++ b/selfdrive/ui/qt/onroad/model.cc @@ -34,7 +34,6 @@ void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) { drawLead(painter, lead_two, lead_vertices[1], surface_rect); } } - drawLeadStatus(painter, surface_rect.height(), surface_rect.width()); painter.restore(); } @@ -175,172 +174,6 @@ QColor ModelRenderer::blendColors(const QColor &start, const QColor &end, float } -void ModelRenderer::drawLeadStatus(QPainter &painter, int height, int width) { - auto *s = uiState(); - auto &sm = *(s->sm); - - if (!sm.alive("radarState")) return; - - const auto &radar_state = sm["radarState"].getRadarState(); - const auto &lead_one = radar_state.getLeadOne(); - const auto &lead_two = radar_state.getLeadTwo(); - - // Check if we have any active leads - bool has_lead_one = lead_one.getStatus(); - bool has_lead_two = lead_two.getStatus(); - - if (!has_lead_one && !has_lead_two) { - // Fade out status display - lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f); - if (lead_status_alpha <= 0.0f) return; - } else { - // Fade in status display - lead_status_alpha = std::min(1.0f, lead_status_alpha + 0.1f); - } - - // Draw status for each lead vehicle under its chevron - if (true) { - drawLeadStatusAtPosition(painter, lead_one, lead_vertices[0], height, width, "L1"); - } - - if (has_lead_two && std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0) { - drawLeadStatusAtPosition(painter, lead_two, lead_vertices[1], height, width, "L2"); - } -} - -void ModelRenderer::drawLeadStatusAtPosition(QPainter &painter, - const cereal::RadarState::LeadData::Reader &lead_data, - const QPointF &chevron_pos, - int height, int width, - const QString &label) { - - float d_rel = lead_data.getDRel(); - float v_rel = lead_data.getVRel(); - auto *s = uiState(); - auto &sm = *(s->sm); - float v_ego = sm["carState"].getCarState().getVEgo(); - - int chevron_data = std::atoi(Params().get("ChevronInfo").c_str()); - - // Calculate chevron size (same logic as drawLead) - float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35; - - QFont content_font = painter.font(); - content_font.setPixelSize(35); - content_font.setBold(true); - painter.setFont(content_font); - - QFontMetrics fm(content_font); - bool is_metric = s->scene.is_metric; - - QStringList text_lines; - - const int chevron_types = 3; - const int chevron_all = chevron_types + 1; // All metrics (value 4) - QStringList chevron_text[chevron_types]; - int position; - float val; - - // Distance display (chevron_data == 1 or all) - if (chevron_data == 1 || chevron_data == chevron_all) { - position = 0; - val = std::max(0.0f, d_rel); - QString distance_unit = is_metric ? "m" : "ft"; - if (!is_metric) { - val *= 3.28084f; // Convert meters to feet - } - chevron_text[position].append(QString::number(val, 'f', 0) + " " + distance_unit); - } - - // Absolute velocity display (chevron_data == 2 or all) - if (chevron_data == 2 || chevron_data == chevron_all) { - position = (chevron_data == 2) ? 0 : 1; - val = std::max(0.0f, (v_rel + v_ego) * (is_metric ? static_cast(MS_TO_KPH) : static_cast(MS_TO_MPH))); - chevron_text[position].append(QString::number(val, 'f', 0) + " " + (is_metric ? "km/h" : "mph")); - } - - // Time-to-contact display (chevron_data == 3 or all) - if (chevron_data == 3 || chevron_data == chevron_all) { - position = (chevron_data == 3) ? 0 : 2; - val = (d_rel > 0 && v_ego > 0) ? std::max(0.0f, d_rel / v_ego) : 0.0f; - QString ttc_str = (val > 0 && val < 200) ? QString::number(val, 'f', 1) + "s" : "---"; - chevron_text[position].append(ttc_str); - } - - // Collect all non-empty text lines - for (int i = 0; i < chevron_types; ++i) { - if (!chevron_text[i].isEmpty()) { - text_lines.append(chevron_text[i]); - } - } - - // If no text to display, return early - if (text_lines.isEmpty()) { - return; - } - - // Text box dimensions - float str_w = 150; // Width of text area - float str_h = 45; // Height per line - - // Position text below chevron, centered horizontally - float text_x = chevron_pos.x() - str_w / 2; - float text_y = chevron_pos.y() + sz + 15; - - // Clamp to screen bounds - text_x = std::clamp(text_x, 10.0f, (float)width - str_w - 10); - - // Shadow offset - QPoint shadow_offset(2, 2); - - // Draw each line of text with shadow - for (int i = 0; i < text_lines.size(); ++i) { - if (!text_lines[i].isEmpty()) { - QRect textRect(text_x, text_y + (i * str_h), str_w, str_h); - - // Draw shadow - painter.setPen(QColor(0x0, 0x0, 0x0, (int)(200 * lead_status_alpha))); - painter.drawText(textRect.translated(shadow_offset.x(), shadow_offset.y()), - Qt::AlignBottom | Qt::AlignHCenter, text_lines[i]); - - // Determine text color based on content and danger level - QColor text_color; - - // Check if this is a distance line (contains 'm' or 'ft') - if (text_lines[i].contains("m") || text_lines[i].contains("ft")) { - if (d_rel < 20.0f) { - text_color = QColor(255, 80, 80, (int)(255 * lead_status_alpha)); // Red - danger - } else if (d_rel < 40.0f) { - text_color = QColor(255, 200, 80, (int)(255 * lead_status_alpha)); // Yellow - caution - } else { - text_color = QColor(80, 255, 120, (int)(255 * lead_status_alpha)); // Green - safe - } - } - // Enhanced color coding for time-to-contact - else if (text_lines[i].contains("s") && !text_lines[i].contains("---")) { - float ttc_val = text_lines[i].left(text_lines[i].length() - 1).toFloat(); - if (ttc_val < 3.0f) { - text_color = QColor(255, 80, 80, (int)(255 * lead_status_alpha)); // Red - urgent - } else if (ttc_val < 6.0f) { - text_color = QColor(255, 200, 80, (int)(255 * lead_status_alpha)); // Yellow - caution - } else { - text_color = QColor(0xff, 0xff, 0xff, (int)(255 * lead_status_alpha)); // White - safe - } - } - else { - text_color = QColor(0xff, 0xff, 0xff, (int)(255 * lead_status_alpha)); // White for other lines - } - - // Draw main text - painter.setPen(text_color); - painter.drawText(textRect, Qt::AlignBottom | Qt::AlignHCenter, text_lines[i]); - } - } - - // Reset pen - painter.setPen(Qt::NoPen); -} - void ModelRenderer::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, const QRect &surface_rect) { const float speedBuff = 10.; diff --git a/selfdrive/ui/qt/onroad/model.h b/selfdrive/ui/qt/onroad/model.h index e40f832c37..3ef8ba5320 100644 --- a/selfdrive/ui/qt/onroad/model.h +++ b/selfdrive/ui/qt/onroad/model.h @@ -34,12 +34,6 @@ protected: bool mapToScreen(float in_x, float in_y, float in_z, QPointF *out); void mapLineToPolygon(const cereal::XYZTData::Reader &line, float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert = true); - void drawLeadStatus(QPainter &painter, int height, int width); - void drawLeadStatusAtPosition(QPainter &painter, - const cereal::RadarState::LeadData::Reader &lead_data, - const QPointF &chevron_pos, - int height, int width, - const QString &label); void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, const QRect &surface_rect); void update_leads(const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line); virtual void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead); @@ -65,8 +59,4 @@ protected: Eigen::Matrix3f car_space_transform = Eigen::Matrix3f::Zero(); QRectF clip_region; - float lead_status_alpha = 0.0f; - QPointF lead_status_pos; - QString lead_status_text; - QColor lead_status_color; }; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc index 0324cd70f0..0e42f777cd 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc @@ -140,6 +140,40 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) { vlayout->addWidget(sunnypilotScroller); main_layout->addWidget(sunnypilotScreen); + + QObject::connect(uiState(), &UIState::offroadTransition, this, &VisualsPanel::refreshLongitudinalStatus); + + refreshLongitudinalStatus(); +} + +void VisualsPanel::refreshLongitudinalStatus() { + auto cp_bytes = params.get("CarParamsPersistent"); + if (!cp_bytes.empty()) { + AlignedBuffer aligned_buf; + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size())); + cereal::CarParams::Reader CP = cmsg.getRoot(); + + has_longitudinal_control = hasLongitudinalControl(CP); + } else { + has_longitudinal_control = false; + } + + if (chevron_info_settings) { + QString chevronEnabledDescription = tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control)."); + QString chevronNoLongDescription = tr("This feature requires openpilot longitudinal control to be available."); + + if (has_longitudinal_control) { + chevron_info_settings->setDescription(chevronEnabledDescription); + } else { + // Reset to "Off" when longitudinal not available + params.put("ChevronInfo", "0"); + chevron_info_settings->setDescription(chevronNoLongDescription); + } + + // Enable only when longitudinal is available + chevron_info_settings->setEnabled(has_longitudinal_control); + chevron_info_settings->refresh(); + } } void VisualsPanel::paramsRefresh() { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h index 30ff31c301..76a846dd45 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h @@ -19,6 +19,7 @@ public: explicit VisualsPanel(QWidget *parent = nullptr); void paramsRefresh(); + void refreshLongitudinalStatus(); protected: QStackedLayout* main_layout = nullptr; @@ -29,4 +30,6 @@ protected: ParamWatcher * param_watcher; ButtonParamControlSP *chevron_info_settings; ButtonParamControlSP *dev_ui_settings; + + bool has_longitudinal_control = false; }; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/model.cc b/selfdrive/ui/sunnypilot/qt/onroad/model.cc index 5d92838f8a..875ec6f0b8 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/model.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/model.cc @@ -87,4 +87,139 @@ void ModelRendererSP::drawPath(QPainter &painter, const cereal::ModelDataV2::Rea // Normal path rendering ModelRenderer::drawPath(painter, model, surface_rect.height()); } + + drawLeadStatus(painter, surface_rect.height(), surface_rect.width()); +} + +void ModelRendererSP::drawLeadStatus(QPainter &painter, int height, int width) { + auto *s = uiState(); + auto &sm = *(s->sm); + + bool longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl(); + if (!longitudinal_control) { + lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f); + return; + } + + if (!sm.alive("radarState")) { + lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f); + return; + } + + const auto &radar_state = sm["radarState"].getRadarState(); + const auto &lead_one = radar_state.getLeadOne(); + const auto &lead_two = radar_state.getLeadTwo(); + + bool has_lead_one = lead_one.getStatus(); + bool has_lead_two = lead_two.getStatus(); + + if (!has_lead_one && !has_lead_two) { + lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f); + if (lead_status_alpha <= 0.0f) return; + } else { + lead_status_alpha = std::min(1.0f, lead_status_alpha + 0.1f); + } + + if (has_lead_one) { + drawLeadStatusAtPosition(painter, lead_one, lead_vertices[0], height, width, "L1"); + } + + if (has_lead_two && std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0) { + drawLeadStatusAtPosition(painter, lead_two, lead_vertices[1], height, width, "L2"); + } +} + +void ModelRendererSP::drawLeadStatusAtPosition(QPainter &painter, + const cereal::RadarState::LeadData::Reader &lead_data, + const QPointF &chevron_pos, + int height, int width, + const QString &label) { + float d_rel = lead_data.getDRel(); + float v_rel = lead_data.getVRel(); + auto *s = uiState(); + auto &sm = *(s->sm); + float v_ego = sm["carState"].getCarState().getVEgo(); + + int chevron_data = s->scene.chevron_info; + float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35; + + QFont content_font = painter.font(); + content_font.setPixelSize(50); + content_font.setBold(true); + painter.setFont(content_font); + + bool is_metric = s->scene.is_metric; + QStringList text_lines; + const int chevron_all = 4; + QStringList chevron_text[3]; + + // Distance display + if (chevron_data == 1 || chevron_data == chevron_all) { + int pos = 0; + float val = std::max(0.0f, d_rel); + QString unit = is_metric ? "m" : "ft"; + if (!is_metric) val *= 3.28084f; + chevron_text[pos].append(QString::number(val, 'f', 0) + " " + unit); + } + + // Speed display + if (chevron_data == 2 || chevron_data == chevron_all) { + int pos = (chevron_data == 2) ? 0 : 1; + float multiplier = is_metric ? static_cast(MS_TO_KPH) : static_cast(MS_TO_MPH); + float val = std::max(0.0f, (v_rel + v_ego) * multiplier); + QString unit = is_metric ? "km/h" : "mph"; + chevron_text[pos].append(QString::number(val, 'f', 0) + " " + unit); + } + + // Time to contact + if (chevron_data == 3 || chevron_data == chevron_all) { + int pos = (chevron_data == 3) ? 0 : 2; + float val = (d_rel > 0 && v_ego > 0) ? std::max(0.0f, d_rel / v_ego) : 0.0f; + QString ttc = (val > 0 && val < 200) ? QString::number(val, 'f', 1) + "s" : "---"; + chevron_text[pos].append(ttc); + } + + for (int i = 0; i < 3; ++i) { + if (!chevron_text[i].isEmpty()) text_lines.append(chevron_text[i]); + } + + if (text_lines.isEmpty()) return; + + QFontMetrics fm(content_font); + float text_width = 120.0f; + for (const QString &line : text_lines) { + text_width = std::max(text_width, fm.horizontalAdvance(line) + 20.0f); + } + text_width = std::min(text_width, 250.0f); + + float line_height = 50.0f; + float total_height = text_lines.size() * line_height; + float margin = 20.0f; + + float text_y = chevron_pos.y() + sz + 15; + if (text_y + total_height > height - margin) { + float y_max = chevron_pos.y() > (height - margin) ? (height - margin) : chevron_pos.y(); + text_y = y_max - 15 - total_height; + text_y = std::max(margin, text_y); + } + + float text_x = chevron_pos.x() - text_width / 2; + text_x = std::clamp(text_x, margin, (float)width - text_width - margin); + + QPoint shadow_offset(2, 2); + QColor text_color = QColor(255, 255, 255, (int)(255 * lead_status_alpha)); + for (int i = 0; i < text_lines.size(); ++i) { + float y = text_y + (i * line_height); + if (y + line_height > height - margin) break; + + QRect rect(text_x, y, text_width, line_height); + + // Draw shadow + painter.setPen(QColor(0, 0, 0, (int)(200 * lead_status_alpha))); + painter.drawText(rect.translated(shadow_offset), Qt::AlignCenter, text_lines[i]); + painter.setPen(text_color); + painter.drawText(rect, Qt::AlignCenter, text_lines[i]); + } + + painter.setPen(Qt::NoPen); } diff --git a/selfdrive/ui/sunnypilot/qt/onroad/model.h b/selfdrive/ui/sunnypilot/qt/onroad/model.h index 24404f32f0..73999f0059 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/model.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/model.h @@ -17,6 +17,17 @@ 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; + // Lead status display methods + void drawLeadStatus(QPainter &painter, int height, int width); + void drawLeadStatusAtPosition(QPainter &painter, + const cereal::RadarState::LeadData::Reader &lead_data, + const QPointF &chevron_pos, + int height, int width, + const QString &label); + QPolygonF left_blindspot_vertices; QPolygonF right_blindspot_vertices; + + // Lead status animation + float lead_status_alpha = 0.0f; }; diff --git a/selfdrive/ui/sunnypilot/ui.cc b/selfdrive/ui/sunnypilot/ui.cc index 2244e23199..16d2bac49d 100644 --- a/selfdrive/ui/sunnypilot/ui.cc +++ b/selfdrive/ui/sunnypilot/ui.cc @@ -73,6 +73,7 @@ void ui_update_params_sp(UIStateSP *s) { s->scene.onroadScreenOffTimerParam = std::atoi(params.get("OnroadScreenOffTimer").c_str()); s->scene.turn_signals = params.getBool("ShowTurnSignals"); + s->scene.chevron_info = std::atoi(params.get("ChevronInfo").c_str()); } void UIStateSP::reset_onroad_sleep_timer(OnroadTimerStatusToggle toggleTimerStatus) { diff --git a/selfdrive/ui/sunnypilot/ui_scene.h b/selfdrive/ui/sunnypilot/ui_scene.h index 233f1e15de..353f4ed8da 100644 --- a/selfdrive/ui/sunnypilot/ui_scene.h +++ b/selfdrive/ui/sunnypilot/ui_scene.h @@ -18,4 +18,5 @@ typedef struct UISceneSP : UIScene { bool trueVEgoUI; bool hideVEgoUI; bool turn_signals = false; + int chevron_info; } UISceneSP; From 728108f97f3d094d8502e1334fe62e8dfe52a360 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sun, 12 Oct 2025 23:36:58 -0400 Subject: [PATCH 09/72] ui: Optimize Param Read for Onroad UI (#1365) * Move params to uiscene * more --------- Co-authored-by: Jason Wen --- .../ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc | 4 ++-- selfdrive/ui/sunnypilot/qt/onroad/model.cc | 4 ++-- selfdrive/ui/sunnypilot/ui.cc | 2 ++ selfdrive/ui/sunnypilot/ui_scene.h | 2 ++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc index 96d945ab53..4bbf894572 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc @@ -21,7 +21,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) { paramsRefresh(param_name, param_value); }); - is_sunnylink_enabled = Params().getBool("SunnylinkEnabled"); + is_sunnylink_enabled = params.getBool("SunnylinkEnabled"); connect(uiStateSP(), &UIStateSP::sunnylinkRolesChanged, this, &SunnylinkPanel::updatePanel); connect(uiStateSP(), &UIStateSP::sunnylinkDeviceUsersChanged, this, &SunnylinkPanel::updatePanel); connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { @@ -272,7 +272,7 @@ void SunnylinkPanel::updatePanel() { const auto sunnylinkDongleId = getSunnylinkDongleId().value_or(tr("N/A")); sunnylinkEnabledBtn->setEnabled(!is_onroad); - is_sunnylink_enabled = Params().getBool("SunnylinkEnabled"); + is_sunnylink_enabled = params.getBool("SunnylinkEnabled"); bool is_sub = uiStateSP()->isSunnylinkSponsor() && is_sunnylink_enabled; auto max_current_sponsor_rule = uiStateSP()->sunnylinkSponsorRole(); auto role_name = max_current_sponsor_rule.getSponsorTierString(); diff --git a/selfdrive/ui/sunnypilot/qt/onroad/model.cc b/selfdrive/ui/sunnypilot/qt/onroad/model.cc index 875ec6f0b8..14943b1d48 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/model.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/model.cc @@ -24,7 +24,7 @@ void ModelRendererSP::update_model(const cereal::ModelDataV2::Reader &model, con 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"); + bool blindspot = s->scene.blindspot_ui; if (blindspot) { bool left_blindspot = sm["carState"].getCarState().getLeftBlindspot(); @@ -49,7 +49,7 @@ void ModelRendererSP::drawPath(QPainter &painter, const cereal::ModelDataV2::Rea } } - bool rainbow = Params().getBool("RainbowMode"); + bool rainbow = s->scene.rainbow_mode; //float v_ego = sm["carState"].getCarState().getVEgo(); if (rainbow) { diff --git a/selfdrive/ui/sunnypilot/ui.cc b/selfdrive/ui/sunnypilot/ui.cc index 16d2bac49d..ab1ca0e6a6 100644 --- a/selfdrive/ui/sunnypilot/ui.cc +++ b/selfdrive/ui/sunnypilot/ui.cc @@ -74,6 +74,8 @@ void ui_update_params_sp(UIStateSP *s) { s->scene.turn_signals = params.getBool("ShowTurnSignals"); s->scene.chevron_info = std::atoi(params.get("ChevronInfo").c_str()); + s->scene.blindspot_ui = params.getBool("BlindSpot"); + s->scene.rainbow_mode = params.getBool("RainbowMode"); } void UIStateSP::reset_onroad_sleep_timer(OnroadTimerStatusToggle toggleTimerStatus) { diff --git a/selfdrive/ui/sunnypilot/ui_scene.h b/selfdrive/ui/sunnypilot/ui_scene.h index 353f4ed8da..da98931e6b 100644 --- a/selfdrive/ui/sunnypilot/ui_scene.h +++ b/selfdrive/ui/sunnypilot/ui_scene.h @@ -19,4 +19,6 @@ typedef struct UISceneSP : UIScene { bool hideVEgoUI; bool turn_signals = false; int chevron_info; + bool blindspot_ui; + bool rainbow_mode; } UISceneSP; From 68d059fd5d3a2503ddb1096518f11fcc3df183d5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 12 Oct 2025 23:48:58 -0400 Subject: [PATCH 10/72] ui: ensure Cruise panel widget is reset when `hideEvent` (#1366) --- .../settings/longitudinal/speed_limit/speed_limit_settings.cc | 4 ++++ .../settings/longitudinal/speed_limit/speed_limit_settings.h | 1 + .../ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc | 4 ++++ .../ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h | 1 + 4 files changed, 10 insertions(+) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc index ff5d6221ce..3efbfeed86 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc @@ -158,3 +158,7 @@ void SpeedLimitSettings::refresh() { void SpeedLimitSettings::showEvent(QShowEvent *event) { refresh(); } + +void SpeedLimitSettings::hideEvent(QHideEvent *event) { + setCurrentWidget(subPanelFrame); +} diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h index f76f9bea2a..23fa7b4ece 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h @@ -21,6 +21,7 @@ public: SpeedLimitSettings(QWidget *parent = nullptr); void refresh(); void showEvent(QShowEvent *event) override; + void hideEvent(QHideEvent *event) override; signals: void backPress(); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc index 3e3977ff64..31124a3242 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc @@ -86,6 +86,10 @@ void LongitudinalPanel::showEvent(QShowEvent *event) { refresh(offroad); } +void LongitudinalPanel::hideEvent(QHideEvent *event) { + main_layout->setCurrentWidget(cruisePanelScreen); +} + void LongitudinalPanel::refresh(bool _offroad) { auto cp_bytes = params.get("CarParamsPersistent"); auto cp_sp_bytes = params.get("CarParamsSPPersistent"); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h index 0b7f39c645..a94369a560 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h @@ -18,6 +18,7 @@ class LongitudinalPanel : public QWidget { public: explicit LongitudinalPanel(QWidget *parent = nullptr); void showEvent(QShowEvent *event) override; + void hideEvent(QHideEvent *event) override; void refresh(bool _offroad); private: From 7d54b58b8d3bb83e20299602a3d748ed8baf3763 Mon Sep 17 00:00:00 2001 From: Kirito3481 <47619491+Kirito3481@users.noreply.github.com> Date: Mon, 13 Oct 2025 13:19:24 +0900 Subject: [PATCH 11/72] ui: Update Korean translations (#1359) Update ko-kr translations Co-authored-by: Jason Wen --- selfdrive/ui/translations/main_ko.ts | 265 ++++++++++++++------------- 1 file changed, 135 insertions(+), 130 deletions(-) diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index f3ec6b69e8..d86468ae6e 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -144,11 +144,11 @@ Please use caution when using this feature. Only use the blinker when traffic an Global Brightness - + 전역 밝기 Overrides the brightness of the device. This applies to both onroad and offroad screens. - + 기기의 밝기를 강제로 설정합니다. 이 설정은 주행 중 화면과 비주행 화면(설정 등) 모두에 적용됩니다. @@ -256,11 +256,11 @@ This only toggles the visibility of the controls; it does not toggle the actual Enable Copyparty service - + Copyparty 서비스 사용 Copyparty is a very capable file server, you can use it to download your routes, view your logs and even make some edits on some files from your browser. Requires you to connect to your comma locally via it's IP. - + Copyparty는 매우 유능한 파일 서버로, 이를 통해 주행 기록을 다운로드하고, 로그 파일을 확인하며, 심지어 브라우저에서 일부 파일을 편집할 수도 있습니다. 이 기능을 사용하려면 comma 기기의 로컬 IP를 통해 접속해야 합니다. @@ -538,32 +538,32 @@ Steering lag calibration is complete. Onroad Uploads - + 주행 중 업로드 Enable Always Offroad - + 항상 오프로드 사용 DisplayPanel Onroad Screen: Reduced Brightness - + 주행 중 화면: 밝기 감소 Turn off device screen or reduce brightness after driving starts. It automatically brightens again when screen is touched or a visible alert is displayed. - + 운전이 시작된 후 기기 화면을 끄거나 밝기를 줄입니다. 화면을 터치하거나 시각적 알림이 표시되면 자동으로 다시 밝아집니다. Interactivity Timeout - 상호작용 타임아웃 + 상호작용 타임아웃 Apply a custom timeout for settings UI. This is the time after which settings UI closes automatically if user is not interacting with the screen. - 설정 화면에 사용자 지정 타임아웃을 적용하세요. -사용자가 화면과 상호작용하지 않으면 설정 화면이 자동으로 닫히는 시간입니다. + 설정 UI에 사용자 지정 타임아웃을 적용하세요. +이는 사용자가 화면과 상호 작용하지 않으면 설정 UI가 자동으로 닫히는 시간입니다. @@ -630,55 +630,55 @@ This is the time after which settings UI closes automatically if user is not int ExternalStorageControl External Storage - + 외부 저장소 Extend your comma device's storage by inserting a USB drive into the aux port. - + USB 드라이브를 AUX 포트에 삽입하여 comma 기기의 저장 공간을 확장합니다. CHECK - 확인 + 확인 MOUNT - + 마운트 UNMOUNT - + 마운트 해제 FORMAT - + 포맷 Are you sure you want to format this drive? This will erase all data. - + 이 드라이브를 포맷하시겠습니까? 이 작업은 모든 데이터를 지웁니다. Format - + 포맷 formatting - + 포맷 중 insert drive - + 드라이브 삽입 needs format - + 포맷 필요 mounting - + 마운트 중 unmounting - + 마운트 해제 중 @@ -737,57 +737,58 @@ Firehose 모드를 사용하면 훈련 데이터 업로드를 극대화하여 op HudRendererSP km/h - km/h + km/h mph - mph + mph GREEN LIGHT - + 녹색 신호 LEAD VEHICLE DEPARTING - + 전방 차량이 +출발하였습니다 SPEED - + SPEED LIMIT - + LIMIT Near - + 근처 km - + km m - + m mi - + mi ft - + ft AHEAD - + 전방 MAX - 최대 + 최대 @@ -905,15 +906,15 @@ DEPARTING Enforce Torque Lateral Control - + 토크 조향 제어 강제 적용 Enable this to enforce sunnypilot to steer with Torque lateral control. - + sunnypilot이 토크 조향 제어를 통해 조향하도록 강제하려면 이 기능을 활성화하세요. Customize Params - + 매개변수 사용자 지정 @@ -940,31 +941,31 @@ DEPARTING Intelligent Cruise Button Management (ICBM) (Alpha) - + 지능형 크루즈 버튼 관리 (ICBM) (알파) When enabled, sunnypilot will attempt to manage the built-in cruise control buttons by emulating button presses for limited longitudinal control. - + 활성화하면, sunnypilot은 제한적인 가감속 제어를 위해 버튼 조작을 재현하여 내장 크루즈 컨트롤 버튼을 관리하려고 시도합니다. Smart Cruise Control - Vision - + 스마트 크루즈 컨트롤 - 비전 Use vision path predictions to estimate the appropriate speed to drive through turns ahead. - + 전방 커브 구간을 통과하기 위한 적절한 속도를 예측하기 위해 시각 경로 예측 기능을 사용합니다. Smart Cruise Control - Map - + 스마트 크루즈 컨트롤 - 지도 Use map data to estimate the appropriate speed to drive through turns ahead. - + 전방 커브 구간을 통과하기 위한 적절한 속도를 예측하기 위해 지도 데이터를 사용합니다. Speed Limit - + 속도 제한 @@ -1195,47 +1196,47 @@ The default software delay value is 0.2 Refresh Model List - + 모델 목록 새로고침 REFRESH - 새로 고침 + 새로고침 Fetching Latest Models - + 최신 모델 불러오는 중 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. - + 이 기능을 켜면 차량이 스스로 조향 응답 속도를 학습하고 맞춥니다. 끄면 고정된 조향 응답 속도를 사용합니다. 이 기능을 켜두는 것이 기본 openpilot 경험을 제공합니다. Live Steer Delay: - + 실시간 조향 지연 시간: Actuator Delay: - + 액츄에이터 지연 시간: Software Delay: - + 소프트웨어 지연 시간: Total Delay: - + 전체 지연 시간: Use Lane Turn Desires - + 차로 회전 의도 사용 Adjust Lane Turn Speed - + 차로 회전 속도 조정 Set the maximum speed for lane turn desires. Default is 19 %1. - + 차로 회전 의도의 최대 속도를 설정합니다. 기본값은 19 %1 입니다. @@ -1400,7 +1401,7 @@ The default software delay value is 0.2 <b>Unsupported branch detected</b> - The current version of <b><u>%1</u></b> branch is no longer supported on the comma three. Please go to <b>[Device > Software]</b> and install a supported branch with <b><u>-tici</u></b> in the branch name for the comma three. - + <b>지원되지 않는 브랜치 감지</b> - 현재 사용 중인 <b><u>%1</u></b> 브랜치는 comma three 기기에서 더 이상 지원되지 않습니다. <b>[기기 > 소프트웨어]</b>로 이동하여 브랜치 이름에 <b><u>-tici</u></b> 가 포함된 comma three용 브랜치로 설치하세요. @@ -1779,56 +1780,59 @@ Warning: You are on a metered connection! None - + 없음 Fixed - + 고정 Percent - + 비율 Car Only - + 차량만 Map Only - + 지도만 Car First - + 차량 +우선 Map First - + 지도 +우선 Combined Data - + 결합 +데이터 Off - + 끄기 Information - + 정보 Warning - + 경고 Assist - + 보조 @@ -1926,7 +1930,7 @@ Data Display - + 화면 @@ -2168,78 +2172,78 @@ Data SpeedLimitPolicy Back - 뒤로 + 뒤로 Speed Limit Source - + 속도 제한 출처 ⦿ Car Only: Use Speed Limit data only from Car - + ⦿ 차량만: 차량에서 제공되는 속도 제한 데이터만 사용 ⦿ Map Only: Use Speed Limit data only from OpenStreetMaps - + ⦿ 지도만: OpenStreetMaps에서 제공되는 속도 제한 데이터만 사용 ⦿ Car First: Use Speed Limit data from Car if available, else use from OpenStreetMaps - + ⦿ 차량 우선: 사용 가능한 경우 차량의 속도 제한 데이터를 사용하고, 그렇지 않은 경우 OpenStreetMaps의 데이터 사용 ⦿ Map First: Use Speed Limit data from OpenStreetMaps if available, else use from Car - + ⦿ 지도 우선: 사용 가능한 경우 OpenStreetMaps의 속도 제한 데이터를 사용하고, 그렇지 않은 경우 차량의 데이터 사용 ⦿ Combined: Use combined Speed Limit data from Car & OpenStreetMaps - + ⦿ 결합: 차량 및 OpenStreetMaps의 속도 제한 결합 데이터 사용 SpeedLimitSettings Back - 뒤로 + 뒤로 Speed Limit - + 속도 제한 Customize Source - + 출처 사용자 지정 Speed Limit Offset - + 속도 제한 오프셋 ⦿ None: No Offset - + ⦿ 없음: 오프셋 없음 ⦿ Fixed: Adds a fixed offset [Speed Limit + Offset] - + ⦿ 고정: 고정 오프셋을 추가합니다. [제한 속도 + 오프셋] ⦿ Percent: Adds a percent offset [Speed Limit + (Offset % Speed Limit)] - + ⦿ 비율: 백분율 오프셋을 추가합니다. [제한 속도 + (오프셋 % 제한 속도)] ⦿ Off: Disables the Speed Limit functions. - + ⦿ 끄기: 속도 제한 기능을 비활성화합니다. ⦿ Information: Displays the current road's speed limit. - + ⦿ 정보: 현재 도로의 제한 속도를 표시합니다. ⦿ Warning: Provides a warning when exceeding the current road's speed limit. - + ⦿ 경고: 현재 도로의 제한 속도를 초과할 경우 경고합니다. ⦿ Assist: Adjusts the vehicle's cruise speed based on the current road's speed limit when operating the +/- buttons. - + ⦿ 보조: +/- 버튼을 조작할 때 현재 도로의 제한 속도를 기준으로 차량의 크루즈 속도를 조정합니다. @@ -2412,27 +2416,27 @@ Data Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.) - + sunnylink 업로더를 활성화하여 sunnypilot이 귀하의 주행 데이터를 sunnypilot 서버로 업로드하도록 허용합니다. (가장 높은 등급에서만 해당하며, 사용자에게는 어떠한 이점도 제공하지 않습니다. 저희는 그냥 데이터 용량을 테스트하는 중입니다.) [Don't use] Enable sunnylink uploader - + [사용하지 마세요] sunnylink 업로더 사용 🚀 sunnylink 🚀 - + 🚀 sunnylink 🚀 For secure backup, restore, and remote configuration - + 안전한 백업, 복원 및 원격 설정을 위해 Sponsorship isn't required for basic backup/restore - + 기본적인 백업/복원은 후원이 필요하지 않습니다 Click the sponsor button for more details - + 후원 버튼을 눌러 더 자세한 정보를 확인하세요 @@ -2628,54 +2632,54 @@ Data TorqueLateralControlCustomParams Manual Real-Time Tuning - + 실시간 수동 튜닝 Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. - + 토크 조향 제어기가 셀프 튜닝에서 학습된 값 대신 고정된 값을 사용하도록 강제합니다. 이 토글을 활성화하면 셀프 튜닝 값이 무시됩니다. Lateral Acceleration Factor - + 조향 가속 계수 Friction - + 마찰 Real-time and Offline - + 실시간 및 오프라인 Offline Only - + 오프라인만 TorqueLateralControlSettings Self-Tune - + 셀프 튜닝 Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. - + 기본적으로 토크 조향 제어를 사용하지 않는 플랫폼에 대해 토크 조향 제어의 셀프 튜닝 기능을 활성화합니다. Less Restrict Settings for Self-Tune (Beta) - + 셀프 튜닝 제한 완화 설정 (베타) Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. - + 셀프 튜닝 사용 시 덜 엄격한 설정을 사용합니다. 이는 학습 값에 대해 torqued가 더 관대하도록 허용합니다. Enable Custom Tuning - + 사용자 지정 튜닝 사용 Enables custom tuning for Torque lateral control. Modifying Lateral Acceleration Factor and Friction below will override the offline values indicated in the YAML files within "opendbc/car/torque_data". The values will also be used live when "Manual Real-Time Tuning" toggle is enabled. - + 토크 조향 제어에 대한 사용자 지정 튜닝을 활성화합니다. 아래에서 조향 가속 계수 및 마찰을 수정하면 "opendbc/car/torque_data" 내의 YAML 파일에 명시된 오프라인 값이 무시됩니다. 이 값들은 "실시간 수동 튜닝" 토글이 활성화되면 실시간으로도 사용됩니다. @@ -2690,7 +2694,7 @@ Data Favorites - + 즐겨찾기 @@ -2737,88 +2741,89 @@ Data Enable Tesla Rainbow Mode - + 테슬라 무지개 모드 사용 A beautiful rainbow effect on the path the model wants to take. - + 모델이 가고자 하는 경로에 아름다운 무지개 효과를 추가합니다. It - + 이는 운전에 does not - + 어떠한 affect driving in any way. - + 영향도 미치지 않습니다. Enable Standstill Timer - + 정차 타이머 사용 Show a timer on the HUD when the car is at a standstill. - + 차량이 정차 상태일 때 HUD에 타이머를 표시합니다. Display Road Name - + 도로 이름 표시 Displays the name of the road the car is traveling on. The OpenStreetMap database of the location must be downloaded from the OSM panel to fetch the road name. - + 차량이 주행 중인 도로의 이름을 표시합니다. 도로 이름을 가져오려면 OSM 패널에서 해당 위치의 OpenStreetMap 데이터베이스를 다운로드해야 합니다. Green Traffic Light Alert (Beta) - + 녹색 신호 감지 알림 (베타) A chime and on-screen alert will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. - + 앞 신호등이 초록 신호로 바뀌고 전방에 차량이 없을 때, 알림음과 화면 경고가 표시됩니다. Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. - + 참고: 이 알림음은 단순한 알림 목적으로만 설계되었습니다. 주변 환경을 살피고 그에 따라 결정하는 것은 운전자의 책임입니다. Lead Departure Alert (Beta) - + 전방 차량 출발 알림 (베타) A chime and on-screen alert will play when you are stopped, and the vehicle in front of you start moving. - + 당신이 정차 중이고 전방 차량이 움직이기 시작할 때, 알림음과 화면 경고가 표시됩니다. Speedometer: Always Display True Speed - + 속도계: 항상 실제 속도 표시 Always display the true vehicle current speed from wheel speed sensors. - + 항상 휠 스피드 센서에서 얻은 실제 차량 속도를 표시합니다. Speedometer: Hide from Onroad Screen - + 속도계: 주행 화면에서 숨기기 Right - + 오른쪽 Right && Bottom - + 오른쪽 && +아래 Developer UI - + 개발자 UI Display real-time parameters and metrics from various sources. - + 다양한 출처의 실시간 매개변수 및 측정 지표를 표시합니다. From bd9bb74d03d4143b65f1a218848ec1f7a195dfe4 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 13 Oct 2025 01:40:22 -0400 Subject: [PATCH 12/72] custom cereal: fix formatting (#1367) --- cereal/custom.capnp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 44b8bd69fa..833845d36d 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -406,11 +406,11 @@ struct ModelDataV2SP @0xa1680744031fdb2d { laneTurnDirection @0 :TurnDirection; } - enum TurnDirection { - none @0; - turnLeft @1; - turnRight @2; - } +enum TurnDirection { + none @0; + turnLeft @1; + turnRight @2; +} struct CustomReserved10 @0xcb9fd56c7057593a { } From 7e03277962091dc9081a97f89673a86705dcffb7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 13 Oct 2025 03:06:39 -0400 Subject: [PATCH 13/72] ui: bigger cluster set speed fonts when ICBM is active (#1369) --- selfdrive/ui/sunnypilot/qt/onroad/hud.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index 718bfb514b..3ed8feb993 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -698,11 +698,13 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) { } else { icbm_active_counter = 0; } + int max_str_size = (icbm_active_counter != 0) ? 60 : 40; + int max_str_y = (icbm_active_counter != 0) ? 15 : 27; QString max_str = (icbm_active_counter != 0) ? QString::number(std::nearbyint(speedCluster)) : tr("MAX"); - p.setFont(InterFont(40, QFont::DemiBold)); + p.setFont(InterFont(max_str_size, QFont::DemiBold)); p.setPen(max_color); - p.drawText(set_speed_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, max_str); + p.drawText(set_speed_rect.adjusted(0, max_str_y, 0, 0), Qt::AlignTop | Qt::AlignHCenter, max_str); // Draw set speed QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(set_speed)) : "–"; From e5f1f86ac2d19550dcd6238635811a0a2efa09b7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 13 Oct 2025 19:37:58 -0400 Subject: [PATCH 14/72] params: helper to clamp out-of-range int params (#1373) * params: helpers to clamp out-of-range values * lint * inline * fix access * actually fix the param * inherit them * more lint --- selfdrive/selfdrived/selfdrived.py | 8 ++++++- sunnypilot/__init__.py | 21 +++++++++++++++++++ .../controls/lib/speed_limit/common.py | 9 ++++---- .../lib/speed_limit/speed_limit_resolver.py | 15 +++++++++++-- 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index 4bc75021f7..0a4ea749e4 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -24,6 +24,7 @@ from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroa from openpilot.system.version import get_build_metadata from openpilot.sunnypilot.mads.mads import ModularAssistiveDrivingSystem +from openpilot.sunnypilot import get_sanitize_int_param from openpilot.sunnypilot.selfdrive.car.car_specific import CarSpecificEventsSP from openpilot.sunnypilot.selfdrive.car.cruise_helpers import CruiseHelper from openpilot.sunnypilot.selfdrive.car.intelligent_cruise_button_management.controller import IntelligentCruiseButtonManagement @@ -130,7 +131,12 @@ class SelfdriveD(CruiseHelper): self.logged_comm_issue = None self.not_running_prev = None self.experimental_mode = False - self.personality = self.params.get("LongitudinalPersonality", return_default=True) + self.personality = get_sanitize_int_param( + "LongitudinalPersonality", + min(log.LongitudinalPersonality.schema.enumerants.values()), + max(log.LongitudinalPersonality.schema.enumerants.values()), + self.params + ) self.recalibrating_seen = False self.state_machine = StateMachine() self.rk = Ratekeeper(100, print_delay_threshold=None) diff --git a/sunnypilot/__init__.py b/sunnypilot/__init__.py index dd9870597d..ccacd0be0a 100644 --- a/sunnypilot/__init__.py +++ b/sunnypilot/__init__.py @@ -5,6 +5,7 @@ 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 enum import IntEnum import hashlib PARAMS_UPDATE_PERIOD = 3 # seconds @@ -16,3 +17,23 @@ def get_file_hash(path: str) -> str: for byte_block in iter(lambda: f.read(4096), b""): sha256_hash.update(byte_block) return sha256_hash.hexdigest() + + +class IntEnumBase(IntEnum): + @classmethod + def min(cls): + return min(cls) + + @classmethod + def max(cls): + return max(cls) + + +def get_sanitize_int_param(key: str, min_val: int, max_val: int, params) -> int: + val: int = params.get(key, return_default=True) + clipped_val = max(min_val, min(max_val, val)) + + if clipped_val != val: + params.put(key, clipped_val) + + return clipped_val diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/common.py b/sunnypilot/selfdrive/controls/lib/speed_limit/common.py index baf9328032..c46768464e 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/common.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/common.py @@ -4,10 +4,11 @@ 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 enum import IntEnum + +from openpilot.sunnypilot import IntEnumBase -class Policy(IntEnum): +class Policy(IntEnumBase): car_state_only = 0 map_data_only = 1 car_state_priority = 2 @@ -15,13 +16,13 @@ class Policy(IntEnum): combined = 4 -class OffsetType(IntEnum): +class OffsetType(IntEnumBase): off = 0 fixed = 1 percentage = 2 -class Mode(IntEnum): +class Mode(IntEnumBase): off = 0 information = 1 warning = 2 diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py index 51fb8f6d64..459334d156 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py @@ -12,7 +12,7 @@ from openpilot.common.constants import CV from openpilot.common.gps import get_gps_location_service from openpilot.common.params import Params from openpilot.common.realtime import DT_MDL -from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD +from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD, get_sanitize_int_param from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit import LIMIT_MAX_MAP_DATA_AGE, LIMIT_ADAPT_ACC from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Policy, OffsetType @@ -42,6 +42,12 @@ class SpeedLimitResolver: self.distance_solutions = {} # Store for distance to current speed limit start for different sources self.policy = self.params.get("SpeedLimitPolicy", return_default=True) + self.policy = get_sanitize_int_param( + "SpeedLimitPolicy", + Policy.min().value, + Policy.max().value, + self.params + ) self._policy_to_sources_map = { Policy.car_state_only: [SpeedLimitSource.car], Policy.map_data_only: [SpeedLimitSource.map], @@ -54,7 +60,12 @@ class SpeedLimitResolver: self._reset_limit_sources(source) self.is_metric = self.params.get_bool("IsMetric") - self.offset_type = self.params.get("SpeedLimitOffsetType", return_default=True) + self.offset_type = get_sanitize_int_param( + "SpeedLimitOffsetType", + OffsetType.min().value, + OffsetType.max().value, + self.params + ) self.offset_value = self.params.get("SpeedLimitValueOffset", return_default=True) self.speed_limit = 0. From 285fd9760698673d99a51cf407d480d2bc4e0531 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 13 Oct 2025 20:36:24 -0400 Subject: [PATCH 15/72] ui: only draw speedCluster speed over "MAX" when ICBM is enabled (#1374) --- selfdrive/ui/sunnypilot/qt/onroad/hud.cc | 4 +++- selfdrive/ui/sunnypilot/qt/onroad/hud.h | 1 + selfdrive/ui/sunnypilot/ui.cc | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index 3ed8feb993..8c913cec17 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -35,6 +35,7 @@ void HudRendererSP::updateState(const UIState &s) { const auto gpsLocation = is_gps_location_external ? sm["gpsLocationExternal"].getGpsLocationExternal() : sm["gpsLocation"].getGpsLocation(); const auto ltp = sm["liveTorqueParameters"].getLiveTorqueParameters(); const auto car_params = sm["carParams"].getCarParams(); + const auto car_params_sp = sm["carParamsSP"].getCarParamsSP(); const auto lp_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP(); const auto lmd = sm["liveMapDataSP"].getLiveMapDataSP(); @@ -130,6 +131,7 @@ void HudRendererSP::updateState(const UIState &s) { carControlEnabled = car_control.getEnabled(); speedCluster = car_state.getCruiseState().getSpeedCluster() * speedConv; + pcmCruiseSpeed = car_params_sp.getPcmCruiseSpeed(); } void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) { @@ -689,7 +691,7 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) { } // Draw "MAX" or carState.cruiseState.speedCluster (when ICBM is active) text - if (carControlEnabled) { + if (!pcmCruiseSpeed && carControlEnabled) { if (std::nearbyint(set_speed) != std::nearbyint(speedCluster)) { icbm_active_counter = 3 * UI_FREQ; } else if (icbm_active_counter > 0) { diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.h b/selfdrive/ui/sunnypilot/qt/onroad/hud.h index 554c9c5d0d..bc7ee81de3 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.h @@ -120,4 +120,5 @@ private: bool carControlEnabled; float speedCluster = 0; int icbm_active_counter = 0; + bool pcmCruiseSpeed; }; diff --git a/selfdrive/ui/sunnypilot/ui.cc b/selfdrive/ui/sunnypilot/ui.cc index ab1ca0e6a6..df7b3ce5ca 100644 --- a/selfdrive/ui/sunnypilot/ui.cc +++ b/selfdrive/ui/sunnypilot/ui.cc @@ -29,7 +29,7 @@ UIStateSP::UIStateSP(QObject *parent) : UIState(parent) { "wideRoadCameraState", "managerState", "selfdriveState", "longitudinalPlan", "modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP", "backupManagerSP", "carControl", "gpsLocationExternal", "gpsLocation", "liveTorqueParameters", - "carStateSP", "liveParameters", "liveMapDataSP" + "carStateSP", "liveParameters", "liveMapDataSP", "carParamsSP" }); // update timer From 39e73cc46ea666787ff49bb1926d9d5625c1ae29 Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Mon, 13 Oct 2025 18:45:59 -0700 Subject: [PATCH 16/72] ui: add ModelRendererSP::draw (#1372) * ModelRendererSP::draw * match * less * huh? * unused --------- Co-authored-by: Jason Wen --- selfdrive/ui/qt/onroad/model.cc | 3 +- selfdrive/ui/qt/onroad/model.h | 4 - selfdrive/ui/sunnypilot/qt/onroad/model.cc | 143 ++++++++++++--------- selfdrive/ui/sunnypilot/qt/onroad/model.h | 14 +- 4 files changed, 88 insertions(+), 76 deletions(-) diff --git a/selfdrive/ui/qt/onroad/model.cc b/selfdrive/ui/qt/onroad/model.cc index a4165be216..176af56613 100644 --- a/selfdrive/ui/qt/onroad/model.cc +++ b/selfdrive/ui/qt/onroad/model.cc @@ -22,7 +22,7 @@ void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) { update_model(model, lead_one); drawLaneLines(painter); - drawPath(painter, model, surface_rect); + drawPath(painter, model, surface_rect.height()); if (longitudinal_control && sm.alive("radarState")) { update_leads(radar_state, model.getPosition()); @@ -173,7 +173,6 @@ QColor ModelRenderer::blendColors(const QColor &start, const QColor &end, float (1 - t) * start.alphaF() + t * end.alphaF()); } - void ModelRenderer::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, const QRect &surface_rect) { const float speedBuff = 10.; diff --git a/selfdrive/ui/qt/onroad/model.h b/selfdrive/ui/qt/onroad/model.h index 3ef8ba5320..0a58d345c4 100644 --- a/selfdrive/ui/qt/onroad/model.h +++ b/selfdrive/ui/qt/onroad/model.h @@ -39,9 +39,6 @@ protected: virtual void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead); void drawLaneLines(QPainter &painter); void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, int height); - virtual void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &surface_rect) {; - drawPath(painter, model, surface_rect.height()); - } void updatePathGradient(QLinearGradient &bg); QColor blendColors(const QColor &start, const QColor &end, float t); @@ -58,5 +55,4 @@ protected: QPointF lead_vertices[2] = {}; Eigen::Matrix3f car_space_transform = Eigen::Matrix3f::Zero(); QRectF clip_region; - }; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/model.cc b/selfdrive/ui/sunnypilot/qt/onroad/model.cc index 14943b1d48..086b703e10 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/model.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/model.cc @@ -21,74 +21,63 @@ void ModelRendererSP::update_model(const cereal::ModelDataV2::Reader &model, con 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) { +void ModelRendererSP::draw(QPainter &painter, const QRect &surface_rect) { + ModelRenderer::draw(painter, surface_rect); auto *s = uiState(); auto &sm = *(s->sm); - bool blindspot = s->scene.blindspot_ui; - 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); - } + if (sm.rcv_frame("liveCalibration") < s->scene.started_frame || + sm.rcv_frame("modelV2") < s->scene.started_frame) { + return; } + painter.save(); + + const auto &model = sm["modelV2"].getModelV2(); + const auto &radar_state = sm["radarState"].getRadarState(); + const auto &lead_one = radar_state.getLeadOne(); + const auto &car_state = sm["carState"].getCarState(); + + update_model(model, lead_one); + drawLaneLines(painter); + + bool blindspot = s->scene.blindspot_ui; bool rainbow = s->scene.rainbow_mode; - //float v_ego = sm["carState"].getCarState().getVEgo(); + + bool left_blindspot = car_state.getLeftBlindspot(); + bool right_blindspot = car_state.getRightBlindspot(); + + if (blindspot) { + drawBlindspot(painter, surface_rect, left_blindspot, right_blindspot); + } if (rainbow) { - // Simple time-based animation - float time_offset = std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count() / 1000.0f; - - // simple linear gradient from bottom to top - QLinearGradient bg(0, surface_rect.height(), 0, 0); - - // evenly spaced colors across the spectrum - // The animation shifts the entire spectrum smoothly - float animation_speed = 40.0f; // speed vroom vroom - float hue_offset = fmod(time_offset * animation_speed, 360.0f); - - // 6-8 color stops for smooth transitions more color makes it laggy - const int num_stops = 7; - for (int i = 0; i < num_stops; i++) { - float position = static_cast(i) / (num_stops - 1); - - float hue = fmod(hue_offset + position * 360.0f, 360.0f); - float saturation = 0.9f; - float lightness = 0.6f; - - // Alpha fades out towards the far end of the path - float alpha = 0.8f * (1.0f - position * 0.3f); - - QColor color = QColor::fromHslF(hue / 360.0f, saturation, lightness, alpha); - bg.setColorAt(position, color); - } - - painter.setBrush(bg); - painter.drawPolygon(track_vertices); + drawRainbowPath(painter, surface_rect); } else { - // Normal path rendering ModelRenderer::drawPath(painter, model, surface_rect.height()); } drawLeadStatus(painter, surface_rect.height(), surface_rect.width()); + + painter.restore(); +} + +void ModelRendererSP::drawBlindspot(QPainter &painter, const QRect &surface_rect, bool left_blindspot, bool right_blindspot) { + 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); + } } void ModelRendererSP::drawLeadStatus(QPainter &painter, int height, int width) { @@ -121,19 +110,16 @@ void ModelRendererSP::drawLeadStatus(QPainter &painter, int height, int width) { } if (has_lead_one) { - drawLeadStatusAtPosition(painter, lead_one, lead_vertices[0], height, width, "L1"); + drawLeadStatusPosition(painter, lead_one, lead_vertices[0], height, width); } if (has_lead_two && std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0) { - drawLeadStatusAtPosition(painter, lead_two, lead_vertices[1], height, width, "L2"); + drawLeadStatusPosition(painter, lead_two, lead_vertices[1], height, width); } } -void ModelRendererSP::drawLeadStatusAtPosition(QPainter &painter, - const cereal::RadarState::LeadData::Reader &lead_data, - const QPointF &chevron_pos, - int height, int width, - const QString &label) { +void ModelRendererSP::drawLeadStatusPosition(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, + const QPointF &chevron_pos, int height, int width) { float d_rel = lead_data.getDRel(); float v_rel = lead_data.getVRel(); auto *s = uiState(); @@ -223,3 +209,36 @@ void ModelRendererSP::drawLeadStatusAtPosition(QPainter &painter, painter.setPen(Qt::NoPen); } + +void ModelRendererSP::drawRainbowPath(QPainter &painter, const QRect &surface_rect) { + // Simple time-based animation + float time_offset = std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count() / 1000.0f; + + // simple linear gradient from bottom to top + QLinearGradient bg(0, surface_rect.height(), 0, 0); + + // evenly spaced colors across the spectrum + // The animation shifts the entire spectrum smoothly + float animation_speed = 40.0f; // speed vroom vroom + float hue_offset = fmod(time_offset * animation_speed, 360.0f); + + // 6-8 color stops for smooth transitions more color makes it laggy + const int num_stops = 7; + for (int i = 0; i < num_stops; i++) { + float position = static_cast(i) / (num_stops - 1); + + float hue = fmod(hue_offset + position * 360.0f, 360.0f); + float saturation = 0.9f; + float lightness = 0.6f; + + // Alpha fades out towards the far end of the path + float alpha = 0.8f * (1.0f - position * 0.3f); + + QColor color = QColor::fromHslF(hue / 360.0f, saturation, lightness, alpha); + bg.setColorAt(position, color); + } + + painter.setBrush(bg); + painter.drawPolygon(track_vertices); +} diff --git a/selfdrive/ui/sunnypilot/qt/onroad/model.h b/selfdrive/ui/sunnypilot/qt/onroad/model.h index 73999f0059..68068f2068 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/model.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/model.h @@ -13,17 +13,15 @@ class ModelRendererSP : public ModelRenderer { public: ModelRendererSP() = default; + void draw(QPainter &painter, const QRect &surface_rect); + 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; - - // Lead status display methods void drawLeadStatus(QPainter &painter, int height, int width); - void drawLeadStatusAtPosition(QPainter &painter, - const cereal::RadarState::LeadData::Reader &lead_data, - const QPointF &chevron_pos, - int height, int width, - const QString &label); + void drawLeadStatusPosition(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, + const QPointF &chevron_pos, int height, int width); + void drawBlindspot(QPainter &painter, const QRect &surface_rect, bool left_blindspot, bool right_blindspot); + void drawRainbowPath(QPainter &painter, const QRect &surface_rect); QPolygonF left_blindspot_vertices; QPolygonF right_blindspot_vertices; From 7229c7541eac0fc321252d4c53a98534ec1fbb07 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon, 13 Oct 2025 19:02:53 -0700 Subject: [PATCH 17/72] capnp: consolidate TurnDirection enum (#1370) Co-authored-by: Jason Wen --- cereal/custom.capnp | 10 +++++----- selfdrive/controls/lib/desire_helper.py | 10 +++++----- sunnypilot/selfdrive/controls/lib/lane_turn_desire.py | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 833845d36d..20f0984620 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -404,12 +404,12 @@ struct LiveMapDataSP @0xf416ec09499d9d19 { struct ModelDataV2SP @0xa1680744031fdb2d { laneTurnDirection @0 :TurnDirection; -} -enum TurnDirection { - none @0; - turnLeft @1; - turnRight @2; + enum TurnDirection { + none @0; + turnLeft @1; + turnRight @2; + } } struct CustomReserved10 @0xcb9fd56c7057593a { diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index e72d464d06..bf24fe2602 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -32,9 +32,9 @@ DESIRES = { } TURN_DESIRES = { - custom.TurnDirection.none: log.Desire.none, - custom.TurnDirection.turnLeft: log.Desire.turnLeft, - custom.TurnDirection.turnRight: log.Desire.turnRight, + custom.ModelDataV2SP.TurnDirection.none: log.Desire.none, + custom.ModelDataV2SP.TurnDirection.turnLeft: log.Desire.turnLeft, + custom.ModelDataV2SP.TurnDirection.turnRight: log.Desire.turnRight, } @@ -49,7 +49,7 @@ class DesireHelper: self.desire = log.Desire.none self.alc = AutoLaneChangeController(self) self.lane_turn_controller = LaneTurnController(self) - self.lane_turn_direction = custom.TurnDirection.none + self.lane_turn_direction = custom.ModelDataV2SP.TurnDirection.none @staticmethod def get_lane_change_direction(CS): @@ -126,7 +126,7 @@ class DesireHelper: self.prev_one_blinker = one_blinker - if self.lane_turn_direction != custom.TurnDirection.none: + if self.lane_turn_direction != custom.ModelDataV2SP.TurnDirection.none: self.desire = TURN_DESIRES[self.lane_turn_direction] else: self.desire = DESIRES[self.lane_change_direction][self.lane_change_state] diff --git a/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py b/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py index 00ce026abb..7767fdae74 100644 --- a/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py +++ b/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py @@ -15,7 +15,7 @@ LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS class LaneTurnController: def __init__(self, desire_helper): self.DH = desire_helper - self.turn_direction = custom.TurnDirection.none + self.turn_direction = custom.ModelDataV2SP.TurnDirection.none self.params = Params() self.lane_turn_value = float(self.params.get("LaneTurnValue", return_default=True)) * CV.MPH_TO_MS self.param_read_counter = 0 @@ -33,13 +33,13 @@ class LaneTurnController: def update_lane_turn(self, blindspot_left: bool, blindspot_right: bool, left_blinker: bool, right_blinker: bool, v_ego: float) -> None: if left_blinker and not right_blinker and v_ego < self.lane_turn_value and not blindspot_left: - self.turn_direction = custom.TurnDirection.turnLeft + self.turn_direction = custom.ModelDataV2SP.TurnDirection.turnLeft elif right_blinker and not left_blinker and v_ego < self.lane_turn_value and not blindspot_right: - self.turn_direction = custom.TurnDirection.turnRight + self.turn_direction = custom.ModelDataV2SP.TurnDirection.turnRight else: - self.turn_direction = custom.TurnDirection.none + self.turn_direction = custom.ModelDataV2SP.TurnDirection.none def get_turn_direction(self): if not self.enabled: - return custom.TurnDirection.none + return custom.ModelDataV2SP.TurnDirection.none return self.turn_direction From 59c64acc2901e7156fddadc9194e99e3fa44ebd5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 13 Oct 2025 22:26:47 -0400 Subject: [PATCH 18/72] Subaru: Stop and Go support (beta) (#1375) * Subaru: Stop and Go auto-resume support * bump * bump * fix * bump * fix init * wat * use just standstill for now * Revert "use just standstill for now" This reverts commit f72cce68921ffe15b8914ce42d213758a1807619. * bump * bump * fix it * only send at 10 * bump * fix type * forget about planner resume, it sucks * try to send off_accel * still need it * always send * disable safety checks for now * same * more * all the time for both * don't need i guess * bump * try 15 frames per try * all should have it * try 3 for all * use throttle for all preglobal? * bump * bump * separate thresholds between preglobal and global * longer wait before sending * shorter time but immediately resend * quick * new timeout * about to cry * same thing but another try * no need * round 3 * try 1.4 * lower! * 1.2 * last try * beta asf * bump --- common/params_keys.h | 2 + opendbc_repo | 2 +- .../settings/vehicle/subaru_settings.cc | 45 +++++++++++++++++++ .../settings/vehicle/subaru_settings.h | 31 +++++++++++++ sunnypilot/selfdrive/car/interfaces.py | 6 +++ 5 files changed, 85 insertions(+), 1 deletion(-) diff --git a/common/params_keys.h b/common/params_keys.h index 3b5d02a429..ecb73a9456 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -205,6 +205,8 @@ inline static std::unordered_map keys = { // sunnypilot car specific params {"HyundaiLongitudinalTuning", {PERSISTENT | BACKUP, INT, "0"}}, + {"SubaruStopAndGo", {PERSISTENT | BACKUP, BOOL, "0"}}, + {"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}}, {"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}}, {"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}}, diff --git a/opendbc_repo b/opendbc_repo index b592ecdd3b..b8a00bddda 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit b592ecdd3b571a1acee0c04726117a137cec5832 +Subproject commit b8a00bddda562f981b24e099a3850209579e890a diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.cc index 47c4057f44..302740a94a 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.cc @@ -8,7 +8,52 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.h" SubaruSettings::SubaruSettings(QWidget *parent) : BrandSettingsInterface(parent) { + stopAndGoToggle = new ParamControl("SubaruStopAndGo", tr("Stop and Go (Beta)"), "", ""); + stopAndGoToggle->setConfirmation(true, false); + list->addItem(stopAndGoToggle); + + stopAndGoManualParkingBrakeToggle = new ParamControl( + "SubaruStopAndGoManualParkingBrake", + tr("Stop and Go for Manual Parking Brake (Beta)"), + "", + "" + ); + stopAndGoManualParkingBrakeToggle->setConfirmation(true, false); + list->addItem(stopAndGoManualParkingBrakeToggle); } void SubaruSettings::updateSettings() { + auto cp_bytes = params.get("CarParamsPersistent"); + if (!cp_bytes.empty()) { + AlignedBuffer aligned_buf; + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size())); + cereal::CarParams::Reader CP = cmsg.getRoot(); + + is_subaru = CP.getBrand() == "subaru"; + + if (is_subaru) { + if (!(CP.getFlags() & (SUBARU_FLAG_GLOBAL_GEN2 | SUBARU_FLAG_HYBRID))) { + has_stop_and_go = true; + } + } + } else { + is_subaru = false; + has_stop_and_go = false; + } + + bool stop_and_go_disabled = !offroad || !has_stop_and_go; + QString stop_and_go_desc = stopAndGoDescriptionBuilder(stopAndGoDesc); + QString stop_and_go_manual_parking_brake_desc = stopAndGoDescriptionBuilder(stopAndGoManualParkingBrakeDesc); + if (stop_and_go_disabled) { + stop_and_go_desc = stopAndGoDescriptionBuilder(stopAndGoDesc, stopAndGoDisabledMsg()); + stop_and_go_manual_parking_brake_desc = stopAndGoDescriptionBuilder(stopAndGoManualParkingBrakeDesc, stopAndGoDisabledMsg()); + } + + stopAndGoToggle->setEnabled(has_stop_and_go); + stopAndGoToggle->setDescription(stop_and_go_desc); + stopAndGoToggle->showDescription(); + + stopAndGoManualParkingBrakeToggle->setEnabled(has_stop_and_go); + stopAndGoManualParkingBrakeToggle->setDescription(stop_and_go_manual_parking_brake_desc); + stopAndGoManualParkingBrakeToggle->showDescription(); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.h index a715951ad9..2bb160beba 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.h @@ -14,6 +14,9 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h" #include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +const int SUBARU_FLAG_GLOBAL_GEN2 = 4; +const int SUBARU_FLAG_HYBRID = 32; + class SubaruSettings : public BrandSettingsInterface { Q_OBJECT @@ -23,4 +26,32 @@ public: private: bool offroad = false; + bool is_subaru; + bool has_stop_and_go; + + ParamControl* stopAndGoToggle; + ParamControl* stopAndGoManualParkingBrakeToggle; + + QString stopAndGoDesc = tr("Experimental feature to enable auto-resume during stop-and-go for certain supported Subaru platforms."); + QString stopAndGoManualParkingBrakeDesc = tr("Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation!"); + + QString stopAndGoDisabledMsg() const { + if (is_subaru && !has_stop_and_go) { + return tr("This feature is currently not available on this platform."); + } + + if (!is_subaru) { + return tr("Start the car to check car compatibility."); + } + + if (!offroad) { + return tr("Enable \"Always Offroad\" in Device panel, or turn vehicle off to toggle."); + } + + return QString(); + } + + static QString stopAndGoDescriptionBuilder(const QString &base_description, const QString &custom_description = "") { + return "" + custom_description + "

" + base_description; + } }; diff --git a/sunnypilot/selfdrive/car/interfaces.py b/sunnypilot/selfdrive/car/interfaces.py index ed0c11fb19..3072cde8cd 100644 --- a/sunnypilot/selfdrive/car/interfaces.py +++ b/sunnypilot/selfdrive/car/interfaces.py @@ -84,4 +84,10 @@ def initialize_params(params) -> list[dict[str, Any]]: "HyundaiLongitudinalTuning" ]) + # subaru + keys.extend([ + "SubaruStopAndGo", + "SubaruStopAndGoManualParkingBrake", + ]) + return [{k: params.get(k, return_default=True)} for k in keys] From 339bc0b8b3c4a192aa4e982039bc2ac28246c080 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 14 Oct 2025 00:19:21 -0400 Subject: [PATCH 19/72] Revert "capnp: consolidate TurnDirection enum" (#1376) Revert "capnp: consolidate TurnDirection enum (#1370)" This reverts commit 7229c7541eac0fc321252d4c53a98534ec1fbb07. --- cereal/custom.capnp | 10 +++++----- selfdrive/controls/lib/desire_helper.py | 10 +++++----- sunnypilot/selfdrive/controls/lib/lane_turn_desire.py | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 20f0984620..833845d36d 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -404,12 +404,12 @@ struct LiveMapDataSP @0xf416ec09499d9d19 { struct ModelDataV2SP @0xa1680744031fdb2d { laneTurnDirection @0 :TurnDirection; +} - enum TurnDirection { - none @0; - turnLeft @1; - turnRight @2; - } +enum TurnDirection { + none @0; + turnLeft @1; + turnRight @2; } struct CustomReserved10 @0xcb9fd56c7057593a { diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index bf24fe2602..e72d464d06 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -32,9 +32,9 @@ DESIRES = { } TURN_DESIRES = { - custom.ModelDataV2SP.TurnDirection.none: log.Desire.none, - custom.ModelDataV2SP.TurnDirection.turnLeft: log.Desire.turnLeft, - custom.ModelDataV2SP.TurnDirection.turnRight: log.Desire.turnRight, + custom.TurnDirection.none: log.Desire.none, + custom.TurnDirection.turnLeft: log.Desire.turnLeft, + custom.TurnDirection.turnRight: log.Desire.turnRight, } @@ -49,7 +49,7 @@ class DesireHelper: self.desire = log.Desire.none self.alc = AutoLaneChangeController(self) self.lane_turn_controller = LaneTurnController(self) - self.lane_turn_direction = custom.ModelDataV2SP.TurnDirection.none + self.lane_turn_direction = custom.TurnDirection.none @staticmethod def get_lane_change_direction(CS): @@ -126,7 +126,7 @@ class DesireHelper: self.prev_one_blinker = one_blinker - if self.lane_turn_direction != custom.ModelDataV2SP.TurnDirection.none: + if self.lane_turn_direction != custom.TurnDirection.none: self.desire = TURN_DESIRES[self.lane_turn_direction] else: self.desire = DESIRES[self.lane_change_direction][self.lane_change_state] diff --git a/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py b/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py index 7767fdae74..00ce026abb 100644 --- a/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py +++ b/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py @@ -15,7 +15,7 @@ LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS class LaneTurnController: def __init__(self, desire_helper): self.DH = desire_helper - self.turn_direction = custom.ModelDataV2SP.TurnDirection.none + self.turn_direction = custom.TurnDirection.none self.params = Params() self.lane_turn_value = float(self.params.get("LaneTurnValue", return_default=True)) * CV.MPH_TO_MS self.param_read_counter = 0 @@ -33,13 +33,13 @@ class LaneTurnController: def update_lane_turn(self, blindspot_left: bool, blindspot_right: bool, left_blinker: bool, right_blinker: bool, v_ego: float) -> None: if left_blinker and not right_blinker and v_ego < self.lane_turn_value and not blindspot_left: - self.turn_direction = custom.ModelDataV2SP.TurnDirection.turnLeft + self.turn_direction = custom.TurnDirection.turnLeft elif right_blinker and not left_blinker and v_ego < self.lane_turn_value and not blindspot_right: - self.turn_direction = custom.ModelDataV2SP.TurnDirection.turnRight + self.turn_direction = custom.TurnDirection.turnRight else: - self.turn_direction = custom.ModelDataV2SP.TurnDirection.none + self.turn_direction = custom.TurnDirection.none def get_turn_direction(self): if not self.enabled: - return custom.ModelDataV2SP.TurnDirection.none + return custom.TurnDirection.none return self.turn_direction From 7f5342f3784c841b275fcbf213ef5e339f1fe991 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 14 Oct 2025 01:13:20 -0400 Subject: [PATCH 20/72] soundd: custom audible alerts (#1377) * Revert "capnp: consolidate TurnDirection enum (#1370)" This reverts commit 7229c7541eac0fc321252d4c53a98534ec1fbb07. * soundd: custom audible alerts * comment --- cereal/custom.capnp | 39 +++++++++++++++++++++++++++++++++++++++ selfdrive/ui/soundd.py | 9 ++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 833845d36d..5243380d31 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -69,6 +69,45 @@ struct LeadData { struct SelfdriveStateSP @0x81c2f05a394cf4af { mads @0 :ModularAssistiveDrivingSystem; intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement; + + enum AudibleAlert { + none @0; + + engage @1; + disengage @2; + refuse @3; + + warningSoft @4; + warningImmediate @5; + + prompt @6; + promptRepeat @7; + promptDistracted @8; + + # unused, these are reserved for upstream events so we don't collide + reserved9 @9; + reserved10 @10; + reserved11 @11; + reserved12 @12; + reserved13 @13; + reserved14 @14; + reserved15 @15; + reserved16 @16; + reserved17 @17; + reserved18 @18; + reserved19 @19; + reserved20 @20; + reserved21 @21; + reserved22 @22; + reserved23 @23; + reserved24 @24; + reserved25 @25; + reserved26 @26; + reserved27 @27; + reserved28 @28; + reserved29 @29; + reserved30 @30; + } } struct ModelManagerSP @0xaedffd8f31e7b55d { diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index a94456efe0..485c406266 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -4,7 +4,7 @@ import time import wave -from cereal import car, messaging +from cereal import car, messaging, custom from openpilot.common.basedir import BASEDIR from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import Ratekeeper @@ -26,8 +26,13 @@ AMBIENT_DB = 30 # DB where MIN_VOLUME is applied DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied AudibleAlert = car.CarControl.HUDControl.AudibleAlert +AudibleAlertSP = custom.SelfdriveStateSP.AudibleAlert +sound_list_sp: dict[int, tuple[str, int | None, float]] = { + # AudibleAlertSP, file name, play count (none for infinite) +} + sound_list: dict[int, tuple[str, int | None, float]] = { # AudibleAlert, file name, play count (none for infinite) AudibleAlert.engage: ("engage.wav", 1, MAX_VOLUME), @@ -40,6 +45,8 @@ sound_list: dict[int, tuple[str, int | None, float]] = { AudibleAlert.warningSoft: ("warning_soft.wav", None, MAX_VOLUME), AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME), + + **sound_list_sp, } def check_selfdrive_timeout_alert(sm): From 4bd020e92b0ee7fe678985efdb7cd2efd6ecf9cd Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 14 Oct 2025 09:19:26 -0400 Subject: [PATCH 21/72] Speed Limit Assist: audible alerts for certain states (#1378) --- cereal/custom.capnp | 3 +++ selfdrive/assets/sounds/prompt_single_high.wav | 3 +++ selfdrive/assets/sounds/prompt_single_low.wav | 3 +++ selfdrive/ui/soundd.py | 2 ++ sunnypilot/selfdrive/selfdrived/events.py | 9 +++++---- 5 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 selfdrive/assets/sounds/prompt_single_high.wav create mode 100644 selfdrive/assets/sounds/prompt_single_low.wav diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 5243380d31..e81749dbac 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -107,6 +107,9 @@ struct SelfdriveStateSP @0x81c2f05a394cf4af { reserved28 @28; reserved29 @29; reserved30 @30; + + promptSingleLow @31; + promptSingleHigh @32; } } diff --git a/selfdrive/assets/sounds/prompt_single_high.wav b/selfdrive/assets/sounds/prompt_single_high.wav new file mode 100644 index 0000000000..202483d17f --- /dev/null +++ b/selfdrive/assets/sounds/prompt_single_high.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbfa5858c0a672411ffdc691efdecb06d01ae458cc1df409bcf3fdeaa4756f72 +size 34638 diff --git a/selfdrive/assets/sounds/prompt_single_low.wav b/selfdrive/assets/sounds/prompt_single_low.wav new file mode 100644 index 0000000000..925401ea27 --- /dev/null +++ b/selfdrive/assets/sounds/prompt_single_low.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db9671bb03e01f119bba1eb6cc0507e0f039ac4e5b7f9f839a87071c52e86e56 +size 44416 diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index 485c406266..01c7c68949 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -31,6 +31,8 @@ AudibleAlertSP = custom.SelfdriveStateSP.AudibleAlert sound_list_sp: dict[int, tuple[str, int | None, float]] = { # AudibleAlertSP, file name, play count (none for infinite) + AudibleAlertSP.promptSingleLow: ("prompt_single_low.wav", 1, MAX_VOLUME), + AudibleAlertSP.promptSingleHigh: ("prompt_single_high.wav", 1, MAX_VOLUME), } sound_list: dict[int, tuple[str, int | None, float]] = { diff --git a/sunnypilot/selfdrive/selfdrived/events.py b/sunnypilot/selfdrive/selfdrived/events.py index e9f4b29bae..5cd7255bc3 100644 --- a/sunnypilot/selfdrive/selfdrived/events.py +++ b/sunnypilot/selfdrive/selfdrived/events.py @@ -11,6 +11,7 @@ AlertSize = log.SelfdriveState.AlertSize AlertStatus = log.SelfdriveState.AlertStatus VisualAlert = car.CarControl.HUDControl.VisualAlert AudibleAlert = car.CarControl.HUDControl.AudibleAlert +AudibleAlertSP = custom.SelfdriveStateSP.AudibleAlert EventNameSP = custom.OnroadEventSP.EventName @@ -58,7 +59,7 @@ def speed_limit_pre_active_alert(CP: car.CarParams, CS: car.CarState, sm: messag "Speed Limit Assist: Activation Required", alert_2_str, AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.none, .1) + Priority.LOW, VisualAlert.none, AudibleAlertSP.promptSingleLow, .1) class EventsSP(EventsBase): @@ -202,7 +203,7 @@ EVENTS_SP: dict[int, dict[str, Alert | AlertCallbackType]] = { "Automatically adjusting to the posted speed limit", "", AlertStatus.normal, AlertSize.small, - Priority.LOW, VisualAlert.none, AudibleAlert.none, 5.), + Priority.LOW, VisualAlert.none, AudibleAlertSP.promptSingleHigh, 5.), }, EventNameSP.speedLimitChanged: { @@ -210,7 +211,7 @@ EVENTS_SP: dict[int, dict[str, Alert | AlertCallbackType]] = { "Set speed changed", "", AlertStatus.normal, AlertSize.small, - Priority.LOW, VisualAlert.none, AudibleAlert.none, 5.), + Priority.LOW, VisualAlert.none, AudibleAlertSP.promptSingleHigh, 5.), }, EventNameSP.speedLimitPreActive: { @@ -222,7 +223,7 @@ EVENTS_SP: dict[int, dict[str, Alert | AlertCallbackType]] = { "Automatically adjusting to the last speed limit", "", AlertStatus.normal, AlertSize.small, - Priority.LOW, VisualAlert.none, AudibleAlert.none, 5.), + Priority.LOW, VisualAlert.none, AudibleAlertSP.promptSingleHigh, 5.), }, EventNameSP.e2eChime: { From fec6382b964c304d6729ad1b26913377760aa949 Mon Sep 17 00:00:00 2001 From: Nayan Date: Tue, 14 Oct 2025 11:29:27 -0400 Subject: [PATCH 22/72] UI: Fix Speed Limit Assist (SLA) Translations (#1379) Fix SLA Translations --- .../longitudinal/speed_limit/helpers.h | 24 ++-- .../speed_limit/speed_limit_policy.cc | 10 +- .../speed_limit/speed_limit_settings.cc | 14 +-- selfdrive/ui/translations/main_ko.ts | 112 +++++++++--------- 4 files changed, 80 insertions(+), 80 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/helpers.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/helpers.h index 295072d1ef..6c02d627fa 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/helpers.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/helpers.h @@ -13,9 +13,9 @@ enum class SpeedLimitOffsetType { }; inline const QString SpeedLimitOffsetTypeTexts[]{ - QObject::tr("None"), - QObject::tr("Fixed"), - QObject::tr("Percent"), + QT_TRANSLATE_NOOP("SpeedLimitSettings", "None"), + QT_TRANSLATE_NOOP("SpeedLimitSettings", "Fixed"), + QT_TRANSLATE_NOOP("SpeedLimitSettings", "Percent"), }; enum class SpeedLimitSourcePolicy { @@ -27,11 +27,11 @@ enum class SpeedLimitSourcePolicy { }; inline const QString SpeedLimitSourcePolicyTexts[]{ - QObject::tr("Car\nOnly"), - QObject::tr("Map\nOnly"), - QObject::tr("Car\nFirst"), - QObject::tr("Map\nFirst"), - QObject::tr("Combined\nData") + QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Car\nOnly"), + QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Map\nOnly"), + QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Car\nFirst"), + QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Map\nFirst"), + QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Combined\nData") }; enum class SpeedLimitMode { @@ -42,8 +42,8 @@ enum class SpeedLimitMode { }; inline const QString SpeedLimitModeTexts[]{ - QObject::tr("Off"), - QObject::tr("Information"), - QObject::tr("Warning"), - QObject::tr("Assist"), + QT_TRANSLATE_NOOP("SpeedLimitSettings", "Off"), + QT_TRANSLATE_NOOP("SpeedLimitSettings", "Information"), + QT_TRANSLATE_NOOP("SpeedLimitSettings", "Warning"), + QT_TRANSLATE_NOOP("SpeedLimitSettings", "Assist"), }; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_policy.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_policy.cc index 764a8e0208..c54ff11f69 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_policy.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_policy.cc @@ -23,11 +23,11 @@ SpeedLimitPolicy::SpeedLimitPolicy(QWidget *parent) : QWidget(parent) { ListWidgetSP *list = new ListWidgetSP(this); std::vector speed_limit_policy_texts{ - SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::CAR_ONLY)], - SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::MAP_ONLY)], - SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::CAR_FIRST)], - SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::MAP_FIRST)], - SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::COMBINED)] + tr(SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::CAR_ONLY)].toStdString().c_str()), + tr(SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::MAP_ONLY)].toStdString().c_str()), + tr(SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::CAR_FIRST)].toStdString().c_str()), + tr(SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::MAP_FIRST)].toStdString().c_str()), + tr(SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::COMBINED)].toStdString().c_str()) }; speed_limit_policy = new ButtonParamControlSP( "SpeedLimitPolicy", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc index 3efbfeed86..f64198f97e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc @@ -25,10 +25,10 @@ SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent) speedLimitPolicyScreen = new SpeedLimitPolicy(this); std::vector speed_limit_mode_texts{ - SpeedLimitModeTexts[static_cast(SpeedLimitMode::OFF)], - SpeedLimitModeTexts[static_cast(SpeedLimitMode::INFORMATION)], - SpeedLimitModeTexts[static_cast(SpeedLimitMode::WARNING)], - SpeedLimitModeTexts[static_cast(SpeedLimitMode::ASSIST)], + tr(SpeedLimitModeTexts[static_cast(SpeedLimitMode::OFF)].toStdString().c_str()), + tr(SpeedLimitModeTexts[static_cast(SpeedLimitMode::INFORMATION)].toStdString().c_str()), + tr(SpeedLimitModeTexts[static_cast(SpeedLimitMode::WARNING)].toStdString().c_str()), + tr(SpeedLimitModeTexts[static_cast(SpeedLimitMode::ASSIST)].toStdString().c_str()) }; speed_limit_mode_settings = new ButtonParamControlSP( "SpeedLimitMode", @@ -64,9 +64,9 @@ SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent) QVBoxLayout *offsetLayout = new QVBoxLayout(offsetFrame); std::vector speed_limit_offset_texts{ - SpeedLimitOffsetTypeTexts[static_cast(SpeedLimitOffsetType::NONE)], - SpeedLimitOffsetTypeTexts[static_cast(SpeedLimitOffsetType::FIXED)], - SpeedLimitOffsetTypeTexts[static_cast(SpeedLimitOffsetType::PERCENT)] + tr(SpeedLimitOffsetTypeTexts[static_cast(SpeedLimitOffsetType::NONE)].toStdString().c_str()), + tr(SpeedLimitOffsetTypeTexts[static_cast(SpeedLimitOffsetType::FIXED)].toStdString().c_str()), + tr(SpeedLimitOffsetTypeTexts[static_cast(SpeedLimitOffsetType::PERCENT)].toStdString().c_str()) }; speed_limit_offset_settings = new ButtonParamControlSP( "SpeedLimitOffsetType", diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index d86468ae6e..164ce09f07 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -1778,62 +1778,6 @@ Warning: You are on a metered connection! sunnypilot sunnypilot - - None - 없음 - - - Fixed - 고정 - - - Percent - 비율 - - - Car -Only - 차량만 - - - Map -Only - 지도만 - - - Car -First - 차량 -우선 - - - Map -First - 지도 -우선 - - - Combined -Data - 결합 -데이터 - - - Off - 끄기 - - - Information - 정보 - - - Warning - 경고 - - - Assist - 보조 -
SettingsWindow @@ -2198,6 +2142,34 @@ Data ⦿ Combined: Use combined Speed Limit data from Car & OpenStreetMaps ⦿ 결합: 차량 및 OpenStreetMaps의 속도 제한 결합 데이터 사용 + + Car +Only + 차량만 + + + Map +Only + 지도만 + + + Car +First + 차량 +우선 + + + Map +First + 지도 +우선 + + + Combined +Data + 결합 +데이터 + SpeedLimitSettings @@ -2245,6 +2217,34 @@ Data ⦿ Assist: Adjusts the vehicle's cruise speed based on the current road's speed limit when operating the +/- buttons. ⦿ 보조: +/- 버튼을 조작할 때 현재 도로의 제한 속도를 기준으로 차량의 크루즈 속도를 조정합니다. + + None + 없음 + + + Fixed + 고정 + + + Percent + 비율 + + + Off + 끄기 + + + Information + 정보 + + + Warning + 경고 + + + Assist + 보조 + SshControl From d3e3628a9586f131b23a1c1cac9520c21a3626ff Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 14 Oct 2025 11:40:42 -0400 Subject: [PATCH 23/72] ui: only draw ahead speed limit if it's parsed from OSM (#1380) --- selfdrive/ui/sunnypilot/qt/onroad/hud.cc | 4 +++- selfdrive/ui/sunnypilot/qt/onroad/hud.h | 1 + .../controls/lib/speed_limit/speed_limit_resolver.py | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index 8c913cec17..fa57e2b334 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -46,6 +46,7 @@ void HudRendererSP::updateState(const UIState &s) { speedLimitValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitValid(); speedLimitLastValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLastValid(); speedLimitFinalLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitFinalLast() * speedConv; + speedLimitSource = lp_sp.getSpeedLimit().getResolver().getSource(); speedLimitMode = static_cast(s.scene.speed_limit_mode); speedLimitAssistState = lp_sp.getSpeedLimit().getAssist().getState(); speedLimitAssistActive = lp_sp.getSpeedLimit().getAssist().getActive(); @@ -547,7 +548,8 @@ void HudRendererSP::drawSpeedLimitSigns(QPainter &p, QRect &sign_rect) { } void HudRendererSP::drawUpcomingSpeedLimit(QPainter &p) { - bool speed_limit_ahead = speedLimitAheadValid && speedLimitAhead > 0 && speedLimitAhead != speedLimit && speedLimitAheadValidFrame > 0; + bool speed_limit_ahead = speedLimitAheadValid && speedLimitAhead > 0 && speedLimitAhead != speedLimit && speedLimitAheadValidFrame > 0 && + speedLimitSource == cereal::LongitudinalPlanSP::SpeedLimit::Source::MAP; if (!speed_limit_ahead) { return; } diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.h b/selfdrive/ui/sunnypilot/qt/onroad/hud.h index bc7ee81de3..f9135ee9ef 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.h @@ -83,6 +83,7 @@ private: bool speedLimitValid; bool speedLimitLastValid; float speedLimitFinalLast; + cereal::LongitudinalPlanSP::SpeedLimit::Source speedLimitSource; bool speedLimitAheadValid; float speedLimitAhead; float speedLimitAheadDistance; diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py index 459334d156..35965c0e18 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py @@ -142,6 +142,7 @@ class SpeedLimitResolver: self.limit_solutions[SpeedLimitSource.map] = speed_limit self.distance_solutions[SpeedLimitSource.map] = 0. + # FIXME-SP: this is not working as expected if 0. < next_speed_limit < self.v_ego: adapt_time = (next_speed_limit - self.v_ego) / LIMIT_ADAPT_ACC adapt_distance = self.v_ego * adapt_time + 0.5 * LIMIT_ADAPT_ACC * adapt_time ** 2 From 9a14baac4deac77ef4d9171a3ce4af2754317035 Mon Sep 17 00:00:00 2001 From: Nayan Date: Tue, 14 Oct 2025 20:01:42 -0400 Subject: [PATCH 24/72] Green Light and Lead Departure alerts improvements (#1381) --- .../controls/lib/e2e_alerts_helper.py | 64 +++++++++++++++---- sunnypilot/selfdrive/selfdrived/events.py | 2 +- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/sunnypilot/selfdrive/controls/lib/e2e_alerts_helper.py b/sunnypilot/selfdrive/controls/lib/e2e_alerts_helper.py index 0135b2806c..5a92d878d6 100644 --- a/sunnypilot/selfdrive/controls/lib/e2e_alerts_helper.py +++ b/sunnypilot/selfdrive/controls/lib/e2e_alerts_helper.py @@ -12,47 +12,83 @@ from openpilot.common.realtime import DT_MDL from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP -TRIGGER_THRESHOLD = 30 +GREEN_LIGHT_X_THRESHOLD = 30 class E2EAlertsHelper: def __init__(self): self._params = Params() - self._frame = -1 + self.frame = -1 self.green_light_alert = False self.green_light_alert_enabled = self._params.get_bool("GreenLightAlert") self.lead_depart_alert = False self.lead_depart_alert_enabled = self._params.get_bool("LeadDepartAlert") + self.alert_allowed = False + self.green_light_alert_count = 0 + self.last_lead_distance = -1 + self.last_moving_frame = -1 + def _read_params(self) -> None: - if self._frame % int(PARAMS_UPDATE_PERIOD / DT_MDL) == 0: + if self.frame % int(PARAMS_UPDATE_PERIOD / DT_MDL) == 0: self.green_light_alert_enabled = self._params.get_bool("GreenLightAlert") self.lead_depart_alert_enabled = self._params.get_bool("LeadDepartAlert") - self._frame += 1 - def update(self, sm: messaging.SubMaster, events_sp: EventsSP) -> None: self._read_params() - if not (self.green_light_alert_enabled or self.lead_depart_alert_enabled): - return - CS = sm['carState'] CC = sm['carControl'] model_x = sm['modelV2'].position.x max_idx = len(model_x) - 1 has_lead = sm['radarState'].leadOne.status - lead_vRel: float = sm['radarState'].leadOne.vRel + lead_dRel = sm['radarState'].leadOne.dRel + standstill = CS.standstill + moving = not standstill and CS.vEgo > 0.1 + _allowed = standstill and not CS.gasPressed and not CC.enabled - # Green light alert - self.green_light_alert = (self.green_light_alert_enabled and model_x[max_idx] > TRIGGER_THRESHOLD - and not has_lead and CS.standstill and not CS.gasPressed and not CC.enabled) + if moving: + self.last_moving_frame = self.frame + recent_moving = self.last_moving_frame == -1 or (self.frame - self.last_moving_frame) * DT_MDL < 2.0 + + if standstill and not recent_moving: + self.alert_allowed = True + elif not standstill: + self.alert_allowed = False + self.green_light_alert_count = 0 + self.last_lead_distance = -1 + + # Green Light Alert + _green_light_alert = False + if self.green_light_alert_enabled and _allowed and not has_lead and model_x[max_idx] > GREEN_LIGHT_X_THRESHOLD: + if self.alert_allowed: + self.green_light_alert_count += 1 + else: + self.green_light_alert_count = 0 + + if self.green_light_alert_count > 2 and self.alert_allowed: + _green_light_alert = True + self.alert_allowed = False + else: + self.green_light_alert_count = 0 + + self.green_light_alert = _green_light_alert # Lead Departure Alert - self.lead_depart_alert = (self.lead_depart_alert_enabled and CS.standstill and model_x[max_idx] > 30 - and has_lead and lead_vRel > 1 and not CS.gasPressed) + _lead_depart_alert = False + if self.lead_depart_alert_enabled and _allowed and has_lead: + if self.last_lead_distance == -1 or lead_dRel < self.last_lead_distance: + self.last_lead_distance = lead_dRel + + if self.last_lead_distance != -1 and (lead_dRel - self.last_lead_distance > 1.0) and self.alert_allowed: + _lead_depart_alert = True + self.alert_allowed = False + + self.lead_depart_alert = _lead_depart_alert if self.green_light_alert or self.lead_depart_alert: events_sp.add(custom.OnroadEventSP.EventName.e2eChime) + + self.frame += 1 diff --git a/sunnypilot/selfdrive/selfdrived/events.py b/sunnypilot/selfdrive/selfdrived/events.py index 5cd7255bc3..5d5424bb16 100644 --- a/sunnypilot/selfdrive/selfdrived/events.py +++ b/sunnypilot/selfdrive/selfdrived/events.py @@ -231,6 +231,6 @@ EVENTS_SP: dict[int, dict[str, Alert | AlertCallbackType]] = { "", "", AlertStatus.normal, AlertSize.none, - Priority.MID, VisualAlert.none, AudibleAlert.prompt, 0.1), + Priority.MID, VisualAlert.none, AudibleAlert.prompt, 3.), }, } From 734151f59bf535054719e0ffc9a9621bbe963da3 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Tue, 14 Oct 2025 20:32:10 -0700 Subject: [PATCH 25/72] Reapply "capnp: consolidate TurnDirection enum" (#1376) (#1382) * Reapply "capnp: consolidate TurnDirection enum" (#1376) This reverts commit 339bc0b8b3c4a192aa4e982039bc2ac28246c080. * cache it * format --------- Co-authored-by: Jason Wen --- cereal/custom.capnp | 10 +- selfdrive/controls/lib/desire_helper.py | 11 +- selfdrive/selfdrived/selfdrived.py | 5 +- .../controls/lib/lane_turn_desire.py | 12 +- .../lib/tests/test_lane_turn_desire.py | 160 +++++++++--------- 5 files changed, 101 insertions(+), 97 deletions(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index e81749dbac..5c0a004fa6 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -446,12 +446,12 @@ struct LiveMapDataSP @0xf416ec09499d9d19 { struct ModelDataV2SP @0xa1680744031fdb2d { laneTurnDirection @0 :TurnDirection; -} -enum TurnDirection { - none @0; - turnLeft @1; - turnRight @2; + enum TurnDirection { + none @0; + turnLeft @1; + turnRight @2; + } } struct CustomReserved10 @0xcb9fd56c7057593a { diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index e72d464d06..16908fa3e3 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -6,6 +6,7 @@ from openpilot.sunnypilot.selfdrive.controls.lib.lane_turn_desire import LaneTur LaneChangeState = log.LaneChangeState LaneChangeDirection = log.LaneChangeDirection +TurnDirection = custom.ModelDataV2SP.TurnDirection LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS LANE_CHANGE_TIME_MAX = 10. @@ -32,9 +33,9 @@ DESIRES = { } TURN_DESIRES = { - custom.TurnDirection.none: log.Desire.none, - custom.TurnDirection.turnLeft: log.Desire.turnLeft, - custom.TurnDirection.turnRight: log.Desire.turnRight, + TurnDirection.none: log.Desire.none, + TurnDirection.turnLeft: log.Desire.turnLeft, + TurnDirection.turnRight: log.Desire.turnRight, } @@ -49,7 +50,7 @@ class DesireHelper: self.desire = log.Desire.none self.alc = AutoLaneChangeController(self) self.lane_turn_controller = LaneTurnController(self) - self.lane_turn_direction = custom.TurnDirection.none + self.lane_turn_direction = TurnDirection.none @staticmethod def get_lane_change_direction(CS): @@ -126,7 +127,7 @@ class DesireHelper: self.prev_one_blinker = one_blinker - if self.lane_turn_direction != custom.TurnDirection.none: + if self.lane_turn_direction != TurnDirection.none: self.desire = TURN_DESIRES[self.lane_turn_direction] else: self.desire = DESIRES[self.lane_change_direction][self.lane_change_state] diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index 0a4ea749e4..3bc616fb04 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -44,6 +44,7 @@ LaneChangeDirection = log.LaneChangeDirection EventName = log.OnroadEvent.EventName ButtonType = car.CarState.ButtonEvent.Type SafetyModel = car.CarParams.SafetyModel +TurnDirection = custom.ModelDataV2SP.TurnDirection IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput) @@ -305,9 +306,9 @@ class SelfdriveD(CruiseHelper): # Handle lane turn lane_turn_direction = self.sm['modelDataV2SP'].laneTurnDirection - if lane_turn_direction == custom.TurnDirection.turnLeft: + if lane_turn_direction == TurnDirection.turnLeft: self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnLeft) - elif lane_turn_direction == custom.TurnDirection.turnRight: + elif lane_turn_direction == TurnDirection.turnRight: self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnRight) for i, pandaState in enumerate(self.sm['pandaStates']): diff --git a/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py b/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py index 00ce026abb..fa35ebb125 100644 --- a/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py +++ b/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py @@ -9,13 +9,15 @@ from cereal import custom from openpilot.common.constants import CV from openpilot.common.params import Params +TurnDirection = custom.ModelDataV2SP.TurnDirection + LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS class LaneTurnController: def __init__(self, desire_helper): self.DH = desire_helper - self.turn_direction = custom.TurnDirection.none + self.turn_direction = TurnDirection.none self.params = Params() self.lane_turn_value = float(self.params.get("LaneTurnValue", return_default=True)) * CV.MPH_TO_MS self.param_read_counter = 0 @@ -33,13 +35,13 @@ class LaneTurnController: def update_lane_turn(self, blindspot_left: bool, blindspot_right: bool, left_blinker: bool, right_blinker: bool, v_ego: float) -> None: if left_blinker and not right_blinker and v_ego < self.lane_turn_value and not blindspot_left: - self.turn_direction = custom.TurnDirection.turnLeft + self.turn_direction = TurnDirection.turnLeft elif right_blinker and not left_blinker and v_ego < self.lane_turn_value and not blindspot_right: - self.turn_direction = custom.TurnDirection.turnRight + self.turn_direction = TurnDirection.turnRight else: - self.turn_direction = custom.TurnDirection.none + self.turn_direction = TurnDirection.none def get_turn_direction(self): if not self.enabled: - return custom.TurnDirection.none + return TurnDirection.none return self.turn_direction diff --git a/sunnypilot/selfdrive/controls/lib/tests/test_lane_turn_desire.py b/sunnypilot/selfdrive/controls/lib/tests/test_lane_turn_desire.py index 5633ed6efc..57fe7b684f 100644 --- a/sunnypilot/selfdrive/controls/lib/tests/test_lane_turn_desire.py +++ b/sunnypilot/selfdrive/controls/lib/tests/test_lane_turn_desire.py @@ -1,113 +1,113 @@ import pytest -from cereal import log +from cereal import log, custom from openpilot.common.params import Params from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper from openpilot.sunnypilot.selfdrive.controls.lib.lane_turn_desire import LaneTurnController, LANE_CHANGE_SPEED_MIN from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeMode - -class TurnDirection: - none = 0 - turnLeft = 1 - turnRight = 2 +TurnDirection = custom.ModelDataV2SP.TurnDirection @pytest.mark.parametrize("left_blinker,right_blinker,v_ego,blindspot_left,blindspot_right,expected", [ - (True, False, 5, False, False, TurnDirection.turnLeft), - (False, True, 6, False, False, TurnDirection.turnRight), - (True, False, 9, False, False, TurnDirection.none), - (True, False, 7, True, False, TurnDirection.none), - (False, True, 6, False, True, TurnDirection.none), - (False, False, 5, False, False, TurnDirection.none), - (True, True, 5, False, False, TurnDirection.none), + (True, False, 5, False, False, TurnDirection.turnLeft), + (False, True, 6, False, False, TurnDirection.turnRight), + (True, False, 9, False, False, TurnDirection.none), + (True, False, 7, True, False, TurnDirection.none), + (False, True, 6, False, True, TurnDirection.none), + (False, False, 5, False, False, TurnDirection.none), + (True, True, 5, False, False, TurnDirection.none), ]) def test_lane_turn_desire_conditions(left_blinker, right_blinker, v_ego, blindspot_left, blindspot_right, expected): - dh = DesireHelper() - controller = LaneTurnController(dh) - controller.enabled = True - controller.lane_turn_value = LANE_CHANGE_SPEED_MIN - controller.turn_direction = TurnDirection.none - controller.update_lane_turn(blindspot_left, blindspot_right, left_blinker, right_blinker, v_ego) - assert controller.get_turn_direction() == expected + dh = DesireHelper() + controller = LaneTurnController(dh) + controller.enabled = True + controller.lane_turn_value = LANE_CHANGE_SPEED_MIN + controller.turn_direction = TurnDirection.none + controller.update_lane_turn(blindspot_left, blindspot_right, left_blinker, right_blinker, v_ego) + assert controller.get_turn_direction() == expected def test_lane_turn_desire_disabled(): - dh = DesireHelper() - controller = LaneTurnController(dh) - controller.enabled = False - controller.lane_turn_value = LANE_CHANGE_SPEED_MIN - controller.turn_direction = TurnDirection.none - controller.update_lane_turn(False, False, True, False, 7) - assert controller.get_turn_direction() == TurnDirection.none + dh = DesireHelper() + controller = LaneTurnController(dh) + controller.enabled = False + controller.lane_turn_value = LANE_CHANGE_SPEED_MIN + controller.turn_direction = TurnDirection.none + controller.update_lane_turn(False, False, True, False, 7) + assert controller.get_turn_direction() == TurnDirection.none def test_lane_turn_overrides_lane_change(): - dh = DesireHelper() - controller = LaneTurnController(dh) - controller.enabled = True - controller.lane_turn_value = LANE_CHANGE_SPEED_MIN - controller.turn_direction = TurnDirection.none - # left turn desire - controller.update_lane_turn(False, False, True, False, 5) - assert controller.get_turn_direction() == TurnDirection.turnLeft - # right turn desire - controller.update_lane_turn(False, False, False, True, 6) - assert controller.get_turn_direction() == TurnDirection.turnRight - # no turn - controller.update_lane_turn(False, False, False, False, 7) - assert controller.get_turn_direction() == TurnDirection.none + dh = DesireHelper() + controller = LaneTurnController(dh) + controller.enabled = True + controller.lane_turn_value = LANE_CHANGE_SPEED_MIN + controller.turn_direction = TurnDirection.none + # left turn desire + controller.update_lane_turn(False, False, True, False, 5) + assert controller.get_turn_direction() == TurnDirection.turnLeft + # right turn desire + controller.update_lane_turn(False, False, False, True, 6) + assert controller.get_turn_direction() == TurnDirection.turnRight + # no turn + controller.update_lane_turn(False, False, False, False, 7) + assert controller.get_turn_direction() == TurnDirection.none @pytest.mark.parametrize("v_ego,expected", [ - (8.93, TurnDirection.turnLeft), # just below threshold - (8.96, TurnDirection.none), # above threshold - (8.95, TurnDirection.none), # just above threshold + (8.93, TurnDirection.turnLeft), # just below threshold + (8.96, TurnDirection.none), # above threshold + (8.95, TurnDirection.none), # just above threshold ]) def test_lane_turn_desire_speed_boundary(v_ego, expected): - dh = DesireHelper() - controller = LaneTurnController(dh) - controller.enabled = True - controller.lane_turn_value = LANE_CHANGE_SPEED_MIN - controller.turn_direction = TurnDirection.none - controller.update_lane_turn(False, True, True, False, v_ego) - assert controller.get_turn_direction() == expected + dh = DesireHelper() + controller = LaneTurnController(dh) + controller.enabled = True + controller.lane_turn_value = LANE_CHANGE_SPEED_MIN + controller.turn_direction = TurnDirection.none + controller.update_lane_turn(False, True, True, False, v_ego) + assert controller.get_turn_direction() == expected class DummyCarState: - def __init__(self, vEgo=0, leftBlinker=False, rightBlinker=False, leftBlindspot=False, rightBlindspot=False, - steeringPressed=False, steeringTorque=0, brakePressed=False): - self.vEgo = vEgo - self.leftBlinker = leftBlinker - self.rightBlinker = rightBlinker - self.leftBlindspot = leftBlindspot - self.rightBlindspot = rightBlindspot - self.steeringPressed = steeringPressed - self.steeringTorque = steeringTorque - self.brakePressed = brakePressed + def __init__(self, vEgo=0, leftBlinker=False, rightBlinker=False, leftBlindspot=False, rightBlindspot=False, + steeringPressed=False, steeringTorque=0, brakePressed=False): + self.vEgo = vEgo + self.leftBlinker = leftBlinker + self.rightBlinker = rightBlinker + self.leftBlindspot = leftBlindspot + self.rightBlindspot = rightBlindspot + self.steeringPressed = steeringPressed + self.steeringTorque = steeringTorque + self.brakePressed = brakePressed + @pytest.fixture def set_lane_turn_params(): - params = Params() - params.put("LaneTurnDesire", True) - params.put("LaneTurnValue", 20.0) + params = Params() + params.put("LaneTurnDesire", True) + params.put("LaneTurnValue", 20.0) + @pytest.mark.parametrize("carstate, lateral_active, lane_change_prob, expected_desire", [ - # Lane turn desire overrides lane change desire - (DummyCarState(vEgo=5, leftBlinker=True, rightBlinker=False, leftBlindspot=False, rightBlindspot=False), True, 1.0, log.Desire.turnLeft), - (DummyCarState(vEgo=7, leftBlinker=False, rightBlinker=True, leftBlindspot=False, rightBlindspot=False), True, 1.0, log.Desire.turnRight), - # Lane change desire only (no turn desires) - (DummyCarState(vEgo=9, leftBlinker=True, rightBlinker=False, leftBlindspot=False, rightBlindspot=False, - steeringPressed=True, steeringTorque=1), True, 1.0, log.Desire.laneChangeLeft), - (DummyCarState(vEgo=9, leftBlinker=False, rightBlinker=True, leftBlindspot=False, rightBlindspot=False, - steeringPressed=True, steeringTorque=-1), True, 1.0, log.Desire.laneChangeRight), - # No desire (inactive) - (DummyCarState(vEgo=9, leftBlinker=False, rightBlinker=False), False, 1.0, log.Desire.none), - (DummyCarState(vEgo=4, leftBlinker=False, rightBlinker=False), True, 1.0, log.Desire.none), # No blinkers? no desire! + # Lane turn desire overrides lane change desire + (DummyCarState(vEgo=5, leftBlinker=True, rightBlinker=False, leftBlindspot=False, rightBlindspot=False), True, 1.0, + log.Desire.turnLeft), + (DummyCarState(vEgo=7, leftBlinker=False, rightBlinker=True, leftBlindspot=False, rightBlindspot=False), True, 1.0, + log.Desire.turnRight), + # Lane change desire only (no turn desires) + (DummyCarState(vEgo=9, leftBlinker=True, rightBlinker=False, leftBlindspot=False, rightBlindspot=False, + steeringPressed=True, steeringTorque=1), True, 1.0, log.Desire.laneChangeLeft), + (DummyCarState(vEgo=9, leftBlinker=False, rightBlinker=True, leftBlindspot=False, rightBlindspot=False, + steeringPressed=True, steeringTorque=-1), True, 1.0, log.Desire.laneChangeRight), + # No desire (inactive) + (DummyCarState(vEgo=9, leftBlinker=False, rightBlinker=False), False, 1.0, log.Desire.none), + (DummyCarState(vEgo=4, leftBlinker=False, rightBlinker=False), True, 1.0, log.Desire.none), # No blinkers? no desire! ]) def test_desire_helper_integration(carstate, lateral_active, lane_change_prob, expected_desire, set_lane_turn_params): - dh = DesireHelper() - dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE - for _ in range(10): - dh.update(carstate, lateral_active, lane_change_prob) - assert dh.desire == expected_desire # The first four tests were unit tests to test the controller, where this tests the integration in desire helpers + dh = DesireHelper() + dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE + for _ in range(10): + dh.update(carstate, lateral_active, lane_change_prob) + assert dh.desire == expected_desire # The first four tests were unit tests to test the controller, where this tests the integration in desire helpers From e0ccc175e4b49dc389ede1bc522b7de64056e450 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 15 Oct 2025 00:59:40 -0400 Subject: [PATCH 26/72] liveMapDataSP: improve speed limit validation logic (#1383) --- sunnypilot/mapd/live_map_data/base_map_data.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sunnypilot/mapd/live_map_data/base_map_data.py b/sunnypilot/mapd/live_map_data/base_map_data.py index 25925937f5..723864dd2a 100644 --- a/sunnypilot/mapd/live_map_data/base_map_data.py +++ b/sunnypilot/mapd/live_map_data/base_map_data.py @@ -8,8 +8,12 @@ from abc import abstractmethod, ABC import cereal.messaging as messaging from openpilot.common.params import Params +from openpilot.common.constants import CV +from openpilot.selfdrive.car.cruise import V_CRUISE_UNSET from openpilot.sunnypilot.navd.helpers import coordinate_from_param +MAX_SPEED_LIMIT = V_CRUISE_UNSET * CV.KPH_TO_MS + class BaseMapData(ABC): def __init__(self): @@ -46,9 +50,9 @@ class BaseMapData(ABC): mapd_sp_send.valid = self.sm['liveLocationKalman'].gpsOK live_map_data = mapd_sp_send.liveMapDataSP - live_map_data.speedLimitValid = bool(speed_limit > 0) + live_map_data.speedLimitValid = bool(MAX_SPEED_LIMIT > speed_limit > 0) live_map_data.speedLimit = speed_limit - live_map_data.speedLimitAheadValid = bool(next_speed_limit > 0) + live_map_data.speedLimitAheadValid = bool(MAX_SPEED_LIMIT > next_speed_limit > 0) live_map_data.speedLimitAhead = next_speed_limit live_map_data.speedLimitAheadDistance = next_speed_limit_distance live_map_data.roadName = self.get_current_road_name() From 6d51d64285fdfef07f09845d8d96425bc0a769ab Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 15 Oct 2025 09:46:53 -0400 Subject: [PATCH 27/72] interfaces: clean up unsupported params during initialization (#1385) * interfaces: clean up unsupported params during initialization * fix * logging and no DEC when no long * ui * ui --- .../speed_limit/speed_limit_settings.cc | 6 ++++- .../qt/offroad/settings/longitudinal_panel.cc | 13 ++++++++-- sunnypilot/selfdrive/car/interfaces.py | 24 +++++++++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc index f64198f97e..95aa4ef26f 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc @@ -122,6 +122,10 @@ void SpeedLimitSettings::refresh() { has_longitudinal_control = hasLongitudinalControl(CP); intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable(); + + if (!has_longitudinal_control && CP_SP.getPcmCruiseSpeed()) { + params.put("SpeedLimitMode", std::to_string(static_cast(SpeedLimitMode::WARNING))); + } } else { has_longitudinal_control = false; intelligent_cruise_button_management_available = false; @@ -148,7 +152,7 @@ void SpeedLimitSettings::refresh() { speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues(getSpeedLimitModeValues())); } else { speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues( - {SpeedLimitMode::OFF,SpeedLimitMode::INFORMATION, SpeedLimitMode::WARNING})); + {SpeedLimitMode::OFF, SpeedLimitMode::INFORMATION, SpeedLimitMode::WARNING})); } speed_limit_mode_settings->showDescription(); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc index 31124a3242..a5098a13e6 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc @@ -104,6 +104,17 @@ void LongitudinalPanel::refresh(bool _offroad) { has_longitudinal_control = hasLongitudinalControl(CP); is_pcm_cruise = CP.getPcmCruise(); intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable(); + + if (!intelligent_cruise_button_management_available || has_longitudinal_control) { + params.remove("IntelligentCruiseButtonManagement"); + } + + if (!has_longitudinal_control && CP_SP.getPcmCruiseSpeed()) { + params.remove("CustomAccIncrementsEnabled"); + params.remove("DynamicExperimentalControl"); + params.remove("SmartCruiseControlVision"); + params.remove("SmartCruiseControlMap"); + } } else { has_longitudinal_control = false; is_pcm_cruise = false; @@ -127,11 +138,9 @@ void LongitudinalPanel::refresh(bool _offroad) { customAccIncrement->setDescription(accEnabledDescription); } } else { - params.remove("CustomAccIncrementsEnabled"); customAccIncrement->toggleFlipped(false); customAccIncrement->setDescription(accNoLongDescription); customAccIncrement->showDescription(); - params.remove("IntelligentCruiseButtonManagement"); intelligentCruiseButtonManagement->toggleFlipped(false); } } diff --git a/sunnypilot/selfdrive/car/interfaces.py b/sunnypilot/selfdrive/car/interfaces.py index 3072cde8cd..5467639a63 100644 --- a/sunnypilot/selfdrive/car/interfaces.py +++ b/sunnypilot/selfdrive/car/interfaces.py @@ -11,6 +11,7 @@ from opendbc.car.interfaces import CarInterfaceBase from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog from openpilot.sunnypilot.selfdrive.controls.lib.nnlc.helpers import get_nn_model_path +from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode as SpeedLimitMode import openpilot.system.sentry as sentry @@ -66,6 +67,28 @@ def _initialize_torque_lateral_control(CI: CarInterfaceBase, CP: structs.CarPara CI.configure_torque_tune(CP.carFingerprint, CP.lateralTuning) +def _cleanup_unsupported_params(CP: structs.CarParams, CP_SP: structs.CarParamsSP, params: Params = None) -> None: + if params is None: + params = Params() + + if CP.steerControlType == structs.CarParams.SteerControlType.angle: + cloudlog.warning("SteerControlType is angle, cleaning up params") + params.remove("NeuralNetworkLateralControl") + params.remove("EnforceTorqueControl") + + if not CP_SP.intelligentCruiseButtonManagementAvailable or CP.openpilotLongitudinalControl: + cloudlog.warning("ICBM not available or openpilot Longitudinal Control enabled, cleaning up params") + params.remove("IntelligentCruiseButtonManagement") + + if not CP.openpilotLongitudinalControl and CP_SP.pcmCruiseSpeed: + cloudlog.warning("openpilot Longitudinal Control and ICBM not available, cleaning up params") + params.remove("DynamicExperimentalControl") + params.remove("CustomAccIncrementsEnabled") + params.remove("SmartCruiseControlVision") + params.remove("SmartCruiseControlMap") + params.put("SpeedLimitMode", int(SpeedLimitMode.warning)) + + def setup_interfaces(CI: CarInterfaceBase, params: Params = None) -> None: CP = CI.CP CP_SP = CI.CP_SP @@ -74,6 +97,7 @@ def setup_interfaces(CI: CarInterfaceBase, params: Params = None) -> None: nnlc_enabled = _initialize_neural_network_lateral_control(CP, CP_SP, params) _initialize_intelligent_cruise_button_management(CP, CP_SP, params) _initialize_torque_lateral_control(CI, CP, enforce_torque, nnlc_enabled) + _cleanup_unsupported_params(CP, CP_SP) def initialize_params(params) -> list[dict[str, Any]]: From d7e1c42c2b73a3c96fa17908ee1118a5171f4183 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 15 Oct 2025 12:25:23 -0400 Subject: [PATCH 28/72] ui: move Dynamic Experimental Control (DEC) toggle to Longitudinal panel (#1388) - Implemented a new toggle for enabling Dynamic Experimental Control (DEC) in longitudinal settings. - Removed previous implementation for DEC from general settings. - Updated accessibility based on longitudinal control status. --- selfdrive/ui/qt/offroad/settings.cc | 7 ------- .../qt/offroad/settings/longitudinal_panel.cc | 10 ++++++++++ .../qt/offroad/settings/longitudinal_panel.h | 1 + 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 205a14624a..6f594d939a 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -33,13 +33,6 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { "../assets/icons/experimental_white.svg", false, }, - { - "DynamicExperimentalControl", - tr("Enable Dynamic Experimental Control"), - tr("Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal."), - "../assets/offroad/icon_blank.png", - false, - }, { "DisengageOnAccelerator", tr("Disengage on Accelerator Pedal"), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc index a5098a13e6..f2c7ea3825 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc @@ -43,6 +43,15 @@ LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) { intelligentCruiseButtonManagement->setConfirmation(true, false); list->addItem(intelligentCruiseButtonManagement); + dynamicExperimentalControl = new ParamControlSP( + "DynamicExperimentalControl", + tr("Dynamic Experimental Control (DEC)"), + tr("Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal."), + "", + this + ); + list->addItem(dynamicExperimentalControl); + SmartCruiseControlVision = new ParamControl( "SmartCruiseControlVision", tr("Smart Cruise Control - Vision"), @@ -153,6 +162,7 @@ void LongitudinalPanel::refresh(bool _offroad) { customAccIncrement->setEnabled(cai_allowed && !offroad); customAccIncrement->refresh(); + dynamicExperimentalControl->setEnabled(has_longitudinal_control); SmartCruiseControlVision->setEnabled(has_longitudinal_control || icbm_allowed); SmartCruiseControlMap->setEnabled(has_longitudinal_control || icbm_allowed); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h index a94369a560..127b7871eb 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h @@ -35,6 +35,7 @@ private: ParamControl *SmartCruiseControlVision; ParamControl *SmartCruiseControlMap; ParamControl *intelligentCruiseButtonManagement = nullptr; + ParamControl *dynamicExperimentalControl = nullptr; SpeedLimitSettings *speedLimitScreen; PushButtonSP *speedLimitSettings; }; From f1ca81debf97153a0b531bfe6d774869af83bc77 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 15 Oct 2025 16:20:31 -0400 Subject: [PATCH 29/72] ui: chevron should always be on top of driving path (#1391) --- selfdrive/ui/sunnypilot/qt/onroad/model.cc | 34 ++++++++++++++-------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/model.cc b/selfdrive/ui/sunnypilot/qt/onroad/model.cc index 086b703e10..590ade24a1 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/model.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/model.cc @@ -22,7 +22,6 @@ void ModelRendererSP::update_model(const cereal::ModelDataV2::Reader &model, con } void ModelRendererSP::draw(QPainter &painter, const QRect &surface_rect) { - ModelRenderer::draw(painter, surface_rect); auto *s = uiState(); auto &sm = *(s->sm); @@ -31,6 +30,11 @@ void ModelRendererSP::draw(QPainter &painter, const QRect &surface_rect) { return; } + clip_region = surface_rect.adjusted(-CLIP_MARGIN, -CLIP_MARGIN, CLIP_MARGIN, CLIP_MARGIN); + experimental_mode = sm["selfdriveState"].getSelfdriveState().getExperimentalMode(); + longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl(); + path_offset_z = sm["liveCalibration"].getLiveCalibration().getHeight()[0]; + painter.save(); const auto &model = sm["modelV2"].getModelV2(); @@ -41,22 +45,28 @@ void ModelRendererSP::draw(QPainter &painter, const QRect &surface_rect) { update_model(model, lead_one); drawLaneLines(painter); - bool blindspot = s->scene.blindspot_ui; - bool rainbow = s->scene.rainbow_mode; - - bool left_blindspot = car_state.getLeftBlindspot(); - bool right_blindspot = car_state.getRightBlindspot(); - - if (blindspot) { - drawBlindspot(painter, surface_rect, left_blindspot, right_blindspot); - } - - if (rainbow) { + if (s->scene.rainbow_mode) { drawRainbowPath(painter, surface_rect); } else { ModelRenderer::drawPath(painter, model, surface_rect.height()); } + if (longitudinal_control && sm.alive("radarState")) { + update_leads(radar_state, model.getPosition()); + const auto &lead_two = radar_state.getLeadTwo(); + if (lead_one.getStatus()) { + drawLead(painter, lead_one, lead_vertices[0], surface_rect); + } + if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) { + drawLead(painter, lead_two, lead_vertices[1], surface_rect); + } + } + + if (s->scene.blindspot_ui) { + const bool left_blindspot = car_state.getLeftBlindspot(); + const bool right_blindspot = car_state.getRightBlindspot(); + drawBlindspot(painter, surface_rect, left_blindspot, right_blindspot); + } drawLeadStatus(painter, surface_rect.height(), surface_rect.width()); painter.restore(); From c438aeb5a5ce4d4e5c8aee5c68a5eaac08ce3afd Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 15 Oct 2025 16:47:00 -0400 Subject: [PATCH 30/72] ui: check for updated message before updating states in HUD (#1392) --- selfdrive/ui/sunnypilot/qt/onroad/hud.cc | 105 ++++++++++++++--------- selfdrive/ui/sunnypilot/qt/onroad/hud.h | 2 +- 2 files changed, 64 insertions(+), 43 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index fa57e2b334..d8176051fb 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -26,31 +26,54 @@ HudRendererSP::HudRendererSP() { void HudRendererSP::updateState(const UIState &s) { HudRenderer::updateState(s); + float speedConv = is_metric ? MS_TO_KPH : MS_TO_MPH; + devUiInfo = s.scene.dev_ui_info; + roadName = s.scene.road_name; + showTurnSignals = s.scene.turn_signals; + speedLimitMode = static_cast(s.scene.speed_limit_mode); + speedUnit = is_metric ? tr("km/h") : tr("mph"); + standstillTimer = s.scene.standstill_timer; + const SubMaster &sm = *(s.sm); const auto cs = sm["controlsState"].getControlsState(); const auto car_state = sm["carState"].getCarState(); const auto car_control = sm["carControl"].getCarControl(); const auto radar_state = sm["radarState"].getRadarState(); const auto is_gps_location_external = sm.rcv_frame("gpsLocationExternal") > 1; - const auto gpsLocation = is_gps_location_external ? sm["gpsLocationExternal"].getGpsLocationExternal() : sm["gpsLocation"].getGpsLocation(); + const char *gps_source = is_gps_location_external ? "gpsLocationExternal" : "gpsLocation"; + const auto gpsLocation = is_gps_location_external ? sm[gps_source].getGpsLocationExternal() : sm[gps_source].getGpsLocation(); const auto ltp = sm["liveTorqueParameters"].getLiveTorqueParameters(); const auto car_params = sm["carParams"].getCarParams(); const auto car_params_sp = sm["carParamsSP"].getCarParamsSP(); const auto lp_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP(); const auto lmd = sm["liveMapDataSP"].getLiveMapDataSP(); - float speedConv = is_metric ? MS_TO_KPH : MS_TO_MPH; - speedLimit = lp_sp.getSpeedLimit().getResolver().getSpeedLimit() * speedConv; - speedLimitLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLast() * speedConv; - speedLimitOffset = lp_sp.getSpeedLimit().getResolver().getSpeedLimitOffset() * speedConv; - speedLimitValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitValid(); - speedLimitLastValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLastValid(); - speedLimitFinalLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitFinalLast() * speedConv; - speedLimitSource = lp_sp.getSpeedLimit().getResolver().getSource(); - speedLimitMode = static_cast(s.scene.speed_limit_mode); - speedLimitAssistState = lp_sp.getSpeedLimit().getAssist().getState(); - speedLimitAssistActive = lp_sp.getSpeedLimit().getAssist().getActive(); - roadName = s.scene.road_name; + if (sm.updated("carParams")) { + steerControlType = car_params.getSteerControlType(); + } + + if (sm.updated("carParamsSP")) { + pcmCruiseSpeed = car_params_sp.getPcmCruiseSpeed(); + } + + if (sm.updated("longitudinalPlanSP")) { + speedLimit = lp_sp.getSpeedLimit().getResolver().getSpeedLimit() * speedConv; + speedLimitLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLast() * speedConv; + speedLimitOffset = lp_sp.getSpeedLimit().getResolver().getSpeedLimitOffset() * speedConv; + speedLimitValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitValid(); + speedLimitLastValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLastValid(); + speedLimitFinalLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitFinalLast() * speedConv; + speedLimitSource = lp_sp.getSpeedLimit().getResolver().getSource(); + speedLimitAssistState = lp_sp.getSpeedLimit().getAssist().getState(); + speedLimitAssistActive = lp_sp.getSpeedLimit().getAssist().getActive(); + smartCruiseControlVisionEnabled = lp_sp.getSmartCruiseControl().getVision().getEnabled(); + smartCruiseControlVisionActive = lp_sp.getSmartCruiseControl().getVision().getActive(); + smartCruiseControlMapEnabled = lp_sp.getSmartCruiseControl().getMap().getEnabled(); + smartCruiseControlMapActive = lp_sp.getSmartCruiseControl().getMap().getActive(); + greenLightAlert = lp_sp.getE2eAlerts().getGreenLightAlert(); + leadDepartAlert = lp_sp.getE2eAlerts().getLeadDepartAlert(); + } + if (sm.updated("liveMapDataSP")) { roadNameStr = QString::fromStdString(lmd.getRoadName()); speedLimitAheadValid = lmd.getSpeedLimitAheadValid(); @@ -66,7 +89,7 @@ void HudRendererSP::updateState(const UIState &s) { static int reverse_delay = 0; bool reverse_allowed = false; - if (int(car_state.getGearShifter()) != 4) { + if (car_state.getGearShifter() != cereal::CarState::GearShifter::REVERSE) { reverse_delay = 0; reverse_allowed = false; } else { @@ -78,46 +101,47 @@ void HudRendererSP::updateState(const UIState &s) { reversing = reverse_allowed; + if (sm.updated("liveParameters")) { + roll = sm["liveParameters"].getLiveParameters().getRoll(); + } + + if (sm.updated("deviceState")) { + memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent(); + } + + if (sm.updated(gps_source)) { + gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0; // External reports accuracy, internal does not. + altitude = gpsLocation.getAltitude(); + bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg(); + bearingDeg = gpsLocation.getBearingDeg(); + } + + if (sm.updated("liveTorqueParameters")) { + torquedUseParams = ltp.getUseParams(); + latAccelFactorFiltered = ltp.getLatAccelFactorFiltered(); + frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered(); + liveValid = ltp.getLiveValid(); + } + latActive = car_control.getLatActive(); + actuators = car_control.getActuators(); + longOverride = car_control.getCruiseControl().getOverride(); + carControlEnabled = car_control.getEnabled(); + steerOverride = car_state.getSteeringPressed(); - - devUiInfo = s.scene.dev_ui_info; - - speedUnit = is_metric ? tr("km/h") : tr("mph"); lead_d_rel = radar_state.getLeadOne().getDRel(); lead_v_rel = radar_state.getLeadOne().getVRel(); lead_status = radar_state.getLeadOne().getStatus(); - steerControlType = car_params.getSteerControlType(); - actuators = car_control.getActuators(); torqueLateral = steerControlType == cereal::CarParams::SteerControlType::TORQUE; angleSteers = car_state.getSteeringAngleDeg(); desiredCurvature = cs.getDesiredCurvature(); curvature = cs.getCurvature(); - roll = sm["liveParameters"].getLiveParameters().getRoll(); - memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent(); - gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0; // External reports accuracy, internal does not. - altitude = gpsLocation.getAltitude(); vEgo = car_state.getVEgo(); aEgo = car_state.getAEgo(); steeringTorqueEps = car_state.getSteeringTorqueEps(); - bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg(); - bearingDeg = gpsLocation.getBearingDeg(); - torquedUseParams = ltp.getUseParams(); - latAccelFactorFiltered = ltp.getLatAccelFactorFiltered(); - frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered(); - liveValid = ltp.getLiveValid(); - standstillTimer = s.scene.standstill_timer; isStandstill = car_state.getStandstill(); if (not s.scene.started) standstillElapsedTime = 0.0; - longOverride = car_control.getCruiseControl().getOverride(); - smartCruiseControlVisionEnabled = lp_sp.getSmartCruiseControl().getVision().getEnabled(); - smartCruiseControlVisionActive = lp_sp.getSmartCruiseControl().getVision().getActive(); - smartCruiseControlMapEnabled = lp_sp.getSmartCruiseControl().getMap().getEnabled(); - smartCruiseControlMapActive = lp_sp.getSmartCruiseControl().getMap().getActive(); - - greenLightAlert = lp_sp.getE2eAlerts().getGreenLightAlert(); - leadDepartAlert = lp_sp.getE2eAlerts().getLeadDepartAlert(); // override stock current speed values float v_ego = (v_ego_cluster_seen && !s.scene.trueVEgoUI) ? car_state.getVEgoCluster() : car_state.getVEgo(); @@ -128,11 +152,8 @@ void HudRendererSP::updateState(const UIState &s) { rightBlinkerOn = car_state.getRightBlinker(); leftBlindspot = car_state.getLeftBlindspot(); rightBlindspot = car_state.getRightBlindspot(); - showTurnSignals = s.scene.turn_signals; - carControlEnabled = car_control.getEnabled(); speedCluster = car_state.getCruiseState().getSpeedCluster() * speedConv; - pcmCruiseSpeed = car_params_sp.getPcmCruiseSpeed(); } void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) { diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.h b/selfdrive/ui/sunnypilot/qt/onroad/hud.h index f9135ee9ef..a32fdb28a7 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.h @@ -121,5 +121,5 @@ private: bool carControlEnabled; float speedCluster = 0; int icbm_active_counter = 0; - bool pcmCruiseSpeed; + bool pcmCruiseSpeed = true; }; From 99bd9075d539b3f74af8610626a02d22ce53d931 Mon Sep 17 00:00:00 2001 From: Nayan Date: Wed, 15 Oct 2025 17:18:31 -0400 Subject: [PATCH 31/72] ui: Fix Onroad Screen-Off default param (#1389) Change defaults Co-authored-by: Jason Wen --- common/params_keys.h | 4 ++-- .../qt/offroad/settings/display/onroad_screen_brightness.cc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index ecb73a9456..dd58462a95 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -160,9 +160,9 @@ inline static std::unordered_map keys = { {"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}}, {"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}}, {"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}}, - {"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "100"}}, + {"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "0"}}, {"OnroadScreenOffControl", {PERSISTENT | BACKUP, BOOL}}, - {"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "0"}}, + {"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "15"}}, {"OnroadUploads", {PERSISTENT | BACKUP, BOOL, "1"}}, {"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}}, {"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}}, diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/display/onroad_screen_brightness.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/display/onroad_screen_brightness.cc index f28d9e45e8..e29d06cb82 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/display/onroad_screen_brightness.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/display/onroad_screen_brightness.cc @@ -29,7 +29,7 @@ OnroadScreenBrightnessControl::OnroadScreenBrightnessControl(const QString ¶ "Onroad Brightness", "", "", - {0, 100}, 10, true); + {0, 90}, 10, true); connect(onroadScreenOffTimer, &OptionControlSP::updateLabels, this, &OnroadScreenBrightnessControl::refresh); connect(onroadScreenBrightness, &OptionControlSP::updateLabels, this, &OnroadScreenBrightnessControl::refresh); From 9e6af5ba740eecd78dec2af302ea2bbc8d969f8f Mon Sep 17 00:00:00 2001 From: Nayan Date: Wed, 15 Oct 2025 17:40:52 -0400 Subject: [PATCH 32/72] ui: Adjust UI Elements to account for Sidebar & Dev UI (#1390) * resize & reposition * Apply suggestion from @sunnyhaibin * sir, this is Wendy's * this is still a Wendy's --------- Co-authored-by: Jason Wen --- selfdrive/ui/sunnypilot/qt/onroad/hud.cc | 32 ++++++++++++------------ selfdrive/ui/sunnypilot/qt/onroad/hud.h | 10 +++----- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index d8176051fb..5db7ba1814 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -12,15 +12,12 @@ HudRendererSP::HudRendererSP() { - plus_arrow_up_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_plus_arrow_up", {105, 105}); - minus_arrow_down_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_minus_arrow_down", {105, 105}); + plus_arrow_up_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_plus_arrow_up", {90, 90}); + minus_arrow_down_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_minus_arrow_down", {90, 90}); - int small_max = e2e_alert_small * 2 - 40; - int large_max = e2e_alert_large * 2 - 40; - green_light_alert_small_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/green_light.png", {small_max, small_max}); - green_light_alert_large_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/green_light.png", {large_max, large_max}); - lead_depart_alert_small_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/lead_depart.png", {small_max, small_max}); - lead_depart_alert_large_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/lead_depart.png", {large_max, large_max}); + int size = e2e_alert_size * 2 - 40; + green_light_alert_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/green_light.png", {size, size}); + lead_depart_alert_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/lead_depart.png", {size, size}); } void HudRendererSP::updateState(const UIState &s) { @@ -154,6 +151,9 @@ void HudRendererSP::updateState(const UIState &s) { rightBlindspot = car_state.getRightBlindspot(); speedCluster = car_state.getCruiseState().getSpeedCluster() * speedConv; + + allow_e2e_alerts = sm["selfdriveState"].getSelfdriveState().getAlertSize() == cereal::SelfdriveState::AlertSize::NONE && + sm.rcv_frame("driverStateV2") > s.scene.started_frame && !reversing; } void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) { @@ -256,11 +256,11 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) { e2eAlertFrame++; if (greenLightAlert) { alert_text = tr("GREEN\nLIGHT"); - alert_img = devUiInfo > 0 ? green_light_alert_small_img : green_light_alert_large_img; + alert_img = green_light_alert_img; } else if (leadDepartAlert) { alert_text = tr("LEAD VEHICLE\nDEPARTING"); - alert_img = devUiInfo > 0 ? lead_depart_alert_small_img : lead_depart_alert_large_img; + alert_img = lead_depart_alert_img; } drawE2eAlert(p, surface_rect); } @@ -664,7 +664,7 @@ void HudRendererSP::drawRoadName(QPainter &p, const QRect &surface_rect) { void HudRendererSP::drawSpeedLimitPreActiveArrow(QPainter &p, QRect &sign_rect) { const int sign_margin = 12; - const int arrow_spacing = sign_margin * 3; + const int arrow_spacing = sign_margin * 1.4; int arrow_x = sign_rect.right() + arrow_spacing; int _set_speed = std::nearbyint(set_speed); @@ -739,11 +739,11 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) { } void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const QString &alert_alt_text) { - int size = devUiInfo > 0 ? e2e_alert_small : e2e_alert_large; - int x = surface_rect.center().x() + surface_rect.width() / 4; + if (!allow_e2e_alerts) return; + + int x = surface_rect.right() - e2e_alert_size - (devUiInfo > 0 ? 180 : 100) - (UI_BORDER_SIZE * 3); int y = surface_rect.center().y() + 20; - x += devUiInfo > 0 ? 0 : 50; - QRect alertRect(x - size, y - size, size * 2, size * 2); + QRect alertRect(x - e2e_alert_size, y - e2e_alert_size, e2e_alert_size * 2, e2e_alert_size * 2); // Alert Circle QPoint center = alertRect.center(); @@ -752,7 +752,7 @@ void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const Q else frameColor = pulseElement(e2eAlertFrame) ? QColor(255, 255, 255, 75) : QColor(0, 255, 0, 75); p.setPen(QPen(frameColor, 15)); p.setBrush(QColor(0, 0, 0, 190)); - p.drawEllipse(center, size, size); + p.drawEllipse(center, e2e_alert_size, e2e_alert_size); // Alert Text QColor txtColor; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.h b/selfdrive/ui/sunnypilot/qt/onroad/hud.h index a32fdb28a7..bc80a5e8bc 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.h @@ -97,16 +97,14 @@ private: int speedLimitAssistFrame; QPixmap plus_arrow_up_img; QPixmap minus_arrow_down_img; - int e2e_alert_small = 250; - int e2e_alert_large = 300; - QPixmap green_light_alert_small_img; - QPixmap green_light_alert_large_img; + int e2e_alert_size = 300; + QPixmap green_light_alert_img; bool greenLightAlert; int e2eAlertFrame; int e2eAlertDisplayTimer = 0; + bool allow_e2e_alerts; bool leadDepartAlert; - QPixmap lead_depart_alert_small_img; - QPixmap lead_depart_alert_large_img; + QPixmap lead_depart_alert_img; QString alert_text; QPixmap alert_img; bool hideVEgoUI; From 437726b3486c2e214205577ad009b11c79668cdc Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 15 Oct 2025 18:05:50 -0400 Subject: [PATCH 33/72] Speed Limit Mode: only cleanup param if Assist was selected (#1393) Speed Limit Mode: only cleanup param if it was Assist --- .../settings/longitudinal/speed_limit/speed_limit_settings.cc | 4 +++- sunnypilot/selfdrive/car/interfaces.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc index 95aa4ef26f..5c3b03d2af 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc @@ -124,7 +124,9 @@ void SpeedLimitSettings::refresh() { intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable(); if (!has_longitudinal_control && CP_SP.getPcmCruiseSpeed()) { - params.put("SpeedLimitMode", std::to_string(static_cast(SpeedLimitMode::WARNING))); + if (speed_limit_mode_param == SpeedLimitMode::ASSIST) { + params.put("SpeedLimitMode", std::to_string(static_cast(SpeedLimitMode::WARNING))); + } } } else { has_longitudinal_control = false; diff --git a/sunnypilot/selfdrive/car/interfaces.py b/sunnypilot/selfdrive/car/interfaces.py index 5467639a63..6a868ab85b 100644 --- a/sunnypilot/selfdrive/car/interfaces.py +++ b/sunnypilot/selfdrive/car/interfaces.py @@ -86,7 +86,9 @@ def _cleanup_unsupported_params(CP: structs.CarParams, CP_SP: structs.CarParamsS params.remove("CustomAccIncrementsEnabled") params.remove("SmartCruiseControlVision") params.remove("SmartCruiseControlMap") - params.put("SpeedLimitMode", int(SpeedLimitMode.warning)) + + if params.get("SpeedLimitMode", return_default=True) == SpeedLimitMode.assist: + params.put("SpeedLimitMode", int(SpeedLimitMode.warning)) def setup_interfaces(CI: CarInterfaceBase, params: Params = None) -> None: From 50462a1d01e639c8f823f5ed550b2fd29e01f667 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 16 Oct 2025 00:55:17 -0400 Subject: [PATCH 34/72] E2E Alert: universal state machine (#1395) * E2E Helper: universal state machine * not used * rename * 10 frames for both * time based * magic * lead depart: only arm if we have a confirmed close lead for over a second after allowing alert * less * shorter trigger * lol * always update --- selfdrive/ui/sunnypilot/qt/onroad/hud.cc | 18 +-- .../controls/lib/e2e_alerts_helper.py | 138 ++++++++++++++---- 2 files changed, 116 insertions(+), 40 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index 5db7ba1814..2fc9eb98fc 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -67,9 +67,9 @@ void HudRendererSP::updateState(const UIState &s) { smartCruiseControlVisionActive = lp_sp.getSmartCruiseControl().getVision().getActive(); smartCruiseControlMapEnabled = lp_sp.getSmartCruiseControl().getMap().getEnabled(); smartCruiseControlMapActive = lp_sp.getSmartCruiseControl().getMap().getActive(); - greenLightAlert = lp_sp.getE2eAlerts().getGreenLightAlert(); - leadDepartAlert = lp_sp.getE2eAlerts().getLeadDepartAlert(); } + greenLightAlert = lp_sp.getE2eAlerts().getGreenLightAlert(); + leadDepartAlert = lp_sp.getE2eAlerts().getLeadDepartAlert(); if (sm.updated("liveMapDataSP")) { roadNameStr = QString::fromStdString(lmd.getRoadName()); @@ -138,7 +138,7 @@ void HudRendererSP::updateState(const UIState &s) { steeringTorqueEps = car_state.getSteeringTorqueEps(); isStandstill = car_state.getStandstill(); - if (not s.scene.started) standstillElapsedTime = 0.0; + if (!s.scene.started) standstillElapsedTime = 0.0; // override stock current speed values float v_ego = (v_ego_cluster_seen && !s.scene.trueVEgoUI) ? car_state.getVEgoCluster() : car_state.getVEgo(); @@ -246,7 +246,7 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) { drawRoadName(p, surface_rect); // Green Light & Lead Depart Alerts - if (greenLightAlert or leadDepartAlert) { + if (greenLightAlert || leadDepartAlert) { e2eAlertDisplayTimer = 3 * UI_FREQ; // reset onroad sleep timer for e2e alerts uiStateSP()->reset_onroad_sleep_timer(); @@ -278,7 +278,7 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) { // No Alerts displayed else { e2eAlertFrame = 0; - if (not isStandstill) standstillElapsedTime = 0.0; + if (!isStandstill) standstillElapsedTime = 0.0; } // Blinker @@ -748,7 +748,7 @@ void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const Q // Alert Circle QPoint center = alertRect.center(); QColor frameColor; - if (not alert_alt_text.isEmpty()) frameColor = QColor(255, 255, 255, 75); + if (!alert_alt_text.isEmpty()) frameColor = QColor(255, 255, 255, 75); else frameColor = pulseElement(e2eAlertFrame) ? QColor(255, 255, 255, 75) : QColor(0, 255, 0, 75); p.setPen(QPen(frameColor, 15)); p.setBrush(QColor(0, 0, 0, 190)); @@ -758,7 +758,7 @@ void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const Q QColor txtColor; QFont font; int alert_bottom_adjustment; - if (not alert_alt_text.isEmpty()) { + if (!alert_alt_text.isEmpty()) { font = InterFont(100, QFont::Bold); alert_bottom_adjustment = 5; txtColor = QColor(255, 255, 255, 255); @@ -775,7 +775,7 @@ void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const Q textRect.moveBottom(alertRect.bottom() - alertRect.height() / alert_bottom_adjustment); p.drawText(textRect, Qt::AlignCenter, alert_text); - if (not alert_alt_text.isEmpty()) { + if (!alert_alt_text.isEmpty()) { // Alert Alternate Text p.setFont(InterFont(80, QFont::Bold)); p.setPen(QColor(255, 175, 3, 240)); @@ -804,7 +804,7 @@ void HudRendererSP::drawCurrentSpeedSP(QPainter &p, const QRect &surface_rect) { void HudRendererSP::drawBlinker(QPainter &p, const QRect &surface_rect) { const bool hazard = leftBlinkerOn && rightBlinkerOn; - int blinkerStatus = hazard ? 2 : (leftBlinkerOn or rightBlinkerOn) ? 1 : 0; + int blinkerStatus = hazard ? 2 : (leftBlinkerOn || rightBlinkerOn) ? 1 : 0; if (!leftBlinkerOn && !rightBlinkerOn) { blinkerFrameCounter = 0; diff --git a/sunnypilot/selfdrive/controls/lib/e2e_alerts_helper.py b/sunnypilot/selfdrive/controls/lib/e2e_alerts_helper.py index 5a92d878d6..944bf617e9 100644 --- a/sunnypilot/selfdrive/controls/lib/e2e_alerts_helper.py +++ b/sunnypilot/selfdrive/controls/lib/e2e_alerts_helper.py @@ -13,80 +13,156 @@ from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP GREEN_LIGHT_X_THRESHOLD = 30 +LEAD_DEPART_DIST_THRESHOLD = 1.0 +TRIGGER_TIMER_THRESHOLD = 0.3 + + +class E2EStates: + INACTIVE = 0 + ARMED = 1 + CONSUMED = 2 class E2EAlertsHelper: def __init__(self): self._params = Params() self.frame = -1 + self.green_light_state = E2EStates.INACTIVE + self.prev_green_light_state = E2EStates.INACTIVE + self.lead_depart_state = E2EStates.INACTIVE + self.prev_lead_depart_state = E2EStates.INACTIVE self.green_light_alert = False self.green_light_alert_enabled = self._params.get_bool("GreenLightAlert") self.lead_depart_alert = False self.lead_depart_alert_enabled = self._params.get_bool("LeadDepartAlert") - self.alert_allowed = False - self.green_light_alert_count = 0 + self.green_light_trigger_timer = 0 + self.lead_depart_trigger_timer = 0 self.last_lead_distance = -1 self.last_moving_frame = -1 + self.allowed = False + self.last_allowed = False + self.has_lead = False + + self.lead_depart_arm_timer = 0 + self.lead_depart_confirmed_lead = False + self.lead_depart_armed = False + def _read_params(self) -> None: if self.frame % int(PARAMS_UPDATE_PERIOD / DT_MDL) == 0: self.green_light_alert_enabled = self._params.get_bool("GreenLightAlert") self.lead_depart_alert_enabled = self._params.get_bool("LeadDepartAlert") - def update(self, sm: messaging.SubMaster, events_sp: EventsSP) -> None: - self._read_params() - + def update_alert_trigger(self, sm: messaging.SubMaster): CS = sm['carState'] CC = sm['carControl'] model_x = sm['modelV2'].position.x max_idx = len(model_x) - 1 - has_lead = sm['radarState'].leadOne.status + self.has_lead = sm['radarState'].leadOne.status lead_dRel = sm['radarState'].leadOne.dRel + standstill = CS.standstill moving = not standstill and CS.vEgo > 0.1 - _allowed = standstill and not CS.gasPressed and not CC.enabled if moving: self.last_moving_frame = self.frame recent_moving = self.last_moving_frame == -1 or (self.frame - self.last_moving_frame) * DT_MDL < 2.0 - if standstill and not recent_moving: - self.alert_allowed = True - elif not standstill: - self.alert_allowed = False - self.green_light_alert_count = 0 - self.last_lead_distance = -1 + self.allowed = not moving and not CS.gasPressed and not CC.enabled and not recent_moving # Green Light Alert - _green_light_alert = False - if self.green_light_alert_enabled and _allowed and not has_lead and model_x[max_idx] > GREEN_LIGHT_X_THRESHOLD: - if self.alert_allowed: - self.green_light_alert_count += 1 + green_light_trigger = False + if self.green_light_state == E2EStates.ARMED: + if model_x[max_idx] > GREEN_LIGHT_X_THRESHOLD: + self.green_light_trigger_timer += 1 else: - self.green_light_alert_count = 0 + self.green_light_trigger_timer = 0 - if self.green_light_alert_count > 2 and self.alert_allowed: - _green_light_alert = True - self.alert_allowed = False - else: - self.green_light_alert_count = 0 - - self.green_light_alert = _green_light_alert + if self.green_light_trigger_timer * DT_MDL > TRIGGER_TIMER_THRESHOLD: + green_light_trigger = True + elif self.green_light_state != E2EStates.ARMED: + self.green_light_trigger_timer = 0 # Lead Departure Alert - _lead_depart_alert = False - if self.lead_depart_alert_enabled and _allowed and has_lead: + close_lead_valid = self.has_lead and lead_dRel < 8.0 + if self.allowed and not self.last_allowed and close_lead_valid: + self.lead_depart_confirmed_lead = True + elif not self.allowed: + self.lead_depart_confirmed_lead = False + + if self.allowed and self.lead_depart_confirmed_lead and close_lead_valid: + self.lead_depart_arm_timer += 1 + + if self.lead_depart_arm_timer * DT_MDL >= 1.0: + self.lead_depart_armed = True + else: + self.lead_depart_arm_timer = 0 + self.lead_depart_armed = False + + lead_depart_trigger = False + if self.lead_depart_state == E2EStates.ARMED: if self.last_lead_distance == -1 or lead_dRel < self.last_lead_distance: self.last_lead_distance = lead_dRel - if self.last_lead_distance != -1 and (lead_dRel - self.last_lead_distance > 1.0) and self.alert_allowed: - _lead_depart_alert = True - self.alert_allowed = False + if self.last_lead_distance != -1 and (lead_dRel - self.last_lead_distance > LEAD_DEPART_DIST_THRESHOLD): + self.lead_depart_trigger_timer += 1 + else: + self.lead_depart_trigger_timer = 0 - self.lead_depart_alert = _lead_depart_alert + if self.lead_depart_trigger_timer * DT_MDL > TRIGGER_TIMER_THRESHOLD: + lead_depart_trigger = True + elif self.lead_depart_state != E2EStates.ARMED: + self.last_lead_distance = -1 + self.lead_depart_trigger_timer = 0 + + self.last_allowed = self.allowed + + return green_light_trigger, lead_depart_trigger + + @staticmethod + def update_state_machine(state: int, enabled: bool, allowed: bool, triggered: bool) -> tuple[int, bool]: + if state != E2EStates.INACTIVE: + if not allowed or not enabled: + state = E2EStates.INACTIVE + + else: + if state == E2EStates.ARMED: + if triggered: + state = E2EStates.CONSUMED + + elif state == E2EStates.CONSUMED: + pass + + elif state == E2EStates.INACTIVE: + if allowed and enabled: + state = E2EStates.ARMED + + return state, triggered + + def update(self, sm: messaging.SubMaster, events_sp: EventsSP) -> None: + self._read_params() + + green_light_trigger, lead_depart_trigger = self.update_alert_trigger(sm) + + self.prev_green_light_state = self.green_light_state + self.prev_lead_depart_state = self.lead_depart_state + + self.green_light_state, self.green_light_alert = self.update_state_machine( + self.green_light_state, + self.green_light_alert_enabled, + self.allowed and not self.has_lead, + green_light_trigger + ) + + self.lead_depart_state, self.lead_depart_alert = self.update_state_machine( + self.lead_depart_state, + self.lead_depart_alert_enabled, + self.allowed and self.lead_depart_armed, + lead_depart_trigger + ) if self.green_light_alert or self.lead_depart_alert: events_sp.add(custom.OnroadEventSP.EventName.e2eChime) From 063aa994d267d730f1400eebde18992be50e4883 Mon Sep 17 00:00:00 2001 From: Nayan Date: Thu, 16 Oct 2025 01:03:45 -0400 Subject: [PATCH 35/72] ui: Resize E2E Alerts (#1396) because people be enabling ALL THE UI Co-authored-by: Jason Wen --- selfdrive/ui/sunnypilot/qt/onroad/hud.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.h b/selfdrive/ui/sunnypilot/qt/onroad/hud.h index bc80a5e8bc..f021a949e4 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.h @@ -97,7 +97,7 @@ private: int speedLimitAssistFrame; QPixmap plus_arrow_up_img; QPixmap minus_arrow_down_img; - int e2e_alert_size = 300; + int e2e_alert_size = 250; QPixmap green_light_alert_img; bool greenLightAlert; int e2eAlertFrame; From 2825c00fcc72c30f4e8768e7e60c6db02513b1a0 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 17 Oct 2025 22:53:31 -0400 Subject: [PATCH 36/72] controlsd: update lateral delay param in a separate thread (#1402) --- selfdrive/controls/controlsd.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 3601ae310e..1694afee23 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -99,7 +99,6 @@ class Controls(ControlsExt, ModelStateBase): self.LaC.extension.update_model_v2(self.sm['modelV2']) - self.lat_delay = get_lat_delay(self.params, self.sm["liveDelay"].lateralDelay) self.LaC.extension.update_lateral_lag(self.lat_delay) long_plan = self.sm['longitudinalPlan'] @@ -234,6 +233,9 @@ class Controls(ControlsExt, ModelStateBase): while not evt.is_set(): self.get_params_sp() + if self.CP.lateralTuning.which() == 'torque': + self.lat_delay = get_lat_delay(self.params, self.sm["liveDelay"].lateralDelay) + time.sleep(0.1) def run(self): From 72282f2d2e0ffeab3b49fc82e1504e08f55d279f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 17 Oct 2025 23:30:06 -0400 Subject: [PATCH 37/72] Speed Limit Assist: update events handling (#1400) * Speed Limit Assist: update active event handling * ok no more for non pcm long it was annoying * 5 seconds preActive for non pcm long now * Revert "5 seconds preActive for non pcm long now" This reverts commit dfcc601035f4c34c8ce213cb221e9148b96fdcc3. * dynamic alert size * do the same here * lint --- .../lib/speed_limit/speed_limit_assist.py | 18 +++++++++++---- sunnypilot/selfdrive/selfdrived/events.py | 23 +++++++------------ 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py index 302d8cd14d..e2e1125a79 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py @@ -109,6 +109,16 @@ class SpeedLimitAssist: def target_set_speed_confirmed(self) -> bool: return bool(self.v_cruise_cluster_conv == self.target_set_speed_conv) + @property + def v_cruise_cluster_below_confirm_speed_threshold(self) -> bool: + return bool(self.v_cruise_cluster_conv < CONFIRM_SPEED_THRESHOLD[self.is_metric]) + + def update_active_event(self, events_sp: EventsSP) -> None: + if self.v_cruise_cluster_below_confirm_speed_threshold: + events_sp.add(EventNameSP.speedLimitChanged) + else: + events_sp.add(EventNameSP.speedLimitActive) + def get_v_target_from_control(self) -> float: if self._has_speed_limit: if self.pcm_op_long and self.is_enabled: @@ -175,7 +185,7 @@ class SpeedLimitAssist: @property def apply_confirm_speed_threshold(self) -> bool: # below CST: always require user confirmation - if self.v_cruise_cluster_conv < CONFIRM_SPEED_THRESHOLD[self.is_metric]: + if self.v_cruise_cluster_below_confirm_speed_threshold: return True # at/above CST: @@ -351,15 +361,15 @@ class SpeedLimitAssist: if self.is_active: if self._state_prev not in ACTIVE_STATES: - events_sp.add(EventNameSP.speedLimitActive) + self.update_active_event(events_sp) # only notify if we acquire a valid speed limit # do not check has_speed_limit here elif self._speed_limit != self.speed_limit_prev: if self.speed_limit_prev <= 0: - events_sp.add(EventNameSP.speedLimitActive) + self.update_active_event(events_sp) elif self.speed_limit_prev > 0 and self._speed_limit > 0: - events_sp.add(EventNameSP.speedLimitChanged) + self.update_active_event(events_sp) def update(self, long_enabled: bool, long_override: bool, v_ego: float, a_ego: float, v_cruise_cluster: float, speed_limit: float, speed_limit_final_last: float, has_speed_limit: bool, distance: float, events_sp: EventsSP) -> None: diff --git a/sunnypilot/selfdrive/selfdrived/events.py b/sunnypilot/selfdrive/selfdrived/events.py index 5d5424bb16..4edc0bd470 100644 --- a/sunnypilot/selfdrive/selfdrived/events.py +++ b/sunnypilot/selfdrive/selfdrived/events.py @@ -4,7 +4,6 @@ from openpilot.common.constants import CV from openpilot.sunnypilot.selfdrive.selfdrived.events_base import EventsBase, Priority, ET, Alert, \ NoEntryAlert, ImmediateDisableAlert, EngagementAlert, NormalPermanentAlert, AlertCallbackType, wrong_car_mode_alert from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit import PCM_LONG_REQUIRED_MAX_SET_SPEED, CONFIRM_SPEED_THRESHOLD -from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.helpers import compare_cluster_target AlertSize = log.SelfdriveState.AlertSize @@ -34,6 +33,9 @@ def speed_limit_pre_active_alert(CP: car.CarParams, CS: car.CarState, sm: messag speed_conv = CV.MS_TO_KPH if metric else CV.MS_TO_MPH speed_limit_final_last = sm['longitudinalPlanSP'].speedLimit.resolver.speedLimitFinalLast speed_limit_final_last_conv = round(speed_limit_final_last * speed_conv) + alert_1_str = "" + alert_2_str = "" + alert_size = AlertSize.none if CP.openpilotLongitudinalControl and CP.pcmCruise: # PCM long @@ -41,24 +43,15 @@ def speed_limit_pre_active_alert(CP: car.CarParams, CS: car.CarState, sm: messag pcm_long_required_max = cst_low if speed_limit_final_last_conv < CONFIRM_SPEED_THRESHOLD[metric] else cst_high pcm_long_required_max_set_speed_conv = round(pcm_long_required_max * speed_conv) speed_unit = "km/h" if metric else "mph" + + alert_1_str = "Speed Limit Assist: Activation Required" alert_2_str = f"Manually change set speed to {pcm_long_required_max_set_speed_conv} {speed_unit} to activate" - else: - # Non PCM long - v_cruise_cluster = CS.vCruiseCluster * CV.KPH_TO_MS - - req_plus, req_minus = compare_cluster_target(v_cruise_cluster, speed_limit_final_last, metric) - arrow_str = "" - if req_plus: - arrow_str = "RES/+" - elif req_minus: - arrow_str = "SET/-" - - alert_2_str = f"Operate the {arrow_str} cruise control button to activate" + alert_size = AlertSize.mid return Alert( - "Speed Limit Assist: Activation Required", + alert_1_str, alert_2_str, - AlertStatus.normal, AlertSize.mid, + AlertStatus.normal, alert_size, Priority.LOW, VisualAlert.none, AudibleAlertSP.promptSingleLow, .1) From 523c92c6fe06a3564fa5d0208f24fcd21c6d3628 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 17 Oct 2025 23:41:33 -0400 Subject: [PATCH 38/72] Speed Limit Assist: lower `preActive` timer for Non PCM Longitudinal and ICBM cars (#1403) 5 seconds preActive for non pcm long now --- .../lib/speed_limit/speed_limit_assist.py | 20 +++++++++++-------- .../tests/test_speed_limit_assist.py | 4 ++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py index e2e1125a79..b948452059 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py @@ -27,7 +27,11 @@ ACTIVE_STATES = (SpeedLimitAssistState.active, SpeedLimitAssistState.adapting) ENABLED_STATES = (SpeedLimitAssistState.preActive, SpeedLimitAssistState.pending, *ACTIVE_STATES) DISABLED_GUARD_PERIOD = 0.5 # secs. -PRE_ACTIVE_GUARD_PERIOD = 15 # secs. Time to wait after activation before considering temp deactivation signal. +# secs. Time to wait after activation before considering temp deactivation signal. +PRE_ACTIVE_GUARD_PERIOD = { + True: 15, + False: 5, +} SPEED_LIMIT_CHANGED_HOLD_PERIOD = 1 # secs. Time to wait after speed limit change before switching to preActive. LIMIT_MIN_ACC = -1.5 # m/s^2 Maximum deceleration allowed for limit controllers to provide. @@ -241,7 +245,7 @@ class SpeedLimitAssist: self.state = SpeedLimitAssistState.inactive elif self.speed_limit_changed and self.apply_confirm_speed_threshold: self.state = SpeedLimitAssistState.preActive - self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL) + self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL) elif self._has_speed_limit and self.v_offset < LIMIT_SPEED_OFFSET_TH: self.state = SpeedLimitAssistState.adapting @@ -251,7 +255,7 @@ class SpeedLimitAssist: self.state = SpeedLimitAssistState.inactive elif self.speed_limit_changed and self.apply_confirm_speed_threshold: self.state = SpeedLimitAssistState.preActive - self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL) + self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL) elif self.v_offset >= LIMIT_SPEED_OFFSET_TH: self.state = SpeedLimitAssistState.active @@ -261,7 +265,7 @@ class SpeedLimitAssist: self._update_confirmed_state() elif self.speed_limit_changed: self.state = SpeedLimitAssistState.preActive - self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL) + self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL) # PRE_ACTIVE elif self.state == SpeedLimitAssistState.preActive: @@ -287,7 +291,7 @@ class SpeedLimitAssist: self._update_confirmed_state() elif self._has_speed_limit: self.state = SpeedLimitAssistState.preActive - self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL) + self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL) else: self.state = SpeedLimitAssistState.pending @@ -313,7 +317,7 @@ class SpeedLimitAssist: elif self.speed_limit_changed and self.apply_confirm_speed_threshold: self.state = SpeedLimitAssistState.preActive - self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL) + self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL) # PRE_ACTIVE elif self.state == SpeedLimitAssistState.preActive: @@ -327,7 +331,7 @@ class SpeedLimitAssist: elif self.state == SpeedLimitAssistState.inactive: if self.speed_limit_changed: self.state = SpeedLimitAssistState.preActive - self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL) + self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL) elif self._update_non_pcm_long_confirmed_state(): self.state = SpeedLimitAssistState.active @@ -343,7 +347,7 @@ class SpeedLimitAssist: self.state = SpeedLimitAssistState.active elif self._has_speed_limit: self.state = SpeedLimitAssistState.preActive - self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL) + self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL) else: self.state = SpeedLimitAssistState.inactive diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py b/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py index d2c7a4716d..168c53016b 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py @@ -39,7 +39,7 @@ class TestSpeedLimitAssist: self.events_sp = EventsSP() CI = self._setup_platform(TOYOTA.TOYOTA_RAV4_TSS2) self.sla = SpeedLimitAssist(CI.CP) - self.sla.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL) + self.sla.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.sla.pcm_op_long] / DT_MDL) self.pcm_long_max_set_speed = PCM_LONG_REQUIRED_MAX_SET_SPEED[self.sla.is_metric][1] # use 80 MPH for now self.speed_conv = CV.MS_TO_KPH if self.sla.is_metric else CV.MS_TO_MPH @@ -114,7 +114,7 @@ class TestSpeedLimitAssist: self.sla.state = SpeedLimitAssistState.preActive self.sla.update(True, False, SPEED_LIMITS['city'], 0, SPEED_LIMITS['highway'], SPEED_LIMITS['city'], SPEED_LIMITS['city'], True, 0, self.events_sp) - for _ in range(int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL)): + for _ in range(int(PRE_ACTIVE_GUARD_PERIOD[self.sla.pcm_op_long] / DT_MDL)): self.sla.update(True, False, SPEED_LIMITS['city'], 0, SPEED_LIMITS['highway'], SPEED_LIMITS['city'], SPEED_LIMITS['city'], True, 0, self.events_sp) assert self.sla.state == SpeedLimitAssistState.inactive From 025a930ce8f4cbf1650ac5bfc12dd7cdd194fd37 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 18 Oct 2025 04:04:48 -0400 Subject: [PATCH 39/72] ui: update longitudinal-related settings handling (#1401) * ui: update ICBM-related settings handling * oops * oops * single location * some more * fix cruise toggles * always init true * check this * nah * should be this --- .../speed_limit/speed_limit_settings.cc | 13 +++-- .../speed_limit/speed_limit_settings.h | 1 + .../qt/offroad/settings/longitudinal_panel.cc | 50 +++++++++++-------- .../qt/offroad/settings/longitudinal_panel.h | 2 +- selfdrive/ui/sunnypilot/qt/util.cc | 4 ++ selfdrive/ui/sunnypilot/qt/util.h | 1 + 6 files changed, 45 insertions(+), 26 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc index 5c3b03d2af..1696c7f63a 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc @@ -7,6 +7,8 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h" +#include "selfdrive/ui/sunnypilot/qt/util.h" + SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent) { subPanelFrame = new QFrame(); QVBoxLayout *subPanelLayout = new QVBoxLayout(subPanelFrame); @@ -109,7 +111,7 @@ void SpeedLimitSettings::refresh() { QString offsetLabel = QString::fromStdString(params.get("SpeedLimitValueOffset")); bool has_longitudinal_control; - bool intelligent_cruise_button_management_available; + bool has_icbm; auto cp_bytes = params.get("CarParamsPersistent"); auto cp_sp_bytes = params.get("CarParamsSPPersistent"); if (!cp_bytes.empty() && !cp_sp_bytes.empty()) { @@ -121,16 +123,16 @@ void SpeedLimitSettings::refresh() { cereal::CarParamsSP::Reader CP_SP = cmsg_sp.getRoot(); has_longitudinal_control = hasLongitudinalControl(CP); - intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable(); + has_icbm = hasIntelligentCruiseButtonManagement(CP_SP); - if (!has_longitudinal_control && CP_SP.getPcmCruiseSpeed()) { + if (!has_longitudinal_control && !has_icbm) { if (speed_limit_mode_param == SpeedLimitMode::ASSIST) { params.put("SpeedLimitMode", std::to_string(static_cast(SpeedLimitMode::WARNING))); } } } else { has_longitudinal_control = false; - intelligent_cruise_button_management_available = false; + has_icbm = false; } speed_limit_mode_settings->setDescription(modeDescription(speed_limit_mode_param)); @@ -150,13 +152,14 @@ void SpeedLimitSettings::refresh() { speed_limit_offset->showDescription(); } - if (has_longitudinal_control || intelligent_cruise_button_management_available) { + if (has_longitudinal_control || has_icbm) { speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues(getSpeedLimitModeValues())); } else { speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues( {SpeedLimitMode::OFF, SpeedLimitMode::INFORMATION, SpeedLimitMode::WARNING})); } + speed_limit_mode_settings->refresh(); speed_limit_mode_settings->showDescription(); speed_limit_offset->showDescription(); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h index 23fa7b4ece..69e85814c2 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h @@ -35,6 +35,7 @@ private: SpeedLimitPolicy *speedLimitPolicyScreen; ButtonParamControlSP *speed_limit_offset_settings; OptionControlSP *speed_limit_offset; + bool icbm_available = false; static QString offsetDescription(SpeedLimitOffsetType type = SpeedLimitOffsetType::NONE) { QString none_str = tr("⦿ None: No Offset"); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc index f2c7ea3825..2cc59ea333 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc @@ -7,6 +7,8 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h" +#include "selfdrive/ui/sunnypilot/qt/util.h" + LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) { setStyleSheet(R"( #back_btn { @@ -40,7 +42,9 @@ LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) { "", this ); - intelligentCruiseButtonManagement->setConfirmation(true, false); + QObject::connect(intelligentCruiseButtonManagement, &ParamControlSP::toggleFlipped, this, [=](bool) { + refresh(offroad); + }); list->addItem(intelligentCruiseButtonManagement); dynamicExperimentalControl = new ParamControlSP( @@ -112,22 +116,41 @@ void LongitudinalPanel::refresh(bool _offroad) { has_longitudinal_control = hasLongitudinalControl(CP); is_pcm_cruise = CP.getPcmCruise(); - intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable(); + has_icbm = hasIntelligentCruiseButtonManagement(CP_SP); - if (!intelligent_cruise_button_management_available || has_longitudinal_control) { + if (CP_SP.getIntelligentCruiseButtonManagementAvailable() && !has_longitudinal_control) { + intelligentCruiseButtonManagement->setEnabled(offroad); + } else { params.remove("IntelligentCruiseButtonManagement"); + intelligentCruiseButtonManagement->setEnabled(false); } - if (!has_longitudinal_control && CP_SP.getPcmCruiseSpeed()) { + if (has_longitudinal_control || has_icbm) { + // enable Custom ACC Increments when long is available and is not PCM cruise + customAccIncrement->setEnabled(((has_longitudinal_control && !is_pcm_cruise) || has_icbm) && offroad); + dynamicExperimentalControl->setEnabled(has_longitudinal_control); + SmartCruiseControlVision->setEnabled(true); + SmartCruiseControlMap->setEnabled(true); + } else { params.remove("CustomAccIncrementsEnabled"); params.remove("DynamicExperimentalControl"); params.remove("SmartCruiseControlVision"); params.remove("SmartCruiseControlMap"); + customAccIncrement->setEnabled(false); + dynamicExperimentalControl->setEnabled(false); + SmartCruiseControlVision->setEnabled(false); + SmartCruiseControlMap->setEnabled(false); } + + intelligentCruiseButtonManagement->refresh(); + customAccIncrement->refresh(); + dynamicExperimentalControl->refresh(); + SmartCruiseControlVision->refresh(); + SmartCruiseControlMap->refresh(); } else { has_longitudinal_control = false; is_pcm_cruise = false; - intelligent_cruise_button_management_available = false; + has_icbm = false; } QString accEnabledDescription = tr("Enable custom Short & Long press increments for cruise speed increase/decrease."); @@ -139,8 +162,8 @@ void LongitudinalPanel::refresh(bool _offroad) { customAccIncrement->setDescription(onroadOnlyDescription); customAccIncrement->showDescription(); } else { - if (has_longitudinal_control || intelligent_cruise_button_management_available) { - if (is_pcm_cruise) { + if (has_longitudinal_control || has_icbm) { + if (has_longitudinal_control && is_pcm_cruise) { customAccIncrement->setDescription(accPcmCruiseDisabledDescription); customAccIncrement->showDescription(); } else { @@ -150,21 +173,8 @@ void LongitudinalPanel::refresh(bool _offroad) { customAccIncrement->toggleFlipped(false); customAccIncrement->setDescription(accNoLongDescription); customAccIncrement->showDescription(); - intelligentCruiseButtonManagement->toggleFlipped(false); } } - bool icbm_allowed = intelligent_cruise_button_management_available && !has_longitudinal_control; - intelligentCruiseButtonManagement->setEnabled(icbm_allowed && offroad); - - // enable toggle when long is available and is not PCM cruise - bool cai_allowed = (has_longitudinal_control && !is_pcm_cruise) || icbm_allowed; - customAccIncrement->setEnabled(cai_allowed && !offroad); - customAccIncrement->refresh(); - - dynamicExperimentalControl->setEnabled(has_longitudinal_control); - SmartCruiseControlVision->setEnabled(has_longitudinal_control || icbm_allowed); - SmartCruiseControlMap->setEnabled(has_longitudinal_control || icbm_allowed); - offroad = _offroad; } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h index 127b7871eb..2b9c0b8968 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h @@ -25,7 +25,7 @@ private: Params params; bool has_longitudinal_control = false; bool is_pcm_cruise = false; - bool intelligent_cruise_button_management_available = false;; + bool has_icbm = false; bool offroad = false; QStackedLayout *main_layout = nullptr; diff --git a/selfdrive/ui/sunnypilot/qt/util.cc b/selfdrive/ui/sunnypilot/qt/util.cc index be39b297d7..eaa1f4bd12 100644 --- a/selfdrive/ui/sunnypilot/qt/util.cc +++ b/selfdrive/ui/sunnypilot/qt/util.cc @@ -122,3 +122,7 @@ std::optional loadCerealEvent(Params& params, const std:: return std::nullopt; } } + +bool hasIntelligentCruiseButtonManagement(const cereal::CarParamsSP::Reader &car_params_sp) { + return car_params_sp.getIntelligentCruiseButtonManagementAvailable() && Params().getBool("IntelligentCruiseButtonManagement"); +} diff --git a/selfdrive/ui/sunnypilot/qt/util.h b/selfdrive/ui/sunnypilot/qt/util.h index 4b9d615ce5..60a73615ba 100644 --- a/selfdrive/ui/sunnypilot/qt/util.h +++ b/selfdrive/ui/sunnypilot/qt/util.h @@ -23,3 +23,4 @@ std::optional getParamIgnoringDefault(const std::string ¶m_name, co QMap loadPlatformList(); QStringList searchFromList(const QString &query, const QStringList &list); std::optional loadCerealEvent(Params& params, const std::string& _param); +bool hasIntelligentCruiseButtonManagement(const cereal::CarParamsSP::Reader &car_params_sp); From c85b6a0d1c62537fd7e8341d7cf884deb359f504 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 21 Oct 2025 00:53:16 -0400 Subject: [PATCH 40/72] branches: track sunnypilot release branches separately (#1409) * branches: track sunnypilot release branches separately * more remotes for legacy support * bruh * revert --- common/params_keys.h | 1 + selfdrive/ui/qt/offroad/developer_panel.h | 2 +- .../qt/offroad/settings/developer_panel.cc | 5 ++++- system/manager/manager.py | 1 + system/version.py | 14 ++++++++++---- 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index dd58462a95..d8485be157 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -154,6 +154,7 @@ inline static std::unordered_map keys = { {"IntelligentCruiseButtonManagement", {PERSISTENT | BACKUP , BOOL}}, {"InteractivityTimeout", {PERSISTENT | BACKUP, INT, "0"}}, {"IsDevelopmentBranch", {CLEAR_ON_MANAGER_START, BOOL}}, + {"IsReleaseSpBranch", {CLEAR_ON_MANAGER_START, BOOL}}, {"LastGPSPositionLLK", {PERSISTENT, STRING}}, {"LeadDepartAlert", {PERSISTENT | BACKUP, BOOL, "0"}}, {"MaxTimeOffroad", {PERSISTENT | BACKUP, INT, "1800"}}, diff --git a/selfdrive/ui/qt/offroad/developer_panel.h b/selfdrive/ui/qt/offroad/developer_panel.h index 51b1b0b6f0..699fa1edcf 100644 --- a/selfdrive/ui/qt/offroad/developer_panel.h +++ b/selfdrive/ui/qt/offroad/developer_panel.h @@ -12,7 +12,7 @@ public: explicit DeveloperPanel(SettingsWindow *parent); void showEvent(QShowEvent *event) override; -private: +protected: Params params; ParamControl* adbToggle; ParamControl* joystickToggle; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc index 021f5b3776..9eb8da71ec 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc @@ -60,7 +60,7 @@ DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(pare void DeveloperPanelSP::updateToggles(bool offroad) { bool disable_updates = params.getBool("DisableUpdates"); - bool is_release = params.getBool("IsReleaseBranch"); + bool is_release = params.getBool("IsReleaseBranch") || params.getBool("IsReleaseSpBranch"); bool is_tested = params.getBool("IsTestedBranch"); bool is_development = params.getBool("IsDevelopmentBranch"); @@ -79,6 +79,9 @@ void DeveloperPanelSP::updateToggles(bool offroad) { enableGithubRunner->setVisible(!is_release); errorLogBtn->setVisible(!is_release); showAdvancedControls->setEnabled(true); + + joystickToggle->setVisible(!is_release); + longManeuverToggle->setVisible(!is_release); } void DeveloperPanelSP::showEvent(QShowEvent *event) { diff --git a/system/manager/manager.py b/system/manager/manager.py index 1186c25114..23e8ff7255 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -67,6 +67,7 @@ def manager_init() -> None: params.put_bool("IsDevelopmentBranch", build_metadata.development_channel) params.put_bool("IsTestedBranch", build_metadata.tested_channel) params.put_bool("IsReleaseBranch", build_metadata.release_channel) + params.put_bool("IsReleaseSpBranch", build_metadata.release_sp_channel) params.put("HardwareSerial", serial) # set dongle id diff --git a/system/version.py b/system/version.py index 9719311b7e..6ff290e032 100755 --- a/system/version.py +++ b/system/version.py @@ -13,8 +13,8 @@ from openpilot.common.git import get_commit, get_origin, get_branch, get_short_b RELEASE_SP_BRANCHES = ['release-c3', 'release'] TESTED_SP_BRANCHES = ['staging-c3', 'staging-c3-new', 'staging'] MASTER_SP_BRANCHES = ['master'] -RELEASE_BRANCHES = ['release3-staging', 'release3', 'release-tici', 'nightly'] + RELEASE_SP_BRANCHES -TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging', 'nightly-dev'] + TESTED_SP_BRANCHES +RELEASE_BRANCHES = ['release3-staging', 'release3', 'release-tici', 'nightly'] +TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging', 'nightly-dev'] + RELEASE_SP_BRANCHES + TESTED_SP_BRANCHES SP_BRANCH_MIGRATIONS = { ("tici", "staging-c3-new"): "staging-tici", @@ -96,7 +96,9 @@ class OpenpilotMetadata: @property def sunnypilot_remote(self) -> bool: return self.git_normalized_origin in ("github.com/sunnypilot/sunnypilot", - "github.com/sunnypilot/openpilot") + "github.com/sunnypilot/openpilot", + "github.com/sunnyhaibin/sunnypilot", + "github.com/sunnyhaibin/openpilot") @property def git_normalized_origin(self) -> str: @@ -120,6 +122,10 @@ class BuildMetadata: def release_channel(self) -> bool: return self.channel in RELEASE_BRANCHES + @property + def release_sp_channel(self) -> bool: + return self.channel in RELEASE_SP_BRANCHES + @property def canonical(self) -> str: return f"{self.openpilot.version}-{self.openpilot.git_commit}-{self.openpilot.build_style}" @@ -146,7 +152,7 @@ class BuildMetadata: return "staging" elif self.master_channel: return "master" - elif self.release_channel: + elif self.release_channel or self.release_sp_channel: return "release" else: return "feature" From cb5d12013683055f1b9b2361e1b80275431a5e1b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 21 Oct 2025 14:22:37 -0400 Subject: [PATCH 41/72] FCA: update minEnableSpeed and LKAS control logic (#1386) * FCA: update minEnableSpeed and LKAS control logic * bump --- opendbc_repo | 2 +- sunnypilot/selfdrive/car/car_specific.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index b8a00bddda..0a2315efd1 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit b8a00bddda562f981b24e099a3850209579e890a +Subproject commit 0a2315efd175176c318aa34d634d55d9dcd9350a diff --git a/sunnypilot/selfdrive/car/car_specific.py b/sunnypilot/selfdrive/car/car_specific.py index 97ce26429c..bce496ed28 100644 --- a/sunnypilot/selfdrive/car/car_specific.py +++ b/sunnypilot/selfdrive/car/car_specific.py @@ -37,7 +37,7 @@ class CarSpecificEventsSP: # TODO-SP: add 1 m/s hysteresis if CS.vEgo >= self.CP.minEnableSpeed: self.low_speed_alert = False - if CS.gearShifter != GearShifter.drive: + if self.CP.minEnableSpeed >= 14.5 and CS.gearShifter != GearShifter.drive: self.low_speed_alert = True if self.low_speed_alert: events.add(EventName.belowSteerSpeed) From f57de1c5b2b19807ac65f349e3c29546bef92fff Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 21 Oct 2025 17:12:57 -0400 Subject: [PATCH 42/72] version: a new beginning (#1411) * version: a new beginning * changelog * singular * show ours * actual * readjust * updated * more * official spelling * more * sync * fix * send it * push * we never had this lol * syncs --- .codespellignore | 1 + .../workflows/sunnypilot-build-prebuilt.yaml | 2 +- CHANGELOG.md | 1066 +++++++++++++++++ common/swaglog.cc | 4 +- common/tests/test_markdown.py | 2 +- common/tests/test_swaglog.cc | 4 +- release/build_release.sh | 2 +- release/build_stripped.sh | 2 +- release/ci/publish.sh | 4 +- selfdrive/ui/tests/cycle_offroad_alerts.py | 2 +- selfdrive/ui/tests/test_ui/run.py | 2 +- sunnypilot/common/version.h | 1 + system/loggerd/logger.cc | 4 +- system/updated/updated.py | 4 +- system/version.py | 4 +- tools/replay/api.cc | 4 +- tools/replay/consoleui.cc | 4 +- 17 files changed, 1095 insertions(+), 17 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 sunnypilot/common/version.h diff --git a/.codespellignore b/.codespellignore index 9328517d4a..ab63c0cd29 100644 --- a/.codespellignore +++ b/.codespellignore @@ -3,3 +3,4 @@ REGIST PullRequest cancelled FOF +NoO diff --git a/.github/workflows/sunnypilot-build-prebuilt.yaml b/.github/workflows/sunnypilot-build-prebuilt.yaml index 00ae1e28bf..f7719709ae 100644 --- a/.github/workflows/sunnypilot-build-prebuilt.yaml +++ b/.github/workflows/sunnypilot-build-prebuilt.yaml @@ -79,7 +79,7 @@ jobs: is_stable_branch="$(echo "$CONFIG" | jq -r '.stable_branch // false')"; echo "is_stable_branch=$is_stable_branch" >> $GITHUB_OUTPUT - stable_version=$(cat common/version.h | grep COMMA_VERSION | sed -e 's/[^0-9|.]//g'); + stable_version=$(cat sunnypilot/common/version.h | grep SUNNYPILOT_VERSION | sed -e 's/[^0-9|.]//g'); echo "version=$([ "$is_stable_branch" = "true" ] && echo "$stable_version" || echo "$BUILD")" >> $GITHUB_OUTPUT echo "extra_version_identifier=${environment}" >> $GITHUB_OUTPUT fi diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..687484affe --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1066 @@ +sunnypilot Version 2025.001.000 (2025-10-25) +======================== +* 🛠️ Major rewrite + * Most features are intended to be identical to previous versions with slight improvements + * Fully adopts upstream commaai’s openpilot, opendbc (car interface and safety), and panda test suites to ensure consistent safety compliance and reliability across all systems + * Added regression testing to verify expected behavior and maintain stability across core modules + * Aligns with comma.ai’s safety policy: preserving driver monitoring, actuation checks, and safety test suite coverage + * Some features have not yet been reimplemented in this rewrite and are temporarily disabled in this release. They may return in future releases once fully ported and validated. See the end of the changelog to get a list of what's not going to be present. +* 🌟 Major Features & Systems + * Modular Assistive Driving System (MADS) + * Complete driving assistance framework + * Driving Model Manager + * Custom driving model selection with support for about 86 models (as of writing), from Night Strike (October 2023) up to The Cool People’s Models (October 2025) + * Neural Network Lateral Control (NNLC) (Formerly NNFF) + * Advanced torque-based lateral control + * Dynamic Experimental Control (DEC) + * Intelligent longitudinal control adaptation + * Speed Limit Assist (SLA) + * Comprehensive speed limit integration featuring @pfeiferj's `mapd` for offline map limits downloads, a Speed Limit Resolver for sourcing data (from car, map, combined, etc), on-screen UI for Speed Limit Information/Warning, and Speed Limit Assist (SLA) to adjust cruise speed automatically. + * Intelligent Cruise Button Management (ICBM) + * System designed to manage the vehicle’s speed by sending cruise control button commands to the car’s ECU. + * Smart Cruise Control Map & Vision (SCC-M / SCC-V) + * When using any form of long control (openpilot longitudinal or ICBM) it will control the speed at which you enter and perform a turn by leveraging map data (SCC-M) and/or by leveraging what the model sees about the curve ahead (SCC-V) + * Vehicle Selector + * If your vehicle isn’t fingerprinted automatically, you can still use the vehicle selector to get it working + * sunnylink Integration + * Cloud connectivity and settings backup/restore + * PENDING: The infrastructure is ready for remote setting management, including remote driving model switching. An announcement will be made when this is ready to use in current and future releases. + * External Storage Support + * Expanded storage options + * mapd Integration (thanks to @pfeiferj) + * Allow downloading OpenStreetMap databases for your area, which could be useful for Speed Limit Assist (SLA) +* User Interface Enhancements + * Complete UI Redesign from Default openpilot Experience + * A total overhaul of the sunnypilot offroad user interface for a modern and intuitive experience. + * New Settings Panels + * Reorganized settings into dedicated panels: Steering, Longitudinal, Vehicle, Models, Visuals, Display, and Trips. + * Advanced Controls Toggle + * Out of the box experience has a slightly reduced set of settings for a lower barrier of entry, once you are ready, you can get a few extra settings by toggling on the Advanced Controls. + * Models Panel + * A dedicated panel for model management, featuring a download manager, model folders, a favorites system, fuzzy search, and a cache refresh button. + * Visuals & Display + * Extensive customization options including brightness controls, custom interactivity timeouts, green light indicator, lead vehicle indicator, on-screen turn signals, blind spot indicators, lead chevron info, standstill timer, road name display, and a Tesla-like 🌈 rainbow road path. + * Screen Off while driving + * Options to turn the screen off while driving and customize wake-up behavior for alerts. + * Branch & Platform Selectors + * Improved software management with a searchable branch selector and a platform selector that displays the current fingerprint. + * Developer UI + * An enhanced developer UI with better alert positioning and an integrated error log viewer. + * Convenience Features + * Added an “Exit Offroad” button, “Always Offroad” mode, Quiet Mode, and customizable max time offroad settings. + * OpenStreetMap Database Downloader + * The OpenStreetMap database downloader now includes a search feature for easily finding areas. +* Model and AI Improvements + * Modular Model Backend + * Major refactor of `modeld` to support modular runners (SNPE, thneed, tinygrad) and dynamic model inputs. + * Enhanced Model Outputs + * Models now provide additional outputs like “turn desires” for improved control. + * Live Parameter Adjustments + * Support for live delay adjustments and software delay controls directly from the UI. + * Model Management + * Added model caching, automatic refresh capabilities, and shape inference from inputs for better compatibility. +* Control Systems + * Pause Lateral on Blinker + * Option to temporarily pause lateral control when the turn signal is active. + * Custom ACC Setpoint Increments + * Configure custom increments for adjusting the ACC set speed for applicable vehicle platforms. + * Steering on Brake Press + * Customizable steering behavior when the brake pedal is pressed. + * Enforce Torque Lateral Control + * New customized settings for fine-tuning torque-based steering. + * Automatic Lane Change + * Support for automatic lane changes, including a mode to disable it. +* Technical Infrastructure + * Custom Cereal Implementation + * Migrated sunnypilot-specific events, car parameters, and car controls to a dedicated cereal for better compatibility and performance. + * Car Interface Abstractions + * Refactored car interfaces to support brand-specific settings and easier integration. + * Param Store Caching + * Implemented a cache for the parameter store to reduce startup times, with support for live parameter updates. + * Enhanced Error Handling + * Improved exception management and Sentry logging for better stability and debugging. + * Docker & CI/CD + * Full Docker image support, a dedicated GitHub runner service, and comprehensive improvements to the entire CI/CD pipeline for automated testing, building, and releasing. +* Bug Fixes and Stability + * Registration Requirement Removed + * No longer necessary to register the device to go onroad. + * Panda Firmware Checks + * Improved firmware checks to gracefully handle deprecated Panda devices. + * Numerous Fixes + * Addressed a wide range of bugs across the system for a more stable and reliable experience. +* Developer Experience + * CLion IDE integration and external tools + * Comprehensive testing and build automation + * Model building and publishing automation + * UI preview generation and testing + * Release drafting and version management + * Code quality and maintenance workflows +* Translations and Localization + * Korean translation updates + * Automated translation management system +* ❌ Removed + * Navigate on openpilot (NoO) + * Navigate on openpilot (NoO) has been removed as upstream is prioritizing improving the driving model’s capabilities and simplifying the training stack. + * The feature may return in a future upstream release by comma.ai once model improvements from upstream make it more reliable. + * Visuals: Rocket Fuel + * Visuals: Displaying Braking Status + * Vehicle: Toyota - Enforce Stock Longitudinal Control + * Subaru: Increase Steering Torque + * Longitudinal: Acceleration Personality + * UI: Display CPU Temperature on Sidebar + * Lateral: Block Lane Change with Road Edge Detection + * UI: Display DM Camera in Reverse Gear + * UI: Auto-hide Selected UI Elements + * Visuals: Display End-to-End Longitudinal Status + * Toyota: Stop and Go Hack (alpha) + * Visuals: Onroad Settings + * Honda: Serial Steering Support + * Volkswagen: Non-ACC Platforms Support + * Longitudinal: Dynamic Personality + * Honda Nidec: Allow Stock Longitudinal Control + * Lateral Planner: Dynamic Lane Profile + * Lateral Planner: Laneful Mode + * Lateral: Custom Camera and Path Offsets + * Toyota: Door Controls +* New Contributors + * @discountchubbs made their first contribution in "ui: Error log button to Developer panel (#627)" + * @First made their first contribution in "NNLC: bump max similarity for higher accuracy (#704)" + * @nayan made their first contribution in "UI: Update AbstractControlSP_SELECTOR and OptionControlSP (#800)" + * @wtogamiwtogami made their first contribution in "Add TOYOTA_RAV4_PRIME NNLC tuning gen 1 (#850)" + * @dparring made their first contribution in "FCA: Ram 1500 improvements (#797)" + * @Kirito3481 made their first contribution in "Update ko-kr translation (#1167)" + * @michael-was-taken made their first contributio@dzid26 in "Reorder README tables: show -new branches first (#1191)" + * @dzid26 made their first contribution in "docs: clarify pedal press (#1289)" + * @HazZelnutz made their first contribution in "Visuals: Turn signals on screen when blinker is used (#1291)" +************************ +* Synced with commaai's openpilot (v0.10.1) + * master commit c9dbf97649a27117be6d5955a49e2d4253337288 (September 12, 2025) +* New driving model + * World Model: removed global localization inputs + * World Model: 2x the number of parameters + * World Model: trained on 4x the number of segments + * Driving Vision Model: trained on 4x the number of segments +* Honda City 2023 support thanks to vanillagorillaa and drFritz! +* Honda N-Box 2018 support thanks to miettal! +* Honda Odyssey 2021-25 support thanks to csouers and MVL! + +sunnypilot - 0.9.7.1 (2024-06-13) +======================== +* New driving model + * Inputs the past curvature for smoother and more accurate lateral control + * Simplified neural network architecture in the model's last layers + * Minor fixes to desire augmentation and weight decay +* New driver monitoring model + * Improved end-to-end bit for phone detection +* Adjust driving personality with the follow distance button +* Support for hybrid variants of supported Ford models +* Fingerprinting without the OBD-II port on all cars +* Improved fuzzy fingerprinting for Ford and Volkswagen +************************ +* UPDATED: Synced with commaai's openpilot + * master commit f8cb04e (June 10, 2024) +* NEW❗: sunnylink (Alpha early access) + * NEW❗: Config/Settings Backup + * Remotely back up and restore sunnypilot settings easily + * Device registration with sunnylink ensures a secure, integrated experience across services + * AES encryption derived from the device's RSA private key is used for utmost security + * Settings are encrypted on-device, transmitted securely via HTTPS, and stored encrypted on sunnylink + * Prevents loss of settings after device resets, offering peace of mind through end-to-end encryption + * Early alpha access to all current and previous GitHub Sponsors and Patreon supporters + * GitHub account pairing from device settings scanning QR code + * Pairing your account will allow you to access features via our API (still WIP but accessible if you dig a little on our code 😉) + * Allow inheritance of your sponsorship status, allowing you to get extra features and early access whenever applicable +* NEW❗: iOS Siri Shortcuts Navigation support thanks to twilsonco and mike86437! + * iOS and macOS Shortcuts to quickly set navigation destinations from your iOS device + * comma Prime support + * Personal Mapbox/Amap/Google Maps token support + * Instructions on how to set up your iOS Siri Shortcuts: https://routinehub.co/shortcut/17677/ +* NEW❗: Forced Offroad mode + * Force sunnypilot in the offroad state even when the car is on + * When Forced Offroad mode is on, allows changing offroad-only settings even when the car is turned on + * To engage/disengage Force Offroad, go to Settings -> Device panel +* UPDATED: Auto Lane Change Timer -> Auto Lane Change by Blinker + * NEW❗: New "Off" option to disable lane change by blinker +* UPDATED: Pause Lateral Below Speed with Blinker + * NEW❗: Customizable Pause Lateral Speed + * Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. +* UPDATED: Hyundai CAN Longitudinal + * Auto-enable radar tracks on platforms with applicable Mando radar +* UPDATED: Hyundai CAN-FD Camera-based SCC + * NEW❗: Parse lead info for camera-based SCC platforms with longitudinal support + * Improve lead tracking when using openpilot longitudinal +* RE-ENABLED: Map-based Turn Speed Control (M-TSC) for supported platforms + * openpilot Longitudinal Control available cars + * Custom Stock Longitudinal Control available cars +* UPDATED: Continued support for comma Pedal + * In response to the official deprecation of support for comma Pedal in the upstream, sunnypilot will continue maintaining software support for comma Pedal +* UPDATED: Driving Model Selector v4 + * NEW❗: Driving Model additions + * North Dakota (April 29, 2024) - NDv2 + * WD40 (April 09, 2024) - WD40 + * Duck Amigo (March 18, 2024) - DA + * Recertified Herbalist (March 01, 2024) - CHLR + * Legacy Driving Models with Navigate on openpilot (NoO) support + * Includes Duck Amigo and all preceding models +* UPDATED: Bumping mapd by [@pfeiferj](https://github.com/pfeiferj) to version [v1.9.0](https://github.com/pfeiferj/mapd/releases/tag/v1.9.0) thanks to pfeiferj! +* UPDATED: Reset Mapbox Access Token -> Reset Access Tokens for Map Services + * Reset self-service access tokens for Mapbox, Amap, and Google Maps +* UPDATED: Upstream native support for Gap Adjust Cruise +* UPDATED: Neural Network Lateral Control (NNLC) + * Due to upstream changes with platform simplifications, most platforms will match and fallback to combined platform model + * This will be updated when the new mapping of platforms are restructured (thanks @twilsonco 😉) +* UI Updates + * Display Metrics Below Chevron + * NEW❗: Metrics is now being displayed below the chevron instead of above + * NEW❗: Display both Distance and Speed simultaneously + * NEW❗: View sunnylink connectivity status on the left sidebar! + +sunnypilot - 0.9.6.2 (2024-05-29) +======================== +* REMOVED: Screen Recorder + * Screen Recorder is removed due to unnecessary resource usage + * An improved version will be available in the near future. Stay tuned! + +sunnypilot - 0.9.6.1 (2024-02-27) +======================== +* New driving model + * Vision model trained on more data + * Improved driving performance + * Directly outputs curvature for lateral control +* New driver monitoring model + * Trained on larger dataset +* AGNOS 9 +* comma body streaming and controls over WebRTC +* Improved fuzzy fingerprinting for many makes and models +* Alpha longitudinal support for new Toyota models +* Chevrolet Equinox 2019-22 support thanks to JasonJShuler and nworb-cire! +* Dodge Durango 2020-21 support +* Hyundai Staria 2023 support thanks to sunnyhaibin! +* Kia Niro Plug-in Hybrid 2022 support thanks to sunnyhaibin! +* Lexus LC 2024 support thanks to nelsonjchen! +* Toyota RAV4 2023-24 support +* Toyota RAV4 Hybrid 2023-24 support +************************ +* UPDATED: Synced with commaai's openpilot + * master commit db57a21 (February 22, 2024) + * v0.9.6 release (February 27, 2024) +* UPDATED: Dynamic Experimental Control (DEC) + * Synced with dragonpilot-community/dragonpilot:beta3 commit f4ee52f +* NEW❗: Default Driving Model: Certified Herbalist v2 (February 13, 2024) +* UPDATED: Driving Model Selector v3 + * NEW❗: Driving Model additions + * Certified Herbalist v2 (February 13, 2024) - CHv2 + * Certified Herbalist (February 5, 2024) - CH + * Los Angeles v2 (January 24, 2024) - LAv2 + * Los Angeles (January 22, 2024) - LAv1 + * NEW❗: Model Caching thanks to DevTekVE! + * Model caching allows the selection of previously downloaded Driving Model + * Users can now access cached versions of selected models, eliminating redundant downloads for previously fetched models + * Legacy Driving Models support + * New Delhi (December 21, 2023) - ND + * Blue Diamond v2 (December 11, 2023) - BDv2 + * Blue Diamond (November 18, 2023) - BDv1 + * Farmville (November 7, 2023) - FV + * Night Strike (October 3, 2023) - NS + * Certain features are deprecated with newer Driving Models + * Dynamic Lane Profile (DLP) + * Custom Offsets +* UPDATED: Dynamic Lane Profile (DLP) + * Continued support for Legacy Driving Models (e.g., ND, BDv2, BDv1, FV, NS) + * Deprecated support for newer Driving Models (e.g., CHv2, CH, LAv2, LAv1) +* UPDATED: Custom Offsets + * Continued support for Legacy Driving Models (e.g., ND, BDv2, BDv1, FV, NS) + * Deprecated support for newer Driving Models (e.g., CHv2, CH, LAv2, LAv1) +* UPDATED: Hyundai/Kia/Genesis - ESCC Radar Interceptor + * Message parsing improvements with the latest firmware update: https://github.com/sunnypilot/panda/tree/test-escc-smdps +* UI Updates + * NEW❗: Visuals: Display Feature Status toggle + * Display the statuses of certain features on the driving screen + * NEW❗: Visuals: Enable Onroad Settings toggle + * Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu + * REMOVED: "Device ambient" temperature option on the sidebar +* FIXED: New comma 3X support +* FIXED: New comma eSIM support +* Bug fixes and performance improvements + +sunnypilot - 0.9.5.3 (2023-12-24) +======================== +* UPDATED: Dynamic Experimental Control (DEC) + * Synced with dragonpilot-community/dragonpilot:lp-dp-beta2 commit 578d38b +* UPDATED: Driving Model Selector v2 + * Driving models sort in descending order based on availability date + * Experimental/unmerged driving models are only available in "dev-c3" branch + * To select and use experimental driving models, navigate to "Software" panel, select the "dev-c3" branch, and check for update +* UPDATED: Vision-based Turn Speed Control (V-TSC) implementation + * Refactored implementation thanks to pfeiferj! + * More accurate and consistent velocity calculation to achieve smoother longitudinal control in curves +* NEW❗: Speed Limit Warning + * Display alert and/or chime to warn the driver when the cruising speed is faster than the speed limit plus the Warning Offset + * Customizable Warning Offset, independent of Speed Limit Control (SLC)'s Limit Offset +* UPDATED: Speed Limit Source Policy + * Selectable speed limit source for Speed Limit Control and Speed Limit Warning + * Applicable to: Speed Limit Control, Speed Limit Warning +* UPDATED: Speed Limit Control (SLC) + * Engage Mode: Removed "Warning Only" mode - this has been replaced by the new Speed Limit Warning sub-menu +* UPDATED: OpenStreetMap (OSM) implementation + * Refactored implementation thanks to pfeiferj! + * Less resource impact + * Significantly smaller sizes with databases + * All regions are available to download + * Weekly map updates thanks to pfeiferj! + * Increased the font size of the road name + * C3X-specific changes + * Altitude (ALT.) display on Developer UI + * Current street name on top of driving screen when "OSM Debug UI" is enabled +* UPDATED: Map-based Turn Speed Control (M-TSC) implementation + * Only available in "staging-c3" and "dev-c3" branches. If you are using "release-c3" branch, navigate to "Software" panel, select the desired target branch, and check for update + * Refactored implementation thanks to pfeiferj! + * Based on the new OpenStreetMap implementation + * Improved predicted curvature calculations from OpenStreetMap data +* UI updates + * RE-ENABLED: Navigation: Full screen support + * Display the map view in full screen + * To switch back to driving view, tap on the border edge +* Hyundai Bayon Non-SCC 2019 support thanks to polein78! + +sunnypilot - 0.9.5.2 (2023-12-07) +======================== +* NEW❗: MADS: Allow Navigate on openpilot in Chill Mode + * Allow navigation to feed map view into the driving model while using Chill Mode + * Support all platforms, including platforms that do not support openpilot longitudinal control & Experimental Mode +* NEW❗: Neural Network Lateral Controller + * Formerly known as "NNFF", this replaces the lateral "torque" controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy + * Contact @twilsonco in the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported +* NEW❗: Driving Model Selector + * Easily switch between driving models without reinstalling branches. Offering immediate access to the latest models upon release + * An internet connection is required for downloading models. Each model switch currently involves downloading the model again. Future updates may allow for offline switching + * Warning is displayed for metered connections to avoid unexpected data usage if on cellular data + * Change driving models via **Settings -> Software -> Current Driving Model**. +* NEW❗: Hyundai CAN longitudinal: + * NEW❗: Enable radar tracks for certain Santa Fe platforms + * Internal Combustion Engine (ICE) 2021-23 + * Hybrid 2022-23 + * Plug-in Hybrid 2022-23 +* NEW❗: Lane Change: When manually braking with steering engaged, turning on the turn signal will default to Nudge mode +* Volkswagen MQB CC only platforms (radar or no radar) support thanks to jyoung8607! + +sunnypilot - 0.9.5.1 (2023-11-17) +======================== +* UPDATED: Synced with commaai's master commit e94c3c5 +* NEW❗: Farmville driving model +* NEW❗: Onroad Settings Panel + * Onroad buttons (i.e., DLP, GAC) moved to its dedicated panel + * Driving Personality + * Dynamic Lane Profile (DLP) + * Dynamic Experimental Control (DEC) + * Speed Limit Control (SLC) +* NEW❗: Display main feature status on onroad view in real-time + * GAP - Driving Personality + * DLP - Dynamic Lane Profile + * DEC - Dynamic Experimental Control + * SLC - Speed Limit Control +* NEW❗: Dynamic Experimental Control (DEC) thanks to dragonpilot-community! + * Automatically determines and selects between openpilot ACC and openpilot End to End longitudinal based on conditions for a more natural drive + * Dynamic Experimental Control is only active while in Experimental Mode + * When Dynamic Experimental Control is ON, initially setting cruise speed will set to the vehicle's current speed +* NEW❗: Hyundai CAN longitudinal: + * NEW❗: Parse lead info for camera-based SCC platforms + * Improve lead tracking when using openpilot longitudinal + * NEW❗: Parse lead distance to display on car cluster + * Introduced better lead distance calculation to display on the car's cluster, replacing the binary "lead visible" indication on the SCC cluster + * Lead distance is now categorized into different ranges for more detailed and comprehensive information to the driver similar to how stock ACC does it + * NEW❗: Parse speed limit sign recognition from camera for certain supported platforms +* NEW❗: Subaru - Stop and Go auto-resume support thanks to martinl! + * Global (excluding Gen 2 and Hybrid) and Pre-Global support +* NEW❗: Toyota - Stop and Go hack + * Allow some Toyota/Lexus cars to auto resume during stop and go traffic + * Only applicable to certain models and model years +* NEW❗: Toyota: ZSS support thanks to dragonpilot-community and ErichMoraga! +* NEW❗: MSPA (Cereal structs refactor) + * Make sunnypilot Parsable Again - @sshane + * sunnypilot is now parsable with stock openpilot tools +* NEW❗: Display 3D buildings on map thanks to jakethesnake420! +* openpilot Longitudianl Control capable cars only + * UPDATED: Gap Adjust Cruise is now a part of Driving Personality + * [DISTANCE/FOLLOW DISTANCE/GAP DISTANCE] physical button on the steering wheel to select Driving Personality on by default + * Status now viewable in onroad view or Onroad Settings Panel + * REMOVED: Gap Adjust Cruise toggle +* UPDATED: Speed Limit Control (SLC) + * NEW❗: Speed Limit Engage Mode + * Select the desired mode to set the cruising speed to the speed limit + * Warning Only: Warn the driver when the vehicle is driven faster than the speed limit + * Auto: Automatic speed adjustment on motorways based on speed limit data + * User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit + * Supported platforms + * openpilot Longitudinal Control available cars (Excluding certain Toyota/Lexus, Ford, explained below) + * Custom Stock Longitudinal Control available cars + * Unsupported platforms + * Toyota/Lexus and Ford - most platforms do not allow us to control the PCM's set speed, requires testers to verify + * NEW❗: Speed limit source selector + * Select the desired precedence order of sources used to adapt cruise speed to road limits +* UPDATED: Custom Stock Longitudinal Control + * RE-ENABLED: Hyundai/Kia/Genesis CAN-FD platforms +* UPDATED: Custom Offsets reimplementation + * Camera Offset only works in Laneful (Laneful Only or Laneful in Auto mode when using Dynamic Lane Profile) + * Path Offset can be applied to both Laneless and Laneful +* UPDATED: Refactored Torque Lateral Control custom tuning menu + * NEW❗: Less Restrict Settings for Self-Tune (Beta) + * NEW❗: Custom Tuning for setting offline and live values in real-time +* UPDATED: Auto-detect custom Mapbox token if a personal Mapbox token is provided + * REMOVED: "Enable Mapbox Navigation" toggle +* UI updates + * New Settings menu redesign and improved interactions +* FIXED: Retain hotspot/tethering state was not consistently saved +* FIXED: Map stuck in "Map Loading" if comma Prime is active +* FIXED: OpenStreetMap implementation on C3X devices + * M-TSC + * Altitude (ALT.) display on Developer UI + * Current street name on top of driving screen when "OSM Debug UI" is enabled +* Hyundai Kona Non-SCC 2019 support thanks to Quex! +* Kia Seltos Non-SCC 2023-24 support thanks to Moodkiller and jeroid_! + +sunnypilot - 0.9.4.1 (2023-08-11) +======================== +* UPDATED: Synced with commaai's 0.9.4 release +* NEW❗: Moonrise driving model +* NEW❗: Ford upstream models support +* UPDATED: Dynamic Lane Profile selector in the "SP - Controls" menu +* REMOVED: Dynamic Lane Profile driving screen UI button +* FIXED: Disallow torque lateral control for angle control platforms (e.g. Ford, Nissan, Tesla) + * Torque lateral control cannot be used by angle control platforms, and would cause a "Controls Unresponsive" error if Torque lateral control is enforced in settings +* REMOVED: Speed Limit Style override +* Honda Accord 2016-17 support thanks to mlocoteta! + * Serial Steering hardware required. For more information, see https://github.com/mlocoteta/serialSteeringHardware +* mapd: utilize advisory speed limit in curves (#142) thanks to pfeiferj! + +sunnypilot - 0.9.3.1 (2023-07-09) +======================== +* UPDATED: Synced with commaai's 0.9.3 release +* NEW❗: Display Temperature on Sidebar toggle + * Display Ambient temperature, memory temperature, CPU core with the highest temperature, GPU temperature, or max of Memory/CPU/GPU on the sidebar + * Replace "Display CPU Temperature on Sidebar" toggle +* NEW❗: Hot Coffee driving model +* NEW❗: HKG CAN: Smoother Stopping Performance (Beta) toggle + * Smoother stopping behind a stopped car or desired stopping event. + * This is only applicable to HKG CAN platforms using openpilot longitudinal control +* NEW❗: Toyota: TSS2 longitudinal: Custom Tuning + * Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars thanks to dragonpilot-community! +* NEW❗: Enable Screen Recorder toggle + * Enable this will display a button on the onroad screen to toggle on or off real-time screen recording with UI elements. +* IMPROVED: Dynamic Lane Profile: when using Laneline planner via Laneline Mode or Auto Mode, enforce Laneless planner while traveling below 10 MPH or 16 km/h +* REMOVED: Display CPU Temperature on Sidebar + +sunnypilot - 0.9.2.3 (2023-06-18) +======================== +* NEW❗: Auto Lane Change: Delay with Blind Spot + * Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects an obstructing vehicle, ensuring safe maneuvering +* NEW❗: Driving Screen Off: Wake with Non-Critical Events + * When Driving Screen Off Timer is not set to "Always On": + * Enabled: Wake the brightness of the screen to display all events + * Disabled: Wake the brightness of the screen to display critical events + * Currently, all non-nudge modes are default to continue lane change after 1 seconds of blind spot detection +* NEW❗: Fleet Manager PIN Requirement toggle + * User can now enable or disable PIN requirement on the comma device before accessing Fleet Manager +* NEW❗: Reset all sunnypilot settings toggle +* NEW❗: Turn signals display on screen when blinker is used + * Green: Blinker is on + * Red: Blinker is on, car detected in the adjacent blind spot or road edge detected +* IMPROVED: mapd: better exceptions handling when loading dependencies +* UPDATED: Green Traffic Light Chime no longer displays an orange border when executed +* FIXED: mapd: Road name flashing caused by desync with last GPS timestamp +* FIXED: Ram HD (2500/3500): Ignore paramsd sanity check + * Live parameters have trouble with self-tuning on this platform with upstream openpilot 0.9.2 +* Hyundai: Longitudinal support for CAN-based Camera SCC cars thanks to Zack1010OP's Patreon sponsor! + +sunnypilot - 0.9.2.2 (2023-06-13) +======================== +* NEW❗: Toyota: Allow M.A.D.S. toggling with LKAS Button (Beta) +* IMPROVED: Ram: cruise button handling + +sunnypilot - 0.9.2.1 (2023-06-10) +======================== +* UPDATED: Synced with commaai's 0.9.2 release +* UPDATED: feature revamp with better stability +* UPDATED: + * M.A.D.S. + * Path color becomes LIGHT ORANGE during Driver Steering Override + * Gap Adjust Cruise (now known as Driving Personality in upstream openpilot 0.9.3): + * Updated profiles and jerk changes + * Experimental Mode support + * Three settings: Stock, Aggressive, and Maniac + * Stock is recommended and the default + * In Aggressive/Maniac mode, lead follow distance is shorter and quicker gas/brake response + * Dynamic Lane Profile + * Display blue borders on both sides of the driving path when Laneline mode is being used in the planner + * Auto Mode optimization + * Permanent: Laneless during Auto Lane Change execution + * Mapd + * OpenStreetMap Database: new regions added + * Developer UI (Dev UI) + * REMOVED: 2-column design + * NEW❗: 1-column + 1-row design + * Custom Stock Longitudinal Control + * NEW❗: Chrysler/Jeep/Ram support + * NEW❗: Mazda support + * NEW❗: Volkswagen PQ support + * DISABLED: Hyundai/Kia/Genesis CAN-FD platforms +* NEW❗: Switch between Chill (openpilot ACC) and Experimental (E2E longitudinal) with DISTANCE button on the steering wheel + * To switch between Chill and Experimental Mode: press and hold the DISTANCE button on the steering wheel for over 0.5 second + * All openpilot longitudinal capable cars support +* NEW❗: Nicki Minaj driving model +* NEW❗: Nissan and Mazda upstream models support +* NEW❗: Pre-Global Subaru upstream models support +* NEW❗: Display End-to-end Longitudinal Status (Beta) + * Display an icon that appears when the End-to-end model decides to start or stop +* NEW❗: Green Traffic Light Chime (Beta) + * A chime will play when the traffic light you are waiting for turns green, and you have no vehicle in front of you. +* NEW❗: Lead Vehicle Departure Alert + * Notify when the leading vehicle drives away +* NEW❗: Speedometer: Display True Speed + * Display the true vehicle current speed from wheel speed sensors. +* NEW❗: Speedometer: Hide from Onroad Screen +* NEW❗: Auto-Hide UI Buttons + * Hide UI buttons on driving screen after a 30-second timeout. Tap on the screen at anytime to reveal the UI buttons + * Applicable to Dynamic Lane Profile (DLP) and Gap Adjust Cruise (GAC) +* NEW❗: Display DM Camera in Reverse Gear + * Show Driver Monitoring camera while the car is in reverse gear +* NEW❗: Block Lane Change: Road Edge Detection (Beta) + * Block lane change when road edge is detected on the stalk actuated side +* NEW❗: Display CPU Temperature on Sidebar + * Display the CPU core with the highest temperature on the sidebar +* NEW❗: Display current driving model in Software settings +* NEW❗: HKG: smartMDPS automatic detection (installed with applicable firmware) +* FIXED: Unintended siren/alarm from the comma device if the vehicle is turned off too quickly in PARK gear +* FIXED: mapd: Exception handling for loading dependencies +* Fleet Manager via Browser support thanks to actuallylemoncurd, AlexandreSato, ntegan1, and royjr! + * Access your dashcam footage, screen recordings, and error logs when the car is turned off + * Connect to the device via Wi-Fi, mobile hotspot, or tethering on the comma device, then navigate to http://ipAddress:5050 to access. +* Honda Clarity 2018-22 support thanks to mcallbosco, vanillagorillaa and wirelessnet2! +* Ram: Steer to 0/7 MPH support thanks to vincentw56! +* Retain hotspot/tethering state across reboots thanks to rogerioaguas! + +sunnypilot - Version Latest (2023-02-22) +======================== +* UPDATED: Synced with commaai's master branch - 2023.02.19-04:52:00:GMT - 0.9.2 +* Refactor sunnypilot features to be more stable + +sunnypilot - Version Latest (2022-12-16) +======================== +* UPDATED: Synced with commaai's master branch - 2022.12.16-06:31:00:GMT - 0.9.1 +* NEW❗: GM: + * NEW❗: Gap Adjust Cruise support - Chill, Normal, Aggressive + * NEW❗: Experimental Mode: Hold DISTANCE button on the steering wheel for 0.5 second to switch between Experimental Mode and Chill Mode +* REMOVED❌: Toytoa: SnG Hack + * This method is not recommended and may cause some cars to not behave as expected + * SDSU is strongly recommended to enable SnG for Toyota vehicles without SnG from factory +* commaai: radard: add missing accel data for vision-only leads (commaai/openpilot#26619) - pending PR + * VOACC performance is drastically improved when using Chill Mode +* IMPROVED: M.A.D.S. events handling +* IMPROVED: UI: screen recorder button change +* IMPROVED: OpenStreetMap Offline Database optimization +* FIXED: Toyota: vehicles' LKAS button no longer has a delay with toggling M.A.D.S. +* FIXED: Toyota: brake pedal press at standstill causing Cruise Fault +* FIXED: Volkswagen MQB: reduce Camera Malfunction occurrences (requires testing) +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-12-10) +======================== +* IMPROVED: NEW❗ Developer UI design + * Second column metrics is now moved to the bottom of the screen + * ACC. = Acceleration + * L.S. = Lead Speed + * E.T. = EPS Torque + * B.D. = Bearing Degree + * FRI. = Friction + * L.A. = Lateral Acceleration + * ALT. = Altitude +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-12-07) +======================== +* NEW❗: Screen Recorder support thanks to neokii and Kumar! +* NEW❗: End-to-end longitudinal start/stop status icon + * Only appears when Experimental Mode is enabled +* NEW❗: End-to-end longitudinal car chime when starting + * Hyundai/Kia/Genesis CAN platform, Honda/Acura Bosch/Nidec, Toyota/Lexus + * i.e. Traffic light turns green, stop sign ready to go, etc. + * Only appears when Experimental Mode is enabled AND longitudinal control is disengaged +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-12-05) +======================== +* UPDATED: Synced with commaai's master branch - 2022.12.04-22:46:00:GMT - 0.9.1 +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-11-12) +======================== +* UPDATED: Synced with commaai's master branch - 2022.11.12-10:02:00:GMT - 0.8.17 +* FIXED: CAN Error for CAN HKG cars that do not have navigation from the factory +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-11-11) +======================== +* UPDATED: Synced with commaai's master branch - 2022.11.11-21:22:00:GMT - 0.8.17 +* commaai: AGNOS 6.2 (commaai/openpilot#26441) +* NEW❗: Speed Limit Control - HKG - add speed limit from car's navigation head unit + * Compatible with certain models, trims, and model years +* DISABLED: FCA: RAM HD - steer down to 0 +* FIXED: UI: End-to-end longitudinal button on driving screen synchronization +* FIXED: Honda: Longitudinal status with set cruise speed now displays properly in the car's dashboard +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-11-08) +======================== +* ADDED: New Zealand offline OpenStreetMap database + +sunnypilot - Version Latest (2022-11-04) +======================== +* UPDATED: Synced with commaai's master branch - 2022.11.05-01:44:00:GMT - 0.8.17 +* RE-ENABLED: Dynamic Lane Profile - preserves lanelines + * Can be found in "SP - Controls" menu +* NEW❗: DLP: switch to laneless for current/future curves thanks to @twilsonco! + * Can be found in "SP - Controls" menu +* NEW❗: UI: Road Camera Selector + * Enable this will display a button on the driving screen to select the driving camera + * Can be found in "SP - Visuals" menu +* NEW❗: Controls: Camera & Path Custom Offsets + * Only applicable to laneline mode when using Dynamic Lane Profile +* NEW❗: Buttons on driving screen are now sorted based on priority and availability +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-28) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.28-03:53:00:GMT - 0.8.17 +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-26) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.26-06:20:00:GMT - 0.8.17 +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-25) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.25-23:53:00:GMT - 0.8.17 +* Pre-Global Subaru support thanks to @martinl! +* NEW❗: Speed Limit values turn red when current speed is higher than posted speed limit +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-23) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.22-23:15:00:GMT - 0.8.17 +* IMPROVED: Custom Stock Longitudinal Control - HKG - only allow engagement on user button press +* IMPROVED: Custom Stock Longitudinal Control - Volkswagen MQB & PQ - more consistent set speed change +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-21) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.21-17:33:00:GMT - 0.8.17 +* IMPROVED: Custom Stock Longitudinal Control - Volkswagen MQB & PQ - more predictable button send logic +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-20) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.20-20:25:00:GMT - 0.8.17 +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-19) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.19-08:31:00:GMT - 0.8.17 +* IMPROVED: Controls: Speed Limit Control - accelerator press only disengage if "Disengage on Accelerator Pedal" is enabled +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-18) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.18-04:44:00:GMT - 0.8.17 +* RE-ENABLED: Volkswagen MQB & PQ with Custom Stock Longitudinal Control +* NEW❗: Steering Rate Cost Live Tune + * Enables live tune for Steering Rate Cost. Lower value allows steering wheel to move more freely at low speed + * Can be found in "SP - Controls" menu +* FIXED: MADS: GM - include Regen Paddle logic thanks to @twilsonco! +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-17) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.17-23:54:00:GMT+1 - 0.8.17 +* ENABLED: "Custom Stock Longitudinal Control" toggle for CAN-FD cars +* FIXED: HKG CAN-FD: Could not engage when openpilot longitudinal is enabled +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-13) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.13-19:43:00:GMT+1 - 0.8.17 +* ADDED: Live Tmux toggle + * Can be found in "SP - General" menu +* IMPROVED: OpenStreetMap Database Update - only check for database update with explicit user decision +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-11) +======================== +* ADDED: Hyundai openpilot longitudinal improvements - huge thanks to @aragon7777! +* ADDED: Check for OpenStreetMap Database Update button +* UPDATED: commaai: Low speed lateral control improvements (commaai:openpilot#26022, bbcd448) - pending PR +* FIXED: MUTCD speed limit spacing adjusts dynamically when no subtext is shown (i.e., speed limit offset, distance to next speed limit) +* FIXED: MADS: Intermittent CAN Error when engaging for Toyota Prius TSS-P +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-09) +======================== +* ADDED: commaai: Low speed lateral control improvements (commaai:openpilot#26022, bca288bb) - pending PR +* FIXED: MADS: Intermittent CAN Error when engaging for Toyota Prius TSS-P +* IMPROVED: mapd: stop signs and other supported traffic_calming tags are now slowing/stopping as expected +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-08) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.08-12:07:00:GMT+1 - 0.8.17 +* FIXED: MADS: Intermittent CAN Error when engaging for Toyota Prius TSS-P +* IMPROVED: mapd: Speed Humps are now set at 20 MPH or 32 km/h +* IMPROVED: OpenStreetMap Offline Database download experience +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-07) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.07-08:16:00:GMT - 0.8.17 +* NEW❗: OpenStreetMap database can now be downloaded locally for offline use + * Now offering US South, US West, US Northeast, US Florida, Taiwan, and South Africa + * Databases updated - 2022.10.05-03:30:00:GMT +* NEW❗: mapd: Stop Sign, Yield, Speed Bump, Speed Hump, Sharp Curve support - huge thanks to @move-fast and @dragonpilot-community! + * Go to https://openstreetmap.org and start mapping out your area! +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-09-30) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.30-22:43:00:GMT - 0.8.17 +* RE-ADDED: Torque Lateral Controller Live Tune Menu +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-09-23) +======================== +* ADDED: Developer UI: latAccelFactorFiltered & frictionCoefficientFiltered values displays in green if Torque is using live params +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-09-22) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.19-22:19:00:GMT - 0.8.17 +* NEW❗: Toggle to explicitly enable Custom Stock Longitudinal Control + * Applicable cars only: Honda, Hyundai/Kia/Genesis + * Settings -> Toggles menu + +sunnypilot - Version Latest (2022-09-21) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.19-22:19:00:GMT - 0.8.17 +* ADDED: Toggle to enable Live Torque (self/auto tune) with Torque lateral controller + * To enable, first enable "Enforce Torque Lateral Controller" toggle +* UPDATED: New metrics in Developer UI (when Live Torque is enabled) + * REMOVED: latAccelFactorRaw & frictionCoefficientRaw from torqued + * ADDED: latAccelFactorFiltered & frictionCoefficientFiltered from torqued +* REMOVED: Temporary remove Torque Lateral Controller Live Tune Menu + +sunnypilot - Version Latest (2022-09-20) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.19-22:19:00:GMT - 0.8.17 +* ADDED: Toggle to enable Live Torque (self/auto tune) with Torque lateral controller + * To enable, first enable "Enforce Torque Lateral Controller" toggle +* REMOVED: Temporary remove Torque Lateral Controller Live Tune Menu + +sunnypilot - Version Latest (2022-09-18) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.17-11:23:00:GMT - 0.8.17 +* ADDED: Kia Forte Non-SCC 2019 support for @askalice +* FIXED: Torque Lateral Control Live Tune now syncs with commaai:openpilot#25822 +* FIXED: mapd dependencies no longer need to be re-downloaded after unknown reboots + +sunnypilot - Version Latest (2022-09-17) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.17-11:23:00:GMT - 0.8.17 +* NEW❗: Non SCC HKG support + * Custom Stock Longitudinal Control + * ❗No❗ openpilot longitudinal control +* FIXED: Honda Bosch random low-value set speed changes + +sunnypilot - Version Latest (2022-09-16) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.16-20:23:00:GMT - 0.8.17 + +sunnypilot - Version Latest (2022-09-15) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.16-02:00:00:GMT - 0.8.17 +* FIXED: Block additional auto lane change actions if blinker stays on after the first lane change +* REVERTED: Some Toyota with LKAS button no longer requires double press to engage/disengage M.A.D.S. + +sunnypilot - Version Latest (2022-09-14)u +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.11-02:47:00:GMT - 0.8.17 +* NEW❗: GM models supported in Force Car Recognition (FCR) + * Under "SP - Vehicles" +* NEW❗: Prompt to select car in "SP - Vehicles" if car unrecognized on startup +* FIXED: Some Toyota with LKAS button no longer requires double press to engage/disengage M.A.D.S. +* UPDATED: ESCC: Use radar tracks from radar if available + +sunnypilot - Version Latest (2022-09-13) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.11-02:47:00:GMT - 0.8.17 +* NEW❗: New metric in Developer UI + * Actual Lateral Acceleration (Roll Compensated) + +sunnypilot - Version Latest (2022-09-12) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.11-02:47:00:GMT - 0.8.17 +* FIXED: Honda Nidec models not gaining speed when longitudinal engaged + +sunnypilot - Version Latest (2022-09-11) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.11-02:47:00:GMT - 0.8.17 +* NEW❗: Hyundai Enhanced SCC now forwards FCW and AEB signals and commands from radar to car +* RE-ENABLED: MADS Status Icon toggle + +sunnypilot - Version Latest (2022-09-10) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.11-02:47:00:GMT - 0.8.17 +* NEW❗: RAM improvement implementation thanks to realfast! +* DISABLED: Chrysler/Jeep/Ram with Custom Stock Longitudinal Control +* DISABLED: Volkswagen MQB & PQ with Custom Stock Longitudinal Control + +sunnypilot - Version Latest (2022-09-09) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.09-07:35:00:GMT - 0.8.17 +* NEW❗: MADS now supporting General Motors (GM) +* ADDED: Custom Stock Longitudinal Control - Volkswagen + * MQB & PQ +* ADDED: Reverse ACC Change + * ACC +/-: Short=5, Long=1 +* ADDED: Custom Stock Longitudinal Control + * Hyundai/Kia/Genesis + * Honda Bosch +* ADDED: Hyundai: 2015-16 Genesis resume from standstill fix (commaai:openpilot#25579) - pending PR +* Vision Turn Speed Control re-enabled +* Disable Onroad Uploads toggle re-enabled + +sunnypilot - Version Latest (2022-09-08) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.08-04:05:00:GMT - 0.8.17 +* NEW❗: Block lane change initiation while brake is pressed + +sunnypilot - Version Latest (2022-09-07) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.08-04:05:00:GMT - 0.8.17 +* NEW❗: Display End-to-end longitudinal 🌮 on screen + * NEW❗: Hold DISTANCE button on the steering wheel for 1 second to switch between E2E Long and ACC mode + * Enable toggle on the driving screen to switch between modes with End-to-end longitudinal + * Only applicable to cars with openpilot longitudinal control +* NEW❗: Block lane change initiation while brake is pressed +* REMOVED: Dynamic Lane Profile - upstream laneless model is now on by default +* REMOVED: hyundai: consistent start from stop (commaai:openpilot#25672) - pending PR + +sunnypilot - Version Latest (2022-09-06) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.06 - 0.8.17 +* NEW❗: Display useful metrics above the chevron that tracks the lead car + * Under "SP - Visuals" menu + * Only applicable to cars with openpilot longitudinal control +* ADDED: hyundai: consistent start from stop (commaai:openpilot#25672) - pending PR +* FIXED: Vienna speed limit interface now scales properly with the outer box +* REMOVED: Hyundai long improvements (commaai:openpilot#25604) - closed PR + +sunnypilot - Version Latest (2022-09-05) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.03 - 0.8.17 +* NEW❗: Speed Limit Control (SLC) interface integrated with upstream +* NEW❗: Speed limit from active navigation is now prioritized for Speed Limit Control +* NEW❗: MUTCD (U.S.) or Vienna (E.U.) speed limit interfaces can now be selected under "SP - Controls" + +sunnypilot - Version Latest (2022-09-04) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.03 - 0.8.17 +* FIXED: Gap Adjust Cruise status now displays properly on screen +* FIXED: mapd - missing index in list caused mapd to crash +* REMOVED: Temporary removed Vision Turn Speed Control + +sunnypilot - Version Latest (2022-09-03) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.03 - 0.8.17 +* ADDED: New border colors for different operation engagements +* ADDED: UI: Show barrier when car detected in blind spot + * Only applicable to cars that have BSM detection with openpilot +* FIXED: Cruise Cancel button no longer display prompt if cruise not engaged +* TWEAKED: Update changelogs on startup in Settings -> Software -> Version +* REMOVED: Upload Raw Logs and Full Resolution Videos toggles + +sunnypilot - Version Latest (2022-08-31) +======================== +* UPDATED: Synced with commaai's master branch - 2022.08.31 - 0.8.17 +* ADDED: New border colors for different operation engagements +* ADDED: UI: Show barrier when car detected in blind spot + * Only applicable to cars that have BSM detection with openpilot +* FIXED: Cruise Cancel button no longer display prompt if cruise not engaged +* REMOVED: Upload Raw Logs and Full Resolution Videos toggles + +sunnypilot - Version 0.8.16 (2022-07-16) +======================== +* Sync with commaai's master branches +* NEW❗: Add toggle to pause lateral actuation below 30 MPH / 50 KM/H +* IMPROVED: Better controls mismatch handling +* IMPROVED: Less frequent Low Memory alert +* IMPROVED: Only allow lateral control when in forward gears +* IMPROVED: Better alerts handling on gear changes + +sunnypilot - Version 0.8.14-1.3 (2022-06-29) +======================== +* Hyundai/Kia/Genesis + * NEW❗: MADS: Add GAP/Distance button on the steering wheel to engage/disengage + * To engage/disengage MADS: Hold the button for 0.5 second +* NEW❗: Dynamic Lane Profile: Add toggle to enable "Laneless for Curves in Auto Lane" +* HOTFIX🛠: Improve Torque lateral control and reduce ping pong for some Toyota cars + * Torque control: higher low speed gains and better steering angle deadzone logic +* Developer UI: Remove Distance Traveled, replace with Memory Usage % + * This may have a potential to fix the Low Memory alert that may appear + +sunnypilot - Version 0.8.14-1 (2022-06-27) +======================== +* HOTFIX🛠: Honda, Toyota, Volkswagen now initialized correctly with Torque Lateral Live Tune + +sunnypilot - Version 0.8.14-1 (2022-06-27) +======================== +* NEW❗: Added toggle to enable updates for sunnypilot +* HOTFIX🛠: Volkswagen car list now displays properly in Force Car Recognition menu +* REVERTED: Honda - temporary removes CRUISE (MAIN) for MADS engagement + * LKAS button continues to be used for MADS engagement/disengagement + +sunnypilot - Version 0.8.14-1 (2022-06-26) +======================== +Visit https://bit.ly/sunnyreadme for more details +* sunnypilot 0.8.14 release - based on openpilot 0.8.14 devel +* "0.8.14-prod-c3" branch only supports comma three + * If you have a comma two, EON, or other devices than a comma three, visit sunnyhaibin's discord server for more details: https://discord.gg/wRW3meAgtx +* Mono-branch support + * Honda/Acura + * Hyundai/Kia/Genesis + * Toyota/Lexus + * Volkswagen MQB +* Modified Assistive Driving Safety (MADS) Mode + * NEW❗: CRUISE (MAIN) now engages MADS for all supported car makes + * NEW❗: Added toggle to disable disengaging Automatic Lane Centering (ALC) on the brake pedal +* Dynamic Lane Profile (DLP) +* NEW❗: Gap Adjust Cruise (GAC) + * openpilot longitudinal cars can now adjust between the lead car's following distance gap via 3 modes: + * Steering Wheel (SW) | User Interface (UI) | Steering Wheel + User Interface (SW+UI) +* NEW❗: Custom Camera & Path Offsets +* NEW❗: Torque Lateral Control from openpilot 0.8.15 master (as of 2022-06-15) +* NEW❗: Torque Lateral Control Live Tune Menu +* NEW❗: Speed Limit Sign from openpilot 0.8.15 master (as of 2022-06-22) +* NEW❗: Mapbox Speed Limit data will now be utilized in Speed Limit Control (SLC) + * Speed limit data will be utilized in the following availability: + * Mapbox (active navigation) -> OpenStreetMap -> Car Interface (Toyota's TSR) +* Custom Stock Longitudinal Control + * NEW❗: Volkswagen MQB + * Honda + * Hyundai/Kia/Genesis +* NEW❗: Mapbox navigation support for non-Prime users + * Visit sunnyhaibin's discord server for more details: https://discord.gg/wRW3meAgtx +* Hyundai/Kia/Genesis + * NEW❗: Enhanced SCC (ESCC) Support + * Requires hardware modification. Visit sunnyhaibin's discord server for more details: https://discord.gg/wRW3meAgtx + * NEW❗: Smart MDPS (SMDPS) Support - Auto-detection + * Requires hardware modification and custom firmware for the SMDPS. Visit sunnyhaibin's discord server for more details: https://discord.gg/wRW3meAgtx +* Toyota/Lexus + * NEW❗: Added toggle to enforce stock longitudinal control + +sunnypilot - Version 0.8.12-4 +======================== +* NEW❗: Custom Stock Longitudinal Control by setting the target speed via openpilot's "MAX" speed thanks to multikyd! + * Speed Limit Control + * Vision-based Turn Control + * Map-based Turn Control +* NEW❗: HDA status integration with Custom Stock Longitudinal Control on applicable HKG cars only +* NEW❗: Roll Compensation and SteerRatio fix from comma's 0.8.13 +* NEW❗: Dev UI to display different metrics on screen + * Click on the "MAX" box on the top left of the openpilot display to toggle different metrics display + * Lead car relative distance; Lead car relative speed; Actual steering degree; Desired steering degree; Engine RPM; Longitudinal acceleration; Lead car actual speed; EPS torque; Current altitude; Compass direction +* NEW❗: Stand Still Timer to display time spent at a stop with M.A.D.S engaged (i.e., stop lights, stop signs, traffic congestions) +* NEW❗: Current car speed text turns red when the car is braking +* NEW❗: Export GPS tracks into GPX files and upload to OSM thanks to eFini! +* NEW❗: Enable ACC and M.A.D.S with a single press of the RES+/SET- button +* NEW❗: ACC +/-: Short=5, Long=1 + * Change the ACC +/- buttons behavior with cruise speed change in openpilot + * Disabled (Stock): Short=1, Long=5 + * Enabled: Short=5, Long=1 +* NEW❗: Speed Limit Value Offset (not %)* + * Set speed limit higher or lower than actual speed limit for a more personalized drive. + * *To use this feature, turn off "Enable Speed Limit % Offset"* +* NEW❗: Dedicated icon to show the status of M.A.D.S. +* NEW❗: No Offroad Fix for non-official devices that cannot shut down after the car is turned off +* NEW❗: Stop N' Go Resume Alternative + * Offer alternative behavior to auto resume when stopped behind a lead car using stock SCC/ACC. This feature removes the repeating prompt chime when stopped and/or allows some cars to use auto resume (i.e., Genesis) +* IMPROVED: Show the lead car icon in the car's dashboard when a lead car is detected by openpilot's camera vision +* FIXED: MADS button unintentionally set MAX when using stock longitudinal control thanks to Spektor56! + +sunnypilot - Version 0.8.12-3 +======================== +* NEW❗: Bypass "System Malfunction" alert toggle + * Prevent openpilot from returning the "System Malfunction" alert that hinders the ability use openpilot +* FIXED: Hyundai/Kia/Genesis Brake Hold Active now outputs the correct events on screen with M.A.D.S. engaged + +sunnypilot - Version 0.8.12-2 +======================== +* NEW❗: Disable M.A.D.S. toggle to disable the beloved M.A.D.S. feature + * Enable Stock openpilot engagement/disengagement +* ADJUST: Initialize Driving Screen Off Brightness at 50% + +sunnypilot - Version 0.8.12-1 +======================== +* sunnypilot 0.8.12 release - based on openpilot 0.8.12 devel +* Dedicated Hyundai/Kia/Genesis branch support +* NEW❗: OpenStreetMap integration thanks to the Move Fast team! + * NEW❗: Vision-based Turn Control + * NEW❗: Map-Data-based Turn Control + * NEW❗: Speed Limit Control w/ optional Speed Limit Offset + * NEW❗: OpenStreetMap integration debug UI + * Only available to openpilot longitudinal enabled cars +* NEW❗: Hands on Wheel Monitoring according to EU r079r4e regulation +* NEW❗: Disable Onroad Uploads for data-limited Wi-Fi hotspots when using OpenStreetMap related features +* NEW❗: Fast Boot (Prebuilt) +* NEW❗: Auto Lane Change Timer +* NEW❗: Screen Brightness Control (Global) +* NEW❗: Driving Screen Off Timer +* NEW❗: Driving Screen Off Brightness (%) +* NEW❗: Max Time Offroad +* Improved user feedback with M.A.D.S. operations thanks to Spektor56! + * Lane Path + * Green🟢 (Laneful), Red🔴 (Laneless): M.A.D.S. engaged + * White⚪: M.A.D.S. suspended or disengaged + * Black⚫: M.A.D.S. engaged, steering is being manually override by user + * Screen border now only illuminates Green when SCC/ACC is engaged + +sunnypilot - Version 0.8.10-1 (Unreleased) +======================== +* sunnypilot 0.8.10 release - based on openpilot 0.8.10 `devel` +* Add Toyota cars to Force Car Recognition + +sunnypilot - Version 0.8.9-4 +======================== +* Hyundai: Fix Ioniq Hybrid signals + +sunnypilot - Version 0.8.9-3 +======================== +* Update home screen brand and version structure + +sunnypilot - Version 0.8.9-2 +======================== +* Added additional Sonata Hybrid Firmware Versions +* Features + * Modified Assistive Driving Safety (MADS) Mode + * Dynamic Lane Profile (DLP) + * Quiet Drive 🤫 + * Force Car Recognition (FCR) + * PID Controller: add kd into the stock PID controller + +sunnypilot - Version 0.8.9-1 +======================== +* First changelog! +* Features + * Modified Assistive Driving Safety (MADS) Mode + * Dynamic Lane Profile (DLP) + * Quiet Drive 🤫 + * Force Car Recognition (FCR) + * PID Controller: add kd into the stock PID controller diff --git a/common/swaglog.cc b/common/swaglog.cc index 62a405a2b6..d73f0c1a59 100644 --- a/common/swaglog.cc +++ b/common/swaglog.cc @@ -15,6 +15,8 @@ #include "common/version.h" #include "system/hardware/hw.h" +#include "sunnypilot/common/version.h" + class SwaglogState { public: SwaglogState() { @@ -56,7 +58,7 @@ public: if (char* daemon_name = getenv("MANAGER_DAEMON")) { ctx_j["daemon"] = daemon_name; } - ctx_j["version"] = COMMA_VERSION; + ctx_j["version"] = SUNNYPILOT_VERSION; ctx_j["dirty"] = !getenv("CLEAN"); ctx_j["device"] = Hardware::get_name(); } diff --git a/common/tests/test_markdown.py b/common/tests/test_markdown.py index d3c7e02c69..7e04bf2920 100644 --- a/common/tests/test_markdown.py +++ b/common/tests/test_markdown.py @@ -6,7 +6,7 @@ from openpilot.common.markdown import parse_markdown class TestMarkdown: def test_all_release_notes(self): - with open(os.path.join(BASEDIR, "RELEASES.md")) as f: + with open(os.path.join(BASEDIR, "CHANGELOG.md")) as f: release_notes = f.read().split("\n\n") assert len(release_notes) > 10 diff --git a/common/tests/test_swaglog.cc b/common/tests/test_swaglog.cc index 09bc4c3795..c97f907c24 100644 --- a/common/tests/test_swaglog.cc +++ b/common/tests/test_swaglog.cc @@ -9,6 +9,8 @@ #include "system/hardware/hw.h" #include "third_party/json11/json11.hpp" +#include "sunnypilot/common/version.h" + std::string daemon_name = "testy"; std::string dongle_id = "test_dongle_id"; int LINE_NO = 0; @@ -53,7 +55,7 @@ void recv_log(int thread_cnt, int thread_msg_cnt) { REQUIRE(ctx["dongle_id"].string_value() == dongle_id); REQUIRE(ctx["dirty"].bool_value() == true); - REQUIRE(ctx["version"].string_value() == COMMA_VERSION); + REQUIRE(ctx["version"].string_value() == SUNNYPILOT_VERSION); std::string device = Hardware::get_name(); REQUIRE(ctx["device"].string_value() == device); diff --git a/release/build_release.sh b/release/build_release.sh index 5ada9c5ecc..9bd4c81f6d 100755 --- a/release/build_release.sh +++ b/release/build_release.sh @@ -39,7 +39,7 @@ cd $BUILD_DIR rm -f panda/board/obj/panda.bin.signed rm -f panda/board/obj/panda_h7.bin.signed -VERSION=$(cat common/version.h | awk -F[\"-] '{print $2}') +VERSION=$(cat sunnypilot/common/version.h | awk -F[\"-] '{print $2}') echo "[-] committing version $VERSION T=$SECONDS" git add -f . git commit -a -m "openpilot v$VERSION release" diff --git a/release/build_stripped.sh b/release/build_stripped.sh index 88c0101a45..df5b2a3dd6 100755 --- a/release/build_stripped.sh +++ b/release/build_stripped.sh @@ -49,7 +49,7 @@ rm -f panda/board/obj/panda.bin.signed GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD) GIT_COMMIT_DATE=$(git --git-dir=$SOURCE_DIR/.git show --no-patch --format='%ct %ci' HEAD) DATETIME=$(date '+%Y-%m-%dT%H:%M:%S') -VERSION=$(cat $SOURCE_DIR/common/version.h | awk -F\" '{print $2}') +VERSION=$(cat $SOURCE_DIR/sunnypilot/common/version.h | awk -F\" '{print $2}') echo -n "$GIT_HASH" > git_src_commit echo -n "$GIT_COMMIT_DATE" > git_src_commit_date diff --git a/release/ci/publish.sh b/release/ci/publish.sh index 4aecbacc4a..315ae22bfe 100755 --- a/release/ci/publish.sh +++ b/release/ci/publish.sh @@ -30,7 +30,7 @@ if [ -z "$GIT_ORIGIN" ]; then fi # "Tagging" -echo "#define COMMA_VERSION \"$VERSION\"" > ${OUTPUT_DIR}/common/version.h +echo "#define SUNNYPILOT_VERSION \"$VERSION\"" > ${OUTPUT_DIR}/sunnypilot/common/version.h ## set git identity #source $DIR/identity.sh @@ -55,7 +55,7 @@ git add -f . # include source commit hash and build date in commit GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD) DATETIME=$(date '+%Y-%m-%dT%H:%M:%S') -SP_VERSION=$(awk -F\" '{print $2}' $SOURCE_DIR/common/version.h) +SP_VERSION=$(awk -F\" '{print $2}' $SOURCE_DIR/sunnypilot/common/version.h) # Commit with detailed message git commit -a -m "sunnypilot v$VERSION diff --git a/selfdrive/ui/tests/cycle_offroad_alerts.py b/selfdrive/ui/tests/cycle_offroad_alerts.py index e468d88e0e..04fcd4017f 100755 --- a/selfdrive/ui/tests/cycle_offroad_alerts.py +++ b/selfdrive/ui/tests/cycle_offroad_alerts.py @@ -18,7 +18,7 @@ if __name__ == "__main__": while True: print("setting alert update") params.put_bool("UpdateAvailable", True) - r = open(os.path.join(BASEDIR, "RELEASES.md")).read() + r = open(os.path.join(BASEDIR, "CHANGELOG.md")).read() r = r[:r.find('\n\n')] # Slice latest release notes params.put("UpdaterNewReleaseNotes", r + "\n") diff --git a/selfdrive/ui/tests/test_ui/run.py b/selfdrive/ui/tests/test_ui/run.py index 68d1f0c185..d254ca8733 100755 --- a/selfdrive/ui/tests/test_ui/run.py +++ b/selfdrive/ui/tests/test_ui/run.py @@ -188,7 +188,7 @@ def setup_offroad_alert(click, pm: PubMaster, scroll=None): def setup_update_available(click, pm: PubMaster, scroll=None): Params().put_bool("UpdateAvailable", True) - release_notes_path = os.path.join(BASEDIR, "RELEASES.md") + release_notes_path = os.path.join(BASEDIR, "CHANGELOG.md") with open(release_notes_path) as file: release_notes = file.read().split('\n\n', 1)[0] Params().put("UpdaterNewReleaseNotes", release_notes + "\n") diff --git a/sunnypilot/common/version.h b/sunnypilot/common/version.h new file mode 100644 index 0000000000..5086f103f8 --- /dev/null +++ b/sunnypilot/common/version.h @@ -0,0 +1 @@ +#define SUNNYPILOT_VERSION "2025.001.000" diff --git a/system/loggerd/logger.cc b/system/loggerd/logger.cc index 0ebe323939..e8aeb96d02 100644 --- a/system/loggerd/logger.cc +++ b/system/loggerd/logger.cc @@ -11,6 +11,8 @@ #include "common/swaglog.h" #include "common/version.h" +#include "sunnypilot/common/version.h" + // ***** log metadata ***** kj::Array logger_build_init_data() { uint64_t wall_time = nanos_since_epoch(); @@ -19,7 +21,7 @@ kj::Array logger_build_init_data() { auto init = msg.initEvent().initInitData(); init.setWallTimeNanos(wall_time); - init.setVersion(COMMA_VERSION); + init.setVersion(SUNNYPILOT_VERSION); init.setDirty(!getenv("CLEAN")); init.setDeviceType(Hardware::get_device_type()); diff --git a/system/updated/updated.py b/system/updated/updated.py index 7ab9e070dc..181e410fa6 100755 --- a/system/updated/updated.py +++ b/system/updated/updated.py @@ -82,7 +82,7 @@ def set_consistent_flag(consistent: bool) -> None: def parse_release_notes(basedir: str) -> bytes: try: - with open(os.path.join(basedir, "RELEASES.md"), "rb") as f: + with open(os.path.join(basedir, "CHANGELOG.md"), "rb") as f: r = f.read().split(b'\n\n', 1)[0] # Slice latest release notes try: return bytes(parse_markdown(r.decode("utf-8")), encoding="utf-8") @@ -294,7 +294,7 @@ class Updater: try: branch = self.get_branch(basedir) commit = self.get_commit_hash(basedir)[:7] - with open(os.path.join(basedir, "common", "version.h")) as f: + with open(os.path.join(basedir, "sunnypilot", "common", "version.h")) as f: version = f.read().split('"')[1] commit_unix_ts = run(["git", "show", "-s", "--format=%ct", "HEAD"], basedir).rstrip() diff --git a/system/version.py b/system/version.py index 6ff290e032..268aaf11ea 100755 --- a/system/version.py +++ b/system/version.py @@ -33,13 +33,13 @@ terms_version: str = "2" def get_version(path: str = BASEDIR) -> str: - with open(os.path.join(path, "common", "version.h")) as _versionf: + with open(os.path.join(path, "sunnypilot", "common", "version.h")) as _versionf: version = _versionf.read().split('"')[1] return version def get_release_notes(path: str = BASEDIR) -> str: - with open(os.path.join(path, "RELEASES.md")) as f: + with open(os.path.join(path, "CHANGELOG.md")) as f: return f.read().split('\n\n', 1)[0] diff --git a/tools/replay/api.cc b/tools/replay/api.cc index 85e4e52b28..49923913de 100644 --- a/tools/replay/api.cc +++ b/tools/replay/api.cc @@ -14,6 +14,8 @@ #include "common/version.h" #include "system/hardware/hw.h" +#include "sunnypilot/common/version.h" + namespace CommaApi2 { // Base64 URL-safe character set (uses '-' and '_' instead of '+' and '/') @@ -141,7 +143,7 @@ std::string httpGet(const std::string &url, long *response_code) { // Handle headers struct curl_slist *headers = nullptr; - headers = curl_slist_append(headers, "User-Agent: openpilot-" COMMA_VERSION); + headers = curl_slist_append(headers, "User-Agent: openpilot-" SUNNYPILOT_VERSION); if (!token.empty()) { headers = curl_slist_append(headers, ("Authorization: JWT " + token).c_str()); } diff --git a/tools/replay/consoleui.cc b/tools/replay/consoleui.cc index 185c7d6c36..bdbdb3e841 100644 --- a/tools/replay/consoleui.cc +++ b/tools/replay/consoleui.cc @@ -10,6 +10,8 @@ #include "common/util.h" #include "common/version.h" +#include "sunnypilot/common/version.h" + namespace { const int BORDER_SIZE = 3; @@ -119,7 +121,7 @@ void ConsoleUI::initWindows() { // set the title bar wbkgd(w[Win::Title], A_REVERSE); - mvwprintw(w[Win::Title], 0, 3, "sunnypilot replay %s", COMMA_VERSION); + mvwprintw(w[Win::Title], 0, 3, "sunnypilot replay %s", SUNNYPILOT_VERSION); // show windows on the real screen refresh(); From 641af6d7e7f7fe882b1bee5ce5b52133cf76ee9b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 21 Oct 2025 17:55:27 -0400 Subject: [PATCH 43/72] changelog: more new contributors! (#1413) --- CHANGELOG.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 687484affe..00be668571 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -123,16 +123,24 @@ sunnypilot Version 2025.001.000 (2025-10-25) * Lateral Planner: Laneful Mode * Lateral: Custom Camera and Path Offsets * Toyota: Door Controls -* New Contributors - * @discountchubbs made their first contribution in "ui: Error log button to Developer panel (#627)" - * @First made their first contribution in "NNLC: bump max similarity for higher accuracy (#704)" - * @nayan made their first contribution in "UI: Update AbstractControlSP_SELECTOR and OptionControlSP (#800)" - * @wtogamiwtogami made their first contribution in "Add TOYOTA_RAV4_PRIME NNLC tuning gen 1 (#850)" +* New Contributors (sunnypilot/sunnypilot) + * @royjr made their first contribution in "NNLC: bump max similarity for higher accuracy (#704)" + * @nayan8teen made their first contribution in "UI: Update AbstractControlSP_SELECTOR and OptionControlSP (#800)" + * @wtogami made their first contribution in "TOYOTA_RAV4_PRIME NNLC tuning gen 1 (#850)" * @dparring made their first contribution in "FCA: Ram 1500 improvements (#797)" * @Kirito3481 made their first contribution in "Update ko-kr translation (#1167)" - * @michael-was-taken made their first contributio@dzid26 in "Reorder README tables: show -new branches first (#1191)" - * @dzid26 made their first contribution in "docs: clarify pedal press (#1289)" + * @michael-was-taken made their first contribution in "Reorder README tables: show -new branches first (#1191)" + * @dzid26 made their first contribution in "params: Fix loading delay on startup (#1297)" * @HazZelnutz made their first contribution in "Visuals: Turn signals on screen when blinker is used (#1291)" +* New Contributors (sunnypilot/opendbc) + * @chrispypatt made their first contribution in "Toyota: SecOC Longitudinal Control (sunnypilot/opendbc#93)" + * @Discountchubbs made their first contribution in "Hyundai: EPS FW For 2022 KIA_NIRO_EV SCC (sunnypilot/opendbc#118)" + * @lukasloetkolben made their first contribution in "Tesla: enableBsm is always true (sunnypilot/opendbc#163)" + * @roenthomas made their first contribution in "Honda: int flag for modified EPS configs (sunnypilot/opendbc#254)" + * @AmyJeanes made their first contribution in "Tesla: Fix stock LKAS being blocked when MADS is enabled (sunnypilot/opendbc#286)" + * @mvl-boston made their first contribution in "Honda: Update Clarity brake to renamed DBC message name (sunnypilot/opendbc#282)" + * @dzid26 made their first contribution in "Tesla: Parse speed limit from CAN (sunnypilot/opendbc#308)" + * @firestar5683 made their first contribution in "GM: Non-ACC platforms with steering only support (sunnypilot/opendbc#229)" ************************ * Synced with commaai's openpilot (v0.10.1) * master commit c9dbf97649a27117be6d5955a49e2d4253337288 (September 12, 2025) From 657ff0f8ecd6de0c3a1b81657aa56b5fab907e2c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 22 Oct 2025 00:09:10 -0400 Subject: [PATCH 44/72] ui: refine ICBM description handling and availability logic (#1414) * ui: refine ICBM description handling and availability logic * car -> platform * retain --- .../qt/offroad/settings/longitudinal_panel.cc | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc index 2cc59ea333..9c51ff8c67 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc @@ -38,7 +38,7 @@ LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) { intelligentCruiseButtonManagement = new ParamControlSP( "IntelligentCruiseButtonManagement", tr("Intelligent Cruise Button Management (ICBM) (Alpha)"), - tr("When enabled, sunnypilot will attempt to manage the built-in cruise control buttons by emulating button presses for limited longitudinal control."), + "", "", this ); @@ -104,6 +104,8 @@ void LongitudinalPanel::hideEvent(QHideEvent *event) { } void LongitudinalPanel::refresh(bool _offroad) { + const QString icbm_description = tr("When enabled, sunnypilot will attempt to manage the built-in cruise control buttons by emulating button presses for limited longitudinal control."); + auto cp_bytes = params.get("CarParamsPersistent"); auto cp_sp_bytes = params.get("CarParamsSPPersistent"); if (!cp_bytes.empty() && !cp_sp_bytes.empty()) { @@ -120,9 +122,24 @@ void LongitudinalPanel::refresh(bool _offroad) { if (CP_SP.getIntelligentCruiseButtonManagementAvailable() && !has_longitudinal_control) { intelligentCruiseButtonManagement->setEnabled(offroad); + intelligentCruiseButtonManagement->setDescription(icbm_description); } else { params.remove("IntelligentCruiseButtonManagement"); intelligentCruiseButtonManagement->setEnabled(false); + + const QString icbm_unavaialble = tr("Intelligent Cruise Button Management is currently unavailable on this platform."); + + QString long_desc = icbm_unavaialble; + if (has_longitudinal_control) { + if (CP.getAlphaLongitudinalAvailable()) { + long_desc = icbm_unavaialble + " " + tr("Disable the openpilot Longitudinal Control (alpha) toggle to allow Intelligent Cruise Button Management."); + } else { + long_desc = icbm_unavaialble + " " + tr("openpilot Longitudinal Control is the default longitudinal control for this platform."); + } + } + + intelligentCruiseButtonManagement->setDescription("" + long_desc + "

" + icbm_description); + intelligentCruiseButtonManagement->showDescription(); } if (has_longitudinal_control || has_icbm) { @@ -151,6 +168,7 @@ void LongitudinalPanel::refresh(bool _offroad) { has_longitudinal_control = false; is_pcm_cruise = false; has_icbm = false; + intelligentCruiseButtonManagement->setDescription("" + tr("Start the vehicle to check vehicle compatibility.") + "
" + icbm_description); } QString accEnabledDescription = tr("Enable custom Short & Long press increments for cruise speed increase/decrease."); From 7097e69aa320e269b9c59d70756da23588a8aeef Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 22 Oct 2025 11:17:02 -0400 Subject: [PATCH 45/72] Speed Limit Assist: generalize availability helper (#1416) * init infra to disable sla in certain conditions * a bit more * in another PR * in another PR * since when? * start here --- .../controls/lib/longitudinal_planner.py | 4 ++-- selfdrive/controls/plannerd.py | 8 +++++-- .../test/longitudinal_maneuvers/plant.py | 4 +++- .../speed_limit/speed_limit_settings.cc | 24 ++++++++++--------- sunnypilot/selfdrive/car/interfaces.py | 5 ++-- .../controls/lib/longitudinal_planner.py | 4 ++-- .../controls/lib/speed_limit/helpers.py | 17 +++++++++++++ .../lib/speed_limit/speed_limit_assist.py | 9 ++++--- .../tests/test_speed_limit_assist.py | 2 +- 9 files changed, 52 insertions(+), 25 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 53eca38356..0501f669f1 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -51,12 +51,12 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP): class LongitudinalPlanner(LongitudinalPlannerSP): - def __init__(self, CP, init_v=0.0, init_a=0.0, dt=DT_MDL): + def __init__(self, CP, CP_SP, init_v=0.0, init_a=0.0, dt=DT_MDL): self.CP = CP self.mpc = LongitudinalMpc(dt=dt) # TODO remove mpc modes when TR released self.mpc.mode = 'acc' - LongitudinalPlannerSP.__init__(self, self.CP, self.mpc) + LongitudinalPlannerSP.__init__(self, self.CP, CP_SP, self.mpc) self.fcw = False self.dt = dt self.allow_throttle = True diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 4565a9c047..f7d3370f90 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from cereal import car +from cereal import car, custom from openpilot.common.gps import get_gps_location_service from openpilot.common.params import Params from openpilot.common.realtime import Priority, config_realtime_process @@ -17,10 +17,14 @@ def main(): CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) cloudlog.info("plannerd got CarParams: %s", CP.brand) + cloudlog.info("plannerd is waiting for CarParamsSP") + CP_SP = messaging.log_from_bytes(params.get("CarParamsSP", block=True), custom.CarParamsSP) + cloudlog.info("plannerd got CarParamsSP") + gps_location_service = get_gps_location_service(params) ldw = LaneDepartureWarning() - longitudinal_planner = LongitudinalPlanner(CP) + longitudinal_planner = LongitudinalPlanner(CP, CP_SP) pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP']) sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState', 'liveMapDataSP', 'carStateSP', gps_location_service], diff --git a/selfdrive/test/longitudinal_maneuvers/plant.py b/selfdrive/test/longitudinal_maneuvers/plant.py index e466472432..e82ef2f65e 100755 --- a/selfdrive/test/longitudinal_maneuvers/plant.py +++ b/selfdrive/test/longitudinal_maneuvers/plant.py @@ -51,7 +51,9 @@ class Plant: from opendbc.car.honda.values import CAR from opendbc.car.honda.interface import CarInterface - self.planner = LongitudinalPlanner(CarInterface.get_non_essential_params(CAR.HONDA_CIVIC), init_v=self.speed) + CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC) + CP_SP = CarInterface.get_non_essential_params_sp(CP, CAR.HONDA_CIVIC) + self.planner = LongitudinalPlanner(CP, CP_SP, init_v=self.speed) @property def current_time(self): diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc index 1696c7f63a..50d9ccbb41 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc @@ -110,8 +110,7 @@ void SpeedLimitSettings::refresh() { SpeedLimitOffsetType offset_type_param = static_cast(std::atoi(params.get("SpeedLimitOffsetType").c_str())); QString offsetLabel = QString::fromStdString(params.get("SpeedLimitValueOffset")); - bool has_longitudinal_control; - bool has_icbm; + bool sla_available; auto cp_bytes = params.get("CarParamsPersistent"); auto cp_sp_bytes = params.get("CarParamsSPPersistent"); if (!cp_bytes.empty() && !cp_sp_bytes.empty()) { @@ -122,17 +121,20 @@ void SpeedLimitSettings::refresh() { cereal::CarParams::Reader CP = cmsg.getRoot(); cereal::CarParamsSP::Reader CP_SP = cmsg_sp.getRoot(); - has_longitudinal_control = hasLongitudinalControl(CP); - has_icbm = hasIntelligentCruiseButtonManagement(CP_SP); + bool has_longitudinal_control = hasLongitudinalControl(CP); + bool has_icbm = hasIntelligentCruiseButtonManagement(CP_SP); - if (!has_longitudinal_control && !has_icbm) { - if (speed_limit_mode_param == SpeedLimitMode::ASSIST) { - params.put("SpeedLimitMode", std::to_string(static_cast(SpeedLimitMode::WARNING))); - } + /* + * Speed Limit Assist is available when: + * - has_longitudinal_control or has_icbm + */ + sla_available = has_longitudinal_control || has_icbm; + + if (!sla_available && speed_limit_mode_param == SpeedLimitMode::ASSIST) { + params.put("SpeedLimitMode", std::to_string(static_cast(SpeedLimitMode::WARNING))); } } else { - has_longitudinal_control = false; - has_icbm = false; + sla_available = false; } speed_limit_mode_settings->setDescription(modeDescription(speed_limit_mode_param)); @@ -152,7 +154,7 @@ void SpeedLimitSettings::refresh() { speed_limit_offset->showDescription(); } - if (has_longitudinal_control || has_icbm) { + if (sla_available) { speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues(getSpeedLimitModeValues())); } else { speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues( diff --git a/sunnypilot/selfdrive/car/interfaces.py b/sunnypilot/selfdrive/car/interfaces.py index 6a868ab85b..7d63d3c5ba 100644 --- a/sunnypilot/selfdrive/car/interfaces.py +++ b/sunnypilot/selfdrive/car/interfaces.py @@ -11,7 +11,7 @@ from opendbc.car.interfaces import CarInterfaceBase from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog from openpilot.sunnypilot.selfdrive.controls.lib.nnlc.helpers import get_nn_model_path -from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode as SpeedLimitMode +from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.helpers import set_speed_limit_assist_availability import openpilot.system.sentry as sentry @@ -87,8 +87,7 @@ def _cleanup_unsupported_params(CP: structs.CarParams, CP_SP: structs.CarParamsS params.remove("SmartCruiseControlVision") params.remove("SmartCruiseControlMap") - if params.get("SpeedLimitMode", return_default=True) == SpeedLimitMode.assist: - params.put("SpeedLimitMode", int(SpeedLimitMode.warning)) + set_speed_limit_assist_availability(CP, CP_SP, params) def setup_interfaces(CI: CarInterfaceBase, params: Params = None) -> None: diff --git a/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py b/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py index 4c2443f253..68b50c3e19 100644 --- a/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py +++ b/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py @@ -22,13 +22,13 @@ LongitudinalPlanSource = custom.LongitudinalPlanSP.LongitudinalPlanSource class LongitudinalPlannerSP: - def __init__(self, CP: structs.CarParams, mpc): + def __init__(self, CP: structs.CarParams, CP_SP: structs.CarParamsSP, mpc): self.events_sp = EventsSP() self.resolver = SpeedLimitResolver() self.dec = DynamicExperimentalController(CP, mpc) self.scc = SmartCruiseControl() self.resolver = SpeedLimitResolver() - self.sla = SpeedLimitAssist(CP) + self.sla = SpeedLimitAssist(CP, CP_SP) self.generation = int(model_bundle.generation) if (model_bundle := get_active_bundle()) else None self.source = LongitudinalPlanSource.cruise self.e2e_alerts_helper = E2EAlertsHelper() diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py b/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py index 40e8f653ff..c209e98dce 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py @@ -5,7 +5,10 @@ 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 custom, car from openpilot.common.constants import CV +from openpilot.common.params import Params +from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode as SpeedLimitMode def compare_cluster_target(v_cruise_cluster: float, target_set_speed: float, is_metric: bool) -> tuple[bool, bool]: @@ -17,3 +20,17 @@ def compare_cluster_target(v_cruise_cluster: float, target_set_speed: float, is_ req_minus = v_cruise_cluster_conv > target_set_speed_conv return req_plus, req_minus + + +def set_speed_limit_assist_availability(CP: car.CarParams, CP_SP: custom.CarParamsSP, params: Params = None) -> None: + if params is None: + params = Params() + + allowed = True + + if not CP.openpilotLongitudinalControl and CP_SP.pcmCruiseSpeed: + allowed = False + + if not allowed: + if params.get("SpeedLimitMode", return_default=True) == SpeedLimitMode.assist: + params.put("SpeedLimitMode", int(SpeedLimitMode.warning)) diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py index b948452059..fcb5a98a2a 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py @@ -15,7 +15,7 @@ from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit import PCM_LONG_REQUIRED_MAX_SET_SPEED, CONFIRM_SPEED_THRESHOLD from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode -from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.helpers import compare_cluster_target +from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.helpers import compare_cluster_target, set_speed_limit_assist_availability from openpilot.selfdrive.modeld.constants import ModelConstants ButtonType = car.CarState.ButtonEvent.Type @@ -38,7 +38,7 @@ LIMIT_MIN_ACC = -1.5 # m/s^2 Maximum deceleration allowed for limit controllers LIMIT_MAX_ACC = 1.0 # m/s^2 Maximum acceleration allowed for limit controllers to provide while active. LIMIT_MIN_SPEED = 8.33 # m/s, Minimum speed limit to provide as solution on limit controllers. LIMIT_SPEED_OFFSET_TH = -1. # m/s Maximum offset between speed limit and current speed for adapting state. -V_CRUISE_UNSET = 255 +V_CRUISE_UNSET = 255. CRUISE_BUTTONS_PLUS = (ButtonType.accelCruise, ButtonType.resumeCruise) CRUISE_BUTTONS_MINUS = (ButtonType.decelCruise, ButtonType.setCruise) @@ -52,13 +52,15 @@ class SpeedLimitAssist: a_ego: float v_offset: float - def __init__(self, CP): + def __init__(self, CP: car.CarParams, CP_SP: custom.CarParamsSP): self.params = Params() self.CP = CP + self.CP_SP = CP_SP self.frame = -1 self.long_engaged_timer = 0 self.pre_active_timer = 0 self.is_metric = self.params.get_bool("IsMetric") + set_speed_limit_assist_availability(self.CP, self.CP_SP, self.params) self.enabled = self.params.get("SpeedLimitMode", return_default=True) == Mode.assist self.long_enabled = False self.long_enabled_prev = False @@ -140,6 +142,7 @@ class SpeedLimitAssist: def update_params(self) -> None: if self.frame % int(PARAMS_UPDATE_PERIOD / DT_MDL) == 0: self.is_metric = self.params.get_bool("IsMetric") + set_speed_limit_assist_availability(self.CP, self.CP_SP, self.params) self.enabled = self.params.get("SpeedLimitMode", return_default=True) == Mode.assist def update_car_state(self, CS: car.CarState) -> None: diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py b/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py index 168c53016b..8a3376a05c 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py @@ -38,7 +38,7 @@ class TestSpeedLimitAssist: self.reset_custom_params() self.events_sp = EventsSP() CI = self._setup_platform(TOYOTA.TOYOTA_RAV4_TSS2) - self.sla = SpeedLimitAssist(CI.CP) + self.sla = SpeedLimitAssist(CI.CP, CI.CP_SP) self.sla.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.sla.pcm_op_long] / DT_MDL) self.pcm_long_max_set_speed = PCM_LONG_REQUIRED_MAX_SET_SPEED[self.sla.is_metric][1] # use 80 MPH for now self.speed_conv = CV.MS_TO_KPH if self.sla.is_metric else CV.MS_TO_MPH From c552567ada51b0189d5f1d742553731054aa102b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 23 Oct 2025 01:08:19 -0400 Subject: [PATCH 46/72] ui: increase minimum button width in ButtonParamControlSP (#1419) --- selfdrive/ui/sunnypilot/qt/widgets/controls.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/qt/widgets/controls.h b/selfdrive/ui/sunnypilot/qt/widgets/controls.h index e111ce2fb5..71041556b7 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/controls.h @@ -390,7 +390,7 @@ class ButtonParamControlSP : public MultiButtonControlSP { Q_OBJECT public: ButtonParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, - const std::vector &button_texts, const int minimum_button_width = 225, const bool inline_layout = false, bool advancedControl = false) : MultiButtonControlSP(title, desc, icon, + const std::vector &button_texts, const int minimum_button_width = 380, const bool inline_layout = false, bool advancedControl = false) : MultiButtonControlSP(title, desc, icon, button_texts, minimum_button_width, inline_layout, advancedControl) { key = param.toStdString(); int value = atoi(params.get(key).c_str()); From 1c89e2b885f0c24372e095360db8896663e32e78 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 23 Oct 2025 03:26:32 -0400 Subject: [PATCH 47/72] Speed Limit Assist: Disable for Tesla in release (#1418) * Speed Limit Assist: Disable for Tesla in release * add test * unused * use constant * eh * flip * universal it * check release state and align in tests * use this * eh * update changelog --- CHANGELOG.md | 2 + .../speed_limit/speed_limit_settings.cc | 7 ++- .../controls/lib/speed_limit/helpers.py | 9 +++- .../lib/speed_limit/speed_limit_assist.py | 4 +- .../tests/test_speed_limit_assist.py | 43 +++++++++++++++++-- 5 files changed, 56 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00be668571..66f5f432b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ sunnypilot Version 2025.001.000 (2025-10-25) * Intelligent longitudinal control adaptation * Speed Limit Assist (SLA) * Comprehensive speed limit integration featuring @pfeiferj's `mapd` for offline map limits downloads, a Speed Limit Resolver for sourcing data (from car, map, combined, etc), on-screen UI for Speed Limit Information/Warning, and Speed Limit Assist (SLA) to adjust cruise speed automatically. + * Currently disabled for Tesla with sunnypilot Longitudinal Control in release + * May return in future releases * Intelligent Cruise Button Management (ICBM) * System designed to manage the vehicle’s speed by sending cruise control button commands to the car’s ECU. * Smart Cruise Control Map & Vision (SCC-M / SCC-V) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc index 50d9ccbb41..8724e68651 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc @@ -105,6 +105,7 @@ SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent) } void SpeedLimitSettings::refresh() { + bool is_release = params.getBool("IsReleaseSpBranch"); bool is_metric_param = params.getBool("IsMetric"); SpeedLimitMode speed_limit_mode_param = static_cast(std::atoi(params.get("SpeedLimitMode").c_str())); SpeedLimitOffsetType offset_type_param = static_cast(std::atoi(params.get("SpeedLimitOffsetType").c_str())); @@ -126,9 +127,11 @@ void SpeedLimitSettings::refresh() { /* * Speed Limit Assist is available when: - * - has_longitudinal_control or has_icbm + * - has_longitudinal_control or has_icbm, and + * - is not a release branch or not a disallowed brand */ - sla_available = has_longitudinal_control || has_icbm; + bool sla_disallow_in_release = CP.getBrand() == "tesla" && is_release; + sla_available = (has_longitudinal_control || has_icbm) && !sla_disallow_in_release; if (!sla_available && speed_limit_mode_param == SpeedLimitMode::ASSIST) { params.put("SpeedLimitMode", std::to_string(static_cast(SpeedLimitMode::WARNING))); diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py b/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py index c209e98dce..04a85f57c4 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py @@ -22,15 +22,22 @@ def compare_cluster_target(v_cruise_cluster: float, target_set_speed: float, is_ return req_plus, req_minus -def set_speed_limit_assist_availability(CP: car.CarParams, CP_SP: custom.CarParamsSP, params: Params = None) -> None: +def set_speed_limit_assist_availability(CP: car.CarParams, CP_SP: custom.CarParamsSP, params: Params = None) -> bool: if params is None: params = Params() + is_release = params.get_bool("IsReleaseSpBranch") + disallow_in_release = CP.brand == "tesla" and is_release allowed = True + if disallow_in_release: + allowed = False + if not CP.openpilotLongitudinalControl and CP_SP.pcmCruiseSpeed: allowed = False if not allowed: if params.get("SpeedLimitMode", return_default=True) == SpeedLimitMode.assist: params.put("SpeedLimitMode", int(SpeedLimitMode.warning)) + + return allowed diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py index fcb5a98a2a..ff7be8a8be 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py @@ -10,13 +10,13 @@ from cereal import custom, car from openpilot.common.params import Params from openpilot.common.constants import CV from openpilot.common.realtime import DT_MDL -from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N +from openpilot.selfdrive.modeld.constants import ModelConstants +from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit import PCM_LONG_REQUIRED_MAX_SET_SPEED, CONFIRM_SPEED_THRESHOLD from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.helpers import compare_cluster_target, set_speed_limit_assist_availability -from openpilot.selfdrive.modeld.constants import ModelConstants ButtonType = car.CarState.ButtonEvent.Type EventNameSP = custom.OnroadEventSP.EventName diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py b/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py index 8a3376a05c..5dc89cfd80 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py @@ -4,17 +4,21 @@ 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 custom +import pytest + +from cereal import custom from opendbc.car.car_helpers import interfaces +from opendbc.car.tesla.values import CAR as TESLA from opendbc.car.toyota.values import CAR as TOYOTA from openpilot.common.constants import CV from openpilot.common.params import Params from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.car.cruise import V_CRUISE_UNSET +from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfaces -from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit import PCM_LONG_REQUIRED_MAX_SET_SPEED +from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.speed_limit_assist import SpeedLimitAssist, \ PRE_ACTIVE_GUARD_PERIOD, ACTIVE_STATES from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP @@ -30,14 +34,28 @@ SPEED_LIMITS = { 'freeway': 80 * CV.MPH_TO_MS, # 80 mph } +DEFAULT_CAR = TOYOTA.TOYOTA_RAV4_TSS2 + + +@pytest.fixture +def car_name(request): + return getattr(request, "param", DEFAULT_CAR) + + +@pytest.fixture(autouse=True) +def set_car_name_on_instance(request, car_name): + instance = getattr(request, "instance", None) + if instance: + instance.car_name = car_name + class TestSpeedLimitAssist: - def setup_method(self): + def setup_method(self, method): self.params = Params() self.reset_custom_params() self.events_sp = EventsSP() - CI = self._setup_platform(TOYOTA.TOYOTA_RAV4_TSS2) + CI = self._setup_platform(self.car_name) self.sla = SpeedLimitAssist(CI.CP, CI.CP_SP) self.sla.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.sla.pcm_op_long] / DT_MDL) self.pcm_long_max_set_speed = PCM_LONG_REQUIRED_MAX_SET_SPEED[self.sla.is_metric][1] # use 80 MPH for now @@ -51,10 +69,12 @@ class TestSpeedLimitAssist: CP = CarInterface.get_non_essential_params(car_name) CP_SP = CarInterface.get_non_essential_params_sp(CP, car_name) CI = CarInterface(CP, CP_SP) + CI.CP.openpilotLongitudinalControl = True # always assume it's openpilot longitudinal sunnypilot_interfaces.setup_interfaces(CI, self.params) return CI def reset_custom_params(self): + self.params.put("IsReleaseSpBranch", True) self.params.put("SpeedLimitMode", int(Mode.assist)) self.params.put_bool("IsMetric", False) self.params.put("SpeedLimitOffsetType", 0) @@ -84,6 +104,21 @@ class TestSpeedLimitAssist: assert not self.sla.is_active assert V_CRUISE_UNSET == self.sla.get_v_target_from_control() + @pytest.mark.parametrize("car_name", [TESLA.TESLA_MODEL_Y], indirect=True) + def test_disallowed_brands(self, car_name): + """ + Speed Limit Assist is disabled for the following brands and conditions: + - All Tesla and is a release branch + """ + assert not self.sla.enabled + + # stay disallowed even when the param may have changed from somewhere else + self.params.put("SpeedLimitMode", int(Mode.assist)) + for _ in range(int(PARAMS_UPDATE_PERIOD / DT_MDL)): + self.sla.update(True, False, SPEED_LIMITS['city'], 0, SPEED_LIMITS['highway'], SPEED_LIMITS['city'], + SPEED_LIMITS['city'], True, 0, self.events_sp) + assert not self.sla.enabled + def test_disabled(self): self.params.put("SpeedLimitMode", int(Mode.off)) for _ in range(int(10. / DT_MDL)): From 5d47ffdb8ab2bbc6a842dd346fdeb8e9b13c737f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 23 Oct 2025 03:48:04 -0400 Subject: [PATCH 48/72] Speed Limit Assist: Disable for Rivian (#1421) * Speed Limit Assist: Disable for Tesla in release * add test * unused * use constant * eh * flip * universal it * check release state and align in tests * use this * eh * update changelog * Speed Limit Assist: Disable for Rivian * desc * changelog --- CHANGELOG.md | 2 +- .../longitudinal/speed_limit/speed_limit_settings.cc | 6 ++++-- sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py | 3 ++- .../lib/speed_limit/tests/test_speed_limit_assist.py | 6 ++++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66f5f432b1..5b79d67826 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ sunnypilot Version 2025.001.000 (2025-10-25) * Intelligent longitudinal control adaptation * Speed Limit Assist (SLA) * Comprehensive speed limit integration featuring @pfeiferj's `mapd` for offline map limits downloads, a Speed Limit Resolver for sourcing data (from car, map, combined, etc), on-screen UI for Speed Limit Information/Warning, and Speed Limit Assist (SLA) to adjust cruise speed automatically. - * Currently disabled for Tesla with sunnypilot Longitudinal Control in release + * Currently disabled for Tesla with sunnypilot Longitudinal Control in release and Rivian with sunnypilot Longitudinal Control in all branches * May return in future releases * Intelligent Cruise Button Management (ICBM) * System designed to manage the vehicle’s speed by sending cruise control button commands to the car’s ECU. diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc index 8724e68651..99c227d233 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc @@ -128,10 +128,12 @@ void SpeedLimitSettings::refresh() { /* * Speed Limit Assist is available when: * - has_longitudinal_control or has_icbm, and - * - is not a release branch or not a disallowed brand + * - is not a release branch or not a disallowed brand, and + * - is not always disallowed */ bool sla_disallow_in_release = CP.getBrand() == "tesla" && is_release; - sla_available = (has_longitudinal_control || has_icbm) && !sla_disallow_in_release; + bool sla_always_disallow = CP.getBrand() == "rivian"; + sla_available = (has_longitudinal_control || has_icbm) && !sla_disallow_in_release && !sla_always_disallow; if (!sla_available && speed_limit_mode_param == SpeedLimitMode::ASSIST) { params.put("SpeedLimitMode", std::to_string(static_cast(SpeedLimitMode::WARNING))); diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py b/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py index 04a85f57c4..4f388befc7 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py @@ -28,9 +28,10 @@ def set_speed_limit_assist_availability(CP: car.CarParams, CP_SP: custom.CarPara is_release = params.get_bool("IsReleaseSpBranch") disallow_in_release = CP.brand == "tesla" and is_release + always_disallow = CP.brand == "rivian" allowed = True - if disallow_in_release: + if disallow_in_release or always_disallow: allowed = False if not CP.openpilotLongitudinalControl and CP_SP.pcmCruiseSpeed: diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py b/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py index 5dc89cfd80..aa6650adda 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py @@ -9,6 +9,7 @@ import pytest from cereal import custom from opendbc.car.car_helpers import interfaces +from opendbc.car.rivian.values import CAR as RIVIAN from opendbc.car.tesla.values import CAR as TESLA from opendbc.car.toyota.values import CAR as TOYOTA from openpilot.common.constants import CV @@ -104,11 +105,12 @@ class TestSpeedLimitAssist: assert not self.sla.is_active assert V_CRUISE_UNSET == self.sla.get_v_target_from_control() - @pytest.mark.parametrize("car_name", [TESLA.TESLA_MODEL_Y], indirect=True) + @pytest.mark.parametrize("car_name", [RIVIAN.RIVIAN_R1_GEN1, TESLA.TESLA_MODEL_Y], indirect=True) def test_disallowed_brands(self, car_name): """ Speed Limit Assist is disabled for the following brands and conditions: - - All Tesla and is a release branch + - All Tesla and is a release branch; + - All Rivian """ assert not self.sla.enabled From 4e3b1f1f6b2193fb090ec777c1ffdcd502635b93 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 24 Oct 2025 14:56:24 -0400 Subject: [PATCH 49/72] interface: add `is_release` flag to `get_params_sp` (#1426) * interface: add `is_release` flag to `get_params_sp` * split and rename * debump --- selfdrive/car/card.py | 3 ++- selfdrive/car/tests/test_models.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index 4c44e43281..6758432b68 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -88,6 +88,7 @@ class Car: self.can_callbacks = can_comm_callbacks(self.can_sock, self.pm.sock['sendcan']) is_release = self.params.get_bool("IsReleaseBranch") + is_release_sp = self.params.get_bool("IsReleaseSpBranch") if CI is None: # wait for one pandaState and one CAN packet @@ -110,7 +111,7 @@ class Car: init_params_list_sp = sunnypilot_interfaces.initialize_params(self.params) self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, num_pandas, cached_params, - fixed_fingerprint, init_params_list_sp) + fixed_fingerprint, init_params_list_sp, is_release_sp) sunnypilot_interfaces.setup_interfaces(self.CI, self.params) self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP, self.CI.CP_SP) self.CP = self.CI.CP diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 4f61ad3c35..48da4b1ce2 100644 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -151,7 +151,7 @@ class TestCarModelBase(unittest.TestCase): cls.CarInterface = interfaces[cls.platform] cls.CP = cls.CarInterface.get_params(cls.platform, cls.fingerprint, car_fw, alpha_long, False, docs=False) - cls.CP_SP = cls.CarInterface.get_params_sp(cls.CP, cls.platform, cls.fingerprint, car_fw, alpha_long, docs=False) + cls.CP_SP = cls.CarInterface.get_params_sp(cls.CP, cls.platform, cls.fingerprint, car_fw, alpha_long, False, docs=False) assert cls.CP assert cls.CP_SP assert cls.CP.carFingerprint == cls.platform From 432c6050edee69bfd718a3da9e96c6cf2153d2bd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 15:28:08 -0400 Subject: [PATCH 50/72] [bot] Update Python packages (#1338) Update Python packages Co-authored-by: github-actions[bot] --- docs/CARS.md | 30 ++++++++++++++++-------------- opendbc_repo | 2 +- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 25f84b389e..695c2589e3 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 337 Supported Cars +# 339 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -21,7 +21,10 @@ A supported vehicle is one that just works when you install a comma device. All |Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Chevrolet|Bolt EV Non-ACC 2017|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Chevrolet|Bolt EV Non-ACC 2018-21|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Chevrolet|Malibu Non-ACC 2016-23|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| @@ -236,20 +239,20 @@ A supported vehicle is one that just works when you install a comma device. All |Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Rivian A connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Subaru|Ascent 2019-21|All[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Forester 2017-18|EyeSight Driver Assistance[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Forester 2019-21|All[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Impreza 2017-19|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Impreza 2020-22|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Legacy 2015-18|EyeSight Driver Assistance[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Ascent 2019-21|All[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Forester 2017-18|EyeSight Driver Assistance[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Forester 2019-21|All[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Impreza 2017-19|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Impreza 2020-22|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Legacy 2015-18|EyeSight Driver Assistance[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Legacy 2020-22|All[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Outback 2015-17|EyeSight Driver Assistance[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Outback 2018-19|EyeSight Driver Assistance[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Outback 2015-17|EyeSight Driver Assistance[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Outback 2018-19|EyeSight Driver Assistance[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Outback 2020-22|All[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|XV 2018-19|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|XV 2020-21|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|XV 2018-19|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|XV 2020-21|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Škoda|Fabia 2022-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| |Škoda|Kamiq 2021-23[13,15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| |Škoda|Karoq 2019-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| @@ -308,7 +311,6 @@ A supported vehicle is one that just works when you install a comma device. All |Toyota|RAV4 Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Toyota|RAV4 Hybrid 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Wildlander PHEV 2021|All|openpilot|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| diff --git a/opendbc_repo b/opendbc_repo index 0a2315efd1..952a061397 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 0a2315efd175176c318aa34d634d55d9dcd9350a +Subproject commit 952a0613977eee8cb5180f4562121ec2291c92b5 From 43e7d8717657a062ef0b6535cb43506668469341 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 24 Oct 2025 15:34:50 -0400 Subject: [PATCH 51/72] version: more release branches (#1427) --- system/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/version.py b/system/version.py index 268aaf11ea..a3f2e36da5 100755 --- a/system/version.py +++ b/system/version.py @@ -10,7 +10,7 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.swaglog import cloudlog from openpilot.common.git import get_commit, get_origin, get_branch, get_short_branch, get_commit_date -RELEASE_SP_BRANCHES = ['release-c3', 'release'] +RELEASE_SP_BRANCHES = ['release-c3', 'release', 'release-tizi', 'release-tici', 'release-tizi-staging', 'release-tici-staging'] TESTED_SP_BRANCHES = ['staging-c3', 'staging-c3-new', 'staging'] MASTER_SP_BRANCHES = ['master'] RELEASE_BRANCHES = ['release3-staging', 'release3', 'release-tici', 'nightly'] From ae9bd39883fab51a850f92191f45f05cdb9089fa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 17:04:27 -0400 Subject: [PATCH 52/72] [bot] Update Python packages (#1428) Update Python packages Co-authored-by: github-actions[bot] --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 952a061397..efe4ff137f 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 952a0613977eee8cb5180f4562121ec2291c92b5 +Subproject commit efe4ff137f839bbd416ac8ff8f5b78550a7d6eec From 3a45fff1b973ad291115dc5e2a97754b4664f1b6 Mon Sep 17 00:00:00 2001 From: MuskratGG <67509251+sirmuskrat@users.noreply.github.com> Date: Fri, 24 Oct 2025 21:09:01 -0400 Subject: [PATCH 53/72] =?UTF-8?q?ui:=20openpilot=20Longitudinal=20Control?= =?UTF-8?q?=20=E2=86=92=20sunnypilot=20Longitudinal=20Control=20(#1422)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update developer_panel.cc Changed mentions of "openpilot Longitudinal Control" to "sunnypilot Longitudinal Control" to align with other UI elements pointing users towards enabling "sunnypilot Longitudinal Control" * Update warning message for longitudinal control * more * a bit more * slightly more --------- Co-authored-by: Jason Wen --- CHANGELOG.md | 2 +- selfdrive/ui/qt/offroad/developer_panel.cc | 8 ++++---- selfdrive/ui/qt/offroad/settings.cc | 2 +- .../sunnypilot/qt/offroad/settings/longitudinal_panel.cc | 6 +++--- .../qt/offroad/settings/vehicle/hyundai_settings.h | 4 ++-- .../ui/sunnypilot/qt/offroad/settings/visuals_panel.cc | 6 +++--- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b79d67826..a36cca7cda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ sunnypilot Version 2025.001.000 (2025-10-25) * Intelligent Cruise Button Management (ICBM) * System designed to manage the vehicle’s speed by sending cruise control button commands to the car’s ECU. * Smart Cruise Control Map & Vision (SCC-M / SCC-V) - * When using any form of long control (openpilot longitudinal or ICBM) it will control the speed at which you enter and perform a turn by leveraging map data (SCC-M) and/or by leveraging what the model sees about the curve ahead (SCC-V) + * When using any form of long control (sunnypilot longitudinal control or ICBM) it will control the speed at which you enter and perform a turn by leveraging map data (SCC-M) and/or by leveraging what the model sees about the curve ahead (SCC-V) * Vehicle Selector * If your vehicle isn’t fingerprinted automatically, you can still use the vehicle selector to get it working * sunnylink Integration diff --git a/selfdrive/ui/qt/offroad/developer_panel.cc b/selfdrive/ui/qt/offroad/developer_panel.cc index fbfb16294b..37c5d19272 100644 --- a/selfdrive/ui/qt/offroad/developer_panel.cc +++ b/selfdrive/ui/qt/offroad/developer_panel.cc @@ -32,11 +32,11 @@ DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) { experimentalLongitudinalToggle = new ParamControl( "AlphaLongitudinalEnabled", - tr("openpilot Longitudinal Control (Alpha)"), + tr("sunnypilot Longitudinal Control (Alpha)"), QString("%1

%2") - .arg(tr("WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).")) - .arg(tr("On this car, sunnypilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. " - "Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.")), + .arg(tr("WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).")) + .arg(tr("On this car, sunnypilot defaults to the car's built-in ACC instead of sunnypilot's longitudinal control. " + "Enable this to switch to sunnypilot longitudinal control. Enabling Experimental mode is recommended when enabling sunnypilot longitudinal control alpha.")), "" ); experimentalLongitudinalToggle->setConfirmation(true, false); diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 6f594d939a..9a909ff2d2 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -188,7 +188,7 @@ void TogglesPanel::updateToggles() { const QString unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control."); QString long_desc = unavailable + " " + \ - tr("openpilot longitudinal control may come in a future update."); + tr("sunnypilot longitudinal control may come in a future update."); if (CP.getAlphaLongitudinalAvailable()) { if (is_release) { long_desc = unavailable + " " + tr("An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches."); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc index 9c51ff8c67..ab357a2c16 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc @@ -132,9 +132,9 @@ void LongitudinalPanel::refresh(bool _offroad) { QString long_desc = icbm_unavaialble; if (has_longitudinal_control) { if (CP.getAlphaLongitudinalAvailable()) { - long_desc = icbm_unavaialble + " " + tr("Disable the openpilot Longitudinal Control (alpha) toggle to allow Intelligent Cruise Button Management."); + long_desc = icbm_unavaialble + " " + tr("Disable the sunnypilot Longitudinal Control (alpha) toggle to allow Intelligent Cruise Button Management."); } else { - long_desc = icbm_unavaialble + " " + tr("openpilot Longitudinal Control is the default longitudinal control for this platform."); + long_desc = icbm_unavaialble + " " + tr("sunnypilot Longitudinal Control is the default longitudinal control for this platform."); } } @@ -172,7 +172,7 @@ void LongitudinalPanel::refresh(bool _offroad) { } QString accEnabledDescription = tr("Enable custom Short & Long press increments for cruise speed increase/decrease."); - QString accNoLongDescription = tr("This feature can only be used with openpilot longitudinal control enabled."); + QString accNoLongDescription = tr("This feature can only be used with sunnypilot longitudinal control enabled."); QString accPcmCruiseDisabledDescription = tr("This feature is not supported on this platform due to vehicle limitations."); QString onroadOnlyDescription = tr("Start the vehicle to check vehicle compatibility."); 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..b1ae95a01e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.h @@ -33,7 +33,7 @@ private: static QString toggleDisableMsg(bool _offroad, bool _has_longitudinal_control) { if (!_has_longitudinal_control) { - return tr("This feature can only be used with openpilot longitudinal control enabled."); + return tr("This feature can only be used with sunnypilot longitudinal control enabled."); } if (!_offroad) { @@ -57,7 +57,7 @@ private: } return QString("%1

%2
%3
%4
") - .arg(tr("Fine-tune your driving experience by adjusting acceleration smoothness with openpilot longitudinal control.")) + .arg(tr("Fine-tune your driving experience by adjusting acceleration smoothness with sunnypilot longitudinal control.")) .arg(off_str) .arg(dynamic_str) .arg(predictive_str); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc index 0e42f777cd..37e982a982 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc @@ -119,7 +119,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) { // Visuals: Display Metrics below Chevron std::vector chevron_info_settings_texts{tr("Off"), tr("Distance"), tr("Speed"), tr("Time"), tr("All")}; chevron_info_settings = new ButtonParamControlSP( - "ChevronInfo", tr("Display Metrics Below Chevron"), tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control)."), + "ChevronInfo", tr("Display Metrics Below Chevron"), tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with sunnypilot longitudinal control)."), "", chevron_info_settings_texts, 200); @@ -159,8 +159,8 @@ void VisualsPanel::refreshLongitudinalStatus() { } if (chevron_info_settings) { - QString chevronEnabledDescription = tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control)."); - QString chevronNoLongDescription = tr("This feature requires openpilot longitudinal control to be available."); + QString chevronEnabledDescription = tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with sunnypilot longitudinal control)."); + QString chevronNoLongDescription = tr("This feature requires sunnypilot longitudinal control to be available."); if (has_longitudinal_control) { chevron_info_settings->setDescription(chevronEnabledDescription); From c1e15e5544b6c9cc32ee7d8488baa7d1c86d5ea6 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 25 Oct 2025 01:00:46 -0400 Subject: [PATCH 54/72] changelog: add new contributor entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a36cca7cda..2c899f18d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -134,6 +134,7 @@ sunnypilot Version 2025.001.000 (2025-10-25) * @michael-was-taken made their first contribution in "Reorder README tables: show -new branches first (#1191)" * @dzid26 made their first contribution in "params: Fix loading delay on startup (#1297)" * @HazZelnutz made their first contribution in "Visuals: Turn signals on screen when blinker is used (#1291)" + * @sirmuskrat made their first contribution in "ui: openpilot Longitudinal Control → sunnypilot Longitudinal Control (#1422)" * New Contributors (sunnypilot/opendbc) * @chrispypatt made their first contribution in "Toyota: SecOC Longitudinal Control (sunnypilot/opendbc#93)" * @Discountchubbs made their first contribution in "Hyundai: EPS FW For 2022 KIA_NIRO_EV SCC (sunnypilot/opendbc#118)" From 1a4ea669875ffef3bffa27f355bc764c761cbfc8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 25 Oct 2025 22:45:17 -0400 Subject: [PATCH 55/72] version: bump to 2025.002.000 --- CHANGELOG.md | 3 +++ sunnypilot/common/version.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c899f18d4..5cb25994be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +sunnypilot Version 2025.002.000 (2025-xx-xx) +======================== + sunnypilot Version 2025.001.000 (2025-10-25) ======================== * 🛠️ Major rewrite diff --git a/sunnypilot/common/version.h b/sunnypilot/common/version.h index 5086f103f8..b52d984b4d 100644 --- a/sunnypilot/common/version.h +++ b/sunnypilot/common/version.h @@ -1 +1 @@ -#define SUNNYPILOT_VERSION "2025.001.000" +#define SUNNYPILOT_VERSION "2025.002.000" From eecb8e5c19e1862ad71ffa0e47812ac1e3864323 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat, 25 Oct 2025 19:55:26 -0700 Subject: [PATCH 56/72] models: bump model json to v8 (#1430) models: bump model json to v8 post release Co-authored-by: Jason Wen --- sunnypilot/models/fetcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sunnypilot/models/fetcher.py b/sunnypilot/models/fetcher.py index 3be6e0b46c..8c9c4eac71 100644 --- a/sunnypilot/models/fetcher.py +++ b/sunnypilot/models/fetcher.py @@ -116,7 +116,7 @@ class ModelCache: class ModelFetcher: """Handles fetching and caching of model data from remote source""" - MODEL_URL = "https://docs.sunnypilot.ai/driving_models_v7.json" + MODEL_URL = "https://docs.sunnypilot.ai/driving_models_v8.json" def __init__(self, params: Params): self.params = params From b460d5804c9c6f9ee7e314f94ae9af0eab5b1883 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat, 25 Oct 2025 20:47:37 -0700 Subject: [PATCH 57/72] LiveLocationKalman: skip tests on unsupported msgq (#1407) * locationd llk: skip tests on unsupported msgq * Update sunnypilot/selfdrive/locationd/tests/test_locationd.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Jason Wen Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- sunnypilot/selfdrive/locationd/tests/test_locationd.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sunnypilot/selfdrive/locationd/tests/test_locationd.py b/sunnypilot/selfdrive/locationd/tests/test_locationd.py index c409f5b5a5..877bc821da 100644 --- a/sunnypilot/selfdrive/locationd/tests/test_locationd.py +++ b/sunnypilot/selfdrive/locationd/tests/test_locationd.py @@ -1,4 +1,5 @@ import pytest +import platform import json import random import time @@ -12,6 +13,10 @@ from openpilot.common.transformations.coordinates import ecef2geodetic from openpilot.system.manager.process_config import managed_processes +if platform.system() == 'Darwin': + pytest.skip("Skipping locationd test on macOS due to unsupported msgq.", allow_module_level=True) + + class TestLocationdProc: LLD_MSGS = ['gpsLocationExternal', 'cameraOdometry', 'carState', 'liveCalibration', 'accelerometer', 'gyroscope', 'magnetometer'] From e4aada10a400552f63ba4cda11371841071bdf24 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sun, 26 Oct 2025 21:43:30 -0400 Subject: [PATCH 58/72] Bug: Model UI Crash Fix (#1431) Model UI Crash Fix --- selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc index c3f795e18c..4d89ea58b0 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc @@ -310,9 +310,8 @@ void ModelsPanel::handleCurrentModelLblBtnClicked() { QList sortedModels; QSet modelFolders; QRegularExpression re("\\(([^)]*)\\)[^(]*$"); - const auto bundles = model_manager.getAvailableBundles(); - for (const auto &bundle : bundles) { + for (const auto &bundle : model_manager.getAvailableBundles()) { auto overrides = bundle.getOverrides(); QString folder; for (const auto &override : overrides) { @@ -392,7 +391,7 @@ void ModelsPanel::handleCurrentModelLblBtnClicked() { showResetParamsDialog(); } else { // Find selected bundle and initiate download - for (const auto &bundle: bundles) { + for (const auto &bundle: model_manager.getAvailableBundles()) { if (QString::fromStdString(bundle.getRef()) == selectedBundleRef) { params.put("ModelManager_DownloadIndex", std::to_string(bundle.getIndex())); if (bundle.getGeneration() != model_manager.getActiveBundle().getGeneration()) { From de7acc54660a9886cc872b17af1e2cbf02285f0b Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 28 Oct 2025 16:01:21 +0100 Subject: [PATCH 59/72] ci: integrate Discourse notifications and refactor notification logic (#1435) * ci: integrate Discourse notifications and refactor notification logic - Replaced Discord webhook notifications with Discourse topic updates. - Introduced reusable `post-to-discourse` composite action. - Added `test-discourse.yaml` workflow for debugging and verification. * ci: adjust notification dependencies and prepare_strategy reference - Updated `notify` step to depend on `prepare_strategy` instead of `build`. - Adjusted variable references to use `prepare_strategy` outputs. * Forcing debug * ci: update environment variable references and add commit information - Switched `PUBLIC_REPO_URL` source to environment variable for consistency. - Added commit SHA variables to enhance template generation logic. * more tweaks! * more tweaks! * bad bot lmao * Test? * i mean.... * i mean.... * getting there * testing the if * testing the if * ci: re-enable notify steps for prebuilt workflow - Uncommented `build` and `publish` dependencies. - Restored conditional logic to trigger only for relevant events. * ci: enhance Discourse action to support new topic creation - Added support for creating new topics with `category-id` and `title`. - Improved input validation and response handling for flexibility. * ci: improve conditions for prebuilt workflow notifications - Refined `if` clause to ensure branches in `DEV_FEEDBACK_NOTIFICATION_BRANCHES` are targeted. - Adjusted logic for accurate topic ID mapping in Discourse integration. * forgot to rename --- .../workflows/post-to-discourse/action.yml | 105 ++++++++++++++++++ .../workflows/sunnypilot-build-prebuilt.yaml | 63 +++++++---- .github/workflows/test-discourse.yaml.yml | 78 +++++++++++++ 3 files changed, 222 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/post-to-discourse/action.yml create mode 100644 .github/workflows/test-discourse.yaml.yml diff --git a/.github/workflows/post-to-discourse/action.yml b/.github/workflows/post-to-discourse/action.yml new file mode 100644 index 0000000000..55232ce0e1 --- /dev/null +++ b/.github/workflows/post-to-discourse/action.yml @@ -0,0 +1,105 @@ +name: 'Post to Discourse' +description: 'Posts a message to a Discourse topic (existing or new)' + +inputs: + discourse-url: + description: 'Discourse instance URL (e.g., https://discourse.example.com)' + required: true + api-key: + description: 'Discourse API key' + required: true + api-username: + description: 'Discourse API username' + required: true + topic-id: + description: 'Discourse topic ID to post to (use this OR category-id + title)' + required: false + category-id: + description: 'Category ID for new topic (required if topic-id not provided)' + required: false + title: + description: 'Title for new topic (required if topic-id not provided)' + required: false + message: + description: 'Message content (markdown supported)' + required: true + +outputs: + post-number: + description: 'The post number in the topic' + value: ${{ steps.post.outputs.post_number }} + post-url: + description: 'Direct URL to the post' + value: ${{ steps.post.outputs.post_url }} + topic-id: + description: 'The topic ID (useful when creating a new topic)' + value: ${{ steps.post.outputs.topic_id }} + +runs: + using: "composite" + steps: + - name: Post to Discourse + id: post + shell: bash + run: | + # Validate inputs + if [ -z "${{ inputs.topic-id }}" ] && ([ -z "${{ inputs.category-id }}" ] || [ -z "${{ inputs.title }}" ]); then + echo "❌ Error: Must provide either topic-id OR both category-id and title" + exit 1 + fi + + if [ -n "${{ inputs.topic-id }}" ] && ([ -n "${{ inputs.category-id }}" ] || [ -n "${{ inputs.title }}" ]); then + echo "⚠️ Warning: Both topic-id and category-id/title provided. Will post to existing topic." + fi + + # Determine if creating new topic or posting to existing + if [ -n "${{ inputs.topic-id }}" ]; then + echo "📝 Posting to existing topic ID: ${{ inputs.topic-id }}" + + # Create JSON payload for posting to existing topic + PAYLOAD=$(jq -n \ + --arg content '${{ inputs.message }}' \ + --arg topic_id "${{ inputs.topic-id }}" \ + '{topic_id: $topic_id, raw: $content}') + else + echo "✨ Creating new topic: ${{ inputs.title }}" + + # Create JSON payload for new topic + PAYLOAD=$(jq -n \ + --arg content '${{ inputs.message }}' \ + --arg title "${{ inputs.title }}" \ + --arg category "${{ inputs.category-id }}" \ + '{title: $title, category: ($category | tonumber), raw: $content}') + fi + + # Post to Discourse + RESPONSE=$(curl -s -w "\n%{http_code}" \ + -X POST "${{ inputs.discourse-url }}/posts.json" \ + -H "Content-Type: application/json" \ + -H "Api-Key: ${{ inputs.api-key }}" \ + -H "Api-Username: ${{ inputs.api-username }}" \ + -d "$PAYLOAD") + + HTTP_CODE=$(echo "$RESPONSE" | tail -n1) + BODY=$(echo "$RESPONSE" | sed '$d') + + if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then + echo "✅ Successfully posted to Discourse!" + + POST_NUMBER=$(echo "$BODY" | jq -r '.post_number // "unknown"') + TOPIC_ID=$(echo "$BODY" | jq -r '.topic_id // "${{ inputs.topic-id }}"') + POST_URL="${{ inputs.discourse-url }}/t/${TOPIC_ID}/${POST_NUMBER}" + + echo "post_number=${POST_NUMBER}" >> $GITHUB_OUTPUT + echo "post_url=${POST_URL}" >> $GITHUB_OUTPUT + echo "topic_id=${TOPIC_ID}" >> $GITHUB_OUTPUT + + echo "Topic ID: ${TOPIC_ID}" + echo "Post number: ${POST_NUMBER}" + echo "URL: ${POST_URL}" + else + echo "❌ Failed to post to Discourse" + echo "HTTP Code: ${HTTP_CODE}" + echo "Response: ${BODY}" + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/sunnypilot-build-prebuilt.yaml b/.github/workflows/sunnypilot-build-prebuilt.yaml index f7719709ae..12fb01cbd1 100644 --- a/.github/workflows/sunnypilot-build-prebuilt.yaml +++ b/.github/workflows/sunnypilot-build-prebuilt.yaml @@ -302,36 +302,51 @@ jobs: git push -f origin ${TAG} notify: - needs: [ build, publish ] + needs: + - prepare_strategy + - build + - publish runs-on: ubuntu-24.04 - if: ${{ (always() && !cancelled() && !failure()) && needs.publish.result == 'success' && !failure() && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }} + if: ${{ (always() && !cancelled() && !failure()) + && needs.publish.result == 'success' + && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) + && (fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES_V2)[github.head_ref || github.ref_name] != null) }} steps: - uses: actions/checkout@v4 - - name: Setup Alpine Linux environment - uses: jirutka/setup-alpine@v1.2.0 - with: - packages: 'jq gettext curl' - - name: Send Discord Notification - env: - DISCORD_WEBHOOK: ${{ contains(fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES), env.SOURCE_BRANCH) && secrets.DISCORD_DEV_FEEDBACK_CHANNEL_WEBHOOK || secrets.DISCORD_DEV_PRIVATE_CHANNEL_WEBHOOK }} + - name: Prepare notification message + id: message run: | - TEMPLATE='${{ vars.DISCORD_GENERAL_UPDATE_NOTICE }}' - export EXTRA_VERSION_IDENTIFIER="${{ needs.build.outputs.extra_version_identifier }}" - export VERSION="${{ needs.build.outputs.version }}" - export branch_name=${{ env.SOURCE_BRANCH }} - export new_branch=${{ needs.build.outputs.new_branch }} - export extra_version_identifier=${{ needs.build.outputs.extra_version_identifier || github.run_number}} - echo ${TEMPLATE} | envsubst | jq -c '.' | tee payload.json - curl -X POST -H "Content-Type: application/json" -d @payload.json $DISCORD_WEBHOOK + TEMPLATE='${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }}' + export VERSION="${{ needs.prepare_strategy.outputs.version }}" + export branch_name="${{ env.SOURCE_BRANCH }}" + export new_branch="${{ needs.prepare_strategy.outputs.new_branch }}" + export commit_sha="${{ github.sha }}" + export commit_short_sha="${{ github.sha }}" + export commit_short_sha="${commit_short_sha:0:7}" + export extra_version_identifier="${{ needs.prepare_strategy.outputs.extra_version_identifier || github.run_number }}" + export PUBLIC_REPO_URL="${{ env.PUBLIC_REPO_URL }}" - echo "" - echo "---- ℹ️ To update the list of branches that notify to dev-feedback -----" - echo "" - echo "1. Go to: ${{ github.server_url }}/${{ github.repository }}/settings/variables/actions/DEV_FEEDBACK_NOTIFICATION_BRANCHES" - echo "2. Current value: ${{ vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES }}" - echo "3. Update as needed (JSON array with no spaces)" - shell: alpine.sh {0} + MESSAGE=$(cat << 'EOF' | envsubst + ${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }} + EOF + ) + + { + echo 'content<> $GITHUB_OUTPUT + shell: bash + + - name: Post to Discourse + uses: ./.github/workflows/post-to-discourse + with: + discourse-url: ${{ vars.DISCOURSE_URL }} + api-key: ${{ secrets.DISCOURSE_API_KEY }} + api-username: "system" + topic-id: ${{ fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES_V2)[github.head_ref || github.ref_name].topic_id }} + message: ${{ steps.message.outputs.content }} manage-pr-labels: name: Remove prebuilt label diff --git a/.github/workflows/test-discourse.yaml.yml b/.github/workflows/test-discourse.yaml.yml new file mode 100644 index 0000000000..fadaec4eaa --- /dev/null +++ b/.github/workflows/test-discourse.yaml.yml @@ -0,0 +1,78 @@ +name: Debug Discourse Posting + +on: + push: + +jobs: + test-discourse-post: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Post test message to Discourse + uses: ./.github/workflows/post-to-discourse + with: + discourse-url: ${{ vars.DISCOURSE_URL }} + api-key: ${{ secrets.DISCOURSE_API_KEY }} + api-username: ${{ secrets.DISCOURSE_API_USERNAME }} + topic-id: ${{ vars.DISCOURSE_UPDATES_TOPIC_ID }} + message: | + ## 🧪 Test Post from GitHub Actions + + **This is a test post to verify Discourse integration** + + - **Workflow**: ${{ github.workflow }} + - **Run Number**: #${{ github.run_number }} + - **Branch**: `${{ github.ref_name }}` + - **Commit**: ${{ github.sha }} + - **Actor**: @${{ github.actor }} + - **Timestamp**: ${{ github.event.head_commit.timestamp }} + + --- + + ### Fake Build Info (for testing) + - **Version**: 0.9.8-test + - **Build**: #42 + - **Branch**: release-test + + [View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + + *This is an automated test message. Drive safe! 🚗💨* + + + - name: Create topic on Discourse + uses: ./.github/workflows/post-to-discourse + with: + discourse-url: ${{ vars.DISCOURSE_URL }} + api-key: ${{ secrets.DISCOURSE_API_KEY }} + api-username: ${{ secrets.DISCOURSE_API_USERNAME }} + #topic-id: ${{ vars.DISCOURSE_UPDATES_TOPIC_ID }} + category-id: 4 + title: "This is a test of a new topic instead of a reply" + message: | + ## 🧪 Test Post from GitHub Actions + + **This is a test post to verify Discourse integration** + + - **Workflow**: ${{ github.workflow }} + - **Run Number**: #${{ github.run_number }} + - **Branch**: `${{ github.ref_name }}` + - **Commit**: ${{ github.sha }} + - **Actor**: @${{ github.actor }} + - **Timestamp**: ${{ github.event.head_commit.timestamp }} + + --- + + ### Fake Build Info (for testing) + - **Version**: 0.9.8-test + - **Build**: #42 + - **Branch**: release-test + + [View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + + *This is an automated test message. Drive safe! 🚗💨* + - name: Display results + if: always() + run: | + echo "::notice::Discourse post test completed" + echo "Check your Discourse topic to verify the post appeared correctly" \ No newline at end of file From 55147d8a55e23293a95b353039bc9caeb866ec04 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 28 Oct 2025 18:58:04 +0100 Subject: [PATCH 60/72] ci: use environment variable for PR label in query (#1436) * ci: use environment variable for PR label in query - Replaced static `PR_LABEL` references with `${{ env.PR_LABEL }}` for consistency. - Ensures flexibility and reduces hardcoded values in the workflow. * does this work better? * fuck this * aight --- .github/workflows/sunnypilot-master-dev-prep.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sunnypilot-master-dev-prep.yaml b/.github/workflows/sunnypilot-master-dev-prep.yaml index 4aafd4a4a4..968540955f 100644 --- a/.github/workflows/sunnypilot-master-dev-prep.yaml +++ b/.github/workflows/sunnypilot-master-dev-prep.yaml @@ -3,7 +3,6 @@ name: Build dev env: DEFAULT_SOURCE_BRANCH: "master" DEFAULT_TARGET_BRANCH: "master-dev" - PR_LABEL: "dev" LFS_URL: 'https://gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git/info/lfs' LFS_PUSH_URL: 'ssh://git@gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git' @@ -119,7 +118,7 @@ jobs: # Use GitHub API to get PRs with specific label, ordered by creation date PR_LIST=$(gh api graphql -f query=' query($search_query:String!) { - search(query: $search_query, type:ISSUE, first:100) { + search(query: $search_query, type:ISSUE, first:40) { nodes { ... on PullRequest { number @@ -149,7 +148,7 @@ jobs: } } } - }' -F search_query="repo:${{ github.repository }} is:pr is:open label:${PR_LABEL},${PR_LABEL}-c3 draft:false sort:created-asc") + }' -F search_query="repo:${{ github.repository }} is:pr is:open label:${{ vars.PREBUILT_PR_LABEL }},${{ vars.PREBUILT_PR_LABEL }}-c3 draft:false sort:created-asc") PR_LIST=${PR_LIST//\'/} echo "PR_LIST=${PR_LIST}" >> $GITHUB_OUTPUT From 707e2aedae4ddc872c609df1ae7c9e3f1e8738cd Mon Sep 17 00:00:00 2001 From: THERoenPR Date: Tue, 28 Oct 2025 22:13:39 -0400 Subject: [PATCH 61/72] controlsd: add `CP_SP` to `get_pid_accel_limits` (#1410) * Add CP_SP to get_pid_accel_limits() call in controlsd Match input parameters of CP_SP commit * bump * bump --------- Co-authored-by: roenthomas <43324106+roenthomas@users.noreply.github.com> Co-authored-by: Jason Wen --- opendbc_repo | 2 +- selfdrive/controls/controlsd.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index efe4ff137f..e0e1626820 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit efe4ff137f839bbd416ac8ff8f5b78550a7d6eec +Subproject commit e0e1626820d6a18a984ae69eebc012180699d41c diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 1694afee23..b381879a7a 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -132,7 +132,7 @@ class Controls(ControlsExt, ModelStateBase): self.LoC.reset() # accel PID loop - pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, CS.vCruise * CV.KPH_TO_MS) + pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, self.CP_SP, CS.vEgo, CS.vCruise * CV.KPH_TO_MS) actuators.accel = float(self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits)) # Steering PID loop and lateral MPC From f833819143926bf987070632588a0dc271e84c55 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 31 Oct 2025 17:54:39 +0100 Subject: [PATCH 62/72] ci: update trigger for prebuilt (#1439) Updated workflow `if` conditions to use `vars.PREBUILT_PR_LABEL`. --- .github/workflows/sunnypilot-master-dev-prep.yaml | 4 ++-- .../ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sunnypilot-master-dev-prep.yaml b/.github/workflows/sunnypilot-master-dev-prep.yaml index 968540955f..e93e778aa6 100644 --- a/.github/workflows/sunnypilot-master-dev-prep.yaml +++ b/.github/workflows/sunnypilot-master-dev-prep.yaml @@ -42,7 +42,7 @@ jobs: if: ( (github.event_name == 'workflow_dispatch') || (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)) - || (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev')))) + || (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == vars.PREBUILT_PR_LABEL || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, vars.PREBUILT_PR_LABEL)))) ) steps: - uses: actions/checkout@v4 @@ -54,7 +54,7 @@ jobs: uses: ./.github/workflows/wait-for-action # Path to where you place the action if: ( (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)) - || (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev')))) + || (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == vars.PREBUILT_PR_LABEL || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, vars.PREBUILT_PR_LABEL)))) ) with: workflow: selfdrive_tests.yaml # The workflow file to monitor diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc index 4bbf894572..86b5b93e27 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc @@ -290,7 +290,7 @@ void SunnylinkPanel::updatePanel() { pairSponsorBtn->setEnabled(!is_onroad && is_sunnylink_enabled); pairSponsorBtn->setValue(is_paired ? tr("Paired") : tr("Not Paired")); - sunnylinkUploaderEnabledBtn->setEnabled(max_current_sponsor_rule.roleTier == SponsorTier::Guardian && is_sunnylink_enabled); + sunnylinkUploaderEnabledBtn->setEnabled(max_current_sponsor_rule.roleTier >= SponsorTier::Novice && is_sunnylink_enabled); if (!is_sunnylink_enabled) { sunnylinkEnabledBtn->setValue(""); From f60c2b6a835201ac06fa109ae98b14c36afabe18 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 1 Nov 2025 12:14:57 +0100 Subject: [PATCH 63/72] sunnylink: update uploader button logic to support novice tier and above (#1438) * sunnylink: update uploader button logic to support novice tier and above - Adjusted the enable condition to include SponsorTier::Novice and above. * sunnylink: improve uploader button visibility and accessibility logic - Made uploader button conditionally visible based on user tier and settings. - Clarified button label to specify testing purposes only. --- .../ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc index 86b5b93e27..924d1d3c09 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc @@ -90,7 +90,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) { QString sunnylinkUploaderDesc = tr("Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)"); sunnylinkUploaderEnabledBtn = new ParamControlSP( "EnableSunnylinkUploader", - tr("[Don't use] Enable sunnylink uploader"), + tr("Enable sunnylink uploader (just for testing infrastructure)"), sunnylinkUploaderDesc, "", nullptr, true); list->addItem(sunnylinkUploaderEnabledBtn); @@ -290,7 +290,10 @@ void SunnylinkPanel::updatePanel() { pairSponsorBtn->setEnabled(!is_onroad && is_sunnylink_enabled); pairSponsorBtn->setValue(is_paired ? tr("Paired") : tr("Not Paired")); - sunnylinkUploaderEnabledBtn->setEnabled(max_current_sponsor_rule.roleTier >= SponsorTier::Novice && is_sunnylink_enabled); + bool can_do_uploads = max_current_sponsor_rule.roleTier >= SponsorTier::Novice && is_sunnylink_enabled; + sunnylinkUploaderEnabledBtn->setVisible(can_do_uploads); + sunnylinkUploaderEnabledBtn->setEnabled(can_do_uploads); + if (!is_sunnylink_enabled) { sunnylinkEnabledBtn->setValue(""); From 682d738ffac3432549643a9889e1424192f88291 Mon Sep 17 00:00:00 2001 From: Amy Jeanes Date: Sun, 2 Nov 2025 02:47:30 +0000 Subject: [PATCH 64/72] Tesla: Coop Steering (#1283) * Tesla: Coop Steering * bump * bump * sync with opendbc/master * resolve comment * add oscillation warning and add confirmation * styling desc * beta --------- Co-authored-by: Jason Wen --- common/params_keys.h | 1 + opendbc_repo | 2 +- .../settings/vehicle/tesla_settings.cc | 34 +++++++++++++++++++ .../offroad/settings/vehicle/tesla_settings.h | 2 +- sunnypilot/selfdrive/car/interfaces.py | 5 +++ 5 files changed, 42 insertions(+), 2 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index d8485be157..df53261eb2 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -208,6 +208,7 @@ inline static std::unordered_map keys = { {"HyundaiLongitudinalTuning", {PERSISTENT | BACKUP, INT, "0"}}, {"SubaruStopAndGo", {PERSISTENT | BACKUP, BOOL, "0"}}, {"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}}, + {"TeslaCoopSteering", {PERSISTENT | BACKUP, BOOL, "0"}}, {"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}}, {"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}}, diff --git a/opendbc_repo b/opendbc_repo index e0e1626820..c7126f8ba6 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit e0e1626820d6a18a984ae69eebc012180699d41c +Subproject commit c7126f8ba620eb06fe345013081e97503331bbe1 diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.cc index 50ab023021..2fffdb8ad4 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.cc @@ -8,7 +8,41 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.h" TeslaSettings::TeslaSettings(QWidget *parent) : BrandSettingsInterface(parent) { + constexpr int coopSteeringMinKmh = 23; // minimum speed for cooperative steering (enforced by Tesla firmware) + constexpr int oemSteeringMinKmh = 48; // minimum speed for OEM lane departure avoidance (enforced by Tesla firmware) + bool is_metric = params.getBool("IsMetric"); + QString unit = is_metric ? "km/h" : "mph"; + int display_value_coop; + int display_value_oem; + if (is_metric) { + display_value_coop = coopSteeringMinKmh; + display_value_oem = oemSteeringMinKmh; + } else { + display_value_coop = static_cast(std::round(coopSteeringMinKmh * KM_TO_MILE)); + display_value_oem = static_cast(std::round(oemSteeringMinKmh * KM_TO_MILE)); + } + const QString coop_desc = QString("%1

" + "%2
" + "%3
") + .arg(tr("Warning: May experience steering oscillations below %5 %6 during turns, recommend disabling this feature if you experience these.")) + .arg(tr("Allows the driver to provide limited steering input while openpilot is engaged.")) + .arg(tr("Only works above %4 %6.")) + .arg(display_value_coop) + .arg(display_value_oem) + .arg(unit); + + coopSteeringToggle = new ParamControlSP( + "TeslaCoopSteering", + tr("Cooperative Steering (Beta)"), + coop_desc, + "", + this + ); + list->addItem(coopSteeringToggle); + coopSteeringToggle->showDescription(); + coopSteeringToggle->setConfirmation(true, false); } void TeslaSettings::updateSettings() { + coopSteeringToggle->setEnabled(offroad); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.h index 37f2936cdf..a1294513ee 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.h @@ -22,5 +22,5 @@ public: void updateSettings() override; private: - bool offroad = false; + ParamControlSP *coopSteeringToggle = nullptr; }; diff --git a/sunnypilot/selfdrive/car/interfaces.py b/sunnypilot/selfdrive/car/interfaces.py index 7d63d3c5ba..59cfeabd6e 100644 --- a/sunnypilot/selfdrive/car/interfaces.py +++ b/sunnypilot/selfdrive/car/interfaces.py @@ -115,4 +115,9 @@ def initialize_params(params) -> list[dict[str, Any]]: "SubaruStopAndGoManualParkingBrake", ]) + # tesla + keys.extend([ + "TeslaCoopSteering", + ]) + return [{k: params.get(k, return_default=True)} for k in keys] From b81d5bca3c4418fad601f103f070f309077c16ed Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 2 Nov 2025 06:50:41 +0100 Subject: [PATCH 65/72] ui: update discord references and add forum widget (#1440) * sunnylink: introduce community popup with QR code embedding - Added `SunnylinkCommunityPopup` widget to promote the sunnypilot Community Forum. - Integrated a QR code generator and display for quick access. - Updated `WiFiPromptWidget` to include a "Learn More" button triggering the community popup. * sunnylink: adjust community popup styling for better layout - Reduced font size of description text slightly for consistency. - Decreased QR code dimensions to improve visual balance. * Making more space out of thin air * sunnylink: update community references to use forum links - Replaced Discord links with Community Forum URLs for better alignment. - Improved clarity in sponsorship instructions. --- selfdrive/ui/qt/widgets/wifi.cc | 11 +- selfdrive/ui/qt/widgets/wifi.h | 5 + selfdrive/ui/sunnypilot/SConscript | 1 + .../settings/sunnylink/community_widget.cc | 139 ++++++++++++++++++ .../settings/sunnylink/community_widget.h | 40 +++++ .../settings/sunnylink/sponsor_widget.cc | 4 +- 6 files changed, 193 insertions(+), 7 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.cc create mode 100644 selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.h diff --git a/selfdrive/ui/qt/widgets/wifi.cc b/selfdrive/ui/qt/widgets/wifi.cc index d7eb8beeb3..8958e48c93 100644 --- a/selfdrive/ui/qt/widgets/wifi.cc +++ b/selfdrive/ui/qt/widgets/wifi.cc @@ -11,17 +11,18 @@ WiFiPromptWidget::WiFiPromptWidget(QWidget *parent) : QFrame(parent) { main_layout->setContentsMargins(56, 40, 56, 40); main_layout->setSpacing(42); - QLabel *title = new QLabel(tr("🔥 Firehose Mode 🔥")); - title->setStyleSheet("font-size: 64px; font-weight: 500;"); + community_popup = new SunnylinkCommunityPopup(this); + QLabel *title = new QLabel(tr("sunnypilot Community")); + title->setStyleSheet("font-size: 56px; font-weight: 500;"); main_layout->addWidget(title); - QLabel *desc = new QLabel(tr("Maximize your training data uploads to improve openpilot's driving models.")); + QLabel *desc = new QLabel(tr("Need help or have ideas?
Join our community now!")); desc->setStyleSheet("font-size: 40px; font-weight: 400;"); desc->setWordWrap(true); main_layout->addWidget(desc); - QPushButton *settings_btn = new QPushButton(tr("Open")); - connect(settings_btn, &QPushButton::clicked, [=]() { emit openSettings(1, "FirehosePanel"); }); + QPushButton *settings_btn = new QPushButton(tr("Learn More")); + connect(settings_btn, &QPushButton::clicked, [=]() { community_popup->exec(); }); settings_btn->setStyleSheet(R"( QPushButton { font-size: 48px; diff --git a/selfdrive/ui/qt/widgets/wifi.h b/selfdrive/ui/qt/widgets/wifi.h index 3e68a15b7b..8ef9dca91a 100644 --- a/selfdrive/ui/qt/widgets/wifi.h +++ b/selfdrive/ui/qt/widgets/wifi.h @@ -3,12 +3,17 @@ #include #include +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.h" + class WiFiPromptWidget : public QFrame { Q_OBJECT public: explicit WiFiPromptWidget(QWidget* parent = 0); +private: + SunnylinkCommunityPopup *community_popup; + signals: void openSettings(int index = 0, const QString ¶m = ""); }; diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index be92c14261..449093886a 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -35,6 +35,7 @@ qt_src = [ "sunnypilot/qt/offroad/settings/software_panel.cc", "sunnypilot/qt/offroad/settings/sunnylink_panel.cc", "sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.cc", + "sunnypilot/qt/offroad/settings/sunnylink/community_widget.cc", "sunnypilot/qt/offroad/settings/trips_panel.cc", "sunnypilot/qt/offroad/settings/vehicle_panel.cc", "sunnypilot/qt/offroad/settings/visuals_panel.cc", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.cc new file mode 100644 index 0000000000..e29e89ee98 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.cc @@ -0,0 +1,139 @@ +/** + * Copyright (c) 2025-, sunnypilot 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/sunnylink/community_widget.h" + +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/util.h" + +using qrcodegen::QrCode; + +// --- SunnylinkCommunityQRWidget --- + +SunnylinkCommunityQRWidget::SunnylinkCommunityQRWidget(QWidget* parent) + : QWidget(parent) {} + +void SunnylinkCommunityQRWidget::showEvent(QShowEvent *event) { + updateQrCode(SUNNYLINK_COMMUNITY_URL); + update(); +} + +void SunnylinkCommunityQRWidget::updateQrCode(const QString &text) { + QrCode qr = QrCode::encodeText(text.toUtf8().data(), QrCode::Ecc::LOW); + qint32 sz = qr.getSize(); + QImage im(sz, sz, QImage::Format_RGB32); + + QRgb black = qRgb(0, 0, 0); + QRgb white = qRgb(255, 255, 255); + for (int y = 0; y < sz; y++) { + for (int x = 0; x < sz; x++) { + im.setPixel(x, y, qr.getModule(x, y) ? black : white); + } + } + + int final_sz = ((width() / sz) - 1) * sz; + img = QPixmap::fromImage(im.scaled(final_sz, final_sz, Qt::KeepAspectRatio), Qt::MonoOnly); +} + +void SunnylinkCommunityQRWidget::paintEvent(QPaintEvent *e) { + QPainter p(this); + p.fillRect(rect(), Qt::white); + + if (!img.isNull()) { + QSize s = (size() - img.size()) / 2; + p.drawPixmap(s.width(), s.height(), img); + } +} + +// --- SunnylinkCommunityPopup --- + +QStringList SunnylinkCommunityPopup::getInstructions() { + QStringList instructions; + instructions << tr("Scan the QR code and join us!"); + return instructions; +} + +SunnylinkCommunityPopup::SunnylinkCommunityPopup(QWidget* parent) + : DialogBase(parent) { + auto *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + + // Solarized Light base3 background + setStyleSheet("SunnylinkCommunityPopup { background-color: #FDF6E3; }"); + + // Header spanning full width + auto headerWidget = new QWidget(this); + auto headerLayout = new QHBoxLayout(headerWidget); + headerLayout->setContentsMargins(85, 50, 85, 30); + headerLayout->setSpacing(30); + + auto close = new QPushButton(QIcon(":/icons/close.svg"), "", this); + close->setIconSize(QSize(80, 80)); + close->setStyleSheet("border: none;"); + connect(close, &QPushButton::clicked, this, &QDialog::reject); + headerLayout->addWidget(close, 0, Qt::AlignLeft | Qt::AlignVCenter); + + const auto title = new QLabel(tr("Join the sunnypilot Community Forum"), this); + // Solarized base02 for text + title->setStyleSheet("font-size: 65px; color: #073642;"); + title->setWordWrap(false); + title->setAlignment(Qt::AlignCenter); + headerLayout->addWidget(title, 1); + + // Spacer to balance the close button on the right + auto spacer = new QWidget(this); + spacer->setFixedSize(80, 80); + headerLayout->addWidget(spacer, 0); + + mainLayout->addWidget(headerWidget); + + // Two-column content layout + auto contentLayout = new QHBoxLayout(); + contentLayout->setContentsMargins(0, 0, 0, 0); + contentLayout->setSpacing(0); + mainLayout->addLayout(contentLayout, 66); + + // Left side: description + auto leftLayout = new QVBoxLayout(); + leftLayout->setContentsMargins(85, 40, 50, 70); + leftLayout->setSpacing(35); + contentLayout->addLayout(leftLayout, 40); + + // Hype / intro paragraph + const auto desc = new QLabel(tr( + "We're excited to announce our sunnypilot Community Forum

" + "Over the years, Discord just hasn't scaled well for our growing community.
" + "It's noisy, unsearchable, and great discussions disappear too easily.
" + "Our new community forum aims to fix that by making it easier to find answers, share ideas, track feedback, report bugs, help newcomers and more!

" + "Here's what's waiting for you:
" + "• Fully indexable and discoverable through search engines 🔎
" + "• AI-powered🤖 topic and chat summaries, spam detection, and more
" + "• A trust-level system✅ that rewards meaningful contributions
" + "• Designed to work on your own time.🧘

" + "Scan the QR code on the right and join the discussion!" + ), this); + // Solarized base01 for body text + desc->setStyleSheet("font-size: 40px; color: #586E75;"); + desc->setWordWrap(true); + leftLayout->addWidget(desc); + + leftLayout->addStretch(); + + // Right side: QR code and instructions + auto rightLayout = new QVBoxLayout(); + rightLayout->setContentsMargins(50, 40, 85, 70); + rightLayout->setSpacing(40); + contentLayout->addLayout(rightLayout, 1); + + // QR code (smaller, fixed size) + auto *qr = new SunnylinkCommunityQRWidget(this); + qr->setFixedSize(500, 500); + rightLayout->addStretch(); + rightLayout->addWidget(qr, 0, Qt::AlignCenter); + rightLayout->addStretch(); +} \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.h new file mode 100644 index 0000000000..613375d12c --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.h @@ -0,0 +1,40 @@ +/** +* Copyright (c) 2025-, sunnypilot 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 +#include + +#include "common/util.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" + +const QString SUNNYLINK_COMMUNITY_URL = "https://community.sunnypilot.ai/sp-qr"; + +class SunnylinkCommunityQRWidget : public QWidget { + Q_OBJECT + +public: + explicit SunnylinkCommunityQRWidget(QWidget* parent = nullptr); + void paintEvent(QPaintEvent*) override; + +private: + QPixmap img; + void updateQrCode(const QString &text); + void showEvent(QShowEvent *event) override; +}; + +// Popup widget +class SunnylinkCommunityPopup : public DialogBase { + Q_OBJECT + +public: + explicit SunnylinkCommunityPopup(QWidget* parent = nullptr); + +private: + static QStringList getInstructions(); +}; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.cc index 7b5ce48238..a014eddacb 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.cc @@ -79,11 +79,11 @@ QStringList SunnylinkSponsorPopup::getInstructions(bool sponsor_pair) { instructions << tr("Scan the QR code to login to your GitHub account") << tr("Follow the prompts to complete the pairing process") << tr("Re-enter the \"sunnylink\" panel to verify sponsorship status") - << tr("If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot"); + << tr("If sponsorship status was not updated, please contact a moderator on our forum at https://community.sunnypilot.ai"); } else { instructions << tr("Scan the QR code to visit sunnyhaibin's GitHub Sponsors page") << tr("Choose your sponsorship tier and confirm your support") - << tr("Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status"); + << tr("Join our Community Forum at https://community.sunnypilot.ai and reach out to a moderator if you have issues"); } return instructions; } From 18af4d6ad6db657bcdbe892614851d24a33f9dcc Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 2 Nov 2025 20:26:17 +0100 Subject: [PATCH 66/72] ui: Fix spacing in sunnylink panel (#1450) Fix spacing --- selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc index 924d1d3c09..1b170bb0c2 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc @@ -90,7 +90,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) { QString sunnylinkUploaderDesc = tr("Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)"); sunnylinkUploaderEnabledBtn = new ParamControlSP( "EnableSunnylinkUploader", - tr("Enable sunnylink uploader (just for testing infrastructure)"), + tr("Enable sunnylink uploader (infrastructure test)"), sunnylinkUploaderDesc, "", nullptr, true); list->addItem(sunnylinkUploaderEnabledBtn); From 071147baafae09c0a3f8305187596f8af16f800b Mon Sep 17 00:00:00 2001 From: Matt Purnell <65473602+mpurnell1@users.noreply.github.com> Date: Sun, 2 Nov 2025 23:52:17 -0600 Subject: [PATCH 67/72] docs: Update README installation branches and discord links (#1453) * Use sunnypilot CARS.md, update number of supported cars, add comma * Update device reference * Update discord links to forum links * Update references to -c3-new branches and release * Update broken link to branches table * Update README.md --------- Co-authored-by: DevTekVE --- README.md | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 7e686cef5c..598e1273a7 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,9 @@ ## 🌞 What is sunnypilot? [sunnypilot](https://github.com/sunnyhaibin/sunnypilot) is a fork of comma.ai's openpilot, an open source driver assistance system. sunnypilot offers the user a unique driving experience for over 300+ supported car makes and models with modified behaviors of driving assist engagements. sunnypilot complies with comma.ai's safety rules as accurately as possible. -## 💭 Join our Discord -Join the official sunnypilot Discord server to stay up to date with all the latest features and be a part of shaping the future of sunnypilot! -* https://discord.gg/sunnypilot - - ![](https://dcbadge.vercel.app/api/server/wRW3meAgtx?style=flat) ![Discord Shield](https://discordapp.com/api/guilds/880416502577266699/widget.png?style=shield) +## 💭 Join our Community Forum +Join the official sunnypilot community forum to stay up to date with all the latest features and be a part of shaping the future of sunnypilot! +* https://community.sunnypilot.ai/ ## Documentation https://docs.sunnypilot.ai/ is your one stop shop for everything from features to installation to FAQ about the sunnypilot @@ -16,13 +14,13 @@ https://docs.sunnypilot.ai/ is your one stop shop for everything from features t * A supported device to run this software * a [comma three](https://comma.ai/shop/products/three) or a [C3X](https://comma.ai/shop/comma-3x) * This software -* One of [the 300+ supported cars](https://github.com/commaai/openpilot/blob/master/docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford and more. If your car is not supported but has adaptive cruise control and lane-keeping assist, it's likely able to run sunnypilot. +* One of [the 325+ supported cars](https://github.com/sunnypilot/sunnypilot/blob/master/docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford, and more. If your car is not supported but has adaptive cruise control and lane-keeping assist, it's likely able to run sunnypilot. * A [car harness](https://comma.ai/shop/products/car-harness) to connect to your car Detailed instructions for [how to mount the device in a car](https://comma.ai/setup). ## Installation -Please refer to [Recommended Branches](#-recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `staging-c3-new` branch. +Please refer to [Recommended Branches](#recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `staging` branch. ### If you want to use our newest branches (our rewrite) > [!TIP] @@ -31,28 +29,28 @@ Please refer to [Recommended Branches](#-recommended-branches) to find your pref * sunnypilot not installed or you installed a version before 0.8.17? 1. [Factory reset/uninstall](https://github.com/commaai/openpilot/wiki/FAQ#how-can-i-reset-the-device) the previous software if you have another software/fork installed. 2. After factory reset/uninstall and upon reboot, select `Custom Software` when given the option. - 3. Input the installation URL per [Recommended Branches](#-recommended-branches). Example: ```https://staging-c3-new.sunnypilot.ai```. + 3. Input the installation URL per [Recommended Branches](#recommended-branches). Example: ```https://staging.sunnypilot.ai```. 4. Complete the rest of the installation following the onscreen instructions. * sunnypilot already installed and you installed a version after 0.8.17? - 1. On the comma three, go to `Settings` ▶️ `Software`. + 1. On the comma three/3X, go to `Settings` ▶️ `Software`. 2. At the `Download` option, press `CHECK`. This will fetch the list of latest branches from sunnypilot. 3. At the `Target Branch` option, press `SELECT` to open the Target Branch selector. - 4. Scroll to select the desired branch per Recommended Branches (see below). Example: `staging-c3-new` + 4. Scroll to select the desired branch per Recommended Branches (see below). Example: `staging` - -| Branch | Installation URL | -|:----------------:|:---------------------------------------------:| -| `staging-c3-new` | `https://staging-c3-new.sunnypilot.ai` | -| `dev-c3-new` | `https://dev-c3-new.sunnypilot.ai` | -| `custom-branch` | `https://install.sunnypilot.ai/{branch_name}` | -| `release-c3-new` | **Not yet available**. | +### Recommended Branches +| Branch | Installation URL | +|:---------------:|:---------------------------------------------:| +| `release` | `https://release.sunnypilot.ai` | +| `staging` | `https://staging.sunnypilot.ai` | +| `dev` | `https://dev.sunnypilot.ai` | +| `custom-branch` | `https://install.sunnypilot.ai/{branch_name}` | > [!TIP] -> You can use sunnypilot/targetbranch as an install URL. Example: 'sunnypilot/staging-c3-new'. +> You can use sunnypilot/targetbranch as an install URL. Example: 'sunnypilot/staging'. > [!NOTE] -> Do you require further assistance with software installation? Join the [sunnypilot Discord server](https://discord.sunnypilot.com) and message us in the `#installation-help` channel. +> Do you require further assistance with software installation? Join the [sunnypilot community forum](https://community.sunnypilot.ai/new-topic?category=general/qa) and create a topic in the General/Q&A Category channel.
From 4b5de0eddbf34080106e1ee1e8bf424aa1ce82ca Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 4 Nov 2025 22:53:31 +0100 Subject: [PATCH 68/72] stats: sunnylink integration (#1454) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * sunnylink: add statsd process and related telemetry logging infrastructure - Introduced `statsd_sp` process for handling Sunnylink-specific stats. - Enhanced metrics logging with improved directory structure and data handling. * sunnylink: re-enable and refine stat_handler for telemetry processing - Reactivated `stat_handler` thread with improved path handling. - Made `stat_handler` more flexible by allowing directory injection. * statsd: fix formatting issue in telemetry string generation - Corrected missing comma between `sunnylink_dongle_id` and `comma_dongle_id`. * update statsd_sp process configuration for enhanced readiness logic - Modified `statsd_sp` initialization to include `always_run` alongside `sunnylink_ready_shim`. - Ensures robust process activation conditions. * refactor(statsd): enhance and unify StatLogSP implementation - Replaced custom `StatLogSP` in sunnylink with centralized implementation from `system.statsd`. - Ensures consistent logic for StatLogSP handling across modules. * fix * refactor(statsd): add intercept parameter to StatLogSP for configurable logging - Introduced optional `intercept` parameter to `StatLogSP` to manage `comma_statlog` initialization. - Updated usage in `sunnylink` to disable interception where unnecessary. * Dont complain * feat(statsd): add raw metric type and SunnyPilot-specific stats collection - Introduced `METRIC_TYPE.RAW` for base64-encoded raw data metrics. - Added `sp_stats` thread to export SunnyPilot params as raw metrics. - Enhanced telemetry handling with decoding and serialization updates. * refactor(statsd): improve `sp_stats` error handling and param processing - Enhanced exception handling for `params.get` to prevent crashes. - Added support for nested dict values to be included in stats. * refactor(statsd): adjust imports and minor code formatting updates - Updated `Ratekeeper` import path for consistency with the `openpilot` module structure. - Fixed minor formatting for improved readability. * refactor(statsd): update typings and remove unused NoReturn annotation - Removed unnecessary `NoReturn` typing for `stats_main` to simplify function definition. - Adjusted `get_influxdb_line_raw` to refine typing for `value` parameter. * cleanup * init * init * slightly more * staticmethod * handle them all * get them models * log with route * more * car * Revert "car" This reverts commit fe1c90cf4d422864388e4a73d5cd25c9e585a9b3. * handle capnp * Revert "handle capnp" This reverts commit c5aea6880333863838a7c258541fb7a2812f2e2b. * 1 more time * Revert "1 more time" This reverts commit a364474fa580d9ef9760a8896f9db76d526abead. * Cleaning to expose wider * feat(interfaces, statsd): log car params to stats system - Added `STATSLOGSP` import and logging to capture `carFingerprint` in metrics. - Improved error handling in `get_influxdb_line_raw` for robust metric generation. * refactor(interfaces): streamline car params logging to stats - Simplified logging by directly converting `CP` to a dictionary. - Removed legacy stats aggregation for clarity. * feat(sunnylink): enable compression for stats in SunnyLink - Added optional compression for stats payload to support large data. - Updated `stat_handler` to handle compression and base64 encoding. * fix(statsd): filter complex types in `get_influxdb_line_raw` - Skips unsupported types (dict, list, bytes) to prevent formatting errors. - Simplifies type annotation for `value` parameter. * fix(statsd): use `json.dumps` for string conversion in `get_influxdb_line_raw` - Ensures proper handling of special characters in values. - Prevents potential formatting issues with raw `str()` conversion. * refactor(interfaces, statsd): update parameter keys for stats logging - Renamed logged keys for better clarity (`sunnypilot_params` → `sunnypilot.car_params`, `device_params`). - Ensures consistency across data logs. * bet --------- Co-authored-by: Jason Wen --- sunnypilot/selfdrive/car/interfaces.py | 5 + sunnypilot/sunnylink/athena/sunnylinkd.py | 5 +- sunnypilot/sunnylink/statsd.py | 278 ++++++++++++++++++++++ system/athena/athenad.py | 24 +- system/hardware/hw.py | 7 + system/manager/process_config.py | 1 + system/statsd.py | 59 ++++- 7 files changed, 368 insertions(+), 11 deletions(-) create mode 100755 sunnypilot/sunnylink/statsd.py diff --git a/sunnypilot/selfdrive/car/interfaces.py b/sunnypilot/selfdrive/car/interfaces.py index 59cfeabd6e..2cfbd1eec8 100644 --- a/sunnypilot/selfdrive/car/interfaces.py +++ b/sunnypilot/selfdrive/car/interfaces.py @@ -15,6 +15,8 @@ from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.helpers import set_ import openpilot.system.sentry as sentry +from sunnypilot.sunnylink.statsd import STATSLOGSP + def log_fingerprint(CP: structs.CarParams) -> None: if CP.carFingerprint == "MOCK": @@ -100,6 +102,9 @@ def setup_interfaces(CI: CarInterfaceBase, params: Params = None) -> None: _initialize_torque_lateral_control(CI, CP, enforce_torque, nnlc_enabled) _cleanup_unsupported_params(CP, CP_SP) + STATSLOGSP.raw('sunnypilot.car_params', CP.to_dict()) + # STATSLOGSP.raw('sunnypilot_params.car_params_sp', CP_SP.to_dict()) # https://github.com/sunnypilot/opendbc/pull/361 + def initialize_params(params) -> list[dict[str, Any]]: keys: list = [] diff --git a/sunnypilot/sunnylink/athena/sunnylinkd.py b/sunnypilot/sunnylink/athena/sunnylinkd.py index d1b38d656f..4c431c5d34 100755 --- a/sunnypilot/sunnylink/athena/sunnylinkd.py +++ b/sunnypilot/sunnylink/athena/sunnylinkd.py @@ -16,8 +16,9 @@ from functools import partial from openpilot.common.params import Params from openpilot.common.realtime import set_core_affinity from openpilot.common.swaglog import cloudlog +from openpilot.system.hardware.hw import Paths from openpilot.system.athena.athenad import ws_send, jsonrpc_handler, \ - recv_queue, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage, log_handler, start_local_proxy_shim, upload_handler + recv_queue, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage, log_handler, start_local_proxy_shim, upload_handler, stat_handler from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutException, create_connection, WebSocketConnectionClosedException) @@ -51,7 +52,7 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: threading.Thread(target=ws_queue, args=(end_event,), name='ws_queue'), threading.Thread(target=upload_handler, args=(end_event,), name='upload_handler'), # threading.Thread(target=sunny_log_handler, args=(end_event, comma_prime_cellular_end_event), name='log_handler'), - # threading.Thread(target=stat_handler, args=(end_event,), name='stat_handler'), + threading.Thread(target=stat_handler, args=(end_event, Paths.stats_sp_root(), True), name='stat_handler'), ] + [ threading.Thread(target=jsonrpc_handler, args=(end_event, partial(startLocalProxy, end_event),), name=f'worker_{x}') for x in range(HANDLER_THREADS) diff --git a/sunnypilot/sunnylink/statsd.py b/sunnypilot/sunnylink/statsd.py new file mode 100755 index 0000000000..eefff63516 --- /dev/null +++ b/sunnypilot/sunnylink/statsd.py @@ -0,0 +1,278 @@ +#!/usr/bin/env python3 +import base64 +import json +import os +import threading +import traceback + +import zmq +import time +import uuid +from pathlib import Path +from collections import defaultdict +from datetime import datetime, UTC + +from openpilot.common.params import Params +from cereal.messaging import SubMaster +from openpilot.system.hardware.hw import Paths +from openpilot.common.swaglog import cloudlog +from openpilot.system.hardware import HARDWARE +from openpilot.common.file_helpers import atomic_write_in_dir +from openpilot.system.version import get_build_metadata +from openpilot.system.loggerd.config import STATS_DIR_FILE_LIMIT, STATS_SOCKET, STATS_FLUSH_TIME_S +from openpilot.system.statsd import METRIC_TYPE, StatLogSP +from openpilot.common.realtime import Ratekeeper + +STATSLOGSP = StatLogSP(intercept=False) + +def sp_stats(end_event): + """Collect sunnypilot-specific statistics and send as raw metrics.""" + rk = Ratekeeper(.1, print_delay_threshold=None) + statlogsp = STATSLOGSP + params = Params() + + def flatten_dict(d, parent_key='', sep='.'): + items = {} + if isinstance(d, dict): + for k, v in d.items(): + new_key = f"{parent_key}{sep}{k}" if parent_key else k + items.update(flatten_dict(v, new_key, sep=sep)) + elif isinstance(d, (list, tuple)): + for i, v in enumerate(d): + new_key = f"{parent_key}[{i}]" + items.update(flatten_dict(v, new_key, sep=sep)) + else: + items[parent_key] = d + return items + + # Collect sunnypilot parameters + stats_dict = {} + + param_keys = [ + 'SunnylinkEnabled', + 'AutoLaneChangeBsmDelay', + 'AutoLaneChangeTimer', + 'CarPlatformBundle', + 'CurrentRoute', + 'DevUIInfo', + 'EnableCopyparty', + 'IntelligentCruiseButtonManagement', + 'QuietMode', + 'RainbowMode', + 'ShowAdvancedControls', + 'Mads', + 'MadsMainCruiseAllowed', + 'MadsSteeringMode', + 'MadsUnifiedEngagementMode', + 'ModelManager_ActiveBundle', + 'ModelManager_Favs', + 'EnableSunnylinkUploader', + 'SunnylinkEnabled', + 'InstallDate', + 'UptimeOffroad', + 'UptimeOnroad', + ] + + while not end_event.is_set(): + try: + for key in param_keys: + + try: + value = params.get(key) + except Exception as e: + stats_dict[key] = e + continue + + if value is None: + continue + + if isinstance(value, (dict, list, tuple)): + stats_dict.update(flatten_dict(value, key)) + else: + stats_dict[key] = value + + if stats_dict: + statlogsp.raw('sunnypilot.device_params', stats_dict) + except Exception as e: + cloudlog.error(f"Exception {e}") + finally: + rk.keep_time() + + +def stats_main(end_event): + comma_dongle_id = Params().get("DongleId") + sunnylink_dongle_id = Params().get("SunnylinkDongleId") + + def get_influxdb_line(measurement: str, value: float | dict[str, float], timestamp: datetime, tags: dict) -> str: + res = f"{measurement}" + for k, v in tags.items(): + res += f",{k}={str(v)}" + res += " " + + if isinstance(value, float): + value = {'value': value} + + for k, v in value.items(): + res += f"{k}={str(v)}," + + res += f"sunnylink_dongle_id=\"{sunnylink_dongle_id}\",comma_dongle_id=\"{comma_dongle_id}\" {int(timestamp.timestamp() * 1e9)}\n" + return res + + def get_influxdb_line_raw(measurement: str, value: dict, timestamp: datetime, tags: dict) -> str: + res = f"{measurement}" + try: + custom_tags = "" + for k, v in tags.items(): + custom_tags += f",{k}={str(v)}" + res += custom_tags + + fields = "" + for k, v in value.items(): + # Skip complex types - only keep simple scalar values + if isinstance(v, (dict, list, bytes, bytearray)): + continue + + fields += f"{k}={json.dumps(v)}," + + res += f" {fields}" + except Exception as e: + cloudlog.error(f"Unable to get influxdb line for: {value}") + res += f",invalid=1 reason={e}," + + res += f"sunnylink_dongle_id=\"{sunnylink_dongle_id}\",comma_dongle_id=\"{comma_dongle_id}\" {int(timestamp.timestamp() * 1e9)}\n" + return res + + # open statistics socket + ctx = zmq.Context.instance() + sock = ctx.socket(zmq.PULL) + sock.bind(f"{STATS_SOCKET}_sp") + + STATS_DIR = Paths.stats_sp_root() + + # initialize stats directory + Path(STATS_DIR).mkdir(parents=True, exist_ok=True) + + build_metadata = get_build_metadata() + + # initialize tags + tags = { + 'started': False, + 'version': build_metadata.openpilot.version, + 'branch': build_metadata.channel, + 'dirty': build_metadata.openpilot.is_dirty, + 'origin': build_metadata.openpilot.git_normalized_origin, + 'deviceType': HARDWARE.get_device_type(), + } + + # subscribe to deviceState for started state + sm = SubMaster(['deviceState']) + + idx = 0 + boot_uid = str(uuid.uuid4())[:8] + last_flush_time = time.monotonic() + gauges = {} + samples: dict[str, list[float]] = defaultdict(list) + raws: dict = defaultdict() + try: + while not end_event.is_set(): + started_prev = sm['deviceState'].started + sm.update() + + # Update metrics + while True: + try: + metric = sock.recv_string(zmq.NOBLOCK) + try: + metric_type = metric.split('|')[1] + metric_name = metric.split(':')[0] + metric_value_raw = metric.split('|')[0].split(':')[1] + + if metric_type == METRIC_TYPE.GAUGE: + metric_value = float(metric_value_raw) + gauges[metric_name] = metric_value + elif metric_type == METRIC_TYPE.SAMPLE: + metric_value = float(metric_value_raw) + samples[metric_name].append(metric_value) + elif metric_type == METRIC_TYPE.RAW: + raws[metric_name] = metric_value_raw + else: + cloudlog.event("unknown metric type", metric_type=metric_type) + except Exception: + print(traceback.format_exc()) + cloudlog.event("malformed metric", metric=metric) + except zmq.error.Again: + break + + # flush when started state changes or after FLUSH_TIME_S + if (time.monotonic() > last_flush_time + STATS_FLUSH_TIME_S) or (sm['deviceState'].started != started_prev): + result = "" + current_time = datetime.now(UTC) + tags['started'] = sm['deviceState'].started + + for key, value in raws.items(): + decoded_value = json.loads(base64.b64decode(value).decode('utf-8')) + result += get_influxdb_line_raw(key, decoded_value, current_time, tags) + + for key, value in gauges.items(): + result += get_influxdb_line(f"gauge.{key}", value, current_time, tags) + + for key, values in samples.items(): + values.sort() + sample_count = len(values) + sample_sum = sum(values) + + stats = { + 'count': sample_count, + 'min': values[0], + 'max': values[-1], + 'mean': sample_sum / sample_count, + } + for percentile in [0.05, 0.5, 0.95]: + value = values[int(round(percentile * (sample_count - 1)))] + stats[f"p{int(percentile * 100)}"] = value + + result += get_influxdb_line(f"sample.{key}", stats, current_time, tags) + + # clear intermediate data + gauges.clear() + samples.clear() + last_flush_time = time.monotonic() + + # check that we aren't filling up the drive + if len(os.listdir(STATS_DIR)) < STATS_DIR_FILE_LIMIT: + if len(result) > 0: + stats_path = os.path.join(STATS_DIR, f"{boot_uid}_{idx}") + with atomic_write_in_dir(stats_path) as f: + f.write(result) + idx += 1 + else: + cloudlog.error("stats dir full") + finally: + sock.close() + ctx.term() + + +def main(): + rk = Ratekeeper(1, print_delay_threshold=None) + end_event = threading.Event() + + threads = [ + threading.Thread(target=stats_main, args=(end_event,)), + threading.Thread(target=sp_stats, args=(end_event,)), + ] + + for t in threads: + t.start() + + try: + while all(t.is_alive() for t in threads): + rk.keep_time() + finally: + end_event.set() + + for t in threads: + t.join() + + +if __name__ == "__main__": + main() diff --git a/system/athena/athenad.py b/system/athena/athenad.py index 42c9cf8a1c..716733021a 100755 --- a/system/athena/athenad.py +++ b/system/athena/athenad.py @@ -744,26 +744,40 @@ def log_handler(end_event: threading.Event, log_attr_name=LOG_ATTR_NAME) -> None cloudlog.exception("athena.log_handler.exception") -def stat_handler(end_event: threading.Event) -> None: - STATS_DIR = Paths.stats_root() +def stat_handler(end_event: threading.Event, stats_dir=None, is_sunnylink=False) -> None: + stats_dir = stats_dir or Paths.stats_root() last_scan = 0.0 while not end_event.is_set(): curr_scan = time.monotonic() try: if curr_scan - last_scan > 10: - stat_filenames = list(filter(lambda name: not name.startswith(tempfile.gettempprefix()), os.listdir(STATS_DIR))) + stat_filenames = list(filter(lambda name: not name.startswith(tempfile.gettempprefix()), os.listdir(stats_dir))) if len(stat_filenames) > 0: - stat_path = os.path.join(STATS_DIR, stat_filenames[0]) + stat_path = os.path.join(stats_dir, stat_filenames[0]) with open(stat_path) as f: + payload = f.read() + is_compressed = False + + # Log the current size of the file + if is_sunnylink: + # Compress and encode the data if it exceeds the maximum size + compressed_data = gzip.compress(payload.encode()) + payload = base64.b64encode(compressed_data).decode() + is_compressed = True + jsonrpc = { "method": "storeStats", "params": { - "stats": f.read() + "stats": payload }, "jsonrpc": "2.0", "id": stat_filenames[0] } + + if is_sunnylink and is_compressed: + jsonrpc["params"]["compressed"] = is_compressed + low_priority_send_queue.put_nowait(json.dumps(jsonrpc)) os.remove(stat_path) last_scan = curr_scan diff --git a/system/hardware/hw.py b/system/hardware/hw.py index d24857e8bd..3527aac872 100644 --- a/system/hardware/hw.py +++ b/system/hardware/hw.py @@ -55,6 +55,13 @@ class Paths: else: return "/data/stats/" + @staticmethod + def stats_sp_root() -> str: + if PC: + return str(Path(Paths.comma_home()) / "stats") + else: + return "/data/stats_sp/" + @staticmethod def config_root() -> str: if PC: diff --git a/system/manager/process_config.py b/system/manager/process_config.py index c5f20e511b..6e52635a64 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -164,6 +164,7 @@ procs = [ # sunnylink <3 DaemonProcess("manage_sunnylinkd", "sunnypilot.sunnylink.athena.manage_sunnylinkd", "SunnylinkdPid"), PythonProcess("sunnylink_registration_manager", "sunnypilot.sunnylink.registration_manager", sunnylink_need_register_shim), + PythonProcess("statsd_sp", "sunnypilot.sunnylink.statsd", and_(always_run, sunnylink_ready_shim)), ] # sunnypilot diff --git a/system/statsd.py b/system/statsd.py index d60064fc91..89ffa0d6fc 100755 --- a/system/statsd.py +++ b/system/statsd.py @@ -1,11 +1,15 @@ #!/usr/bin/env python3 +import base64 +import json import os +from decimal import Decimal + import zmq import time import uuid from pathlib import Path from collections import defaultdict -from datetime import datetime, UTC +from datetime import datetime, UTC, date from typing import NoReturn from openpilot.common.params import Params @@ -21,18 +25,21 @@ from openpilot.system.loggerd.config import STATS_DIR_FILE_LIMIT, STATS_SOCKET, class METRIC_TYPE: GAUGE = 'g' SAMPLE = 'sa' + RAW = 'r' + class StatLog: def __init__(self): self.pid = None self.zctx = None self.sock = None + self.stats_socket = STATS_SOCKET def connect(self) -> None: - self.zctx = zmq.Context() + self.zctx = zmq.Context.instance() or zmq.Context() self.sock = self.zctx.socket(zmq.PUSH) self.sock.setsockopt(zmq.LINGER, 10) - self.sock.connect(STATS_SOCKET) + self.sock.connect(self.stats_socket) self.pid = os.getpid() def __del__(self): @@ -60,6 +67,50 @@ class StatLog: self._send(f"{name}:{value}|{METRIC_TYPE.SAMPLE}") +class StatLogSP(StatLog): + def __init__(self, intercept=True): + """ + Initializes the class instance with an optional parameter to determine + if statistical logging should be configured or not. + + :param intercept: A boolean flag that indicates whether to initialize + the `comma_statlog`. If True, the `comma_statlog` attribute is + instantiated as a `StatLog` object. Defaults to True. + """ + super().__init__() + self.comma_statlog = StatLog() if intercept else None + self.stats_socket = f"{STATS_SOCKET}_sp" + + def connect(self) -> None: + super().connect() + if self.comma_statlog: + self.comma_statlog.connect() + + def __del__(self): + super().__del__() + if self.comma_statlog: + self.comma_statlog.__del__() + + def _send(self, metric: str) -> None: + super()._send(metric) + if self.comma_statlog: + self.comma_statlog._send(metric) + + @staticmethod + def default_converter(obj): + if isinstance(obj, (datetime, date)): + return obj.isoformat() + if isinstance(obj, set): + return list(obj) + if isinstance(obj, Decimal): + return float(obj) + return str(obj) # fallback for unknown types + + def raw(self, name: str, value: dict) -> None: + encoded_dict = base64.b64encode(json.dumps(value, default=self.default_converter).encode("utf-8")).decode("utf-8") + self._send(f"{name}:{encoded_dict}|{METRIC_TYPE.RAW}") + + def main() -> NoReturn: dongle_id = Params().get("DongleId") def get_influxdb_line(measurement: str, value: float | dict[str, float], timestamp: datetime, tags: dict) -> str: @@ -180,4 +231,4 @@ def main() -> NoReturn: if __name__ == "__main__": main() else: - statlog = StatLog() + statlog = StatLogSP(intercept=True) From cde88fd8ed6dfa0f2218745acf53ff8170ffd086 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Thu, 6 Nov 2025 12:13:15 +0100 Subject: [PATCH 69/72] bug: Fix initial registration for sunnylink (#1457) refactor(sunnylink): defer `SunnylinkApi` initialization to function scope - Moved `SunnylinkApi` object creation into individual functions as needed. - Prevents unnecessary initialization when the object isn't used. --- sunnypilot/sunnylink/athena/sunnylinkd.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sunnypilot/sunnylink/athena/sunnylinkd.py b/sunnypilot/sunnylink/athena/sunnylinkd.py index 4c431c5d34..911739c312 100755 --- a/sunnypilot/sunnylink/athena/sunnylinkd.py +++ b/sunnypilot/sunnylink/athena/sunnylinkd.py @@ -34,9 +34,6 @@ SUNNYLINK_RECONNECT_TIMEOUT_S = 70 # FYI changing this will also would require DISALLOW_LOG_UPLOAD = threading.Event() params = Params() -sunnylink_dongle_id = params.get("SunnylinkDongleId") -sunnylink_api = SunnylinkApi(sunnylink_dongle_id) - def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: cloudlog.info("sunnylinkd.handle_long_poll started") @@ -133,6 +130,8 @@ def ws_ping(ws: WebSocket, end_event: threading.Event) -> None: def ws_queue(end_event: threading.Event) -> None: + sunnylink_dongle_id = params.get("SunnylinkDongleId") + sunnylink_api = SunnylinkApi(sunnylink_dongle_id) resume_requested = False tries = 0 @@ -234,6 +233,9 @@ def saveParams(params_to_update: dict[str, str], compression: bool = False) -> N def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local_port: int) -> dict[str, int]: + sunnylink_dongle_id = params.get("SunnylinkDongleId") + sunnylink_api = SunnylinkApi(sunnylink_dongle_id) + cloudlog.debug("athena.startLocalProxy.starting") ws = create_connection( remote_ws_uri, @@ -255,6 +257,8 @@ def main(exit_event: threading.Event = None): cloudlog.info("Waiting for sunnylink registration to complete") time.sleep(10) + sunnylink_dongle_id = params.get("SunnylinkDongleId") + sunnylink_api = SunnylinkApi(sunnylink_dongle_id) UploadQueueCache.initialize(upload_queue) ws_uri = f"{SUNNYLINK_ATHENA_HOST}" From 8c1d59fecd43d0e4b7d3c3742023fa0be0ff91f2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 22:47:55 -0500 Subject: [PATCH 70/72] [bot] Update Python packages (#1434) Update Python packages Co-authored-by: github-actions[bot] --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index c7126f8ba6..c32e79f3c6 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit c7126f8ba620eb06fe345013081e97503331bbe1 +Subproject commit c32e79f3c6a6170bec9f879a0ffdf901caeb4ab0 From 2ab45b552d8ceae61647f6ac64b238be1b7e5a06 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 6 Nov 2025 23:10:03 -0500 Subject: [PATCH 71/72] Update CHANGELOG.md --- CHANGELOG.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cb25994be..ea7199a854 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ -sunnypilot Version 2025.002.000 (2025-xx-xx) +sunnypilot Version 2025.002.000 (2025-11-06) ======================== +* What's Changed (sunnypilot/sunnypilot) + * models: bump model json to v8 by @Discountchubbs + * Bug: Model UI Crash Fix by @nayan8teen + * controlsd: add `CP_SP` to `get_pid_accel_limits` by @THERoenPR + * sunnylink: update uploader button logic to support novice tier and above by @devtekve + * Tesla: Coop Steering by @AmyJeanes + * ui: update discord references and add forum widget by @devtekve + * ui: Fix spacing in sunnylink panel by @devtekve + * docs: Update README installation branches and discord links by @mpurnell1 in + * stats: sunnylink integration by @devtekve + * bug: Fix initial registration for sunnylink by @devtekve +* What's Changed (sunnypilot/opendbc) + * Honda: add brake hold messages for Clarity by @mvl-boston + * interface: add `CP_SP` to `get_pid_accel_limits` method signature by @roenthomas + * Honda: use fixed accel min/max constants for Gas Interceptor by @roenthomas + * Tesla: Coop Steering by @AmyJeanes +* New Contributors (sunnypilot/sunnypilot) + * @THERoenPR made their first contribution in "controlsd: add `CP_SP` to `get_pid_accel_limits`" + * @AmyJeanes made their first contribution in "Tesla: Coop Steering" + * @mpurnell1 made their first contribution in "docs: Update README installation branches and discord links" +* Full Changelog: https://github.com/sunnypilot/sunnypilot/compare/v2025.001.000...v2025.002.000 sunnypilot Version 2025.001.000 (2025-10-25) ======================== From c1d3ae427bd773edbf3d4bd73f06ab52659e638f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 6 Nov 2025 23:12:41 -0500 Subject: [PATCH 72/72] version: bump to 2025.003.000 --- CHANGELOG.md | 3 +++ sunnypilot/common/version.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea7199a854..61bc81bafc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +sunnypilot Version 2025.003.000 (20xx-xx-xx) +======================== + sunnypilot Version 2025.002.000 (2025-11-06) ======================== * What's Changed (sunnypilot/sunnypilot) diff --git a/sunnypilot/common/version.h b/sunnypilot/common/version.h index b52d984b4d..6833a508de 100644 --- a/sunnypilot/common/version.h +++ b/sunnypilot/common/version.h @@ -1 +1 @@ -#define SUNNYPILOT_VERSION "2025.002.000" +#define SUNNYPILOT_VERSION "2025.003.000"