Bench mode (--bench flag): - bench_onroad.py publishes fake vehicle state as managed process - manager blocks real car processes (pandad, thermald, controlsd, etc.) - bench_cmd.py for setting params and querying UI state - BLOCKER: UI segfaults (SIGSEGV) when OnroadWindow becomes visible without camera frames — need to make CameraWidget handle missing VisionIPC gracefully before bench drive mode works ClearPilot menu: - Replaced grid launcher with sidebar settings panel (ClearPilotPanel) - Sidebar: Home, Dashcam, Debug - Home panel: Status and System Settings buttons - Debug panel: telemetry logging toggle (ParamControl) - Tap from any view (splash, onroad) opens ClearPilotPanel - Back button returns to splash/onroad Status window: - Live system stats refreshing every 1 second - Storage, RAM, load, IP, WiFi, VPN, GPS, time, telemetry status - Tap anywhere to close, returns to previous view - Honors interactive timeout UI introspection RPC: - ZMQ REP server at ipc:///tmp/clearpilot_ui_rpc - Dumps full widget tree with visibility, geometry, stacked indices - bench_cmd dump queries it, detects crash loops via process uptime - ui_dump.py standalone tool Other: - Telemetry toggle wired to TelemetryEnabled param with disk space guard - Telemetry disabled on every manager start - Blinking red circle on onroad UI when telemetry recording - Fixed showDriverView overriding park/drive transitions every frame - Fixed offroadTransition sidebar visibility race in MainWindow - launch_openpilot.sh: cd to /data/openpilot, --bench flag support Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
291 lines
8.9 KiB
C++
Executable File
291 lines
8.9 KiB
C++
Executable File
#include "selfdrive/ui/qt/home.h"
|
|
|
|
#include <QHBoxLayout>
|
|
#include <QMouseEvent>
|
|
#include <QStackedWidget>
|
|
#include <QVBoxLayout>
|
|
|
|
#include "common/swaglog.h"
|
|
#include "selfdrive/ui/qt/util.h"
|
|
#include "selfdrive/ui/qt/widgets/scrollview.h"
|
|
|
|
// HomeWindow: the container for the offroad and onroad UIs
|
|
|
|
HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) {
|
|
// CLEARPILOT Sidebar set to invisible in drive view.
|
|
params.putBool("Sidebar", false);
|
|
|
|
QHBoxLayout *main_layout = new QHBoxLayout(this);
|
|
main_layout->setMargin(0);
|
|
main_layout->setSpacing(0);
|
|
|
|
sidebar = new Sidebar(this);
|
|
main_layout->addWidget(sidebar);
|
|
QObject::connect(sidebar, &Sidebar::openSettings, this, &HomeWindow::openSettings);
|
|
QObject::connect(sidebar, &Sidebar::openOnroad, this, &HomeWindow::showOnroad);
|
|
|
|
slayout = new QStackedLayout();
|
|
main_layout->addLayout(slayout);
|
|
|
|
home = new ClearPilotPanel(this);
|
|
QObject::connect(home, &ClearPilotPanel::openSettings, this, &HomeWindow::openSettings);
|
|
QObject::connect(home, &ClearPilotPanel::openStatus, this, &HomeWindow::openStatus);
|
|
QObject::connect(home, &ClearPilotPanel::closePanel, [=]() {
|
|
// Return to splash or onroad depending on state
|
|
if (uiState()->scene.started) {
|
|
slayout->setCurrentWidget(onroad);
|
|
} else {
|
|
slayout->setCurrentWidget(ready);
|
|
}
|
|
});
|
|
slayout->addWidget(home);
|
|
|
|
onroad = new OnroadWindow(this);
|
|
slayout->addWidget(onroad);
|
|
|
|
// CLEARPILOT
|
|
ready = new ReadyWindow(this);
|
|
slayout->addWidget(ready);
|
|
|
|
driver_view = new DriverViewWindow(this);
|
|
connect(driver_view, &DriverViewWindow::done, [=] {
|
|
showDriverView(false);
|
|
});
|
|
slayout->addWidget(driver_view);
|
|
|
|
setAttribute(Qt::WA_NoSystemBackground);
|
|
|
|
QObject::connect(uiState(), &UIState::uiUpdate, this, &HomeWindow::updateState);
|
|
QObject::connect(uiState(), &UIState::offroadTransition, this, &HomeWindow::offroadTransition);
|
|
QObject::connect(uiState(), &UIState::offroadTransition, sidebar, &Sidebar::offroadTransition);
|
|
}
|
|
|
|
// Debug function to activate onroad UI
|
|
void HomeWindow::showOnroad() {
|
|
sidebar->setVisible(false);
|
|
slayout->setCurrentWidget(onroad);
|
|
// sidebar->setVisible(params.getBool("Sidebar"));
|
|
}
|
|
|
|
void HomeWindow::showSidebar(bool show) {
|
|
sidebar->setVisible(show);
|
|
}
|
|
|
|
void HomeWindow::updateState(const UIState &s) {
|
|
if (s.scene.started) {
|
|
showDriverView(s.scene.driver_camera_timer >= 10, true);
|
|
|
|
// CLEARPILOT: show splash screen when onroad but in park
|
|
bool parked = s.scene.parked;
|
|
if (parked && !was_parked_onroad) {
|
|
LOGW("CLP UI: park transition -> showing splash");
|
|
slayout->setCurrentWidget(ready);
|
|
} else if (!parked && was_parked_onroad) {
|
|
LOGW("CLP UI: drive transition -> showing onroad");
|
|
slayout->setCurrentWidget(onroad);
|
|
}
|
|
was_parked_onroad = parked;
|
|
|
|
// CLEARPILOT: honor display on/off while showing splash in park
|
|
if (parked && ready->isVisible()) {
|
|
int screenMode = paramsMemory.getInt("ScreenDisaplayMode");
|
|
if (screenMode == 1) {
|
|
Hardware::set_display_power(false);
|
|
} else {
|
|
Hardware::set_display_power(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void HomeWindow::offroadTransition(bool offroad) {
|
|
sidebar->setVisible(false);
|
|
if (offroad) {
|
|
LOGW("CLP UI: offroad transition -> showing splash");
|
|
was_parked_onroad = false;
|
|
slayout->setCurrentWidget(ready);
|
|
} else {
|
|
// CLEARPILOT: start onroad in splash — updateState will switch to
|
|
// camera view once the car shifts out of park
|
|
LOGW("CLP UI: onroad transition -> showing splash (parked)");
|
|
was_parked_onroad = true;
|
|
slayout->setCurrentWidget(ready);
|
|
}
|
|
}
|
|
|
|
void HomeWindow::showDriverView(bool show, bool started) {
|
|
if (show) {
|
|
LOGW("CLP UI: showDriverView(true) -> driver_view");
|
|
emit closeSettings();
|
|
slayout->setCurrentWidget(driver_view);
|
|
sidebar->setVisible(false);
|
|
} else if (!started) {
|
|
// Offroad, not started — show home menu
|
|
slayout->setCurrentWidget(home);
|
|
sidebar->setVisible(false);
|
|
}
|
|
// CLEARPILOT: when started, don't touch slayout here —
|
|
// updateState handles park->splash and drive->onroad transitions
|
|
}
|
|
|
|
void HomeWindow::mousePressEvent(QMouseEvent* e) {
|
|
// CLEARPILOT: tap from any view goes to ClearPilotPanel
|
|
if (ready->isVisible() || onroad->isVisible()) {
|
|
LOGW("CLP UI: tap -> showing ClearPilotPanel");
|
|
sidebar->setVisible(false);
|
|
slayout->setCurrentWidget(home);
|
|
}
|
|
}
|
|
|
|
void HomeWindow::mouseDoubleClickEvent(QMouseEvent* e) {
|
|
HomeWindow::mousePressEvent(e);
|
|
// const SubMaster &sm = *(uiState()->sm);
|
|
}
|
|
|
|
// CLEARPILOT: ClearPilotPanel — settings-style sidebar menu
|
|
|
|
static const char *clpSidebarBtnStyle = R"(
|
|
QPushButton {
|
|
color: grey;
|
|
border: none;
|
|
background: none;
|
|
font-size: 65px;
|
|
font-weight: 500;
|
|
}
|
|
QPushButton:checked {
|
|
color: white;
|
|
}
|
|
QPushButton:pressed {
|
|
color: #ADADAD;
|
|
}
|
|
)";
|
|
|
|
ClearPilotPanel::ClearPilotPanel(QWidget* parent) : QFrame(parent) {
|
|
// Sidebar
|
|
QWidget *sidebar_widget = new QWidget;
|
|
QVBoxLayout *sidebar_layout = new QVBoxLayout(sidebar_widget);
|
|
sidebar_layout->setContentsMargins(50, 50, 100, 50);
|
|
|
|
// Close button
|
|
QPushButton *close_btn = new QPushButton("← Back");
|
|
close_btn->setStyleSheet(R"(
|
|
QPushButton {
|
|
color: white;
|
|
border-radius: 25px;
|
|
background: #292929;
|
|
font-size: 50px;
|
|
font-weight: 500;
|
|
}
|
|
QPushButton:pressed {
|
|
color: #ADADAD;
|
|
}
|
|
)");
|
|
close_btn->setFixedSize(300, 125);
|
|
sidebar_layout->addSpacing(10);
|
|
sidebar_layout->addWidget(close_btn, 0, Qt::AlignRight);
|
|
QObject::connect(close_btn, &QPushButton::clicked, [=]() { emit closePanel(); });
|
|
|
|
// Panel content area
|
|
panel_widget = new QStackedWidget();
|
|
|
|
// Define panels: sidebar label -> content widget
|
|
// Home panel: buttons for Status and System Settings
|
|
QWidget *home_panel = new QWidget(this);
|
|
QVBoxLayout *home_layout = new QVBoxLayout(home_panel);
|
|
home_layout->setContentsMargins(50, 25, 50, 25);
|
|
home_layout->setSpacing(20);
|
|
|
|
QPushButton *status_btn = new QPushButton("Status");
|
|
status_btn->setFixedHeight(120);
|
|
status_btn->setStyleSheet(R"(
|
|
QPushButton {
|
|
background-color: #393939;
|
|
color: white;
|
|
border-radius: 15px;
|
|
font-size: 50px;
|
|
font-weight: 500;
|
|
text-align: left;
|
|
padding-left: 30px;
|
|
}
|
|
QPushButton:pressed { background-color: #4a4a4a; }
|
|
)");
|
|
QObject::connect(status_btn, &QPushButton::clicked, [=]() { emit openStatus(); });
|
|
home_layout->addWidget(status_btn);
|
|
|
|
QPushButton *sysset_btn = new QPushButton("System Settings");
|
|
sysset_btn->setFixedHeight(120);
|
|
sysset_btn->setStyleSheet(status_btn->styleSheet());
|
|
QObject::connect(sysset_btn, &QPushButton::clicked, [=]() { emit openSettings(); });
|
|
home_layout->addWidget(sysset_btn);
|
|
|
|
home_layout->addStretch();
|
|
|
|
// Dashcam panel: placeholder
|
|
QWidget *dashcam_panel = new QWidget(this);
|
|
QVBoxLayout *dash_layout = new QVBoxLayout(dashcam_panel);
|
|
dash_layout->setContentsMargins(50, 25, 50, 25);
|
|
QLabel *dash_label = new QLabel("Dashcam viewer coming soon");
|
|
dash_label->setStyleSheet("color: grey; font-size: 40px;");
|
|
dash_label->setAlignment(Qt::AlignCenter);
|
|
dash_layout->addWidget(dash_label);
|
|
dash_layout->addStretch();
|
|
|
|
// Debug panel
|
|
ListWidget *debug_panel = new ListWidget(this);
|
|
debug_panel->setContentsMargins(50, 25, 50, 25);
|
|
|
|
auto *telemetry_toggle = new ParamControl("TelemetryEnabled", "Telemetry Logging",
|
|
"Record telemetry data to CSV in the session log directory. "
|
|
"Captures only changed values for efficiency.", "", this);
|
|
debug_panel->addItem(telemetry_toggle);
|
|
|
|
// Register panels with sidebar buttons
|
|
QList<QPair<QString, QWidget *>> panels = {
|
|
{"Home", home_panel},
|
|
{"Dashcam", dashcam_panel},
|
|
{"Debug", debug_panel},
|
|
};
|
|
|
|
for (auto &[name, panel] : panels) {
|
|
QPushButton *btn = new QPushButton(name);
|
|
btn->setCheckable(true);
|
|
btn->setStyleSheet(clpSidebarBtnStyle);
|
|
btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
|
sidebar_layout->addWidget(btn, 0, Qt::AlignRight);
|
|
|
|
ScrollView *panel_frame = new ScrollView(panel, this);
|
|
panel_widget->addWidget(panel_frame);
|
|
|
|
QObject::connect(btn, &QPushButton::clicked, [=, w = panel_frame]() {
|
|
btn->setChecked(true);
|
|
panel_widget->setCurrentWidget(w);
|
|
});
|
|
}
|
|
|
|
// Select Home by default
|
|
if (auto *first_btn = sidebar_widget->findChild<QPushButton *>(QString(), Qt::FindDirectChildrenOnly)) {
|
|
// Skip close_btn, find first sidebar btn
|
|
}
|
|
panel_widget->setCurrentIndex(0);
|
|
|
|
// Main layout: sidebar + panels
|
|
QHBoxLayout *main_layout = new QHBoxLayout(this);
|
|
sidebar_widget->setFixedWidth(500);
|
|
main_layout->addWidget(sidebar_widget);
|
|
main_layout->addWidget(panel_widget);
|
|
|
|
setStyleSheet(R"(
|
|
* {
|
|
color: white;
|
|
font-size: 50px;
|
|
}
|
|
ClearPilotPanel {
|
|
background-color: black;
|
|
}
|
|
QStackedWidget, ScrollView {
|
|
background-color: #292929;
|
|
border-radius: 30px;
|
|
}
|
|
)");
|
|
}
|