This commit is contained in:
Your Name
2024-04-27 13:43:16 -05:00
parent 21363ce751
commit ea1aad5ed1
128 changed files with 3533 additions and 1918 deletions

6
selfdrive/ui/.gitignore vendored Executable file → Normal file
View File

@@ -3,11 +3,13 @@ moc_*
translations/main_test_en.*
_text
_spinner
ui
mui
watch3
installer/installers/*
qt/text
qt/spinner
qt/setup/setup
qt/setup/reset
qt/setup/wifi

51
selfdrive/ui/SConscript Executable file → Normal file
View File

@@ -11,29 +11,26 @@ if arch == 'larch64':
maps = arch in ['larch64', 'aarch64', 'x86_64']
if maps and arch != 'larch64':
rpath = [Dir(f"#third_party/mapbox-gl-native-qt/{arch}").srcnode().abspath]
qt_env["RPATH"] += rpath
if arch == "Darwin":
del base_libs[base_libs.index('OpenCL')]
qt_env['FRAMEWORKS'] += ['OpenCL']
# FIXME: remove this once we're on 5.15 (24.04)
qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"]
qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"], LIBS=base_libs)
widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/drive_stats.cc", "qt/widgets/wifi.cc",
"qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc",
"qt/widgets/offroad_alerts.cc", "qt/widgets/prime.cc", "qt/widgets/keyboard.cc",
"qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc",
"qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc",
"../frogpilot/ui/frogpilot_functions.cc", "../frogpilot/navigation/ui/navigation_settings.cc",
"../frogpilot/ui/control_settings.cc", "../frogpilot/ui/vehicle_settings.cc",
"../frogpilot/ui/visual_settings.cc",
"../oscarpilot/settings/settings.cc", "../oscarpilot/settings/basic.cc",
]
"../frogpilot/ui/qt/widgets/frogpilot_controls.cc", "../frogpilot/navigation/ui/navigation_settings.cc",
"../frogpilot/ui/qt/offroad/control_settings.cc", "../frogpilot/ui/qt/offroad/vehicle_settings.cc",
"../frogpilot/ui/qt/offroad/visual_settings.cc"]
qt_env['CPPDEFINES'] = []
if maps:
base_libs += ['qmapboxgl']
base_libs += ['QMapLibre']
widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc",
"qt/maps/map_eta.cc", "qt/maps/map_instructions.cc"]
qt_env['CPPDEFINES'] += ["ENABLE_MAPS"]
@@ -48,21 +45,6 @@ qt_src = ["main.cc", "qt/sidebar.cc", "qt/onroad.cc", "qt/body.cc",
"qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc",
"../frogpilot/screenrecorder/omx_encoder.cc", "../frogpilot/screenrecorder/screenrecorder.cc"]
def is_running_on_wsl2():
try:
with open('/proc/version', 'r') as f:
return 'WSL2' in f.read()
except FileNotFoundError:
return False
if is_running_on_wsl2():
qt_env.Append(CXXFLAGS=['-DWSL2'])
base_libs.remove('OmxCore')
qt_libs.remove('OmxCore')
qt_src.remove("../frogpilot/screenrecorder/screenrecorder.cc")
qt_src.remove("../frogpilot/screenrecorder/omx_encoder.cc")
print("Building for WSL2. Removing Screen Recorder")
# build translation files
with open(File("translations/languages.json").abspath) as f:
languages = json.loads(f.read())
@@ -94,8 +76,8 @@ asset_obj = qt_env.Object("assets", assets)
qt_env.SharedLibrary("qt/python_helpers", ["qt/qt_window.cc"], LIBS=qt_libs)
# spinner and text window
qt_env.Program("qt/text", ["qt/text.cc"], LIBS=qt_libs)
qt_env.Program("qt/spinner", ["qt/spinner.cc"], LIBS=qt_libs)
qt_env.Program("_text", ["qt/text.cc"], LIBS=qt_libs)
qt_env.Program("_spinner", ["qt/spinner.cc"], LIBS=qt_libs)
# build main UI
qt_env.Program("ui", qt_src + [asset_obj], LIBS=qt_libs)
@@ -115,28 +97,25 @@ if GetOption('extras') and arch != "Darwin":
# build updater UI
qt_env.Program("qt/setup/updater", ["qt/setup/updater.cc", asset_obj], LIBS=qt_libs)
# build mui
qt_env.Program("mui", ["mui.cc"], LIBS=qt_libs)
# build installers
senv = qt_env.Clone()
senv['LINKFLAGS'].append('-Wl,-strip-debug')
release = "release3"
dashcam = "dashcam3"
installers = [
("openpilot", release),
("openpilot_test", f"{release}-staging"),
("openpilot_nightly", "nightly"),
("openpilot_internal", "master"),
("dashcam", dashcam),
("dashcam_test", f"{dashcam}-staging"),
]
cont = {}
for brand in ("openpilot", "dashcam"):
cont[brand] = senv.Command(f"installer/continue_{brand}.o", f"installer/continue_{brand}.sh",
cont = senv.Command(f"installer/continue_openpilot.o", f"installer/continue_openpilot.sh",
"ld -r -b binary -o $TARGET $SOURCE")
for name, branch in installers:
brand = "dashcam" if "dashcam" in branch else "openpilot"
d = {'BRANCH': f"'\"{branch}\"'", 'BRAND': f"'\"{brand}\"'"}
d = {'BRANCH': f"'\"{branch}\"'"}
if "internal" in name:
d['INTERNAL'] = "1"
@@ -145,7 +124,7 @@ if GetOption('extras') and arch != "Darwin":
r.raise_for_status()
d['SSH_KEYS'] = f'\\"{r.text.strip()}\\"'
obj = senv.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d)
f = senv.Program(f"installer/installers/installer_{name}", [obj, cont[brand]], LIBS=qt_libs)
f = senv.Program(f"installer/installers/installer_{name}", [obj, cont], LIBS=qt_libs)
# keep installers small
assert f[0].get_size() < 350*1e3

0
selfdrive/ui/__init__.py Normal file
View File

View File

@@ -0,0 +1,4 @@
#!/usr/bin/bash
cd /data/openpilot
exec ./launch_openpilot.sh

4
selfdrive/ui/installer/installer.cc Executable file → Normal file
View File

@@ -33,8 +33,8 @@ const QString CACHE_PATH = "/data/openpilot.cache";
#define INSTALL_PATH "/data/openpilot"
#define TMP_INSTALL_PATH "/data/tmppilot"
extern const uint8_t str_continue[] asm("_binary_selfdrive_ui_installer_continue_" BRAND "_sh_start");
extern const uint8_t str_continue_end[] asm("_binary_selfdrive_ui_installer_continue_" BRAND "_sh_end");
extern const uint8_t str_continue[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_start");
extern const uint8_t str_continue_end[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_end");
bool time_valid() {
time_t rawtime;

0
selfdrive/ui/installer/installer.h Executable file → Normal file
View File

0
selfdrive/ui/main.cc Executable file → Normal file
View File

50
selfdrive/ui/mui.cc Normal file
View File

@@ -0,0 +1,50 @@
#include <QApplication>
#include <QtWidgets>
#include <QTimer>
#include "cereal/messaging/messaging.h"
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/qt_window.h"
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
QWidget w;
setMainWindow(&w);
w.setStyleSheet("background-color: black;");
// our beautiful UI
QVBoxLayout *layout = new QVBoxLayout(&w);
QLabel *label = new QLabel("");
layout->addWidget(label, 0, Qt::AlignCenter);
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [=]() {
static SubMaster sm({"deviceState", "controlsState"});
bool onroad_prev = sm.allAliveAndValid({"deviceState"}) &&
sm["deviceState"].getDeviceState().getStarted();
sm.update(0);
bool onroad = sm.allAliveAndValid({"deviceState"}) &&
sm["deviceState"].getDeviceState().getStarted();
if (onroad) {
label->setText("");
auto cs = sm["controlsState"].getControlsState();
UIStatus status = cs.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED;
label->setStyleSheet(QString("color: %1; font-size: 250px;").arg(bg_colors[status].name()));
} else {
label->setText("offroad");
label->setStyleSheet("color: grey; font-size: 40px;");
}
if ((onroad != onroad_prev) || sm.frame < 2) {
Hardware::set_brightness(50);
Hardware::set_display_power(onroad);
}
});
timer.start(50);
return a.exec();
}

0
selfdrive/ui/qt/api.cc Executable file → Normal file
View File

0
selfdrive/ui/qt/api.h Executable file → Normal file
View File

159
selfdrive/ui/qt/body.cc Executable file → Normal file
View File

@@ -6,51 +6,156 @@
#include <QPainter>
#include <QStackedLayout>
#include <QApplication>
#include <QGridLayout>
#include <QString>
#include <QTransform>
#include <QPixmap>
#include "common/params.h"
#include "common/timing.h"
#include "system/hardware/hw.h"
#include "selfdrive/ui/qt/qt_window.h"
#include "selfdrive/ui/qt/util.h"
RecordButton::RecordButton(QWidget *parent) : QPushButton(parent) {
setCheckable(true);
setChecked(false);
setFixedSize(148, 148);
BodyWindow::BodyWindow(QWidget *parent) : QWidget(parent) {
QGridLayout *layout = new QGridLayout(this);
layout->setSpacing(0);
layout->setMargin(200);
QObject::connect(this, &QPushButton::toggled, [=]() {
setEnabled(false);
});
}
setAttribute(Qt::WA_OpaquePaintEvent);
void RecordButton::paintEvent(QPaintEvent *event) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
setStyleSheet(R"(
BodyWindow {
background-color: blue;
QPoint center(width() / 2, height() / 2);
QColor bg(isChecked() ? "#FFFFFF" : "#737373");
QColor accent(isChecked() ? "#FF0000" : "#FFFFFF");
if (!isEnabled()) {
bg = QColor("#404040");
accent = QColor("#FFFFFF");
}
)");
if (isDown()) {
accent.setAlphaF(0.7);
}
p.setPen(Qt::NoPen);
p.setBrush(bg);
p.drawEllipse(center, 74, 74);
p.setPen(QPen(accent, 6));
p.setBrush(Qt::NoBrush);
p.drawEllipse(center, 42, 42);
p.setPen(Qt::NoPen);
p.setBrush(accent);
p.drawEllipse(center, 22, 22);
}
BodyWindow::BodyWindow(QWidget *parent) : fuel_filter(1.0, 5., 1. / UI_FREQ), QWidget(parent) {
QStackedLayout *layout = new QStackedLayout(this);
layout->setStackingMode(QStackedLayout::StackAll);
QWidget *w = new QWidget;
QVBoxLayout *vlayout = new QVBoxLayout(w);
vlayout->setMargin(45);
layout->addWidget(w);
// face
face = new QLabel();
face->setAlignment(Qt::AlignCenter);
layout->addWidget(face);
awake = new QMovie("../assets/body/awake.gif", {}, this);
awake->setCacheMode(QMovie::CacheAll);
sleep = new QMovie("../assets/body/sleep.gif", {}, this);
sleep->setCacheMode(QMovie::CacheAll);
// record button
btn = new RecordButton(this);
vlayout->addWidget(btn, 0, Qt::AlignBottom | Qt::AlignRight);
QObject::connect(btn, &QPushButton::clicked, [=](bool checked) {
btn->setEnabled(false);
Params().putBool("DisableLogging", !checked);
last_button = nanos_since_boot();
});
w->raise();
QObject::connect(uiState(), &UIState::uiUpdate, this, &BodyWindow::updateState);
}
void BodyWindow::paintEvent(QPaintEvent *event) {
QPainter painter(this);
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
QPixmap comma_img = loadPixmap("../assets/oscarpilot_ready.png");
p.fillRect(rect(), QColor(0, 0, 0));
// Calculate the top-left position to center the image in the window.
int x = (this->width() - comma_img.width()) / 2;
int y = (this->height() - comma_img.height()) / 2;
// battery outline + detail
p.translate(width() - 136, 16);
const QColor gray = QColor("#737373");
p.setBrush(Qt::NoBrush);
p.setPen(QPen(gray, 4, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
p.drawRoundedRect(2, 2, 78, 36, 8, 8);
// Draw the pixmap at the calculated position.
painter.drawPixmap(x, y, comma_img);
}
p.setPen(Qt::NoPen);
p.setBrush(gray);
p.drawRoundedRect(84, 12, 6, 16, 4, 4);
p.drawRect(84, 12, 3, 16);
// battery level
double fuel = std::clamp(fuel_filter.x(), 0.2f, 1.0f);
const int m = 5; // manual margin since we can't do an inner border
p.setPen(Qt::NoPen);
p.setBrush(fuel > 0.25 ? QColor("#32D74B") : QColor("#FF453A"));
p.drawRoundedRect(2 + m, 2 + m, (78 - 2*m)*fuel, 36 - 2*m, 4, 4);
void BodyWindow::updateState(const UIState &s) {
// charging status
if (charging) {
p.setPen(Qt::NoPen);
p.setBrush(Qt::white);
const QPolygonF charger({
QPointF(12.31, 0),
QPointF(12.31, 16.92),
QPointF(18.46, 16.92),
QPointF(6.15, 40),
QPointF(6.15, 23.08),
QPointF(0, 23.08),
});
p.drawPolygon(charger.translated(98, 0));
}
}
void BodyWindow::offroadTransition(bool offroad) {
btn->setChecked(true);
btn->setEnabled(true);
fuel_filter.reset(1.0);
}
void BodyWindow::updateState(const UIState &s) {
if (!isVisible()) {
return;
}
const SubMaster &sm = *(s.sm);
auto cs = sm["carState"].getCarState();
charging = cs.getCharging();
fuel_filter.update(cs.getFuelGauge());
// TODO: use carState.standstill when that's fixed
const bool standstill = std::abs(cs.getVEgo()) < 0.01;
QMovie *m = standstill ? sleep : awake;
if (m != face->movie()) {
face->setMovie(m);
face->movie()->start();
}
// update record button state
if (sm.updated("managerState") && (sm.rcv_time("managerState") - last_button)*1e-9 > 0.5) {
for (auto proc : sm["managerState"].getManagerState().getProcesses()) {
if (proc.getName() == "loggerd") {
btn->setEnabled(true);
btn->setChecked(proc.getRunning());
}
}
}
update();
}

View File

@@ -1,60 +0,0 @@
#include "selfdrive/ui/qt/body.h"
#include <cmath>
#include <algorithm>
#include <QPainter>
#include <QStackedLayout>
#include <QApplication>
#include <QGridLayout>
#include <QString>
#include <QTransform>
#include <QPixmap>
#include "common/params.h"
#include "common/timing.h"
#include "system/hardware/hw.h"
#include "selfdrive/ui/qt/qt_window.h"
#include "selfdrive/ui/qt/util.h"
void LogoWidget::paintEvent(QPaintEvent *event) {
QPainter painter(this);
}
BodyWindow::BodyWindow(QWidget *parent) : QWidget(parent) {
QGridLayout *layout = new QGridLayout(this);
layout->setSpacing(0);
layout->setMargin(200);
setAttribute(Qt::WA_OpaquePaintEvent);
setStyleSheet(R"(
BodyWindow {
background-color: blue;
}
)");
QObject::connect(uiState(), &UIState::uiUpdate, this, &BodyWindow::updateState);
}
void BodyWindow::paintEvent(QPaintEvent *event) {
QPainter painter(this);
QPixmap comma_img = loadPixmap("../assets/oscarpilot_ready.png");
// Calculate the top-left position to center the image in the window.
int x = (this->width() - comma_img.width()) / 2;
int y = (this->height() - comma_img.height()) / 2;
// Draw the pixmap at the calculated position.
painter.drawPixmap(x, y, comma_img);
}
void BodyWindow::updateState(const UIState &s) {
}
void BodyWindow::offroadTransition(bool offroad) {
}

28
selfdrive/ui/qt/body.h Executable file → Normal file
View File

@@ -3,21 +3,35 @@
#include <QMovie>
#include <QLabel>
#include <QPushButton>
#include <QPixmap>
#include <QProgressBar>
#include <QSocketNotifier>
#include <QVariantAnimation>
#include <QWidget>
#include "common/util.h"
#include "selfdrive/ui/ui.h"
class BodyWindow : public QWidget {
class RecordButton : public QPushButton {
Q_OBJECT
public:
BodyWindow(QWidget* parent = 0);
RecordButton(QWidget* parent = 0);
private:
void paintEvent(QPaintEvent*) override;
};
class BodyWindow : public QWidget {
Q_OBJECT
public:
BodyWindow(QWidget* parent = 0);
private:
bool charging = false;
uint64_t last_button = 0;
FirstOrderFilter fuel_filter;
QLabel *face;
QMovie *awake, *sleep;
RecordButton *btn;
void paintEvent(QPaintEvent*) override;
private slots:
void updateState(const UIState &s);
void offroadTransition(bool onroad);

View File

@@ -1,38 +0,0 @@
#pragma once
#include <QMovie>
#include <QLabel>
#include <QPushButton>
#include "common/util.h"
#include "selfdrive/ui/ui.h"
class RecordButton : public QPushButton {
Q_OBJECT
public:
RecordButton(QWidget* parent = 0);
private:
void paintEvent(QPaintEvent*) override;
};
class BodyWindow : public QWidget {
Q_OBJECT
public:
BodyWindow(QWidget* parent = 0);
private:
bool charging = false;
uint64_t last_button = 0;
FirstOrderFilter fuel_filter;
QLabel *face;
QMovie *awake, *sleep;
RecordButton *btn;
void paintEvent(QPaintEvent*) override;
private slots:
void updateState(const UIState &s);
void offroadTransition(bool onroad);
};

View File

@@ -1,161 +0,0 @@
#include "selfdrive/ui/qt/body.h"
#include <cmath>
#include <algorithm>
#include <QPainter>
#include <QStackedLayout>
#include "common/params.h"
#include "common/timing.h"
RecordButton::RecordButton(QWidget *parent) : QPushButton(parent) {
setCheckable(true);
setChecked(false);
setFixedSize(148, 148);
QObject::connect(this, &QPushButton::toggled, [=]() {
setEnabled(false);
});
}
void RecordButton::paintEvent(QPaintEvent *event) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
QPoint center(width() / 2, height() / 2);
QColor bg(isChecked() ? "#FFFFFF" : "#737373");
QColor accent(isChecked() ? "#FF0000" : "#FFFFFF");
if (!isEnabled()) {
bg = QColor("#404040");
accent = QColor("#FFFFFF");
}
if (isDown()) {
accent.setAlphaF(0.7);
}
p.setPen(Qt::NoPen);
p.setBrush(bg);
p.drawEllipse(center, 74, 74);
p.setPen(QPen(accent, 6));
p.setBrush(Qt::NoBrush);
p.drawEllipse(center, 42, 42);
p.setPen(Qt::NoPen);
p.setBrush(accent);
p.drawEllipse(center, 22, 22);
}
BodyWindow::BodyWindow(QWidget *parent) : fuel_filter(1.0, 5., 1. / UI_FREQ), QWidget(parent) {
QStackedLayout *layout = new QStackedLayout(this);
layout->setStackingMode(QStackedLayout::StackAll);
QWidget *w = new QWidget;
QVBoxLayout *vlayout = new QVBoxLayout(w);
vlayout->setMargin(45);
layout->addWidget(w);
// face
face = new QLabel();
face->setAlignment(Qt::AlignCenter);
layout->addWidget(face);
awake = new QMovie("../assets/body/awake.gif", {}, this);
awake->setCacheMode(QMovie::CacheAll);
sleep = new QMovie("../assets/body/sleep.gif", {}, this);
sleep->setCacheMode(QMovie::CacheAll);
// record button
btn = new RecordButton(this);
vlayout->addWidget(btn, 0, Qt::AlignBottom | Qt::AlignRight);
QObject::connect(btn, &QPushButton::clicked, [=](bool checked) {
btn->setEnabled(false);
Params().putBool("DisableLogging", !checked);
last_button = nanos_since_boot();
});
w->raise();
QObject::connect(uiState(), &UIState::uiUpdate, this, &BodyWindow::updateState);
}
void BodyWindow::paintEvent(QPaintEvent *event) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
p.fillRect(rect(), QColor(0, 0, 0));
// battery outline + detail
p.translate(width() - 136, 16);
const QColor gray = QColor("#737373");
p.setBrush(Qt::NoBrush);
p.setPen(QPen(gray, 4, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
p.drawRoundedRect(2, 2, 78, 36, 8, 8);
p.setPen(Qt::NoPen);
p.setBrush(gray);
p.drawRoundedRect(84, 12, 6, 16, 4, 4);
p.drawRect(84, 12, 3, 16);
// battery level
double fuel = std::clamp(fuel_filter.x(), 0.2f, 1.0f);
const int m = 5; // manual margin since we can't do an inner border
p.setPen(Qt::NoPen);
p.setBrush(fuel > 0.25 ? QColor("#32D74B") : QColor("#FF453A"));
p.drawRoundedRect(2 + m, 2 + m, (78 - 2*m)*fuel, 36 - 2*m, 4, 4);
// charging status
if (charging) {
p.setPen(Qt::NoPen);
p.setBrush(Qt::white);
const QPolygonF charger({
QPointF(12.31, 0),
QPointF(12.31, 16.92),
QPointF(18.46, 16.92),
QPointF(6.15, 40),
QPointF(6.15, 23.08),
QPointF(0, 23.08),
});
p.drawPolygon(charger.translated(98, 0));
}
}
void BodyWindow::offroadTransition(bool offroad) {
btn->setChecked(true);
btn->setEnabled(true);
fuel_filter.reset(1.0);
}
void BodyWindow::updateState(const UIState &s) {
if (!isVisible()) {
return;
}
const SubMaster &sm = *(s.sm);
auto cs = sm["carState"].getCarState();
charging = cs.getCharging();
fuel_filter.update(cs.getFuelGauge());
// TODO: use carState.standstill when that's fixed
const bool standstill = std::abs(cs.getVEgo()) < 0.01;
QMovie *m = standstill ? sleep : awake;
if (m != face->movie()) {
face->setMovie(m);
face->movie()->start();
}
// update record button state
if (sm.updated("managerState") && (sm.rcv_time("managerState") - last_button)*1e-9 > 0.5) {
for (auto proc : sm["managerState"].getManagerState().getProcesses()) {
if (proc.getName() == "loggerd") {
btn->setEnabled(true);
btn->setChecked(proc.getRunning());
}
}
}
update();
}

View File

@@ -1,52 +0,0 @@
#include "selfdrive/ui/qt/body.h"
#include <cmath>
#include <algorithm>
#include <QPainter>
#include <QStackedLayout>
#include <QApplication>
#include <QGridLayout>
#include <QString>
#include <QTransform>
#include <QPixmap>
#include <QWebEngineView> // Include the QWebEngineView header
#include "common/params.h"
#include "common/timing.h"
#include "system/hardware/hw.h"
#include "selfdrive/ui/qt/qt_window.h"
#include "selfdrive/ui/qt/util.h"
void LogoWidget::paintEvent(QPaintEvent *event) {
QPainter painter(this);
}
BodyWindow::BodyWindow(QWidget *parent) : QWidget(parent) {
// Create a QWebEngineView
QWebEngineView *view = new QWebEngineView(this);
view->setUrl(QUrl("http://www.fark.com/")); // Set the URL to fark.com
// Filler
QGridLayout *layout = new QGridLayout(this);
layout->setSpacing(0);
layout->setMargin(0); // Set margin to 0 to fill the entire window
layout->addWidget(view, 0, 0); // Add the view to the layout
setAttribute(Qt::WA_OpaquePaintEvent);
setStyleSheet(R"(
BodyWindow {
background-color: blue;
}
)");
QObject::connect(uiState(), &UIState::uiUpdate, this, &BodyWindow::updateState);
}
void BodyWindow::updateState(const UIState &s) {
}
void BodyWindow::offroadTransition(bool offroad) {
}

49
selfdrive/ui/qt/home.cc Executable file → Normal file
View File

@@ -45,7 +45,7 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) {
});
slayout->addWidget(driver_view);
setAttribute(Qt::WA_NoSystemBackground);
// QObject::connect(uiState(), &UIState::uiUpdate, this, &HomeWindow::updateState);
QObject::connect(uiState(), &UIState::uiUpdate, this, &HomeWindow::updateState);
QObject::connect(uiState(), &UIState::offroadTransition, this, &HomeWindow::offroadTransition);
QObject::connect(uiState(), &UIState::offroadTransition, sidebar, &Sidebar::offroadTransition);
}
@@ -66,37 +66,45 @@ void HomeWindow::updateState(const UIState &s) {
body->setEnabled(true);
slayout->setCurrentWidget(body);
}
if (s.scene.started) {
showDriverView(s.scene.driver_camera_timer >= 10, true);
}
}
void HomeWindow::offroadTransition(bool offroad) {
body->setEnabled(false);
sidebar->setVisible(false);
sidebar->setVisible(offroad);
if (offroad) {
slayout->setCurrentWidget(body);
slayout->setCurrentWidget(home);
} else {
slayout->setCurrentWidget(onroad);
uiState()->scene.map_open = onroad->isMapVisible();
}
}
void HomeWindow::showDriverView(bool show) {
void HomeWindow::showDriverView(bool show, bool started) {
if (show) {
emit closeSettings();
slayout->setCurrentWidget(driver_view);
sidebar->setVisible(show == false);
} else {
slayout->setCurrentWidget(body);
if (started) {
slayout->setCurrentWidget(onroad);
sidebar->setVisible(params.getBool("Sidebar"));
} else {
slayout->setCurrentWidget(home);
sidebar->setVisible(show == false);
}
}
sidebar->setVisible(false);
}
void HomeWindow::mousePressEvent(QMouseEvent* e) {
if (body->isVisible()) {
showSidebar(true);
slayout->setCurrentWidget(home);
} else {
// Handle sidebar collapsing
if ((onroad->isVisible() || body->isVisible()) && (!sidebar->isVisible() || e->x() > sidebar->width())) {
sidebar->setVisible(!sidebar->isVisible() && !onroad->isMapVisible());
}
uiState()->scene.map_open = onroad->isMapVisible();
params.putBool("Sidebar", sidebar->isVisible());
}
}
@@ -165,9 +173,9 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
left_widget->addWidget(new DriveStats);
left_widget->setStyleSheet("border-radius: 10px;");
left_widget->setCurrentIndex(params.getBool("DriveStats") ? 2 : uiState()->hasPrime() ? 0 : 1);
left_widget->setCurrentIndex(2);
connect(uiState(), &UIState::primeChanged, [=](bool prime) {
left_widget->setCurrentIndex(prime ? 0 : 1);
left_widget->setCurrentIndex(2);
});
home_layout->addWidget(left_widget, 1);
@@ -222,17 +230,6 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
font-size: 55px;
}
)");
// Set the model name
std::map<int, QString> MODEL_NAME {
{0, "New Delhi"},
{1, "Blue Diamond V1"},
{2, "Blue Diamond V2"},
{3, "Farmville"},
{4, "New Lemon Pie"},
};
modelName = MODEL_NAME[params.getInt("Model")];
}
void OffroadHome::showEvent(QShowEvent *event) {
@@ -245,8 +242,10 @@ void OffroadHome::hideEvent(QHideEvent *event) {
}
void OffroadHome::refresh() {
QString model = QString::fromStdString(params.get("ModelName"));
date->setText(QLocale(uiState()->language.mid(5)).toString(QDateTime::currentDateTime(), "dddd, MMMM d"));
version->setText(getBrand() + " v" + getVersion().left(14).trimmed() + " - " + modelName);
version->setText(getBrand() + " v" + getVersion().left(14).trimmed() + " - " + model);
bool updateAvailable = update_widget->refresh();
int alerts = alerts_widget->refresh();

8
selfdrive/ui/qt/home.h Executable file → Normal file
View File

@@ -33,7 +33,6 @@ private:
Params params;
QTimer* timer;
ElidedLabel* date;
ElidedLabel* version;
QStackedLayout* center_layout;
UpdateAlert *update_widget;
@@ -42,7 +41,7 @@ private:
QPushButton* update_notif;
// FrogPilot variables
QString modelName;
ElidedLabel* date;
};
class HomeWindow : public QWidget {
@@ -57,7 +56,7 @@ signals:
public slots:
void offroadTransition(bool offroad);
void showDriverView(bool show);
void showDriverView(bool show, bool started=false);
void showSidebar(bool show);
void showMapPanel(bool show);
@@ -73,6 +72,9 @@ private:
DriverViewWindow *driver_view;
QStackedLayout *slayout;
// FrogPilot variables
Params params;
private slots:
void updateState(const UIState &s);
};

81
selfdrive/ui/qt/maps/map.cc Executable file → Normal file
View File

@@ -18,7 +18,7 @@ const float MAX_PITCH = 50;
const float MIN_PITCH = 0;
const float MAP_SCALE = 2;
MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings), velocity_filter(0, 10, 0.05, false) {
MapWindow::MapWindow(const QMapLibre::Settings &settings) : m_settings(settings), velocity_filter(0, 10, 0.05, false) {
QObject::connect(uiState(), &UIState::uiUpdate, this, &MapWindow::updateState);
map_overlay = new QWidget (this);
@@ -57,10 +57,10 @@ void MapWindow::initLayers() {
if (!m_map->layerExists("modelPathLayer")) {
qDebug() << "Initializing modelPathLayer";
QVariantMap modelPath;
modelPath["id"] = "modelPathLayer";
//modelPath["id"] = "modelPathLayer";
modelPath["type"] = "line";
modelPath["source"] = "modelPathSource";
m_map->addLayer(modelPath);
m_map->addLayer("modelPathLayer", modelPath);
m_map->setPaintProperty("modelPathLayer", "line-color", QColor("red"));
m_map->setPaintProperty("modelPathLayer", "line-width", 5.0);
m_map->setLayoutProperty("modelPathLayer", "line-cap", "round");
@@ -68,10 +68,9 @@ void MapWindow::initLayers() {
if (!m_map->layerExists("navLayer")) {
qDebug() << "Initializing navLayer";
QVariantMap nav;
nav["id"] = "navLayer";
nav["type"] = "line";
nav["source"] = "navSource";
m_map->addLayer(nav, "road-intersection");
m_map->addLayer("navLayer", nav, "road-intersection");
QVariantMap transition;
transition["duration"] = 400; // ms
@@ -84,10 +83,9 @@ void MapWindow::initLayers() {
qDebug() << "Initializing pinLayer";
m_map->addImage("default_marker", QImage("../assets/navigation/default_marker.svg"));
QVariantMap pin;
pin["id"] = "pinLayer";
pin["type"] = "symbol";
pin["source"] = "pinSource";
m_map->addLayer(pin);
m_map->addLayer("pinLayer", pin);
m_map->setLayoutProperty("pinLayer", "icon-pitch-alignment", "viewport");
m_map->setLayoutProperty("pinLayer", "icon-image", "default_marker");
m_map->setLayoutProperty("pinLayer", "icon-ignore-placement", true);
@@ -100,10 +98,9 @@ void MapWindow::initLayers() {
m_map->addImage("label-arrow", QImage("../assets/images/triangle.svg"));
QVariantMap carPos;
carPos["id"] = "carPosLayer";
carPos["type"] = "symbol";
carPos["source"] = "carPosSource";
m_map->addLayer(carPos);
m_map->addLayer("carPosLayer", carPos);
m_map->setLayoutProperty("carPosLayer", "icon-pitch-alignment", "map");
m_map->setLayoutProperty("carPosLayer", "icon-image", "label-arrow");
m_map->setLayoutProperty("carPosLayer", "icon-size", 0.5);
@@ -112,7 +109,6 @@ void MapWindow::initLayers() {
// TODO: remove, symbol-sort-key does not seem to matter outside of each layer
m_map->setLayoutProperty("carPosLayer", "symbol-sort-key", 0);
}
if (!m_map->layerExists("buildingsLayer")) {
qDebug() << "Initializing buildingsLayer";
QVariantMap buildings;
@@ -121,7 +117,7 @@ void MapWindow::initLayers() {
buildings["source-layer"] = "building";
buildings["type"] = "fill-extrusion";
buildings["minzoom"] = 15;
m_map->addLayer(buildings);
m_map->addLayer("buildingsLayer", buildings);
m_map->setFilter("buildingsLayer", QVariantList({"==", "extrude", "true"}));
QVariantList fillExtrusionHight = {
@@ -168,8 +164,7 @@ void MapWindow::updateState(const UIState &s) {
if (sm.updated("modelV2")) {
// set path color on change, and show map on rising edge of navigate on openpilot
bool nav_enabled = sm["modelV2"].getModelV2().getNavEnabled() &&
(sm["controlsState"].getControlsState().getEnabled() || sm["frogpilotCarControl"].getFrogpilotCarControl().getAlwaysOnLateral()) &&
(!params.get("NavDestination").empty() || params.getInt("PrimeType") != 0);
(sm["controlsState"].getControlsState().getEnabled() || sm["frogpilotCarControl"].getFrogpilotCarControl().getAlwaysOnLateral());
if (nav_enabled != uiState()->scene.navigate_on_openpilot) {
if (loaded_once) {
m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(nav_enabled));
@@ -194,12 +189,24 @@ void MapWindow::updateState(const UIState &s) {
locationd_valid = (locationd_pos.getValid() && locationd_orientation.getValid() && locationd_velocity.getValid() && pos_accurate_enough);
if (locationd_valid) {
last_position = QMapbox::Coordinate(locationd_pos.getValue()[0], locationd_pos.getValue()[1]);
last_position = QMapLibre::Coordinate(locationd_pos.getValue()[0], locationd_pos.getValue()[1]);
last_bearing = RAD2DEG(locationd_orientation.getValue()[2]);
velocity_filter.update(std::max(10.0, locationd_velocity.getValue()[0]));
}
}
// Credit to jakethesnake420
if (loaded_once && (sm.rcv_frame("uiPlan") != model_rcv_frame)) {
auto locationd_location = sm["liveLocationKalman"].getLiveLocationKalman();
auto model_path = model_to_collection(locationd_location.getCalibratedOrientationECEF(), locationd_location.getPositionECEF(), sm["uiPlan"].getUiPlan().getPosition());
QMapLibre::Feature model_path_feature(QMapLibre::Feature::LineStringType, model_path, {}, {});
QVariantMap modelV2Path;
modelV2Path["type"] = "geojson";
modelV2Path["data"] = QVariant::fromValue<QMapLibre::Feature>(model_path_feature);
m_map->updateSource("modelPathSource", modelV2Path);
model_rcv_frame = sm.rcv_frame("uiPlan");
}
if (sm.updated("navRoute") && sm["navRoute"].getNavRoute().getCoordinates().size()) {
auto nav_dest = coordinate_from_param("NavDestination");
bool allow_open = std::exchange(last_valid_nav_dest, nav_dest) != nav_dest &&
@@ -231,10 +238,10 @@ void MapWindow::updateState(const UIState &s) {
if (locationd_valid) {
// Update current location marker
auto point = coordinate_to_collection(*last_position);
QMapbox::Feature feature1(QMapbox::Feature::PointType, point, {}, {});
QMapLibre::Feature feature1(QMapLibre::Feature::PointType, point, {}, {});
QVariantMap carPosSource;
carPosSource["type"] = "geojson";
carPosSource["data"] = QVariant::fromValue<QMapbox::Feature>(feature1);
carPosSource["data"] = QVariant::fromValue<QMapLibre::Feature>(feature1);
m_map->updateSource("carPosSource", carPosSource);
// Map bearing isn't updated when interacting, keep location marker up to date
@@ -275,16 +282,40 @@ void MapWindow::updateState(const UIState &s) {
qWarning() << "Updating navLayer with new route";
auto route = sm["navRoute"].getNavRoute();
auto route_points = capnp_coordinate_list_to_collection(route.getCoordinates());
QMapbox::Feature feature(QMapbox::Feature::LineStringType, route_points, {}, {});
QMapLibre::Feature feature(QMapLibre::Feature::LineStringType, route_points, {}, {});
QVariantMap navSource;
navSource["type"] = "geojson";
navSource["data"] = QVariant::fromValue<QMapbox::Feature>(feature);
navSource["data"] = QVariant::fromValue<QMapLibre::Feature>(feature);
m_map->updateSource("navSource", navSource);
m_map->setLayoutProperty("navLayer", "visibility", "visible");
route_rcv_frame = sm.rcv_frame("navRoute");
updateDestinationMarker();
}
// Map Styling - Credit goes to OPKR!
int map_style = uiState()->scene.map_style;
if (map_style != previous_map_style) {
std::unordered_map<int, std::string> styleUrls = {
{0, "mapbox://styles/commaai/clkqztk0f00ou01qyhsa5bzpj"}, // Stock openpilot
{1, "mapbox://styles/mapbox/streets-v11"}, // Mapbox Streets
{2, "mapbox://styles/mapbox/outdoors-v11"}, // Mapbox Outdoors
{3, "mapbox://styles/mapbox/light-v10"}, // Mapbox Light
{4, "mapbox://styles/mapbox/dark-v10"}, // Mapbox Dark
{5, "mapbox://styles/mapbox/satellite-v9"}, // Mapbox Satellite
{6, "mapbox://styles/mapbox/satellite-streets-v11"}, // Mapbox Satellite Streets
{7, "mapbox://styles/mapbox/navigation-day-v1"}, // Mapbox Navigation Day
{8, "mapbox://styles/mapbox/navigation-night-v1"}, // Mapbox Navigation Night
{9, "mapbox://styles/mapbox/traffic-night-v2"}, // Mapbox Traffic Night
{10, "mapbox://styles/mike854/clt0hm8mw01ok01p4blkr27jp"}, // mike854's (Satellite hybrid)
};
std::unordered_map<int, std::string>::iterator it = styleUrls.find(map_style);
m_map->setStyleUrl(QString::fromStdString(it->second));
}
previous_map_style = map_style;
}
void MapWindow::setError(const QString &err_str) {
@@ -301,24 +332,24 @@ void MapWindow::resizeGL(int w, int h) {
}
void MapWindow::initializeGL() {
m_map.reset(new QMapboxGL(this, m_settings, size(), 1));
m_map.reset(new QMapLibre::Map(this, m_settings, size(), 1));
if (last_position) {
m_map->setCoordinateZoom(*last_position, MAX_ZOOM);
} else {
m_map->setCoordinateZoom(QMapbox::Coordinate(64.31990695292795, -149.79038934046247), MIN_ZOOM);
m_map->setCoordinateZoom(QMapLibre::Coordinate(64.31990695292795, -149.79038934046247), MIN_ZOOM);
}
m_map->setMargins({0, 350, 0, 50});
m_map->setPitch(MIN_PITCH);
m_map->setStyleUrl("mapbox://styles/commaai/clkqztk0f00ou01qyhsa5bzpj");
QObject::connect(m_map.data(), &QMapboxGL::mapChanged, [=](QMapboxGL::MapChange change) {
QObject::connect(m_map.data(), &QMapLibre::Map::mapChanged, [=](QMapLibre::Map::MapChange change) {
// set global animation duration to 0 ms so visibility changes are instant
if (change == QMapboxGL::MapChange::MapChangeDidFinishLoadingStyle) {
if (change == QMapLibre::Map::MapChange::MapChangeDidFinishLoadingStyle) {
m_map->setTransitionOptions(0, 0);
}
if (change == QMapboxGL::MapChange::MapChangeDidFinishLoadingMap) {
if (change == QMapLibre::Map::MapChange::MapChangeDidFinishLoadingMap) {
loaded_once = true;
}
});
@@ -426,10 +457,10 @@ void MapWindow::updateDestinationMarker() {
auto nav_dest = coordinate_from_param("NavDestination");
if (nav_dest.has_value()) {
auto point = coordinate_to_collection(*nav_dest);
QMapbox::Feature feature(QMapbox::Feature::PointType, point, {}, {});
QMapLibre::Feature feature(QMapLibre::Feature::PointType, point, {}, {});
QVariantMap pinSource;
pinSource["type"] = "geojson";
pinSource["data"] = QVariant::fromValue<QMapbox::Feature>(feature);
pinSource["data"] = QVariant::fromValue<QMapLibre::Feature>(feature);
m_map->updateSource("pinSource", pinSource);
m_map->setPaintProperty("pinLayer", "visibility", "visible");
} else {

17
selfdrive/ui/qt/maps/map.h Executable file → Normal file
View File

@@ -6,7 +6,8 @@
#include <QGestureEvent>
#include <QLabel>
#include <QMap>
#include <QMapboxGL>
#include <QMapLibre/Map>
#include <QMapLibre/Settings>
#include <QMouseEvent>
#include <QOpenGLWidget>
#include <QPixmap>
@@ -27,7 +28,7 @@ class MapWindow : public QOpenGLWidget {
Q_OBJECT
public:
MapWindow(const QMapboxGLSettings &);
MapWindow(const QMapLibre::Settings &);
~MapWindow();
private:
@@ -35,8 +36,8 @@ private:
void paintGL() final;
void resizeGL(int w, int h) override;
QMapboxGLSettings m_settings;
QScopedPointer<QMapboxGL> m_map;
QMapLibre::Settings m_settings;
QScopedPointer<QMapLibre::Map> m_map;
void initLayers();
@@ -56,8 +57,8 @@ private:
int interaction_counter = 0;
// Position
std::optional<QMapbox::Coordinate> last_valid_nav_dest;
std::optional<QMapbox::Coordinate> last_position;
std::optional<QMapLibre::Coordinate> last_valid_nav_dest;
std::optional<QMapLibre::Coordinate> last_position;
std::optional<float> last_bearing;
FirstOrderFilter velocity_filter;
bool locationd_valid = false;
@@ -80,6 +81,10 @@ private:
// FrogPilot variables
Params params;
int previous_map_style;
uint64_t model_rcv_frame = 0;
private slots:
void updateState(const UIState &s);

0
selfdrive/ui/qt/maps/map_eta.cc Executable file → Normal file
View File

0
selfdrive/ui/qt/maps/map_eta.h Executable file → Normal file
View File

37
selfdrive/ui/qt/maps/map_helpers.cc Executable file → Normal file
View File

@@ -16,24 +16,25 @@ QString get_mapbox_token() {
return MAPBOX_TOKEN.isEmpty() ? CommaApi::create_jwt({}, 4 * 7 * 24 * 3600) : MAPBOX_TOKEN;
}
QMapboxGLSettings get_mapbox_settings() {
QMapboxGLSettings settings;
QMapLibre::Settings get_mapbox_settings() {
QMapLibre::Settings settings;
settings.setProviderTemplate(QMapLibre::Settings::ProviderTemplate::MapboxProvider);
if (!Hardware::PC()) {
settings.setCacheDatabasePath(MAPS_CACHE_PATH);
settings.setCacheDatabaseMaximumSize(100 * 1024 * 1024);
}
settings.setApiBaseUrl(MAPS_HOST);
settings.setAccessToken(get_mapbox_token());
settings.setApiKey(get_mapbox_token());
return settings;
}
QGeoCoordinate to_QGeoCoordinate(const QMapbox::Coordinate &in) {
QGeoCoordinate to_QGeoCoordinate(const QMapLibre::Coordinate &in) {
return QGeoCoordinate(in.first, in.second);
}
QMapbox::CoordinatesCollections model_to_collection(
QMapLibre::CoordinatesCollections model_to_collection(
const cereal::LiveLocationKalman::Measurement::Reader &calibratedOrientationECEF,
const cereal::LiveLocationKalman::Measurement::Reader &positionECEF,
const cereal::XYZTData::Reader &line){
@@ -42,7 +43,7 @@ QMapbox::CoordinatesCollections model_to_collection(
Eigen::Vector3d orient(calibratedOrientationECEF.getValue()[0], calibratedOrientationECEF.getValue()[1], calibratedOrientationECEF.getValue()[2]);
Eigen::Matrix3d ecef_from_local = euler2rot(orient);
QMapbox::Coordinates coordinates;
QMapLibre::Coordinates coordinates;
auto x = line.getX();
auto y = line.getY();
auto z = line.getZ();
@@ -52,28 +53,28 @@ QMapbox::CoordinatesCollections model_to_collection(
coordinates.push_back({point_geodetic.lat, point_geodetic.lon});
}
return {QMapbox::CoordinatesCollection{coordinates}};
return {QMapLibre::CoordinatesCollection{coordinates}};
}
QMapbox::CoordinatesCollections coordinate_to_collection(const QMapbox::Coordinate &c) {
QMapbox::Coordinates coordinates{c};
return {QMapbox::CoordinatesCollection{coordinates}};
QMapLibre::CoordinatesCollections coordinate_to_collection(const QMapLibre::Coordinate &c) {
QMapLibre::Coordinates coordinates{c};
return {QMapLibre::CoordinatesCollection{coordinates}};
}
QMapbox::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List<cereal::NavRoute::Coordinate>::Reader& coordinate_list) {
QMapbox::Coordinates coordinates;
QMapLibre::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List<cereal::NavRoute::Coordinate>::Reader& coordinate_list) {
QMapLibre::Coordinates coordinates;
for (auto const &c : coordinate_list) {
coordinates.push_back({c.getLatitude(), c.getLongitude()});
}
return {QMapbox::CoordinatesCollection{coordinates}};
return {QMapLibre::CoordinatesCollection{coordinates}};
}
QMapbox::CoordinatesCollections coordinate_list_to_collection(const QList<QGeoCoordinate> &coordinate_list) {
QMapbox::Coordinates coordinates;
QMapLibre::CoordinatesCollections coordinate_list_to_collection(const QList<QGeoCoordinate> &coordinate_list) {
QMapLibre::Coordinates coordinates;
for (auto &c : coordinate_list) {
coordinates.push_back({c.latitude(), c.longitude()});
}
return {QMapbox::CoordinatesCollection{coordinates}};
return {QMapLibre::CoordinatesCollection{coordinates}};
}
QList<QGeoCoordinate> polyline_to_coordinate_list(const QString &polylineString) {
@@ -118,7 +119,7 @@ QList<QGeoCoordinate> polyline_to_coordinate_list(const QString &polylineString)
return path;
}
std::optional<QMapbox::Coordinate> coordinate_from_param(const std::string &param) {
std::optional<QMapLibre::Coordinate> coordinate_from_param(const std::string &param) {
QString json_str = QString::fromStdString(Params().get(param));
if (json_str.isEmpty()) return {};
@@ -127,7 +128,7 @@ std::optional<QMapbox::Coordinate> coordinate_from_param(const std::string &para
QJsonObject json = doc.object();
if (json["latitude"].isDouble() && json["longitude"].isDouble()) {
QMapbox::Coordinate coord(json["latitude"].toDouble(), json["longitude"].toDouble());
QMapLibre::Coordinate coord(json["latitude"].toDouble(), json["longitude"].toDouble());
return coord;
} else {
return {};

17
selfdrive/ui/qt/maps/map_helpers.h Executable file → Normal file
View File

@@ -3,8 +3,9 @@
#include <optional>
#include <string>
#include <utility>
#include <QMapLibre/Map>
#include <QMapLibre/Settings>
#include <eigen3/Eigen/Dense>
#include <QMapboxGL>
#include <QGeoCoordinate>
#include "common/params.h"
@@ -18,15 +19,15 @@ const QString MAPS_HOST = util::getenv("MAPS_HOST", MAPBOX_TOKEN.isEmpty() ? "ht
const QString MAPS_CACHE_PATH = "/data/mbgl-cache-navd.db";
QString get_mapbox_token();
QMapboxGLSettings get_mapbox_settings();
QGeoCoordinate to_QGeoCoordinate(const QMapbox::Coordinate &in);
QMapbox::CoordinatesCollections model_to_collection(
QMapLibre::Settings get_mapbox_settings();
QGeoCoordinate to_QGeoCoordinate(const QMapLibre::Coordinate &in);
QMapLibre::CoordinatesCollections model_to_collection(
const cereal::LiveLocationKalman::Measurement::Reader &calibratedOrientationECEF,
const cereal::LiveLocationKalman::Measurement::Reader &positionECEF,
const cereal::XYZTData::Reader &line);
QMapbox::CoordinatesCollections coordinate_to_collection(const QMapbox::Coordinate &c);
QMapbox::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List<cereal::NavRoute::Coordinate>::Reader &coordinate_list);
QMapbox::CoordinatesCollections coordinate_list_to_collection(const QList<QGeoCoordinate> &coordinate_list);
QMapLibre::CoordinatesCollections coordinate_to_collection(const QMapLibre::Coordinate &c);
QMapLibre::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List<cereal::NavRoute::Coordinate>::Reader &coordinate_list);
QMapLibre::CoordinatesCollections coordinate_list_to_collection(const QList<QGeoCoordinate> &coordinate_list);
QList<QGeoCoordinate> polyline_to_coordinate_list(const QString &polylineString);
std::optional<QMapbox::Coordinate> coordinate_from_param(const std::string &param);
std::optional<QMapLibre::Coordinate> coordinate_from_param(const std::string &param);
std::pair<QString, QString> map_format_distance(float d, bool is_metric);

0
selfdrive/ui/qt/maps/map_instructions.cc Executable file → Normal file
View File

0
selfdrive/ui/qt/maps/map_instructions.h Executable file → Normal file
View File

7
selfdrive/ui/qt/maps/map_panel.cc Executable file → Normal file
View File

@@ -8,7 +8,7 @@
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/ui.h"
MapPanel::MapPanel(const QMapboxGLSettings &mapboxSettings, QWidget *parent) : QFrame(parent) {
MapPanel::MapPanel(const QMapLibre::Settings &mapboxSettings, QWidget *parent) : QFrame(parent) {
content_stack = new QStackedLayout(this);
content_stack->setContentsMargins(0, 0, 0, 0);
@@ -41,8 +41,3 @@ void MapPanel::toggleMapSettings() {
emit mapPanelRequested();
show();
}
void MapPanel::setVisible(bool visible) {
QFrame::setVisible(visible);
uiState()->scene.map_open = visible;
}

5
selfdrive/ui/qt/maps/map_panel.h Executable file → Normal file
View File

@@ -1,15 +1,14 @@
#pragma once
#include <QFrame>
#include <QMapboxGL>
#include <QMapLibre/Settings>
#include <QStackedLayout>
class MapPanel : public QFrame {
Q_OBJECT
public:
explicit MapPanel(const QMapboxGLSettings &settings, QWidget *parent = nullptr);
void setVisible(bool visible);
explicit MapPanel(const QMapLibre::Settings &settings, QWidget *parent = nullptr);
signals:
void mapPanelRequested();

14
selfdrive/ui/qt/maps/map_settings.cc Executable file → Normal file
View File

@@ -62,13 +62,7 @@ MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) {
title->setStyleSheet("color: #FFFFFF; font-size: 54px; font-weight: 600;");
heading->addWidget(title);
// NOO without Prime IP extraction
if (notPrime) {
ipAddress = QString("%1:8082").arg(wifi->getIp4Address());
subtitle = new QLabel(tr("Manage at %1").arg(ipAddress), this);
} else {
subtitle = new QLabel(tr("Manage at connect.comma.ai"), this);
}
subtitle->setStyleSheet("color: #A0A0A0; font-size: 40px; font-weight: 300;");
heading->addWidget(subtitle);
}
@@ -99,6 +93,8 @@ MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) {
setStyleSheet("MapSettings { background-color: #333333; }");
QObject::connect(NavManager::instance(), &NavManager::updated, this, &MapSettings::refresh);
wifi = new WifiManager(this);
}
void MapSettings::showEvent(QShowEvent *event) {
@@ -145,9 +141,9 @@ void MapSettings::refresh() {
setUpdatesEnabled(true);
// NOO without Prime IP update
if (notPrime) {
ipAddress = QString("%1:8082").arg(wifi->getIp4Address());
// Use IP for NOO without Prime
if (!uiState()->hasPrime()) {
QString ipAddress = QString("%1:8082").arg(wifi->getIp4Address());
subtitle->setText(tr("Manage at %1").arg(ipAddress));
}
}

5
selfdrive/ui/qt/maps/map_settings.h Executable file → Normal file
View File

@@ -66,10 +66,9 @@ private:
std::vector<DestinationWidget *> widgets;
// FrogPilot variables
bool notPrime = Params().getInt("PrimeType") == 0;
QLabel *subtitle;
QString ipAddress;
WifiManager *wifi = new WifiManager(this);
WifiManager *wifi;
signals:
void closeSettings();

12
selfdrive/ui/qt/network/networking.cc Executable file → Normal file
View File

@@ -82,11 +82,11 @@ void Networking::connectToNetwork(const Network n) {
if (wifi->isKnownConnection(n.ssid)) {
wifi->activateWifiConnection(n.ssid);
} else if (n.security_type == SecurityType::OPEN) {
wifi->connect(n);
wifi->connect(n, false);
} else if (n.security_type == SecurityType::WPA) {
QString pass = InputDialog::getText(tr("Enter password"), this, tr("for \"%1\"").arg(QString::fromUtf8(n.ssid)), true, 8);
if (!pass.isEmpty()) {
wifi->connect(n, pass);
wifi->connect(n, false, pass);
}
}
}
@@ -96,7 +96,7 @@ void Networking::wrongPassword(const QString &ssid) {
const Network &n = wifi->seenNetworks.value(ssid);
QString pass = InputDialog::getText(tr("Wrong password"), this, tr("for \"%1\"").arg(QString::fromUtf8(n.ssid)), true, 8);
if (!pass.isEmpty()) {
wifi->connect(n, pass);
wifi->connect(n, false, pass);
}
}
}
@@ -131,7 +131,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
list->addItem(tetheringToggle);
QObject::connect(tetheringToggle, &ToggleControl::toggleFlipped, this, &AdvancedNetworking::toggleTethering);
if (params.getBool("TetheringEnabled")) {
tetheringToggle->setVisualOn();
tetheringToggle->refresh();
uiState()->scene.tethering_enabled = true;
}
@@ -196,9 +196,9 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
hidden_network.ssid = ssid.toUtf8();
if (!pass.isEmpty()) {
hidden_network.security_type = SecurityType::WPA;
wifi->connect(hidden_network, pass);
wifi->connect(hidden_network, true, pass);
} else {
wifi->connect(hidden_network);
wifi->connect(hidden_network, true);
}
emit requestWifiScreen();
}

0
selfdrive/ui/qt/network/networking.h Executable file → Normal file
View File

0
selfdrive/ui/qt/network/networkmanager.h Executable file → Normal file
View File

3
selfdrive/ui/qt/network/wifi_manager.cc Executable file → Normal file
View File

@@ -166,7 +166,7 @@ SecurityType WifiManager::getSecurityType(const QVariantMap &properties) {
}
}
void WifiManager::connect(const Network &n, const QString &password, const QString &username) {
void WifiManager::connect(const Network &n, const bool is_hidden, const QString &password, const QString &username) {
setCurrentConnecting(n.ssid);
forgetConnection(n.ssid); // Clear all connections that may already exist to the network we are connecting
Connection connection;
@@ -176,6 +176,7 @@ void WifiManager::connect(const Network &n, const QString &password, const QStri
connection["connection"]["autoconnect-retries"] = 0;
connection["802-11-wireless"]["ssid"] = n.ssid;
connection["802-11-wireless"]["hidden"] = is_hidden;
connection["802-11-wireless"]["mode"] = "infrastructure";
if (n.security_type == SecurityType::WPA) {

2
selfdrive/ui/qt/network/wifi_manager.h Executable file → Normal file
View File

@@ -52,7 +52,7 @@ public:
std::optional<QDBusPendingCall> activateWifiConnection(const QString &ssid);
NetworkType currentNetworkType();
void updateGsmSettings(bool roaming, QString apn, bool metered);
void connect(const Network &ssid, const QString &password = {}, const QString &username = {});
void connect(const Network &ssid, const bool is_hidden = false, const QString &password = {}, const QString &username = {});
// Tethering functions
void setTetheringEnabled(bool enabled);

0
selfdrive/ui/qt/offroad/driverview.cc Executable file → Normal file
View File

0
selfdrive/ui/qt/offroad/driverview.h Executable file → Normal file
View File

0
selfdrive/ui/qt/offroad/experimental_mode.cc Executable file → Normal file
View File

0
selfdrive/ui/qt/offroad/experimental_mode.h Executable file → Normal file
View File

14
selfdrive/ui/qt/offroad/onboarding.cc Executable file → Normal file
View File

@@ -183,8 +183,8 @@ void DeclinePage::showEvent(QShowEvent *event) {
void OnboardingWindow::updateActiveScreen() {
if (!accepted_terms) {
setCurrentIndex(0);
// } else if (!training_done && !params.getBool("Passive")) {
// setCurrentIndex(1);
} else if (!training_done) {
setCurrentIndex(1);
} else {
emit onboardingDone();
}
@@ -199,7 +199,7 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) {
TermsPage* terms = new TermsPage(this);
addWidget(terms);
connect(terms, &TermsPage::acceptedTerms, [=]() {
Params().put("HasAcceptedTerms", current_terms_version);
params.put("HasAcceptedTerms", current_terms_version);
accepted_terms = true;
updateActiveScreen();
});
@@ -209,7 +209,7 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) {
addWidget(tr);
connect(tr, &TrainingGuide::completedTraining, [=]() {
training_done = true;
Params().put("CompletedTrainingVersion", current_training_version);
params.put("CompletedTrainingVersion", current_training_version);
updateActiveScreen();
});
@@ -230,11 +230,5 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) {
background-color: #4F4F4F;
}
)");
// # Oscar sez
Params().put("HasAcceptedTerms", current_terms_version);
Params().put("CompletedTrainingVersion", current_training_version);
accepted_terms = true;
emit onboardingDone();
updateActiveScreen();
}

0
selfdrive/ui/qt/offroad/onboarding.h Executable file → Normal file
View File

363
selfdrive/ui/qt/offroad/settings.cc Executable file → Normal file
View File

@@ -2,6 +2,8 @@
#include <cassert>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <string>
#include <tuple>
#include <vector>
@@ -25,9 +27,9 @@
#include "selfdrive/ui/qt/qt_window.h"
#include "selfdrive/frogpilot/navigation/ui/navigation_settings.h"
#include "selfdrive/frogpilot/ui/control_settings.h"
#include "selfdrive/frogpilot/ui/vehicle_settings.h"
#include "selfdrive/frogpilot/ui/visual_settings.h"
#include "selfdrive/frogpilot/ui/qt/offroad/control_settings.h"
#include "selfdrive/frogpilot/ui/qt/offroad/vehicle_settings.h"
#include "selfdrive/frogpilot/ui/qt/offroad/visual_settings.h"
TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
// param, title, desc, icon
@@ -97,9 +99,14 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
std::vector<QString> longi_button_texts{tr("Aggressive"), tr("Standard"), tr("Relaxed")};
long_personality_setting = new ButtonParamControl("LongitudinalPersonality", tr("Driving Personality"),
tr("Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. "
"In relaxed mode openpilot will stay further away from lead cars."),
"In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with "
"your steering wheel distance button."),
"../assets/offroad/icon_speed_limit.png",
longi_button_texts);
// set up uiState update for personality setting
QObject::connect(uiState(), &UIState::uiUpdate, this, &TogglesPanel::updateState);
for (auto &[param, title, desc, icon] : toggle_defs) {
auto toggle = new ParamControl(param, title, desc, icon, this);
@@ -110,7 +117,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
toggles[param.toStdString()] = toggle;
// insert longitudinal personality after NDOG toggle
if (param == "DisengageOnAccelerator" && !params.getInt("AdjustablePersonalities")) {
if (param == "DisengageOnAccelerator") {
addItem(long_personality_setting);
}
}
@@ -129,6 +136,18 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
});
}
void TogglesPanel::updateState(const UIState &s) {
const SubMaster &sm = *(s.sm);
if (sm.updated("controlsState")) {
auto personality = sm["controlsState"].getControlsState().getPersonality();
if (personality != s.scene.personality && s.scene.started && isVisible()) {
long_personality_setting->setCheckedButton(static_cast<int>(personality));
}
uiState()->scene.personality = personality;
}
}
void TogglesPanel::expandToggleDescription(const QString &param) {
toggles[param.toStdString()]->showDescription();
}
@@ -138,6 +157,13 @@ void TogglesPanel::showEvent(QShowEvent *event) {
}
void TogglesPanel::updateToggles() {
auto disengage_on_accelerator_toggle = toggles["DisengageOnAccelerator"];
disengage_on_accelerator_toggle->setVisible(!params.getBool("AlwaysOnLateral"));
auto driver_camera_toggle = toggles["RecordFront"];
driver_camera_toggle->setVisible(!(params.getBool("DeviceManagement") && params.getBool("NoLogging") && params.getBool("NoUploads")));
auto nav_settings_left_toggle = toggles["NavSettingLeftSide"];
nav_settings_left_toggle->setVisible(!params.getBool("FullMap"));
auto experimental_mode_toggle = toggles["ExperimentalMode"];
auto op_long_toggle = toggles["ExperimentalLongitudinalEnabled"];
const QString e2e_description = QString("%1<br>"
@@ -173,7 +199,12 @@ void TogglesPanel::updateToggles() {
op_long_toggle->setVisible(CP.getExperimentalLongitudinalAvailable() && !is_release);
if (hasLongitudinalControl(CP)) {
// normal description and toggle
experimental_mode_toggle->setEnabled(!params.getBool("ConditionalExperimental"));
bool conditional_experimental = params.getBool("ConditionalExperimental");
if (conditional_experimental) {
params.putBool("ExperimentalMode", true);
experimental_mode_toggle->refresh();
}
experimental_mode_toggle->setEnabled(!conditional_experimental);
experimental_mode_toggle->setDescription(e2e_description);
long_personality_setting->setEnabled(true);
} else {
@@ -225,7 +256,6 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
});
addItem(resetCalibBtn);
if (!params.getBool("Passive")) {
auto retrainingBtn = new ButtonControl(tr("Review Training Guide"), tr("REVIEW"), tr("Review the rules, features, and limitations of openpilot"));
connect(retrainingBtn, &ButtonControl::clicked, [=]() {
if (ConfirmationDialog::confirm(tr("Are you sure you want to review the training guide?"), tr("Review"), this)) {
@@ -233,7 +263,6 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
}
});
addItem(retrainingBtn);
}
if (Hardware::TICI()) {
auto regulatoryBtn = new ButtonControl(tr("Regulatory"), tr("VIEW"), "");
@@ -258,44 +287,251 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
addItem(translateBtn);
// Delete driving footage button
auto deleteFootageBtn = new ButtonControl(tr("Delete Driving Data"), tr("DELETE"), tr("This button provides a swift and secure way to permanently delete all "
auto deleteDrivingDataBtn = new ButtonControl(tr("Delete Driving Data"), tr("DELETE"), tr("This button provides a swift and secure way to permanently delete all "
"stored driving footage and data from your device. Ideal for maintaining privacy or freeing up space.")
);
connect(deleteFootageBtn, &ButtonControl::clicked, [this]() {
connect(deleteDrivingDataBtn, &ButtonControl::clicked, [=]() {
if (!ConfirmationDialog::confirm(tr("Are you sure you want to permanently delete all of your driving footage and data?"), tr("Delete"), this)) return;
std::thread([&] {
deleteDrivingDataBtn->setValue(tr("Deleting footage..."));
std::system("rm -rf /data/media/0/realdata");
deleteDrivingDataBtn->setValue(tr("Deleted!"));
std::this_thread::sleep_for(std::chrono::seconds(3));
deleteDrivingDataBtn->setValue("");
}).detach();
});
addItem(deleteFootageBtn);
addItem(deleteDrivingDataBtn);
// Panda flashing button
auto flashPandaBtn = new ButtonControl(tr("Flash Panda"), tr("FLASH"), "Use this button to troubleshoot and update the Panda device's firmware.");
connect(flashPandaBtn, &ButtonControl::clicked, [this]() {
if (!ConfirmationDialog::confirm(tr("Are you sure you want to flash the Panda?"), tr("Flash"), this)) return;
auto flashPandaBtn = new ButtonControl(tr("Flash Panda"), tr("FLASH"), tr("Use this button to troubleshoot and update the Panda device's firmware."));
connect(flashPandaBtn, &ButtonControl::clicked, [=]() {
if (ConfirmationDialog::confirm(tr("Are you sure you want to flash the Panda?"), tr("Flash"), this)) {
std::thread([=]() {
flashPandaBtn->setValue(tr("Flashing..."));
QProcess process;
// Get Panda type
SubMaster &sm = *(uiState()->sm);
auto pandaStates = sm["pandaStates"].getPandaStates();
// Choose recovery script based on Panda type
if (pandaStates.size() != 0) {
auto pandaType = pandaStates[0].getPandaType();
bool isRedPanda = (pandaType == cereal::PandaState::PandaType::RED_PANDA ||
pandaType == cereal::PandaState::PandaType::RED_PANDA_V2);
QString recoveryScript = isRedPanda ? "./recover.sh" : "./recover.py";
// Run recovery script and flash Panda
process.setWorkingDirectory("/data/openpilot/panda/board");
process.start("/bin/sh", QStringList{"-c", recoveryScript});
process.start("/bin/sh", QStringList{"-c", "./recover.py"});
process.waitForFinished();
process.start("/bin/sh", QStringList{"-c", "./flash.py"});
process.waitForFinished();
process.setWorkingDirectory("/data/openpilot/panda/tests");
process.start("/bin/sh", QStringList{"-c", "python reflash_internal_panda.py"});
process.waitForFinished();
Hardware::soft_reboot();
}).detach();
}
// Run the killall script as a redundancy
process.setWorkingDirectory("/data/openpilot/panda");
process.start("/bin/sh", QStringList{"-c", "pkill -f boardd; PYTHONPATH=.. python -c \"from panda import Panda; Panda().flash()\""});
process.waitForFinished();
Hardware::reboot();
});
addItem(flashPandaBtn);
// Reset toggle button
auto resetTogglesBtn = new ButtonControl(tr("Reset Toggle Settings"), tr("RESET"), tr("Reset your toggle settings back to default."));
connect(resetTogglesBtn, &ButtonControl::clicked, [=]() {
if (!ConfirmationDialog::confirm(tr("Are you sure you want to completely reset your toggle settings? This is irreversible!"), tr("Reset"), this)) return;
std::thread([&] {
resetTogglesBtn->setValue(tr("Resetting toggles..."));
std::system("find /data/params -type f ! -name 'FrogPilotDrives' ! -name 'FrogPilotMinutes' ! -name 'FrogPilotKilometers' -exec rm {} +");
std::system("find /persist/params -type f ! -name 'FrogPilotDrives' ! -name 'FrogPilotMinutes' ! -name 'FrogPilotKilometers' -exec rm {} +");
Hardware::soft_reboot();
}).detach();
});
addItem(resetTogglesBtn);
// Backup FrogPilot
std::vector<QString> frogpilotBackupOptions{tr("Backup"), tr("Delete"), tr("Restore")};
FrogPilotButtonsControl *frogpilotBackup = new FrogPilotButtonsControl(tr("FrogPilot Backups"), tr("Backup, delete, or restore your FrogPilot backups."), "", frogpilotBackupOptions);
connect(frogpilotBackup, &FrogPilotButtonsControl::buttonClicked, [=](int id) {
QDir backupDir("/data/backups");
if (id == 0) {
QString nameSelection = InputDialog::getText(tr("Name your backup"), this, "", false, 1);
if (!nameSelection.isEmpty()) {
std::thread([=]() {
frogpilotBackup->setValue(tr("Backing up..."));
std::string fullBackupPath = backupDir.absolutePath().toStdString() + "/" + nameSelection.toStdString();
std::ostringstream commandStream;
commandStream << "mkdir -p " << std::quoted(fullBackupPath)
<< " && rsync -av /data/openpilot/ " << std::quoted(fullBackupPath + "/");
std::string command = commandStream.str();
int result = std::system(command.c_str());
if (result == 0) {
std::cout << "Backup successful to " << fullBackupPath << std::endl;
frogpilotBackup->setValue(tr("Success!"));
std::this_thread::sleep_for(std::chrono::seconds(3));
frogpilotBackup->setValue("");
} else {
std::cerr << "Backup failed with error code: " << result << std::endl;
frogpilotBackup->setValue(tr("Failed..."));
std::this_thread::sleep_for(std::chrono::seconds(3));
frogpilotBackup->setValue("");
}
}).detach();
}
} else if (id == 1) {
QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
QString selection = MultiOptionDialog::getSelection(tr("Select a backup to delete"), backupNames, "", this);
if (!selection.isEmpty()) {
if (!ConfirmationDialog::confirm(tr("Are you sure you want to delete this backup?"), tr("Delete"), this)) return;
std::thread([=]() {
frogpilotBackup->setValue(tr("Deleting..."));
QDir dirToDelete(backupDir.absoluteFilePath(selection));
if (dirToDelete.removeRecursively()) {
frogpilotBackup->setValue(tr("Deleted!"));
} else {
frogpilotBackup->setValue(tr("Failed..."));
}
std::this_thread::sleep_for(std::chrono::seconds(3));
frogpilotBackup->setValue("");
}).detach();
}
} else {
QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
QString selection = MultiOptionDialog::getSelection(tr("Select a restore point"), backupNames, "", this);
if (!selection.isEmpty()) {
if (!ConfirmationDialog::confirm(tr("Are you sure you want to restore this version of FrogPilot?"), tr("Restore"), this)) return;
std::thread([=]() {
frogpilotBackup->setValue(tr("Restoring..."));
std::string sourcePath = backupDir.absolutePath().toStdString() + "/" + selection.toStdString();
std::string targetPath = "/data/safe_staging/finalized";
std::string consistentFilePath = targetPath + "/.overlay_consistent";
std::ostringstream commandStream;
commandStream << "rsync -av --delete --exclude='.overlay_consistent' " << std::quoted(sourcePath + "/") << " " << std::quoted(targetPath + "/");
std::string command = commandStream.str();
int result = std::system(command.c_str());
if (result == 0) {
std::cout << "Restore successful from " << sourcePath << " to " << targetPath << std::endl;
std::ofstream consistentFile(consistentFilePath);
if (consistentFile) {
consistentFile.close();
std::cout << ".overlay_consistent file created successfully." << std::endl;
} else {
std::cerr << "Failed to create .overlay_consistent file." << std::endl;
frogpilotBackup->setValue(tr("Failed..."));
std::this_thread::sleep_for(std::chrono::seconds(3));
frogpilotBackup->setValue("");
}
Hardware::soft_reboot();
} else {
std::cerr << "Restore failed with error code: " << result << std::endl;
}
}).detach();
}
}
});
addItem(frogpilotBackup);
// Backup toggles
std::vector<QString> toggleBackupOptions{tr("Backup"), tr("Delete"), tr("Restore")};
FrogPilotButtonsControl *toggleBackup = new FrogPilotButtonsControl(tr("Toggle Backups"), tr("Backup, delete, or restore your toggle backups."), "", toggleBackupOptions);
connect(toggleBackup, &FrogPilotButtonsControl::buttonClicked, [=](int id) {
QDir backupDir("/data/toggle_backups");
if (id == 0) {
QString nameSelection = InputDialog::getText(tr("Name your backup"), this, "", false, 1);
if (!nameSelection.isEmpty()) {
std::thread([=]() {
toggleBackup->setValue(tr("Backing up..."));
std::string fullBackupPath = backupDir.absolutePath().toStdString() + "/" + nameSelection.toStdString() + "/";
std::ostringstream commandStream;
commandStream << "mkdir -p " << std::quoted(fullBackupPath)
<< " && rsync -av /data/params/d/ " << std::quoted(fullBackupPath);
std::string command = commandStream.str();
int result = std::system(command.c_str());
if (result == 0) {
std::cout << "Backup successful to " << fullBackupPath << std::endl;
toggleBackup->setValue(tr("Success!"));
std::this_thread::sleep_for(std::chrono::seconds(3));
toggleBackup->setValue("");
} else {
std::cerr << "Backup failed with error code: " << result << std::endl;
toggleBackup->setValue(tr("Failed..."));
std::this_thread::sleep_for(std::chrono::seconds(3));
toggleBackup->setValue("");
}
}).detach();
}
} else if (id == 1) {
QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
QString selection = MultiOptionDialog::getSelection(tr("Select a backup to delete"), backupNames, "", this);
if (!selection.isEmpty()) {
if (!ConfirmationDialog::confirm(tr("Are you sure you want to delete this backup?"), tr("Delete"), this)) return;
std::thread([=]() {
toggleBackup->setValue(tr("Deleting..."));
QDir dirToDelete(backupDir.absoluteFilePath(selection));
if (dirToDelete.removeRecursively()) {
toggleBackup->setValue(tr("Deleted!"));
} else {
toggleBackup->setValue(tr("Failed..."));
}
std::this_thread::sleep_for(std::chrono::seconds(3));
toggleBackup->setValue("");
}).detach();
}
} else {
QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
QString selection = MultiOptionDialog::getSelection(tr("Select a restore point"), backupNames, "", this);
if (!selection.isEmpty()) {
if (!ConfirmationDialog::confirm(tr("Are you sure you want to restore this toggle backup?"), tr("Restore"), this)) return;
std::thread([=]() {
toggleBackup->setValue(tr("Restoring..."));
std::string sourcePath = backupDir.absolutePath().toStdString() + "/" + selection.toStdString() + "/";
std::string targetPath = "/data/params/d/";
std::ostringstream commandStream;
commandStream << "rsync -av --delete " << std::quoted(sourcePath) << " " << std::quoted(targetPath);
std::string command = commandStream.str();
int result = std::system(command.c_str());
if (result == 0) {
std::cout << "Restore successful from " << sourcePath << " to " << targetPath << std::endl;
toggleBackup->setValue(tr("Success!"));
paramsMemory.putBool("FrogPilotTogglesUpdated", true);
std::this_thread::sleep_for(std::chrono::seconds(3));
toggleBackup->setValue("");
paramsMemory.putBool("FrogPilotTogglesUpdated", false);
} else {
std::cerr << "Restore failed with error code: " << result << std::endl;
toggleBackup->setValue(tr("Failed..."));
std::this_thread::sleep_for(std::chrono::seconds(3));
toggleBackup->setValue("");
}
}).detach();
}
}
});
addItem(toggleBackup);
auto lockDoorsButton = new ButtonControl(tr("Lock Doors"), tr("LOCK"), tr("Use this button to lock the doors on Toyota/Lexus vehicles."));
connect(lockDoorsButton, &ButtonControl::clicked, [this]() {
QProcess *process = new QProcess(this);
QString scriptPath = "/data/openpilot/frogpilot/controls/lib/lock_doors.py";
QStringList arguments{"--lock"};
process->start("python3", QStringList() << scriptPath << arguments);
});
addItem(lockDoorsButton);
QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) {
for (auto btn : findChildren<ButtonControl *>()) {
btn->setEnabled(offroad);
@@ -311,6 +547,11 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
power_layout->addWidget(reboot_btn);
QObject::connect(reboot_btn, &QPushButton::clicked, this, &DevicePanel::reboot);
QPushButton *softreboot_btn = new QPushButton(tr("Soft Reboot"));
softreboot_btn->setObjectName("softreboot_btn");
power_layout->addWidget(softreboot_btn);
QObject::connect(softreboot_btn, &QPushButton::clicked, this, &DevicePanel::softreboot);
QPushButton *poweroff_btn = new QPushButton(tr("Power Off"));
poweroff_btn->setObjectName("poweroff_btn");
power_layout->addWidget(poweroff_btn);
@@ -321,6 +562,8 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
}
setStyleSheet(R"(
#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; }
@@ -366,6 +609,18 @@ void DevicePanel::reboot() {
}
}
void DevicePanel::softreboot() {
if (!uiState()->engaged()) {
if (ConfirmationDialog::confirm(tr("Are you sure you want to soft reboot?"), tr("Soft Reboot"), this)) {
if (!uiState()->engaged()) {
params.putBool("DoSoftReboot", true);
}
}
} else {
ConfirmationDialog::alert(tr("Disengage to Soft Reboot"), this);
}
}
void DevicePanel::poweroff() {
if (!uiState()->engaged()) {
if (ConfirmationDialog::confirm(tr("Are you sure you want to power off?"), tr("Power Off"), this)) {
@@ -379,6 +634,15 @@ void DevicePanel::poweroff() {
}
}
void SettingsWindow::hideEvent(QHideEvent *event) {
closeParentToggle();
parentToggleOpen = false;
subParentToggleOpen = false;
previousScrollPosition = 0;
}
void SettingsWindow::showEvent(QShowEvent *event) {
setCurrentPanel(0);
}
@@ -396,33 +660,34 @@ 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();
// close button
QPushButton *close_btn = new QPushButton(tr("← Back"));
close_btn->setStyleSheet(R"(
QPushButton {
font-size: 50px;
padding-bottom: 0px;
border 1px grey solid;
color: white;
border-radius: 25px;
background-color: #292929;
background: #292929;
font-size: 50px;
font-weight: 500;
}
QPushButton:pressed {
background-color: #3B3B3B;
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, [this]() {
if (frogPilotTogglesOpen) {
frogPilotTogglesOpen = false;
this->closeParentToggle();
if (subParentToggleOpen) {
closeSubParentToggle();
subParentToggleOpen = false;
} else if (parentToggleOpen) {
closeParentToggle();
parentToggleOpen = false;
} else {
this->closeSettings();
closeSettings();
}
});
@@ -435,20 +700,19 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
QObject::connect(this, &SettingsWindow::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription);
QObject::connect(toggles, &TogglesPanel::updateMetric, this, &SettingsWindow::updateMetric);
// FrogPilotControlsPanel *frogpilotControls = new FrogPilotControlsPanel(this);
// QObject::connect(frogpilotControls, &FrogPilotControlsPanel::closeParentToggle, this, [this]() {frogPilotTogglesOpen = false;});
// QObject::connect(frogpilotControls, &FrogPilotControlsPanel::openParentToggle, this, [this]() {frogPilotTogglesOpen = true;});
FrogPilotControlsPanel *frogpilotControls = new FrogPilotControlsPanel(this);
QObject::connect(frogpilotControls, &FrogPilotControlsPanel::openSubParentToggle, this, [this]() {subParentToggleOpen = true;});
QObject::connect(frogpilotControls, &FrogPilotControlsPanel::openParentToggle, this, [this]() {parentToggleOpen = true;});
FrogPilotVisualsPanel *frogpilotVisuals = new FrogPilotVisualsPanel(this);
QObject::connect(frogpilotVisuals, &FrogPilotVisualsPanel::closeParentToggle, this, [this]() {frogPilotTogglesOpen = false;});
QObject::connect(frogpilotVisuals, &FrogPilotVisualsPanel::openParentToggle, this, [this]() {frogPilotTogglesOpen = true;});
QObject::connect(frogpilotVisuals, &FrogPilotVisualsPanel::openParentToggle, this, [this]() {parentToggleOpen = true;});
QList<QPair<QString, QWidget *>> panels = {
{tr("Device"), device},
{tr("Network"), new Networking(this)},
{tr("Toggles"), toggles},
{tr("Software"), new SoftwarePanel(this)},
// {tr("Controls"), frogpilotControls},
{tr("Controls"), frogpilotControls},
{tr("Navigation"), new FrogPilotNavigationPanel(this)},
{tr("Vehicles"), new FrogPilotVehiclesPanel(this)},
{tr("Visuals"), frogpilotVisuals},
@@ -484,21 +748,24 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
ScrollView *panel_frame = new ScrollView(panel, this);
panel_widget->addWidget(panel_frame);
if (name == tr("Controls") || name == tr("Visuals")) {
if (name == tr("Controls")) {
QScrollBar *scrollbar = panel_frame->verticalScrollBar();
QObject::connect(scrollbar, &QScrollBar::valueChanged, this, [this](int value) {
if (!frogPilotTogglesOpen) {
if (!parentToggleOpen) {
previousScrollPosition = value;
}
});
QObject::connect(scrollbar, &QScrollBar::rangeChanged, this, [this, panel_frame]() {
if (!parentToggleOpen) {
panel_frame->restorePosition(previousScrollPosition);
}
});
}
QObject::connect(btn, &QPushButton::clicked, [=, w = panel_frame]() {
closeParentToggle();
previousScrollPosition = 0;
btn->setChecked(true);
panel_widget->setCurrentWidget(w);

27
selfdrive/ui/qt/offroad/settings.h Executable file → Normal file
View File

@@ -11,6 +11,7 @@
#include <QWidget>
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/qt/widgets/controls.h"
@@ -25,6 +26,9 @@ public:
protected:
void showEvent(QShowEvent *event) override;
// FrogPilot widgets
void hideEvent(QHideEvent *event) override;
signals:
void closeSettings();
void reviewTrainingGuide();
@@ -33,7 +37,9 @@ signals:
// FrogPilot signals
void closeParentToggle();
void closeSubParentToggle();
void updateMetric();
private:
QPushButton *sidebar_alert_widget;
QWidget *sidebar_widget;
@@ -41,7 +47,9 @@ private:
QStackedWidget *panel_widget;
// FrogPilot variables
bool frogPilotTogglesOpen;
bool parentToggleOpen;
bool subParentToggleOpen;
int previousScrollPosition;
};
@@ -56,10 +64,14 @@ signals:
private slots:
void poweroff();
void reboot();
void softreboot();
void updateCalibDescription();
private:
Params params;
// FrogPilot variables
Params paramsMemory{"/dev/shm/params"};
};
class TogglesPanel : public ListWidget {
@@ -75,6 +87,9 @@ signals:
public slots:
void expandToggleDescription(const QString &param);
private slots:
void updateState(const UIState &s);
private:
Params params;
std::map<std::string, ParamControl*> toggles;
@@ -97,7 +112,6 @@ private:
QLabel *onroadLbl;
LabelControl *versionLbl;
ButtonControl *errorLogBtn;
ButtonControl *installBtn;
ButtonControl *downloadBtn;
ButtonControl *targetBranchBtn;
@@ -106,11 +120,6 @@ private:
ParamWatcher *fs_watch;
// FrogPilot variables
void automaticUpdate();
ButtonControl *updateTime;
int deviceShutdown;
int schedule;
int time;
Params paramsMemory{"/dev/shm/params"};
UIScene &scene;
};

152
selfdrive/ui/qt/offroad/software_settings.cc Executable file → Normal file
View File

@@ -2,8 +2,6 @@
#include <cassert>
#include <cmath>
#include <iomanip>
#include <sstream>
#include <string>
#include <QDebug>
@@ -18,13 +16,14 @@
#include "selfdrive/ui/qt/widgets/input.h"
#include "system/hardware/hw.h"
#include "selfdrive/frogpilot/ui/qt/widgets/frogpilot_controls.h"
void SoftwarePanel::checkForUpdates() {
std::system("pkill -SIGUSR1 -f selfdrive.updated");
std::system("pkill -SIGUSR1 -f selfdrive.updated.updated");
}
SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
onroadLbl = new QLabel(tr("Updates are only downloaded while the car is off."));
SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent), scene(uiState()->scene) {
onroadLbl = new QLabel(tr("Updates are only downloaded while the car is off or in park."));
onroadLbl->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left; padding-top: 30px; padding-bottom: 30px;");
addItem(onroadLbl);
@@ -32,40 +31,17 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
versionLbl = new LabelControl(tr("Current Version"), "");
addItem(versionLbl);
// update scheduler
std::vector<QString> scheduleOptions{tr("Manually"), tr("Daily"), tr("Weekly")};
ButtonParamControl *preferredSchedule = new ButtonParamControl("UpdateSchedule", tr("Update Scheduler"),
tr("Choose the update frequency for FrogPilot's automatic updates.\n\n"
"This feature will handle the download, installation, and device reboot for a seamless 'Set and Forget' experience.\n\n"
"Weekly updates start at midnight every Sunday."),
"",
scheduleOptions);
schedule = params.getInt("UpdateSchedule");
addItem(preferredSchedule);
updateTime = new ButtonControl(tr("Update Time"), tr("SELECT"));
QStringList hours;
for (int h = 0; h < 24; h++) {
int displayHour = (h % 12 == 0) ? 12 : h % 12;
QString meridiem = (h < 12) ? "AM" : "PM";
hours << QString("%1:00 %2").arg(displayHour).arg(meridiem)
<< QString("%1:30 %2").arg(displayHour).arg(meridiem);
}
QObject::connect(updateTime, &ButtonControl::clicked, [=]() {
int currentHourIndex = params.getInt("UpdateTime");
QString currentHourLabel = hours[currentHourIndex];
QString selection = MultiOptionDialog::getSelection(tr("Select a time to automatically update"), hours, currentHourLabel, this);
if (!selection.isEmpty()) {
int selectedHourIndex = hours.indexOf(selection);
params.putInt("UpdateTime", selectedHourIndex);
updateTime->setValue(selection);
}
// automatic updates toggle
ParamControl *automaticUpdatesToggle = new ParamControl("AutomaticUpdates", tr("Automatically Update FrogPilot"),
tr("FrogPilot will automatically update itself and it's assets when you're offroad and connected to Wi-Fi."), "");
connect(automaticUpdatesToggle, &ToggleControl::toggleFlipped, [this]() {
std::thread([this]() {
paramsMemory.putBool("FrogPilotTogglesUpdated", true);
std::this_thread::sleep_for(std::chrono::seconds(1));
paramsMemory.putBool("FrogPilotTogglesUpdated", false);
}).detach();
});
time = params.getInt("UpdateTime");
deviceShutdown = params.getInt("DeviceShutdown") * 3600;
updateTime->setValue(hours[time]);
addItem(updateTime);
addItem(automaticUpdatesToggle);
// download update btn
downloadBtn = new ButtonControl(tr("Download"), tr("CHECK"));
@@ -74,8 +50,9 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
if (downloadBtn->text() == tr("CHECK")) {
checkForUpdates();
} else {
std::system("pkill -SIGHUP -f selfdrive.updated");
std::system("pkill -SIGHUP -f selfdrive.updated.updated");
}
paramsMemory.putBool("ManualUpdateInitiated", true);
});
addItem(downloadBtn);
@@ -92,6 +69,11 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
connect(targetBranchBtn, &ButtonControl::clicked, [=]() {
auto current = params.get("GitBranch");
QStringList branches = QString::fromStdString(params.get("UpdaterAvailableBranches")).split(",");
if (!Params("/persist/params").getBool("FrogsGoMoo")) {
branches.removeAll("FrogPilot-Development");
branches.removeAll("FrogPilot-New");
branches.removeAll("MAKE-PRS-HERE");
}
for (QString b : {current.c_str(), "devel-staging", "devel", "nightly", "master-ci", "master"}) {
auto i = branches.indexOf(b);
if (i >= 0) {
@@ -122,7 +104,7 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
addItem(uninstallBtn);
// error log button
errorLogBtn = new ButtonControl(tr("Error Log"), tr("VIEW"), "View the error log for debugging purposes when openpilot crashes.");
auto errorLogBtn = new ButtonControl(tr("Error Log"), tr("VIEW"), tr("View the error log for openpilot crashes."));
connect(errorLogBtn, &ButtonControl::clicked, [=]() {
std::string txt = util::read_file("/data/community/crashes/error.txt");
ConfirmationDialog::rich(QString::fromStdString(txt), this);
@@ -131,8 +113,6 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
fs_watch = new ParamWatcher(this);
QObject::connect(fs_watch, &ParamWatcher::paramChanged, [=](const QString &param_name, const QString &param_value) {
schedule = params.getInt("UpdateSchedule");
time = params.getInt("UpdateTime");
updateLabels();
});
@@ -141,8 +121,6 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
updateLabels();
});
QObject::connect(uiState(), &UIState::uiUpdate, this, &SoftwarePanel::automaticUpdate);
updateLabels();
}
@@ -160,16 +138,15 @@ void SoftwarePanel::updateLabels() {
fs_watch->addParam("UpdaterState");
fs_watch->addParam("UpdateAvailable");
fs_watch->addParam("UpdateSchedule");
fs_watch->addParam("UpdateTime");
if (!isVisible()) {
return;
}
// updater only runs offroad
onroadLbl->setVisible(is_onroad);
downloadBtn->setVisible(!is_onroad);
// updater only runs offroad or when parked
bool parked = scene.parked;
onroadLbl->setVisible(is_onroad && !parked);
downloadBtn->setVisible(!is_onroad || parked);
// download update
QString updater_state = QString::fromStdString(params.get("UpdaterState"));
@@ -201,82 +178,9 @@ void SoftwarePanel::updateLabels() {
versionLbl->setText(QString::fromStdString(params.get("UpdaterCurrentDescription")));
versionLbl->setDescription(QString::fromStdString(params.get("UpdaterCurrentReleaseNotes")));
installBtn->setVisible(!is_onroad && params.getBool("UpdateAvailable"));
installBtn->setVisible((!is_onroad || parked) && params.getBool("UpdateAvailable"));
installBtn->setValue(QString::fromStdString(params.get("UpdaterNewDescription")));
installBtn->setDescription(QString::fromStdString(params.get("UpdaterNewReleaseNotes")));
updateTime->setVisible(params.getInt("UpdateSchedule"));
update();
}
void SoftwarePanel::automaticUpdate() {
static int timer = 0;
static std::chrono::system_clock::time_point lastOffroadTime;
if (!is_onroad) {
timer = (timer == 0) ? 0 : std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - lastOffroadTime).count();
lastOffroadTime = std::chrono::system_clock::now();
} else {
timer = 0;
}
bool isWifiConnected = (*uiState()->sm)["deviceState"].getDeviceState().getNetworkType() == cereal::DeviceState::NetworkType::WIFI;
if (schedule == 0 || is_onroad || !isWifiConnected || isVisible()) return;
static bool isDownloadCompleted = false;
if (isDownloadCompleted && params.getBool("UpdateAvailable")) {
params.putBool(timer > deviceShutdown ? "DoShutdown" : "DoReboot", true);
return;
}
int updateHour = time / 2;
int updateMinute = (time % 2) * 30;
if (updateHour >= 1 && updateHour <= 11 && time >= 24) {
updateHour += 12;
} else if (updateHour == 12 && time < 24) {
updateHour = 0;
}
std::time_t currentTimeT = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::tm now = *std::localtime(&currentTimeT);
static bool updateCheckedToday = false;
static int lastCheckedDay = now.tm_yday;
if (lastCheckedDay != now.tm_yday) {
updateCheckedToday = false;
lastCheckedDay = now.tm_yday;
}
if (now.tm_hour != updateHour || now.tm_min < updateMinute) return;
std::string lastUpdateStr = params.get("Updated");
std::tm lastUpdate = {};
std::istringstream iss(lastUpdateStr);
if (iss >> std::get_time(&lastUpdate, "%Y-%m-%d %H:%M:%S")) {
lastUpdate.tm_year -= 1900;
lastUpdate.tm_mon -= 1;
}
std::time_t lastUpdateTimeT = std::mktime(&lastUpdate);
if (lastUpdate.tm_yday == now.tm_yday) {
return;
}
if (!isDownloadCompleted) {
std::chrono::hours durationSinceLastUpdate = std::chrono::duration_cast<std::chrono::hours>(std::chrono::system_clock::now() - std::chrono::system_clock::from_time_t(lastUpdateTimeT));
int daysSinceLastUpdate = durationSinceLastUpdate.count() / 24;
if ((schedule == 1 && daysSinceLastUpdate >= 1) || (schedule == 2 && (now.tm_yday / 7) != (std::localtime(&lastUpdateTimeT)->tm_yday / 7))) {
if (downloadBtn->text() == tr("CHECK") && !updateCheckedToday) {
checkForUpdates();
updateCheckedToday = true;
} else {
std::system("pkill -SIGHUP -f selfdrive.updated");
isDownloadCompleted = true;
}
}
}
}

0
selfdrive/ui/qt/offroad/text_view.qml Executable file → Normal file
View File

1285
selfdrive/ui/qt/onroad.cc Executable file → Normal file

File diff suppressed because it is too large Load Diff

132
selfdrive/ui/qt/onroad.h Executable file → Normal file
View File

@@ -2,7 +2,8 @@
#include <memory>
#include <QElapsedTimer>
#include <QMovie>
#include <QLabel>
#include <QPushButton>
#include <QStackedLayout>
#include <QWidget>
@@ -16,7 +17,6 @@
const int btn_size = 192;
const int img_size = (btn_size / 4) * 3;
// FrogPilot global variables
static double fps;
// ***** onroad widgets *****
@@ -42,13 +42,14 @@ class Compass : public QWidget {
public:
explicit Compass(QWidget *parent = nullptr);
void initializeStaticElements();
void updateState(int bearing_deg);
void updateState();
protected:
void paintEvent(QPaintEvent *event) override;
private:
UIScene &scene;
int bearingDeg;
int circleOffset;
int compassSize;
@@ -56,10 +57,37 @@ private:
int innerCompass;
int x;
int y;
QPixmap compassInnerImg;
QPixmap staticElements;
};
class DistanceButton : public QPushButton {
public:
explicit DistanceButton(QWidget *parent = nullptr);
void buttonPressed();
void buttonReleased();
void updateState();
protected:
void paintEvent(QPaintEvent *event) override;
private:
Params paramsMemory{"/dev/shm/params"};
UIScene &scene;
bool trafficModeActive;
int personality;
QElapsedTimer transitionTimer;
QVector<std::pair<QPixmap, QString>> profile_data;
QVector<std::pair<QPixmap, QString>> profile_data_kaofui;
};
class ExperimentalButton : public QPushButton {
Q_OBJECT
@@ -78,14 +106,24 @@ private:
bool engageable;
// FrogPilot variables
Params paramsMemory{"/dev/shm/params"};
UIScene &scene;
std::map<int, QPixmap> wheelImages;
QMap<int, QPixmap> wheelImages;
QMap<int, QMovie*> wheelImagesGif;
QMovie engage_gif;
QLabel *gifLabel;
bool docRandomEventTriggered;
bool firefoxRandomEventTriggered;
bool rotatingWheel;
bool treeFiddyRandomEventTriggered;
bool weebRandomEventTriggered;
int steeringAngleDeg;
int wheelIcon;
int wheelIconGif;
int y_offset;
};
@@ -102,28 +140,25 @@ private:
QPixmap settings_img;
};
// FrogPilot buttons
class PersonalityButton : public QPushButton {
public:
explicit PersonalityButton(QWidget *parent = 0);
class PedalIcons : public QWidget {
Q_OBJECT
void checkUpdate();
void handleClick();
public:
explicit PedalIcons(QWidget *parent = 0);
void updateState();
private:
void paintEvent(QPaintEvent *event) override;
Params params;
Params paramsMemory{"/dev/shm/params"};
QPixmap brake_pedal_img;
QPixmap gas_pedal_img;
UIScene &scene;
int personalityProfile = 0;
bool accelerating;
bool decelerating;
QElapsedTimer transitionTimer;
QVector<std::pair<QPixmap, QString>> profile_data;
float acceleration;
};
// container window for the NVG UI
@@ -163,79 +198,86 @@ private:
bool wide_cam_requested = false;
// FrogPilot widgets
void initializeFrogPilotWidgets();
void paintFrogPilotWidgets(QPainter &p);
void updateFrogPilotWidgets();
void drawLeadInfo(QPainter &p);
void drawSLCConfirmation(QPainter &p);
void drawStatusBar(QPainter &p);
void drawTurnSignals(QPainter &p);
void initializeFrogPilotWidgets();
void updateFrogPilotWidgets(QPainter &p);
// FrogPilot variables
Params paramsMemory{"/dev/shm/params"};
UIScene &scene;
Compass *compass_img;
PersonalityButton *personality_btn;
DistanceButton *distance_btn;
PedalIcons *pedal_icons;
ScreenRecorder *recorder_btn;
QHBoxLayout *bottom_layout;
bool accelerationPath;
bool adjacentPath;
bool alwaysOnLateral;
bool alwaysOnLateralActive;
bool bigMapOpen;
bool blindSpotLeft;
bool blindSpotRight;
bool compass;
bool conditionalExperimental;
bool experimentalMode;
bool hideSpeed;
bool leadInfo;
bool mapOpen;
bool muteDM;
bool onroadAdjustableProfiles;
bool reverseCruise;
bool onroadDistanceButton;
bool roadNameUI;
bool showDriverCamera;
bool showAlwaysOnLateralStatusBar;
bool showConditionalExperimentalStatusBar;
bool showSLCOffset;
bool slcOverridden;
bool speedLimitController;
bool trafficModeActive;
bool turnSignalLeft;
bool turnSignalRight;
bool useSI;
bool useViennaSLCSign;
double maxAcceleration;
bool vtscControllingCurve;
float cruiseAdjustment;
float distanceConversion;
float laneDetectionWidth;
float laneWidthLeft;
float laneWidthRight;
float slcOverriddenSpeed;
float slcSpeedLimit;
float slcSpeedLimitOffset;
int bearingDeg;
float speedConversion;
int alertSize;
int cameraView;
int conditionalSpeed;
int conditionalSpeedLead;
int conditionalStatus;
int currentHolidayTheme;
int customColors;
int customSignals;
int desiredFollow;
int obstacleDistance;
int obstacleDistanceStock;
int stoppedEquivalence;
int totalFrames = 8;
QTimer *animationTimer;
QString leadDistanceUnit;
QString leadSpeedUnit;
size_t animationFrameIndex;
inline QColor greenColor(int alpha = 242) { return QColor(23, 134, 68, alpha); }
std::unordered_map<int, std::pair<QString, std::pair<QColor, std::map<double, QBrush>>>> themeConfiguration;
std::unordered_map<int, std::tuple<QString, QColor, std::map<double, QBrush>>> themeConfiguration;
std::unordered_map<int, std::tuple<QString, QColor, std::map<double, QBrush>>> holidayThemeConfiguration;
std::vector<QPixmap> signalImgVector;
QTimer *animationTimer;
inline QColor blueColor(int alpha = 255) { return QColor(0, 150, 255, alpha); }
inline QColor greenColor(int alpha = 242) { return QColor(23, 134, 68, alpha); }
protected:
void paintGL() override;
void initializeGL() override;
void showEvent(QShowEvent *event) override;
void updateFrameMat() override;
void drawLaneLines(QPainter &painter, const UIState *s);
void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd);
void drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd, const float v_ego);
void drawHud(QPainter &p);
void drawDriverState(QPainter &painter, const UIState *s);
void paintEvent(QPaintEvent *event) override;
@@ -270,6 +312,8 @@ private:
// FrogPilot variables
UIScene &scene;
Params params;
Params paramsMemory{"/dev/shm/params"};
QPoint timeoutPoint = QPoint(420, 69);
QTimer clickTimer;

View File

@@ -0,0 +1,20 @@
import os
from cffi import FFI
import sip
from openpilot.common.ffi_wrapper import suffix
from openpilot.common.basedir import BASEDIR
def get_ffi():
lib = os.path.join(BASEDIR, "selfdrive", "ui", "qt", "libpython_helpers" + suffix())
ffi = FFI()
ffi.cdef("void set_main_window(void *w);")
return ffi, ffi.dlopen(lib)
def set_main_window(widget):
ffi, lib = get_ffi()
lib.set_main_window(ffi.cast('void*', sip.unwrapinstance(widget)))

0
selfdrive/ui/qt/qt_window.cc Executable file → Normal file
View File

0
selfdrive/ui/qt/qt_window.h Executable file → Normal file
View File

0
selfdrive/ui/qt/request_repeater.cc Executable file → Normal file
View File

0
selfdrive/ui/qt/request_repeater.h Executable file → Normal file
View File

9
selfdrive/ui/qt/setup/reset.cc Executable file → Normal file
View File

@@ -57,7 +57,7 @@ Reset::Reset(ResetMode mode, QWidget *parent) : QWidget(parent) {
main_layout->addSpacing(60);
body = new QLabel(tr("Press confirm to erase all content and settings. Press cancel to resume boot."));
body = new QLabel(tr("System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot."));
body->setWordWrap(true);
body->setStyleSheet("font-size: 80px; font-weight: light;");
main_layout->addWidget(body, 1, Qt::AlignTop | Qt::AlignLeft);
@@ -97,11 +97,6 @@ Reset::Reset(ResetMode mode, QWidget *parent) : QWidget(parent) {
body->setText(tr("Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device."));
}
// automatically start if we're just finishing up an ABL reset
if (mode == ResetMode::FORMAT) {
startReset();
}
setStyleSheet(R"(
* {
font-family: Inter;
@@ -129,8 +124,6 @@ int main(int argc, char *argv[]) {
if (argc > 1) {
if (strcmp(argv[1], "--recover") == 0) {
mode = ResetMode::RECOVER;
} else if (strcmp(argv[1], "--format") == 0) {
mode = ResetMode::FORMAT;
}
}

1
selfdrive/ui/qt/setup/reset.h Executable file → Normal file
View File

@@ -5,7 +5,6 @@
enum ResetMode {
USER_RESET, // user initiated a factory reset from openpilot
RECOVER, // userdata is corrupt for some reason, give a chance to recover
FORMAT, // finish up an ABL factory reset
};
class Reset : public QWidget {

122
selfdrive/ui/qt/setup/setup.cc Executable file → Normal file
View File

@@ -20,7 +20,7 @@
#include "selfdrive/ui/qt/widgets/input.h"
const std::string USER_AGENT = "AGNOSSetup-";
const QString DASHCAM_URL = "https://dashcam.comma.ai";
const QString OPENPILOT_URL = "https://openpilot.comma.ai";
bool is_elf(char *fname) {
FILE *fp = fopen(fname, "rb");
@@ -201,20 +201,7 @@ QWidget * Setup::network_setup() {
QPushButton *cont = new QPushButton();
cont->setObjectName("navBtn");
cont->setProperty("primary", true);
QObject::connect(cont, &QPushButton::clicked, [=]() {
auto w = currentWidget();
QTimer::singleShot(0, [=]() {
setCurrentWidget(downloading_widget);
});
QString url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software"));
if (!url.isEmpty()) {
QTimer::singleShot(1000, this, [=]() {
download(url);
});
} else {
setCurrentWidget(w);
}
});
QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage);
blayout->addWidget(cont);
// setup timer for testing internet connection
@@ -229,11 +216,11 @@ QWidget * Setup::network_setup() {
}
repaint();
});
request->sendRequest(DASHCAM_URL);
request->sendRequest(OPENPILOT_URL);
QTimer *timer = new QTimer(this);
QObject::connect(timer, &QTimer::timeout, [=]() {
if (!request->active() && cont->isVisible()) {
request->sendRequest(DASHCAM_URL);
request->sendRequest(OPENPILOT_URL);
}
});
timer->start(1000);
@@ -241,6 +228,106 @@ QWidget * Setup::network_setup() {
return widget;
}
QWidget * radio_button(QString title, QButtonGroup *group) {
QPushButton *btn = new QPushButton(title);
btn->setCheckable(true);
group->addButton(btn);
btn->setStyleSheet(R"(
QPushButton {
height: 230;
padding-left: 100px;
padding-right: 100px;
text-align: left;
font-size: 80px;
font-weight: 400;
border-radius: 10px;
background-color: #4F4F4F;
}
QPushButton:checked {
background-color: #465BEA;
}
)");
// checkmark icon
QPixmap pix(":/img_circled_check.svg");
btn->setIcon(pix);
btn->setIconSize(QSize(0, 0));
btn->setLayoutDirection(Qt::RightToLeft);
QObject::connect(btn, &QPushButton::toggled, [=](bool checked) {
btn->setIconSize(checked ? QSize(104, 104) : QSize(0, 0));
});
return btn;
}
QWidget * Setup::software_selection() {
QWidget *widget = new QWidget();
QVBoxLayout *main_layout = new QVBoxLayout(widget);
main_layout->setContentsMargins(55, 50, 55, 50);
main_layout->setSpacing(0);
// title
QLabel *title = new QLabel(tr("Choose Software to Install"));
title->setStyleSheet("font-size: 90px; font-weight: 500;");
main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
main_layout->addSpacing(50);
// openpilot + custom radio buttons
QButtonGroup *group = new QButtonGroup(widget);
group->setExclusive(true);
QWidget *openpilot = radio_button(tr("openpilot"), group);
main_layout->addWidget(openpilot);
main_layout->addSpacing(30);
QWidget *custom = radio_button(tr("Custom Software"), group);
main_layout->addWidget(custom);
main_layout->addStretch();
// back + continue buttons
QHBoxLayout *blayout = new QHBoxLayout;
main_layout->addLayout(blayout);
blayout->setSpacing(50);
QPushButton *back = new QPushButton(tr("Back"));
back->setObjectName("navBtn");
QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
blayout->addWidget(back);
QPushButton *cont = new QPushButton(tr("Continue"));
cont->setObjectName("navBtn");
cont->setEnabled(false);
cont->setProperty("primary", true);
blayout->addWidget(cont);
QObject::connect(cont, &QPushButton::clicked, [=]() {
auto w = currentWidget();
QTimer::singleShot(0, [=]() {
setCurrentWidget(downloading_widget);
});
QString url = OPENPILOT_URL;
if (group->checkedButton() != openpilot) {
url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software"));
}
if (!url.isEmpty()) {
QTimer::singleShot(1000, this, [=]() {
download(url);
});
} else {
setCurrentWidget(w);
}
});
connect(group, QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked), [=](QAbstractButton *btn) {
btn->setChecked(true);
cont->setEnabled(true);
});
return widget;
}
QWidget * Setup::downloading() {
QWidget *widget = new QWidget();
QVBoxLayout *main_layout = new QVBoxLayout(widget);
@@ -326,6 +413,7 @@ Setup::Setup(QWidget *parent) : QStackedWidget(parent) {
addWidget(getting_started());
addWidget(network_setup());
addWidget(software_selection());
downloading_widget = downloading();
addWidget(downloading_widget);

1
selfdrive/ui/qt/setup/setup.h Executable file → Normal file
View File

@@ -17,6 +17,7 @@ private:
QWidget *low_voltage();
QWidget *getting_started();
QWidget *network_setup();
QWidget *software_selection();
QWidget *downloading();
QWidget *download_failed(QLabel *url, QLabel *body);

31
selfdrive/ui/qt/setup/updater.cc Executable file → Normal file
View File

@@ -54,12 +54,7 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
background-color: #3049F4;
}
)");
QObject::connect(connect, &QPushButton::clicked, [=]() {
countdownTimer->stop();
setCurrentWidget(wifi);
});
QObject::connect(install, &QPushButton::clicked, this, &Updater::installUpdate);
hlayout->addWidget(install);
}
@@ -119,20 +114,6 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
addWidget(wifi);
addWidget(progress);
// Initialize the countdown timer and value
countdownValue = 5; // 5 seconds countdown
countdownTimer = new QTimer(this);
countdownTimer->setInterval(1000); // 1 second interval
// Connect the timer's timeout signal to update the countdown and button text
QObject::connect(countdownTimer, &QTimer::timeout, this, &Updater::updateCountdown);
// Start the countdown
countdownTimer->start();
// Set initial button text
install->setText(tr("Install (5)"));
setStyleSheet(R"(
* {
color: white;
@@ -163,16 +144,6 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
)");
}
void Updater::updateCountdown() {
countdownValue--;
if (countdownValue > 0) {
install->setText(tr("Install (%1)").arg(countdownValue));
} else {
countdownTimer->stop();
installUpdate(); // Assuming this is the method that starts the update
}
}
void Updater::installUpdate() {
setCurrentWidget(progress);
QObject::connect(&proc, &QProcess::readyReadStandardOutput, this, &Updater::readProgress);

3
selfdrive/ui/qt/setup/updater.h Executable file → Normal file
View File

@@ -26,7 +26,4 @@ private:
QProgressBar *bar;
QPushButton *reboot;
QWidget *prompt, *wifi, *progress;
QTimer *countdownTimer;
int countdownValue;
};

109
selfdrive/ui/qt/sidebar.cc Executable file → Normal file
View File

@@ -43,16 +43,40 @@ Sidebar::Sidebar(QWidget *parent) : QFrame(parent), onroad(false), flag_pressed(
isCPU = params.getBool("ShowCPU");
isGPU = params.getBool("ShowGPU");
isIP = params.getBool("ShowIP");
isMemoryUsage = params.getBool("ShowMemoryUsage");
isStorageLeft = params.getBool("ShowStorageLeft");
isStorageUsed = params.getBool("ShowStorageUsed");
isNumericalTemp = params.getBool("NumericalTemp");
isFahrenheit = params.getBool("Fahrenheit");
holidayThemeConfiguration = {
{0, {"stock", {QColor(255, 255, 255)}}},
{1, {"april_fools", {QColor(255, 165, 0)}}},
{2, {"christmas", {QColor(0, 72, 255)}}},
{3, {"cinco_de_mayo", {QColor(0, 104, 71)}}},
{4, {"easter", {QColor(200, 150, 200)}}},
{5, {"fourth_of_july", {QColor(0, 72, 255)}}},
{6, {"halloween", {QColor(255, 0, 0)}}},
{7, {"new_years_day", {QColor(23, 134, 68)}}},
{8, {"st_patricks_day", {QColor(0, 128, 0)}}},
{9, {"thanksgiving", {QColor(255, 0, 0)}}},
{10, {"valentines_day", {QColor(23, 134, 68)}}},
{11, {"world_frog_day", {QColor(23, 134, 68)}}},
};
for (auto &[key, themeData] : holidayThemeConfiguration) {
QString &themeName = themeData.first;
QString base = themeName == "stock" ? "../assets/images" : QString("../frogpilot/assets/holiday_themes/%1/images").arg(themeName);
std::vector<QString> paths = {base + "/button_home.png", base + "/button_flag.png", base + "/button_settings.png"};
holiday_home_imgs[key] = loadPixmap(paths[0], home_btn.size());
holiday_flag_imgs[key] = loadPixmap(paths[1], home_btn.size());
holiday_settings_imgs[key] = loadPixmap(paths[2], settings_btn.size(), Qt::IgnoreAspectRatio);
}
themeConfiguration = {
{0, {"stock", {QColor(255, 255, 255)}}},
{1, {"frog_theme", {QColor(0, 72, 255)}}},
{1, {"frog_theme", {QColor(23, 134, 68)}}},
{2, {"tesla_theme", {QColor(0, 72, 255)}}},
{3, {"stalin_theme", {QColor(255, 0, 0)}}}
};
@@ -70,7 +94,6 @@ Sidebar::Sidebar(QWidget *parent) : QFrame(parent), onroad(false), flag_pressed(
home_img = home_imgs[scene.custom_icons];
flag_img = flag_imgs[scene.custom_icons];
settings_img = settings_imgs[scene.custom_icons];
currentColors = themeConfiguration[scene.custom_colors].second;
}
@@ -78,35 +101,52 @@ void Sidebar::mousePressEvent(QMouseEvent *event) {
// Declare the click boxes
QRect cpuRect = {30, 496, 240, 126};
QRect memoryRect = {30, 654, 240, 126};
QRect networkRect = {30, 196, 240, 126};
QRect tempRect = {30, 338, 240, 126};
static int showChip = 0;
static int showMemory = 0;
static int showNetwork = 0;
static int showTemp = 0;
// Swap between the respective metrics upon tap
if (cpuRect.contains(event->pos())) {
showChip = (showChip + 1) % 3;
isCPU = (showChip == 1);
isGPU = (showChip == 2);
params.putBoolNonBlocking("ShowCPU", isCPU);
params.putBoolNonBlocking("ShowGPU", isGPU);
update();
} else if (memoryRect.contains(event->pos())) {
showMemory = (showMemory + 1) % 4;
isMemoryUsage = (showMemory == 1);
isStorageLeft = (showMemory == 2);
isStorageUsed = (showMemory == 3);
params.putBoolNonBlocking("ShowMemoryUsage", isMemoryUsage);
params.putBoolNonBlocking("ShowStorageLeft", isStorageLeft);
params.putBoolNonBlocking("ShowStorageUsed", isStorageUsed);
update();
} else if (networkRect.contains(event->pos())) {
showNetwork = (showNetwork + 1) % 2;
isIP = (showNetwork == 1);
params.putBoolNonBlocking("ShowIP", isIP);
update();
} else if (tempRect.contains(event->pos())) {
showTemp = (showTemp + 1) % 3;
isNumericalTemp = (showTemp != 0);
isFahrenheit = (showTemp == 2);
params.putBoolNonBlocking("Fahrenheit", isFahrenheit);
params.putBoolNonBlocking("NumericalTemp", isNumericalTemp);
scene.fahrenheit = showTemp == 2;
scene.numerical_temp = showTemp != 0;
params.putBoolNonBlocking("Fahrenheit", showTemp == 2);
params.putBoolNonBlocking("NumericalTemp", showTemp != 0);
update();
} else if (onroad && home_btn.contains(event->pos())) {
flag_pressed = true;
@@ -147,17 +187,24 @@ void Sidebar::updateState(const UIState &s) {
setProperty("netStrength", strength > 0 ? strength + 1 : 0);
// FrogPilot properties
if (scene.current_holiday_theme != 0) {
home_img = holiday_home_imgs[scene.current_holiday_theme];
flag_img = holiday_flag_imgs[scene.current_holiday_theme];
settings_img = holiday_settings_imgs[scene.current_holiday_theme];
currentColors = holidayThemeConfiguration[scene.current_holiday_theme].second;
} else {
home_img = home_imgs[scene.custom_icons];
flag_img = flag_imgs[scene.custom_icons];
settings_img = settings_imgs[scene.custom_icons];
currentColors = themeConfiguration[scene.custom_colors].second;
}
auto frogpilotDeviceState = sm["frogpilotDeviceState"].getFrogpilotDeviceState();
bool isNumericalTemp = scene.numerical_temp;
int maxTempC = deviceState.getMaxTempC();
QString max_temp = isFahrenheit ? QString::number(maxTempC * 9 / 5 + 32) + "°F" : QString::number(maxTempC) + "°C";
QColor theme_color = currentColors[0];
QString max_temp = scene.fahrenheit ? QString::number(maxTempC * 9 / 5 + 32) + "°F" : QString::number(maxTempC) + "°C";
// FrogPilot metrics
if (isCPU || isGPU) {
@@ -171,11 +218,11 @@ void Sidebar::updateState(const UIState &s) {
QString metric = isGPU ? gpu : cpu;
int usage = isGPU ? gpu_usage : cpu_usage;
ItemStatus cpuStatus = {{tr(isGPU ? "GPU" : "CPU"), metric}, theme_color};
ItemStatus cpuStatus = {{isGPU ? tr("GPU") : tr("CPU"), metric}, currentColors[0]};
if (usage >= 85) {
cpuStatus = {{tr(isGPU ? "GPU" : "CPU"), metric}, danger_color};
cpuStatus = {{isGPU ? tr("GPU") : tr("CPU"), metric}, danger_color};
} else if (usage >= 70) {
cpuStatus = {{tr(isGPU ? "GPU" : "CPU"), metric}, warning_color};
cpuStatus = {{isGPU ? tr("GPU") : tr("CPU"), metric}, warning_color};
}
setProperty("cpuStatus", QVariant::fromValue(cpuStatus));
}
@@ -186,10 +233,10 @@ void Sidebar::updateState(const UIState &s) {
int storage_used = frogpilotDeviceState.getUsedSpace();
QString memory = QString::number(memory_usage) + "%";
QString storage = QString::number(isStorageLeft ? storage_left : storage_used) + " GB";
QString storage = QString::number(isStorageLeft ? storage_left : storage_used) + tr(" GB");
if (isMemoryUsage) {
ItemStatus memoryStatus = {{tr("MEMORY"), memory}, theme_color};
ItemStatus memoryStatus = {{tr("MEMORY"), memory}, currentColors[0]};
if (memory_usage >= 85) {
memoryStatus = {{tr("MEMORY"), memory}, danger_color};
} else if (memory_usage >= 70) {
@@ -197,11 +244,11 @@ void Sidebar::updateState(const UIState &s) {
}
setProperty("memoryStatus", QVariant::fromValue(memoryStatus));
} else {
ItemStatus storageStatus = {{tr(isStorageLeft ? "LEFT" : "USED"), storage}, theme_color};
if (10 <= storage_left && storage_left < 25) {
storageStatus = {{tr(isStorageLeft ? "LEFT" : "USED"), storage}, warning_color};
} else if (storage_left < 10) {
storageStatus = {{tr(isStorageLeft ? "LEFT" : "USED"), storage}, danger_color};
ItemStatus storageStatus = {{isStorageLeft ? tr("LEFT") : tr("USED"), storage}, currentColors[0]};
if (25 > storage_left && storage_left >= 10) {
storageStatus = {{isStorageLeft ? tr("LEFT") : tr("USED"), storage}, warning_color};
} else if (10 > storage_left) {
storageStatus = {{isStorageLeft ? tr("LEFT") : tr("USED"), storage}, danger_color};
}
setProperty("storageStatus", QVariant::fromValue(storageStatus));
}
@@ -213,7 +260,7 @@ void Sidebar::updateState(const UIState &s) {
connectStatus = ItemStatus{{tr("CONNECT"), tr("OFFLINE")}, warning_color};
} else {
connectStatus = nanos_since_boot() - last_ping < 80e9
? ItemStatus{{tr("CONNECT"), tr("ONLINE")}, theme_color}
? ItemStatus{{tr("CONNECT"), tr("ONLINE")}, currentColors[0]}
: ItemStatus{{tr("CONNECT"), tr("ERROR")}, danger_color};
}
setProperty("connectStatus", QVariant::fromValue(connectStatus));
@@ -221,13 +268,13 @@ void Sidebar::updateState(const UIState &s) {
ItemStatus tempStatus = {{tr("TEMP"), isNumericalTemp ? max_temp : tr("HIGH")}, danger_color};
auto ts = deviceState.getThermalStatus();
if (ts == cereal::DeviceState::ThermalStatus::GREEN) {
tempStatus = {{tr("TEMP"), isNumericalTemp ? max_temp : tr("GOOD")}, theme_color};
tempStatus = {{tr("TEMP"), isNumericalTemp ? max_temp : tr("GOOD")}, currentColors[0]};
} else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) {
tempStatus = {{tr("TEMP"), isNumericalTemp ? max_temp : tr("OK")}, warning_color};
}
setProperty("tempStatus", QVariant::fromValue(tempStatus));
ItemStatus pandaStatus = {{tr("VEHICLE"), tr("ONLINE")}, theme_color};
ItemStatus pandaStatus = {{tr("VEHICLE"), tr("ONLINE")}, currentColors[0]};
if (s.scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) {
pandaStatus = {{tr("NO"), tr("PANDA")}, danger_color};
} else if (s.scene.started && !sm["liveLocationKalman"].getLiveLocationKalman().getGpsOK()) {
@@ -253,14 +300,24 @@ void Sidebar::paintEvent(QPaintEvent *event) {
// network
int x = 58;
const QColor gray(0x54, 0x54, 0x54);
p.setFont(InterFont(35));
if (isIP) {
p.setPen(QColor(0xff, 0xff, 0xff));
p.save();
p.setFont(InterFont(30));
QRect ipBox = QRect(50, 196, 225, 27);
p.drawText(ipBox, Qt::AlignLeft | Qt::AlignVCenter, uiState()->wifi->getIp4Address());
p.restore();
} else {
for (int i = 0; i < 5; ++i) {
p.setBrush(i < net_strength ? Qt::white : gray);
p.drawEllipse(x, 196, 27, 27);
x += 37;
}
p.setFont(InterFont(35));
p.setPen(QColor(0xff, 0xff, 0xff));
}
const QRect r = QRect(50, 247, 100, 50);
p.drawText(r, Qt::AlignCenter, net_type);

21
selfdrive/ui/qt/sidebar.h Executable file → Normal file
View File

@@ -71,17 +71,22 @@ private:
ItemStatus cpu_status, memory_status, storage_status;
bool isCPU;
bool isGPU;
bool isIP;
bool isMemoryUsage;
bool isStorageLeft;
bool isStorageUsed;
std::unordered_map<int, std::pair<QString, std::vector<QColor>>> themeConfiguration;
std::unordered_map<int, QPixmap> flag_imgs;
std::unordered_map<int, QPixmap> home_imgs;
std::unordered_map<int, QPixmap> settings_imgs;
std::vector<QColor> currentColors;
bool isCPU;
bool isFahrenheit;
bool isGPU;
bool isMemoryUsage;
bool isNumericalTemp;
bool isStorageLeft;
bool isStorageUsed;
std::unordered_map<int, std::pair<QString, std::vector<QColor>>> holidayThemeConfiguration;
std::unordered_map<int, QPixmap> holiday_flag_imgs;
std::unordered_map<int, QPixmap> holiday_home_imgs;
std::unordered_map<int, QPixmap> holiday_settings_imgs;
std::vector<QColor> currentColors;
};

2
selfdrive/ui/qt/spinner.cc Executable file → Normal file
View File

@@ -88,7 +88,7 @@ Spinner::Spinner(QWidget *parent) : QWidget(parent) {
}
QProgressBar::chunk {
border-radius: 10px;
background-color: rgba(179, 0, 0, 255);
background-color: rgba(23, 134, 68, 255);
}
)");

0
selfdrive/ui/qt/spinner.h Executable file → Normal file
View File

48
selfdrive/ui/qt/text.cc Executable file → Normal file
View File

@@ -4,32 +4,11 @@
#include <QScrollBar>
#include <QVBoxLayout>
#include <QWidget>
#include <QProcess>
#include "system/hardware/hw.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/qt/qt_window.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
#include "selfdrive/ui/qt/network/wifi_manager.h"
// wifi connection screen
// wifi = new QWidget;
// {
// QVBoxLayout *layout = new QVBoxLayout(wifi);
// layout->setContentsMargins(100, 100, 100, 100);
// Networking *networking = new Networking(this, false);
// networking->setStyleSheet("Networking { background-color: #292929; border-radius: 13px; }");
// layout->addWidget(networking, 1);
// QPushButton *back = new QPushButton(tr("Back"));
// back->setObjectName("navBtn");
// back->setStyleSheet("padding-left: 60px; padding-right: 60px;");
// QObject::connect(back, &QPushButton::clicked, [=]() {
// setCurrentWidget(prompt);
// });
// layout->addWidget(back, 0, Qt::AlignLeft);
// }
int main(int argc, char *argv[]) {
initApp(argc, argv);
@@ -52,36 +31,11 @@ int main(int argc, char *argv[]) {
scroll->verticalScrollBar()->setValue(scroll->verticalScrollBar()->maximum());
});
QPushButton *btnupdate = new QPushButton();
#ifdef __aarch64__
btnupdate->setText(QObject::tr("Update"));
QObject::connect(btnupdate, &QPushButton::clicked, [=]() {
QProcess process;
label->setText("Attempting to connect to wifi");
// TODO: make this work, then copy the compiled binary into git
// wifi = new WifiManager(null);
// connect(wifi, &WifiManager::refreshSignal, [=]() {
// label->setText("Performing update");
// process.setWorkingDirectory("/data/openpilot/");
// process.start("/bin/bash", QStringList{"-c", "update.sh"});
// process.waitForFinished();
// label->setText("Rebooting");
// Hardware::reboot();
// });
// wifi->start();
});
#else
btnupdate->setText(QObject::tr("Exit"));
QObject::connect(btnupdate, &QPushButton::clicked, &a, &QApplication::quit);
#endif
main_layout->addWidget(btnupdate, 0, 0, Qt::AlignLeft | Qt::AlignBottom);
QPushButton *btn = new QPushButton();
#ifdef __aarch64__
btn->setText(QObject::tr("Reboot"));
QObject::connect(btn, &QPushButton::clicked, [=]() {
Hardware::reboot(); // bbot this is the dreaded crash reboot button
Hardware::reboot();
});
#else
btn->setText(QObject::tr("Exit"));

8
selfdrive/ui/qt/util.cc Executable file → Normal file
View File

@@ -5,6 +5,7 @@
#include <vector>
#include <QApplication>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QHash>
@@ -25,7 +26,7 @@ QString getVersion() {
}
QString getBrand() {
return Params().getBool("Passive") ? QObject::tr("dashcam") : QObject::tr("OscarPilot");
return QObject::tr("FrogPilot");
}
QString getUserAgent() {
@@ -114,6 +115,11 @@ void initApp(int argc, char *argv[], bool disable_hidpi) {
qputenv("QT_DBL_CLICK_DIST", QByteArray::number(150));
// ensure the current dir matches the exectuable's directory
QApplication tmp(argc, argv);
QString appDir = QCoreApplication::applicationDirPath();
QDir::setCurrent(appDir);
setQtSurfaceFormat();
}

0
selfdrive/ui/qt/util.h Executable file → Normal file
View File

3
selfdrive/ui/qt/widgets/cameraview.cc Executable file → Normal file
View File

@@ -41,6 +41,8 @@ const char frame_fragment_shader[] =
"out vec4 colorOut;\n"
"void main() {\n"
" colorOut = texture(uTexture, vTexCoord);\n"
// gamma to improve worst case visibility when dark
" colorOut.rgb = pow(colorOut.rgb, vec3(1.0/1.28));\n"
"}\n";
#else
#ifdef __APPLE__
@@ -217,7 +219,6 @@ 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);
frame_mat.v[0] *= -1.0;
}
} else {
// Project point at "infinity" to compute x and y offsets

0
selfdrive/ui/qt/widgets/cameraview.h Executable file → Normal file
View File

0
selfdrive/ui/qt/widgets/controls.cc Executable file → Normal file
View File

27
selfdrive/ui/qt/widgets/controls.h Executable file → Normal file
View File

@@ -127,15 +127,15 @@ public:
QObject::connect(&toggle, &Toggle::stateChanged, this, &ToggleControl::toggleFlipped);
}
void setVisualOn() {
toggle.togglePosition();
}
void setEnabled(bool enabled) {
toggle.setEnabled(enabled);
toggle.update();
}
void refresh() {
toggle.togglePosition();
}
signals:
void toggleFlipped(bool state);
@@ -206,7 +206,7 @@ public:
background-color: #4a4a4a;
}
QPushButton:checked:enabled {
background-color: #0048FF;
background-color: #33Ab4C;
}
QPushButton:disabled {
color: #33E4E4E4;
@@ -227,10 +227,8 @@ public:
button_group->addButton(button, i);
}
QObject::connect(button_group, QOverload<int, bool>::of(&QButtonGroup::buttonToggled), [=](int id, bool checked) {
if (checked) {
QObject::connect(button_group, QOverload<int>::of(&QButtonGroup::buttonClicked), [=](int id) {
params.put(key, std::to_string(id));
}
});
}
@@ -240,6 +238,19 @@ public:
}
}
void setCheckedButton(int id) {
button_group->button(id)->setChecked(true);
}
void refresh() {
int value = atoi(params.get(key).c_str());
button_group->button(value)->setChecked(true);
}
void showEvent(QShowEvent *event) override {
refresh();
}
private:
std::string key;
Params params;

38
selfdrive/ui/qt/widgets/drive_stats.cc Executable file → Normal file
View File

@@ -5,7 +5,6 @@
#include <QJsonObject>
#include <QVBoxLayout>
#include "common/params.h"
#include "selfdrive/ui/qt/request_repeater.h"
#include "selfdrive/ui/qt/util.h"
@@ -16,19 +15,19 @@ static QLabel* newLabel(const QString& text, const QString &type) {
}
DriveStats::DriveStats(QWidget* parent) : QFrame(parent) {
metric_ = Params().getBool("IsMetric");
metric_ = params.getBool("IsMetric");
QVBoxLayout* main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(50, 50, 50, 60);
main_layout->setContentsMargins(50, 25, 50, 20);
auto add_stats_layouts = [=](const QString &title, StatsLabels& labels) {
auto add_stats_layouts = [=](const QString &title, StatsLabels& labels, bool FrogPilot=false) {
QGridLayout* grid_layout = new QGridLayout;
grid_layout->setVerticalSpacing(10);
grid_layout->setContentsMargins(0, 10, 0, 10);
int row = 0;
grid_layout->addWidget(newLabel(title, "title"), row++, 0, 1, 3);
grid_layout->addItem(new QSpacerItem(0, 50), row++, 0, 1, 1);
grid_layout->addWidget(newLabel(title, FrogPilot ? "frogpilot_title" : "title"), row++, 0, 1, 3);
grid_layout->addItem(new QSpacerItem(0, 10), row++, 0, 1, 1);
grid_layout->addWidget(labels.routes = newLabel("0", "number"), row, 0, Qt::AlignLeft);
grid_layout->addWidget(labels.distance = newLabel("0", "number"), row, 1, Qt::AlignLeft);
@@ -39,11 +38,12 @@ DriveStats::DriveStats(QWidget* parent) : QFrame(parent) {
grid_layout->addWidget(newLabel(tr("Hours"), "unit"), row + 1, 2, Qt::AlignLeft);
main_layout->addLayout(grid_layout);
main_layout->addStretch(1);
};
add_stats_layouts(tr("ALL TIME"), all_);
main_layout->addStretch();
add_stats_layouts(tr("PAST WEEK"), week_);
add_stats_layouts(tr("FROGPILOT"), frogPilot_, true);
if (auto dongleId = getDongleId()) {
QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/stats";
@@ -57,13 +57,25 @@ DriveStats::DriveStats(QWidget* parent) : QFrame(parent) {
border-radius: 10px;
}
QLabel[type="title"] { font-size: 51px; font-weight: 500; }
QLabel[type="number"] { font-size: 78px; font-weight: 500; }
QLabel[type="unit"] { font-size: 51px; font-weight: 300; color: #A0A0A0; }
QLabel[type="title"] { font-size: 50px; font-weight: 500; }
QLabel[type="frogpilot_title"] { font-size: 50px; font-weight: 500; color: #178643; }
QLabel[type="number"] { font-size: 65px; font-weight: 400; }
QLabel[type="unit"] { font-size: 50px; font-weight: 300; color: #A0A0A0; }
)");
}
void DriveStats::updateStats() {
QJsonObject json = stats_.object();
auto updateFrogPilot = [this](const QJsonObject& obj, StatsLabels& labels) {
labels.routes->setText(QString::number(paramsStorage.getInt("FrogPilotDrives")));
labels.distance->setText(QString::number(int(paramsStorage.getFloat("FrogPilotKilometers") * (metric_ ? 1 : KM_TO_MILE))));
labels.distance_unit->setText(getDistanceUnit());
labels.hours->setText(QString::number(int(paramsStorage.getFloat("FrogPilotMinutes") / 60)));
};
updateFrogPilot(json["frogpilot"].toObject(), frogPilot_);
auto update = [=](const QJsonObject& obj, StatsLabels& labels) {
labels.routes->setText(QString::number((int)obj["routes"].toDouble()));
labels.distance->setText(QString::number(int(obj["distance"].toDouble() * (metric_ ? MILE_TO_KM : 1))));
@@ -71,7 +83,6 @@ void DriveStats::updateStats() {
labels.hours->setText(QString::number((int)(obj["minutes"].toDouble() / 60)));
};
QJsonObject json = stats_.object();
update(json["all"].toObject(), all_);
update(json["week"].toObject(), week_);
}
@@ -89,9 +100,6 @@ void DriveStats::parseResponse(const QString& response, bool success) {
}
void DriveStats::showEvent(QShowEvent* event) {
bool metric = Params().getBool("IsMetric");
if (metric_ != metric) {
metric_ = metric;
metric_ = params.getBool("IsMetric");
updateStats();
}
}

6
selfdrive/ui/qt/widgets/drive_stats.h Executable file → Normal file
View File

@@ -3,6 +3,8 @@
#include <QJsonDocument>
#include <QLabel>
#include "common/params.h"
class DriveStats : public QFrame {
Q_OBJECT
@@ -15,10 +17,12 @@ private:
inline QString getDistanceUnit() const { return metric_ ? tr("KM") : tr("Miles"); }
bool metric_;
Params params;
Params paramsStorage{"/persist/params"};
QJsonDocument stats_;
struct StatsLabels {
QLabel *routes, *distance, *distance_unit, *hours;
} all_, week_;
} all_, week_, frogPilot_;
private slots:
void parseResponse(const QString &response, bool success);

0
selfdrive/ui/qt/widgets/input.cc Executable file → Normal file
View File

0
selfdrive/ui/qt/widgets/input.h Executable file → Normal file
View File

0
selfdrive/ui/qt/widgets/keyboard.cc Executable file → Normal file
View File

0
selfdrive/ui/qt/widgets/keyboard.h Executable file → Normal file
View File

8
selfdrive/ui/qt/widgets/offroad_alerts.cc Executable file → Normal file
View File

@@ -37,13 +37,9 @@ AbstractAlert::AbstractAlert(bool hasRebootBtn, QWidget *parent) : QFrame(parent
disable_check_btn->setFixedSize(625, 125);
footer_layout->addWidget(disable_check_btn, 1, Qt::AlignBottom | Qt::AlignCenter);
QObject::connect(disable_check_btn, &QPushButton::clicked, [=]() {
if (!params.getBool("FireTheBabysitter")) {
params.putBool("FireTheBabysitter", true);
}
if (!params.getBool("OfflineMode")) {
params.putBool("SnoozeUpdate", true);
params.putBool("DeviceManagement", true);
params.putBool("OfflineMode", true);
}
Hardware::reboot();
});
QObject::connect(disable_check_btn, &QPushButton::clicked, this, &AbstractAlert::dismiss);
disable_check_btn->setStyleSheet(R"(color: white; background-color: #4F4F4F;)");

0
selfdrive/ui/qt/widgets/offroad_alerts.h Executable file → Normal file
View File

0
selfdrive/ui/qt/widgets/prime.cc Executable file → Normal file
View File

0
selfdrive/ui/qt/widgets/prime.h Executable file → Normal file
View File

0
selfdrive/ui/qt/widgets/scrollview.cc Executable file → Normal file
View File

1
selfdrive/ui/qt/widgets/scrollview.h Executable file → Normal file
View File

@@ -10,6 +10,7 @@ public:
// FrogPilot functions
void restorePosition(int previousScrollPosition);
protected:
void hideEvent(QHideEvent *e) override;
};

0
selfdrive/ui/qt/widgets/ssh_keys.cc Executable file → Normal file
View File

0
selfdrive/ui/qt/widgets/ssh_keys.h Executable file → Normal file
View File

2
selfdrive/ui/qt/widgets/toggle.cc Executable file → Normal file
View File

@@ -75,7 +75,7 @@ void Toggle::setEnabled(bool value) {
enabled = value;
if (value) {
circleColor.setRgb(0xfafafa);
green.setRgb(0x0048FF);
green.setRgb(0x33ab4c);
} else {
circleColor.setRgb(0x888888);
green.setRgb(0x227722);

0
selfdrive/ui/qt/widgets/toggle.h Executable file → Normal file
View File

31
selfdrive/ui/qt/widgets/wifi.cc Executable file → Normal file
View File

@@ -81,6 +81,34 @@ WiFiPromptWidget::WiFiPromptWidget(QWidget *parent) : QFrame(parent) {
}
stack->addWidget(uploading);
QWidget *notUploading = new QWidget;
QVBoxLayout *not_uploading_layout = new QVBoxLayout(notUploading);
not_uploading_layout->setContentsMargins(64, 56, 64, 56);
not_uploading_layout->setSpacing(36);
{
QHBoxLayout *title_layout = new QHBoxLayout;
{
QLabel *title = new QLabel(tr("Uploading disabled"));
title->setStyleSheet("font-size: 64px; font-weight: 600;");
title->setWordWrap(true);
title->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
title_layout->addWidget(title);
title_layout->addStretch();
QLabel *icon = new QLabel;
QPixmap pixmap("../frogpilot/assets/other_images/icon_wifi_uploading_disabled.svg");
icon->setPixmap(pixmap.scaledToWidth(120, Qt::SmoothTransformation));
title_layout->addWidget(icon);
}
not_uploading_layout->addLayout(title_layout);
QLabel *desc = new QLabel(tr("Toggle off the 'Disable Uploading' toggle to enable uploads."));
desc->setStyleSheet("font-size: 48px; font-weight: 400;");
desc->setWordWrap(true);
not_uploading_layout->addWidget(desc);
}
stack->addWidget(notUploading);
setStyleSheet(R"(
WiFiPromptWidget {
background-color: #333333;
@@ -99,5 +127,6 @@ void WiFiPromptWidget::updateState(const UIState &s) {
auto network_type = sm["deviceState"].getDeviceState().getNetworkType();
auto uploading = network_type == cereal::DeviceState::NetworkType::WIFI ||
network_type == cereal::DeviceState::NetworkType::ETHERNET;
stack->setCurrentIndex(uploading ? 1 : 0);
bool uploading_disabled = params.getBool("DeviceManagement") && params.getBool("NoUploads");
stack->setCurrentIndex(uploading_disabled ? 2 : uploading ? 1 : 0);
}

4
selfdrive/ui/qt/widgets/wifi.h Executable file → Normal file
View File

@@ -18,6 +18,10 @@ signals:
public slots:
void updateState(const UIState &s);
private:
// FrogPilot variables
Params params;
protected:
QStackedLayout *stack;
};

37
selfdrive/ui/qt/window.cc Executable file → Normal file
View File

@@ -13,14 +13,14 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) {
QObject::connect(homeWindow, &HomeWindow::openSettings, this, &MainWindow::openSettings);
QObject::connect(homeWindow, &HomeWindow::closeSettings, this, &MainWindow::closeSettings);
oscarSettingsWindow = new OscarSettingsWindow(this);
main_layout->addWidget(oscarSettingsWindow);
QObject::connect(oscarSettingsWindow, &OscarSettingsWindow::closeSettings, this, &MainWindow::closeSettings);
// QObject::connect(oscarSettingsWindow, &OscarSettingsWindow::reviewTrainingGuide, [=]() {
// onboardingWindow->showTrainingGuide();
// main_layout->setCurrentWidget(onboardingWindow);
// });
QObject::connect(oscarSettingsWindow, &OscarSettingsWindow::showDriverView, [=] {
settingsWindow = new SettingsWindow(this);
main_layout->addWidget(settingsWindow);
QObject::connect(settingsWindow, &SettingsWindow::closeSettings, this, &MainWindow::closeSettings);
QObject::connect(settingsWindow, &SettingsWindow::reviewTrainingGuide, [=]() {
onboardingWindow->showTrainingGuide();
main_layout->setCurrentWidget(onboardingWindow);
});
QObject::connect(settingsWindow, &SettingsWindow::showDriverView, [=] {
homeWindow->showDriverView(true);
});
@@ -38,11 +38,11 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) {
closeSettings();
}
});
// QObject::connect(device(), &Device::interactiveTimeout, [=]() {
// if (main_layout->currentWidget() == oscarSettingsWindow) {
// closeSettings();
// }
// });
QObject::connect(device(), &Device::interactiveTimeout, [=]() {
if (main_layout->currentWidget() == settingsWindow) {
closeSettings();
}
});
// load fonts
QFontDatabase::addApplicationFont("../assets/fonts/Inter-Black.ttf");
@@ -66,8 +66,8 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) {
}
void MainWindow::openSettings(int index, const QString &param) {
main_layout->setCurrentWidget(oscarSettingsWindow);
oscarSettingsWindow->setCurrentPanel(index, param);
main_layout->setCurrentWidget(settingsWindow);
settingsWindow->setCurrentPanel(index, param);
}
void MainWindow::closeSettings() {
@@ -93,12 +93,7 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
case QEvent::MouseMove: {
// ignore events when device is awakened by resetInteractiveTimeout
ignore = !device()->isAwake();
// if (main_layout->currentWidget() == oscarSettingsWindow) {
// Not working...
// device()->resetInteractiveTimeout(60 * 5); // 5 minute timeout if looking at settings window
// } else {
device()->resetInteractiveTimeout(); // Default 30 seconds otherwise
// }
device()->resetInteractiveTimeout(uiState()->scene.screen_timeout, uiState()->scene.screen_timeout_onroad);
break;
}
default:

3
selfdrive/ui/qt/window.h Executable file → Normal file
View File

@@ -6,7 +6,6 @@
#include "selfdrive/ui/qt/home.h"
#include "selfdrive/ui/qt/offroad/onboarding.h"
#include "selfdrive/ui/qt/offroad/settings.h"
#include "selfdrive/oscarpilot/settings/settings.h"
class MainWindow : public QWidget {
Q_OBJECT
@@ -21,7 +20,7 @@ private:
QStackedLayout *main_layout;
HomeWindow *homeWindow;
OscarSettingsWindow *oscarSettingsWindow;
SettingsWindow *settingsWindow;
OnboardingWindow *onboardingWindow;
// FrogPilot variables

106
selfdrive/ui/soundd.py Executable file → Normal file
View File

@@ -1,10 +1,9 @@
import math
import numpy as np
import os
import time
import threading
import wave
from typing import Dict, Optional, Tuple
from cereal import car, messaging
from openpilot.common.basedir import BASEDIR
@@ -29,7 +28,7 @@ DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
sound_list: Dict[int, Tuple[str, Optional[int], float]] = {
sound_list: dict[int, tuple[str, int | None, float]] = {
# AudibleAlert, file name, play count (none for infinite)
AudibleAlert.engage: ("engage.wav", 1, MAX_VOLUME),
AudibleAlert.disengage: ("disengage.wav", 1, MAX_VOLUME),
@@ -42,11 +41,21 @@ sound_list: Dict[int, Tuple[str, Optional[int], float]] = {
AudibleAlert.warningSoft: ("warning_soft.wav", None, MAX_VOLUME),
AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME),
AudibleAlert.firefox: ("firefox.wav", None, MAX_VOLUME),
# Random Events
AudibleAlert.angry: ("angry.wav", 1, MAX_VOLUME),
AudibleAlert.doc: ("doc.wav", 1, MAX_VOLUME),
AudibleAlert.fart: ("fart.wav", 1, MAX_VOLUME),
AudibleAlert.firefox: ("firefox.wav", 1, MAX_VOLUME),
AudibleAlert.nessie: ("nessie.wav", 1, MAX_VOLUME),
AudibleAlert.noice: ("noice.wav", 1, MAX_VOLUME),
AudibleAlert.uwu: ("uwu.wav", 1, MAX_VOLUME),
# Other
AudibleAlert.goat: ("goat.wav", None, MAX_VOLUME),
}
def check_controls_timeout_alert(sm):
controls_missing = time.monotonic() - sm.rcv_time['controlsState']
controls_missing = time.monotonic() - sm.recv_time['controlsState']
if controls_missing > CONTROLS_TIMEOUT:
if sm['controlsState'].enabled and (controls_missing - CONTROLS_TIMEOUT) < 10:
@@ -61,9 +70,20 @@ class Soundd:
self.params = Params()
self.params_memory = Params("/dev/shm/params")
self.update_frogpilot_params()
self.previous_sound_directory = None
self.random_events_directory = BASEDIR + "/selfdrive/frogpilot/assets/random_events/sounds/"
self.load_sounds()
self.random_events_map = {
AudibleAlert.angry: MAX_VOLUME,
AudibleAlert.doc: MAX_VOLUME,
AudibleAlert.fart: MAX_VOLUME,
AudibleAlert.firefox: MAX_VOLUME,
AudibleAlert.nessie: MAX_VOLUME,
AudibleAlert.noice: MAX_VOLUME,
AudibleAlert.uwu: MAX_VOLUME,
}
self.update_frogpilot_params()
self.current_alert = AudibleAlert.none
self.current_volume = MIN_VOLUME
@@ -74,13 +94,22 @@ class Soundd:
self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False)
def load_sounds(self):
self.loaded_sounds: Dict[int, np.ndarray] = {}
self.loaded_sounds: dict[int, np.ndarray] = {}
# Load all sounds
for sound in sound_list:
if sound == AudibleAlert.goat and not self.goat_scream:
continue
filename, play_count, volume = sound_list[sound]
if sound in self.random_events_map:
wavefile = wave.open(self.random_events_directory + filename, 'r')
else:
try:
wavefile = wave.open(self.sound_directory + filename, 'r')
except FileNotFoundError:
wavefile = wave.open(BASEDIR + "/selfdrive/assets/sounds/" + filename, 'r')
assert wavefile.getnchannels() == 1
assert wavefile.getsampwidth() == 2
@@ -156,9 +185,15 @@ class Soundd:
while True:
sm.update(0)
if sm.updated['microphone'] and self.current_alert == AudibleAlert.none: # only update volume filter when not playing alert
if sm.updated['microphone'] and self.current_alert == AudibleAlert.none and not self.alert_volume_control: # only update volume filter when not playing alert
self.spl_filter_weighted.update(sm["microphone"].soundPressureWeightedDb)
self.current_volume = self.calculate_volume(float(self.spl_filter_weighted.x)) if not self.silent_mode else 0
self.current_volume = self.calculate_volume(float(self.spl_filter_weighted.x))
elif self.alert_volume_control and self.current_alert in self.volume_map:
self.current_volume = self.volume_map[self.current_alert] / 100.0
elif self.current_alert in self.random_events_map:
self.current_volume = self.random_events_map[self.current_alert]
self.get_audible_alert(sm)
@@ -168,27 +203,66 @@ class Soundd:
# Update FrogPilot parameters
if self.params_memory.get_bool("FrogPilotTogglesUpdated"):
updateFrogPilotParams = threading.Thread(target=self.update_frogpilot_params)
updateFrogPilotParams.start()
self.update_frogpilot_params()
def update_frogpilot_params(self):
self.silent_mode = self.params.get_bool("SilentMode")
self.alert_volume_control = self.params.get_bool("AlertVolumeControl")
self.volume_map = {
AudibleAlert.engage: self.params.get_int("EngageVolume"),
AudibleAlert.disengage: self.params.get_int("DisengageVolume"),
AudibleAlert.refuse: self.params.get_int("RefuseVolume"),
AudibleAlert.prompt: self.params.get_int("PromptVolume"),
AudibleAlert.promptRepeat: self.params.get_int("PromptVolume"),
AudibleAlert.promptDistracted: self.params.get_int("PromptDistractedVolume"),
AudibleAlert.warningSoft: self.params.get_int("WarningSoftVolume"),
AudibleAlert.warningImmediate: self.params.get_int("WarningImmediateVolume"),
AudibleAlert.goat: self.params.get_int("PromptVolume"),
}
custom_theme = self.params.get_bool("CustomTheme")
custom_sounds = self.params.get_int("CustomSounds") if custom_theme else 0
self.goat_scream = custom_sounds == 1 and self.params.get_bool("GoatScream")
theme_configuration = {
0: "stock",
1: "frog_theme",
2: "tesla_theme",
3: "stalin_theme"
}
theme_name = theme_configuration.get(custom_sounds, "stock")
self.sound_directory = (f"{BASEDIR}/selfdrive/frogpilot/assets/custom_themes/{theme_name}/sounds/" if custom_sounds else f"{BASEDIR}/selfdrive/assets/sounds/")
holiday_themes = custom_theme and self.params.get_bool("HolidayThemes")
current_holiday_theme = self.params_memory.get_int("CurrentHolidayTheme") if holiday_themes else 0
holiday_theme_configuration = {
1: "april_fools",
2: "christmas",
3: "cinco_de_mayo",
4: "easter",
5: "fourth_of_july",
6: "halloween",
7: "new_years_day",
8: "st_patricks_day",
9: "thanksgiving",
10: "valentines_day",
11: "world_frog_day",
}
if current_holiday_theme != 0:
theme_name = holiday_theme_configuration.get(current_holiday_theme)
self.sound_directory = BASEDIR + ("/selfdrive/frogpilot/assets/holiday_themes/" + theme_name + "/sounds/")
self.goat_scream = False
else:
theme_name = theme_configuration.get(custom_sounds)
self.sound_directory = BASEDIR + ("/selfdrive/frogpilot/assets/custom_themes/" + theme_name + "/sounds/" if custom_sounds != 0 else "/selfdrive/assets/sounds/")
if self.sound_directory != self.previous_sound_directory:
self.load_sounds()
self.previous_sound_directory = self.sound_directory
def main():
s = Soundd()
s.soundd_thread()

View File

@@ -1,7 +1,7 @@
#!/bin/sh
if [ -f /TICI ] && [ ! -f qt/spinner ]; then
cp qt/spinner_larch64 qt/spinner
if [ -f /TICI ] && [ ! -f _spinner ]; then
cp qt/spinner_larch64 _spinner
fi
exec ./qt/spinner "$1"
exec ./_spinner "$1"

6
selfdrive/ui/tests/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
test
playsound
test_sound
test_translations
ui_snapshot
test_ui/report

View File

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python3
import time
import cereal.messaging as messaging
if __name__ == "__main__":
while True:
pm = messaging.PubMaster(['carParams', 'carState'])
batt = 1.
while True:
msg = messaging.new_message('carParams')
msg.carParams.carName = "COMMA BODY"
msg.carParams.notCar = True
pm.send('carParams', msg)
for b in range(100, 0, -1):
msg = messaging.new_message('carState')
msg.carState.charging = True
msg.carState.fuelGauge = b / 100.
pm.send('carState', msg)
time.sleep(0.1)
time.sleep(1)

View File

@@ -0,0 +1,18 @@
#!/bin/bash
set -e
UI_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"/..
TEST_TEXT="(WRAPPED_SOURCE_TEXT)"
TEST_TS_FILE=$UI_DIR/translations/main_test_en.ts
TEST_QM_FILE=$UI_DIR/translations/main_test_en.qm
# translation strings
UNFINISHED="<translation type=\"unfinished\"><\/translation>"
TRANSLATED="<translation>$TEST_TEXT<\/translation>"
mkdir -p $UI_DIR/translations
rm -f $TEST_TS_FILE $TEST_QM_FILE
lupdate -recursive "$UI_DIR" -ts $TEST_TS_FILE
sed -i "s/$UNFINISHED/$TRANSLATED/" $TEST_TS_FILE
lrelease $TEST_TS_FILE

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env python3
import os
import sys
import time
import json
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert
if __name__ == "__main__":
params = Params()
with open(os.path.join(BASEDIR, "selfdrive/controls/lib/alerts_offroad.json")) as f:
offroad_alerts = json.load(f)
t = 10 if len(sys.argv) < 2 else int(sys.argv[1])
while True:
print("setting alert update")
params.put_bool("UpdateAvailable", True)
r = open(os.path.join(BASEDIR, "RELEASES.md")).read()
r = r[:r.find('\n\n')] # Slice latest release notes
params.put("UpdaterNewReleaseNotes", r + "\n")
time.sleep(t)
params.put_bool("UpdateAvailable", False)
# cycle through normal alerts
for a in offroad_alerts:
print("setting alert:", a)
set_offroad_alert(a, True)
time.sleep(t)
set_offroad_alert(a, False)
print("no alert")
time.sleep(t)

View File

@@ -0,0 +1,30 @@
#include <QApplication>
#include <QSoundEffect>
#include <QTimer>
#include <QDebug>
int main(int argc, char **argv) {
QApplication a(argc, argv);
QTimer::singleShot(0, [=]{
QSoundEffect s;
const char *vol = getenv("VOLUME");
s.setVolume(vol ? atof(vol) : 1.0);
for (int i = 1; i < argc; i++) {
QString fn = argv[i];
qDebug() << "playing" << fn;
QEventLoop loop;
s.setSource(QUrl::fromLocalFile(fn));
QEventLoop::connect(&s, &QSoundEffect::loadedChanged, &loop, &QEventLoop::quit);
loop.exec();
s.play();
QEventLoop::connect(&s, &QSoundEffect::playingChanged, &loop, &QEventLoop::quit);
loop.exec();
}
QCoreApplication::exit();
});
return a.exec();
}

View File

@@ -0,0 +1,25 @@
#define CATCH_CONFIG_RUNNER
#include "catch2/catch.hpp"
#include <QApplication>
#include <QDebug>
#include <QDir>
#include <QTranslator>
int main(int argc, char **argv) {
// unit tests for Qt
QApplication app(argc, argv);
QString language_file = "main_test_en";
qDebug() << "Loading language:" << language_file;
QTranslator translator;
QString translationsPath = QDir::cleanPath(qApp->applicationDirPath() + "/../translations");
if (!translator.load(language_file, translationsPath)) {
qDebug() << "Failed to load translation file!";
}
app.installTranslator(&translator);
const int res = Catch::Session().run(argc, argv);
return (res < 0xff ? res : 0xff);
}

View File

@@ -0,0 +1,41 @@
#!/usr/bin/env python3
import unittest
from cereal import car
from cereal import messaging
from cereal.messaging import SubMaster, PubMaster
from openpilot.selfdrive.ui.soundd import CONTROLS_TIMEOUT, check_controls_timeout_alert
import time
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
class TestSoundd(unittest.TestCase):
def test_check_controls_timeout_alert(self):
sm = SubMaster(['controlsState'])
pm = PubMaster(['controlsState'])
for _ in range(100):
cs = messaging.new_message('controlsState')
cs.controlsState.enabled = True
pm.send("controlsState", cs)
time.sleep(0.01)
sm.update(0)
self.assertFalse(check_controls_timeout_alert(sm))
for _ in range(CONTROLS_TIMEOUT * 110):
sm.update(0)
time.sleep(0.01)
self.assertTrue(check_controls_timeout_alert(sm))
# TODO: add test with micd for checking that soundd actually outputs sounds
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,48 @@
#include "catch2/catch.hpp"
#include "common/params.h"
#include "selfdrive/ui/qt/window.h"
const QString TEST_TEXT = "(WRAPPED_SOURCE_TEXT)"; // what each string should be translated to
QRegExp RE_NUM("\\d*");
QStringList getParentWidgets(QWidget* widget){
QStringList parentWidgets;
while (widget->parentWidget() != Q_NULLPTR) {
widget = widget->parentWidget();
parentWidgets.append(widget->metaObject()->className());
}
return parentWidgets;
}
template <typename T>
void checkWidgetTrWrap(MainWindow &w) {
for (auto widget : w.findChildren<T>()) {
const QString text = widget->text();
bool isNumber = RE_NUM.exactMatch(text);
bool wrapped = text.contains(TEST_TEXT);
QString parentWidgets = getParentWidgets(widget).join("->");
if (!text.isEmpty() && !isNumber && !wrapped) {
FAIL(("\"" + text + "\" must be wrapped. Parent widgets: " + parentWidgets).toStdString());
}
// warn if source string wrapped, but UI adds text
// TODO: add way to ignore this
if (wrapped && text != TEST_TEXT) {
WARN(("\"" + text + "\" is dynamic and needs a custom retranslate function. Parent widgets: " + parentWidgets).toStdString());
}
}
}
// Tests all strings in the UI are wrapped with tr()
TEST_CASE("UI: test all strings wrapped") {
Params().remove("LanguageSetting");
Params().remove("HardwareSerial");
Params().remove("DongleId");
qputenv("TICI", "1");
MainWindow w;
checkWidgetTrWrap<QPushButton*>(w);
checkWidgetTrWrap<QLabel*>(w);
}

Some files were not shown because too many files have changed in this diff Show More