You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
441 lines
15 KiB
441 lines
15 KiB
#include "selfdrive/ui/qt/offroad/settings.h"
|
|
|
|
#include <cassert>
|
|
#include <string>
|
|
|
|
#include <QDebug>
|
|
|
|
#ifndef QCOM
|
|
#include "selfdrive/ui/qt/offroad/networking.h"
|
|
#endif
|
|
|
|
#ifdef ENABLE_MAPS
|
|
#include "selfdrive/ui/qt/maps/map_settings.h"
|
|
#endif
|
|
|
|
#include "selfdrive/common/params.h"
|
|
#include "selfdrive/common/util.h"
|
|
#include "selfdrive/hardware/hw.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/widgets/ssh_keys.h"
|
|
#include "selfdrive/ui/qt/widgets/toggle.h"
|
|
#include "selfdrive/ui/ui.h"
|
|
#include "selfdrive/ui/qt/util.h"
|
|
#include "selfdrive/ui/qt/qt_window.h"
|
|
|
|
TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
|
|
// param, title, desc, icon
|
|
std::vector<std::tuple<QString, QString, QString, QString>> toggles{
|
|
{
|
|
"OpenpilotEnabledToggle",
|
|
"Enable openpilot",
|
|
"Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off.",
|
|
"../assets/offroad/icon_openpilot.png",
|
|
},
|
|
{
|
|
"IsLdwEnabled",
|
|
"Enable Lane Departure Warnings",
|
|
"Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31mph (50kph).",
|
|
"../assets/offroad/icon_warning.png",
|
|
},
|
|
{
|
|
"IsRHD",
|
|
"Enable Right-Hand Drive",
|
|
"Allow openpilot to obey left-hand traffic conventions and perform driver monitoring on right driver seat.",
|
|
"../assets/offroad/icon_openpilot_mirrored.png",
|
|
},
|
|
{
|
|
"IsMetric",
|
|
"Use Metric System",
|
|
"Display speed in km/h instead of mph.",
|
|
"../assets/offroad/icon_metric.png",
|
|
},
|
|
{
|
|
"CommunityFeaturesToggle",
|
|
"Enable Community Features",
|
|
"Use features, such as community supported hardware, from the open source community that are not maintained or supported by comma.ai and have not been confirmed to meet the standard safety model. Be extra cautious when using these features",
|
|
"../assets/offroad/icon_shell.png",
|
|
},
|
|
{
|
|
"UploadRaw",
|
|
"Upload Raw Logs",
|
|
"Upload full logs and full resolution video by default while on Wi-Fi. If not enabled, individual logs can be marked for upload at useradmin.comma.ai.",
|
|
"../assets/offroad/icon_network.png",
|
|
},
|
|
{
|
|
"RecordFront",
|
|
"Record and Upload Driver Camera",
|
|
"Upload data from the driver facing camera and help improve the driver monitoring algorithm.",
|
|
"../assets/offroad/icon_monitoring.png",
|
|
},
|
|
{
|
|
"EndToEndToggle",
|
|
"\U0001f96c Disable use of lanelines (Alpha) \U0001f96c",
|
|
"In this mode openpilot will ignore lanelines and just drive how it thinks a human would.",
|
|
"../assets/offroad/icon_road.png",
|
|
},
|
|
#ifdef ENABLE_MAPS
|
|
{
|
|
"NavSettingTime24h",
|
|
"Show ETA in 24h format",
|
|
"Use 24h format instead of am/pm",
|
|
"../assets/offroad/icon_metric.png",
|
|
},
|
|
#endif
|
|
|
|
};
|
|
|
|
Params params;
|
|
|
|
if (params.getBool("DisableRadar_Allow")) {
|
|
toggles.push_back({
|
|
"DisableRadar",
|
|
"openpilot Longitudinal Control",
|
|
"openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB!",
|
|
"../assets/offroad/icon_speed_limit.png",
|
|
});
|
|
}
|
|
|
|
for (auto &[param, title, desc, icon] : toggles) {
|
|
auto toggle = new ParamControl(param, title, desc, icon, this);
|
|
bool locked = params.getBool((param + "Lock").toStdString());
|
|
toggle->setEnabled(!locked);
|
|
if (!locked) {
|
|
connect(parent, &SettingsWindow::offroadTransition, toggle, &ParamControl::setEnabled);
|
|
}
|
|
addItem(toggle);
|
|
}
|
|
}
|
|
|
|
DevicePanel::DevicePanel(QWidget* parent) : ListWidget(parent) {
|
|
setSpacing(50);
|
|
Params params = Params();
|
|
addItem(new LabelControl("Dongle ID", getDongleId().value_or("N/A")));
|
|
|
|
QString serial = QString::fromStdString(params.get("HardwareSerial", false));
|
|
addItem(new LabelControl("Serial", serial));
|
|
|
|
// offroad-only buttons
|
|
|
|
auto dcamBtn = new ButtonControl("Driver Camera", "PREVIEW",
|
|
"Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)");
|
|
connect(dcamBtn, &ButtonControl::clicked, [=]() { emit showDriverView(); });
|
|
|
|
QString resetCalibDesc = "openpilot requires the device to be mounted within 4° left or right and within 5° up or down. openpilot is continuously calibrating, resetting is rarely required.";
|
|
auto resetCalibBtn = new ButtonControl("Reset Calibration", "RESET", resetCalibDesc);
|
|
connect(resetCalibBtn, &ButtonControl::clicked, [=]() {
|
|
if (ConfirmationDialog::confirm("Are you sure you want to reset calibration?", this)) {
|
|
Params().remove("CalibrationParams");
|
|
}
|
|
});
|
|
connect(resetCalibBtn, &ButtonControl::showDescription, [=]() {
|
|
QString desc = resetCalibDesc;
|
|
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<cereal::Event>().getLiveCalibration();
|
|
if (calib.getCalStatus() != 0) {
|
|
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 ? "up" : "down",
|
|
QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? "right" : "left");
|
|
}
|
|
} catch (kj::Exception) {
|
|
qInfo() << "invalid CalibrationParams";
|
|
}
|
|
}
|
|
resetCalibBtn->setDescription(desc);
|
|
});
|
|
|
|
ButtonControl *retrainingBtn = nullptr;
|
|
if (!params.getBool("Passive")) {
|
|
retrainingBtn = new ButtonControl("Review Training Guide", "REVIEW", "Review the rules, features, and limitations of openpilot");
|
|
connect(retrainingBtn, &ButtonControl::clicked, [=]() {
|
|
if (ConfirmationDialog::confirm("Are you sure you want to review the training guide?", this)) {
|
|
emit reviewTrainingGuide();
|
|
}
|
|
});
|
|
}
|
|
|
|
ButtonControl *regulatoryBtn = nullptr;
|
|
if (Hardware::TICI()) {
|
|
regulatoryBtn = new ButtonControl("Regulatory", "VIEW", "");
|
|
connect(regulatoryBtn, &ButtonControl::clicked, [=]() {
|
|
const std::string txt = util::read_file("../assets/offroad/fcc.html");
|
|
RichTextDialog::alert(QString::fromStdString(txt), this);
|
|
});
|
|
}
|
|
|
|
for (auto btn : {dcamBtn, resetCalibBtn, retrainingBtn, regulatoryBtn}) {
|
|
if (btn) {
|
|
connect(parent, SIGNAL(offroadTransition(bool)), btn, SLOT(setEnabled(bool)));
|
|
addItem(btn);
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
QObject::connect(reboot_btn, &QPushButton::clicked, [=]() {
|
|
if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) {
|
|
if (ConfirmationDialog::confirm("Are you sure you want to reboot?", this)) {
|
|
// Check engaged again in case it changed while the dialog was open
|
|
if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) {
|
|
Params().putBool("DoReboot", true);
|
|
}
|
|
}
|
|
} else {
|
|
ConfirmationDialog::alert("Disengage to Reboot", this);
|
|
}
|
|
});
|
|
|
|
QPushButton *poweroff_btn = new QPushButton("Power Off");
|
|
poweroff_btn->setObjectName("poweroff_btn");
|
|
power_layout->addWidget(poweroff_btn);
|
|
QObject::connect(poweroff_btn, &QPushButton::clicked, [=]() {
|
|
if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) {
|
|
if (ConfirmationDialog::confirm("Are you sure you want to power off?", this)) {
|
|
// Check engaged again in case it changed while the dialog was open
|
|
if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) {
|
|
Params().putBool("DoShutdown", true);
|
|
}
|
|
}
|
|
} else {
|
|
ConfirmationDialog::alert("Disengage to Power Off", this);
|
|
}
|
|
});
|
|
|
|
setStyleSheet(R"(
|
|
QPushButton {
|
|
height: 120px;
|
|
border-radius: 15px;
|
|
}
|
|
#reboot_btn { background-color: #393939; }
|
|
#reboot_btn:pressed { background-color: #4a4a4a; }
|
|
#poweroff_btn { background-color: #E22C2C; }
|
|
#poweroff_btn:pressed { background-color: #FF2424; }
|
|
)");
|
|
addItem(power_layout);
|
|
}
|
|
|
|
SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
|
|
gitBranchLbl = new LabelControl("Git Branch");
|
|
gitCommitLbl = new LabelControl("Git Commit");
|
|
osVersionLbl = new LabelControl("OS Version");
|
|
versionLbl = new LabelControl("Version", "", QString::fromStdString(params.get("ReleaseNotes")).trimmed());
|
|
lastUpdateLbl = new LabelControl("Last Update Check", "", "The last time openpilot successfully checked for an update. The updater only runs while the car is off.");
|
|
updateBtn = new ButtonControl("Check for Update", "");
|
|
connect(updateBtn, &ButtonControl::clicked, [=]() {
|
|
if (params.getBool("IsOffroad")) {
|
|
fs_watch->addPath(QString::fromStdString(params.getParamPath("LastUpdateTime")));
|
|
fs_watch->addPath(QString::fromStdString(params.getParamPath("UpdateFailedCount")));
|
|
updateBtn->setText("CHECKING");
|
|
updateBtn->setEnabled(false);
|
|
}
|
|
std::system("pkill -1 -f selfdrive.updated");
|
|
});
|
|
|
|
|
|
auto uninstallBtn = new ButtonControl("Uninstall " + getBrand(), "UNINSTALL");
|
|
connect(uninstallBtn, &ButtonControl::clicked, [=]() {
|
|
if (ConfirmationDialog::confirm("Are you sure you want to uninstall?", this)) {
|
|
Params().putBool("DoUninstall", true);
|
|
}
|
|
});
|
|
connect(parent, SIGNAL(offroadTransition(bool)), uninstallBtn, SLOT(setEnabled(bool)));
|
|
|
|
QWidget *widgets[] = {versionLbl, lastUpdateLbl, updateBtn, gitBranchLbl, gitCommitLbl, osVersionLbl, uninstallBtn};
|
|
for (QWidget* w : widgets) {
|
|
addItem(w);
|
|
}
|
|
|
|
fs_watch = new QFileSystemWatcher(this);
|
|
QObject::connect(fs_watch, &QFileSystemWatcher::fileChanged, [=](const QString path) {
|
|
if (path.contains("UpdateFailedCount") && std::atoi(params.get("UpdateFailedCount").c_str()) > 0) {
|
|
lastUpdateLbl->setText("failed to fetch update");
|
|
updateBtn->setText("CHECK");
|
|
updateBtn->setEnabled(true);
|
|
} else if (path.contains("LastUpdateTime")) {
|
|
updateLabels();
|
|
}
|
|
});
|
|
}
|
|
|
|
void SoftwarePanel::showEvent(QShowEvent *event) {
|
|
updateLabels();
|
|
}
|
|
|
|
void SoftwarePanel::updateLabels() {
|
|
QString lastUpdate = "";
|
|
auto tm = params.get("LastUpdateTime");
|
|
if (!tm.empty()) {
|
|
lastUpdate = timeAgo(QDateTime::fromString(QString::fromStdString(tm + "Z"), Qt::ISODate));
|
|
}
|
|
|
|
versionLbl->setText(getBrandVersion());
|
|
lastUpdateLbl->setText(lastUpdate);
|
|
updateBtn->setText("CHECK");
|
|
updateBtn->setEnabled(true);
|
|
gitBranchLbl->setText(QString::fromStdString(params.get("GitBranch")));
|
|
gitCommitLbl->setText(QString::fromStdString(params.get("GitCommit")).left(10));
|
|
osVersionLbl->setText(QString::fromStdString(Hardware::get_os_version()).trimmed());
|
|
}
|
|
|
|
QWidget * network_panel(QWidget * parent) {
|
|
#ifdef QCOM
|
|
QWidget *w = new QWidget(parent);
|
|
QVBoxLayout *layout = new QVBoxLayout(w);
|
|
layout->setContentsMargins(50, 0, 50, 0);
|
|
|
|
ListWidget *list = new ListWidget();
|
|
list->setSpacing(30);
|
|
// wifi + tethering buttons
|
|
auto wifiBtn = new ButtonControl("Wi-Fi Settings", "OPEN");
|
|
QObject::connect(wifiBtn, &ButtonControl::clicked, [=]() { HardwareEon::launch_wifi(); });
|
|
list->addItem(wifiBtn);
|
|
|
|
auto tetheringBtn = new ButtonControl("Tethering Settings", "OPEN");
|
|
QObject::connect(tetheringBtn, &ButtonControl::clicked, [=]() { HardwareEon::launch_tethering(); });
|
|
list->addItem(tetheringBtn);
|
|
|
|
// SSH key management
|
|
list->addItem(new SshToggle());
|
|
list->addItem(new SshControl());
|
|
|
|
layout->addWidget(list);
|
|
layout->addStretch(1);
|
|
#else
|
|
Networking *w = new Networking(parent);
|
|
#endif
|
|
return w;
|
|
}
|
|
|
|
void SettingsWindow::showEvent(QShowEvent *event) {
|
|
panel_widget->setCurrentIndex(0);
|
|
nav_btns->buttons()[0]->setChecked(true);
|
|
}
|
|
|
|
SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
|
|
|
|
// setup two main layouts
|
|
sidebar_widget = new QWidget;
|
|
QVBoxLayout *sidebar_layout = new QVBoxLayout(sidebar_widget);
|
|
sidebar_layout->setMargin(0);
|
|
panel_widget = new QStackedWidget();
|
|
panel_widget->setStyleSheet(R"(
|
|
border-radius: 30px;
|
|
background-color: #292929;
|
|
)");
|
|
|
|
// close button
|
|
QPushButton *close_btn = new QPushButton("×");
|
|
close_btn->setStyleSheet(R"(
|
|
QPushButton {
|
|
font-size: 140px;
|
|
padding-bottom: 20px;
|
|
font-weight: bold;
|
|
border 1px grey solid;
|
|
border-radius: 100px;
|
|
background-color: #292929;
|
|
font-weight: 400;
|
|
}
|
|
QPushButton:pressed {
|
|
background-color: #3B3B3B;
|
|
}
|
|
)");
|
|
close_btn->setFixedSize(200, 200);
|
|
sidebar_layout->addSpacing(45);
|
|
sidebar_layout->addWidget(close_btn, 0, Qt::AlignCenter);
|
|
QObject::connect(close_btn, &QPushButton::clicked, this, &SettingsWindow::closeSettings);
|
|
|
|
// setup panels
|
|
DevicePanel *device = new DevicePanel(this);
|
|
QObject::connect(device, &DevicePanel::reviewTrainingGuide, this, &SettingsWindow::reviewTrainingGuide);
|
|
QObject::connect(device, &DevicePanel::showDriverView, this, &SettingsWindow::showDriverView);
|
|
|
|
QList<QPair<QString, QWidget *>> panels = {
|
|
{"Device", device},
|
|
{"Network", network_panel(this)},
|
|
{"Toggles", new TogglesPanel(this)},
|
|
{"Software", new SoftwarePanel(this)},
|
|
};
|
|
|
|
#ifdef ENABLE_MAPS
|
|
auto map_panel = new MapPanel(this);
|
|
panels.push_back({"Navigation", map_panel});
|
|
QObject::connect(map_panel, &MapPanel::closeSettings, this, &SettingsWindow::closeSettings);
|
|
#endif
|
|
|
|
const int padding = panels.size() > 3 ? 25 : 35;
|
|
|
|
nav_btns = new QButtonGroup();
|
|
for (auto &[name, panel] : panels) {
|
|
QPushButton *btn = new QPushButton(name);
|
|
btn->setCheckable(true);
|
|
btn->setChecked(nav_btns->buttons().size() == 0);
|
|
btn->setStyleSheet(QString(R"(
|
|
QPushButton {
|
|
color: grey;
|
|
border: none;
|
|
background: none;
|
|
font-size: 65px;
|
|
font-weight: 500;
|
|
padding-top: %1px;
|
|
padding-bottom: %1px;
|
|
}
|
|
QPushButton:checked {
|
|
color: white;
|
|
}
|
|
QPushButton:pressed {
|
|
color: #ADADAD;
|
|
}
|
|
)").arg(padding));
|
|
|
|
nav_btns->addButton(btn);
|
|
sidebar_layout->addWidget(btn, 0, Qt::AlignRight);
|
|
|
|
const int lr_margin = name != "Network" ? 50 : 0; // Network panel handles its own margins
|
|
panel->setContentsMargins(lr_margin, 25, lr_margin, 25);
|
|
|
|
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);
|
|
});
|
|
}
|
|
sidebar_layout->setContentsMargins(50, 50, 100, 50);
|
|
|
|
// main settings layout, sidebar + main panel
|
|
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;
|
|
}
|
|
SettingsWindow {
|
|
background-color: black;
|
|
}
|
|
)");
|
|
}
|
|
|
|
void SettingsWindow::hideEvent(QHideEvent *event) {
|
|
#ifdef QCOM
|
|
HardwareEon::close_activities();
|
|
#endif
|
|
}
|
|
|