bench mode, ClearPilot menu, status window, UI introspection RPC

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>
This commit is contained in:
2026-04-13 02:33:33 +00:00
parent e3bdae8b5e
commit 4b0d0bb708
15 changed files with 863 additions and 94 deletions

View File

@@ -5,7 +5,9 @@
#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
@@ -25,8 +27,17 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) {
slayout = new QStackedLayout();
main_layout->addLayout(slayout);
home = new OffroadHome(this);
QObject::connect(home, &OffroadHome::openSettings, this, &HomeWindow::openSettings);
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);
@@ -67,10 +78,10 @@ void HomeWindow::updateState(const UIState &s) {
// CLEARPILOT: show splash screen when onroad but in park
bool parked = s.scene.parked;
if (parked && !was_parked_onroad) {
// just shifted into park — show splash
LOGW("CLP UI: park transition -> showing splash");
slayout->setCurrentWidget(ready);
} else if (!parked && was_parked_onroad) {
// just shifted out of park — show onroad camera
LOGW("CLP UI: drive transition -> showing onroad");
slayout->setCurrentWidget(onroad);
}
was_parked_onroad = parked;
@@ -90,11 +101,13 @@ void HomeWindow::updateState(const UIState &s) {
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);
}
@@ -102,34 +115,26 @@ void HomeWindow::offroadTransition(bool offroad) {
void HomeWindow::showDriverView(bool show, bool started) {
if (show) {
LOGW("CLP UI: showDriverView(true) -> driver_view");
emit closeSettings();
slayout->setCurrentWidget(driver_view);
sidebar->setVisible(show == false);
} else {
if (started) {
slayout->setCurrentWidget(onroad);
sidebar->setVisible(params.getBool("Sidebar"));
} else {
slayout->setCurrentWidget(home);
sidebar->setVisible(show == false);
}
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 todo - tap on main goes straight to settings
// Unless we click a debug widget.
// CLEARPILOT - click ready shows home (no sidebar)
if (!onroad->isVisible() && ready->isVisible()) {
// 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);
}
// Todo: widgets
if (onroad->isVisible()) {
emit openSettings();
}
}
void HomeWindow::mouseDoubleClickEvent(QMouseEvent* e) {
@@ -137,59 +142,149 @@ void HomeWindow::mouseDoubleClickEvent(QMouseEvent* e) {
// const SubMaster &sm = *(uiState()->sm);
}
// CLEARPILOT: OffroadHome — clean grid launcher
// CLEARPILOT: ClearPilotPanel — settings-style sidebar menu
OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
QVBoxLayout* main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(80, 80, 80, 80);
main_layout->setSpacing(0);
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;
}
)";
// grid of launcher buttons
QGridLayout *grid = new QGridLayout();
grid->setSpacing(40);
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);
// Dashcam viewer button
QPushButton *dashcam_btn = new QPushButton("Dashcam");
dashcam_btn->setFixedSize(400, 300);
dashcam_btn->setStyleSheet(R"(
// Close button
QPushButton *close_btn = new QPushButton("← Back");
close_btn->setStyleSheet(R"(
QPushButton {
background-color: #333333;
color: white;
border-radius: 20px;
font-size: 48px;
font-weight: 600;
border-radius: 25px;
background: #292929;
font-size: 50px;
font-weight: 500;
}
QPushButton:pressed {
background-color: #555555;
color: #ADADAD;
}
)");
grid->addWidget(dashcam_btn, 0, 0);
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(); });
// Settings button
QPushButton *settings_btn = new QPushButton("Settings");
settings_btn->setFixedSize(400, 300);
settings_btn->setStyleSheet(R"(
// 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: #333333;
background-color: #393939;
color: white;
border-radius: 20px;
font-size: 48px;
font-weight: 600;
}
QPushButton:pressed {
background-color: #555555;
border-radius: 15px;
font-size: 50px;
font-weight: 500;
text-align: left;
padding-left: 30px;
}
QPushButton:pressed { background-color: #4a4a4a; }
)");
QObject::connect(settings_btn, &QPushButton::clicked, [=]() { emit openSettings(); });
grid->addWidget(settings_btn, 0, 1);
QObject::connect(status_btn, &QPushButton::clicked, [=]() { emit openStatus(); });
home_layout->addWidget(status_btn);
main_layout->addStretch();
main_layout->addLayout(grid);
main_layout->addStretch();
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"(
OffroadHome {
* {
color: white;
font-size: 50px;
}
ClearPilotPanel {
background-color: black;
}
QStackedWidget, ScrollView {
background-color: #292929;
border-radius: 30px;
}
)");
}