From 1e5758e7126b528741ce1c9d98b36065590bc8e5 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sun, 12 Oct 2025 00:21:38 -0400 Subject: [PATCH 01/30] 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 02/30] 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 03/30] 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 04/30] 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 05/30] 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 06/30] 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 07/30] 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 08/30] 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 09/30] 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 10/30] 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 11/30] 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 12/30] 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 13/30] 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 14/30] 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 15/30] 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 16/30] 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 17/30] 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 18/30] 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 19/30] 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 20/30] 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 21/30] 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 22/30] 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 23/30] 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 24/30] 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 25/30] 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 26/30] 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 27/30] 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 28/30] 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 29/30] 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 30/30] 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: