UI: Visual indicator for blind spot (#834)

* Refactor and extend ModelRenderer for custom Sunnypilot logic

Refactored `ModelRenderer` to `ModelRendererSP` with enhanced features such as lane line updates, path drawing, and lead management for Sunnypilot. Introduced new methods for model updates, lead drawing, and improved path rendering with experimental mode support. Ensured compatibility by integrating with Sunnypilot-specific HUD and camera components.

* Update selfdrive/ui/sunnypilot/qt/onroad/model.cc

* Refactor `ModelRenderer` for modularity

Moved constants and `get_path_length_idx` function to the header file for reuse and clarity. Updated `drawPath` and related methods to better handle surface dimensions, improving rendering flexibility. Made key functions virtual to allow further customization in derived classes.

* Cleaning logic on ModelRenderSP

Given that we've refactored slightly the original ModelRender, we no longer need to duplicate the logic on our own implementation

* Enable blind spot detection and visualization.

Added support for blind spot warnings, including gradient-colored visualizations for left and right blind spots on the on-road UI. Introduced a new "BlindSpot" parameter with related logic for detection and rendering, as well as a settings option for user toggling.

* Cleanup format

Clean

Cleanup and fixes

* Let's backup the BlindSpot setting

* add false for restart-needed

* Add blind spot warning toggle to VisualsPanel

Moved blind spot warning toggle from settings.cc to VisualsPanel and implemented support for dynamic parameter updates. This change introduces a dedicated layout for managing visual settings and improves modularity in the settings interface.

* Update Blind Spot Warnings setting description

Clarified the description to specify that warnings are displayed only if the car supports Blind Spot Monitoring (BSM). This ensures better user understanding of the feature's requirements.

* Avoid diff on settings.cc

* More cleanup

---------

Co-authored-by: DevTekVE <devtekve@gmail.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
Co-authored-by: Nayan <nayan8teen@gmail.com>
This commit is contained in:
Kumar
2025-06-06 13:39:26 -07:00
committed by GitHub
parent debba0c9f9
commit 1979b00cc0
6 changed files with 123 additions and 0 deletions
+1
View File
@@ -171,6 +171,7 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
{"HyundaiLongitudinalTuning", PERSISTENT},
{"DynamicExperimentalControl", PERSISTENT},
{"BlindSpot", PERSISTENT | BACKUP},
// model panel params
{"LagdToggle", PERSISTENT | BACKUP},
@@ -8,5 +8,63 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h"
VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
param_watcher = new ParamWatcher(this);
connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString &param_name, const QString &param_value) {
paramsRefresh();
});
main_layout = new QStackedLayout(this);
ListWidgetSP *list = new ListWidgetSP(this, false);
sunnypilotScreen = new QWidget(this);
QVBoxLayout* vlayout = new QVBoxLayout(sunnypilotScreen);
vlayout->setContentsMargins(50, 20, 50, 20);
std::vector<std::tuple<QString, QString, QString, QString, bool> > toggle_defs{
{
"BlindSpot",
tr("Show Blind Spot Warnings"),
tr("Enabling this will display warnings when a vehicle is detected in your blind spot as long as your car has BSM supported."),
"../assets/offroad/icon_monitoring.png",
false,
},
};
for (auto &[param, title, desc, icon, needs_restart] : toggle_defs) {
auto toggle = new ParamControlSP(param, title, desc, icon, this);
bool locked = params.getBool((param + "Lock").toStdString());
toggle->setEnabled(!locked);
if (needs_restart && !locked) {
toggle->setDescription(toggle->getDescription() + tr(" Changing this setting will restart openpilot if the car is powered on."));
QObject::connect(uiState(), &UIState::engagedChanged, [toggle](bool engaged) {
toggle->setEnabled(!engaged);
});
QObject::connect(toggle, &ParamControlSP::toggleFlipped, [=](bool state) {
params.putBool("OnroadCycleRequested", true);
});
}
list->addItem(toggle);
toggles[param.toStdString()] = toggle;
param_watcher->addParam(param);
}
sunnypilotScroller = new ScrollViewSP(list, this);
vlayout->addWidget(sunnypilotScroller);
main_layout->addWidget(sunnypilotScreen);
}
void VisualsPanel::paramsRefresh() {
if (!isVisible()) {
return;
}
for (auto toggle : toggles) {
toggle.second->refresh();
}
}
@@ -8,6 +8,9 @@
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
class ScrollViewSP;
class VisualsPanel : public QWidget {
Q_OBJECT
@@ -15,4 +18,13 @@ class VisualsPanel : public QWidget {
public:
explicit VisualsPanel(QWidget *parent = nullptr);
void paramsRefresh();
protected:
QStackedLayout* main_layout = nullptr;
QWidget* sunnypilotScreen = nullptr;
ScrollViewSP *sunnypilotScroller = nullptr;
Params params;
std::map<std::string, ParamControlSP*> toggles;
ParamWatcher * param_watcher;
};
@@ -6,3 +6,47 @@
*/
#include "selfdrive/ui/sunnypilot/qt/onroad/model.h"
void ModelRendererSP::update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead) {
ModelRenderer::update_model(model, lead);
const auto &model_position = model.getPosition();
const auto &lane_lines = model.getLaneLines();
float max_distance = std::clamp(*(model_position.getX().end() - 1), MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE);
int max_idx = get_path_length_idx(lane_lines[0], max_distance);
// update blindspot vertices
float max_distance_barrier = 100;
int max_idx_barrier = std::min(max_idx, get_path_length_idx(lane_lines[0], max_distance_barrier));
mapLineToPolygon(model.getLaneLines()[1], 0.2, -0.05, &left_blindspot_vertices, max_idx_barrier);
mapLineToPolygon(model.getLaneLines()[2], 0.2, -0.05, &right_blindspot_vertices, max_idx_barrier);
}
void ModelRendererSP::drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &surface_rect) {
auto *s = uiState();
auto &sm = *(s->sm);
bool blindspot = Params().getBool("BlindSpot");
if (blindspot) {
bool left_blindspot = sm["carState"].getCarState().getLeftBlindspot();
bool right_blindspot = sm["carState"].getCarState().getRightBlindspot();
//painter.setBrush(QColor::fromRgbF(1.0, 0.0, 0.0, 0.4)); // Red with alpha for blind spot
if (left_blindspot && !left_blindspot_vertices.isEmpty()) {
QLinearGradient gradient(0, 0, surface_rect.width(), 0); // Horizontal gradient from left to right
gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha
gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha
painter.setBrush(gradient);
painter.drawPolygon(left_blindspot_vertices);
}
if (right_blindspot && !right_blindspot_vertices.isEmpty()) {
QLinearGradient gradient(surface_rect.width(), 0, 0, 0); // Horizontal gradient from right to left
gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha
gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha
painter.setBrush(gradient);
painter.drawPolygon(right_blindspot_vertices);
}
}
ModelRenderer::drawPath(painter, model, surface_rect.height());
}
@@ -12,4 +12,11 @@
class ModelRendererSP : public ModelRenderer {
public:
ModelRendererSP() = default;
private:
void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead) override;
void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &rect) override;
QPolygonF left_blindspot_vertices;
QPolygonF right_blindspot_vertices;
};
+1
View File
@@ -44,6 +44,7 @@ def manager_init() -> None:
sunnypilot_default_params: list[tuple[str, str | bytes]] = [
("AutoLaneChangeTimer", "0"),
("AutoLaneChangeBsmDelay", "0"),
("BlindSpot", "0"),
("BlinkerMinLateralControlSpeed", "20"), # MPH or km/h
("BlinkerPauseLateralControl", "0"),
("DynamicExperimentalControl", "0"),