From 3faf709387afa0e2e632ea1047035c55c8f015fd Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Sat, 19 Jul 2025 06:17:19 -0700 Subject: [PATCH] ui: Visuals Panel Lead Chevron Info (#1033) * lead info * ui:chevron params * Update system/manager/manager.py. --------- Co-authored-by: DevTekVE --- common/params_keys.h | 1 + selfdrive/ui/qt/onroad/model.cc | 168 ++++++++++++++++++ selfdrive/ui/qt/onroad/model.h | 11 ++ .../qt/offroad/settings/visuals_panel.cc | 20 ++- .../qt/offroad/settings/visuals_panel.h | 1 + system/manager/manager.py | 3 +- 6 files changed, 201 insertions(+), 3 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index 102b31bea9..e8c155ab41 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -132,6 +132,7 @@ inline static std::unordered_map keys = { {"CarParamsSPCache", CLEAR_ON_MANAGER_START}, {"CarParamsSPPersistent", PERSISTENT}, {"CarPlatformBundle", PERSISTENT}, + {"ChevronInfo", PERSISTENT | BACKUP}, {"CustomAccIncrementsEnabled", PERSISTENT | BACKUP}, {"CustomAccLongPressIncrement", PERSISTENT | BACKUP}, {"CustomAccShortPressIncrement", PERSISTENT | BACKUP}, diff --git a/selfdrive/ui/qt/onroad/model.cc b/selfdrive/ui/qt/onroad/model.cc index dc801e591f..a1e3756cb6 100644 --- a/selfdrive/ui/qt/onroad/model.cc +++ b/selfdrive/ui/qt/onroad/model.cc @@ -34,6 +34,7 @@ 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(); } @@ -173,6 +174,173 @@ QColor ModelRenderer::blendColors(const QColor &start, const QColor &end, float (1 - t) * start.alphaF() + t * end.alphaF()); } + +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 85eb236e76..e40f832c37 100644 --- a/selfdrive/ui/qt/onroad/model.h +++ b/selfdrive/ui/qt/onroad/model.h @@ -34,6 +34,12 @@ 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); @@ -58,4 +64,9 @@ protected: QPointF lead_vertices[2] = {}; 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 0d42bd74f4..818da9b75e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc @@ -12,7 +12,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) { connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) { paramsRefresh(); }); - + main_layout = new QStackedLayout(this); ListWidgetSP *list = new ListWidgetSP(this, false); @@ -30,6 +30,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) { }, }; + // Add regular toggles first for (auto &[param, title, desc, icon, needs_restart] : toggle_defs) { auto toggle = new ParamControlSP(param, title, desc, icon, this); @@ -53,9 +54,20 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) { param_watcher->addParam(param); } + // Visuals: Display Metrics below Chevron + std::vector chevron_info_settings_texts{tr("Off"), tr("Distance"), tr("Speed"), tr("Time"), tr("All")}; + chevron_info_settings = new ButtonParamControlSP( + "ChevronInfo", tr("Display Metrics Below Chevron"), tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control)."), + "", + chevron_info_settings_texts, + 200); + chevron_info_settings->showDescription(); + list->addItem(chevron_info_settings); + param_watcher->addParam("ChevronInfo"); + sunnypilotScroller = new ScrollViewSP(list, this); vlayout->addWidget(sunnypilotScroller); - + main_layout->addWidget(sunnypilotScreen); } @@ -67,4 +79,8 @@ void VisualsPanel::paramsRefresh() { for (auto toggle : toggles) { toggle.second->refresh(); } + + if (chevron_info_settings) { + chevron_info_settings->refresh(); + } } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h index 42e0688957..f342662c22 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h @@ -27,4 +27,5 @@ protected: Params params; std::map toggles; ParamWatcher * param_watcher; + ButtonParamControlSP *chevron_info_settings; }; diff --git a/system/manager/manager.py b/system/manager/manager.py index 922640de8b..b67d592821 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -50,6 +50,8 @@ def manager_init() -> None: ("BlindSpot", "0"), ("BlinkerMinLateralControlSpeed", "20"), # MPH or km/h ("BlinkerPauseLateralControl", "0"), + ("Brightness", "0"), + ("ChevronInfo", "4"), ("CustomAccIncrementsEnabled", "0"), ("CustomAccLongPressIncrement", "5"), ("CustomAccShortPressIncrement", "1"), @@ -66,7 +68,6 @@ def manager_init() -> None: ("MadsUnifiedEngagementMode", "1"), ("MapdVersion", f"{VERSION}"), ("MaxTimeOffroad", "1800"), - ("Brightness", "0"), ("ModelManager_LastSyncTime", "0"), ("ModelManager_ModelsCache", ""), ("NeuralNetworkLateralControl", "0"),