ui: Split sunnypilot into its own classes

This commit is contained in:
Jason Wen
2024-07-29 10:16:46 +00:00
parent be72a8ed06
commit 447f76d9cf
191 changed files with 10742 additions and 4970 deletions
+8 -45
View File
@@ -7,8 +7,6 @@ import numpy as np
import SCons.Errors
from openpilot.common.basedir import BASEDIR
SCons.Warnings.warningAsException(True)
# pending upstream fix - https://github.com/SCons/scons/issues/4461
@@ -18,45 +16,6 @@ TICI = os.path.isfile('/TICI')
AGNOS = TICI
UBUNTU_FOCAL = int(subprocess.check_output('[ -f /etc/os-release ] && . /etc/os-release && [ "$ID" = "ubuntu" ] && [ "$VERSION_ID" = "20.04" ] && echo 1 || echo 0', shell=True, encoding='utf-8').rstrip())
Export('UBUNTU_FOCAL')
_DEBUG = False
def is_internal_developer(debug=False):
def collect_required_gpg_key_ids(keys_dir):
try:
key_ids = [f.split('.')[0] for f in os.listdir(keys_dir) if f.endswith(".gpg")]
if debug:
print(f"SP: Required GPG key IDs: {key_ids}")
return key_ids
except OSError as e:
if debug:
print(f"SP: Failed to read GPG key IDs from {keys_dir}. Error: {e}")
return []
def is_key_available(required_gpg_key_ids):
for key_id in required_gpg_key_ids:
try:
result = subprocess.check_output(['gpg', '--list-keys', key_id], stderr=subprocess.STDOUT)
if key_id in result.decode():
if debug:
print(f"SP: GPG key {key_id} is available.")
return True
except subprocess.CalledProcessError as e:
if debug:
print(f"SP: Failed to list GPG key {key_id}. Error:", e.output.decode().strip())
return False
keys_dir = os.path.join(BASEDIR, ".git-crypt/keys/default/0")
required_gpg_key_ids = collect_required_gpg_key_ids(keys_dir)
sunnypilot = is_key_available(required_gpg_key_ids)
if sunnypilot:
print("SP: Confirmed sunnypilot internal developer.")
print("SP: Loading sunnypilot elements ...")
elif debug:
print("SP: None of the required GPG keys are available.")
return sunnypilot
Decider('MD5-timestamp')
@@ -113,11 +72,11 @@ AddOption('--minimal',
default=os.path.exists(File('#.lfsconfig').abspath), # minimal by default on release branch (where there's no LFS)
help='the minimum build to run openpilot. no tests, tools, etc.')
AddOption('--sunnypilot',
AddOption('--stock-ui',
action='store_true',
dest='sunnypilot',
default=is_internal_developer(_DEBUG), # check if the current user is a sunnypilot developer
help='build sunnypilot elements and other sunnypilot-specific items that are meant for internal development')
dest='stock_ui',
default=False,
help='Build stock UI instead of sunnypilot UI')
## Architecture name breakdown (arch)
## - larch64: linux tici aarch64
@@ -222,6 +181,10 @@ if arch != "Darwin":
cflags += ['-DSWAGLOG="\\"common/swaglog.h\\""']
cxxflags += ['-DSWAGLOG="\\"common/swaglog.h\\""']
if not GetOption('stock_ui'):
cflags += ["-DSUNNYPILOT"]
cxxflags += ["-DSUNNYPILOT"]
ccflags_option = GetOption('ccflags')
if ccflags_option:
ccflags += ccflags_option.split(' ')
+4
View File
@@ -6,7 +6,11 @@
#include "common/util.h"
#include "selfdrive/ui/qt/util.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h"
#else
#include "selfdrive/ui/qt/maps/map_helpers.h"
#endif
#include "selfdrive/navd/map_renderer.h"
#include "system/hardware/hw.h"
+4
View File
@@ -8,7 +8,11 @@
#include "common/util.h"
#include "common/timing.h"
#include "common/swaglog.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h"
#else
#include "selfdrive/ui/qt/maps/map_helpers.h"
#endif
const float DEFAULT_ZOOM = 13.5; // Don't go below 13 or features will start to disappear
const int HEIGHT = 256, WIDTH = 256;
+11 -8
View File
@@ -18,30 +18,32 @@ if arch == "Darwin":
qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"]
sp_widgets_src = []
sp_maps_widgets_src = []
sp_qt_src = []
if GetOption('sunnypilot'):
sp_qt_util = []
if not GetOption('stock_ui'):
SConscript(['sunnypilot/SConscript'])
Import('sp_widgets_src', 'sp_qt_src')
Import('sp_widgets_src', 'sp_maps_widgets_src', 'sp_qt_src', "sp_qt_util")
qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"], LIBS=base_libs)
qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"] + sp_qt_util, LIBS=base_libs)
widgets_src = ["ui.cc", "qt/widgets/input.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"] + sp_widgets_src
qt_env['CPPDEFINES'] = ["SUNNYPILOT"] if GetOption('sunnypilot') else []
qt_env['CPPDEFINES'] = []
if maps:
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/maps/map_eta.cc", "qt/maps/map_instructions.cc"] + sp_maps_widgets_src
qt_env['CPPDEFINES'] += ["ENABLE_MAPS"]
widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs)
Export('widgets')
qt_libs = [widgets, qt_util] + base_libs
qt_src = ["main.cc", "qt/sidebar.cc", "qt/body.cc",
qt_src = ["main.cc", "qt/sidebar.cc", "qt/body.cc", "qt/offroad_home.cc",
"qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc",
"qt/offroad/software_settings.cc", "qt/offroad/onboarding.cc",
"qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc",
@@ -79,14 +81,15 @@ 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("_text", ["qt/text.cc"], LIBS=qt_libs)
text_cc_path = "qt/text.cc" if GetOption('stock_ui') else "sunnypilot/qt/text.cc"
qt_env.Program("_text", [text_cc_path], 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)
if GetOption('extras'):
qt_src.remove("main.cc") # replaced by test_runner
#qt_env.Program('tests/test_translations', [asset_obj, 'tests/test_runner.cc', 'tests/test_translations.cc'] + qt_src, LIBS=qt_libs)
qt_env.Program('tests/test_translations', [asset_obj, 'tests/test_runner.cc', 'tests/test_translations.cc'] + qt_src, LIBS=qt_libs)
qt_env.Program('tests/ui_snapshot', [asset_obj, "tests/ui_snapshot.cc"] + qt_src, LIBS=qt_libs)
+6 -1
View File
@@ -6,7 +6,13 @@
#include "system/hardware/hw.h"
#include "selfdrive/ui/qt/qt_window.h"
#include "selfdrive/ui/qt/util.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/qt/window.h"
#define MainWindow MainWindowSP
#else
#include "selfdrive/ui/qt/window.h"
#endif
int main(int argc, char *argv[]) {
setpriority(PRIO_PROCESS, 0, -20);
@@ -22,7 +28,6 @@ int main(int argc, char *argv[]) {
QApplication a(argc, argv);
a.installTranslator(&translator);
MainWindow w;
setMainWindow(&w);
a.installEventFilter(&w);
+19 -143
View File
@@ -2,7 +2,6 @@
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/aes.h>
#include <QApplication>
#include <QCryptographicHash>
@@ -10,12 +9,10 @@
#include <QDebug>
#include <QJsonDocument>
#include <QNetworkRequest>
#include <QSsl>
#include <memory>
#include <string>
#include "common/swaglog.h"
#include "common/util.h"
#include "system/hardware/hw.h"
#include "selfdrive/ui/qt/util.h"
@@ -48,130 +45,11 @@ QByteArray rsa_sign(const QByteArray &data) {
return sig;
}
void derive_aes_key_iv_from_rsa(EVP_PKEY *rsa_key, unsigned char *aes_key, unsigned char *aes_iv) {
unsigned char hash[SHA256_DIGEST_LENGTH];
size_t pub_len;
unsigned char *pub_key;
// Convert RSA key to public key in DER format for simplicity
pub_len = i2d_PublicKey(rsa_key, NULL);
pub_key = (unsigned char *)malloc(pub_len);
unsigned char *tmp = pub_key;
i2d_PublicKey(rsa_key, &tmp);
// Hash the public key to derive bytes for AES key and IV
SHA256(pub_key, pub_len, hash);
// Assuming AES-256-CBC, we need 32 bytes for the key and 16 for the IV
memcpy(aes_key, hash, 32); // First 32 bytes for AES key
memcpy(aes_iv, hash + 32 - 16, 16); // Last 16 bytes for AES IV
free(pub_key);
}
EVP_PKEY *load_public_key(const char *file) {
EVP_PKEY *key = NULL;
FILE *fp = fopen(file, "r");
if (fp) {
key = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
fclose(fp);
}
return key;
}
EVP_PKEY *load_private_key(const char *file) {
EVP_PKEY *key = NULL;
FILE *fp = fopen(file, "r");
if (fp) {
key = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
fclose(fp);
}
return key;
}
QByteArray rsa_encrypt(const QByteArray &data) {
EVP_PKEY *rsa_key = load_public_key(Path::rsa_pub_file().c_str()); // Load your RSA key
unsigned char aes_key[32], aes_iv[16];
derive_aes_key_iv_from_rsa(rsa_key, aes_key, aes_iv);
EVP_CIPHER_CTX *ctx;
if (!(ctx = EVP_CIPHER_CTX_new())) {
// Handle error: Allocate memory failed
return {};
}
if (EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, aes_key, aes_iv) != 1) {
qDebug() << "Failed to initialize encryption";
EVP_CIPHER_CTX_free(ctx);
return {};
}
int ciphertext_len;
int len = data.size();
unsigned char out[len + AES_BLOCK_SIZE];
auto *in = (unsigned char*) data.constData();
if (EVP_EncryptUpdate(ctx, out, &ciphertext_len, in, len) != 1) {
qDebug() << "Failed to update encryption";
EVP_CIPHER_CTX_free(ctx);
return {};
}
if (EVP_EncryptFinal_ex(ctx, out + ciphertext_len, &len) != 1) {
// Handle error: Encryption finalize failed
qDebug() << "Failed to finalize encryption";
EVP_CIPHER_CTX_free(ctx);
return {};
}
//qDebug() << "Encrypted data length: %d", ciphertext_len + len; // Print the length of encrypted data
EVP_CIPHER_CTX_free(ctx);
return {reinterpret_cast<char*>(out), ciphertext_len + len};
}
QByteArray rsa_decrypt(const QByteArray &data) {
EVP_PKEY *rsa_key = load_public_key(Path::rsa_pub_file().c_str()); // Load your RSA key
unsigned char aes_key[32], aes_iv[16];
derive_aes_key_iv_from_rsa(rsa_key, aes_key, aes_iv);
EVP_CIPHER_CTX *ctx;
if (!(ctx = EVP_CIPHER_CTX_new())) {
qDebug() << "Failed to allocate memory for EVP_CIPHER_CTX";
return {};
}
if (EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, aes_key, aes_iv) != 1) {
qDebug() << "Failed to initialize EVP";
EVP_CIPHER_CTX_free(ctx);
return {};
}
int len = data.size();
unsigned char out[len + AES_BLOCK_SIZE];
auto *in = (unsigned char*) data.constData();
if (EVP_DecryptUpdate(ctx, out, &len, in, len) != 1) {
qDebug() << "Failed to update decryption";
EVP_CIPHER_CTX_free(ctx);
return {};
}
int final_len;
if (EVP_DecryptFinal_ex(ctx, out + len, &final_len) != 1) {
qDebug() << "Failed to finalize decryption";
EVP_CIPHER_CTX_free(ctx);
return {};
}
EVP_CIPHER_CTX_free(ctx);
//qDebug() << "Decrypted data length: %d", len + final_len; // Print the length of decrypted data
return {reinterpret_cast<char*>(out), len + final_len};
}
QString create_jwt(const QJsonObject &payloads, int expiry, bool sunnylink) {
QString create_jwt(const QJsonObject &payloads, int expiry) {
QJsonObject header = {{"alg", "RS256"}};
auto t = QDateTime::currentSecsSinceEpoch();
auto dongle_id = sunnylink ? getSunnylinkDongleId() : getDongleId();
QJsonObject payload = {{"identity", dongle_id.value_or("")}, {"nbf", t}, {"iat", t}, {"exp", t + expiry}};
QJsonObject payload = {{"identity", getDongleId().value_or("")}, {"nbf", t}, {"iat", t}, {"exp", t + expiry}};
for (auto it = payloads.begin(); it != payloads.end(); ++it) {
payload.insert(it.key(), it.value());
}
@@ -186,7 +64,7 @@ QString create_jwt(const QJsonObject &payloads, int expiry, bool sunnylink) {
} // namespace CommaApi
HttpRequest::HttpRequest(QObject *parent, bool create_jwt, int timeout, const bool sunnylink) : create_jwt(create_jwt), sunnylink(sunnylink), QObject(parent) {
HttpRequest::HttpRequest(QObject *parent, bool create_jwt, int timeout) : create_jwt(create_jwt), QObject(parent) {
networkTimer = new QTimer(this);
networkTimer->setSingleShot(true);
networkTimer->setInterval(timeout);
@@ -201,40 +79,38 @@ bool HttpRequest::timeout() const {
return reply && reply->error() == QNetworkReply::OperationCanceledError;
}
void HttpRequest::sendRequest(const QString &requestURL, const HttpRequest::Method method, const QByteArray &payload) {
LOGD("Requesting %s", qPrintable(requestURL));
if (active()) {
qDebug() << "HttpRequest is active";
return;
}
QNetworkRequest HttpRequest::prepareRequest(const QString &requestURL)
{
QNetworkRequest request;
QString token;
if (create_jwt) {
token = CommaApi::create_jwt({}, 3600, sunnylink);
token = GetJwtToken();
} else {
QString token_json = QString::fromStdString(util::read_file(util::getenv("HOME") + "/.comma/auth.json"));
QJsonDocument json_d = QJsonDocument::fromJson(token_json.toUtf8());
token = json_d["access_token"].toString();
}
QNetworkRequest request;
request.setUrl(QUrl(requestURL));
request.setRawHeader("User-Agent", getUserAgent(sunnylink).toUtf8());
if (!payload.isEmpty()) {
request.setRawHeader("Content-Type", "application/json");
}
request.setRawHeader("User-Agent", GetUserAgent().toUtf8());
if (!token.isEmpty()) {
request.setRawHeader(QByteArray("Authorization"), ("JWT " + token).toUtf8());
}
return request;
}
if (method == HttpRequest::Method::GET) {
void HttpRequest::sendRequest(const QString &requestURL, const Method method) {
if (active()) {
qDebug() << "HttpRequest is active";
return;
}
QNetworkRequest request = prepareRequest(requestURL);
if (method == Method::GET) {
reply = nam()->get(request);
} else if (method == HttpRequest::Method::DELETE) {
} else if (method == Method::DELETE) {
reply = nam()->deleteResource(request);
} else if (method == HttpRequest::Method::POST) {
reply = nam()->post(request, payload);
} else if (method == HttpRequest::Method::PUT) {
reply = nam()->put(request, payload);
}
networkTimer->start();
+9 -9
View File
@@ -6,14 +6,13 @@
#include <QTimer>
#include "common/util.h"
#include "selfdrive/ui/qt/util.h"
namespace CommaApi {
const QString BASE_URL = util::getenv("API_HOST", "https://api.commadotai.com").c_str();
QByteArray rsa_sign(const QByteArray &data);
QByteArray rsa_encrypt(const QByteArray &data);
QByteArray rsa_decrypt(const QByteArray &data);
QString create_jwt(const QJsonObject &payloads = {}, int expiry = 3600, bool sunnylink = false);
QString create_jwt(const QJsonObject &payloads = {}, int expiry = 3600);
} // namespace CommaApi
@@ -27,8 +26,9 @@ class HttpRequest : public QObject {
public:
enum class Method {GET, DELETE, POST, PUT};
explicit HttpRequest(QObject* parent, bool create_jwt = true, int timeout = 20000, const bool sunnylink = false);
void sendRequest(const QString &requestURL, const Method method = Method::GET, const QByteArray &payload = {});
explicit HttpRequest(QObject* parent, bool create_jwt = true, int timeout = 20000);
virtual void sendRequest(const QString &requestURL, Method method);
void sendRequest(const QString &requestURL) { sendRequest(requestURL, Method::GET);}
bool active() const;
bool timeout() const;
@@ -37,14 +37,14 @@ signals:
protected:
QNetworkReply *reply = nullptr;
private:
static QNetworkAccessManager *nam();
QTimer *networkTimer = nullptr;
bool create_jwt;
bool sunnylink;
virtual QNetworkRequest prepareRequest(const QString& requestURL);
virtual QString GetJwtToken() const { return CommaApi::create_jwt(); }
virtual QString GetUserAgent() const { return getUserAgent(); }
private slots:
protected slots:
void requestTimeout();
void requestFinished();
};
+4
View File
@@ -5,7 +5,11 @@
#include <QPushButton>
#include "common/util.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/ui.h"
#else
#include "selfdrive/ui/ui.h"
#endif
class RecordButton : public QPushButton {
Q_OBJECT
+1 -171
View File
@@ -9,10 +9,6 @@
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/qt/widgets/prime.h"
#ifdef ENABLE_MAPS
#include "selfdrive/ui/qt/maps/map_settings.h"
#endif
// HomeWindow: the container for the offroad and onroad UIs
HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) {
@@ -32,8 +28,6 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) {
slayout->addWidget(home);
onroad = new OnroadWindow(this);
QObject::connect(onroad, &OnroadWindow::mapPanelRequested, this, [=] { sidebar->hide(); });
QObject::connect(onroad, &OnroadWindow::onroadSettingsPanelRequested, this, [=] { sidebar->hide(); });
slayout->addWidget(onroad);
body = new BodyWindow(this);
@@ -54,10 +48,6 @@ void HomeWindow::showSidebar(bool show) {
sidebar->setVisible(show);
}
void HomeWindow::showMapPanel(bool show) {
onroad->showMapPanel(show);
}
void HomeWindow::updateState(const UIState &s) {
const SubMaster &sm = *(s.sm);
@@ -66,9 +56,6 @@ void HomeWindow::updateState(const UIState &s) {
body->setEnabled(true);
slayout->setCurrentWidget(body);
}
uiState()->scene.map_visible = onroad->isMapVisible();
uiState()->scene.onroad_settings_visible = onroad->isOnroadSettingsVisible();
}
void HomeWindow::offroadTransition(bool offroad) {
@@ -92,23 +79,9 @@ void HomeWindow::showDriverView(bool show) {
}
void HomeWindow::mousePressEvent(QMouseEvent* e) {
if (uiState()->scene.started) {
if (uiState()->scene.onroadScreenOff != -2) {
uiState()->scene.touched2 = true;
QTimer::singleShot(500, []() { uiState()->scene.touched2 = false; });
}
if (uiState()->scene.button_auto_hide) {
uiState()->scene.touch_to_wake = true;
uiState()->scene.sleep_btn_fading_in = true;
QTimer::singleShot(500, []() { uiState()->scene.touch_to_wake = false; });
}
}
// Handle sidebar collapsing
if ((onroad->isVisible() || body->isVisible()) && (!sidebar->isVisible() || e->x() > sidebar->width())) {
if (onroad->wakeScreenTimeout()) {
sidebar->setVisible(!sidebar->isVisible() && !onroad->isMapVisible());
}
sidebar->setVisible(!sidebar->isVisible());
}
}
@@ -124,146 +97,3 @@ void HomeWindow::mouseDoubleClickEvent(QMouseEvent* e) {
showSidebar(false);
}
}
// OffroadHome: the offroad home page
OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
QVBoxLayout* main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(40, 40, 40, 40);
// top header
QHBoxLayout* header_layout = new QHBoxLayout();
header_layout->setContentsMargins(0, 0, 0, 0);
header_layout->setSpacing(16);
update_notif = new QPushButton(tr("UPDATE"));
update_notif->setVisible(false);
update_notif->setStyleSheet("background-color: #364DEF;");
QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); });
header_layout->addWidget(update_notif, 0, Qt::AlignHCenter | Qt::AlignLeft);
alert_notif = new QPushButton();
alert_notif->setVisible(false);
alert_notif->setStyleSheet("background-color: #E22C2C;");
QObject::connect(alert_notif, &QPushButton::clicked, [=] { center_layout->setCurrentIndex(2); });
header_layout->addWidget(alert_notif, 0, Qt::AlignHCenter | Qt::AlignLeft);
version = new ElidedLabel();
header_layout->addWidget(version, 0, Qt::AlignHCenter | Qt::AlignRight);
main_layout->addLayout(header_layout);
// main content
main_layout->addSpacing(25);
center_layout = new QStackedLayout();
QWidget *home_widget = new QWidget(this);
{
QHBoxLayout *home_layout = new QHBoxLayout(home_widget);
home_layout->setContentsMargins(0, 0, 0, 0);
home_layout->setSpacing(30);
// left: MapSettings/PrimeAdWidget
QStackedWidget *left_widget = new QStackedWidget(this);
#ifdef ENABLE_MAPS
left_widget->addWidget(new MapSettings);
#else
left_widget->addWidget(new QWidget);
#endif
custom_mapbox = QString::fromStdString(params.get("CustomMapboxTokenSk")) != "";
if (!custom_mapbox) {
left_widget->addWidget(new PrimeAdWidget);
}
left_widget->setStyleSheet("border-radius: 10px;");
left_widget->setCurrentIndex((uiState()->hasPrime() || custom_mapbox) ? 0 : 1);
connect(uiState(), &UIState::primeChanged, [=](bool prime) {
left_widget->setCurrentIndex((prime || custom_mapbox) ? 0 : 1);
});
home_layout->addWidget(left_widget, 1);
// right: ExperimentalModeButton, SetupWidget
QWidget* right_widget = new QWidget(this);
QVBoxLayout* right_column = new QVBoxLayout(right_widget);
right_column->setContentsMargins(0, 0, 0, 0);
right_widget->setFixedWidth(750);
right_column->setSpacing(30);
ExperimentalModeButton *experimental_mode = new ExperimentalModeButton(this);
QObject::connect(experimental_mode, &ExperimentalModeButton::openSettings, this, &OffroadHome::openSettings);
right_column->addWidget(experimental_mode, 1);
SetupWidget *setup_widget = new SetupWidget;
QObject::connect(setup_widget, &SetupWidget::openSettings, this, &OffroadHome::openSettings);
right_column->addWidget(setup_widget, 1);
home_layout->addWidget(right_widget, 1);
}
center_layout->addWidget(home_widget);
// add update & alerts widgets
update_widget = new UpdateAlert();
QObject::connect(update_widget, &UpdateAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); });
center_layout->addWidget(update_widget);
alerts_widget = new OffroadAlert();
QObject::connect(alerts_widget, &OffroadAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); });
center_layout->addWidget(alerts_widget);
main_layout->addLayout(center_layout, 1);
// set up refresh timer
timer = new QTimer(this);
timer->callOnTimeout(this, &OffroadHome::refresh);
setStyleSheet(R"(
* {
color: white;
}
OffroadHome {
background-color: black;
}
OffroadHome > QPushButton {
padding: 15px 30px;
border-radius: 5px;
font-size: 40px;
font-weight: 500;
}
OffroadHome > QLabel {
font-size: 55px;
}
)");
}
void OffroadHome::showEvent(QShowEvent *event) {
refresh();
timer->start(10 * 1000);
}
void OffroadHome::hideEvent(QHideEvent *event) {
timer->stop();
}
void OffroadHome::refresh() {
version->setText(getBrand() + " " + QString::fromStdString(params.get("UpdaterCurrentDescription")));
bool updateAvailable = update_widget->refresh();
int alerts = alerts_widget->refresh();
// pop-up new notification
int idx = center_layout->currentIndex();
if (!updateAvailable && !alerts) {
idx = 0;
} else if (updateAvailable && (!update_notif->isVisible() || (!alerts && idx == 2))) {
idx = 1;
} else if (alerts && (!alert_notif->isVisible() || (!updateAvailable && idx == 1))) {
idx = 2;
}
center_layout->setCurrentIndex(idx);
update_notif->setVisible(updateAvailable);
alert_notif->setVisible(alerts);
if (alerts) {
alert_notif->setText(QString::number(alerts) + (alerts > 1 ? tr(" ALERTS") : tr(" ALERT")));
}
}
+13 -30
View File
@@ -12,35 +12,20 @@
#include "selfdrive/ui/qt/body.h"
#include "selfdrive/ui/qt/onroad/onroad_home.h"
#include "selfdrive/ui/qt/sidebar.h"
#include "selfdrive/ui/qt/widgets/controls.h"
#include "selfdrive/ui/qt/widgets/offroad_alerts.h"
#include "selfdrive/ui/ui.h"
class OffroadHome : public QFrame {
Q_OBJECT
public:
explicit OffroadHome(QWidget* parent = 0);
signals:
void openSettings(int index = 0, const QString &param = "");
private:
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent *event) override;
void refresh();
Params params;
QTimer* timer;
ElidedLabel* version;
QStackedLayout* center_layout;
UpdateAlert *update_widget;
OffroadAlert* alerts_widget;
QPushButton* alert_notif;
QPushButton* update_notif;
bool custom_mapbox;
};
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#include "selfdrive/ui/sunnypilot/qt/offroad_home.h"
#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_home.h"
#define OnroadWindow OnroadWindowSP
#define OffroadHome OffroadHomeSP
#else
#include "selfdrive/ui/qt/widgets/controls.h"
#include "selfdrive/ui/qt/onroad/onroad_home.h"
#include "selfdrive/ui/qt/offroad_home.h"
#endif
class HomeWindow : public QWidget {
Q_OBJECT
@@ -56,13 +41,11 @@ public slots:
void offroadTransition(bool offroad);
void showDriverView(bool show);
void showSidebar(bool show);
void showMapPanel(bool show);
protected:
void mousePressEvent(QMouseEvent* e) override;
void mouseDoubleClickEvent(QMouseEvent* e) override;
private:
Sidebar *sidebar;
OffroadHome *home;
OnroadWindow *onroad;
@@ -70,6 +53,6 @@ private:
DriverViewWindow *driver_view;
QStackedLayout *slayout;
private slots:
void updateState(const UIState &s);
protected slots:
virtual void updateState(const UIState &s);
};
+9 -62
View File
@@ -6,9 +6,14 @@
#include <QDebug>
#include "common/swaglog.h"
#include "selfdrive/ui/qt/maps/map_helpers.h"
#include "selfdrive/ui/qt/util.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h"
#else
#include "selfdrive/ui/qt/maps/map_helpers.h"
#include "selfdrive/ui/ui.h"
#endif
const int INTERACTION_TIMEOUT = 100;
@@ -54,6 +59,7 @@ MapWindow::~MapWindow() {
}
void MapWindow::initLayers() {
RETURN_IF_SUNNYPILOT
// This doesn't work from initializeGL
if (!m_map->layerExists("modelPathLayer")) {
qDebug() << "Initializing modelPathLayer";
@@ -75,7 +81,7 @@ void MapWindow::initLayers() {
QVariantMap transition;
transition["duration"] = 400; // ms
m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(uiState()->scene.navigate_on_openpilot_deprecated));
m_map->setPaintProperty("navLayer", "line-color", QColor("#31a1ee"));
m_map->setPaintProperty("navLayer", "line-color-transition", transition);
m_map->setPaintProperty("navLayer", "line-width", 7.5);
m_map->setLayoutProperty("navLayer", "line-cap", "round");
@@ -110,52 +116,10 @@ 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")) && uiState()->scene.map_3d_buildings) { // Could put this behind the cellular metered toggle in case it increases data usage
qDebug() << "Initializing buildingsLayer";
QVariantMap buildings;
buildings["id"] = "buildingsLayer";
buildings["source"] = "composite";
buildings["source-layer"] = "building";
buildings["type"] = "fill-extrusion";
buildings["minzoom"] = 15;
m_map->addLayer("buildingsLayer", buildings);
m_map->setFilter("buildingsLayer", QVariantList({"==", "extrude", "true"}));
QVariantList fillExtrusionHeight = { // scale buildings as you zoom in
"interpolate",
QVariantList{"linear"},
QVariantList{"zoom"},
15, 0,
15.05, QVariantList{"get", "height"}
};
QVariantList fillExtrusionBase = {
"interpolate",
QVariantList{"linear"},
QVariantList{"zoom"},
15, 0,
15.05, QVariantList{"get", "min_height"}
};
QVariantList fillExtrusionOpacity = {
"interpolate",
QVariantList{"linear"},
QVariantList{"zoom"},
15, 0, // transparent at zoom level 15
15.5, .6, // fade in
17, .6, // begin fading out
20, 0 // fade out when zoomed in
};
m_map->setPaintProperty("buildingsLayer", "fill-extrusion-color", QColor("grey"));
m_map->setPaintProperty("buildingsLayer", "fill-extrusion-opacity", fillExtrusionOpacity);
m_map->setPaintProperty("buildingsLayer", "fill-extrusion-height", fillExtrusionHeight);
m_map->setPaintProperty("buildingsLayer", "fill-extrusion-base", fillExtrusionBase);
m_map->setLayoutProperty("buildingsLayer", "visibility", "visible");
}
}
void MapWindow::updateState(const UIState &s) {
RETURN_IF_SUNNYPILOT
if (!uiState()->scene.started) {
return;
}
@@ -170,22 +134,6 @@ void MapWindow::updateState(const UIState &s) {
}
prev_time_valid = sm.valid("clocks");
if (sm.updated("modelV2")) {
// set path color on change, and show map on rising edge of navigate on openpilot
auto car_control = sm["carControl"].getCarControl();
bool nav_enabled = sm["modelV2"].getModelV2().getNavEnabledDEPRECATED() &&
(sm["controlsState"].getControlsState().getEnabled() || car_control.getLatActive() || car_control.getLongActive());
if (nav_enabled != uiState()->scene.navigate_on_openpilot_deprecated) {
if (loaded_once) {
m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(nav_enabled));
}
if (nav_enabled) {
emit requestVisible(true);
}
}
uiState()->scene.navigate_on_openpilot_deprecated = nav_enabled;
}
if (sm.updated("liveLocationKalman")) {
auto locationd_location = sm["liveLocationKalman"].getLiveLocationKalman();
auto locationd_pos = locationd_location.getPositionGeodetic();
@@ -425,7 +373,6 @@ void MapWindow::pinchTriggered(QPinchGesture *gesture) {
void MapWindow::offroadTransition(bool offroad) {
if (offroad) {
clearRoute();
uiState()->scene.navigate_on_openpilot_deprecated = false;
routing_problem = false;
} else {
auto dest = coordinate_from_param("NavDestination");
+10 -7
View File
@@ -20,7 +20,11 @@
#include "cereal/messaging/messaging.h"
#include "common/params.h"
#include "common/util.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/ui.h"
#else
#include "selfdrive/ui/ui.h"
#endif
#include "selfdrive/ui/qt/maps/map_eta.h"
#include "selfdrive/ui/qt/maps/map_instructions.h"
@@ -32,15 +36,17 @@ public:
~MapWindow();
private:
void initializeGL() final;
void paintGL() final;
void resizeGL(int w, int h) override;
protected:
void initializeGL() final;
QMapLibre::Settings m_settings;
QScopedPointer<QMapLibre::Map> m_map;
void initLayers();
protected:
void mousePressEvent(QMouseEvent *ev) final;
void mouseDoubleClickEvent(QMouseEvent *ev) final;
void mouseMoveEvent(QMouseEvent *ev) final;
@@ -50,9 +56,11 @@ private:
void pinchTriggered(QPinchGesture *gesture);
void setError(const QString &err_str);
protected:
bool loaded_once = false;
bool prev_time_valid = true;
protected:
// Panning
QPointF m_lastPos;
int interaction_counter = 0;
@@ -70,16 +78,11 @@ private:
MapInstructions* map_instructions;
MapETA* map_eta;
// Blue with normal nav, green when nav is input into the model
QColor getNavPathColor(bool nav_enabled) {
return nav_enabled ? QColor("#31ee73") : QColor("#31a1ee");
}
void clearRoute();
void updateDestinationMarker();
uint64_t route_rcv_frame = 0;
private slots:
public slots:
void updateState(const UIState &s);
public slots:
+6 -1
View File
@@ -3,8 +3,13 @@
#include <QDateTime>
#include <QPainter>
#include "selfdrive/ui/qt/maps/map_helpers.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h"
#else
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/maps/map_helpers.h"
#endif
const float MANEUVER_TRANSITION_THRESHOLD = 10;
+4
View File
@@ -1,4 +1,8 @@
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h"
#else
#include "selfdrive/ui/qt/maps/map_helpers.h"
#endif
#include <algorithm>
#include <string>
+1 -2
View File
@@ -12,9 +12,8 @@
#include "common/transformations/coordinates.hpp"
#include "common/transformations/orientation.hpp"
#include "cereal/messaging/messaging.h"
#include "common/params.h"
const QString MAPBOX_TOKEN = QString::fromStdString(Params().get("CustomMapboxTokenSk")) != "" ? QString::fromStdString(Params().get("CustomMapboxTokenSk")) : util::getenv("MAPBOX_TOKEN").c_str();
const QString MAPBOX_TOKEN = util::getenv("MAPBOX_TOKEN").c_str();
const QString MAPS_HOST = util::getenv("MAPS_HOST", MAPBOX_TOKEN.isEmpty() ? "https://maps.comma.ai" : "https://api.mapbox.com").c_str();
const QString MAPS_CACHE_PATH = "/data/mbgl-cache-navd.db";
+6 -1
View File
@@ -3,8 +3,13 @@
#include <QDir>
#include <QVBoxLayout>
#include "selfdrive/ui/qt/maps/map_helpers.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h"
#else
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/maps/map_helpers.h"
#endif
const QString ICON_SUFFIX = ".png";
+7 -1
View File
@@ -3,10 +3,16 @@
#include <QHBoxLayout>
#include <QWidget>
#include "selfdrive/ui/qt/maps/map.h"
#include "selfdrive/ui/qt/maps/map_settings.h"
#include "selfdrive/ui/qt/util.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/maps/map.h"
#define MapWindow MapWindowSP
#else
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/maps/map.h"
#endif
MapPanel::MapPanel(const QMapLibre::Settings &mapboxSettings, QWidget *parent) : QFrame(parent) {
content_stack = new QStackedLayout(this);
+4
View File
@@ -13,7 +13,11 @@
#include "common/params.h"
#include "selfdrive/ui/qt/util.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#else
#include "selfdrive/ui/qt/widgets/controls.h"
#endif
const QString NAV_TYPE_FAVORITE = "favorite";
const QString NAV_TYPE_RECENT = "recent";
+13 -52
View File
@@ -6,11 +6,16 @@
#include <QScrollBar>
#include <QStyle>
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/ui.h"
#else
#include "selfdrive/ui/ui.h"
#endif
#include "selfdrive/ui/qt/qt_window.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/qt/widgets/controls.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
#include "selfdrive/ui/qt/home.h"
static const int ICON_WIDTH = 49;
@@ -26,29 +31,17 @@ Networking::Networking(QWidget* parent, bool show_advanced) : QFrame(parent) {
wifiScreen = new QWidget(this);
QVBoxLayout* vlayout = new QVBoxLayout(wifiScreen);
vlayout->setContentsMargins(20, 20, 20, 20);
QHBoxLayout* hlayout = new QHBoxLayout();
QPushButton* scanButton = new QPushButton(tr("Scan"));
scanButton->setObjectName("scan_btn");
scanButton->setFixedSize(400, 100);
connect(wifi, &WifiManager::refreshSignal, this, [=]() { scanButton->setText(tr("Scan")); scanButton->setEnabled(true); });
connect(scanButton, &QPushButton::clicked, [=]() { scanButton->setText(tr("Scanning...")); scanButton->setEnabled(false); wifi->requestScan(); });
hlayout->addWidget(scanButton);
hlayout->addStretch(1); // Pushes the button all the way to the left
if (show_advanced) {
hlayout->setSpacing(10);
QPushButton* advancedSettings = new QPushButton(tr("Advanced"));
advancedSettings->setObjectName("advanced_btn");
advancedSettings->setStyleSheet("margin-right: 30px;");
advancedSettings->setFixedSize(400, 100);
connect(advancedSettings, &QPushButton::clicked, [=]() { main_layout->setCurrentWidget(an); });
hlayout->addWidget(advancedSettings);
vlayout->addSpacing(10);
vlayout->addWidget(advancedSettings, 0, Qt::AlignRight);
vlayout->addSpacing(10);
}
vlayout->addLayout(hlayout);
vlayout->addSpacing(10);
wifiWidget = new WifiUI(this, wifi);
wifiWidget->setObjectName("wifiWidget");
connect(wifiWidget, &WifiUI::connectToNetwork, this, &Networking::connectToNetwork);
@@ -69,7 +62,7 @@ Networking::Networking(QWidget* parent, bool show_advanced) : QFrame(parent) {
setPalette(pal);
setStyleSheet(R"(
#wifiWidget > QPushButton, #back_btn, #advanced_btn, #scan_btn{
#wifiWidget > QPushButton, #back_btn, #advanced_btn {
font-size: 50px;
margin: 0px;
padding: 15px;
@@ -78,7 +71,7 @@ Networking::Networking(QWidget* parent, bool show_advanced) : QFrame(parent) {
color: #dddddd;
background-color: #393939;
}
#back_btn:pressed, #advanced_btn:pressed, #scan_btn:pressed {
#back_btn:pressed, #advanced_btn:pressed {
background-color: #4a4a4a;
}
)");
@@ -139,23 +132,10 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
ListWidget *list = new ListWidget(this);
// Enable tethering layout
const bool set_hotspot_on_boot = params.getBool("HotspotOnBoot") && params.getBool("HotspotOnBootConfirmed");
tetheringToggle = new ToggleControl(tr("Enable Tethering"), "", "", wifi->isTetheringEnabled() || set_hotspot_on_boot);
tetheringToggle = new ToggleControl(tr("Enable Tethering"), "", "", wifi->isTetheringEnabled());
list->addItem(tetheringToggle);
QObject::connect(tetheringToggle, &ToggleControl::toggleFlipped, this, &AdvancedNetworking::toggleTethering);
hotspotOnBootToggle = new ToggleControl(
tr("Retain hotspot/tethering state"),
tr("Enabling this toggle will retain the hotspot/tethering toggle state across reboots."),
"",
params.getBool("HotspotOnBoot")
);
hotspotOnBootToggle->setEnabled(wifi->isTetheringEnabled() || set_hotspot_on_boot);
QObject::connect(hotspotOnBootToggle, &ToggleControl::toggleFlipped, [=](bool state) {
params.putBool("HotspotOnBoot", state);
});
list->addItem(hotspotOnBootToggle);
// Change tethering password
ButtonControl *editPasswordButton = new ButtonControl(tr("Tethering Password"), tr("EDIT"));
connect(editPasswordButton, &ButtonControl::clicked, [=]() {
@@ -226,19 +206,6 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
});
list->addItem(hiddenNetworkButton);
// Ngrok
QProcess process;
process.start("sudo service ngrok status | grep running");
process.waitForFinished();
QString output = QString(process.readAllStandardOutput());
bool ngrokRunning = !output.isEmpty();
ToggleControl *ngrokToggle = new ToggleControl(tr("Ngrok Service"), "", "", ngrokRunning);
connect(ngrokToggle, &ToggleControl::toggleFlipped, [=](bool state) {
if (state) std::system("sudo ngrok service start");
else std::system("sudo ngrok service stop");
});
list->addItem(ngrokToggle);
// Set initial config
wifi->updateGsmSettings(roamingEnabled, QString::fromStdString(params.get("GsmApn")), metered);
@@ -260,14 +227,8 @@ void AdvancedNetworking::refresh() {
}
void AdvancedNetworking::toggleTethering(bool enabled) {
params.putBool("HotspotOnBootConfirmed", enabled);
wifi->setTetheringEnabled(enabled);
tetheringToggle->setEnabled(false);
hotspotOnBootToggle->setEnabled(enabled);
if (!enabled) {
params.remove("HotspotOnBoot");
}
}
// WifiUI functions
@@ -419,4 +380,4 @@ void WifiItem::setItem(const Network &n, const QPixmap &status_icon, bool show_f
iconLabel->setPixmap(status_icon);
strengthLabel->setPixmap(strength_icon);
}
}
+8 -3
View File
@@ -6,6 +6,13 @@
#include "selfdrive/ui/qt/widgets/input.h"
#include "selfdrive/ui/qt/widgets/ssh_keys.h"
#include "selfdrive/ui/qt/widgets/toggle.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#define LabelControl LabelControlSP
#define ElidedLabel ElidedLabelSP
#else
#include "selfdrive/ui/qt/widgets/controls.h"
#endif
class WifiItem : public QWidget {
Q_OBJECT
@@ -67,8 +74,6 @@ private:
WifiManager* wifi = nullptr;
Params params;
ToggleControl* hotspotOnBootToggle;
signals:
void backPress();
void requestWifiScreen();
@@ -100,4 +105,4 @@ public slots:
private slots:
void connectToNetwork(const Network n);
void wrongPassword(const QString &ssid);
};
};
@@ -1,33 +0,0 @@
#ifndef USER_MODEL_H
#define USER_MODEL_H
#include <QJsonObject>
class UserModel {
public:
QString device_id;
QString user_id;
qint64 created_at;
qint64 updated_at;
QString token_hash;
explicit UserModel(const QJsonObject &json) {
device_id = json["device_id"].toString();
user_id = json["user_id"].toString();
created_at = json["created_at"].toInt();
updated_at = json["updated_at"].toInt();
token_hash = json["token_hash"].toString();
}
[[nodiscard]] QJsonObject toJson() const {
QJsonObject json;
json["device_id"] = device_id;
json["user_id"] = user_id;
json["created_at"] = created_at;
json["updated_at"] = updated_at;
json["token_hash"] = token_hash;
return json;
}
};
#endif
@@ -1,48 +0,0 @@
#include "base_device_service.h"
#include "common/swaglog.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h"
BaseDeviceService::BaseDeviceService(QObject* parent) : QObject(parent), initial_request(nullptr), repeater(nullptr) {
param_watcher = new ParamWatcher(this);
connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString &param_name, const QString &param_value) {
paramsRefresh();
});
param_watcher->addParam("SunnylinkEnabled");
}
void BaseDeviceService::paramsRefresh() {
}
void BaseDeviceService::loadDeviceData(const QString &url, bool poll) {
if (!is_sunnylink_enabled()) {
LOGW("Sunnylink is not enabled, refusing to load data.");
return;
}
auto sl_dongle_id = getSunnylinkDongleId();
if (!sl_dongle_id.has_value())
return;
QString fullUrl = SUNNYLINK_BASE_URL + "/device/" + *sl_dongle_id + url;
if (poll && !isCurrentyPolling()) {
LOGD("Polling %s", qPrintable(fullUrl));
LOGD("Cache key: SunnylinkCache_%s", qPrintable(QString(getCacheKey())));
repeater = new RequestRepeater(this, fullUrl, "SunnylinkCache_" + getCacheKey(), 60, false, true);
connect(repeater, &RequestRepeater::requestDone, this, &BaseDeviceService::handleResponse);
} else if(isCurrentyPolling()){
repeater->ForceUpdate();
} else {
LOGD("Sending one-time %s", qPrintable(fullUrl));
initial_request = new HttpRequest(this, true, 10000, true);
connect(initial_request, &HttpRequest::requestDone, this, &BaseDeviceService::handleResponse);
}
}
void BaseDeviceService::stopPolling() {
if (repeater != nullptr) {
repeater->deleteLater();
repeater = nullptr;
}
}
@@ -1,29 +0,0 @@
#ifndef BASESERVICE_H
#define BASESERVICE_H
#include "selfdrive/ui/qt/api.h"
#include "selfdrive/ui/qt/request_repeater.h"
#include "selfdrive/ui/qt/util.h"
class BaseDeviceService : public QObject {
Q_OBJECT
protected:
void paramsRefresh();
void loadDeviceData(const QString &url, bool poll = false);
virtual void handleResponse(const QString &response, bool success) = 0;
static bool is_sunnylink_enabled() { return Params().getBool("SunnylinkEnabled");};
ParamWatcher* param_watcher;
HttpRequest* initial_request = nullptr;
RequestRepeater* repeater = nullptr;
public:
explicit BaseDeviceService(QObject* parent = nullptr);
virtual QString getCacheKey() const = 0;
bool isCurrentyPolling() {return repeater != nullptr;}
void stopPolling();
};
#endif
@@ -1,29 +0,0 @@
#include "selfdrive/ui/qt/network/sunnylink/services/role_service.h"
#include <QJsonArray>
#include <QJsonDocument>
RoleService::RoleService(QObject* parent) : BaseDeviceService(parent) {}
void RoleService::load() {
loadDeviceData(url);
}
void RoleService::startPolling() {
loadDeviceData(url, true);
}
void RoleService::handleResponse(const QString &response, bool success) {
if (!success) return;
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
QJsonArray jsonArray = doc.array();
std::vector<RoleModel> roles;
for (const auto &value : jsonArray) {
roles.emplace_back(value.toObject());
}
emit rolesReady(roles);
uiState()->setSunnylinkRoles(roles);
}
@@ -1,26 +0,0 @@
#ifndef ROLESERVICE_H
#define ROLESERVICE_H
#include "selfdrive/ui/qt/network/sunnylink/services/base_device_service.h"
#include "selfdrive/ui/qt/network/sunnylink/models/role_model.h"
class RoleService : public BaseDeviceService {
Q_OBJECT
public:
explicit RoleService(QObject* parent = nullptr);
void load();
void startPolling();
[[nodiscard]] QString getCacheKey() const final { return "Roles"; };
signals:
void rolesReady(const std::vector<RoleModel> &roles);
protected:
void handleResponse(const QString&response, bool success) override;
private:
QString url = "/roles";
};
#endif
@@ -1,31 +0,0 @@
#include "selfdrive/ui/qt/network/sunnylink/services/user_service.h"
#include <QJsonArray>
#include <QJsonDocument>
UserService::UserService(QObject* parent) : BaseDeviceService(parent) {
url = "/users";
}
void UserService::load() {
loadDeviceData(url);
}
void UserService::startPolling() {
loadDeviceData(url, true);
}
void UserService::handleResponse(const QString &response, bool success) {
if (!success) return;
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
QJsonArray jsonArray = doc.array();
std::vector<UserModel> users;
for (const auto &value : jsonArray) {
users.emplace_back(value.toObject());
}
emit usersReady(users);
uiState()->setSunnylinkDeviceUsers(users);
}
@@ -1,26 +0,0 @@
#ifndef USERSERVICE_H
#define USERSERVICE_H
#include "selfdrive/ui/qt/network/sunnylink/services/base_device_service.h"
#include "selfdrive/ui/qt/network/sunnylink/models/user_model.h"
class UserService : public BaseDeviceService {
Q_OBJECT
public:
explicit UserService(QObject* parent = nullptr);
void load();
void startPolling();
[[nodiscard]] QString getCacheKey() const final { return "Users"; };
signals:
void usersReady(const std::vector<UserModel>&users);
protected:
void handleResponse(const QString&response, bool success) override;
private:
QString url = "/users";
};
#endif
@@ -1,7 +0,0 @@
#include "selfdrive/ui/qt/network/sunnylink/sunnylink_client.h"
#include "selfdrive/ui/qt/network/sunnylink/services/user_service.h"
SunnylinkClient::SunnylinkClient(QObject* parent) : QObject(parent) {
role_service = new RoleService(parent);
user_service = new UserService(parent);
}
@@ -1,18 +0,0 @@
#ifndef SUNNYLINK_CLIENT_H
#define SUNNYLINK_CLIENT_H
#include <QObject>
#include "selfdrive/ui/qt/network/sunnylink/services/role_service.h"
#include "selfdrive/ui/qt/network/sunnylink/services/user_service.h"
class SunnylinkClient : public QObject {
Q_OBJECT
public:
explicit SunnylinkClient(QObject* parent);
RoleService* role_service;
UserService* user_service;
};
#endif
+4
View File
@@ -2,7 +2,11 @@
#include <utility>
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/ui.h"
#else
#include "selfdrive/ui/ui.h"
#endif
#include "selfdrive/ui/qt/widgets/prime.h"
#include "common/params.h"
+6 -1
View File
@@ -7,13 +7,18 @@
#include <QStyle>
#include "selfdrive/ui/ui.h"
#ifdef SUNNYPILOT
#define TOGGLES_PANEL_INDEX 3
#else
#define TOGGLES_PANEL_INDEX 2
#endif
ExperimentalModeButton::ExperimentalModeButton(QWidget *parent) : QPushButton(parent) {
chill_pixmap = QPixmap("../assets/img_couch.svg").scaledToWidth(img_width, Qt::SmoothTransformation);
experimental_pixmap = QPixmap("../assets/img_experimental_grey.svg").scaledToWidth(img_width, Qt::SmoothTransformation);
// go to toggles and expand experimental mode description
connect(this, &QPushButton::clicked, [=]() { emit openSettings(3, "ExperimentalMode"); });
connect(this, &QPushButton::clicked, [=]() { emit openSettings(TOGGLES_PANEL_INDEX, "ExperimentalMode"); });
setFixedHeight(125);
QHBoxLayout *main_layout = new QHBoxLayout;
+3 -17
View File
@@ -106,8 +106,7 @@ void TermsPage::showEvent(QShowEvent *event) {
text->setAttribute(Qt::WA_AlwaysStackOnTop);
text->setClearColor(QColor("#1B1B1B"));
std::string tc_text = sunnypilot_tc ? "../assets/offroad/sp_tc.html" : "../assets/offroad/tc.html";
QString text_view = util::read_file(tc_text).c_str();
QString text_view = util::read_file("../assets/offroad/tc.html").c_str();
text->rootContext()->setContextProperty("text_view", text_view);
text->setSource(QUrl::fromLocalFile("qt/offroad/text_view.qml"));
@@ -186,8 +185,6 @@ void OnboardingWindow::updateActiveScreen() {
setCurrentIndex(0);
} else if (!training_done) {
setCurrentIndex(1);
} else if (!accepted_terms_sp) {
setCurrentIndex(3);
} else {
emit onboardingDone();
}
@@ -195,13 +192,11 @@ void OnboardingWindow::updateActiveScreen() {
OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) {
std::string current_terms_version = params.get("TermsVersion");
std::string current_terms_version_sp = params.get("TermsVersionSunnypilot");
std::string current_training_version = params.get("TrainingVersion");
accepted_terms = params.get("HasAcceptedTerms") == current_terms_version;
accepted_terms_sp = params.get("HasAcceptedTermsSP") == current_terms_version_sp;
training_done = params.get("CompletedTrainingVersion") == current_training_version;
TermsPage* terms = new TermsPage(false, this);
TermsPage* terms = new TermsPage(this);
addWidget(terms);
connect(terms, &TermsPage::acceptedTerms, [=]() {
params.put("HasAcceptedTerms", current_terms_version);
@@ -222,15 +217,6 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) {
addWidget(declinePage);
connect(declinePage, &DeclinePage::getBack, [=]() { updateActiveScreen(); });
TermsPage* terms_sp = new TermsPage(true, this);
addWidget(terms_sp); // index = 3
connect(terms_sp, &TermsPage::acceptedTerms, [=]() {
params.put("HasAcceptedTermsSP", current_terms_version_sp);
accepted_terms_sp = true;
updateActiveScreen();
});
connect(terms_sp, &TermsPage::declinedTerms, [=]() { setCurrentIndex(2); });
setStyleSheet(R"(
* {
color: white;
@@ -245,4 +231,4 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) {
}
)");
updateActiveScreen();
}
}
+9 -8
View File
@@ -63,16 +63,17 @@ class TermsPage : public QFrame {
Q_OBJECT
public:
explicit TermsPage(bool sunnypilot = false, QWidget *parent = 0) : QFrame(parent), sunnypilot_tc(sunnypilot) {}
explicit TermsPage(QWidget *parent = 0) : QFrame(parent) {}
public slots:
void enableAccept();
protected:
QPushButton *accept_btn;
private:
void showEvent(QShowEvent *event) override;
QPushButton *accept_btn;
bool sunnypilot_tc = false;
signals:
void acceptedTerms();
@@ -98,14 +99,14 @@ class OnboardingWindow : public QStackedWidget {
public:
explicit OnboardingWindow(QWidget *parent = 0);
inline void showTrainingGuide() { setCurrentIndex(1); }
inline bool completed() const { return accepted_terms && accepted_terms_sp && training_done; }
virtual inline bool completed() const { return accepted_terms && training_done; }
private:
void updateActiveScreen();
protected:
virtual void updateActiveScreen();
Params params;
bool accepted_terms = false, accepted_terms_sp = false, training_done = false;
bool accepted_terms = false, training_done = false;
signals:
void onboardingDone();
};
};
+43 -319
View File
@@ -5,28 +5,29 @@
#include <vector>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QDateTime>
#include "common/watchdog.h"
#include "common/util.h"
#include "selfdrive/ui/qt/network/networking.h"
#include "selfdrive/ui/qt/offroad/settings.h"
#include "selfdrive/ui/qt/offroad/sunnypilot_main.h"
#include "selfdrive/ui/qt/qt_window.h"
#include "selfdrive/ui/qt/widgets/prime.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
#include "selfdrive/ui/qt/widgets/ssh_keys.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/sunnypilot_main.h"
#endif
TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
RETURN_IF_SUNNYPILOT
// param, title, desc, icon
std::vector<std::tuple<QString, QString, QString, QString>> toggle_defs{
{
"OpenpilotEnabledToggle",
tr("Enable sunnypilot"),
tr("Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off."),
"../assets/offroad/icon_blank.png",
"../assets/offroad/icon_openpilot.png",
},
{
"ExperimentalLongitudinalEnabled",
@@ -35,105 +36,54 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
.arg(tr("WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB)."))
.arg(tr("On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. "
"Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.")),
"../assets/offroad/icon_blank.png",
},
{
"CustomStockLong",
tr("Custom Stock Longitudinal Control"),
tr("When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses.\nThis feature must be used along with SLC, and/or V-TSC, and/or M-TSC."),
"../assets/offroad/icon_blank.png",
"../assets/offroad/icon_speed_limit.png",
},
{
"ExperimentalMode",
tr("Experimental Mode"),
"",
"../assets/offroad/icon_blank.png",
},
{
"DynamicExperimentalControl",
tr("Enable Dynamic Experimental Control"),
tr("Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal."),
"../assets/offroad/icon_blank.png",
},
{
"DynamicPersonality",
tr("Enable Dynamic Personality"),
tr("Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your \"Driving Personality\" setting. "
"Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car."),
"../assets/offroad/icon_blank.png",
"../assets/img_experimental_white.svg",
},
{
"DisengageOnAccelerator",
tr("Disengage on Accelerator Pedal"),
tr("When enabled, pressing the accelerator pedal will disengage openpilot."),
"../assets/offroad/icon_blank.png",
"../assets/offroad/icon_disengage_on_accelerator.svg",
},
{
"IsLdwEnabled",
tr("Enable Lane Departure Warnings"),
tr("Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h)."),
"../assets/offroad/icon_blank.png",
"../assets/offroad/icon_warning.png",
},
{
"AlwaysOnDM",
tr("Always-On Driver Monitoring"),
tr("Enable driver monitoring even when openpilot is not engaged."),
"../assets/offroad/icon_blank.png",
"../assets/offroad/icon_monitoring.png",
},
{
"RecordFront",
tr("Record and Upload Driver Camera"),
tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."),
"../assets/offroad/icon_blank.png",
},
{
"DisableOnroadUploads",
tr("Disable Onroad Uploads"),
tr("Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC)."),
"../assets/offroad/icon_blank.png",
"../assets/offroad/icon_monitoring.png",
},
{
"IsMetric",
tr("Use Metric System"),
tr("Display speed in km/h instead of mph."),
"../assets/offroad/icon_blank.png",
"../assets/offroad/icon_metric.png",
},
#ifdef ENABLE_MAPS
{
"NavSettingTime24h",
tr("Show ETA in 24h Format"),
tr("Use 24h format instead of am/pm"),
"../assets/offroad/icon_blank.png",
},
{
"NavSettingLeftSide",
tr("Show Map on Left Side of UI"),
tr("Show map on left side when in split screen view."),
"../assets/offroad/icon_blank.png",
},
#endif
};
std::vector<QString> longi_button_texts{tr("Aggressive"), tr("Moderate"), tr("Standard"), tr("Relaxed")};
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 moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. "
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. On supported cars, you can cycle through these personalities with "
"your steering wheel distance button."),
"../assets/offroad/icon_blank.png",
longi_button_texts,
380);
long_personality_setting->showDescription();
// accel controller
std::vector<QString> accel_personality_texts{tr("Sport"), tr("Normal"), tr("Eco"), tr("Stock")};
accel_personality_setting = new ButtonParamControl("AccelPersonality", tr("Acceleration Personality"),
tr("Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. "
"In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these "
"acceleration personality within Onroad Settings on the driving screen."),
"../assets/offroad/icon_blank.png",
accel_personality_texts);
accel_personality_setting->showDescription();
"../assets/offroad/icon_speed_limit.png",
longi_button_texts);
// set up uiState update for personality setting
QObject::connect(uiState(), &UIState::uiUpdate, this, &TogglesPanel::updateState);
@@ -150,28 +100,17 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
// insert longitudinal personality after NDOG toggle
if (param == "DisengageOnAccelerator") {
addItem(long_personality_setting);
addItem(accel_personality_setting);
}
}
// Toggles with confirmation dialogs
//toggles["ExperimentalMode"]->setActiveIcon("../assets/img_experimental.svg");
toggles["ExperimentalMode"]->setActiveIcon("../assets/img_experimental.svg");
toggles["ExperimentalMode"]->setConfirmation(true, true);
toggles["ExperimentalLongitudinalEnabled"]->setConfirmation(true, false);
toggles["CustomStockLong"]->setConfirmation(true, false);
connect(toggles["ExperimentalLongitudinalEnabled"], &ToggleControl::toggleFlipped, [=]() {
updateToggles();
});
connect(toggles["CustomStockLong"], &ToggleControl::toggleFlipped, [=]() {
updateToggles();
});
param_watcher = new ParamWatcher(this);
QObject::connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString &param_name, const QString &param_value) {
updateToggles();
});
}
void TogglesPanel::updateState(const UIState &s) {
@@ -184,14 +123,6 @@ void TogglesPanel::updateState(const UIState &s) {
}
uiState()->scene.personality = personality;
}
if (sm.updated("controlsStateSP")) {
auto accel_personality = sm["controlsStateSP"].getControlsStateSP().getAccelPersonality();
if (accel_personality != s.scene.accel_personality && s.scene.started && isVisible()) {
accel_personality_setting->setCheckedButton(static_cast<int>(accel_personality));
}
uiState()->scene.accel_personality = accel_personality;
}
}
void TogglesPanel::expandToggleDescription(const QString &param) {
@@ -203,15 +134,8 @@ void TogglesPanel::showEvent(QShowEvent *event) {
}
void TogglesPanel::updateToggles() {
param_watcher->addParam("LongitudinalPersonality");
if (!isVisible()) return;
auto experimental_mode_toggle = toggles["ExperimentalMode"];
auto op_long_toggle = toggles["ExperimentalLongitudinalEnabled"];
auto custom_stock_long_toggle = toggles["CustomStockLong"];
auto dec_toggle = toggles["DynamicExperimentalControl"];
auto dynamic_personality_toggle = toggles["DynamicPersonality"];
const QString e2e_description = QString("%1<br>"
"<h4>%2</h4><br>"
"%3<br>"
@@ -236,35 +160,15 @@ void TogglesPanel::updateToggles() {
params.remove("ExperimentalLongitudinalEnabled");
}
op_long_toggle->setVisible(CP.getExperimentalLongitudinalAvailable() && !is_release);
if (!CP.getCustomStockLongAvailable()) {
params.remove("CustomStockLong");
}
custom_stock_long_toggle->setVisible(CP.getCustomStockLongAvailable());
if (hasLongitudinalControl(CP)) {
// normal description and toggle
experimental_mode_toggle->setEnabled(true);
experimental_mode_toggle->setDescription(e2e_description);
long_personality_setting->setEnabled(true);
accel_personality_setting->setEnabled(true);
op_long_toggle->setEnabled(true);
custom_stock_long_toggle->setEnabled(false);
params.remove("CustomStockLong");
dec_toggle->setEnabled(true);
dynamic_personality_toggle->setEnabled(true);
} else if (custom_stock_long_toggle->isToggled()) {
op_long_toggle->setEnabled(false);
experimental_mode_toggle->setEnabled(false);
long_personality_setting->setEnabled(false);
accel_personality_setting->setEnabled(false);
params.remove("ExperimentalLongitudinalEnabled");
params.remove("ExperimentalMode");
} else {
// no long for now
experimental_mode_toggle->setEnabled(false);
long_personality_setting->setEnabled(false);
accel_personality_setting->setEnabled(false);
params.remove("ExperimentalMode");
const QString unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.");
@@ -279,26 +183,12 @@ void TogglesPanel::updateToggles() {
}
}
experimental_mode_toggle->setDescription("<b>" + long_desc + "</b><br><br>" + e2e_description);
op_long_toggle->setEnabled(CP.getExperimentalLongitudinalAvailable() && !is_release);
custom_stock_long_toggle->setEnabled(CP.getCustomStockLongAvailable());
dec_toggle->setEnabled(false);
dynamic_personality_toggle->setEnabled(false);
params.remove("DynamicExperimentalControl");
params.remove("DynamicPersonality");
}
experimental_mode_toggle->refresh();
op_long_toggle->refresh();
custom_stock_long_toggle->refresh();
dec_toggle->refresh();
dynamic_personality_toggle->refresh();
} else {
experimental_mode_toggle->setDescription(e2e_description);
op_long_toggle->setVisible(false);
custom_stock_long_toggle->setVisible(false);
dec_toggle->setVisible(false);
dynamic_personality_toggle->setVisible(false);
}
}
@@ -307,44 +197,6 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
addItem(new LabelControl(tr("Dongle ID"), getDongleId().value_or(tr("N/A"))));
addItem(new LabelControl(tr("Serial"), params.get("HardwareSerial").c_str()));
fleetManagerPin = new ButtonControl(
pin_title + pin, tr("TOGGLE"),
tr("Enable or disable PIN requirement for Fleet Manager access."));
connect(fleetManagerPin, &ButtonControl::clicked, [=]() {
if (params.getBool("FleetManagerPin")) {
if (ConfirmationDialog::confirm(tr("Are you sure you want to turn off PIN requirement?"), tr("Turn Off"), this)) {
params.remove("FleetManagerPin");
refreshPin();
}
} else {
params.putBool("FleetManagerPin", true);
refreshPin();
}
});
addItem(fleetManagerPin);
fs_watch = new QFileSystemWatcher(this);
connect(fs_watch, &QFileSystemWatcher::fileChanged, this, &DevicePanel::onPinFileChanged);
QString pin_path = "/data/otp/otp.conf";
QString pin_require = "/data/params/d/FleetManagerPin";
fs_watch->addPath(pin_path);
fs_watch->addPath(pin_require);
refreshPin();
// Error Troubleshoot
auto errorBtn = new ButtonControl(
tr("Error Troubleshoot"), tr("VIEW"),
tr("Display error from the tmux session when an error has occurred from a system process."));
QFileInfo file("/data/community/crashes/error.txt");
QDateTime modifiedTime = file.lastModified();
QString modified_time = modifiedTime.toString("yyyy-MM-dd hh:mm:ss ");
connect(errorBtn, &ButtonControl::clicked, [=]() {
const std::string txt = util::read_file("/data/community/crashes/error.txt");
ConfirmationDialog::rich(modified_time + QString::fromStdString(txt), this);
});
addItem(errorBtn);
pair_device = new ButtonControl(tr("Pair Device"), tr("PAIR"),
tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."));
connect(pair_device, &ButtonControl::clicked, [=]() {
@@ -370,33 +222,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
});
addItem(resetCalibBtn);
auto resetMapboxTokenBtn = new ButtonControl(tr("Reset Access Tokens for Map Services"), tr("RESET"), tr("Reset self-service access tokens for Mapbox, Amap, and Google Maps."));
connect(resetMapboxTokenBtn, &ButtonControl::clicked, [=]() {
if (ConfirmationDialog::confirm(tr("Are you sure you want to reset access tokens for all map services?"), tr("Reset"), this)) {
std::vector<std::string> tokens = {
"CustomMapboxTokenPk",
"CustomMapboxTokenSk",
"AmapKey1",
"AmapKey2",
"GmapKey"
};
for (const auto& token : tokens) {
params.remove(token);
}
}
});
addItem(resetMapboxTokenBtn);
auto resetParamsBtn = new ButtonControl(tr("Reset sunnypilot Settings"), tr("RESET"), "");
connect(resetParamsBtn, &ButtonControl::clicked, [=]() {
if (ConfirmationDialog::confirm(tr("Are you sure you want to reset all sunnypilot settings?"), tr("Reset"), this)) {
std::system("sudo rm -rf /data/params/d/*");
Hardware::reboot();
}
});
addItem(resetParamsBtn);
auto retrainingBtn = new ButtonControl(tr("Review Training Guide"), tr("REVIEW"), tr("Review the rules, features, and limitations of sunnypilot"));
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)) {
emit reviewTrainingGuide();
@@ -429,16 +255,19 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
QObject::connect(uiState(), &UIState::primeTypeChanged, [this] (PrimeType type) {
pair_device->setVisible(type == PrimeType::UNPAIRED);
});
#ifndef SUNNYPILOT
QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) {
for (auto btn : findChildren<ButtonControl *>()) {
if ((btn != pair_device) && (btn != errorBtn)) {
if (btn != pair_device) {
btn->setEnabled(offroad);
}
}
});
#endif
// power buttons
QHBoxLayout *power_layout = new QHBoxLayout();
power_layout = new QHBoxLayout();
power_layout->setSpacing(30);
QPushButton *reboot_btn = new QPushButton(tr("Reboot"));
@@ -455,46 +284,14 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
connect(uiState(), &UIState::offroadTransition, poweroff_btn, &QPushButton::setVisible);
}
offroad_btn = new QPushButton(tr("Toggle Onroad/Offroad"));
offroad_btn->setObjectName("offroad_btn");
QObject::connect(offroad_btn, &QPushButton::clicked, this, &DevicePanel::forceoffroad);
QVBoxLayout *buttons_layout = new QVBoxLayout();
buttons_layout->setSpacing(24);
buttons_layout->addLayout(power_layout);
buttons_layout->addWidget(offroad_btn);
setStyleSheet(R"(
#reboot_btn { height: 120px; border-radius: 15px; background-color: #393939; }
#reboot_btn:pressed { background-color: #4a4a4a; }
#poweroff_btn { height: 120px; border-radius: 15px; background-color: #E22C2C; }
#poweroff_btn:pressed { background-color: #FF2424; }
)");
addItem(buttons_layout);
updateLabels();
}
void DevicePanel::onPinFileChanged(const QString &file_path) {
if (file_path == "/data/params/d/FleetManagerPin") {
refreshPin();
} else if (file_path == "/data/otp/otp.conf") {
refreshPin();
}
}
void DevicePanel::refreshPin() {
QFile f("/data/otp/otp.conf");
QFile require("/data/params/d/FleetManagerPin");
if (!require.exists()) {
setSpacing(50);
fleetManagerPin->setTitle(pin_title + tr("OFF"));
} else if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
pin = f.readAll();
f.close();
setSpacing(50);
fleetManagerPin->setTitle(pin_title + pin);
}
RETURN_IF_SUNNYPILOT
addItem(power_layout);
}
void DevicePanel::updateCalibDescription() {
@@ -547,51 +344,9 @@ void DevicePanel::poweroff() {
}
}
void DevicePanel::forceoffroad() {
if (!uiState()->engaged()) {
if (params.getBool("ForceOffroad")) {
if (ConfirmationDialog::confirm(tr("Are you sure you want to unforce offroad?"), tr("Unforce"), this)) {
// Check engaged again in case it changed while the dialog was open
if (!uiState()->engaged()) {
params.remove("ForceOffroad");
}
}
} else {
if (ConfirmationDialog::confirm(tr("Are you sure you want to force offroad?"), tr("Force"), this)) {
// Check engaged again in case it changed while the dialog was open
if (!uiState()->engaged()) {
params.putBool("ForceOffroad", true);
}
}
}
} else {
ConfirmationDialog::alert(tr("Disengage to Force Offroad"), this);
}
updateLabels();
}
void DevicePanel::showEvent(QShowEvent *event) {
pair_device->setVisible(uiState()->primeType() == PrimeType::UNPAIRED);
ListWidget::showEvent(event);
updateLabels();
}
void DevicePanel::updateLabels() {
if (!isVisible()) {
return;
}
bool force_offroad_param = params.getBool("ForceOffroad");
QString offroad_btn_style = force_offroad_param ? "#393939" : "#E22C2C";
QString offroad_btn_pressed_style = force_offroad_param ? "#4a4a4a" : "#FF2424";
QString btn_common_style = QString("QPushButton { height: 120px; border-radius: 15px; background-color: %1; }"
"QPushButton:pressed { background-color: %2; }")
.arg(offroad_btn_style,
offroad_btn_pressed_style);
offroad_btn->setText(force_offroad_param ? tr("Unforce Offroad") : tr("Force Offroad"));
offroad_btn->setStyleSheet(btn_common_style + offroad_btn_style + offroad_btn_pressed_style);
}
void SettingsWindow::showEvent(QShowEvent *event) {
@@ -607,23 +362,20 @@ void SettingsWindow::setCurrentPanel(int index, const QString &param) {
}
SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
RETURN_IF_SUNNYPILOT
// setup two main layouts
sidebar_widget = new QWidget;
QVBoxLayout *sidebar_layout = new QVBoxLayout(sidebar_widget);
panel_widget = new QStackedWidget();
// setup layout for close button
QVBoxLayout *close_btn_layout = new QVBoxLayout;
close_btn_layout->setContentsMargins(0, 0, 0, 20);
// close button
QPushButton *close_btn = new QPushButton(tr("×"));
close_btn->setStyleSheet(R"(
QPushButton {
font-size: 140px;
padding-bottom: 20px;
border-radius: 76px;
border-radius: 100px;
background-color: #292929;
font-weight: 400;
}
@@ -631,16 +383,11 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
background-color: #3B3B3B;
}
)");
close_btn->setFixedSize(152, 152);
close_btn_layout->addWidget(close_btn, 0, Qt::AlignLeft);
close_btn->setFixedSize(200, 200);
sidebar_layout->addSpacing(45);
sidebar_layout->addWidget(close_btn, 0, Qt::AlignCenter);
QObject::connect(close_btn, &QPushButton::clicked, this, &SettingsWindow::closeSettings);
// setup buttons widget
QWidget *buttons_widget = new QWidget;
QVBoxLayout *buttons_layout = new QVBoxLayout(buttons_widget);
buttons_layout->setMargin(0);
buttons_layout->addSpacing(10);
// setup panels
DevicePanel *device = new DevicePanel(this);
QObject::connect(device, &DevicePanel::reviewTrainingGuide, this, &SettingsWindow::reviewTrainingGuide);
@@ -649,43 +396,27 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
TogglesPanel *toggles = new TogglesPanel(this);
QObject::connect(this, &SettingsWindow::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription);
QList<PanelInfo> panels = {
PanelInfo(" " + tr("Device"), device, "../assets/navigation/icon_home.svg"),
PanelInfo(" " + tr("Network"), new Networking(this), "../assets/offroad/icon_network.png"),
PanelInfo(" " + tr("sunnylink"), new SunnylinkPanel(this), "../assets/offroad/icon_wifi_strength_full.svg"),
PanelInfo(" " + tr("Toggles"), toggles, "../assets/offroad/icon_toggle.png"),
PanelInfo(" " + tr("Software"), new SoftwarePanelSP(this), "../assets/offroad/icon_software.png"),
PanelInfo(" " + tr("sunnypilot"), new SunnypilotPanel(this), "../assets/offroad/icon_openpilot.png"),
PanelInfo(" " + tr("OSM"), new OsmPanel(this), "../assets/offroad/icon_map.png"),
PanelInfo(" " + tr("Monitoring"), new MonitoringPanel(this), "../assets/offroad/icon_monitoring.png"),
PanelInfo(" " + tr("Visuals"), new VisualsPanel(this), "../assets/offroad/icon_visuals.png"),
PanelInfo(" " + tr("Display"), new DisplayPanel(this), "../assets/offroad/icon_display.png"),
PanelInfo(" " + tr("Trips"), new TripsPanel(this), "../assets/offroad/icon_trips.png"),
PanelInfo(" " + tr("Vehicle"), new VehiclePanel(this), "../assets/offroad/icon_vehicle.png"),
QList<QPair<QString, QWidget *>> panels = {
{tr("Device"), device},
{tr("Network"), new Networking(this)},
{tr("Toggles"), toggles},
{tr("Software"), new SoftwarePanel(this)},
};
nav_btns = new QButtonGroup(this);
for (auto &[name, panel, icon] : panels) {
for (auto &[name, panel] : panels) {
QPushButton *btn = new QPushButton(name);
btn->setCheckable(true);
btn->setChecked(nav_btns->buttons().size() == 0);
btn->setIcon(QIcon(QPixmap(icon)));
btn->setIconSize(QSize(70, 70));
btn->setStyleSheet(R"(
QPushButton {
border-radius: 20px;
width: 400px;
height: 98px;
color: #bdbdbd;
color: grey;
border: none;
background: none;
font-size: 50px;
font-size: 65px;
font-weight: 500;
text-align: left;
padding-left: 22px;
}
QPushButton:checked {
background-color: #696868;
color: white;
}
QPushButton:pressed {
@@ -694,9 +425,9 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
)");
btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
nav_btns->addButton(btn);
buttons_layout->addWidget(btn, 0, Qt::AlignLeft | Qt::AlignBottom);
sidebar_layout->addWidget(btn, 0, Qt::AlignRight);
const int lr_margin = (name != (" " + tr("Network")) || (name != (" " + tr("sunnypilot")))) ? 50 : 0; // Network and sunnypilot panel handles its own margins
const int lr_margin = name != tr("Network") ? 50 : 0; // Network panel handles its own margins
panel->setContentsMargins(lr_margin, 25, lr_margin, 25);
ScrollView *panel_frame = new ScrollView(panel, this);
@@ -707,18 +438,11 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
panel_widget->setCurrentWidget(w);
});
}
sidebar_layout->setContentsMargins(50, 50, 25, 50);
sidebar_layout->setContentsMargins(50, 50, 100, 50);
// main settings layout, sidebar + main panel
QHBoxLayout *main_layout = new QHBoxLayout(this);
// add layout for close button
sidebar_layout->addLayout(close_btn_layout);
// add layout for buttons scrolling
ScrollView *buttons_scrollview = new ScrollView(buttons_widget, this);
sidebar_layout->addWidget(buttons_scrollview);
sidebar_widget->setFixedWidth(500);
main_layout->addWidget(sidebar_widget);
main_layout->addWidget(panel_widget);
@@ -732,7 +456,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
background-color: black;
}
QStackedWidget, ScrollView {
background-color: black;
background-color: #292929;
border-radius: 30px;
}
)");
+21 -32
View File
@@ -8,12 +8,23 @@
#include <QLabel>
#include <QPushButton>
#include <QStackedWidget>
#include <QTimer>
#include <QWidget>
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/util.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#define ListWidget ListWidgetSP
#define ParamControl ParamControlSP
#define ButtonControl ButtonControlSP
#define ButtonParamControl ButtonParamControlSP
#define ToggleControl ToggleControlSP
#define LabelControl LabelControlSP
#else
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/widgets/controls.h"
#endif
// ********** settings window + top-level panels **********
class SettingsWindow : public QFrame {
@@ -32,19 +43,11 @@ signals:
void showDriverView();
void expandToggleDescription(const QString &param);
private:
protected:
QPushButton *sidebar_alert_widget;
QWidget *sidebar_widget;
QButtonGroup *nav_btns;
QStackedWidget *panel_widget;
struct PanelInfo {
QString name;
QWidget *widget;
QString icon;
PanelInfo(const QString &name, QWidget *widget, const QString &icon) : name(name), widget(widget), icon(icon) {}
};
};
class DevicePanel : public ListWidget {
@@ -57,26 +60,15 @@ signals:
void reviewTrainingGuide();
void showDriverView();
private slots:
protected slots:
void poweroff();
void reboot();
void updateCalibDescription();
void onPinFileChanged(const QString &file_path);
void refreshPin();
void forceoffroad();
void updateLabels();
private:
protected:
Params params;
ButtonControl *pair_device;
ButtonControl *fleetManagerPin;
QString pin_title = tr("Fleet Manager PIN:") + " ";
QString pin = "OFF";
QFileSystemWatcher *fs_watch;
QPushButton *offroad_btn;
QHBoxLayout *power_layout;
};
class TogglesPanel : public ListWidget {
@@ -87,18 +79,16 @@ public:
public slots:
void expandToggleDescription(const QString &param);
void updateToggles();
private slots:
void updateState(const UIState &s);
protected slots:
virtual void updateState(const UIState &s);
private:
protected:
Params params;
std::map<std::string, ParamControl*> toggles;
ButtonParamControl *long_personality_setting;
ButtonParamControl *accel_personality_setting;
ParamWatcher *param_watcher;
virtual void updateToggles();
};
class SoftwarePanel : public ListWidget {
@@ -114,7 +104,6 @@ protected:
bool is_onroad = false;
QLabel *onroadLbl;
LabelControl *currentModelLbl;
LabelControl *versionLbl;
ButtonControl *installBtn;
ButtonControl *downloadBtn;
+12 -5
View File
@@ -9,22 +9,27 @@
#include "common/params.h"
#include "common/util.h"
#include "common/model.h"
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/util.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#else
#include "selfdrive/ui/qt/widgets/controls.h"
#endif
#include "selfdrive/ui/qt/widgets/input.h"
#include "system/hardware/hw.h"
#ifdef SUNNYPILOT
#define ListWidget ListWidgetSP
#define ButtonControl ButtonControlSP
#endif
void SoftwarePanel::checkForUpdates() {
std::system("pkill -SIGUSR1 -f system.updated.updated");
}
SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
currentModelLbl = new LabelControl(tr("Driving Model"), CURRENT_MODEL);
addItem(currentModelLbl);
onroadLbl = new QLabel(tr("Updates are only downloaded while the car is off."));
onroadLbl->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left; padding-top: 30px; padding-bottom: 30px;");
addItem(onroadLbl);
@@ -74,7 +79,9 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
checkForUpdates();
}
});
addItem(targetBranchBtn);
if (!params.getBool("IsTestedBranch")) {
addItem(targetBranchBtn);
}
// uninstall button
auto uninstallBtn = new ButtonControl(tr("Uninstall %1").arg(getBrand()), tr("UNINSTALL"));
@@ -1,45 +0,0 @@
#pragma once
#include "selfdrive/ui/qt/widgets/controls.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
class CameraOffset : public SPOptionControl {
Q_OBJECT
public:
CameraOffset();
void refresh();
private:
Params params;
};
class PathOffset : public SPOptionControl {
Q_OBJECT
public:
PathOffset();
void refresh();
private:
Params params;
};
class CustomOffsetsSettings : public QWidget {
Q_OBJECT
public:
explicit CustomOffsetsSettings(QWidget* parent = nullptr);
signals:
void backPress();
private:
Params params;
std::map<std::string, ParamControl*> toggles;
CameraOffset *camera_offset;
PathOffset *path_offset;
};
@@ -1,69 +0,0 @@
#pragma once
#include "selfdrive/ui/qt/widgets/controls.h"
class OnroadScreenOff : public SPOptionControl {
Q_OBJECT
public:
OnroadScreenOff();
void refresh();
private:
Params params;
};
class OnroadScreenOffBrightness : public SPOptionControl {
Q_OBJECT
public:
OnroadScreenOffBrightness();
void refresh();
private:
Params params;
};
class MaxTimeOffroad : public SPOptionControl {
Q_OBJECT
public:
MaxTimeOffroad();
void refresh();
private:
Params params;
};
class BrightnessControl : public SPOptionControl {
Q_OBJECT
public:
BrightnessControl();
void refresh();
private:
Params params;
};
class DisplayPanel : public ListWidget {
Q_OBJECT
public:
explicit DisplayPanel(QWidget *parent = nullptr);
void showEvent(QShowEvent *event) override;
public slots:
void updateToggles();
private:
Params params;
std::map<std::string, ParamControl*> toggles;
OnroadScreenOff *onroad_screen_off;
OnroadScreenOffBrightness *onroad_screen_off_brightness;
};
@@ -1,35 +0,0 @@
#pragma once
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonObject>
#include <QEventLoop>
class JsonFetcher
{
public:
static QJsonObject getJsonFromURL(const QString &url)
{
const auto qurl = QUrl(url);
QNetworkAccessManager manager;
const QNetworkRequest request(qurl);
QNetworkReply *reply = manager.get(request);
QEventLoop loop;
// Send GET request
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Failed to fetch data from URL: " << reply->errorString();
return QJsonObject();
}
const QByteArray responseData = reply->readAll();
const QJsonDocument doc = QJsonDocument::fromJson(responseData);
QJsonObject json = doc.object();
reply->deleteLater();
return json;
}
};
@@ -1,57 +0,0 @@
#pragma once
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/widgets/controls.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
class AutoLaneChangeTimer : public SPOptionControl {
Q_OBJECT
public:
AutoLaneChangeTimer();
void refresh();
signals:
void toggleUpdated();
private:
Params params;
};
class PauseLateralSpeed : public SPOptionControl {
Q_OBJECT
public:
PauseLateralSpeed();
void refresh();
signals:
void ToggleUpdated();
private:
Params params;
};
class LaneChangeSettings : public QWidget {
Q_OBJECT
public:
explicit LaneChangeSettings(QWidget* parent = nullptr);
void showEvent(QShowEvent *event) override;
signals:
void backPress();
public slots:
void updateToggles();
private:
Params params;
std::map<std::string, ParamControl*> toggles;
AutoLaneChangeTimer *auto_lane_change_timer;
PauseLateralSpeed *pause_lateral_speed;
};
@@ -1,25 +0,0 @@
#pragma once
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/widgets/controls.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
class MadsSettings : public QWidget {
Q_OBJECT
public:
explicit MadsSettings(QWidget* parent = nullptr);
void showEvent(QShowEvent *event) override;
signals:
void backPress();
public slots:
void updateToggles();
private:
Params params;
std::map<std::string, ParamControl*> toggles;
ButtonParamControl *dlob_settings;
};
@@ -1,30 +0,0 @@
#include "selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h"
MonitoringPanel::MonitoringPanel(QWidget *parent) : QFrame(parent) {
main_layout = new QStackedLayout(this);
ListWidget *list = new ListWidget(this, false);
// param, title, desc, icon
std::vector<std::tuple<QString, QString, QString, QString>> toggle_defs{
{
"HandsOnWheelMonitoring",
tr("Enable Hands on Wheel Monitoring"),
tr("Monitor and alert when driver is not keeping the hands on the steering wheel."),
"../assets/offroad/icon_blank.png",
}
};
for (auto &[param, title, desc, icon] : toggle_defs) {
auto toggle = new ParamControl(param, title, desc, icon, this);
list->addItem(toggle);
toggles[param.toStdString()] = toggle;
}
monitoringScreen = new QWidget(this);
QVBoxLayout* vlayout = new QVBoxLayout(monitoringScreen);
vlayout->setContentsMargins(50, 20, 50, 20);
vlayout->addWidget(new ScrollView(list, this), 1);
main_layout->addWidget(monitoringScreen);
}
@@ -1,17 +0,0 @@
#pragma once
#include "selfdrive/ui/qt/widgets/controls.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
class MonitoringPanel : public QFrame {
Q_OBJECT
public:
explicit MonitoringPanel(QWidget *parent = nullptr);
private:
QStackedLayout* main_layout = nullptr;
QWidget* monitoringScreen = nullptr;
Params params;
std::map<std::string, ParamControl*> toggles;
};
@@ -1,51 +0,0 @@
#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h"
SpeedLimitPolicySettings::SpeedLimitPolicySettings(QWidget* parent) : QWidget(parent) {
QVBoxLayout* main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(50, 20, 50, 20);
main_layout->setSpacing(20);
// Back button
PanelBackButton* back = new PanelBackButton();
connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
main_layout->addWidget(back, 0, Qt::AlignLeft);
ListWidget *list = new ListWidget(this, false);
speed_limit_policy = new ButtonParamControl(
"SpeedLimitControlPolicy",
tr("Speed Limit Source Policy"),
"",
"../assets/offroad/icon_blank.png",
speed_limit_policy_texts,
250
);
speed_limit_policy->showDescription();
connect(speed_limit_policy, &ButtonParamControl::buttonToggled, this, &SpeedLimitPolicySettings::updateToggles);
list->addItem(speed_limit_policy);
param_watcher = new ParamWatcher(this);
QObject::connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString &param_name, const QString &param_value) {
updateToggles();
});
main_layout->addWidget(new ScrollView(list, this));
}
void SpeedLimitPolicySettings::showEvent(QShowEvent *event) {
updateToggles();
}
void SpeedLimitPolicySettings::updateToggles() {
param_watcher->addParam("SpeedLimitControlPolicy");
if (!isVisible()) {
return;
}
// TODO: SP: use upstream's setCheckedButton
speed_limit_policy->setButton("SpeedLimitControlPolicy");
speed_limit_policy->setDescription(speedLimitPolicyDescriptionBuilder("SpeedLimitControlPolicy", speed_limit_policy_descriptions));
}
@@ -1,84 +0,0 @@
#pragma once
#include "common/model.h"
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.h"
#include "selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h"
#include "selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h"
#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.h"
#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.h"
#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/qt/widgets/controls.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
class TorqueFriction : public SPOptionControl {
Q_OBJECT
public:
TorqueFriction();
void refresh();
private:
Params params;
};
class TorqueMaxLatAccel : public SPOptionControl {
Q_OBJECT
public:
TorqueMaxLatAccel();
void refresh();
private:
Params params;
};
class SunnypilotPanel : public QFrame {
Q_OBJECT
public:
explicit SunnypilotPanel(QWidget *parent = nullptr);
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent* event) override;
public slots:
void updateToggles();
private:
QStackedLayout* main_layout = nullptr;
QWidget* sunnypilotScreen = nullptr;
MadsSettings* mads_settings = nullptr;
SubPanelButton *slcSettings = nullptr;
SlcSettings* slc_settings = nullptr;
SubPanelButton *slwSettings = nullptr;
SpeedLimitWarningSettings* slw_settings = nullptr;
SubPanelButton *slpSettings = nullptr;
SpeedLimitPolicySettings* slp_settings = nullptr;
LaneChangeSettings* lane_change_settings = nullptr;
CustomOffsetsSettings* custom_offsets_settings = nullptr;
Params params;
std::map<std::string, ParamControl*> toggles;
ParamWatcher *param_watcher;
TorqueFriction *friction;
TorqueMaxLatAccel *lat_accel_factor;
ButtonParamControl *dlp_settings;
ScrollView *scrollView = nullptr;
const QString nnff_description = QString("%1<br><br>"
"%2")
.arg(tr("Formerly known as <b>\"NNFF\"</b>, this replaces the lateral <b>\"torque\"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy."))
.arg(tr("Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: ") + "<font color='white'><b>#tuning-nnlc</b></font>");
QString nnffDescriptionBuilder(const QString &custom_description) {
QString description = "<b>" + custom_description + "</b><br><br>" + nnff_description;
return description;
}
const QString custom_offsets_description = QString(tr("Add custom offsets to Camera and Path in sunnypilot."));
const QString dlp_description = QString(tr("Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions."));
};
@@ -1,29 +0,0 @@
#include "selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h"
TripsPanel::TripsPanel(QWidget* parent) : QFrame(parent) {
QVBoxLayout* main_layout = new QVBoxLayout(this);
main_layout->setMargin(0);
// main content
main_layout->addSpacing(25);
center_layout = new QStackedLayout();
driveStatsWidget = new DriveStats;
driveStatsWidget->setStyleSheet(R"(
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; }
)");
center_layout->addWidget(driveStatsWidget);
main_layout->addLayout(center_layout, 1);
setStyleSheet(R"(
* {
color: white;
}
TripsPanel > QLabel {
font-size: 55px;
}
)");
}
@@ -1,17 +0,0 @@
#pragma once
#include "selfdrive/ui/qt/widgets/controls.h"
#include "selfdrive/ui/qt/widgets/sunnypilot/drive_stats.h"
class TripsPanel : public QFrame {
Q_OBJECT
public:
explicit TripsPanel(QWidget* parent = 0);
private:
Params params;
QStackedLayout* center_layout;
DriveStats *driveStatsWidget;
};
@@ -1,59 +0,0 @@
#pragma once
#include <QApplication>
#include "common/watchdog.h"
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/qt/widgets/controls.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
class VehiclePanel : public QWidget {
Q_OBJECT
public:
explicit VehiclePanel(QWidget *parent = nullptr);
void showEvent(QShowEvent *event) override;
public slots:
void updateToggles();
private:
Params params;
QStackedLayout* main_layout = nullptr;
QWidget* home = nullptr;
QPushButton* setCarBtn;
QString set;
QWidget* home_widget;
QString prompt_select = tr("Select your car");
};
class SPVehiclesTogglesPanel : public ListWidget {
Q_OBJECT
public:
explicit SPVehiclesTogglesPanel(VehiclePanel *parent);
void showEvent(QShowEvent *event) override;
public slots:
void updateToggles();
private:
Params params;
bool is_onroad = false;
ParamControl *stockLongToyota;
ParamControl *toyotaEnhancedBsm;
const QString toyotaEnhancedBsmDescription = QString("%1<br><br>"
"%2")
.arg(tr("sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects."))
.arg(tr("Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2."));
QString toyotaEnhancedBsmDesciptionBuilder(const QString &custom_description) {
QString description = "<b>" + custom_description + "</b><br><br>" + toyotaEnhancedBsmDescription;
return description;
}
};
@@ -1,19 +0,0 @@
#pragma once
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/widgets/controls.h"
class VisualsPanel : public ListWidget {
Q_OBJECT
public:
explicit VisualsPanel(QWidget *parent = nullptr);
private:
Params params;
std::map<std::string, ParamControl*> toggles;
ButtonParamControl *dev_ui_settings;
ButtonParamControl *chevron_info_settings;
ButtonParamControl *sidebar_temp_setting;
};
-11
View File
@@ -1,11 +0,0 @@
#pragma once
#include "selfdrive/ui/qt/offroad/sunnypilot/display_settings.h"
#include "selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h"
#include "selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.h"
#include "selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.h"
#include "selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h"
#include "selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h"
#include "selfdrive/ui/qt/offroad/sunnypilot/osm_settings.h"
#include "selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.h"
#include "selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h"
+152
View File
@@ -0,0 +1,152 @@
#include "selfdrive/ui/qt/offroad_home.h"
#include <QHBoxLayout>
#include <QMouseEvent>
#include "selfdrive/ui/qt/offroad/experimental_mode.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/qt/widgets/prime.h"
// OffroadHome: the offroad home page
OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
QVBoxLayout* main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(40, 40, 40, 40);
// top header
QHBoxLayout* header_layout = new QHBoxLayout();
header_layout->setContentsMargins(0, 0, 0, 0);
header_layout->setSpacing(16);
update_notif = new QPushButton(tr("UPDATE"));
update_notif->setVisible(false);
update_notif->setStyleSheet("background-color: #364DEF;");
QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); });
header_layout->addWidget(update_notif, 0, Qt::AlignHCenter | Qt::AlignLeft);
alert_notif = new QPushButton();
alert_notif->setVisible(false);
alert_notif->setStyleSheet("background-color: #E22C2C;");
QObject::connect(alert_notif, &QPushButton::clicked, [=] { center_layout->setCurrentIndex(2); });
header_layout->addWidget(alert_notif, 0, Qt::AlignHCenter | Qt::AlignLeft);
version = new ElidedLabel();
header_layout->addWidget(version, 0, Qt::AlignHCenter | Qt::AlignRight);
main_layout->addLayout(header_layout);
// main content
main_layout->addSpacing(25);
center_layout = new QStackedLayout();
home_widget = new QWidget(this);
{
home_layout = new QHBoxLayout(home_widget);
home_layout->setContentsMargins(0, 0, 0, 0);
home_layout->setSpacing(30);
// left: PrimeAdWidget
left_widget = new QStackedWidget(this);
QVBoxLayout *left_prime_layout = new QVBoxLayout();
QWidget *prime_user = new PrimeUserWidget();
prime_user->setStyleSheet(R"(
border-radius: 10px;
background-color: #333333;
)");
left_prime_layout->addWidget(prime_user);
left_prime_layout->addStretch();
left_widget->addWidget(new LayoutWidget(left_prime_layout));
left_widget->addWidget(new PrimeAdWidget);
left_widget->setStyleSheet("border-radius: 10px;");
left_widget->setCurrentIndex(uiState()->hasPrime() ? 0 : 1);
connect(uiState(), &UIState::primeChanged, [=](bool prime) {
left_widget->setCurrentIndex(prime ? 0 : 1);
});
home_layout->addWidget(left_widget, 1);
// right: ExperimentalModeButton, SetupWidget
QWidget* right_widget = new QWidget(this);
QVBoxLayout* right_column = new QVBoxLayout(right_widget);
right_column->setContentsMargins(0, 0, 0, 0);
right_widget->setFixedWidth(750);
right_column->setSpacing(30);
ExperimentalModeButton *experimental_mode = new ExperimentalModeButton(this);
QObject::connect(experimental_mode, &ExperimentalModeButton::openSettings, this, &OffroadHome::openSettings);
right_column->addWidget(experimental_mode, 1);
SetupWidget *setup_widget = new SetupWidget;
QObject::connect(setup_widget, &SetupWidget::openSettings, this, &OffroadHome::openSettings);
right_column->addWidget(setup_widget, 1);
home_layout->addWidget(right_widget, 1);
}
center_layout->addWidget(home_widget);
// add update & alerts widgets
update_widget = new UpdateAlert();
QObject::connect(update_widget, &UpdateAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); });
center_layout->addWidget(update_widget);
alerts_widget = new OffroadAlert();
QObject::connect(alerts_widget, &OffroadAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); });
center_layout->addWidget(alerts_widget);
main_layout->addLayout(center_layout, 1);
// set up refresh timer
timer = new QTimer(this);
timer->callOnTimeout(this, &OffroadHome::refresh);
setStyleSheet(R"(
* {
color: white;
}
OffroadHome {
background-color: black;
}
OffroadHome > QPushButton {
padding: 15px 30px;
border-radius: 5px;
font-size: 40px;
font-weight: 500;
}
OffroadHome > QLabel {
font-size: 55px;
}
)");
}
void OffroadHome::showEvent(QShowEvent *event) {
refresh();
timer->start(10 * 1000);
}
void OffroadHome::hideEvent(QHideEvent *event) {
timer->stop();
}
void OffroadHome::refresh() {
version->setText(getBrand() + " " + QString::fromStdString(params.get("UpdaterCurrentDescription")));
bool updateAvailable = update_widget->refresh();
int alerts = alerts_widget->refresh();
// pop-up new notification
int idx = center_layout->currentIndex();
if (!updateAvailable && !alerts) {
idx = 0;
} else if (updateAvailable && (!update_notif->isVisible() || (!alerts && idx == 2))) {
idx = 1;
} else if (alerts && (!alert_notif->isVisible() || (!updateAvailable && idx == 1))) {
idx = 2;
}
center_layout->setCurrentIndex(idx);
update_notif->setVisible(updateAvailable);
alert_notif->setVisible(alerts);
if (alerts) {
alert_notif->setText(QString::number(alerts) + (alerts > 1 ? tr(" ALERTS") : tr(" ALERT")));
}
}
+55
View File
@@ -0,0 +1,55 @@
#pragma once
#include <QStackedWidget>
#include <QWidget>
#include "common/params.h"
#include "selfdrive/ui/qt/body.h"
#include "selfdrive/ui/qt/onroad/onroad_home.h"
#include "selfdrive/ui/qt/widgets/offroad_alerts.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_home.h"
#include "selfdrive/ui/sunnypilot/qt/sidebar.h"
#define OnroadWindow OnroadWindowSP
#define OffroadHomeImp OffroadHomeSP
#define LayoutWidget LayoutWidgetSP
#define Sidebar SidebarSP
#define ElidedLabel ElidedLabelSP
#else
#include "selfdrive/ui/qt/widgets/controls.h"
#include "selfdrive/ui/qt/onroad/onroad_home.h"
#include "selfdrive/ui/qt/sidebar.h"
#endif
class OffroadHome : public QFrame {
Q_OBJECT
public:
void do_work(QWidget*& home_widget);
explicit OffroadHome(QWidget* parent = 0);
signals:
void openSettings(int index = 0, const QString &param = "");
protected:
QStackedLayout* center_layout;
QWidget* home_widget;
QHBoxLayout *home_layout;
QStackedWidget *left_widget;
private:
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent *event) override;
void refresh();
Params params;
QTimer* timer;
ElidedLabel* version;
UpdateAlert *update_widget;
OffroadAlert* alerts_widget;
QPushButton* alert_notif;
QPushButton* update_notif;
};
File diff suppressed because it is too large Load Diff
+1 -175
View File
@@ -2,14 +2,10 @@
#include <QVBoxLayout>
#include <memory>
#include <QTimer>
#include "selfdrive/ui/qt/onroad/buttons.h"
#include "selfdrive/ui/qt/widgets/cameraview.h"
const int subsign_img_size = 35;
const int blinker_size = 120;
class AnnotatedCameraWidget : public CameraWidget {
Q_OBJECT
@@ -17,68 +13,8 @@ public:
explicit AnnotatedCameraWidget(VisionStreamType type, QWidget* parent = 0);
void updateState(const UIState &s);
MapSettingsButton *map_settings_btn;
OnroadSettingsButton *onroad_settings_btn;
private:
void drawText(QPainter &p, int x, int y, const QString &text, int alpha = 255);
void drawCenteredText(QPainter &p, int x, int y, const QString &text, QColor color);
void drawVisionTurnControllerUI(QPainter &p, int x, int y, int size, const QColor &color, const QString &speed,
int alpha);
void drawCircle(QPainter &p, int x, int y, int r, QBrush bg);
void drawSpeedSign(QPainter &p, QRect rc, const QString &speed, const QString &sub_text, int subtext_size,
bool is_map_sourced, bool is_active);
void drawTrunSpeedSign(QPainter &p, QRect rc, const QString &speed, const QString &sub_text, int curv_sign,
bool is_active);
void drawColoredText(QPainter &p, int x, int y, const QString &text, QColor color);
void drawStandstillTimer(QPainter &p, int x, int y);
// ############################## DEV UI START ##############################
void drawRightDevUi(QPainter &p, int x, int y);
int drawDevUiElementRight(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color);
int drawNewDevUiElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color);
void drawNewDevUi2(QPainter &p, int x, int y);
void drawCenteredLeftText(QPainter &p, int x, int y, const QString &text1, QColor color1, const QString &text2, const QString &text3, QColor color2);
struct UiElement {
QString value;
QString label;
QString units;
QColor color;
UiElement(const QString &value, const QString &label, const QString &units, const QColor &color = QColor(255, 255, 255, 255))
: value(value), label(label), units(units), color(color) {}
};
UiElement getDRel();
UiElement getVRel();
UiElement getSteeringAngleDeg();
UiElement getActualLateralAccel();
UiElement getSteeringAngleDesiredDeg();
UiElement getMemoryUsagePercent();
UiElement getAEgo();
UiElement getVEgoLead();
UiElement getFrictionCoefficientFiltered();
UiElement getLatAccelFactorFiltered();
UiElement getSteeringTorqueEps();
UiElement getBearingDeg();
UiElement getAltitude();
// ############################## DEV UI END ##############################
void drawE2eStatus(QPainter &p, int x, int y, int w, int h, int e2e_long_status);
void drawLeftTurnSignal(QPainter &painter, int x, int y, int circle_size, int state);
void drawRightTurnSignal(QPainter &painter, int x, int y, int circle_size, int state);
int blinkerPulse(int frame);
void updateButtonsLayout(bool is_rhd);
void drawFeatureStatusText(QPainter &p, int x, int y);
void speedLimitSignPulse(int frame);
void speedLimitWarning(QPainter &p, QRect sign_rect, const int sign_margin);
void mousePressEvent(QMouseEvent* e) override;
void drawRoadNameText(QPainter &p, int x, int y, const QString &text, QColor color);
QVBoxLayout *main_layout;
ExperimentalButton *experimental_btn;
@@ -86,15 +22,12 @@ private:
float speed;
QString speedUnit;
float setSpeed;
float speedLimit;
bool is_cruise_set = false;
bool is_metric = false;
bool dmActive = false;
bool hideBottomIcons = false;
bool rightHandDM = false;
float dm_fade_state = 1.0;
bool has_us_speed_limit = false;
bool has_eu_speed_limit = false;
bool v_ego_cluster_seen = false;
int status = STATUS_DISENGAGED;
std::unique_ptr<PubMaster> pm;
@@ -102,126 +35,19 @@ private:
int skip_frame_count = 0;
bool wide_cam_requested = false;
Params params;
QHBoxLayout *buttons_layout;
QPixmap map_img;
QPixmap left_img;
QPixmap right_img;
bool left_blindspot = false;
bool right_blindspot = false;
std::unique_ptr<PubMaster> e2e_state;
bool steerOverride = false;
bool gasOverride = false;
bool latActive = false;
bool madsEnabled = false;
bool brakeLights;
bool standStillTimer;
bool standStill;
float standstillElapsedTime;
bool showVTC = false;
QString vtcSpeed;
QColor vtcColor;
bool showDebugUI = false;
QString roadName = "";
bool showSpeedLimit = false;
float speedLimitSLC;
float speedLimitSLCOffset;
float speedLimitWarningOffset;
QString slcSubText;
float slcSubTextSize = 0.0;
bool overSpeedLimit;
bool mapSourcedSpeedLimit = false;
bool slcActive = false;
bool showTurnSpeedLimit = false;
QString turnSpeedLimit;
QString tscSubText;
bool tscActive = false;
int curveSign = 0;
bool hideVEgoUi;
bool splitPanelVisible;
// ############################## DEV UI START ##############################
bool lead_status;
float lead_d_rel = 0;
float lead_v_rel = 0;
QString lateralState;
float angleSteers = 0;
float steerAngleDesired = 0;
float curvature;
float roll;
int memoryUsagePercent;
int devUiInfo;
float gpsAccuracy;
float altitude;
float vEgo;
float aEgo;
float steeringTorqueEps;
float bearingAccuracyDeg;
float bearingDeg;
bool torquedUseParams;
float latAccelFactorFiltered;
float frictionCoefficientFiltered;
bool liveValid;
// ############################## DEV UI END ##############################
float btnPerc;
bool reversing;
int e2eState;
int e2eStatus;
bool left_blinker, right_blinker, lane_change_edge_block;
int blinker_frame;
int blinker_state = 0;
cereal::LongitudinalPlanSP::SpeedLimitControlState slcState;
int longitudinalPersonality;
int dynamicLaneProfile;
QString mpcMode;
int speed_limit_frame;
bool slcShowSign = true;
QPixmap plus_arrow_up_img;
QPixmap minus_arrow_down_img;
QRect sl_sign_rect;
int rn_offset = 0;
bool e2eLongAlertUi, dynamicExperimentalControlToggle, speedLimitControlToggle, speedLimitWarningFlash;
cereal::CarParams::Reader car_params;
bool cruiseStateEnabled;
bool experimentalMode;
bool featureStatusToggle;
cereal::ModelGeneration drivingModelGen;
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,
int num, const cereal::CarState::Reader &car_data, int chevron_data);
void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd);
void drawHud(QPainter &p);
void drawDriverState(QPainter &painter, const UIState *s);
inline QColor redColor(int alpha = 255) { return QColor(201, 34, 49, alpha); }
inline QColor whiteColor(int alpha = 255) { return QColor(255, 255, 255, alpha); }
inline QColor blackColor(int alpha = 255) { return QColor(0, 0, 0, alpha); }
void paintEvent(QPaintEvent *event) override;
void rocketFuel(QPainter &p);
double prev_draw_t = 0;
FirstOrderFilter fps_filter;
};
+1 -59
View File
@@ -15,18 +15,6 @@ void drawIcon(QPainter &p, const QPoint &center, const QPixmap &img, const QBrus
p.setOpacity(1.0);
}
static void drawCustomButtonIcon(QPainter &p, const int btn_size_x, const int btn_size_y, const QPixmap &img, const QBrush &bg, float opacity) {
QPoint center(btn_size_x / 2, btn_size_y / 2);
p.setRenderHint(QPainter::Antialiasing);
p.setOpacity(1.0); // bg dictates opacity of ellipse
p.setPen(Qt::NoPen);
p.setBrush(bg);
p.drawEllipse(center, btn_size_x / 2, btn_size_y / 2);
p.setOpacity(opacity);
p.drawPixmap(center - QPoint(img.width() / 2, img.height() / 2), img);
p.setOpacity(1.0);
}
// ExperimentalButton
ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(false), engageable(false), QPushButton(parent) {
setFixedSize(btn_size, btn_size);
@@ -46,8 +34,7 @@ void ExperimentalButton::changeMode() {
void ExperimentalButton::updateState(const UIState &s) {
const auto cs = (*s.sm)["controlsState"].getControlsState();
const auto lp_sp = (*s.sm)["longitudinalPlanSP"].getLongitudinalPlanSP();
bool eng = (cs.getEngageable() || cs.getEnabled()) && !(lp_sp.getVisionTurnControllerState() > cereal::LongitudinalPlanSP::VisionTurnControllerState::DISABLED);
bool eng = cs.getEngageable() || cs.getEnabled();
if ((cs.getExperimentalMode() != experimental_mode) || (eng != engageable)) {
engageable = eng;
experimental_mode = cs.getExperimentalMode();
@@ -60,48 +47,3 @@ void ExperimentalButton::paintEvent(QPaintEvent *event) {
QPixmap img = experimental_mode ? experimental_img : engage_img;
drawIcon(p, QPoint(btn_size / 2, btn_size / 2), img, QColor(0, 0, 0, 166), (isDown() || !engageable) ? 0.6 : 1.0);
}
// MapSettingsButton
MapSettingsButton::MapSettingsButton(QWidget *parent) : QPushButton(parent) {
// btn_size: 192 * 80% ~= 152
// img_size: (152 / 4) * 3 = 114
setFixedSize(152, 152);
settings_img = loadPixmap("../assets/navigation/icon_directions_outlined.svg", {114, 114});
// hidden by default, made visible if map is created (has prime or mapbox token)
setVisible(false);
setEnabled(false);
}
void MapSettingsButton::paintEvent(QPaintEvent *event) {
QPainter p(this);
drawCustomButtonIcon(p, 152, 152, settings_img, QColor(0, 0, 0, 166), isDown() ? 0.6 : 1.0);
}
// OnroadSettingsButton
OnroadSettingsButton::OnroadSettingsButton(QWidget *parent) : QPushButton(parent) {
// btn_size: 192 * 80% ~= 152
// img_size: (152 / 4) * 3 = 114
setFixedSize(152, 152);
settings_img = loadPixmap("../assets/navigation/icon_settings.svg", {114, 114});
// hidden by default, made visible if Driving Personality / GAC, DLP, DEC, or SLC is enabled
setVisible(false);
setEnabled(false);
}
void OnroadSettingsButton::paintEvent(QPaintEvent *event) {
QPainter p(this);
drawCustomButtonIcon(p, 152, 152, settings_img, QColor(0, 0, 0, 166), isDown() ? 0.6 : 1.0);
}
void OnroadSettingsButton::updateState(const UIState &s) {
const auto cp = (*s.sm)["carParams"].getCarParams();
auto dlp_enabled = s.scene.driving_model_generation == cereal::ModelGeneration::ONE;
bool allow_btn = s.scene.onroad_settings_toggle && (dlp_enabled || hasLongitudinalControl(cp) || !cp.getPcmCruiseSpeed());
setVisible(allow_btn);
setEnabled(allow_btn);
}
+8 -29
View File
@@ -2,7 +2,11 @@
#include <QPushButton>
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/ui.h"
#else
#include "selfdrive/ui/ui.h"
#endif
const int btn_size = 192;
const int img_size = (btn_size / 4) * 3;
@@ -14,6 +18,10 @@ public:
explicit ExperimentalButton(QWidget *parent = 0);
void updateState(const UIState &s);
protected:
bool experimental_mode;
bool engageable;
private:
void paintEvent(QPaintEvent *event) override;
void changeMode();
@@ -21,35 +29,6 @@ private:
Params params;
QPixmap engage_img;
QPixmap experimental_img;
bool experimental_mode;
bool engageable;
};
class MapSettingsButton : public QPushButton {
Q_OBJECT
public:
explicit MapSettingsButton(QWidget *parent = 0);
private:
void paintEvent(QPaintEvent *event) override;
QPixmap settings_img;
};
class OnroadSettingsButton : public QPushButton {
Q_OBJECT
public:
explicit OnroadSettingsButton(QWidget *parent = 0);
void updateState(const UIState &s);
private:
void paintEvent(QPaintEvent *event) override;
QPixmap settings_img;
};
void drawIcon(QPainter &p, const QPoint &center, const QPixmap &img, const QBrush &bg, float opacity);
+4 -107
View File
@@ -3,12 +3,6 @@
#include <QPainter>
#include <QStackedLayout>
#ifdef ENABLE_MAPS
#include "selfdrive/ui/qt/maps/map_helpers.h"
#include "selfdrive/ui/qt/maps/map_panel.h"
#endif
#include "selfdrive/ui/qt/onroad_settings_panel.h"
#include "selfdrive/ui/qt/util.h"
OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) {
@@ -31,11 +25,6 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) {
split->insertWidget(0, arCam);
}
if (getenv("MAP_RENDER_VIEW")) {
CameraWidget *map_render = new CameraWidget("navd", VISION_STREAM_MAP, false, this);
split->insertWidget(0, map_render);
}
stacked_layout->addWidget(split_wrapper);
alerts = new OnroadAlerts(this);
@@ -46,9 +35,12 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) {
alerts->raise();
setAttribute(Qt::WA_OpaquePaintEvent);
// We handle the connection of the signals on the derived class
#ifndef SUNNYPILOT
QObject::connect(uiState(), &UIState::uiUpdate, this, &OnroadWindow::updateState);
QObject::connect(uiState(), &UIState::offroadTransition, this, &OnroadWindow::offroadTransition);
QObject::connect(uiState(), &UIState::primeChanged, this, &OnroadWindow::primeChanged);
#endif
}
void OnroadWindow::updateState(const UIState &s) {
@@ -56,12 +48,6 @@ void OnroadWindow::updateState(const UIState &s) {
return;
}
if (s.scene.map_on_left || s.scene.mapbox_fullscreen) {
split->setDirection(QBoxLayout::LeftToRight);
} else {
split->setDirection(QBoxLayout::RightToLeft);
}
alerts->updateState(s);
nvg->updateState(s);
@@ -73,99 +59,10 @@ void OnroadWindow::updateState(const UIState &s) {
}
}
void OnroadWindow::mousePressEvent(QMouseEvent* e) {
#ifdef ENABLE_MAPS
UIState *s = uiState();
UIScene &scene = s->scene;
if (map != nullptr && !isOnroadSettingsVisible()) {
if (wakeScreenTimeout()) {
// Switch between map and sidebar when using navigate on openpilot
bool sidebarVisible = geometry().x() > 0;
bool show_map = uiState()->scene.navigate_on_openpilot_deprecated ? sidebarVisible : !sidebarVisible;
updateMapSize(scene);
map->setVisible(show_map && !map->isVisible());
}
}
#endif
if (onroad_settings != nullptr && !isMapVisible()) {
if (wakeScreenTimeout()) {
onroad_settings->setVisible(false);
}
}
// propagation event to parent(HomeWindow)
QWidget::mousePressEvent(e);
}
void OnroadWindow::createMapWidget() {
#ifdef ENABLE_MAPS
auto m = new MapPanel(get_mapbox_settings());
map = m;
QObject::connect(m, &MapPanel::mapPanelRequested, this, &OnroadWindow::mapPanelRequested);
QObject::connect(m, &MapPanel::mapPanelRequested, this, &OnroadWindow::onroadSettingsPanelNotRequested);
QObject::connect(nvg->map_settings_btn, &MapSettingsButton::clicked, m, &MapPanel::toggleMapSettings);
nvg->map_settings_btn->setEnabled(true);
m->setFixedWidth(uiState()->scene.mapbox_fullscreen ? topWidget(this)->width() :
topWidget(this)->width() / 2 - UI_BORDER_SIZE);
split->insertWidget(0, m);
// hidden by default, made visible when navRoute is published
m->setVisible(false);
#endif
}
void OnroadWindow::createOnroadSettingsWidget() {
auto os = new OnroadSettingsPanel(this);
onroad_settings = os;
QObject::connect(os, &OnroadSettingsPanel::onroadSettingsPanelRequested, this, &OnroadWindow::onroadSettingsPanelRequested);
QObject::connect(os, &OnroadSettingsPanel::onroadSettingsPanelRequested, this, &OnroadWindow::mapPanelNotRequested);
QObject::connect(nvg->onroad_settings_btn, &OnroadSettingsButton::clicked, os, &OnroadSettingsPanel::toggleOnroadSettings);
nvg->onroad_settings_btn->setEnabled(true);
os->setFixedWidth(topWidget(this)->width() / 2.6 - UI_BORDER_SIZE);
split->insertWidget(0, os);
// hidden by default
os->setVisible(false);
}
void OnroadWindow::offroadTransition(bool offroad) {
if (!offroad) {
#ifdef ENABLE_MAPS
if (map == nullptr && (uiState()->hasPrime() || !MAPBOX_TOKEN.isEmpty())) {
createMapWidget();
}
#endif
if (onroad_settings == nullptr) {
createOnroadSettingsWidget();
}
}
alerts->clear();
}
void OnroadWindow::updateMapSize(const UIScene &scene) {
map->setFixedWidth(scene.mapbox_fullscreen ? topWidget(this)->width() :
topWidget(this)->width() / 2 - UI_BORDER_SIZE);
split->insertWidget(0, map);
}
void OnroadWindow::primeChanged(bool prime) {
#ifdef ENABLE_MAPS
if (map && (!prime && MAPBOX_TOKEN.isEmpty())) {
nvg->map_settings_btn->setEnabled(false);
nvg->map_settings_btn->setVisible(false);
map->deleteLater();
map = nullptr;
} else if (!map && (prime || !MAPBOX_TOKEN.isEmpty())) {
createMapWidget();
}
#endif
}
void OnroadWindow::paintEvent(QPaintEvent *event) {
QPainter p(this);
p.fillRect(rect(), QColor(bg.red(), bg.green(), bg.blue(), 255));
+12 -34
View File
@@ -1,51 +1,29 @@
#pragma once
#include "common/params.h"
#include "selfdrive/ui/qt/onroad/alerts.h"
#if SUNNYPILOT
#include "selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h"
#define AnnotatedCameraWidget AnnotatedCameraWidgetSP
#define UIState UIStateSP
#else
#include "selfdrive/ui/qt/onroad/annotated_camera.h"
#endif
class OnroadWindow : public QWidget {
Q_OBJECT
public:
OnroadWindow(QWidget* parent = 0);
bool isMapVisible() const { return map && map->isVisible(); }
void showMapPanel(bool show) { if (map) map->setVisible(show); }
bool isOnroadSettingsVisible() const { return onroad_settings && onroad_settings->isVisible(); }
bool isMapAvailable() const { return map; }
void mapPanelNotRequested() { if (map) map->setVisible(false); }
void onroadSettingsPanelNotRequested() { if (onroad_settings) onroad_settings->setVisible(false); }
bool wakeScreenTimeout() {
if ((uiState()->scene.sleep_btn != 0 && uiState()->scene.sleep_btn_opacity != 0) ||
(uiState()->scene.sleep_time != 0 && uiState()->scene.onroadScreenOff != -2)) {
return true;
}
return false;
}
signals:
void mapPanelRequested();
void onroadSettingsPanelRequested();
private:
void createMapWidget();
void createOnroadSettingsWidget();
void paintEvent(QPaintEvent *event);
void mousePressEvent(QMouseEvent* e) override;
protected:
void paintEvent(QPaintEvent *event) override;
OnroadAlerts *alerts;
AnnotatedCameraWidget *nvg;
QColor bg = bg_colors[STATUS_DISENGAGED];
QWidget *map = nullptr;
QWidget *onroad_settings = nullptr;
QHBoxLayout* split;
Params params;
private slots:
void offroadTransition(bool offroad);
void primeChanged(bool prime);
void updateState(const UIState &s);
void updateMapSize(const UIScene &scene);
protected slots:
virtual void offroadTransition(bool offroad);
virtual void updateState(const UIState &s);
};
-26
View File
@@ -1,26 +0,0 @@
#include "selfdrive/ui/qt/onroad_settings_panel.h"
#include <QHBoxLayout>
#include <QWidget>
#include "selfdrive/ui/qt/onroad_settings.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/ui.h"
OnroadSettingsPanel::OnroadSettingsPanel(QWidget *parent) : QFrame(parent) {
content_stack = new QStackedLayout(this);
content_stack->setContentsMargins(0, 0, 0, 0);
auto onroad_settings = new OnroadSettings(true, parent);
QObject::connect(onroad_settings, &OnroadSettings::closeSettings, this, &OnroadSettings::hide);
content_stack->addWidget(onroad_settings);
}
void OnroadSettingsPanel::toggleOnroadSettings() {
if (isVisible()) {
hide();
} else {
emit onroadSettingsPanelRequested();
show();
}
}
-23
View File
@@ -1,23 +0,0 @@
#pragma once
#include <QFrame>
#ifdef ENABLE_MAPS
#include <QMapLibre/Settings>
#endif
#include <QStackedLayout>
class OnroadSettingsPanel : public QFrame {
Q_OBJECT
public:
explicit OnroadSettingsPanel(QWidget *parent = nullptr);
signals:
void onroadSettingsPanelRequested();
public slots:
void toggleOnroadSettings();
private:
QStackedLayout *content_stack;
};
+17 -17
View File
@@ -1,36 +1,36 @@
#include "selfdrive/ui/qt/request_repeater.h"
#include "common/swaglog.h"
RequestRepeater::RequestRepeater(QObject *parent, const QString &requestURL, const QString &cacheKey,
int period, bool whileOnroad, bool sunnylink) : HttpRequest(parent, true, 20000, sunnylink) {
request_url = requestURL;
while_onroad = whileOnroad;
int period, bool while_onroad) : HttpRequest(parent) {
timer = new QTimer(this);
timer->setTimerType(Qt::VeryCoarseTimer);
connect(timer, &QTimer::timeout, [=]() { this->timerTick(); });
connectTimer(requestURL, while_onroad);
timer->start(period * 1000);
setupCacheProcess(cacheKey);
}
void RequestRepeater::connectTimer(const QString &requestURL, bool while_onroad) {
QObject::connect(timer, &QTimer::timeout, [=]() {
if ((!uiState()->scene.started || while_onroad) && device()->isAwake() && !active()) {
sendRequest(requestURL);
}
});
}
void RequestRepeater::setupCacheProcess(const QString &cacheKey) {
if (!cacheKey.isEmpty()) {
prevResp = QString::fromStdString(params.get(cacheKey.toStdString()));
if (!prevResp.isEmpty()) {
QTimer::singleShot(500, [=]() { emit requestDone(prevResp, true, QNetworkReply::NoError); });
}
connect(this, &HttpRequest::requestDone, [=](const QString &resp, bool success) {
QObject::connect(this, &HttpRequest::requestDone, [=](const QString &resp, bool success) {
if (success && resp != prevResp) {
params.put(cacheKey.toStdString(), resp.toStdString());
prevResp = resp;
}
});
}
// Don't wait for the timer to fire to send the first request
ForceUpdate();
}
void RequestRepeater::timerTick() {
if ((!uiState()->scene.started || while_onroad) && device()->isAwake() && !active()) {
LOGD("Sending request for %s", qPrintable(request_url));
sendRequest(request_url);
}
}
+11 -12
View File
@@ -1,24 +1,23 @@
#pragma once
#include "common/swaglog.h"
#include "common/util.h"
#include "selfdrive/ui/qt/api.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/api.h"
#else
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/api.h"
#endif
class RequestRepeater : public HttpRequest {
public:
void connectTimer(const QString& requestURL, bool while_onroad);
void setupCacheProcess(const QString& cacheKey);
RequestRepeater(QObject *parent, const QString &requestURL, const QString &cacheKey = "", int period = 0, bool while_onroad=false);
private:
Params params;
QTimer *timer;
QString prevResp;
QString request_url;
bool while_onroad;
void timerTick();
public:
RequestRepeater(QObject *parent, const QString &requestURL, const QString &cacheKey = "", int period = 0, bool whileOnroad=false, bool sunnylink = false);
void ForceUpdate() {
LOGD("Forcing update for %s", qPrintable(request_url));
timerTick();
}
};
+13 -76
View File
@@ -1,11 +1,8 @@
#include "selfdrive/ui/qt/sidebar.h"
#include <cmath>
#include <QMouseEvent>
#include "selfdrive/ui/qt/util.h"
#include "common/params.h"
void Sidebar::drawMetric(QPainter &p, const QPair<QString, QString> &label, QColor c, int y) {
const QRect rect = {30, y, 240, 126};
@@ -93,58 +90,16 @@ void Sidebar::updateState(const UIState &s) {
}
setProperty("connectStatus", QVariant::fromValue(connectStatus));
if (sm.frame % UI_FREQ == 0) { // Update every 1 Hz
switch (s.scene.sidebar_temp_options) {
case 0:
break;
case 1:
sidebar_temp = QString::number((int)deviceState.getMemoryTempC());
break;
case 2: {
const auto& cpu_temp_list = deviceState.getCpuTempC();
float max_cpu_temp = std::numeric_limits<float>::lowest();
for (const float& temp : cpu_temp_list) {
max_cpu_temp = std::max(max_cpu_temp, temp);
}
if (max_cpu_temp >= 0) {
sidebar_temp = QString::number(std::nearbyint(max_cpu_temp));
}
break;
}
case 3: {
const auto& gpu_temp_list = deviceState.getGpuTempC();
float max_gpu_temp = std::numeric_limits<float>::lowest();
for (const float& temp : gpu_temp_list) {
max_gpu_temp = std::max(max_gpu_temp, temp);
}
if (max_gpu_temp >= 0) {
sidebar_temp = QString::number(std::nearbyint(max_gpu_temp));
}
break;
}
case 4:
sidebar_temp = QString::number((int)deviceState.getMaxTempC());
break;
default:
break;
}
setProperty("sidebarTemp", sidebar_temp + "°C");
}
bool show_sidebar_temp = s.scene.sidebar_temp_options != 0;
ItemStatus tempStatus = {{tr("TEMP"), show_sidebar_temp ? sidebar_temp_str : tr("HIGH")}, danger_color};
#ifndef SUNNYPILOT
ItemStatus tempStatus = {{tr("TEMP"), tr("HIGH")}, danger_color};
auto ts = deviceState.getThermalStatus();
if (ts == cereal::DeviceState::ThermalStatus::GREEN) {
tempStatus = {{tr("TEMP"), show_sidebar_temp ? sidebar_temp_str : tr("GOOD")}, good_color};
tempStatus = {{tr("TEMP"), tr("GOOD")}, good_color};
} else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) {
tempStatus = {{tr("TEMP"), show_sidebar_temp ? sidebar_temp_str : tr("OK")}, warning_color};
tempStatus = {{tr("TEMP"), tr("OK")}, warning_color};
}
setProperty("tempStatus", QVariant::fromValue(tempStatus));
#endif
ItemStatus pandaStatus = {{tr("VEHICLE"), tr("ONLINE")}, good_color};
if (s.scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) {
@@ -153,32 +108,14 @@ void Sidebar::updateState(const UIState &s) {
pandaStatus = {{tr("GPS"), tr("SEARCH")}, warning_color};
}
setProperty("pandaStatus", QVariant::fromValue(pandaStatus));
ItemStatus sunnylinkStatus;
auto sl_dongle_id = getSunnylinkDongleId();
auto last_sunnylink_ping_str = params.get("LastSunnylinkPingTime");
auto last_sunnylink_ping = std::stoull(last_sunnylink_ping_str.empty() ? "0" : last_sunnylink_ping_str);
auto elapsed_sunnylink_ping = nanos_since_boot() - last_sunnylink_ping;
auto sunnylink_enabled = params.getBool("SunnylinkEnabled");
QString status = tr("DISABLED");
QColor color = disabled_color;
if (sunnylink_enabled && last_sunnylink_ping == 0) {
// If sunnylink is enabled, but we don't have a dongle id, and we haven't received a ping yet, we are registering
status = sl_dongle_id.has_value() ? tr("OFFLINE") : tr("REGIST...");
color = sl_dongle_id.has_value() ? warning_color : progress_color;
} else if (sunnylink_enabled) {
// If sunnylink is enabled, we are considered online if we have received a ping in the last 80 seconds, else error.
status = elapsed_sunnylink_ping < 80000000000ULL ? tr("ONLINE") : tr("ERROR");
color = elapsed_sunnylink_ping < 80000000000ULL ? good_color : danger_color;
}
sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), status}, color };
setProperty("sunnylinkStatus", QVariant::fromValue(sunnylinkStatus));
}
void Sidebar::paintEvent(QPaintEvent *event) {
QPainter p(this);
DrawSidebar(p); // Because derived classes implement this. Otherwise QPainter gets terminated before time.
}
void Sidebar::DrawSidebar(QPainter &p){
p.setPen(Qt::NoPen);
p.setRenderHint(QPainter::Antialiasing);
@@ -204,10 +141,10 @@ void Sidebar::paintEvent(QPaintEvent *event) {
p.setPen(QColor(0xff, 0xff, 0xff));
const QRect r = QRect(50, 247, 100, 50);
p.drawText(r, Qt::AlignCenter, net_type);
RETURN_IF_SUNNYPILOT // Because we draw ourselves
// metrics
drawMetric(p, temp_status.first, temp_status.second, 310);
drawMetric(p, panda_status.first, panda_status.second, 440);
drawMetric(p, connect_status.first, connect_status.second, 570);
drawMetric(p, sunnylink_status.first, sunnylink_status.second, 700);
drawMetric(p, temp_status.first, temp_status.second, 338);
drawMetric(p, panda_status.first, panda_status.second, 496);
drawMetric(p, connect_status.first, connect_status.second, 654);
}
+7 -9
View File
@@ -4,8 +4,13 @@
#include <QFrame>
#include <QMap>
#include "cereal/messaging/messaging.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/ui.h"
#else
#include "selfdrive/ui/ui.h"
#endif
typedef QPair<QPair<QString, QString>, QColor> ItemStatus;
Q_DECLARE_METATYPE(ItemStatus);
@@ -13,12 +18,10 @@ Q_DECLARE_METATYPE(ItemStatus);
class Sidebar : public QFrame {
Q_OBJECT
Q_PROPERTY(ItemStatus connectStatus MEMBER connect_status NOTIFY valueChanged);
Q_PROPERTY(ItemStatus sunnylinkStatus MEMBER sunnylink_status NOTIFY valueChanged);
Q_PROPERTY(ItemStatus pandaStatus MEMBER panda_status NOTIFY valueChanged);
Q_PROPERTY(ItemStatus tempStatus MEMBER temp_status NOTIFY valueChanged);
Q_PROPERTY(QString netType MEMBER net_type NOTIFY valueChanged);
Q_PROPERTY(int netStrength MEMBER net_strength NOTIFY valueChanged);
Q_PROPERTY(QString sidebarTemp MEMBER sidebar_temp_str NOTIFY valueChanged);
public:
explicit Sidebar(QWidget* parent = 0);
@@ -36,6 +39,7 @@ protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void drawMetric(QPainter &p, const QPair<QString, QString> &label, QColor c, int y);
virtual void DrawSidebar(QPainter &p);
QPixmap home_img, flag_img, settings_img;
bool onroad, flag_pressed, settings_pressed;
@@ -52,19 +56,13 @@ protected:
const QRect home_btn = QRect(60, 860, 180, 180);
const QRect settings_btn = QRect(50, 35, 200, 117);
const QColor good_color = QColor(255, 255, 255);
const QColor progress_color = QColor(3, 132, 252);
const QColor warning_color = QColor(218, 202, 37);
const QColor danger_color = QColor(201, 34, 49);
const QColor disabled_color = QColor(128, 128, 128);
ItemStatus connect_status, panda_status, temp_status, sunnylink_status;
ItemStatus connect_status, panda_status, temp_status;
QString net_type;
int net_strength = 0;
private:
Params params;
std::unique_ptr<PubMaster> pm;
QString sidebar_temp = "0";
QString sidebar_temp_str = "0";
};
-72
View File
@@ -4,41 +4,12 @@
#include <QScrollBar>
#include <QVBoxLayout>
#include <QWidget>
#include <QtConcurrent>
#include <cstdio>
#include <sstream>
#include "common/params.h"
#include "common/swaglog.h"
#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"
std::string executeCommand(const char* cmd) {
std::array<char, 128> buffer{};
std::ostringstream result;
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
if (!pipe) {
LOGW("Failed to open pipe");
throw std::runtime_error("popen() failed!");
}
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
result << buffer.data();
}
return result.str();
}
// The format is intentional!
QString text = QString("%1\n%2\n%3\n%4\n%5\n%6\n%7")
.arg(" ________________________________________")
.arg("| |")
.arg("| " + QObject::tr("Update downloaded. Ready to reboot.") + " |")
.arg("| |")
.arg("| " + QObject::tr("Update: Check and Download Update") + " |")
.arg("| " + QObject::tr("Reboot: Reboot Device") + " |")
.arg("|________________________________________|");
int main(int argc, char *argv[]) {
initApp(argc, argv);
QApplication a(argc, argv);
@@ -51,7 +22,6 @@ int main(int argc, char *argv[]) {
QLabel *label = new QLabel(argv[1]);
label->setWordWrap(true);
label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
label->setAlignment(Qt::AlignTop | Qt::AlignLeft);
ScrollView *scroll = new ScrollView(label);
scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
main_layout->addWidget(scroll, 0, 0, Qt::AlignTop);
@@ -62,54 +32,16 @@ int main(int argc, char *argv[]) {
});
QPushButton *btn = new QPushButton();
QPushButton *update_btn = new QPushButton();
update_btn->setText(QObject::tr("Update"));
#ifdef __aarch64__
btn->setText(QObject::tr("Reboot"));
QFutureWatcher<void> watcher;
QObject::connect(btn, &QPushButton::clicked, [=]() {
Hardware::reboot();
});
QObject::connect(update_btn, &QPushButton::clicked, [=, &watcher]() {
btn->setEnabled(false);
update_btn->setEnabled(false);
update_btn->setText(QObject::tr("Updating..."));
const std::string git_branch = Params().get("GitBranch");
const std::string git_remote = Params().get("GitRemote");
const std::string to_home_dir = "cd /data/openpilot";
const std::string check_remote = "git remote | grep origin-update";
const std::string reset_remote = "git remote remove origin-update && git remote add origin-update " + git_remote;
const std::string add_remote = "git remote add origin-update " + git_remote;
const std::string fetch_remote = "git fetch origin-update " + git_branch;
const std::string reset_branch = "git reset --hard origin-update/" + git_branch + " 2>&1";
std::string remote_cmd = add_remote;
if (!std::system((to_home_dir + " && " + check_remote).c_str())) {
remote_cmd = reset_remote;
}
const std::string cmd = to_home_dir + "; " + remote_cmd + "; " + fetch_remote + "; " + reset_branch;
QFuture<void> future = QtConcurrent::run([=]() {
label->clear();
std::string output = executeCommand(cmd.c_str());
//LOGW("CHECK OUTPUT PLS\n%s", output.c_str());
QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection,
Q_ARG(QString, QString::fromStdString(output) + text));
});
QObject::connect(&watcher, &QFutureWatcher<void>::finished, [=]() {
btn->setEnabled(true);
update_btn->setEnabled(true);
update_btn->setText(QObject::tr("Update"));
});
watcher.setFuture(future);
});
#else
update_btn->setEnabled(false);
btn->setText(QObject::tr("Exit"));
QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit);
#endif
main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom);
main_layout->addWidget(update_btn, 0, 0, Qt::AlignLeft | Qt::AlignBottom);
window.setStyleSheet(R"(
* {
@@ -126,10 +58,6 @@ int main(int argc, char *argv[]) {
border-radius: 20px;
margin-right: 40px;
}
QPushButton:disabled {
color: #33FFFFFF;
border: 2px solid #33FFFFFF;
}
)");
return a.exec();
+23 -93
View File
@@ -29,54 +29,38 @@ QString getBrand() {
return QObject::tr("sunnypilot");
}
QString getUserAgent(bool sunnylink) {
return (sunnylink ? "sunnypilot-" : "openpilot-") + getVersion();
QString getUserAgent() {
return "openpilot-" + getVersion();
}
std::optional<QString> getParamIgnoringDefault(const std::string &param_name, const std::string &default_value) {
std::string value = Params().get(param_name);
if (!value.empty() && value != default_value)
return QString::fromStdString(value);
return {};
}
std::optional<QString> getDongleId() {
std::string id = Params().get("DongleId");
if (!id.empty() && (id != "UnregisteredDevice")) {
return QString::fromStdString(id);
} else {
return {};
}
return getParamIgnoringDefault("DongleId", "UnregisteredDevice");
}
std::optional<QString> getSunnylinkDongleId() {
std::string id = Params().get("SunnylinkDongleId");
QMap<QString, QString> getFromJsonFile(const QString &path) {
QFile f(path);
f.open(QIODevice::ReadOnly | QIODevice::Text);
QString val = f.readAll();
if (!id.empty() && (id != "UnregisteredDevice")) {
return QString::fromStdString(id);
} else {
return {};
QJsonObject obj = QJsonDocument::fromJson(val.toUtf8()).object();
QMap<QString, QString> map;
for (auto key : obj.keys()) {
map[key] = obj[key].toString();
}
return map;
}
QMap<QString, QString> getSupportedLanguages() {
QFile f(":/languages.json");
f.open(QIODevice::ReadOnly | QIODevice::Text);
QString val = f.readAll();
QJsonObject obj = QJsonDocument::fromJson(val.toUtf8()).object();
QMap<QString, QString> map;
for (auto key : obj.keys()) {
map[key] = obj[key].toString();
}
return map;
}
QMap<QString, QString> getCarNames() {
QFile f("/data/openpilot/selfdrive/car/sunnypilot_carname.json");
f.open(QIODevice::ReadOnly | QIODevice::Text);
QString val = f.readAll();
QJsonObject obj = QJsonDocument::fromJson(val.toUtf8()).object();
QMap<QString, QString> map;
for (auto key : obj.keys()) {
map[key] = obj[key].toString();
}
return map;
return getFromJsonFile(":/languages.json");
}
QString timeAgo(const QDateTime &date) {
@@ -179,60 +163,6 @@ QPixmap loadPixmap(const QString &fileName, const QSize &size, Qt::AspectRatioMo
}
}
void drawRoundedRect(QPainter &painter, const QRectF &rect, qreal xRadiusTop, qreal yRadiusTop, qreal xRadiusBottom, qreal yRadiusBottom){
qreal w_2 = rect.width() / 2;
qreal h_2 = rect.height() / 2;
xRadiusTop = 100 * qMin(xRadiusTop, w_2) / w_2;
yRadiusTop = 100 * qMin(yRadiusTop, h_2) / h_2;
xRadiusBottom = 100 * qMin(xRadiusBottom, w_2) / w_2;
yRadiusBottom = 100 * qMin(yRadiusBottom, h_2) / h_2;
qreal x = rect.x();
qreal y = rect.y();
qreal w = rect.width();
qreal h = rect.height();
qreal rxx2Top = w*xRadiusTop/100;
qreal ryy2Top = h*yRadiusTop/100;
qreal rxx2Bottom = w*xRadiusBottom/100;
qreal ryy2Bottom = h*yRadiusBottom/100;
QPainterPath path;
path.arcMoveTo(x, y, rxx2Top, ryy2Top, 180);
path.arcTo(x, y, rxx2Top, ryy2Top, 180, -90);
path.arcTo(x+w-rxx2Top, y, rxx2Top, ryy2Top, 90, -90);
path.arcTo(x+w-rxx2Bottom, y+h-ryy2Bottom, rxx2Bottom, ryy2Bottom, 0, -90);
path.arcTo(x, y+h-ryy2Bottom, rxx2Bottom, ryy2Bottom, 270, -90);
path.closeSubpath();
painter.drawPath(path);
}
QColor interpColor(float xv, std::vector<float> xp, std::vector<QColor> fp) {
assert(xp.size() == fp.size());
int N = xp.size();
int hi = 0;
while (hi < N and xv > xp[hi]) hi++;
int low = hi - 1;
if (hi == N && xv > xp[low]) {
return fp[fp.size() - 1];
} else if (hi == 0){
return fp[0];
} else {
return QColor(
(xv - xp[low]) * (fp[hi].red() - fp[low].red()) / (xp[hi] - xp[low]) + fp[low].red(),
(xv - xp[low]) * (fp[hi].green() - fp[low].green()) / (xp[hi] - xp[low]) + fp[low].green(),
(xv - xp[low]) * (fp[hi].blue() - fp[low].blue()) / (xp[hi] - xp[low]) + fp[low].blue(),
(xv - xp[low]) * (fp[hi].alpha() - fp[low].alpha()) / (xp[hi] - xp[low]) + fp[low].alpha());
}
}
static QHash<QString, QByteArray> load_bootstrap_icons() {
QHash<QString, QByteArray> icons;
@@ -297,4 +227,4 @@ void ParamWatcher::fileChanged(const QString &path) {
void ParamWatcher::addParam(const QString &param_name) {
watcher->addPath(QString::fromStdString(params.getParamPath(param_name.toStdString())));
}
}
+9 -6
View File
@@ -13,13 +13,19 @@
#include "cereal/gen/cpp/car.capnp.h"
#include "common/params.h"
#ifdef SUNNYPILOT
#define RETURN_IF_SUNNYPILOT return;
#else
#define RETURN_IF_SUNNYPILOT // Do nothing
#endif
QString getVersion();
QString getBrand();
QString getUserAgent(bool sunnylink = false);
QString getUserAgent();
std::optional<QString> getParamIgnoringDefault(const std::string &param_name, const std::string &default_value);
std::optional<QString> getDongleId();
std::optional<QString> getSunnylinkDongleId();
QMap<QString, QString> getFromJsonFile(const QString &path);
QMap<QString, QString> getSupportedLanguages();
QMap<QString, QString> getCarNames();
void setQtSurfaceFormat();
void sigTermHandler(int s);
QString timeAgo(const QDateTime &date);
@@ -28,9 +34,6 @@ void initApp(int argc, char *argv[], bool disable_hidpi = true);
QWidget* topWidget(QWidget* widget);
QPixmap loadPixmap(const QString &fileName, const QSize &size = {}, Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio);
QPixmap bootstrapPixmap(const QString &id);
void drawRoundedRect(QPainter &painter, const QRectF &rect, qreal xRadiusTop, qreal yRadiusTop, qreal xRadiusBottom, qreal yRadiusBottom);
QColor interpColor(float xv, std::vector<float> xp, std::vector<QColor> fp);
bool hasLongitudinalControl(const cereal::CarParams::Reader &car_params);
struct InterFont : public QFont {
+4
View File
@@ -24,7 +24,11 @@
#include "msgq/visionipc/visionipc_client.h"
#include "system/camerad/cameras/camera_common.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/ui.h"
#else
#include "selfdrive/ui/ui.h"
#endif
const int FRAME_BUFFER_SIZE = 5;
static_assert(FRAME_BUFFER_SIZE <= YUV_BUFFER_COUNT);
+3 -67
View File
@@ -2,20 +2,10 @@
#include <QPainter>
#include <QStyleOption>
QFrame *horizontal_line(QWidget *parent) {
QFrame *line = new QFrame(parent);
line->setFrameShape(QFrame::StyledPanel);
line->setStyleSheet(R"(
border-width: 2px;
border-bottom-style: solid;
border-color: gray;
)");
line->setFixedHeight(10);
return line;
}
#include <selfdrive/ui/qt/util.h>
AbstractControl::AbstractControl(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) {
RETURN_IF_SUNNYPILOT
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setMargin(0);
@@ -36,7 +26,7 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons
// title
title_label = new QPushButton(title);
title_label->setFixedHeight(120);
title_label->setStyleSheet("font-size: 50px; font-weight: 450; text-align: left; border: none;");
title_label->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left; border: none;");
hlayout->addWidget(title_label, 1);
// value next to control button
@@ -74,51 +64,6 @@ void AbstractControl::hideEvent(QHideEvent *e) {
}
}
SPAbstractControl::SPAbstractControl(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setMargin(0);
hlayout = new QHBoxLayout;
hlayout->setMargin(0);
hlayout->setSpacing(0);
// title
if (!title.isEmpty()) {
title_label = new QPushButton(title);
title_label->setFixedHeight(120);
title_label->setStyleSheet("font-size: 50px; font-weight: 450; text-align: left; border: none;");
main_layout->addWidget(title_label, 1);
connect(title_label, &QPushButton::clicked, [=]() {
if (!description->isVisible()) {
emit showDescriptionEvent();
}
if (!description->text().isEmpty()) {
description->setVisible(!description->isVisible());
}
});
} else {
main_layout->addSpacing(20);
}
main_layout->addLayout(hlayout);
main_layout->addSpacing(2);
// description
description = new QLabel(desc);
description->setContentsMargins(0, 20, 40, 20);
description->setStyleSheet("font-size: 40px; color: grey");
description->setWordWrap(true);
description->setVisible(false);
main_layout->addWidget(description);
main_layout->addStretch();
}
void SPAbstractControl::hideEvent(QHideEvent *e) {
}
// controls
ButtonControl::ButtonControl(const QString &title, const QString &text, const QString &desc, QWidget *parent) : AbstractControl(title, desc, "", parent) {
@@ -178,15 +123,6 @@ ParamControl::ParamControl(const QString &param, const QString &title, const QSt
: ToggleControl(title, desc, icon, false, parent) {
key = param.toStdString();
QObject::connect(this, &ParamControl::toggleFlipped, this, &ParamControl::toggleClicked);
hlayout->removeWidget(&toggle);
hlayout->insertWidget(0, &toggle);
hlayout->removeWidget(this->icon_label);
this->icon_pixmap = QPixmap(icon).scaledToWidth(20, Qt::SmoothTransformation);
this->icon_label->setPixmap(this->icon_pixmap);
this->icon_label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
hlayout->insertWidget(1, this->icon_label);
}
void ParamControl::toggleClicked(bool state) {
+13 -324
View File
@@ -1,6 +1,5 @@
#pragma once
#include <optional>
#include <string>
#include <vector>
@@ -15,8 +14,6 @@
#include "selfdrive/ui/qt/widgets/input.h"
#include "selfdrive/ui/qt/widgets/toggle.h"
QFrame *horizontal_line(QWidget *parent = nullptr);
class ElidedLabel : public QLabel {
Q_OBJECT
@@ -24,10 +21,6 @@ public:
explicit ElidedLabel(QWidget *parent = 0);
explicit ElidedLabel(const QString &text, QWidget *parent = 0);
void setColor(const QString &color) {
setStyleSheet("QLabel { color : " + color + "; }");
}
signals:
void clicked();
@@ -55,11 +48,8 @@ public:
title_label->setText(title);
}
void setValue(const QString &val, std::optional<QString> color = std::nullopt) {
void setValue(const QString &val) {
value->setText(val);
if (color.has_value()) {
value->setColor(color.value());
}
}
const QString getDescription() {
@@ -74,10 +64,6 @@ public slots:
description->setVisible(true);
}
void hideDescription() {
description->setVisible(false);
}
signals:
void showDescriptionEvent();
@@ -117,7 +103,6 @@ public:
ButtonControl(const QString &title, const QString &text, const QString &desc = "", QWidget *parent = nullptr);
inline void setText(const QString &text) { btn.setText(text); }
inline QString text() const { return btn.text(); }
inline void click() { btn.click(); }
signals:
void clicked();
@@ -181,8 +166,6 @@ public:
refresh();
}
bool isToggled() { return params.getBool(key); }
private:
void toggleClicked(bool state);
void setIcon(bool state) {
@@ -200,71 +183,29 @@ private:
bool store_confirm = false;
};
class SPAbstractControl : public QFrame {
Q_OBJECT
public:
void setDescription(const QString &desc) {
if (description) description->setText(desc);
}
void setTitle(const QString &title) {
title_label->setText(title);
}
const QString getDescription() {
return description->text();
}
public slots:
void showDescription() {
description->setVisible(true);
}
void hideDescription() {
description->setVisible(false);
}
signals:
void showDescriptionEvent();
protected:
SPAbstractControl(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr);
void hideEvent(QHideEvent *e) override;
QHBoxLayout *hlayout;
QPushButton *title_label;
private:
QLabel *description = nullptr;
};
class ButtonParamControl : public SPAbstractControl {
class ButtonParamControl : public AbstractControl {
Q_OBJECT
public:
ButtonParamControl(const QString &param, const QString &title, const QString &desc, const QString &icon,
const std::vector<QString> &button_texts, const int minimum_button_width = 300) : SPAbstractControl(title, desc, icon), button_texts(button_texts) {
const std::vector<QString> &button_texts, const int minimum_button_width = 225) : AbstractControl(title, desc, icon) {
const QString style = R"(
QPushButton {
border-radius: 20px;
font-size: 50px;
font-weight: 450;
height:150px;
border-radius: 50px;
font-size: 40px;
font-weight: 500;
height:100px;
padding: 0 25 0 25;
color: #FFFFFF;
color: #E4E4E4;
background-color: #393939;
}
QPushButton:pressed {
background-color: #4a4a4a;
}
QPushButton:checked:enabled {
background-color: #696868;
background-color: #33Ab4C;
}
QPushButton:disabled {
color: #33FFFFFF;
}
QPushButton:checked:disabled {
background-color: #121212;
color: #33FFFFFF;
color: #33E4E4E4;
}
)";
key = param.toStdString();
@@ -278,16 +219,12 @@ public:
button->setChecked(i == value);
button->setStyleSheet(style);
button->setMinimumWidth(minimum_button_width);
if (i == 0) hlayout->addSpacing(2);
hlayout->addWidget(button);
button_group->addButton(button, i);
}
hlayout->setAlignment(Qt::AlignLeft);
QObject::connect(button_group, QOverload<int>::of(&QButtonGroup::buttonClicked), [=](int id) {
params.put(key, std::to_string(id));
emit buttonToggled(id);
});
}
@@ -295,9 +232,6 @@ public:
for (auto btn : button_group->buttons()) {
btn->setEnabled(enable);
}
button_group_enabled = enable;
update();
}
void setCheckedButton(int id) {
@@ -306,14 +240,6 @@ public:
void refresh() {
int value = atoi(params.get(key).c_str());
if (value >= button_texts.size()) {
value = button_texts.size() - 1;
}
if (value < 0) {
value = 0;
}
button_group->button(value)->setChecked(true);
}
@@ -321,59 +247,16 @@ public:
refresh();
}
void setButton(QString param) {
key = param.toStdString();
int value = atoi(params.get(key).c_str());
for (int i = 0; i < button_group->buttons().size(); i++) {
button_group->buttons()[i]->setChecked(i == value);
}
}
void setDisabledSelectedButton(std::string val) {
int value = atoi(val.c_str());
for (int i = 0; i < button_group->buttons().size(); i++) {
button_group->buttons()[i]->setEnabled(i != value);
}
}
protected:
void paintEvent(QPaintEvent *event) override {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
// Calculate the total width and height for the background rectangle
int w = 0;
int h = 150;
for (int i = 0; i < hlayout->count(); ++i) {
QPushButton *button = qobject_cast<QPushButton *>(hlayout->itemAt(i)->widget());
if (button) {
w += button->width();
}
}
// Draw the rectangle
QRect rect(0 + 2, h - 24, w, h);
p.setPen(QPen(QColor(button_group_enabled ? "#696868" : "#121212"), 3));
p.drawRoundedRect(rect, 20, 20);
}
signals:
void buttonToggled(int btn_id);
private:
std::string key;
Params params;
QButtonGroup *button_group;
std::vector<QString> button_texts;
bool button_group_enabled = true;
};
class ListWidget : public QWidget {
Q_OBJECT
public:
explicit ListWidget(QWidget *parent = 0, const bool split_line = true) : QWidget(parent), _split_line(split_line), outer_layout(this) {
explicit ListWidget(QWidget *parent = 0) : QWidget(parent), outer_layout(this) {
outer_layout.setMargin(0);
outer_layout.setSpacing(0);
outer_layout.addLayout(&inner_layout);
@@ -385,30 +268,13 @@ class ListWidget : public QWidget {
inline void addItem(QLayout *layout) { inner_layout.addLayout(layout); }
inline void setSpacing(int spacing) { inner_layout.setSpacing(spacing); }
inline void AddWidgetAt(const int index, QWidget *new_widget) { inner_layout.insertWidget(index, new_widget); }
inline void RemoveWidgetAt(const int index) {
if (QLayoutItem* item; (item = inner_layout.takeAt(index)) != nullptr) {
if(item->widget()) delete item->widget();
delete item;
}
}
inline void ReplaceOrAddWidget(QWidget *old_widget, QWidget *new_widget) {
if (const int index = inner_layout.indexOf(old_widget); index != -1) {
RemoveWidgetAt(index);
AddWidgetAt(index, new_widget);
} else {
addItem(new_widget);
}
}
private:
void paintEvent(QPaintEvent *) override {
QPainter p(this);
p.setPen(Qt::gray);
for (int i = 0; i < inner_layout.count() - 1; ++i) {
QWidget *widget = inner_layout.itemAt(i)->widget();
if ((widget == nullptr || widget->isVisible()) && _split_line) {
if (widget == nullptr || widget->isVisible()) {
QRect r = inner_layout.itemAt(i)->geometry();
int bottom = r.bottom() + inner_layout.spacing() / 2;
p.drawLine(r.left() + 40, bottom, r.right() - 40, bottom);
@@ -417,8 +283,6 @@ private:
}
QVBoxLayout outer_layout;
QVBoxLayout inner_layout;
bool _split_line;
};
// convenience class for wrapping layouts
@@ -430,178 +294,3 @@ public:
setLayout(l);
}
};
class SPOptionControl : public SPAbstractControl {
Q_OBJECT
private:
struct MinMaxValue {
int min_value;
int max_value;
};
public:
SPOptionControl(const QString &param, const QString &title, const QString &desc, const QString &icon,
const MinMaxValue &range, const int per_value_change = 1) : _title(title), SPAbstractControl(title, desc, icon) {
const QString style = R"(
QPushButton {
border-radius: 20px;
font-size: 60px;
font-weight: 500;
width: 150px;
height: 150px;
padding: -3 25 3 25;
color: #FFFFFF;
font-weight: bold;
}
QPushButton:pressed {
color: #5C5C5C;
}
QPushButton:disabled {
color: #5C5C5C;
}
)";
label.setStyleSheet(label_enabled_style);
label.setFixedWidth(300);
label.setAlignment(Qt::AlignCenter);
const std::vector<QString> button_texts{"", ""};
key = param.toStdString();
value = atoi(params.get(key).c_str());
button_group = new QButtonGroup(this);
button_group->setExclusive(true);
for (int i = 0; i < button_texts.size(); i++) {
QPushButton *button = new QPushButton(button_texts[i], this);
button->setStyleSheet(style + ((i == 0) ? "QPushButton { text-align: left; }" :
"QPushButton { text-align: right; }"));
hlayout->addWidget(button, 0, ((i == 0) ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignVCenter);
if (i == 0) {
hlayout->addWidget(&label, 0, Qt::AlignCenter);
}
button_group->addButton(button, i);
QObject::connect(button, &QPushButton::clicked, [=]() {
int change_value = (i == 0) ? -per_value_change : per_value_change;
key = param.toStdString();
value = atoi(params.get(key).c_str());
value += change_value;
value = std::clamp(value, range.min_value, range.max_value);
params.put(key, QString::number(value).toStdString());
button_group->button(0)->setEnabled(!(value <= range.min_value));
button_group->button(1)->setEnabled(!(value >= range.max_value));
updateLabels();
if (request_update) {
emit updateOtherToggles();
}
});
}
hlayout->setAlignment(Qt::AlignLeft);
}
void setUpdateOtherToggles(bool _update) {
request_update = _update;
}
inline void setLabel(const QString &text) { label.setText(text); }
void setEnabled(bool enabled) {
for (auto btn : button_group->buttons()) {
btn->setEnabled(enabled);
}
label.setEnabled(enabled);
label.setStyleSheet(enabled ? label_enabled_style : label_disabled_style);
button_enabled = enabled;
update();
}
protected:
void paintEvent(QPaintEvent *event) override {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
// Calculate the total width and height for the background rectangle
int w = 0;
int h = 150;
for (int i = 0; i < hlayout->count(); ++i) {
QWidget *widget = qobject_cast<QWidget *>(hlayout->itemAt(i)->widget());
if (widget) {
w += widget->width();
}
}
// Draw the rectangle
QRect rect(0, !_title.isEmpty() ? (h - 24) : 20, w, h);
p.setBrush(QColor(button_enabled ? "#b24a4a4a" : "#121212")); // Background color
p.setPen(QPen(Qt::NoPen));
p.drawRoundedRect(rect, 20, 20);
}
signals:
void updateLabels();
void updateOtherToggles();
private:
std::string key;
int value;
QButtonGroup *button_group;
QLabel label;
Params params;
std::map<QString, QString> option_label = {};
bool request_update = false;
QString _title = "";
const QString label_enabled_style = "font-size: 50px; font-weight: 450; color: #FFFFFF;";
const QString label_disabled_style = "font-size: 50px; font-weight: 450; color: #5C5C5C;";
bool button_enabled = true;
};
class SubPanelButton : public QPushButton {
Q_OBJECT
public:
SubPanelButton(const QString &text, const int minimum_button_width = 800, QWidget *parent = nullptr) : QPushButton(text, parent) {
const QString buttonStyle = R"(
QPushButton {
border-radius: 20px;
font-size: 50px;
font-weight: 450;
height: 150px;
padding: 0 25px 0 25px;
color: #FFFFFF;
}
QPushButton:enabled {
background-color: #393939;
}
QPushButton:pressed {
background-color: #4A4A4A;
}
QPushButton:disabled {
background-color: #121212;
color: #5C5C5C;
}
)";
setStyleSheet(buttonStyle);
setFixedWidth(minimum_button_width);
}
};
class PanelBackButton : public QPushButton {
Q_OBJECT
public:
PanelBackButton(const QString &label = "Back", QWidget *parent = nullptr) : QPushButton(label, parent) {
setObjectName("back_btn");
setFixedSize(400, 100);
}
};
-8
View File
@@ -47,11 +47,3 @@ ScrollView::ScrollView(QWidget *w, QWidget *parent) : QScrollArea(parent) {
void ScrollView::hideEvent(QHideEvent *e) {
verticalScrollBar()->setValue(0);
}
void ScrollView::setLastScrollPosition() {
lastScrollPosition = verticalScrollBar()->value();
}
void ScrollView::restoreScrollPosition() {
verticalScrollBar()->setValue(lastScrollPosition);
}
-7
View File
@@ -9,11 +9,4 @@ public:
explicit ScrollView(QWidget *w = nullptr, QWidget *parent = nullptr);
protected:
void hideEvent(QHideEvent *e) override;
public slots:
void setLastScrollPosition();
void restoreScrollPosition();
private:
int lastScrollPosition = 0;
};
+6
View File
@@ -3,7 +3,13 @@
#include <QPushButton>
#include "system/hardware/hw.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#define ToggleControl ToggleControlSP
#define ButtonControl ButtonControlSP
#else
#include "selfdrive/ui/qt/widgets/controls.h"
#endif
// SSH enable toggle
class SshToggle : public ToggleControl {
@@ -1,25 +0,0 @@
#pragma once
#include <QJsonDocument>
#include <QLabel>
class DriveStats : public QFrame {
Q_OBJECT
public:
explicit DriveStats(QWidget* parent = 0);
private:
void showEvent(QShowEvent *event) override;
void updateStats();
inline QString getDistanceUnit() const { return metric_ ? tr("KM") : tr("Miles"); }
bool metric_;
QJsonDocument stats_;
struct StatsLabels {
QLabel *routes, *distance, *distance_unit, *hours;
} all_, week_;
private slots:
void parseResponse(const QString &response, bool success);
};
+2 -2
View File
@@ -75,9 +75,9 @@ void Toggle::setEnabled(bool value) {
enabled = value;
if (value) {
circleColor.setRgb(0xfafafa);
green.setRgb(0x1e79e8);
green.setRgb(0x33ab4c);
} else {
circleColor.setRgb(0x888888);
green.setRgb(0x125db8);
green.setRgb(0x227722);
}
}
+1 -2
View File
@@ -23,14 +23,13 @@ public:
update();
}
bool getEnabled();
void setEnabled(bool value);
virtual void setEnabled(bool value);
protected:
void paintEvent(QPaintEvent*) override;
void mouseReleaseEvent(QMouseEvent*) override;
void enterEvent(QEvent*) override;
private:
QColor circleColor;
QColor green;
bool enabled = true;
+4
View File
@@ -4,7 +4,11 @@
#include <QStackedLayout>
#include <QWidget>
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/ui.h"
#else
#include "selfdrive/ui/ui.h"
#endif
class WiFiPromptWidget : public QFrame {
Q_OBJECT
+5 -8
View File
@@ -4,16 +4,18 @@
#include "system/hardware/hw.h"
MainWindow::MainWindow(QWidget *parent) : QWidget(parent) {
MainWindow::MainWindow(QWidget* parent, HomeWindow* hw, SettingsWindow* sw, OnboardingWindow* ow)
: QWidget(parent),
homeWindow(hw ? hw : new HomeWindow(this)),
settingsWindow(sw ? sw : new SettingsWindow(this)),
onboardingWindow(ow ? ow : new OnboardingWindow(this)) {
main_layout = new QStackedLayout(this);
main_layout->setMargin(0);
homeWindow = new HomeWindow(this);
main_layout->addWidget(homeWindow);
QObject::connect(homeWindow, &HomeWindow::openSettings, this, &MainWindow::openSettings);
QObject::connect(homeWindow, &HomeWindow::closeSettings, this, &MainWindow::closeSettings);
settingsWindow = new SettingsWindow(this);
main_layout->addWidget(settingsWindow);
QObject::connect(settingsWindow, &SettingsWindow::closeSettings, this, &MainWindow::closeSettings);
QObject::connect(settingsWindow, &SettingsWindow::reviewTrainingGuide, [=]() {
@@ -24,7 +26,6 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) {
homeWindow->showDriverView(true);
});
onboardingWindow = new OnboardingWindow(this);
main_layout->addWidget(onboardingWindow);
QObject::connect(onboardingWindow, &OnboardingWindow::onboardingDone, [=]() {
main_layout->setCurrentWidget(homeWindow);
@@ -75,10 +76,6 @@ void MainWindow::closeSettings() {
if (uiState()->scene.started) {
homeWindow->showSidebar(false);
// Map is always shown when using navigate on openpilot
if (uiState()->scene.navigate_on_openpilot_deprecated) {
homeWindow->showMapPanel(true);
}
}
}
+8 -5
View File
@@ -11,15 +11,18 @@ class MainWindow : public QWidget {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
explicit MainWindow(QWidget *parent = nullptr) : MainWindow(parent, nullptr, nullptr, nullptr) {}
protected:
explicit MainWindow(QWidget* parent, HomeWindow* hw = nullptr, SettingsWindow* sw = nullptr, OnboardingWindow* ow = nullptr);
HomeWindow *homeWindow;
SettingsWindow *settingsWindow;
OnboardingWindow *onboardingWindow;
virtual void closeSettings();
private:
bool eventFilter(QObject *obj, QEvent *event) override;
void openSettings(int index = 0, const QString &param = "");
void closeSettings();
QStackedLayout *main_layout;
HomeWindow *homeWindow;
SettingsWindow *settingsWindow;
OnboardingWindow *onboardingWindow;
};
+51 -24
View File
@@ -1,36 +1,63 @@
widgets_src = [
"qt/offroad/sunnypilot/custom_offsets_settings.cc",
"qt/offroad/sunnypilot/display_settings.cc",
"qt/offroad/sunnypilot/lane_change_settings.cc",
"qt/offroad/sunnypilot/mads_settings.cc",
"qt/offroad/sunnypilot/models_fetcher.cc",
"qt/offroad/sunnypilot/monitoring_settings.cc",
"qt/offroad/sunnypilot/osm_settings.cc",
"qt/offroad/sunnypilot/software_settings_sp.cc",
"qt/offroad/sunnypilot/speed_limit_control_settings.cc",
"qt/offroad/sunnypilot/speed_limit_policy_settings.cc",
"qt/offroad/sunnypilot/speed_limit_warning_settings.cc",
"qt/offroad/sunnypilot/sunnypilot_settings.cc",
"qt/offroad/sunnypilot/sunnylink_settings.cc",
"qt/offroad/sunnypilot/trips_settings.cc",
"qt/offroad/sunnypilot/vehicle_settings.cc",
"qt/offroad/sunnypilot/visuals_settings.cc",
"qt/widgets/sunnypilot/drive_stats.cc"
"sunnypilot/ui.cc",
"sunnypilot/qt/window.cc",
"sunnypilot/qt/request_repeater.cc",
"sunnypilot/qt/network/networking.cc",
"sunnypilot/qt/offroad/settings/display_settings.cc",
"sunnypilot/qt/offroad/settings/sunnypilot_settings.cc",
"sunnypilot/qt/offroad/settings/vehicle_settings.cc",
"sunnypilot/qt/offroad/settings/visuals_settings.cc",
"sunnypilot/qt/offroad/settings/trips_settings.cc",
"sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.cc",
"sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.cc",
"sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.cc",
"sunnypilot/qt/offroad/settings/monitoring_settings.cc",
"sunnypilot/qt/offroad/settings/osm_settings.cc",
"sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.cc",
"sunnypilot/qt/widgets/drive_stats.cc",
"sunnypilot/qt/offroad/settings/software_settings.cc",
"sunnypilot/qt/offroad/settings/osm/models_fetcher.cc",
"sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_warning_settings.cc",
"sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.cc",
"sunnypilot/qt/offroad/settings/sunnylink_settings.cc",
"sunnypilot/qt/widgets/controls.cc",
"sunnypilot/qt/widgets/scrollview.cc",
"sunnypilot/qt/widgets/toggle.cc"
]
sp_maps_widgets_src = [
"sunnypilot/qt/maps/map.cc"
]
sp_qt_util = [
# "#selfdrive/ui/sunnypilot/qt/api.cc",
"#selfdrive/ui/sunnypilot/qt/util.cc",
]
network_src = [
"qt/network/sunnylink/services/base_device_service.cc",
"qt/network/sunnylink/services/role_service.cc",
"qt/network/sunnylink/services/user_service.cc",
"qt/network/sunnylink/sunnylink_client.cc"
"sunnypilot/qt/network/sunnylink/sunnylink_client.cc",
"sunnypilot/qt/network/sunnylink/services/base_device_service.cc",
"sunnypilot/qt/network/sunnylink/services/role_service.cc",
"sunnypilot/qt/network/sunnylink/services/user_service.cc"
]
qt_src = [
"qt/onroad_settings.cc",
"qt/onroad_settings_panel.cc"
"sunnypilot/qt/api.cc",
"sunnypilot/qt/home.cc",
"sunnypilot/qt/offroad_home.cc",
"sunnypilot/qt/sidebar.cc",
"sunnypilot/qt/offroad/settings/onboarding.cc",
"sunnypilot/qt/offroad/settings/device_panel.cc",
"sunnypilot/qt/offroad/settings/settings.cc",
"sunnypilot/qt/onroad/buttons.cc",
"sunnypilot/qt/onroad/onroad_home.cc",
"sunnypilot/qt/onroad/onroad_settings.cc",
"sunnypilot/qt/onroad/annotated_camera.cc",
"sunnypilot/qt/onroad/onroad_settings_panel.cc",
"sunnypilot/qt/onroad/developer_ui/developer_ui.cc",
]
sp_widgets_src = widgets_src + network_src
sp_qt_src = qt_src
Export('sp_widgets_src', 'sp_qt_src')
Export('sp_widgets_src', 'sp_maps_widgets_src', 'sp_qt_src', "sp_qt_util")
+212
View File
@@ -0,0 +1,212 @@
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#include "selfdrive/ui/sunnypilot/qt/api.h"
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/aes.h>
#include <QApplication>
#include <QJsonDocument>
#include <string>
#include <common/swaglog.h>
#include "util.h"
#include "common/util.h"
#include "system/hardware/hw.h"
#include "selfdrive/ui/qt/util.h"
namespace SunnylinkApi {
QString create_jwt(const QJsonObject &payloads, int expiry, bool sunnylink) {
QJsonObject header = {{"alg", "RS256"}};
auto t = QDateTime::currentSecsSinceEpoch();
auto dongle_id = sunnylink ? getSunnylinkDongleId() : getDongleId();
QJsonObject payload = {{"identity", dongle_id.value_or("")}, {"nbf", t}, {"iat", t}, {"exp", t + expiry}};
for (auto it = payloads.begin(); it != payloads.end(); ++it) {
payload.insert(it.key(), it.value());
}
auto b64_opts = QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals;
QString jwt = QJsonDocument(header).toJson(QJsonDocument::Compact).toBase64(b64_opts) + '.' +
QJsonDocument(payload).toJson(QJsonDocument::Compact).toBase64(b64_opts);
auto hash = QCryptographicHash::hash(jwt.toUtf8(), QCryptographicHash::Sha256);
return jwt + "." + CommaApi::rsa_sign(hash).toBase64(b64_opts);
}
void derive_aes_key_iv_from_rsa(EVP_PKEY *rsa_key, unsigned char *aes_key, unsigned char *aes_iv) {
unsigned char hash[SHA256_DIGEST_LENGTH];
size_t pub_len;
unsigned char *pub_key;
// Convert RSA key to public key in DER format for simplicity
pub_len = i2d_PublicKey(rsa_key, NULL);
pub_key = (unsigned char *)malloc(pub_len);
unsigned char *tmp = pub_key;
i2d_PublicKey(rsa_key, &tmp);
// Hash the public key to derive bytes for AES key and IV
SHA256(pub_key, pub_len, hash);
// Assuming AES-256-CBC, we need 32 bytes for the key and 16 for the IV
memcpy(aes_key, hash, 32); // First 32 bytes for AES key
memcpy(aes_iv, hash + 32 - 16, 16); // Last 16 bytes for AES IV
free(pub_key);
}
EVP_PKEY *load_public_key(const char *file) {
EVP_PKEY *key = NULL;
FILE *fp = fopen(file, "r");
if (fp) {
key = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
fclose(fp);
}
return key;
}
EVP_PKEY *load_private_key(const char *file) {
EVP_PKEY *key = NULL;
FILE *fp = fopen(file, "r");
if (fp) {
key = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
fclose(fp);
}
return key;
}
QByteArray rsa_encrypt(const QByteArray &data) {
EVP_PKEY *rsa_key = load_public_key(Path::rsa_pub_file().c_str()); // Load your RSA key
unsigned char aes_key[32], aes_iv[16];
derive_aes_key_iv_from_rsa(rsa_key, aes_key, aes_iv);
EVP_CIPHER_CTX *ctx;
if (!(ctx = EVP_CIPHER_CTX_new())) {
// Handle error: Allocate memory failed
return {};
}
if (EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, aes_key, aes_iv) != 1) {
qDebug() << "Failed to initialize encryption";
EVP_CIPHER_CTX_free(ctx);
return {};
}
int ciphertext_len;
int len = data.size();
unsigned char out[len + AES_BLOCK_SIZE];
auto *in = (unsigned char*) data.constData();
if (EVP_EncryptUpdate(ctx, out, &ciphertext_len, in, len) != 1) {
qDebug() << "Failed to update encryption";
EVP_CIPHER_CTX_free(ctx);
return {};
}
if (EVP_EncryptFinal_ex(ctx, out + ciphertext_len, &len) != 1) {
// Handle error: Encryption finalize failed
qDebug() << "Failed to finalize encryption";
EVP_CIPHER_CTX_free(ctx);
return {};
}
//qDebug() << "Encrypted data length: %d", ciphertext_len + len; // Print the length of encrypted data
EVP_CIPHER_CTX_free(ctx);
return {reinterpret_cast<char*>(out), ciphertext_len + len};
}
QByteArray rsa_decrypt(const QByteArray &data) {
EVP_PKEY *rsa_key = load_public_key(Path::rsa_pub_file().c_str()); // Load your RSA key
unsigned char aes_key[32], aes_iv[16];
derive_aes_key_iv_from_rsa(rsa_key, aes_key, aes_iv);
EVP_CIPHER_CTX *ctx;
if (!(ctx = EVP_CIPHER_CTX_new())) {
qDebug() << "Failed to allocate memory for EVP_CIPHER_CTX";
return {};
}
if (EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, aes_key, aes_iv) != 1) {
qDebug() << "Failed to initialize EVP";
EVP_CIPHER_CTX_free(ctx);
return {};
}
int len = data.size();
unsigned char out[len + AES_BLOCK_SIZE];
auto *in = (unsigned char*) data.constData();
if (EVP_DecryptUpdate(ctx, out, &len, in, len) != 1) {
qDebug() << "Failed to update decryption";
EVP_CIPHER_CTX_free(ctx);
return {};
}
int final_len;
if (EVP_DecryptFinal_ex(ctx, out + len, &final_len) != 1) {
qDebug() << "Failed to finalize decryption";
EVP_CIPHER_CTX_free(ctx);
return {};
}
EVP_CIPHER_CTX_free(ctx);
//qDebug() << "Decrypted data length: %d", len + final_len; // Print the length of decrypted data
return {reinterpret_cast<char*>(out), len + final_len};
}
} // namespace SunnylinkApi
void HttpRequestSP::sendRequest(const QString& requestURL, Method method, const QByteArray& payload) {
if (active()) {
return;
}
QNetworkRequest request = prepareRequest(requestURL);
if(!payload.isEmpty() && (method == Method::POST || method == Method::PUT)) {
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
}
switch (method) {
case Method::GET:
reply = nam()->get(request);
break;
case Method::DELETE:
reply = nam()->deleteResource(request);
break;
case Method::POST:
reply = nam()->post(request, payload);
break;
case Method::PUT:
reply = nam()->put(request, payload);
break;
}
networkTimer->start();
connect(reply, &QNetworkReply::finished, this, &HttpRequestSP::requestFinished);
}
+55
View File
@@ -0,0 +1,55 @@
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#pragma once
#include "selfdrive/ui/qt/api.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
#include "common/util.h"
namespace SunnylinkApi {
QByteArray rsa_encrypt(const QByteArray& data);
QByteArray rsa_decrypt(const QByteArray& data);
QString create_jwt(const QJsonObject& payloads = {}, int expiry = 3600, bool sunnylink = false);
}
class HttpRequestSP : public HttpRequest {
Q_OBJECT
public:
explicit HttpRequestSP(QObject* parent, bool create_jwt = true, int timeout = 20000, bool sunnylink = false) :
HttpRequest(parent, create_jwt, timeout), sunnylink(sunnylink) {}
using HttpRequest::sendRequest;
void sendRequest(const QString& requestURL, Method method, const QByteArray& payload);
private:
bool sunnylink;
protected:
[[nodiscard]] QString GetJwtToken() const override { return SunnylinkApi::create_jwt({}, 3600, sunnylink); }
[[nodiscard]] QString GetUserAgent() const override { return getUserAgent(sunnylink); }
};
@@ -0,0 +1,60 @@
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#pragma once
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonObject>
#include <QEventLoop>
class JsonFetcher {
public:
static QJsonObject getJsonFromURL(const QString &url) {
const auto qurl = QUrl(url);
QNetworkAccessManager manager;
const QNetworkRequest request(qurl);
QNetworkReply *reply = manager.get(request);
QEventLoop loop;
// Send GET request
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Failed to fetch data from URL: " << reply->errorString();
return QJsonObject();
}
const QByteArray responseData = reply->readAll();
const QJsonDocument doc = QJsonDocument::fromJson(responseData);
QJsonObject json = doc.object();
reply->deleteLater();
return json;
}
};
+80
View File
@@ -0,0 +1,80 @@
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#include "selfdrive/ui/sunnypilot/qt/home.h"
#include <QHBoxLayout>
#include <QMouseEvent>
#include <common/swaglog.h>
#include "selfdrive/ui/qt/offroad/experimental_mode.h"
#include "selfdrive/ui/qt/util.h"
// HomeWindowSP: the container for the offroad and onroad UIs
HomeWindowSP::HomeWindowSP(QWidget* parent) : HomeWindow(parent){
QObject::connect(onroad, &OnroadWindow::mapPanelRequested, this, [=] { sidebar->hide(); });
QObject::connect(onroad, &OnroadWindow::onroadSettingsPanelRequested, this, [=] { sidebar->hide(); });
}
void HomeWindowSP::showMapPanel(bool show) {
onroad->showMapPanel(show);
}
void HomeWindowSP::updateState(const UIState &s) { //OVERRIDE
HomeWindow::updateState(s);
uiStateSP()->scene.map_visible = onroad->isMapVisible();
uiStateSP()->scene.onroad_settings_visible = onroad->isOnroadSettingsVisible();
}
void HomeWindowSP::mousePressEvent(QMouseEvent* e) {
// We are not calling the parent for the time being because it only handles sidebar, and the sidebar code conflicts
// with ours as we turn off the sidebar after it was turned on by the parent when the tap happens beyond the 300px of the left.
// HomeWindow::mousePressEvent(e);
if (uiStateSP()->scene.started) {
if (uiStateSP()->scene.onroadScreenOff != -2) {
uiStateSP()->scene.touched2 = true;
QTimer::singleShot(500, []() { uiStateSP()->scene.touched2 = false; });
}
if (uiStateSP()->scene.button_auto_hide) {
uiStateSP()->scene.touch_to_wake = true;
uiStateSP()->scene.sleep_btn_fading_in = true;
QTimer::singleShot(500, []() { uiStateSP()->scene.touch_to_wake = false; });
}
}
// TODO: a very similar, but not identical call is made by parent. Which made me question if I should override it here...
// Will have to revisit later if this is not behaving as expected.
// Handle sidebar collapsing
if ((onroad->isVisible() || body->isVisible()) && (!sidebar->isVisible() || e->x() > sidebar->width())) {
LOGD("HomeWindowSP sidebar->isVisible() [%d] | e->x() [%d] | sidebar->width() [%d]", sidebar->isVisible(), e->x(), sidebar->width());
if (onroad->wakeScreenTimeout()) {
LOGD("HomeWindowSP wakeScreenTimeout() [%d]", onroad->wakeScreenTimeout());
sidebar->setVisible(!sidebar->isVisible() && !onroad->isMapVisible());
}
}
}
+59
View File
@@ -0,0 +1,59 @@
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#pragma once
#include <QFrame>
#include <QWidget>
#include "common/params.h"
#include "selfdrive/ui/qt/body.h"
#include "selfdrive/ui/qt/widgets/offroad_alerts.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/qt/home.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/qt/sidebar.h"
#define OnroadWindow OnroadWindowSP
#else
#include "selfdrive/ui/qt/sidebar.h"
#include "selfdrive/ui/qt/onroad/onroad_home.h"
#endif
class HomeWindowSP : public HomeWindow {
Q_OBJECT
public:
explicit HomeWindowSP(QWidget* parent = 0);
public slots:
void showMapPanel(bool show);
protected:
void mousePressEvent(QMouseEvent* e) override;
private slots:
void updateState(const UIState &s) override;
};
+293
View File
@@ -0,0 +1,293 @@
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#include "selfdrive/ui/sunnypilot/qt/maps/map.h"
#include "common/swaglog.h"
#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h"
const float MAX_ZOOM = 17;
const float MIN_ZOOM = 14;
const float MAX_PITCH = 50;
MapWindowSP::MapWindowSP(const QMapLibre::Settings &settings) : MapWindow(settings) {
QObject::disconnect(uiState(), &UIState::uiUpdate, this, &MapWindow::updateState);
QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &MapWindowSP::updateState);
}
MapWindowSP::~MapWindowSP() {
makeCurrent();
}
void MapWindowSP::initLayers() {
// This doesn't work from initializeGL
if (!m_map->layerExists("modelPathLayer")) {
qDebug() << "Initializing modelPathLayer";
QVariantMap modelPath;
//modelPath["id"] = "modelPathLayer";
modelPath["type"] = "line";
modelPath["source"] = "modelPathSource";
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");
}
if (!m_map->layerExists("navLayer")) {
qDebug() << "Initializing navLayer";
QVariantMap nav;
nav["type"] = "line";
nav["source"] = "navSource";
m_map->addLayer("navLayer", nav, "road-intersection");
QVariantMap transition;
transition["duration"] = 400; // ms
m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(uiStateSP()->scene.navigate_on_openpilot_deprecated));
m_map->setPaintProperty("navLayer", "line-color-transition", transition);
m_map->setPaintProperty("navLayer", "line-width", 7.5);
m_map->setLayoutProperty("navLayer", "line-cap", "round");
}
if (!m_map->layerExists("pinLayer")) {
qDebug() << "Initializing pinLayer";
m_map->addImage("default_marker", QImage("../assets/navigation/default_marker.svg"));
QVariantMap pin;
pin["type"] = "symbol";
pin["source"] = "pinSource";
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);
m_map->setLayoutProperty("pinLayer", "icon-allow-overlap", true);
m_map->setLayoutProperty("pinLayer", "symbol-sort-key", 0);
m_map->setLayoutProperty("pinLayer", "icon-anchor", "bottom");
}
if (!m_map->layerExists("carPosLayer")) {
qDebug() << "Initializing carPosLayer";
m_map->addImage("label-arrow", QImage("../assets/images/triangle.svg"));
QVariantMap carPos;
carPos["type"] = "symbol";
carPos["source"] = "carPosSource";
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);
m_map->setLayoutProperty("carPosLayer", "icon-ignore-placement", true);
m_map->setLayoutProperty("carPosLayer", "icon-allow-overlap", true);
// 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")) && uiStateSP()->scene.map_3d_buildings) { // Could put this behind the cellular metered toggle in case it increases data usage
qDebug() << "Initializing buildingsLayer";
QVariantMap buildings;
buildings["id"] = "buildingsLayer";
buildings["source"] = "composite";
buildings["source-layer"] = "building";
buildings["type"] = "fill-extrusion";
buildings["minzoom"] = 15;
m_map->addLayer("buildingsLayer", buildings);
m_map->setFilter("buildingsLayer", QVariantList({"==", "extrude", "true"}));
QVariantList fillExtrusionHeight = { // scale buildings as you zoom in
"interpolate",
QVariantList{"linear"},
QVariantList{"zoom"},
15, 0,
15.05, QVariantList{"get", "height"}
};
QVariantList fillExtrusionBase = {
"interpolate",
QVariantList{"linear"},
QVariantList{"zoom"},
15, 0,
15.05, QVariantList{"get", "min_height"}
};
QVariantList fillExtrusionOpacity = {
"interpolate",
QVariantList{"linear"},
QVariantList{"zoom"},
15, 0, // transparent at zoom level 15
15.5, .6, // fade in
17, .6, // begin fading out
20, 0 // fade out when zoomed in
};
m_map->setPaintProperty("buildingsLayer", "fill-extrusion-color", QColor("grey"));
m_map->setPaintProperty("buildingsLayer", "fill-extrusion-opacity", fillExtrusionOpacity);
m_map->setPaintProperty("buildingsLayer", "fill-extrusion-height", fillExtrusionHeight);
m_map->setPaintProperty("buildingsLayer", "fill-extrusion-base", fillExtrusionBase);
m_map->setLayoutProperty("buildingsLayer", "visibility", "visible");
}
}
void MapWindowSP::updateState(const UIStateSP &s) {
if (!uiState()->scene.started) {
return;
}
const SubMaster &sm = *(s.sm);
update();
// on rising edge of a valid system time, reinitialize the map to set a new token
if (sm.valid("clocks") && !prev_time_valid) {
LOGW("Time is now valid, reinitializing map");
m_settings.setApiKey(get_mapbox_token());
initializeGL();
}
prev_time_valid = sm.valid("clocks");
if (sm.updated("modelV2")) {
// set path color on change, and show map on rising edge of navigate on openpilot
auto car_control = sm["carControl"].getCarControl();
bool nav_enabled = sm["modelV2"].getModelV2().getNavEnabledDEPRECATED() &&
(sm["controlsState"].getControlsState().getEnabled() || car_control.getLatActive() || car_control.getLongActive());
if (nav_enabled != uiState()->scene.navigate_on_openpilot_deprecated) {
if (loaded_once) {
m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(nav_enabled));
}
if (nav_enabled) {
emit requestVisible(true);
}
}
uiState()->scene.navigate_on_openpilot_deprecated = nav_enabled;
}
if (sm.updated("liveLocationKalman")) {
auto locationd_location = sm["liveLocationKalman"].getLiveLocationKalman();
auto locationd_pos = locationd_location.getPositionGeodetic();
auto locationd_orientation = locationd_location.getCalibratedOrientationNED();
auto locationd_velocity = locationd_location.getVelocityCalibrated();
auto locationd_ecef = locationd_location.getPositionECEF();
locationd_valid = (locationd_pos.getValid() && locationd_orientation.getValid() && locationd_velocity.getValid() && locationd_ecef.getValid());
if (locationd_valid) {
// Check std norm
auto pos_ecef_std = locationd_ecef.getStd();
bool pos_accurate_enough = sqrt(pow(pos_ecef_std[0], 2) + pow(pos_ecef_std[1], 2) + pow(pos_ecef_std[2], 2)) < 100;
locationd_valid = pos_accurate_enough;
}
if (locationd_valid) {
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]));
}
}
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 &&
nav_dest && !isVisible();
qWarning() << "Got new navRoute from navd. Opening map:" << allow_open;
// Show map on destination set/change
if (allow_open) {
emit requestSettings(false);
emit requestVisible(true);
}
}
loaded_once = loaded_once || (m_map && m_map->isFullyLoaded());
if (!loaded_once) {
setError(tr("Map Loading"));
return;
}
initLayers();
if (!locationd_valid) {
setError(tr("Waiting for GPS"));
} else if (routing_problem) {
setError(tr("Waiting for route"));
} else {
setError("");
}
if (locationd_valid) {
// Update current location marker
auto point = coordinate_to_collection(*last_position);
QMapLibre::Feature feature1(QMapLibre::Feature::PointType, point, {}, {});
QVariantMap carPosSource;
carPosSource["type"] = "geojson";
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
if (last_bearing) {
m_map->setLayoutProperty("carPosLayer", "icon-rotate", *last_bearing - m_map->bearing());
}
}
if (interaction_counter == 0) {
if (last_position) m_map->setCoordinate(*last_position);
if (last_bearing) m_map->setBearing(*last_bearing);
m_map->setZoom(util::map_val<float>(velocity_filter.x(), 0, 30, MAX_ZOOM, MIN_ZOOM));
} else {
interaction_counter--;
}
if (sm.updated("navInstruction")) {
// an invalid navInstruction packet with a nav destination is only possible if:
// - API exception/no internet
// - route response is empty
// - any time navd is waiting for recompute_countdown
routing_problem = !sm.valid("navInstruction") && coordinate_from_param("NavDestination").has_value();
if (sm.valid("navInstruction")) {
auto i = sm["navInstruction"].getNavInstruction();
map_eta->updateETA(i.getTimeRemaining(), i.getTimeRemainingTypical(), i.getDistanceRemaining());
if (locationd_valid) {
m_map->setPitch(MAX_PITCH); // TODO: smooth pitching based on maneuver distance
map_instructions->updateInstructions(i);
}
} else {
clearRoute();
}
}
if (sm.rcv_frame("navRoute") != route_rcv_frame) {
qWarning() << "Updating navLayer with new route";
auto route = sm["navRoute"].getNavRoute();
auto route_points = capnp_coordinate_list_to_collection(route.getCoordinates());
QMapLibre::Feature feature(QMapLibre::Feature::LineStringType, route_points, {}, {});
QVariantMap navSource;
navSource["type"] = "geojson";
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();
}
}
void MapWindowSP::offroadTransition(bool offroad) {
if (offroad) {
uiStateSP()->scene.navigate_on_openpilot_deprecated = false;
}
MapWindow::offroadTransition(offroad);
}
+53
View File
@@ -0,0 +1,53 @@
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#pragma once
#include "selfdrive/ui/qt/maps/map.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include <QColor>
class MapWindowSP : public MapWindow {
Q_OBJECT
public:
explicit MapWindowSP(const QMapLibre::Settings &);
~MapWindowSP();
private:
void initLayers();
// Blue with normal nav, green when nav is input into the model
QColor getNavPathColor(bool nav_enabled) {
return nav_enabled ? QColor("#31ee73") : QColor("#31a1ee");
}
protected slots:
void updateState(const UIStateSP &s);
public slots:
void offroadTransition(bool offroad);
};
@@ -0,0 +1,38 @@
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#pragma once
#include "selfdrive/ui/qt/maps/map_helpers.h"
#define MAPBOX_TOKEN MAPBOX_TOKEN_SP
#define MAPS_HOST MAPS_HOST_SP
#include "common/params.h"
const QString MAPBOX_TOKEN = QString::fromStdString(Params().get("CustomMapboxTokenSk")) != "" ?
QString::fromStdString(Params().get("CustomMapboxTokenSk")) : util::getenv("MAPBOX_TOKEN").c_str();
const QString MAPS_HOST = util::getenv("MAPS_HOST", MAPBOX_TOKEN.isEmpty() ? "https://maps.comma.ai" : "https://api.mapbox.com").c_str();
@@ -0,0 +1,448 @@
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#include "selfdrive/ui/sunnypilot/qt/network/networking.h"
#include <algorithm>
#include <QHBoxLayout>
#include <QScrollBar>
#include <QStyle>
#include "selfdrive/ui/qt/widgets/ssh_keys.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/qt/qt_window.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
static const int ICON_WIDTH = 49;
// Networking functions
NetworkingSP::NetworkingSP(QWidget* parent, bool show_advanced) : QFrame(parent) {
main_layout = new QStackedLayout(this);
wifi = new WifiManager(this);
connect(wifi, &WifiManager::refreshSignal, this, &NetworkingSP::refresh);
connect(wifi, &WifiManager::wrongPassword, this, &NetworkingSP::wrongPassword);
wifiScreen = new QWidget(this);
QVBoxLayout* vlayout = new QVBoxLayout(wifiScreen);
vlayout->setContentsMargins(20, 20, 20, 20);
QHBoxLayout* hlayout = new QHBoxLayout();
QPushButton* scanButton = new QPushButton(tr("Scan"));
scanButton->setObjectName("scan_btn");
scanButton->setFixedSize(400, 100);
connect(wifi, &WifiManager::refreshSignal, this, [=]() { scanButton->setText(tr("Scan")); scanButton->setEnabled(true); });
connect(scanButton, &QPushButton::clicked, [=]() { scanButton->setText(tr("Scanning...")); scanButton->setEnabled(false); wifi->requestScan(); });
hlayout->addWidget(scanButton);
hlayout->addStretch(1); // Pushes the button all the way to the left
if (show_advanced) {
hlayout->setSpacing(10);
QPushButton* advancedSettings = new QPushButton(tr("Advanced"));
advancedSettings->setObjectName("advanced_btn");
advancedSettings->setFixedSize(400, 100);
connect(advancedSettings, &QPushButton::clicked, [=]() { main_layout->setCurrentWidget(an); });
hlayout->addWidget(advancedSettings);
}
vlayout->addLayout(hlayout);
vlayout->addSpacing(10);
wifiWidget = new WifiUISP(this, wifi);
wifiWidget->setObjectName("wifiWidget");
connect(wifiWidget, &WifiUISP::connectToNetwork, this, &NetworkingSP::connectToNetwork);
ScrollView *wifiScroller = new ScrollView(wifiWidget, this);
wifiScroller->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
vlayout->addWidget(wifiScroller, 1);
main_layout->addWidget(wifiScreen);
an = new AdvancedNetworkingSP(this, wifi);
connect(an, &AdvancedNetworkingSP::backPress, [=]() { main_layout->setCurrentWidget(wifiScreen); });
connect(an, &AdvancedNetworkingSP::requestWifiScreen, [=]() { main_layout->setCurrentWidget(wifiScreen); });
main_layout->addWidget(an);
QPalette pal = palette();
pal.setColor(QPalette::Window, QColor(0x29, 0x29, 0x29));
setAutoFillBackground(true);
setPalette(pal);
setStyleSheet(R"(
#wifiWidget > QPushButton, #back_btn, #advanced_btn, #scan_btn{
font-size: 50px;
margin: 0px;
padding: 15px;
border-width: 0;
border-radius: 30px;
color: #dddddd;
background-color: #393939;
}
#back_btn:pressed, #advanced_btn:pressed, #scan_btn:pressed {
background-color: #4a4a4a;
}
)");
main_layout->setCurrentWidget(wifiScreen);
}
void NetworkingSP::refresh() {
wifiWidget->refresh();
an->refresh();
}
void NetworkingSP::connectToNetwork(const Network n) {
if (wifi->isKnownConnection(n.ssid)) {
wifi->activateWifiConnection(n.ssid);
} else if (n.security_type == SecurityType::OPEN) {
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, false, pass);
}
}
}
void NetworkingSP::wrongPassword(const QString &ssid) {
if (wifi->seenNetworks.contains(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, false, pass);
}
}
}
void NetworkingSP::showEvent(QShowEvent *event) {
wifi->start();
}
void NetworkingSP::hideEvent(QHideEvent *event) {
main_layout->setCurrentWidget(wifiScreen);
wifi->stop();
}
// AdvancedNetworkingSP functions
AdvancedNetworkingSP::AdvancedNetworkingSP(QWidget* parent, WifiManager* wifi): QWidget(parent), wifi(wifi) {
QVBoxLayout* main_layout = new QVBoxLayout(this);
main_layout->setMargin(40);
main_layout->setSpacing(20);
// Back button
QPushButton* back = new QPushButton(tr("Back"));
back->setObjectName("back_btn");
back->setFixedSize(400, 100);
connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
main_layout->addWidget(back, 0, Qt::AlignLeft);
ListWidgetSP *list = new ListWidgetSP(this);
// Enable tethering layout
const bool set_hotspot_on_boot = params.getBool("HotspotOnBoot") && params.getBool("HotspotOnBootConfirmed");
tetheringToggle = new ToggleControlSP(tr("Enable Tethering"), "", "", wifi->isTetheringEnabled() || set_hotspot_on_boot);
list->addItem(tetheringToggle);
QObject::connect(tetheringToggle, &ToggleControlSP::toggleFlipped, this, &AdvancedNetworkingSP::toggleTethering);
hotspotOnBootToggle = new ToggleControlSP(
tr("Retain hotspot/tethering state"),
tr("Enabling this toggle will retain the hotspot/tethering toggle state across reboots."),
"",
params.getBool("HotspotOnBoot")
);
hotspotOnBootToggle->setEnabled(wifi->isTetheringEnabled() || set_hotspot_on_boot);
QObject::connect(hotspotOnBootToggle, &ToggleControlSP::toggleFlipped, [=](bool state) {
params.putBool("HotspotOnBoot", state);
});
list->addItem(hotspotOnBootToggle);
// Change tethering password
ButtonControlSP *editPasswordButton = new ButtonControlSP(tr("Tethering Password"), tr("EDIT"));
connect(editPasswordButton, &ButtonControlSP::clicked, [=]() {
QString pass = InputDialog::getText(tr("Enter new tethering password"), this, "", true, 8, wifi->getTetheringPassword());
if (!pass.isEmpty()) {
wifi->changeTetheringPassword(pass);
}
});
list->addItem(editPasswordButton);
// IP address
ipLabel = new LabelControlSP(tr("IP Address"), wifi->ipv4_address);
list->addItem(ipLabel);
// SSH keys
list->addItem(new SshToggle());
list->addItem(new SshControl());
// Roaming toggle
const bool roamingEnabled = params.getBool("GsmRoaming");
roamingToggle = new ToggleControlSP(tr("Enable Roaming"), "", "", roamingEnabled);
QObject::connect(roamingToggle, &ToggleControlSP::toggleFlipped, [=](bool state) {
params.putBool("GsmRoaming", state);
wifi->updateGsmSettings(state, QString::fromStdString(params.get("GsmApn")), params.getBool("GsmMetered"));
});
list->addItem(roamingToggle);
// APN settings
editApnButton = new ButtonControlSP(tr("APN Setting"), tr("EDIT"));
connect(editApnButton, &ButtonControlSP::clicked, [=]() {
const QString cur_apn = QString::fromStdString(params.get("GsmApn"));
QString apn = InputDialog::getText(tr("Enter APN"), this, tr("leave blank for automatic configuration"), false, -1, cur_apn).trimmed();
if (apn.isEmpty()) {
params.remove("GsmApn");
} else {
params.put("GsmApn", apn.toStdString());
}
wifi->updateGsmSettings(params.getBool("GsmRoaming"), apn, params.getBool("GsmMetered"));
});
list->addItem(editApnButton);
// Metered toggle
const bool metered = params.getBool("GsmMetered");
meteredToggle = new ToggleControlSP(tr("Cellular Metered"), tr("Prevent large data uploads when on a metered connection"), "", metered);
QObject::connect(meteredToggle, &SshToggle::toggleFlipped, [=](bool state) {
params.putBool("GsmMetered", state);
wifi->updateGsmSettings(params.getBool("GsmRoaming"), QString::fromStdString(params.get("GsmApn")), state);
});
list->addItem(meteredToggle);
// Hidden Network
hiddenNetworkButton = new ButtonControlSP(tr("Hidden Network"), tr("CONNECT"));
connect(hiddenNetworkButton, &ButtonControlSP::clicked, [=]() {
QString ssid = InputDialog::getText(tr("Enter SSID"), this, "", false, 1);
if (!ssid.isEmpty()) {
QString pass = InputDialog::getText(tr("Enter password"), this, tr("for \"%1\"").arg(ssid), true, -1);
Network hidden_network;
hidden_network.ssid = ssid.toUtf8();
if (!pass.isEmpty()) {
hidden_network.security_type = SecurityType::WPA;
wifi->connect(hidden_network, true, pass);
} else {
wifi->connect(hidden_network, true);
}
emit requestWifiScreen();
}
});
list->addItem(hiddenNetworkButton);
// Ngrok
QProcess process;
process.start("sudo service ngrok status | grep running");
process.waitForFinished();
QString output = QString(process.readAllStandardOutput());
bool ngrokRunning = !output.isEmpty();
ToggleControlSP *ngrokToggle = new ToggleControlSP(tr("Ngrok Service"), "", "", ngrokRunning);
connect(ngrokToggle, &ToggleControlSP::toggleFlipped, [=](bool state) {
if (state) std::system("sudo ngrok service start");
else std::system("sudo ngrok service stop");
});
list->addItem(ngrokToggle);
// Set initial config
wifi->updateGsmSettings(roamingEnabled, QString::fromStdString(params.get("GsmApn")), metered);
connect(uiState(), &UIState::primeTypeChanged, this, [=](PrimeType prime_type) {
bool gsmVisible = prime_type == PrimeType::NONE || prime_type == PrimeType::LITE;
roamingToggle->setVisible(gsmVisible);
editApnButton->setVisible(gsmVisible);
meteredToggle->setVisible(gsmVisible);
});
main_layout->addWidget(new ScrollView(list, this));
main_layout->addStretch(1);
}
void AdvancedNetworkingSP::refresh() {
ipLabel->setText(wifi->ipv4_address);
tetheringToggle->setEnabled(true);
update();
}
void AdvancedNetworkingSP::toggleTethering(bool enabled) {
params.putBool("HotspotOnBootConfirmed", enabled);
wifi->setTetheringEnabled(enabled);
tetheringToggle->setEnabled(false);
hotspotOnBootToggle->setEnabled(enabled);
if (!enabled) {
params.remove("HotspotOnBoot");
}
}
// WifiUISP functions
WifiUISP::WifiUISP(QWidget *parent, WifiManager* wifi) : QWidget(parent), wifi(wifi) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0);
main_layout->setSpacing(0);
// load imgs
for (const auto &s : {"low", "medium", "high", "full"}) {
QPixmap pix(ASSET_PATH + "/offroad/icon_wifi_strength_" + s + ".svg");
strengths.push_back(pix.scaledToHeight(68, Qt::SmoothTransformation));
}
lock = QPixmap(ASSET_PATH + "offroad/icon_lock_closed.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation);
checkmark = QPixmap(ASSET_PATH + "offroad/icon_checkmark.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation);
circled_slash = QPixmap(ASSET_PATH + "img_circled_slash.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation);
scanningLabel = new QLabel(tr("Scanning for networks..."));
scanningLabel->setStyleSheet("font-size: 65px;");
main_layout->addWidget(scanningLabel, 0, Qt::AlignCenter);
wifi_list_widget = new ListWidgetSP(this);
wifi_list_widget->setVisible(false);
main_layout->addWidget(wifi_list_widget);
setStyleSheet(R"(
QScrollBar::handle:vertical {
min-height: 0px;
border-radius: 4px;
background-color: #8A8A8A;
}
#forgetBtn {
font-size: 32px;
font-weight: 600;
color: #292929;
background-color: #BDBDBD;
border-width: 1px solid #828282;
border-radius: 5px;
padding: 40px;
padding-bottom: 16px;
padding-top: 16px;
}
#forgetBtn:pressed {
background-color: #828282;
}
#connecting {
font-size: 32px;
font-weight: 600;
color: white;
border-radius: 0;
padding: 27px;
padding-left: 43px;
padding-right: 43px;
background-color: black;
}
#ssidLabel {
text-align: left;
border: none;
padding-top: 50px;
padding-bottom: 50px;
}
#ssidLabel:disabled {
color: #696969;
}
)");
}
void WifiUISP::refresh() {
bool is_empty = wifi->seenNetworks.isEmpty();
scanningLabel->setVisible(is_empty);
wifi_list_widget->setVisible(!is_empty);
if (is_empty) return;
setUpdatesEnabled(false);
const bool is_tethering_enabled = wifi->isTetheringEnabled();
QList<Network> sortedNetworks = wifi->seenNetworks.values();
std::sort(sortedNetworks.begin(), sortedNetworks.end(), compare_by_strength);
int n = 0;
for (Network &network : sortedNetworks) {
QPixmap status_icon;
if (network.connected == ConnectedType::CONNECTED) {
status_icon = checkmark;
} else if (network.security_type == SecurityType::UNSUPPORTED) {
status_icon = circled_slash;
} else if (network.security_type == SecurityType::WPA) {
status_icon = lock;
}
bool show_forget_btn = wifi->isKnownConnection(network.ssid) && !is_tethering_enabled;
QPixmap strength = strengths[strengthLevel(network.strength)];
auto item = getItem(n++);
item->setItem(network, status_icon, show_forget_btn, strength);
item->setVisible(true);
}
for (; n < wifi_items.size(); ++n) wifi_items[n]->setVisible(false);
setUpdatesEnabled(true);
}
WifiItemSP *WifiUISP::getItem(int n) {
auto item = n < wifi_items.size() ? wifi_items[n] : wifi_items.emplace_back(new WifiItemSP(tr("CONNECTING..."), tr("FORGET")));
if (!item->parentWidget()) {
QObject::connect(item, &WifiItemSP::connectToNetwork, this, &WifiUISP::connectToNetwork);
QObject::connect(item, &WifiItemSP::forgotNetwork, [this](const Network n) {
if (ConfirmationDialog::confirm(tr("Forget Wi-Fi Network \"%1\"?").arg(QString::fromUtf8(n.ssid)), tr("Forget"), this))
wifi->forgetConnection(n.ssid);
});
wifi_list_widget->addItem(item);
}
return item;
}
// WifiItemSP
WifiItemSP::WifiItemSP(const QString &connecting_text, const QString &forget_text, QWidget *parent) : QWidget(parent) {
QHBoxLayout *hlayout = new QHBoxLayout(this);
hlayout->setContentsMargins(44, 0, 73, 0);
hlayout->setSpacing(50);
hlayout->addWidget(ssidLabel = new ElidedLabelSP());
ssidLabel->setObjectName("ssidLabel");
ssidLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
hlayout->addWidget(connecting = new QPushButton(connecting_text), 0, Qt::AlignRight);
connecting->setObjectName("connecting");
hlayout->addWidget(forgetBtn = new QPushButton(forget_text), 0, Qt::AlignRight);
forgetBtn->setObjectName("forgetBtn");
hlayout->addWidget(iconLabel = new QLabel(), 0, Qt::AlignRight);
hlayout->addWidget(strengthLabel = new QLabel(), 0, Qt::AlignRight);
iconLabel->setFixedWidth(ICON_WIDTH);
QObject::connect(forgetBtn, &QPushButton::clicked, [this]() { emit forgotNetwork(network); });
QObject::connect(ssidLabel, &ElidedLabelSP::clicked, [this]() {
if (network.connected == ConnectedType::DISCONNECTED) emit connectToNetwork(network);
});
}
void WifiItemSP::setItem(const Network &n, const QPixmap &status_icon, bool show_forget_btn, const QPixmap &strength_icon) {
network = n;
ssidLabel->setText(n.ssid);
ssidLabel->setEnabled(n.security_type != SecurityType::UNSUPPORTED);
ssidLabel->setFont(InterFont(55, network.connected == ConnectedType::DISCONNECTED ? QFont::Normal : QFont::Bold));
connecting->setVisible(n.connected == ConnectedType::CONNECTING);
forgetBtn->setVisible(show_forget_btn);
iconLabel->setPixmap(status_icon);
strengthLabel->setPixmap(strength_icon);
}
@@ -0,0 +1,128 @@
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#pragma once
#include <QWidget>
#include <vector>
#include "selfdrive/ui/qt/network/wifi_manager.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
class WifiItemSP : public QWidget {
Q_OBJECT
public:
explicit WifiItemSP(const QString &connecting_text, const QString &forget_text, QWidget* parent = nullptr);
void setItem(const Network& n, const QPixmap &icon, bool show_forget_btn, const QPixmap &strength);
signals:
// Cannot pass Network by reference. it may change after the signal is sent.
void connectToNetwork(const Network n);
void forgotNetwork(const Network n);
protected:
ElidedLabelSP* ssidLabel;
QPushButton* connecting;
QPushButton* forgetBtn;
QLabel* iconLabel;
QLabel* strengthLabel;
Network network;
};
class WifiUISP : public QWidget {
Q_OBJECT
public:
explicit WifiUISP(QWidget *parent = 0, WifiManager* wifi = 0);
private:
WifiItemSP *getItem(int n);
WifiManager *wifi = nullptr;
QLabel *scanningLabel = nullptr;
QPixmap lock;
QPixmap checkmark;
QPixmap circled_slash;
QVector<QPixmap> strengths;
ListWidgetSP *wifi_list_widget = nullptr;
std::vector<WifiItemSP*> wifi_items;
signals:
void connectToNetwork(const Network n);
public slots:
void refresh();
};
class AdvancedNetworkingSP : public QWidget {
Q_OBJECT
public:
explicit AdvancedNetworkingSP(QWidget* parent = 0, WifiManager* wifi = 0);
private:
LabelControlSP* ipLabel;
ToggleControlSP* tetheringToggle;
ToggleControlSP* roamingToggle;
ButtonControlSP* editApnButton;
ButtonControlSP* hiddenNetworkButton;
ToggleControlSP* meteredToggle;
WifiManager* wifi = nullptr;
Params params;
ToggleControlSP* hotspotOnBootToggle;
signals:
void backPress();
void requestWifiScreen();
public slots:
void toggleTethering(bool enabled);
void refresh();
};
class NetworkingSP : public QFrame {
Q_OBJECT
public:
explicit NetworkingSP(QWidget* parent = 0, bool show_advanced = true);
WifiManager* wifi = nullptr;
private:
QStackedLayout* main_layout = nullptr;
QWidget* wifiScreen = nullptr;
AdvancedNetworkingSP* an = nullptr;
WifiUISP* wifiWidget;
void showEvent(QShowEvent* event) override;
void hideEvent(QHideEvent* event) override;
public slots:
void refresh();
private slots:
void connectToNetwork(const Network n);
void wrongPassword(const QString &ssid);
};
@@ -1,5 +1,30 @@
#ifndef ROLE_MODEL_H
#define ROLE_MODEL_H
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#pragma once
#include <QJsonObject>
@@ -56,5 +81,3 @@ public:
return T(m_raw_json_object);
}
};
#endif
@@ -1,5 +1,30 @@
#ifndef SPONSORROLE_MODEL_H
#define SPONSORROLE_MODEL_H
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#pragma once
#include <QJsonObject>
@@ -24,7 +49,7 @@ public:
QJsonObject json = RoleModel::toJson();
json["role_tier"] = sponsorTierToString(roleTier);
return json;
};
}
static SponsorTier stringToSponsorTier(const QString &sponsorTierString) {
const auto sponsorTierStringLower = sponsorTierString.toLower();
@@ -80,5 +105,3 @@ public:
}
[[nodiscard]] auto getSponsorTierColor() const { return sponsorTierToColor(roleTier); }
};
#endif
@@ -0,0 +1,56 @@
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#pragma once
#include <QJsonObject>
class UserModel {
public:
QString device_id;
QString user_id;
qint64 created_at;
qint64 updated_at;
QString token_hash;
explicit UserModel(const QJsonObject &json) {
device_id = json["device_id"].toString();
user_id = json["user_id"].toString();
created_at = json["created_at"].toInt();
updated_at = json["updated_at"].toInt();
token_hash = json["token_hash"].toString();
}
[[nodiscard]] QJsonObject toJson() const {
QJsonObject json;
json["device_id"] = device_id;
json["user_id"] = user_id;
json["created_at"] = created_at;
json["updated_at"] = updated_at;
json["token_hash"] = token_hash;
return json;
}
};
@@ -0,0 +1,76 @@
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink//services/base_device_service.h"
#include "selfdrive/ui/sunnypilot/qt/request_repeater.h"
#include "common/swaglog.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.h"
BaseDeviceService::BaseDeviceService(QObject* parent) : QObject(parent), initial_request(nullptr), repeater(nullptr) {
param_watcher = new ParamWatcher(this);
connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString &param_name, const QString &param_value) {
paramsRefresh();
});
param_watcher->addParam("SunnylinkEnabled");
}
void BaseDeviceService::paramsRefresh() {
}
void BaseDeviceService::loadDeviceData(const QString &url, bool poll) {
if (!is_sunnylink_enabled()) {
LOGW("Sunnylink is not enabled, refusing to load data.");
return;
}
auto sl_dongle_id = getSunnylinkDongleId();
if (!sl_dongle_id.has_value())
return;
QString fullUrl = SUNNYLINK_BASE_URL + "/device/" + *sl_dongle_id + url;
if (poll && !isCurrentyPolling()) {
LOGD("Polling %s", qPrintable(fullUrl));
LOGD("Cache key: SunnylinkCache_%s", qPrintable(QString(getCacheKey())));
repeater = new RequestRepeaterSP(this, fullUrl, "SunnylinkCache_" + getCacheKey(), 60, false, true);
connect(repeater, &RequestRepeaterSP::requestDone, this, &BaseDeviceService::handleResponse);
} else if (isCurrentyPolling()) {
repeater->ForceUpdate();
} else {
LOGD("Sending one-time %s", qPrintable(fullUrl));
initial_request = new HttpRequestSP(this, true, 10000, true);
connect(initial_request, &HttpRequestSP::requestDone, this, &BaseDeviceService::handleResponse);
}
}
void BaseDeviceService::stopPolling() {
if (repeater != nullptr) {
repeater->deleteLater();
repeater = nullptr;
}
}
@@ -0,0 +1,50 @@
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#pragma once
#include "selfdrive/ui/sunnypilot/qt/request_repeater.h"
#include "selfdrive/ui/qt/util.h"
class BaseDeviceService : public QObject {
Q_OBJECT
protected:
void paramsRefresh();
void loadDeviceData(const QString &url, bool poll = false);
virtual void handleResponse(const QString &response, bool success) = 0;
static bool is_sunnylink_enabled() { return Params().getBool("SunnylinkEnabled");}
ParamWatcher* param_watcher;
HttpRequestSP* initial_request = nullptr;
RequestRepeaterSP* repeater = nullptr;
public:
explicit BaseDeviceService(QObject* parent = nullptr);
virtual QString getCacheKey() const = 0;
bool isCurrentyPolling() {return repeater != nullptr;}
void stopPolling();
};
@@ -0,0 +1,55 @@
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h"
#include <QJsonArray>
#include <QJsonDocument>
RoleService::RoleService(QObject* parent) : BaseDeviceService(parent) {}
void RoleService::load() {
loadDeviceData(url);
}
void RoleService::startPolling() {
loadDeviceData(url, true);
}
void RoleService::handleResponse(const QString &response, bool success) {
if (!success) return;
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
QJsonArray jsonArray = doc.array();
std::vector<RoleModel> roles;
for (const auto &value : jsonArray) {
roles.emplace_back(value.toObject());
}
emit rolesReady(roles);
uiStateSP()->setSunnylinkRoles(roles);
}
@@ -0,0 +1,51 @@
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#pragma once
#include <vector>
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h"
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h"
class RoleService : public BaseDeviceService {
Q_OBJECT
public:
explicit RoleService(QObject* parent = nullptr);
void load();
void startPolling();
[[nodiscard]] QString getCacheKey() const final { return "Roles"; }
signals:
void rolesReady(const std::vector<RoleModel> &roles);
protected:
void handleResponse(const QString&response, bool success) override;
private:
QString url = "/roles";
};
@@ -0,0 +1,57 @@
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h"
#include <QJsonArray>
#include <QJsonDocument>
UserService::UserService(QObject* parent) : BaseDeviceService(parent) {
url = "/users";
}
void UserService::load() {
loadDeviceData(url);
}
void UserService::startPolling() {
loadDeviceData(url, true);
}
void UserService::handleResponse(const QString &response, bool success) {
if (!success) return;
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
QJsonArray jsonArray = doc.array();
std::vector<UserModel> users;
for (const auto &value : jsonArray) {
users.emplace_back(value.toObject());
}
emit usersReady(users);
uiStateSP()->setSunnylinkDeviceUsers(users);
}
@@ -0,0 +1,51 @@
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#pragma once
#include <vector>
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h"
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h"
class UserService : public BaseDeviceService {
Q_OBJECT
public:
explicit UserService(QObject* parent = nullptr);
void load();
void startPolling();
[[nodiscard]] QString getCacheKey() const final { return "Users"; };
signals:
void usersReady(const std::vector<UserModel>&users);
protected:
void handleResponse(const QString&response, bool success) override;
private:
QString url = "/users";
};
@@ -0,0 +1,33 @@
/**
The MIT License
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Last updated: July 29, 2024
***/
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h"
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h"
SunnylinkClient::SunnylinkClient(QObject* parent) : QObject(parent) {
role_service = new RoleService(parent);
user_service = new UserService(parent);
}

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