diff --git a/build_only.sh b/build_only.sh index a0113e0..8467c5f 100755 --- a/build_only.sh +++ b/build_only.sh @@ -7,6 +7,9 @@ BASEDIR="/data/openpilot" +# Fix ownership — we edit as root, openpilot builds/runs as comma +sudo chown -R comma:comma "$BASEDIR" + # Kill stale error displays and any running manager/launch/managed processes pkill -9 -f "selfdrive/ui/text" 2>/dev/null pkill -9 -f 'launch_openpilot.sh' 2>/dev/null diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index b3f957f..0c0b2fc 100755 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -6,8 +6,14 @@ #include #include "common/swaglog.h" +#include "common/params.h" +#include "system/hardware/hw.h" #include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/qt/widgets/input.h" #include "selfdrive/ui/qt/widgets/scrollview.h" +#include "selfdrive/ui/qt/network/networking.h" +#include "cereal/messaging/messaging.h" // HomeWindow: the container for the offroad and onroad UIs @@ -46,7 +52,7 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) { // CLEARPILOT ready = new ReadyWindow(this); slayout->addWidget(ready); - + driver_view = new DriverViewWindow(this); connect(driver_view, &DriverViewWindow::done, [=] { showDriverView(false); @@ -160,6 +166,27 @@ static const char *clpSidebarBtnStyle = R"( } )"; +static const char *clpActionBtnStyle = 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; } +)"; + +// Shutdown timer: param value -> display label +static QString shutdownLabel(int val) { + if (val == 0) return "5 mins"; + if (val <= 3) return QString::number(val * 15) + " mins"; + int hours = val - 3; + return QString::number(hours) + (hours == 1 ? " hour" : " hours"); +} + ClearPilotPanel::ClearPilotPanel(QWidget* parent) : QFrame(parent) { // Sidebar QWidget *sidebar_widget = new QWidget; @@ -188,39 +215,137 @@ ClearPilotPanel::ClearPilotPanel(QWidget* parent) : QFrame(parent) { // 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); + // ── General panel ── + ListWidget *general_panel = new ListWidget(this); + general_panel->setContentsMargins(50, 25, 50, 25); + // Status button 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; } - )"); + status_btn->setStyleSheet(clpActionBtnStyle); QObject::connect(status_btn, &QPushButton::clicked, [=]() { emit openStatus(); }); - home_layout->addWidget(status_btn); + general_panel->addItem(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); + // Reset Calibration + auto resetCalibBtn = new ButtonControl("Reset Calibration", "RESET", ""); + connect(resetCalibBtn, &ButtonControl::showDescriptionEvent, [=]() { + QString desc = "openpilot requires the device to be mounted within 4° left or right and " + "within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required."; + std::string calib_bytes = Params().get("CalibrationParams"); + if (!calib_bytes.empty()) { + try { + AlignedBuffer aligned_buf; + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(calib_bytes.data(), calib_bytes.size())); + auto calib = cmsg.getRoot().getLiveCalibration(); + if (calib.getCalStatus() != cereal::LiveCalibrationData::Status::UNCALIBRATED) { + double pitch = calib.getRpyCalib()[1] * (180 / M_PI); + double yaw = calib.getRpyCalib()[2] * (180 / M_PI); + desc += QString(" Your device is pointed %1° %2 and %3° %4.") + .arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? "down" : "up", + QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? "left" : "right"); + } + } catch (...) { + qInfo() << "invalid CalibrationParams"; + } + } + qobject_cast(sender())->setDescription(desc); + }); + connect(resetCalibBtn, &ButtonControl::clicked, [=]() { + if (ConfirmationDialog::confirm("Are you sure you want to reset calibration?", "Reset", this)) { + Params().remove("CalibrationParams"); + Params().remove("LiveTorqueParameters"); + } + }); + general_panel->addItem(resetCalibBtn); - home_layout->addStretch(); + // Shutdown Timer + int cur_shutdown = Params().getInt("DeviceShutdown"); + auto shutdownBtn = new ButtonControl("Shutdown Timer", shutdownLabel(cur_shutdown), + "How long the device stays on after the car is turned off."); + connect(shutdownBtn, &ButtonControl::clicked, [=]() { + QStringList options; + for (int i = 0; i <= 33; i++) { + options << shutdownLabel(i); + } + int current = Params().getInt("DeviceShutdown"); + QString sel = MultiOptionDialog::getSelection("Shutdown Timer", options, shutdownLabel(current), this); + if (!sel.isEmpty()) { + int idx = options.indexOf(sel); + if (idx >= 0) { + Params().putInt("DeviceShutdown", idx); + shutdownBtn->setValue(shutdownLabel(idx)); + } + } + }); + general_panel->addItem(shutdownBtn); - // Dashcam panel: placeholder + // Power buttons + QHBoxLayout *power_layout = new QHBoxLayout(); + power_layout->setSpacing(30); + + QPushButton *reboot_btn = new QPushButton("Reboot"); + reboot_btn->setObjectName("reboot_btn"); + power_layout->addWidget(reboot_btn); + + QPushButton *softreboot_btn = new QPushButton("Soft Reboot"); + softreboot_btn->setObjectName("softreboot_btn"); + power_layout->addWidget(softreboot_btn); + + QPushButton *poweroff_btn = new QPushButton("Power Off"); + poweroff_btn->setObjectName("poweroff_btn"); + power_layout->addWidget(poweroff_btn); + + QObject::connect(reboot_btn, &QPushButton::clicked, [=]() { + if (!uiState()->engaged()) { + if (ConfirmationDialog::confirm("Are you sure you want to reboot?", "Reboot", this)) { + if (!uiState()->engaged()) { + Params().putBool("DoReboot", true); + } + } + } else { + ConfirmationDialog::alert("Disengage to Reboot", this); + } + }); + + QObject::connect(softreboot_btn, &QPushButton::clicked, [=]() { + if (!uiState()->engaged()) { + if (ConfirmationDialog::confirm("Are you sure you want to soft reboot?", "Soft Reboot", this)) { + if (!uiState()->engaged()) { + Params().putBool("DoSoftReboot", true); + } + } + } else { + ConfirmationDialog::alert("Disengage to Soft Reboot", this); + } + }); + + QObject::connect(poweroff_btn, &QPushButton::clicked, [=]() { + if (!uiState()->engaged()) { + if (ConfirmationDialog::confirm("Are you sure you want to power off?", "Power Off", this)) { + if (!uiState()->engaged()) { + Params().putBool("DoShutdown", true); + } + } + } else { + ConfirmationDialog::alert("Disengage to Power Off", this); + } + }); + + general_panel->addItem(power_layout); + + // ── Network panel ── + Networking *network_panel = new Networking(this); + // Hide APN button — find by searching QPushButton labels inside AdvancedNetworking + for (auto *btn : network_panel->findChildren()) { + if (btn->text().contains("APN", Qt::CaseInsensitive)) { + // Hide the parent AbstractControl frame, not just the button + if (auto *frame = qobject_cast(btn->parentWidget())) { + frame->setVisible(false); + } + } + } + + // ── Dashcam panel ── QWidget *dashcam_panel = new QWidget(this); QVBoxLayout *dash_layout = new QVBoxLayout(dashcam_panel); dash_layout->setContentsMargins(50, 25, 50, 25); @@ -230,7 +355,7 @@ ClearPilotPanel::ClearPilotPanel(QWidget* parent) : QFrame(parent) { dash_layout->addWidget(dash_label); dash_layout->addStretch(); - // Debug panel + // ── Debug panel ── ListWidget *debug_panel = new ListWidget(this); debug_panel->setContentsMargins(50, 25, 50, 25); @@ -239,9 +364,10 @@ ClearPilotPanel::ClearPilotPanel(QWidget* parent) : QFrame(parent) { "Captures only changed values for efficiency.", "", this); debug_panel->addItem(telemetry_toggle); - // Register panels with sidebar buttons + // ── Register panels with sidebar buttons ── QList> panels = { - {"Home", home_panel}, + {"General", general_panel}, + {"Network", network_panel}, {"Dashcam", dashcam_panel}, {"Debug", debug_panel}, }; @@ -253,19 +379,24 @@ ClearPilotPanel::ClearPilotPanel(QWidget* parent) : QFrame(parent) { 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); - }); + // Network panel handles its own scrolling/margins + if (name == "Network") { + panel_widget->addWidget(panel); + QObject::connect(btn, &QPushButton::clicked, [=, w = panel]() { + btn->setChecked(true); + panel_widget->setCurrentWidget(w); + }); + } else { + 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(QString(), Qt::FindDirectChildrenOnly)) { - // Skip close_btn, find first sidebar btn - } + // Select General by default panel_widget->setCurrentIndex(0); // Main layout: sidebar + panels @@ -282,9 +413,15 @@ ClearPilotPanel::ClearPilotPanel(QWidget* parent) : QFrame(parent) { ClearPilotPanel { background-color: black; } - QStackedWidget, ScrollView { + QStackedWidget, ScrollView, Networking { background-color: #292929; border-radius: 30px; } + #softreboot_btn { height: 120px; border-radius: 15px; background-color: #e2e22c; } + #softreboot_btn:pressed { background-color: #ffe224; } + #reboot_btn { height: 120px; border-radius: 15px; background-color: #393939; } + #reboot_btn:pressed { background-color: #4a4a4a; } + #poweroff_btn { height: 120px; border-radius: 15px; background-color: #E22C2C; } + #poweroff_btn:pressed { background-color: #FF2424; } )"); } diff --git a/selfdrive/ui/qt/spinner b/selfdrive/ui/qt/spinner index 4cd6e4a..a6d6ecb 100755 Binary files a/selfdrive/ui/qt/spinner and b/selfdrive/ui/qt/spinner differ