diff --git a/common/params.cc b/common/params.cc index a8eefa1..5f1f906 100644 --- a/common/params.cc +++ b/common/params.cc @@ -251,6 +251,7 @@ std::unordered_map keys = { {"DisableOpenpilotLongitudinal", PERSISTENT}, {"DisengageVolume", PERSISTENT}, {"DragonPilotTune", PERSISTENT}, + {"DriverCamera", PERSISTENT}, {"DriveStats", PERSISTENT}, {"DynamicPathWidth", PERSISTENT}, {"EngageVolume", PERSISTENT}, diff --git a/selfdrive/frogpilot/ui/visual_settings.cc b/selfdrive/frogpilot/ui/visual_settings.cc index 1900145..447c933 100644 --- a/selfdrive/frogpilot/ui/visual_settings.cc +++ b/selfdrive/frogpilot/ui/visual_settings.cc @@ -27,6 +27,8 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilot {"BlindSpotPath", "Blind Spot Path", "Visualize your blind spots with a red path when another vehicle is detected nearby.", ""}, {"LeadInfo", "Lead Info and Logics", "Get detailed information about the vehicle ahead, including speed and distance, and the logic behind your following distance.", ""}, + {"DriverCamera", "Driver Camera On Reverse", "Show the driver's camera feed when you shift to reverse.", "../assets/img_driver_face_static.png"}, + {"ModelUI", "Model UI", "Personalize how the model's visualizations appear on your screen.", "../assets/offroad/icon_calibration.png"}, {"DynamicPathWidth", "Dynamic Path Width", "Have the path width dynamically adjust based on the current engagement state of openpilot.", ""}, {"LaneLinesWidth", "Lane Lines", "Adjust the visual thickness of lane lines on your display.\n\nDefault matches the MUTCD average of 4 inches.", ""}, diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index a94e91f..ccb7de7 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -163,7 +163,7 @@ void OnroadAlerts::updateAlert(const Alert &a) { } void OnroadAlerts::paintEvent(QPaintEvent *event) { - if (alert.size == cereal::ControlsState::AlertSize::NONE) { + if (alert.size == cereal::ControlsState::AlertSize::NONE || scene.show_driver_camera && alert.status != cereal::ControlsState::AlertStatus::CRITICAL) { return; } static std::map alert_heights = { @@ -256,7 +256,10 @@ void ExperimentalButton::updateState(const UIState &s, bool leadInfo) { void ExperimentalButton::paintEvent(QPaintEvent *event) { QPainter p(this); QPixmap img = experimental_mode ? experimental_img : engage_img; - drawIcon(p, QPoint(btn_size / 2, btn_size / 2 + y_offset), img, QColor(0, 0, 0, 166), (isDown() || !(engageable || scene.always_on_lateral_active)) ? 0.6 : 1.0); + + if (!scene.show_driver_camera) { + drawIcon(p, QPoint(btn_size / 2, btn_size / 2 + y_offset), img, QColor(0, 0, 0, 166), (isDown() || !(engageable || scene.always_on_lateral_active)) ? 0.6 : 1.0); + } } @@ -328,7 +331,7 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { has_eu_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA); is_metric = s.scene.is_metric; speedUnit = s.scene.is_metric ? tr("km/h") : tr("mph"); - hideBottomIcons = (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE || customSignals && (turnSignalLeft || turnSignalRight)); + hideBottomIcons = (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE || customSignals && (turnSignalLeft || turnSignalRight)) || showDriverCamera; status = s.status; // update engageability/experimental mode button @@ -361,87 +364,91 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { QString speedStr = QString::number(std::nearbyint(speed)); QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(setSpeed)) : "–"; - // Draw outer box + border to contain set speed and speed limit - const int sign_margin = 12; - const int us_sign_height = 186; - const int eu_sign_size = 176; + if (!showDriverCamera) { + // Draw outer box + border to contain set speed and speed limit + const int sign_margin = 12; + const int us_sign_height = 186; + const int eu_sign_size = 176; - const QSize default_size = {172, 204}; - QSize set_speed_size = default_size; - if (is_metric || has_eu_speed_limit) set_speed_size.rwidth() = 200; - if (has_us_speed_limit && speedLimitStr.size() >= 3) set_speed_size.rwidth() = 223; + const QSize default_size = {172, 204}; + QSize set_speed_size = default_size; + if (is_metric || has_eu_speed_limit) set_speed_size.rwidth() = 200; + if (has_us_speed_limit && speedLimitStr.size() >= 3) set_speed_size.rwidth() = 223; - if (has_us_speed_limit) set_speed_size.rheight() += us_sign_height + sign_margin; - else if (has_eu_speed_limit) set_speed_size.rheight() += eu_sign_size + sign_margin; + if (has_us_speed_limit) set_speed_size.rheight() += us_sign_height + sign_margin; + else if (has_eu_speed_limit) set_speed_size.rheight() += eu_sign_size + sign_margin; - int top_radius = 32; - int bottom_radius = has_eu_speed_limit ? 100 : 32; + int top_radius = 32; + int bottom_radius = has_eu_speed_limit ? 100 : 32; - QRect set_speed_rect(QPoint(60 + (default_size.width() - set_speed_size.width()) / 2, 45), set_speed_size); - p.setPen(QPen(whiteColor(75), 6)); - p.setBrush(blackColor(166)); - drawRoundedRect(p, set_speed_rect, top_radius, top_radius, bottom_radius, bottom_radius); + QRect set_speed_rect(QPoint(60 + (default_size.width() - set_speed_size.width()) / 2, 45), set_speed_size); + p.setPen(QPen(whiteColor(75), 6)); + p.setBrush(blackColor(166)); + drawRoundedRect(p, set_speed_rect, top_radius, top_radius, bottom_radius, bottom_radius); - // Draw MAX - QColor max_color = QColor(0x80, 0xd8, 0xa6, 0xff); - QColor set_speed_color = whiteColor(); - if (is_cruise_set) { - if (status == STATUS_DISENGAGED) { - max_color = whiteColor(); - } else if (status == STATUS_OVERRIDE) { - max_color = QColor(0x91, 0x9b, 0x95, 0xff); - } else if (speedLimit > 0) { - auto interp_color = [=](QColor c1, QColor c2, QColor c3) { - return speedLimit > 0 ? interpColor(setSpeed, {speedLimit + 5, speedLimit + 15, speedLimit + 25}, {c1, c2, c3}) : c1; - }; - max_color = interp_color(max_color, QColor(0xff, 0xe4, 0xbf), QColor(0xff, 0xbf, 0xbf)); - set_speed_color = interp_color(set_speed_color, QColor(0xff, 0x95, 0x00), QColor(0xff, 0x00, 0x00)); + // Draw MAX + QColor max_color = QColor(0x80, 0xd8, 0xa6, 0xff); + QColor set_speed_color = whiteColor(); + if (is_cruise_set) { + if (status == STATUS_DISENGAGED) { + max_color = whiteColor(); + } else if (status == STATUS_OVERRIDE) { + max_color = QColor(0x91, 0x9b, 0x95, 0xff); + } else if (speedLimit > 0) { + auto interp_color = [=](QColor c1, QColor c2, QColor c3) { + return speedLimit > 0 ? interpColor(setSpeed, {speedLimit + 5, speedLimit + 15, speedLimit + 25}, {c1, c2, c3}) : c1; + }; + max_color = interp_color(max_color, QColor(0xff, 0xe4, 0xbf), QColor(0xff, 0xbf, 0xbf)); + set_speed_color = interp_color(set_speed_color, QColor(0xff, 0x95, 0x00), QColor(0xff, 0x00, 0x00)); + } + } else { + max_color = QColor(0xa6, 0xa6, 0xa6, 0xff); + set_speed_color = QColor(0x72, 0x72, 0x72, 0xff); } - } else { - max_color = QColor(0xa6, 0xa6, 0xa6, 0xff); - set_speed_color = QColor(0x72, 0x72, 0x72, 0xff); - } - p.setFont(InterFont(40, QFont::DemiBold)); - p.setPen(max_color); - p.drawText(set_speed_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("MAX")); - p.setFont(InterFont(90, QFont::Bold)); - p.setPen(set_speed_color); - p.drawText(set_speed_rect.adjusted(0, 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, setSpeedStr); + p.setFont(InterFont(40, QFont::DemiBold)); + p.setPen(max_color); + p.drawText(set_speed_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("MAX")); + p.setFont(InterFont(90, QFont::Bold)); + p.setPen(set_speed_color); + p.drawText(set_speed_rect.adjusted(0, 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, setSpeedStr); - const QRect sign_rect = set_speed_rect.adjusted(sign_margin, default_size.height(), -sign_margin, -sign_margin); - // US/Canada (MUTCD style) sign - if (has_us_speed_limit) { - p.setPen(Qt::NoPen); - p.setBrush(whiteColor()); - p.drawRoundedRect(sign_rect, 24, 24); - p.setPen(QPen(blackColor(), 6)); - p.drawRoundedRect(sign_rect.adjusted(9, 9, -9, -9), 16, 16); + const QRect sign_rect = set_speed_rect.adjusted(sign_margin, default_size.height(), -sign_margin, -sign_margin); + // US/Canada (MUTCD style) sign + if (has_us_speed_limit) { + p.setPen(Qt::NoPen); + p.setBrush(whiteColor()); + p.drawRoundedRect(sign_rect, 24, 24); + p.setPen(QPen(blackColor(), 6)); + p.drawRoundedRect(sign_rect.adjusted(9, 9, -9, -9), 16, 16); - p.setFont(InterFont(28, QFont::DemiBold)); - p.drawText(sign_rect.adjusted(0, 22, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("SPEED")); - p.drawText(sign_rect.adjusted(0, 51, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("LIMIT")); - p.setFont(InterFont(70, QFont::Bold)); - p.drawText(sign_rect.adjusted(0, 85, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitStr); - } + p.setFont(InterFont(28, QFont::DemiBold)); + p.drawText(sign_rect.adjusted(0, 22, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("SPEED")); + p.drawText(sign_rect.adjusted(0, 51, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("LIMIT")); + p.setFont(InterFont(70, QFont::Bold)); + p.drawText(sign_rect.adjusted(0, 85, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitStr); + } - // EU (Vienna style) sign - if (has_eu_speed_limit) { - p.setPen(Qt::NoPen); - p.setBrush(whiteColor()); - p.drawEllipse(sign_rect); - p.setPen(QPen(Qt::red, 20)); - p.drawEllipse(sign_rect.adjusted(16, 16, -16, -16)); + // EU (Vienna style) sign + if (has_eu_speed_limit) { + p.setPen(Qt::NoPen); + p.setBrush(whiteColor()); + p.drawEllipse(sign_rect); + p.setPen(QPen(Qt::red, 20)); + p.drawEllipse(sign_rect.adjusted(16, 16, -16, -16)); - p.setFont(InterFont((speedLimitStr.size() >= 3) ? 60 : 70, QFont::Bold)); - p.setPen(blackColor()); - p.drawText(sign_rect, Qt::AlignCenter, speedLimitStr); + p.setFont(InterFont((speedLimitStr.size() >= 3) ? 60 : 70, QFont::Bold)); + p.setPen(blackColor()); + p.drawText(sign_rect, Qt::AlignCenter, speedLimitStr); + } } // current speed - p.setFont(InterFont(176, QFont::Bold)); - drawText(p, rect().center().x(), 210, speedStr); - p.setFont(InterFont(66)); - drawText(p, rect().center().x(), 290, speedUnit, 200); + if (!showDriverCamera) { + p.setFont(InterFont(176, QFont::Bold)); + drawText(p, rect().center().x(), 210, speedStr); + p.setFont(InterFont(66)); + drawText(p, rect().center().x(), 290, speedUnit, 200); + } p.restore(); } @@ -807,7 +814,7 @@ void AnnotatedCameraWidget::paintGL() { // for replay of old routes, never go to widecam wide_cam_requested = wide_cam_requested && s->scene.calibration_wide_valid; } - CameraWidget::setStreamType(cameraView == 3 ? VISION_STREAM_DRIVER : + CameraWidget::setStreamType(cameraView == 3 || showDriverCamera ? VISION_STREAM_DRIVER : cameraView == 2 || wide_cam_requested ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD); @@ -826,7 +833,7 @@ void AnnotatedCameraWidget::paintGL() { painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::NoPen); - if (s->scene.world_objects_visible) { + if (s->scene.world_objects_visible && !showDriverCamera) { update_model(s, model, sm["uiPlan"].getUiPlan()); drawLaneLines(painter, s); @@ -936,24 +943,28 @@ void AnnotatedCameraWidget::updateFrogPilotWidgets(QPainter &p) { mapOpen = scene.map_open; + showDriverCamera = scene.show_driver_camera; + turnSignalLeft = scene.turn_signal_left; turnSignalRight = scene.turn_signal_right; - if (leadInfo) { - drawLeadInfo(p); - } - - if (alwaysOnLateral || conditionalExperimental) { - drawStatusBar(p); - } - - if (customSignals && (turnSignalLeft || turnSignalRight)) { - if (!animationTimer->isActive()) { - animationTimer->start(totalFrames * 11); // 440 milliseconds per loop; syncs up perfectly with my 2019 Lexus ES 350 turn signal clicks + if (!showDriverCamera) { + if (leadInfo) { + drawLeadInfo(p); + } + + if (alwaysOnLateral || conditionalExperimental) { + drawStatusBar(p); + } + + if (customSignals && (turnSignalLeft || turnSignalRight)) { + if (!animationTimer->isActive()) { + animationTimer->start(totalFrames * 11); // 440 milliseconds per loop; syncs up perfectly with my 2019 Lexus ES 350 turn signal clicks + } + drawTurnSignals(p); + } else if (animationTimer->isActive()) { + animationTimer->stop(); } - drawTurnSignals(p); - } else if (animationTimer->isActive()) { - animationTimer->stop(); } map_settings_btn_bottom->setEnabled(map_settings_btn->isEnabled()); diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index 94f2d3a..10656c2 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -130,6 +130,7 @@ private: bool experimentalMode; bool leadInfo; bool mapOpen; + bool showDriverCamera; bool turnSignalLeft; bool turnSignalRight; diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index 7b1f2f1..956b953 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -217,6 +217,9 @@ void CameraWidget::updateFrameMat() { if (active_stream_type == VISION_STREAM_DRIVER) { if (stream_width > 0 && stream_height > 0) { frame_mat = get_driver_view_transform(w, h, stream_width, stream_height); + if (uiState()->scene.show_driver_camera) { + frame_mat.v[0] *= -1.0; + } } } else { // Project point at "infinity" to compute x and y offsets diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index cca56b2..9f3a39f 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -228,6 +228,9 @@ static void update_state(UIState *s) { scene.turn_signal_left = carState.getLeftBlinker(); scene.turn_signal_right = carState.getRightBlinker(); } + if (scene.driver_camera) { + scene.show_driver_camera = carState.getGearShifter() == cereal::CarState::GearShifter::REVERSE; + } } if (sm.updated("controlsState")) { auto controlsState = sm["controlsState"].getControlsState(); @@ -302,6 +305,8 @@ void ui_update_frogpilot_params(UIState *s) { scene.custom_icons = custom_theme ? params.getInt("CustomIcons") : 0; scene.custom_signals = custom_theme ? params.getInt("CustomSignals") : 0; + scene.driver_camera = params.getBool("DriverCamera"); + scene.model_ui = params.getBool("ModelUI"); scene.dynamic_path_width = scene.model_ui && params.getBool("DynamicPathWidth"); scene.lane_line_width = params.getInt("LaneLinesWidth") * (scene.is_metric ? 1.0f : INCH_TO_CM) / 200.0f; diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 0a70d34..c066723 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -178,12 +178,14 @@ typedef struct UIScene { bool blind_spot_path; bool blind_spot_right; bool conditional_experimental; + bool driver_camera; bool dynamic_path_width; bool enabled; bool experimental_mode; bool lead_info; bool map_open; bool model_ui; + bool show_driver_camera; bool turn_signal_left; bool turn_signal_right; bool unlimited_road_ui_length;