mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-07-25 14:02:06 +08:00
ui: Split sunnypilot into its own classes
This commit is contained in:
+8
-45
@@ -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(' ')
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
@@ -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,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
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
@@ -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 ¶m = "");
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 ¶m_name, const QString ¶m_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
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
};
|
||||
@@ -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 ¶m_name, const QString ¶m_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 ¶m) {
|
||||
@@ -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 ¶m) {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
)");
|
||||
|
||||
@@ -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 ¶m);
|
||||
|
||||
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 ¶m);
|
||||
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;
|
||||
|
||||
@@ -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 ¶m_name, const QString ¶m_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;
|
||||
};
|
||||
@@ -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"
|
||||
@@ -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")));
|
||||
}
|
||||
}
|
||||
@@ -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 ¶m = "");
|
||||
|
||||
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
@@ -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;
|
||||
};
|
||||
|
||||
@@ -15,18 +15,6 @@ void drawIcon(QPainter &p, const QPoint ¢er, 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);
|
||||
}
|
||||
|
||||
@@ -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 ¢er, const QPixmap &img, const QBrush &bg, float opacity);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
};
|
||||
|
||||
@@ -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
@@ -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 ¶m_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 ¶m_name) {
|
||||
watcher->addPath(QString::fromStdString(params.getParamPath(param_name.toStdString())));
|
||||
}
|
||||
}
|
||||
@@ -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 ¶m_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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 ¶m, 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) {
|
||||
|
||||
@@ -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 ¶m, 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 ¶m, 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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ¶m = "");
|
||||
void closeSettings();
|
||||
|
||||
QStackedLayout *main_layout;
|
||||
HomeWindow *homeWindow;
|
||||
SettingsWindow *settingsWindow;
|
||||
OnboardingWindow *onboardingWindow;
|
||||
};
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
+27
-4
@@ -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
|
||||
+28
-5
@@ -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 ¶m_name, const QString ¶m_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
Reference in New Issue
Block a user