diff --git a/SConstruct b/SConstruct index 13da37c7a8..1ccd76e740 100644 --- a/SConstruct +++ b/SConstruct @@ -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(' ') diff --git a/selfdrive/navd/main.cc b/selfdrive/navd/main.cc index 2e7b4d3b60..da188bb26b 100644 --- a/selfdrive/navd/main.cc +++ b/selfdrive/navd/main.cc @@ -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" diff --git a/selfdrive/navd/map_renderer.cc b/selfdrive/navd/map_renderer.cc index d52ee162bd..f24e6bb876 100644 --- a/selfdrive/navd/map_renderer.cc +++ b/selfdrive/navd/map_renderer.cc @@ -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; diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 3d49b3df14..a825a74ab0 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -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) diff --git a/selfdrive/ui/main.cc b/selfdrive/ui/main.cc index 4903a3db3d..baf09c79f1 100644 --- a/selfdrive/ui/main.cc +++ b/selfdrive/ui/main.cc @@ -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); diff --git a/selfdrive/ui/qt/api.cc b/selfdrive/ui/qt/api.cc index f3314221b8..80019f406e 100644 --- a/selfdrive/ui/qt/api.cc +++ b/selfdrive/ui/qt/api.cc @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -10,12 +9,10 @@ #include #include #include -#include #include #include -#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(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(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(); diff --git a/selfdrive/ui/qt/api.h b/selfdrive/ui/qt/api.h index bbebc502e5..f0e21f56a8 100644 --- a/selfdrive/ui/qt/api.h +++ b/selfdrive/ui/qt/api.h @@ -6,14 +6,13 @@ #include #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(); }; diff --git a/selfdrive/ui/qt/body.h b/selfdrive/ui/qt/body.h index 567a54d49b..ac3cd2ce1c 100644 --- a/selfdrive/ui/qt/body.h +++ b/selfdrive/ui/qt/body.h @@ -5,7 +5,11 @@ #include #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 diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index 48de10a79c..68ab992095 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -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"))); - } -} diff --git a/selfdrive/ui/qt/home.h b/selfdrive/ui/qt/home.h index 7e0267a8a9..e903dad47d 100644 --- a/selfdrive/ui/qt/home.h +++ b/selfdrive/ui/qt/home.h @@ -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); }; diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index b5b731ef66..80cd82277e 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -6,9 +6,14 @@ #include #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"); diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index 62871e79d2..00e8b6f78f 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -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 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: diff --git a/selfdrive/ui/qt/maps/map_eta.cc b/selfdrive/ui/qt/maps/map_eta.cc index 0eb77e36ce..e153884576 100644 --- a/selfdrive/ui/qt/maps/map_eta.cc +++ b/selfdrive/ui/qt/maps/map_eta.cc @@ -3,8 +3,13 @@ #include #include -#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; diff --git a/selfdrive/ui/qt/maps/map_helpers.cc b/selfdrive/ui/qt/maps/map_helpers.cc index 50e1401164..6b7b05e786 100644 --- a/selfdrive/ui/qt/maps/map_helpers.cc +++ b/selfdrive/ui/qt/maps/map_helpers.cc @@ -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 #include diff --git a/selfdrive/ui/qt/maps/map_helpers.h b/selfdrive/ui/qt/maps/map_helpers.h index b9cb1f9469..0f4be674f0 100644 --- a/selfdrive/ui/qt/maps/map_helpers.h +++ b/selfdrive/ui/qt/maps/map_helpers.h @@ -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"; diff --git a/selfdrive/ui/qt/maps/map_instructions.cc b/selfdrive/ui/qt/maps/map_instructions.cc index ba8cb356bd..b631548fe7 100644 --- a/selfdrive/ui/qt/maps/map_instructions.cc +++ b/selfdrive/ui/qt/maps/map_instructions.cc @@ -3,8 +3,13 @@ #include #include -#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"; diff --git a/selfdrive/ui/qt/maps/map_panel.cc b/selfdrive/ui/qt/maps/map_panel.cc index c4cc20e21d..cd448483ff 100644 --- a/selfdrive/ui/qt/maps/map_panel.cc +++ b/selfdrive/ui/qt/maps/map_panel.cc @@ -3,10 +3,16 @@ #include #include -#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); diff --git a/selfdrive/ui/qt/maps/map_settings.h b/selfdrive/ui/qt/maps/map_settings.h index 0e151df4ad..40b0d35a6a 100644 --- a/selfdrive/ui/qt/maps/map_settings.h +++ b/selfdrive/ui/qt/maps/map_settings.h @@ -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"; diff --git a/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc index 484536acf2..4f3d333ffd 100644 --- a/selfdrive/ui/qt/network/networking.cc +++ b/selfdrive/ui/qt/network/networking.cc @@ -6,11 +6,16 @@ #include #include +#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); -} +} \ No newline at end of file diff --git a/selfdrive/ui/qt/network/networking.h b/selfdrive/ui/qt/network/networking.h index 7444e9c28d..5831d66dc9 100644 --- a/selfdrive/ui/qt/network/networking.h +++ b/selfdrive/ui/qt/network/networking.h @@ -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); -}; +}; \ No newline at end of file diff --git a/selfdrive/ui/qt/network/sunnylink/models/user_model.h b/selfdrive/ui/qt/network/sunnylink/models/user_model.h deleted file mode 100644 index 72baa3983d..0000000000 --- a/selfdrive/ui/qt/network/sunnylink/models/user_model.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef USER_MODEL_H -#define USER_MODEL_H - -#include - -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 diff --git a/selfdrive/ui/qt/network/sunnylink/services/base_device_service.cc b/selfdrive/ui/qt/network/sunnylink/services/base_device_service.cc deleted file mode 100644 index 2f2561a49a..0000000000 --- a/selfdrive/ui/qt/network/sunnylink/services/base_device_service.cc +++ /dev/null @@ -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; - } -} diff --git a/selfdrive/ui/qt/network/sunnylink/services/base_device_service.h b/selfdrive/ui/qt/network/sunnylink/services/base_device_service.h deleted file mode 100644 index cd7aa9d9b9..0000000000 --- a/selfdrive/ui/qt/network/sunnylink/services/base_device_service.h +++ /dev/null @@ -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 diff --git a/selfdrive/ui/qt/network/sunnylink/services/role_service.cc b/selfdrive/ui/qt/network/sunnylink/services/role_service.cc deleted file mode 100644 index 8bd0904421..0000000000 --- a/selfdrive/ui/qt/network/sunnylink/services/role_service.cc +++ /dev/null @@ -1,29 +0,0 @@ -#include "selfdrive/ui/qt/network/sunnylink/services/role_service.h" - -#include -#include - -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 roles; - for (const auto &value : jsonArray) { - roles.emplace_back(value.toObject()); - } - - emit rolesReady(roles); - uiState()->setSunnylinkRoles(roles); -} diff --git a/selfdrive/ui/qt/network/sunnylink/services/role_service.h b/selfdrive/ui/qt/network/sunnylink/services/role_service.h deleted file mode 100644 index bc40342982..0000000000 --- a/selfdrive/ui/qt/network/sunnylink/services/role_service.h +++ /dev/null @@ -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 &roles); - -protected: - void handleResponse(const QString&response, bool success) override; - -private: - QString url = "/roles"; -}; - -#endif diff --git a/selfdrive/ui/qt/network/sunnylink/services/user_service.cc b/selfdrive/ui/qt/network/sunnylink/services/user_service.cc deleted file mode 100644 index 2d52421d69..0000000000 --- a/selfdrive/ui/qt/network/sunnylink/services/user_service.cc +++ /dev/null @@ -1,31 +0,0 @@ -#include "selfdrive/ui/qt/network/sunnylink/services/user_service.h" - -#include -#include - -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 users; - for (const auto &value : jsonArray) { - users.emplace_back(value.toObject()); - } - - emit usersReady(users); - uiState()->setSunnylinkDeviceUsers(users); -} diff --git a/selfdrive/ui/qt/network/sunnylink/services/user_service.h b/selfdrive/ui/qt/network/sunnylink/services/user_service.h deleted file mode 100644 index e4af93fa29..0000000000 --- a/selfdrive/ui/qt/network/sunnylink/services/user_service.h +++ /dev/null @@ -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&users); - -protected: - void handleResponse(const QString&response, bool success) override; - -private: - QString url = "/users"; -}; - -#endif diff --git a/selfdrive/ui/qt/network/sunnylink/sunnylink_client.cc b/selfdrive/ui/qt/network/sunnylink/sunnylink_client.cc deleted file mode 100644 index 2d1613c0eb..0000000000 --- a/selfdrive/ui/qt/network/sunnylink/sunnylink_client.cc +++ /dev/null @@ -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); -} diff --git a/selfdrive/ui/qt/network/sunnylink/sunnylink_client.h b/selfdrive/ui/qt/network/sunnylink/sunnylink_client.h deleted file mode 100644 index 872d80ccf7..0000000000 --- a/selfdrive/ui/qt/network/sunnylink/sunnylink_client.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef SUNNYLINK_CLIENT_H -#define SUNNYLINK_CLIENT_H - -#include - -#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 diff --git a/selfdrive/ui/qt/network/wifi_manager.cc b/selfdrive/ui/qt/network/wifi_manager.cc index 717da47096..b12207fab5 100644 --- a/selfdrive/ui/qt/network/wifi_manager.cc +++ b/selfdrive/ui/qt/network/wifi_manager.cc @@ -2,7 +2,11 @@ #include +#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" diff --git a/selfdrive/ui/qt/offroad/experimental_mode.cc b/selfdrive/ui/qt/offroad/experimental_mode.cc index b2c6f3323a..88fadd439b 100644 --- a/selfdrive/ui/qt/offroad/experimental_mode.cc +++ b/selfdrive/ui/qt/offroad/experimental_mode.cc @@ -7,13 +7,18 @@ #include #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; diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index 014e9a6f05..8bf92aa06b 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -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(); -} +} \ No newline at end of file diff --git a/selfdrive/ui/qt/offroad/onboarding.h b/selfdrive/ui/qt/offroad/onboarding.h index 008d86032c..098b0823a7 100644 --- a/selfdrive/ui/qt/offroad/onboarding.h +++ b/selfdrive/ui/qt/offroad/onboarding.h @@ -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(); -}; +}; \ No newline at end of file diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index f9f852a3b4..040b074784 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -5,28 +5,29 @@ #include #include -#include -#include -#include #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> 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 longi_button_texts{tr("Aggressive"), tr("Moderate"), tr("Standard"), tr("Relaxed")}; + std::vector 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 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(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
" "

%2


" "%3
" @@ -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("" + long_desc + "

" + 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 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()) { - 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 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> 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; } )"); diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 06fbe8bfb9..dcc07aa2f2 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -8,12 +8,23 @@ #include #include #include -#include #include -#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 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; diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index 685ca3555d..47b4a22682 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -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")); diff --git a/selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.h deleted file mode 100644 index 63a5a817be..0000000000 --- a/selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.h +++ /dev/null @@ -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 toggles; - - CameraOffset *camera_offset; - PathOffset *path_offset; -}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/display_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/display_settings.h deleted file mode 100644 index b8083b53fc..0000000000 --- a/selfdrive/ui/qt/offroad/sunnypilot/display_settings.h +++ /dev/null @@ -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 toggles; - - OnroadScreenOff *onroad_screen_off; - OnroadScreenOffBrightness *onroad_screen_off_brightness; -}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/json_fetcher.h b/selfdrive/ui/qt/offroad/sunnypilot/json_fetcher.h deleted file mode 100644 index f82041e36e..0000000000 --- a/selfdrive/ui/qt/offroad/sunnypilot/json_fetcher.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#include -#include -#include -#include - -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; - } -}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h deleted file mode 100644 index dd8e1748bd..0000000000 --- a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h +++ /dev/null @@ -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 toggles; - - AutoLaneChangeTimer *auto_lane_change_timer; - PauseLateralSpeed *pause_lateral_speed; -}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h deleted file mode 100644 index 2a949c97a4..0000000000 --- a/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h +++ /dev/null @@ -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 toggles; - - ButtonParamControl *dlob_settings; -}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.cc deleted file mode 100644 index e2a410f9ff..0000000000 --- a/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.cc +++ /dev/null @@ -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> 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); -} diff --git a/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h deleted file mode 100644 index 214d2f8ac0..0000000000 --- a/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h +++ /dev/null @@ -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 toggles; -}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.cc deleted file mode 100644 index 2de70e9b30..0000000000 --- a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.cc +++ /dev/null @@ -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)); -} diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h deleted file mode 100644 index 8026cf7f1b..0000000000 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h +++ /dev/null @@ -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 toggles; - ParamWatcher *param_watcher; - - TorqueFriction *friction; - TorqueMaxLatAccel *lat_accel_factor; - ButtonParamControl *dlp_settings; - - ScrollView *scrollView = nullptr; - - const QString nnff_description = QString("%1

" - "%2") - .arg(tr("Formerly known as \"NNFF\", this replaces the lateral \"torque\" 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: ") + "#tuning-nnlc"); - - QString nnffDescriptionBuilder(const QString &custom_description) { - QString description = "" + custom_description + "

" + 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.")); -}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.cc deleted file mode 100644 index e21dc675ae..0000000000 --- a/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.cc +++ /dev/null @@ -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; - } - )"); -} diff --git a/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h deleted file mode 100644 index dc77acac75..0000000000 --- a/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h +++ /dev/null @@ -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; -}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.h deleted file mode 100644 index b210225f49..0000000000 --- a/selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include - -#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

" - "%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 = "" + custom_description + "

" + toyotaEnhancedBsmDescription; - return description; - } -}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.h deleted file mode 100644 index 3ea5825f59..0000000000 --- a/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.h +++ /dev/null @@ -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 toggles; - - ButtonParamControl *dev_ui_settings; - ButtonParamControl *chevron_info_settings; - ButtonParamControl *sidebar_temp_setting; -}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot_main.h b/selfdrive/ui/qt/offroad/sunnypilot_main.h deleted file mode 100644 index fe4526db08..0000000000 --- a/selfdrive/ui/qt/offroad/sunnypilot_main.h +++ /dev/null @@ -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" diff --git a/selfdrive/ui/qt/offroad_home.cc b/selfdrive/ui/qt/offroad_home.cc new file mode 100644 index 0000000000..e9d54a4efb --- /dev/null +++ b/selfdrive/ui/qt/offroad_home.cc @@ -0,0 +1,152 @@ +#include "selfdrive/ui/qt/offroad_home.h" + +#include +#include + +#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"))); + } +} diff --git a/selfdrive/ui/qt/offroad_home.h b/selfdrive/ui/qt/offroad_home.h new file mode 100644 index 0000000000..5796c60f52 --- /dev/null +++ b/selfdrive/ui/qt/offroad_home.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include + +#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; +}; diff --git a/selfdrive/ui/qt/onroad/annotated_camera.cc b/selfdrive/ui/qt/onroad/annotated_camera.cc index bb19798e92..b08420a713 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/qt/onroad/annotated_camera.cc @@ -1,36 +1,17 @@ + #include "selfdrive/ui/qt/onroad/annotated_camera.h" +#include #include #include -#include -#include #include "common/swaglog.h" #include "selfdrive/ui/qt/onroad/buttons.h" #include "selfdrive/ui/qt/util.h" -static std::pair getFeatureStatus(int value, QStringList text_list, QStringList color_list, - bool condition, QString off_text) { - - QString text("Error"); - QColor color("#ffffff"); - - for (int i = 0; i < text_list.size() && i < color_list.size(); ++i) { - if (value == i) { - text = condition ? text_list[i] : off_text; - color = condition ? QColor(color_list[i]) : QColor("#ffffff"); - break; // Exit the loop once a match is found - } - } - - return {text, color}; -} - - // Window that shows camera view and variety of info drawn on top AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) { pm = std::make_unique>({"uiDebug"}); - e2e_state = std::make_unique>({"e2eLongStateSP"}); main_layout = new QVBoxLayout(this); main_layout->setMargin(UI_BORDER_SIZE); @@ -39,68 +20,7 @@ AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* par experimental_btn = new ExperimentalButton(this); main_layout->addWidget(experimental_btn, 0, Qt::AlignTop | Qt::AlignRight); - onroad_settings_btn = new OnroadSettingsButton(this); - - map_settings_btn = new MapSettingsButton(this); - dm_img = loadPixmap("../assets/img_driver_face.png", {img_size + 5, img_size + 5}); - map_img = loadPixmap("../assets/img_world_icon.png", {subsign_img_size, subsign_img_size}); - left_img = loadPixmap("../assets/img_turn_left_icon.png", {subsign_img_size, subsign_img_size}); - right_img = loadPixmap("../assets/img_turn_right_icon.png", {subsign_img_size, subsign_img_size}); - - buttons_layout = new QHBoxLayout(); - buttons_layout->setContentsMargins(0, 0, 10, 20); - main_layout->addLayout(buttons_layout); - updateButtonsLayout(false); -} - -void AnnotatedCameraWidget::mousePressEvent(QMouseEvent* e) { - bool propagate_event = true; - - UIState *s = uiState(); - UIScene &scene = s->scene; - const SubMaster &sm = *(s->sm); - const auto longitudinal_plan_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP(); - - if (longitudinal_plan_sp.getSpeedLimit() > 0.0 && sl_sign_rect.contains(e->x(), e->y())) { - // If touching the speed limit sign area when visible - scene.last_speed_limit_sign_tap = seconds_since_boot(); - params.putBool("LastSpeedLimitSignTap", true); - scene.speed_limit_control_enabled = !scene.speed_limit_control_enabled; - params.putBool("EnableSlc", scene.speed_limit_control_enabled); - propagate_event = false; - } - - if (propagate_event) { - QWidget::mousePressEvent(e); - } -} - -void AnnotatedCameraWidget::updateButtonsLayout(bool is_rhd) { - QLayoutItem *item; - while ((item = buttons_layout->takeAt(0)) != nullptr) { - delete item; - } - - buttons_layout->setContentsMargins(0, 0, 10, rn_offset != 0 ? rn_offset + 10 : 20); - - if (is_rhd) { - buttons_layout->addSpacing(map_settings_btn->isVisible() ? 30 : 0); - buttons_layout->addWidget(map_settings_btn, 0, Qt::AlignBottom | Qt::AlignLeft); - - buttons_layout->addStretch(1); - - buttons_layout->addWidget(onroad_settings_btn, 0, Qt::AlignBottom | Qt::AlignRight); - buttons_layout->addSpacing(onroad_settings_btn->isVisible() ? 216 : 0); - } else { - buttons_layout->addSpacing(onroad_settings_btn->isVisible() ? 216 : 0); - buttons_layout->addWidget(onroad_settings_btn, 0, Qt::AlignBottom | Qt::AlignLeft); - - buttons_layout->addStretch(1); - - buttons_layout->addWidget(map_settings_btn, 0, Qt::AlignBottom | Qt::AlignRight); - buttons_layout->addSpacing(map_settings_btn->isVisible() ? 30 : 0); // Add spacing to the right - } } void AnnotatedCameraWidget::updateState(const UIState &s) { @@ -108,18 +28,8 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { const SubMaster &sm = *(s.sm); const bool cs_alive = sm.alive("controlsState"); - const bool nav_alive = sm.alive("navInstruction") && sm["navInstruction"].getValid(); const auto cs = sm["controlsState"].getControlsState(); - const auto cs_sp = sm["controlsStateSP"].getControlsStateSP(); const auto car_state = sm["carState"].getCarState(); - const auto nav_instruction = sm["navInstruction"].getNavInstruction(); - const auto car_control = sm["carControl"].getCarControl(); - const auto radar_state = sm["radarState"].getRadarState(); - const auto is_gps_location_external = sm.rcv_frame("gpsLocationExternal") > 1; - const auto gpsLocation = is_gps_location_external ? sm["gpsLocationExternal"].getGpsLocationExternal() : sm["gpsLocation"].getGpsLocation(); - const auto ltp = sm["liveTorqueParameters"].getLiveTorqueParameters(); - const auto lateral_plan_sp = sm["lateralPlanSPDEPRECATED"].getLateralPlanSPDEPRECATED(); - car_params = sm["carParams"].getCarParams(); // Handle older routes where vCruiseCluster is not set float v_cruise = cs.getVCruiseCluster() == 0.0 ? cs.getVCruise() : cs.getVCruiseCluster(); @@ -132,250 +42,23 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { // Handle older routes where vEgoCluster is not set v_ego_cluster_seen = v_ego_cluster_seen || car_state.getVEgoCluster() != 0.0; float v_ego = v_ego_cluster_seen ? car_state.getVEgoCluster() : car_state.getVEgo(); - v_ego = s.scene.true_vego_ui ? car_state.getVEgo() : v_ego; speed = cs_alive ? std::max(0.0, v_ego) : 0.0; speed *= s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH; - auto speed_limit_sign = nav_instruction.getSpeedLimitSign(); - speedLimit = nav_alive ? nav_instruction.getSpeedLimit() : 0.0; - speedLimit *= (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); - - has_us_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::MUTCD); - has_eu_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA); is_metric = s.scene.is_metric; speedUnit = s.scene.is_metric ? tr("km/h") : tr("mph"); hideBottomIcons = (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE); status = s.status; - // TODO: Add minimum speed? - left_blindspot = cs_alive && car_state.getLeftBlindspot(); - right_blindspot = cs_alive && car_state.getRightBlindspot(); - - steerOverride = car_state.getSteeringPressed(); - gasOverride = car_state.getGasPressed(); - latActive = car_control.getLatActive(); - madsEnabled = car_state.getMadsEnabled(); - - brakeLights = car_state.getBrakeLightsDEPRECATED() && s.scene.visual_brake_lights; - - standStillTimer = s.scene.stand_still_timer; - standStill = car_state.getStandstill(); - standstillElapsedTime = lateral_plan_sp.getStandstillElapsed(); - - hideVEgoUi = s.scene.hide_vego_ui; - - splitPanelVisible = s.scene.map_visible || s.scene.onroad_settings_visible; - - // ############################## DEV UI START ############################## - lead_d_rel = radar_state.getLeadOne().getDRel(); - lead_v_rel = radar_state.getLeadOne().getVRel(); - lead_status = radar_state.getLeadOne().getStatus(); - lateralState = QString::fromStdString(cs_sp.getLateralState()); - angleSteers = car_state.getSteeringAngleDeg(); - steerAngleDesired = cs.getLateralControlState().getPidState().getSteeringAngleDesiredDeg(); - curvature = cs.getCurvature(); - roll = sm["liveParameters"].getLiveParameters().getRoll(); - memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent(); - devUiInfo = s.scene.dev_ui_info; - gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0; //External reports accuracy, internal does not. - altitude = gpsLocation.getAltitude(); - vEgo = car_state.getVEgo(); - aEgo = car_state.getAEgo(); - steeringTorqueEps = car_state.getSteeringTorqueEps(); - bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg(); - bearingDeg = gpsLocation.getBearingDeg(); - torquedUseParams = (ltp.getUseParams() || s.scene.live_torque_toggle) && !s.scene.torqued_override; - latAccelFactorFiltered = ltp.getLatAccelFactorFiltered(); - frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered(); - liveValid = ltp.getLiveValid(); - // ############################## DEV UI END ############################## - - btnPerc = s.scene.sleep_btn_opacity * 0.05; - - left_blinker = car_state.getLeftBlinker(); - right_blinker = car_state.getRightBlinker(); - lane_change_edge_block = lateral_plan_sp.getLaneChangeEdgeBlockDEPRECATED(); - // update engageability/experimental mode button experimental_btn->updateState(s); - // update onroad settings button state - onroad_settings_btn->updateState(s); - // update DM icon auto dm_state = sm["driverMonitoringState"].getDriverMonitoringState(); dmActive = dm_state.getIsActiveMode(); rightHandDM = dm_state.getIsRHD(); // DM icon transition dm_fade_state = std::clamp(dm_fade_state+0.2*(0.5-dmActive), 0.0, 1.0); - - // update buttons layout - updateButtonsLayout(rightHandDM); - - // hide map settings button for alerts and flip for right hand DM - if (map_settings_btn->isEnabled()) { - map_settings_btn->setVisible(!hideBottomIcons); - buttons_layout->setAlignment(map_settings_btn, (rightHandDM ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignBottom); - } - - // hide onroad settings button for alerts and flip for right hand DM - if (onroad_settings_btn->isEnabled()) { - onroad_settings_btn->setVisible(!hideBottomIcons); - buttons_layout->setAlignment(onroad_settings_btn, (rightHandDM ? Qt::AlignRight : Qt::AlignLeft) | Qt::AlignBottom); - } - - const auto lp_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP(); - slcState = lp_sp.getSpeedLimitControlState(); - - speedLimitControlToggle = s.scene.speed_limit_control_enabled; - - const auto vtcState = lp_sp.getVisionTurnControllerState(); - const float vtc_speed = lp_sp.getVisionTurnSpeed() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); - const auto lpSoruce = lp_sp.getLongitudinalPlanSource(); - QColor vtc_color = tcs_colors[int(vtcState)]; - vtc_color.setAlpha(lpSoruce == cereal::LongitudinalPlanSP::LongitudinalPlanSource::TURN ? 255 : 100); - - showVTC = vtcState > cereal::LongitudinalPlanSP::VisionTurnControllerState::DISABLED; - vtcSpeed = QString::number(std::nearbyint(vtc_speed)); - vtcColor = vtc_color; - showDebugUI = s.scene.show_debug_ui; - - const auto lmd_sp = sm["liveMapDataSP"].getLiveMapDataSP(); - - const auto data_type = int(lmd_sp.getDataType()); - const QString data_type_draw(data_type == 2 ? "🌐 " : ""); - roadName = QString::fromStdString(lmd_sp.getCurrentRoadName()); - roadName = !roadName.isEmpty() ? data_type_draw + roadName : ""; - - float speed_limit_slc = lp_sp.getSpeedLimit() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); - const float speed_limit_offset = lp_sp.getSpeedLimitOffset() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); - const bool sl_force_active = speedLimitControlToggle && - seconds_since_boot() < s.scene.last_speed_limit_sign_tap + 2.0; - const bool sl_inactive = !sl_force_active && (!speedLimitControlToggle || - slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::INACTIVE); - const bool sl_temp_inactive = !sl_force_active && (speedLimitControlToggle && - slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::TEMP_INACTIVE); - const bool sl_pre_active = !sl_force_active && (speedLimitControlToggle && - slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::PRE_ACTIVE); - const int sl_distance = int(lp_sp.getDistToSpeedLimit() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH) / 10.0) * 10; - const QString sl_distance_str(QString::number(sl_distance) + (s.scene.is_metric ? "m" : "f")); - const QString sl_offset_str(speed_limit_offset > 0.0 ? speed_limit_offset < 0.0 ? - "-" + QString::number(std::nearbyint(std::abs(speed_limit_offset))) : - "+" + QString::number(std::nearbyint(speed_limit_offset)) : ""); - const QString sl_inactive_str(sl_temp_inactive && s.scene.speed_limit_control_engage_type == 0 ? "TEMP" : ""); - const QString sl_substring(sl_inactive || sl_temp_inactive || sl_pre_active ? sl_inactive_str : - sl_distance > 0 ? sl_distance_str : sl_offset_str); - - showSpeedLimit = speed_limit_slc > 0.0; - speedLimitSLC = speed_limit_slc; - speedLimitSLCOffset = speed_limit_offset; - slcSubText = sl_substring; - slcSubTextSize = sl_inactive || sl_temp_inactive || sl_distance > 0 ? 25.0 : 27.0; - mapSourcedSpeedLimit = lp_sp.getIsMapSpeedLimit(); - slcActive = !sl_inactive && !sl_temp_inactive; - overSpeedLimit = showSpeedLimit && s.scene.speed_limit_warning_type != 0 && - (std::nearbyint(speed_limit_slc + s.scene.speed_limit_warning_value_offset) < std::nearbyint(speed)); - plus_arrow_up_img = loadPixmap("../assets/img_plus_arrow_up", {105, 105}); - minus_arrow_down_img = loadPixmap("../assets/img_minus_arrow_down", {105, 105}); - - const float tsc_speed = lp_sp.getTurnSpeed() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); - const auto tscState = lp_sp.getTurnSpeedControlState(); - const int t_distance = int(lp_sp.getDistToTurn() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH) / 10.0) * 10; - const QString t_distance_str(QString::number(t_distance) + (s.scene.is_metric ? "m" : "f")); - - showTurnSpeedLimit = tsc_speed > 0.0 && std::round(tsc_speed) < 224 && (tsc_speed < speed || s.scene.show_debug_ui); - turnSpeedLimit = QString::number(std::nearbyint(tsc_speed)); - tscSubText = t_distance > 0 ? t_distance_str : QString(""); - tscActive = tscState > cereal::LongitudinalPlanSP::SpeedLimitControlState::TEMP_INACTIVE; - curveSign = lp_sp.getTurnSign(); - - // TODO: Add toggle variables to cereal, and parse from cereal - longitudinalPersonality = s.scene.longitudinal_personality; - dynamicLaneProfile = s.scene.dynamic_lane_profile; - mpcMode = QString::fromStdString(lp_sp.getE2eBlended()); - mpcMode = (mpcMode == "blended") ? mpcMode.replace(0, 1, mpcMode[0].toUpper()) : mpcMode.toUpper(); - - static int reverse_delay = 0; - bool reverse_allowed = false; - if (int(car_state.getGearShifter()) != 4) { - reverse_delay = 0; - reverse_allowed = false; - } else { - reverse_delay += 50; - if (reverse_delay >= 1000) { - reverse_allowed = true; - } - } - - reversing = reverse_allowed; - - cruiseStateEnabled = car_state.getCruiseState().getEnabled(); - - int e2eLStatus = 0; - static bool chime_sent = false; - static int chime_count = 0; - int chime_prompt = 0; - static float last_lead_distance = -1; - const float lead_distance = radar_state.getLeadOne().getDRel(); - - if (s.scene.e2eX[12] > 30 && car_state.getVEgo() < 1.0) { - e2eLStatus = 2; - } else if ((s.scene.e2eX[12] > 0 && s.scene.e2eX[12] < 80) || s.scene.e2eX[12] < 0) { - e2eLStatus = 1; - } else { - e2eLStatus = 0; - } - - if (!car_state.getStandstill()) { - chime_prompt = 0; - chime_sent = false; - chime_count = 0; - - if (last_lead_distance != -1) { - last_lead_distance = -1; - } - } - - if ((cruiseStateEnabled || car_state.getBrakeLightsDEPRECATED()) && !car_state.getGasPressed() && car_state.getStandstill()) { - if (e2eLStatus == 2 && !radar_state.getLeadOne().getStatus()) { - if (chime_sent) { - chime_count = 0; - } else { - chime_count += 1; - } - if (s.scene.e2e_long_alert_light && chime_count >= 2 && !chime_sent) { - chime_prompt = 1; - chime_sent = true; - } else { - chime_prompt = 0; - } - } else if (radar_state.getLeadOne().getStatus()) { - if ((last_lead_distance == -1) || (lead_distance < last_lead_distance)) { - last_lead_distance = lead_distance; - } - if (s.scene.e2e_long_alert_lead && (lead_distance - last_lead_distance > 1.0) && !chime_sent) { - chime_prompt = 2; - chime_sent = true; - } else { - chime_prompt = 0; - } - } else { - chime_prompt = 0; - } - } else { - } - - e2eStatus = chime_prompt; - e2eState = e2eLStatus; - e2eLongAlertUi = s.scene.e2e_long_alert_ui; - dynamicExperimentalControlToggle = s.scene.dynamic_experimental_control; - speedLimitWarningFlash = s.scene.speed_limit_warning_flash; - experimentalMode = cs.getExperimentalMode(); - - featureStatusToggle = s.scene.feature_status_toggle; - - experimental_btn->setVisible(!(showDebugUI && showVTC)); - drivingModelGen = s.scene.driving_model_generation; } void AnnotatedCameraWidget::drawHud(QPainter &p) { @@ -387,33 +70,18 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { bg.setColorAt(1, QColor::fromRgbF(0, 0, 0, 0)); p.fillRect(0, 0, width(), UI_HEADER_HEIGHT, bg); - QString speedLimitStr = (speedLimit > 1) ? QString::number(std::nearbyint(speedLimit)) : "–"; - QString speedLimitStrSlc = showSpeedLimit ? QString::number(std::nearbyint(speedLimitSLC)) : "–"; QString speedStr = QString::number(std::nearbyint(speed)); QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(setSpeed)) : "–"; - const bool isNavSpeedLimit = has_us_speed_limit || has_eu_speed_limit; - - // Draw outer box + border to contain set speed and speed limit - const int sign_margin = 12; - const int us_sign_height = slcSubText == "" ? 186 : 216; - const int eu_sign_size = 176; + // Draw outer box + border to contain set speed const QSize default_size = {172, 204}; QSize set_speed_size = default_size; - if (is_metric || has_eu_speed_limit) set_speed_size.rwidth() = 200; - if ((mapSourcedSpeedLimit && !is_metric && speedLimitStrSlc.size() >= 3) || - (has_us_speed_limit && speedLimitStr.size() >= 3)) set_speed_size.rwidth() = 223; - - if ((mapSourcedSpeedLimit && !is_metric) || has_us_speed_limit) set_speed_size.rheight() += us_sign_height + sign_margin; - else if ((mapSourcedSpeedLimit && is_metric) || has_eu_speed_limit) set_speed_size.rheight() += eu_sign_size + sign_margin; - - int top_radius = 32; - int bottom_radius = ((mapSourcedSpeedLimit && is_metric) || has_eu_speed_limit) ? 100 : 32; + if (is_metric) set_speed_size.rwidth() = 200; QRect set_speed_rect(QPoint(60 + (default_size.width() - set_speed_size.width()) / 2, 45), set_speed_size); p.setPen(QPen(whiteColor(75), 6)); p.setBrush(blackColor(166)); - drawRoundedRect(p, set_speed_rect, top_radius, top_radius, bottom_radius, bottom_radius); + p.drawRoundedRect(set_speed_rect, 32, 32); // Draw MAX QColor max_color = QColor(0x80, 0xd8, 0xa6, 0xff); @@ -421,20 +89,8 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { if (is_cruise_set) { if (status == STATUS_DISENGAGED) { max_color = whiteColor(); - } else if (status == STATUS_OVERRIDE && gasOverride) { + } else if (status == STATUS_OVERRIDE) { max_color = QColor(0x91, 0x9b, 0x95, 0xff); - } else if (speedLimitSLC > 0) { - auto interp_color = [=](QColor c1, QColor c2, QColor c3) { - return speedLimitSLC > 0 ? interpColor(setSpeed, {speedLimitSLC + 5, speedLimitSLC + 15, speedLimitSLC + 25}, {c1, c2, c3}) : c1; - }; - max_color = interp_color(max_color, QColor(0xff, 0xe4, 0xbf), QColor(0xff, 0xbf, 0xbf)); - set_speed_color = interp_color(set_speed_color, QColor(0xff, 0x95, 0x00), QColor(0xff, 0x00, 0x00)); - } else if (speedLimit > 0) { - auto interp_color = [=](QColor c1, QColor c2, QColor c3) { - return speedLimit > 0 ? interpColor(setSpeed, {speedLimit + 5, speedLimit + 15, speedLimit + 25}, {c1, c2, c3}) : c1; - }; - max_color = interp_color(max_color, QColor(0xff, 0xe4, 0xbf), QColor(0xff, 0xbf, 0xbf)); - set_speed_color = interp_color(set_speed_color, QColor(0xff, 0x95, 0x00), QColor(0xff, 0x00, 0x00)); } } else { max_color = QColor(0xa6, 0xa6, 0xa6, 0xff); @@ -447,116 +103,11 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { p.setPen(set_speed_color); p.drawText(set_speed_rect.adjusted(0, 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, setSpeedStr); - const QRect sign_rect = set_speed_rect.adjusted(sign_margin, default_size.height(), -sign_margin, -sign_margin); - sl_sign_rect = sign_rect; - - speedLimitWarning(p, sign_rect, sign_margin); - - // US/Canada (MUTCD style) sign - if (((mapSourcedSpeedLimit && !is_metric && !isNavSpeedLimit) || has_us_speed_limit) && slcShowSign) { - p.setPen(Qt::NoPen); - p.setBrush(whiteColor()); - p.drawRoundedRect(sign_rect, 24, 24); - p.setPen(QPen(blackColor(), 6)); - p.drawRoundedRect(sign_rect.adjusted(9, 9, -9, -9), 16, 16); - - p.setFont(InterFont(28, QFont::DemiBold)); - p.drawText(sign_rect.adjusted(0, 22, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("SPEED")); - p.drawText(sign_rect.adjusted(0, 51, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("LIMIT")); - p.setFont(InterFont(70, QFont::Bold)); - if (overSpeedLimit) p.setPen(QColor(255, 0, 0, 255)); - else p.setPen(blackColor()); - p.drawText(sign_rect.adjusted(0, 85, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitStrSlc); - - // Speed limit offset value - p.setFont(InterFont(32, QFont::Bold)); - p.setPen(blackColor()); - p.drawText(sign_rect.adjusted(0, 85 + 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, slcSubText); - } - - // EU (Vienna style) sign - if (((mapSourcedSpeedLimit && is_metric && !isNavSpeedLimit) || has_eu_speed_limit) && slcShowSign) { - p.setPen(Qt::NoPen); - p.setBrush(whiteColor()); - p.drawEllipse(sign_rect); - p.setPen(QPen(Qt::red, 20)); - p.drawEllipse(sign_rect.adjusted(16, 16, -16, -16)); - - p.setFont(InterFont((speedLimitStrSlc.size() >= 3) ? 60 : 70, QFont::Bold)); - if (overSpeedLimit) p.setPen(QColor(255, 0, 0, 255)); - else p.setPen(blackColor()); - p.drawText(sign_rect, Qt::AlignCenter, speedLimitStrSlc); - - // Speed limit offset value - p.setFont(InterFont(slcSubTextSize, QFont::Bold)); - p.setPen(blackColor()); - p.drawText(sign_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, slcSubText); - } - // current speed - if (!hideVEgoUi) { - p.setFont(InterFont(176, QFont::Bold)); - drawColoredText(p, rect().center().x(), 210, speedStr, brakeLights ? QColor(0xff, 0, 0, 255) : QColor(0xff, 0xff, 0xff, 255)); - p.setFont(InterFont(66)); - drawText(p, rect().center().x(), 290, speedUnit, 200); - } - - if (!reversing) { - // ####### 1 ROW ####### - QRect bar_rect1(rect().left(), rect().bottom() - 60, rect().width(), 61); - if (!splitPanelVisible && devUiInfo == 2) { - p.setPen(Qt::NoPen); - p.setBrush(QColor(0, 0, 0, 100)); - p.drawRect(bar_rect1); - drawNewDevUi2(p, bar_rect1.left(), bar_rect1.center().y()); - } - - // ####### 1 COLUMN ######## - QRect rc2(rect().right() - (UI_BORDER_SIZE * 2), UI_BORDER_SIZE * 1.5, 184, 152); - if (devUiInfo != 0) { - drawRightDevUi(p, rect().right() - 184 - UI_BORDER_SIZE * 2, UI_BORDER_SIZE * 2 + rc2.height()); - } - - int rn_btn = 0; - rn_btn = !splitPanelVisible && devUiInfo == 2 ? 35 : 0; - rn_offset = rn_btn; - - // Stand Still Timer - if (standStillTimer && standStill && !splitPanelVisible) { - drawStandstillTimer(p, rect().right() - 650, 30 + 160 + 250); - } - - // V-TSC - if (showDebugUI && showVTC) { - drawVisionTurnControllerUI(p, rect().right() - 184 - (UI_BORDER_SIZE * 1.5), int(UI_BORDER_SIZE * 1.5), 184, vtcColor, vtcSpeed, 100); - } - - // Bottom bar road name - if (showDebugUI && !roadName.isEmpty()) { - int font_size = splitPanelVisible ? 38 : 50; - int h = splitPanelVisible ? 18 : 26; - p.setFont(InterFont(font_size, QFont::Bold)); - drawRoadNameText(p, rect().center().x(), h, roadName, QColor(255, 255, 255, 255)); - } - - // Turn Speed Sign - if (showTurnSpeedLimit) { - QRect rc = sign_rect; - rc.moveTop(sign_rect.bottom() + UI_BORDER_SIZE); - drawTrunSpeedSign(p, rc, turnSpeedLimit, tscSubText, curveSign, tscActive); - } - } - - // E2E Status - if (e2eLongAlertUi && e2eState != 0) { - drawE2eStatus(p, UI_BORDER_SIZE * 2 + 190, 45, 150, 150, e2eState); - } - - if (!hideBottomIcons && featureStatusToggle) { - int x = UI_BORDER_SIZE * 2 + (rightHandDM ? 600 : 370); - int feature_status_text_x = rightHandDM ? rect().right() - x : x; - drawFeatureStatusText(p, feature_status_text_x, rect().bottom() - 160 - rn_offset); - } + p.setFont(InterFont(176, QFont::Bold)); + drawText(p, rect().center().x(), 210, speedStr); + p.setFont(InterFont(66)); + drawText(p, rect().center().x(), 290, speedUnit, 200); p.restore(); } @@ -569,711 +120,6 @@ void AnnotatedCameraWidget::drawText(QPainter &p, int x, int y, const QString &t p.drawText(real_rect.x(), real_rect.bottom(), text); } -void AnnotatedCameraWidget::drawColoredText(QPainter &p, int x, int y, const QString &text, QColor color) { - QRect real_rect = p.fontMetrics().boundingRect(text); - real_rect.moveCenter({x, y - real_rect.height() / 2}); - - p.setPen(color); - p.drawText(real_rect.x(), real_rect.bottom(), text); -} - -void AnnotatedCameraWidget::drawCenteredText(QPainter &p, int x, int y, const QString &text, QColor color) { - QRect real_rect = p.fontMetrics().boundingRect(text); - real_rect.moveCenter({x, y}); - - p.setPen(color); - p.drawText(real_rect, Qt::AlignCenter, text); -} - -void AnnotatedCameraWidget::drawRoadNameText(QPainter &p, int x, int y, const QString &text, QColor color) { - QRect real_rect = p.fontMetrics().boundingRect(text); - real_rect.moveCenter({x, y}); - - QRect real_rect_adjusted(real_rect); - real_rect_adjusted.adjust(-UI_ROAD_NAME_MARGIN_X, 5, UI_ROAD_NAME_MARGIN_X, 0); - QPainterPath path; - path.addRoundedRect(real_rect_adjusted, 10, 10); - p.setPen(Qt::NoPen); - p.setBrush(QColor(0, 0, 0, 100)); - p.drawPath(path); - - p.setPen(color); - p.drawText(real_rect, Qt::AlignCenter, text); -} - -void AnnotatedCameraWidget::drawVisionTurnControllerUI(QPainter &p, int x, int y, int size, const QColor &color, - const QString &vision_speed, int alpha) { - QRect rvtc(x, y, size, size); - p.setPen(QPen(color, 10)); - p.setBrush(QColor(0, 0, 0, alpha)); - p.drawRoundedRect(rvtc, 20, 20); - p.setPen(Qt::NoPen); - - p.setFont(InterFont(56, QFont::DemiBold)); - drawCenteredText(p, rvtc.center().x(), rvtc.center().y(), vision_speed, color); -} - -void AnnotatedCameraWidget::drawStandstillTimer(QPainter &p, int x, int y) { - char lab_str[16]; - char val_str[16]; - int minute = (int)(standstillElapsedTime / 60); - int second = (int)((standstillElapsedTime) - (minute * 60)); - - if (standStill) { - snprintf(lab_str, sizeof(lab_str), "STOP"); - snprintf(val_str, sizeof(val_str), "%01d:%02d", minute, second); - } - - p.setFont(InterFont(125, QFont::DemiBold)); - drawColoredText(p, x, y, QString(lab_str), QColor(255, 175, 3, 240)); - p.setFont(InterFont(150, QFont::DemiBold)); - drawColoredText(p, x, y + 150, QString(val_str), QColor(255, 255, 255, 240)); -} - -void AnnotatedCameraWidget::drawCircle(QPainter &p, int x, int y, int r, QBrush bg) { - p.setPen(Qt::NoPen); - p.setBrush(bg); - p.drawEllipse(x - r, y - r, 2 * r, 2 * r); -} - -void AnnotatedCameraWidget::drawSpeedSign(QPainter &p, QRect rc, const QString &speed_limit, const QString &sub_text, - int subtext_size, bool is_map_sourced, bool is_active) { - const QColor ring_color = is_active ? QColor(255, 0, 0, 255) : QColor(0, 0, 0, 50); - const QColor inner_color = QColor(255, 255, 255, is_active ? 255 : 85); - const QColor text_color = QColor(0, 0, 0, is_active ? 255 : 85); - - const int x = rc.center().x(); - const int y = rc.center().y(); - const int r = rc.width() / 2.0f; - - drawCircle(p, x, y, r, ring_color); - drawCircle(p, x, y, int(r * 0.8f), inner_color); - - p.setFont(InterFont(89, QFont::Bold)); - drawCenteredText(p, x, y, speed_limit, text_color); - p.setFont(InterFont(subtext_size, QFont::Bold)); - drawCenteredText(p, x, y + 55, sub_text, text_color); - - if (is_map_sourced) { - p.setPen(Qt::NoPen); - p.setOpacity(is_active ? 1.0 : 0.3); - p.drawPixmap(x - subsign_img_size / 2, y - 55 - subsign_img_size / 2, map_img); - p.setOpacity(1.0); - } -} - -void AnnotatedCameraWidget::drawTrunSpeedSign(QPainter &p, QRect rc, const QString &turn_speed, const QString &sub_text, - int curv_sign, bool is_active) { - const QColor border_color = is_active ? QColor(255, 0, 0, 255) : QColor(0, 0, 0, 50); - const QColor inner_color = QColor(255, 255, 255, is_active ? 255 : 85); - const QColor text_color = QColor(0, 0, 0, is_active ? 255 : 85); - - const int x = rc.center().x(); - const int y = 184 * 2 + UI_BORDER_SIZE + 202; - const int width = 184; - - const float stroke_w = 15.0; - const float cS = stroke_w / 2.0 + 4.5; // half width of the stroke on the corners of the triangle - const float R = width / 2.0 - stroke_w / 2.0; - const float A = 0.73205; - const float h2 = 2.0 * R / (1.0 + A); - const float h1 = A * h2; - const float L = 4.0 * R / sqrt(3.0); - - // Draw the internal triangle, compensate for stroke width. Needed to improve rendering when in inactive - // state due to stroke transparency being different from inner transparency. - QPainterPath path; - path.moveTo(x, y - R + cS); - path.lineTo(x - L / 2.0 + cS, y + h1 + h2 - R - stroke_w / 2.0); - path.lineTo(x + L / 2.0 - cS, y + h1 + h2 - R - stroke_w / 2.0); - path.lineTo(x, y - R + cS); - p.setPen(Qt::NoPen); - p.setBrush(inner_color); - p.drawPath(path); - - // Draw the stroke - QPainterPath stroke_path; - stroke_path.moveTo(x, y - R); - stroke_path.lineTo(x - L / 2.0, y + h1 + h2 - R); - stroke_path.lineTo(x + L / 2.0, y + h1 + h2 - R); - stroke_path.lineTo(x, y - R); - p.setPen(QPen(border_color, stroke_w, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); - p.setBrush(Qt::NoBrush); - p.drawPath(stroke_path); - - // Draw the turn sign - if (curv_sign != 0) { - p.setPen(Qt::NoPen); - p.setOpacity(is_active ? 1.0 : 0.3); - p.drawPixmap(int(x - (subsign_img_size / 2)), int(y - R + stroke_w + 30), curv_sign > 0 ? left_img : right_img); - p.setOpacity(1.0); - } - - // Draw the texts. - p.setFont(InterFont(67, QFont::Bold)); - drawCenteredText(p, x, y + 25, turn_speed, text_color); - p.setFont(InterFont(22, QFont::Bold)); - drawCenteredText(p, x, y + 65, sub_text, text_color); -} - -// ############################## DEV UI START ############################## -void AnnotatedCameraWidget::drawCenteredLeftText(QPainter &p, int x, int y, const QString &text1, QColor color1, const QString &text2, const QString &text3, QColor color2) { - QFontMetrics fm(p.font()); - QRect init_rect = fm.boundingRect(text1 + " "); - QRect real_rect = fm.boundingRect(init_rect, 0, text1 + " "); - real_rect.moveCenter({x, y}); - - QRect init_rect3 = fm.boundingRect(text3); - QRect real_rect3 = fm.boundingRect(init_rect3, 0, text3); - real_rect3.moveTop(real_rect.top()); - real_rect3.moveLeft(real_rect.right() + 135); - - QRect init_rect2 = fm.boundingRect(text2); - QRect real_rect2 = fm.boundingRect(init_rect2, 0, text2); - real_rect2.moveTop(real_rect.top()); - real_rect2.moveRight(real_rect.right() + 125); - - p.setPen(color1); - p.drawText(real_rect, Qt::AlignLeft | Qt::AlignVCenter, text1); - - p.setPen(color2); - p.drawText(real_rect2, Qt::AlignRight | Qt::AlignVCenter, text2); - p.drawText(real_rect3, Qt::AlignLeft | Qt::AlignVCenter, text3); -} - -int AnnotatedCameraWidget::drawDevUiElementRight(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) { - p.setFont(InterFont(30 * 2, QFont::Bold)); - drawColoredText(p, x + 92, y + 80, value, color); - - p.setFont(InterFont(28, QFont::Bold)); - drawText(p, x + 92, y + 80 + 42, label, 255); - - if (units.length() > 0) { - p.save(); - p.translate(x + 54 + 30 - 3 + 92 + 30, y + 37 + 25); - p.rotate(-90); - drawText(p, 0, 0, units, 255); - p.restore(); - } - - return 110; -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getDRel() { - QString value = lead_status ? QString::number(lead_d_rel, 'f', 0) : "-"; - QColor color = QColor(255, 255, 255, 255); - - if (lead_status) { - // Orange if close, Red if very close - if (lead_d_rel < 5) { - color = QColor(255, 0, 0, 255); - } else if (lead_d_rel < 15) { - color = QColor(255, 188, 0, 255); - } - } - - return UiElement(value, "REL DIST", "m", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getVRel() { - QString value = lead_status ? QString::number(lead_v_rel * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-"; - QColor color = QColor(255, 255, 255, 255); - - if (lead_status) { - // Red if approaching faster than 10mph - // Orange if approaching (negative) - if (lead_v_rel < -4.4704) { - color = QColor(255, 0, 0, 255); - } else if (lead_v_rel < 0) { - color = QColor(255, 188, 0, 255); - } - } - - return UiElement(value, "REL SPEED", speedUnit, color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getSteeringAngleDeg() { - QString value = QString("%1%2%3").arg(QString::number(angleSteers, 'f', 1)).arg("°").arg(""); - QColor color = (madsEnabled && latActive) ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); - - // Red if large steering angle - // Orange if moderate steering angle - if (std::fabs(angleSteers) > 180) { - color = QColor(255, 0, 0, 255); - } else if (std::fabs(angleSteers) > 90) { - color = QColor(255, 188, 0, 255); - } - - return UiElement(value, "REAL STEER", "", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getActualLateralAccel() { - float actualLateralAccel = (curvature * pow(vEgo, 2)) - (roll * 9.81); - - QString value = QString::number(actualLateralAccel, 'f', 2); - QColor color = (madsEnabled && latActive) ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); - - return UiElement(value, "ACTUAL LAT", "m/s²", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getSteeringAngleDesiredDeg() { - QString value = (madsEnabled && latActive) ? QString("%1%2%3").arg(QString::number(steerAngleDesired, 'f', 1)).arg("°").arg("") : "-"; - QColor color = QColor(255, 255, 255, 255); - - if (madsEnabled && latActive) { - // Red if large steering angle - // Orange if moderate steering angle - if (std::fabs(angleSteers) > 180) { - color = QColor(255, 0, 0, 255); - } else if (std::fabs(angleSteers) > 90) { - color = QColor(255, 188, 0, 255); - } else { - color = QColor(0, 255, 0, 255); - } - } - - return UiElement(value, "DESIRED STEER", "", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getMemoryUsagePercent() { - QString value = QString("%1%2").arg(QString::number(memoryUsagePercent, 'd', 0)).arg("%"); - QColor color = (memoryUsagePercent > 85) ? QColor(255, 188, 0, 255) : QColor(255, 255, 255, 255); - - return UiElement(value, "RAM", "", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getAEgo() { - QString value = QString::number(aEgo, 'f', 1); - QColor color = QColor(255, 255, 255, 255); - - return UiElement(value, "ACC.", "m/s²", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getVEgoLead() { - QString value = lead_status ? QString::number((lead_v_rel + vEgo) * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-"; - QColor color = QColor(255, 255, 255, 255); - - if (lead_status) { - // Red if approaching faster than 10mph - // Orange if approaching (negative) - if (lead_v_rel < -4.4704) { - color = QColor(255, 0, 0, 255); - } else if (lead_v_rel < 0) { - color = QColor(255, 188, 0, 255); - } - } - - return UiElement(value, "L.S.", speedUnit, color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getFrictionCoefficientFiltered() { - QString value = QString::number(frictionCoefficientFiltered, 'f', 3); - QColor color = liveValid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); - - return UiElement(value, "FRIC.", "", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getLatAccelFactorFiltered() { - QString value = QString::number(latAccelFactorFiltered, 'f', 3); - QColor color = liveValid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); - - return UiElement(value, "L.A.", "m/s²", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getSteeringTorqueEps() { - QString value = QString::number(std::fabs(steeringTorqueEps), 'f', 1); - QColor color = QColor(255, 255, 255, 255); - - return UiElement(value, "E.T.", "N·dm", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getBearingDeg() { - QString value = (bearingAccuracyDeg != 180.00) ? QString("%1%2%3").arg(QString::number(bearingDeg, 'd', 0)).arg("°").arg("") : "-"; - QColor color = QColor(255, 255, 255, 255); - QString dir_value; - - if (bearingAccuracyDeg != 180.00) { - if (((bearingDeg >= 337.5) && (bearingDeg <= 360)) || ((bearingDeg >= 0) && (bearingDeg <= 22.5))) { - dir_value = "N"; - } else if ((bearingDeg > 22.5) && (bearingDeg < 67.5)) { - dir_value = "NE"; - } else if ((bearingDeg >= 67.5) && (bearingDeg <= 112.5)) { - dir_value = "E"; - } else if ((bearingDeg > 112.5) && (bearingDeg < 157.5)) { - dir_value = "SE"; - } else if ((bearingDeg >= 157.5) && (bearingDeg <= 202.5)) { - dir_value = "S"; - } else if ((bearingDeg > 202.5) && (bearingDeg < 247.5)) { - dir_value = "SW"; - } else if ((bearingDeg >= 247.5) && (bearingDeg <= 292.5)) { - dir_value = "W"; - } else if ((bearingDeg > 292.5) && (bearingDeg < 337.5)) { - dir_value = "NW"; - } - } else { - dir_value = "OFF"; - } - - return UiElement(QString("%1 | %2").arg(dir_value).arg(value), "B.D.", "", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getAltitude() { - QString value = (gpsAccuracy != 0.00) ? QString::number(altitude, 'f', 1) : "-"; - QColor color = QColor(255, 255, 255, 255); - - return UiElement(value, "ALT.", "m", color); -} - -void AnnotatedCameraWidget::drawRightDevUi(QPainter &p, int x, int y) { - int rh = 5; - int ry = y; - - // Add Relative Distance to Primary Lead Car - // Unit: Meters - UiElement dRelElement = getDRel(); - rh += drawDevUiElementRight(p, x, ry, dRelElement.value, dRelElement.label, dRelElement.units, dRelElement.color); - ry = y + rh; - - // Add Relative Velocity vs Primary Lead Car - // Unit: kph if metric, else mph - UiElement vRelElement = getVRel(); - rh += drawDevUiElementRight(p, x, ry, vRelElement.value, vRelElement.label, vRelElement.units, vRelElement.color); - ry = y + rh; - - // Add Real Steering Angle - // Unit: Degrees - UiElement steeringAngleDegElement = getSteeringAngleDeg(); - rh += drawDevUiElementRight(p, x, ry, steeringAngleDegElement.value, steeringAngleDegElement.label, steeringAngleDegElement.units, steeringAngleDegElement.color); - ry = y + rh; - - if (lateralState == "torque") { - // Add Actual Lateral Acceleration (roll compensated) when using Torque - // Unit: m/s² - UiElement actualLateralAccelElement = getActualLateralAccel(); - rh += drawDevUiElementRight(p, x, ry, actualLateralAccelElement.value, actualLateralAccelElement.label, actualLateralAccelElement.units, actualLateralAccelElement.color); - } else { - // Add Desired Steering Angle when using PID - // Unit: Degrees - UiElement steeringAngleDesiredDegElement = getSteeringAngleDesiredDeg(); - rh += drawDevUiElementRight(p, x, ry, steeringAngleDesiredDegElement.value, steeringAngleDesiredDegElement.label, steeringAngleDesiredDegElement.units, steeringAngleDesiredDegElement.color); - } - ry = y + rh; - - // Add Device Memory (RAM) Usage - // Unit: Percent - UiElement memoryUsagePercentElement = getMemoryUsagePercent(); - rh += drawDevUiElementRight(p, x, ry, memoryUsagePercentElement.value, memoryUsagePercentElement.label, memoryUsagePercentElement.units, memoryUsagePercentElement.color); - ry = y + rh; - - rh += 25; - p.setBrush(QColor(0, 0, 0, 0)); - QRect ldu(x, y, 184, rh); -} - -int AnnotatedCameraWidget::drawNewDevUiElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) { - p.setFont(InterFont(38, QFont::Bold)); - drawCenteredLeftText(p, x, y, label, whiteColor(), value, units, color); - - return 430; -} - -void AnnotatedCameraWidget::drawNewDevUi2(QPainter &p, int x, int y) { - int rw = 90; - - // Add Acceleration from Car - // Unit: Meters per Second Squared - UiElement aEgoElement = getAEgo(); - rw += drawNewDevUiElement(p, rw, y, aEgoElement.value, aEgoElement.label, aEgoElement.units, aEgoElement.color); - - // Add Velocity of Primary Lead Car - // Unit: kph if metric, else mph - UiElement vEgoLeadElement = getVEgoLead(); - rw += drawNewDevUiElement(p, rw, y, vEgoLeadElement.value, vEgoLeadElement.label, vEgoLeadElement.units, vEgoLeadElement.color); - - if (torquedUseParams) { - // Add Friction Coefficient Raw from torqued - // Unit: None - UiElement frictionCoefficientFilteredElement = getFrictionCoefficientFiltered(); - rw += drawNewDevUiElement(p, rw, y, frictionCoefficientFilteredElement.value, frictionCoefficientFilteredElement.label, frictionCoefficientFilteredElement.units, frictionCoefficientFilteredElement.color); - - // Add Lateral Acceleration Factor Raw from torqued - // Unit: m/s² - UiElement latAccelFactorFilteredElement = getLatAccelFactorFiltered(); - rw += drawNewDevUiElement(p, rw, y, latAccelFactorFilteredElement.value, latAccelFactorFilteredElement.label, latAccelFactorFilteredElement.units, latAccelFactorFilteredElement.color); - } else { - // Add Steering Torque from Car EPS - // Unit: Newton Meters - UiElement steeringTorqueEpsElement = getSteeringTorqueEps(); - rw += drawNewDevUiElement(p, rw, y, steeringTorqueEpsElement.value, steeringTorqueEpsElement.label, steeringTorqueEpsElement.units, steeringTorqueEpsElement.color); - - // Add Bearing Degree and Direction from Car (Compass) - // Unit: Meters - UiElement bearingDegElement = getBearingDeg(); - rw += drawNewDevUiElement(p, rw, y, bearingDegElement.value, bearingDegElement.label, bearingDegElement.units, bearingDegElement.color); - } - - // Add Altitude of Current Location - // Unit: Meters - UiElement altitudeElement = getAltitude(); - rw += drawNewDevUiElement(p, rw, y, altitudeElement.value, altitudeElement.label, altitudeElement.units, altitudeElement.color); -} - -// ############################## DEV UI END ############################## - -void AnnotatedCameraWidget::drawE2eStatus(QPainter &p, int x, int y, int w, int h, int e2e_long_status) { - QColor status_color; - QRect e2eStatusIcon(x, y, w, h); - p.setPen(Qt::NoPen); - p.setBrush(QBrush(blackColor(70))); - p.drawEllipse(e2eStatusIcon); - e2eStatusIcon -= QMargins(25, 25, 25, 25); - p.setPen(Qt::NoPen); - if (e2e_long_status == 2) { - status_color = QColor::fromRgbF(0.0, 1.0, 0.0, 0.9); - } else if (e2e_long_status == 1) { - status_color = QColor::fromRgbF(1.0, 0.0, 0.0, 0.9); - } - p.setBrush(QBrush(status_color)); - p.drawEllipse(e2eStatusIcon); -} - -void AnnotatedCameraWidget::drawLeftTurnSignal(QPainter &painter, int x, int y, int circle_size, int state) { - painter.setRenderHint(QPainter::Antialiasing, true); - - QColor circle_color, circle_color_0, circle_color_1; - QColor arrow_color, arrow_color_0, arrow_color_1; - if ((left_blindspot || lane_change_edge_block) && !(left_blinker && right_blinker)) { - circle_color_0 = QColor(164, 0, 1); - circle_color_1 = QColor(204, 0, 1); - arrow_color_0 = QColor(72, 1, 1); - arrow_color_1 = QColor(255, 255, 255); - } else { - circle_color_0 = QColor(22, 156, 69); - circle_color_1 = QColor(30, 215, 96); - arrow_color_0 = QColor(9, 56, 27); - arrow_color_1 = QColor(255, 255, 255); - } - - if (state == 1) { - circle_color = circle_color_1; - arrow_color = arrow_color_1; - } else if (state == 0) { - circle_color = circle_color_0; - arrow_color = arrow_color_0; - } - - // Draw the circle - int circleX = x; - int circleY = y; - painter.setPen(Qt::NoPen); - painter.setBrush(circle_color); - painter.drawEllipse(circleX, circleY, circle_size, circle_size); - - // Draw the arrow - int arrowSize = 50; - int arrowX = circleX + (circle_size - arrowSize) / 4; - int arrowY = circleY + (circle_size - arrowSize) / 2; - painter.setPen(Qt::NoPen); - painter.setBrush(arrow_color); - - // Draw the arrow shape - QPolygon arrowPolygon; - arrowPolygon << QPoint(arrowX + 10, arrowY + arrowSize / 2) - << QPoint(arrowX + arrowSize - 3, arrowY) - << QPoint(arrowX + arrowSize, arrowY) - << QPoint(arrowX + arrowSize, arrowY + arrowSize) - << QPoint(arrowX + arrowSize - 3, arrowY + arrowSize) - << QPoint(arrowX + 10, arrowY + arrowSize / 2); - painter.drawPolygon(arrowPolygon); - - // Draw the tail rectangle - int tailWidth = arrowSize / 2.25; - int tailHeight = arrowSize / 2; - QRect tailRect(arrowX + arrowSize - 3, arrowY + arrowSize / 4, tailWidth, tailHeight); - painter.fillRect(tailRect, arrow_color); -} - -void AnnotatedCameraWidget::drawRightTurnSignal(QPainter &painter, int x, int y, int circle_size, int state) { - painter.setRenderHint(QPainter::Antialiasing, true); - - QColor circle_color, circle_color_0, circle_color_1; - QColor arrow_color, arrow_color_0, arrow_color_1; - if ((right_blindspot || lane_change_edge_block) && !(left_blinker && right_blinker)) { - circle_color_0 = QColor(164, 0, 1); - circle_color_1 = QColor(204, 0, 1); - arrow_color_0 = QColor(72, 1, 1); - arrow_color_1 = QColor(255, 255, 255); - } else { - circle_color_0 = QColor(22, 156, 69); - circle_color_1 = QColor(30, 215, 96); - arrow_color_0 = QColor(9, 56, 27); - arrow_color_1 = QColor(255, 255, 255); - } - - if (state == 1) { - circle_color = circle_color_1; - arrow_color = arrow_color_1; - } else if (state == 0) { - circle_color = circle_color_0; - arrow_color = arrow_color_0; - } - - - // Draw the circle - int circleX = x; - int circleY = y; - painter.setPen(Qt::NoPen); - painter.setBrush(circle_color); - painter.drawEllipse(circleX, circleY, circle_size, circle_size); - - // Draw the arrow - int arrowSize = 50; - int arrowX = circleX + (circle_size - arrowSize) / 2 + (arrowSize / 2.5) - 3; - int arrowY = circleY + (circle_size - arrowSize) / 2; - painter.setPen(Qt::NoPen); - painter.setBrush(arrow_color); - - // Draw the arrow shape - QPolygon arrowPolygon; - arrowPolygon << QPoint(arrowX + arrowSize - 10, arrowY + arrowSize / 2) - << QPoint(arrowX + 3, arrowY) - << QPoint(arrowX, arrowY) - << QPoint(arrowX, arrowY + arrowSize) - << QPoint(arrowX + 3, arrowY + arrowSize) - << QPoint(arrowX + arrowSize - 10, arrowY + arrowSize / 2); - painter.drawPolygon(arrowPolygon); - - // Draw the tail rectangle - int tailWidth = arrowSize / 2.25; - int tailHeight = arrowSize / 2; - QRect tailRect(arrowX - tailWidth + 3, arrowY + arrowSize / 4, tailWidth, tailHeight); - painter.fillRect(tailRect, arrow_color); -} - -int AnnotatedCameraWidget::blinkerPulse(int frame) { - if (frame % UI_FREQ < (UI_FREQ / 2)) { - blinker_state = 1; - } else { - blinker_state = 0; - } - - return blinker_state; -} - -void AnnotatedCameraWidget::speedLimitSignPulse(int frame) { - if (frame % UI_FREQ < (UI_FREQ / 2.5)) { - slcShowSign = false; - } else { - slcShowSign = true; - } -} - -void AnnotatedCameraWidget::drawFeatureStatusText(QPainter &p, int x, int y) { - const FeatureStatusText feature_text; - const FeatureStatusColor feature_color; - const QColor text_color = whiteColor(); - const QColor shadow_color = blackColor(38); - const int text_height = 34; - const int drop_shadow_size = 2; - const int eclipse_x_offset = 25; - const int eclipse_y_offset = 20; - const int w = 16; - const int h = 16; - - const bool longitudinal = hasLongitudinalControl(car_params); - - p.setFont(InterFont(32, QFont::Bold)); - - // Define a function to draw a feature status button - auto drawFeatureStatusElement = [&](int value, const QStringList& text_list, const QStringList& color_list, bool condition, const QString& off_text, const QString& label) { - std::pair feature_status = getFeatureStatus(value, text_list, color_list, condition, off_text); - QRect btn(x - eclipse_x_offset, y - eclipse_y_offset, w, h); - QRect btn_shadow(x - eclipse_x_offset + drop_shadow_size, y - eclipse_y_offset + drop_shadow_size, w, h); - p.setPen(Qt::NoPen); - p.setBrush(shadow_color); - p.drawEllipse(btn_shadow); - p.setBrush(feature_status.second); - p.drawEllipse(btn); - QString status_text; - status_text.sprintf("%s: %s", label.toStdString().c_str(), (feature_status.first).toStdString().c_str()); - p.setPen(QPen(shadow_color, 2)); - p.drawText(x + drop_shadow_size, y + drop_shadow_size, status_text); - p.setPen(QPen(text_color, 2)); - p.drawText(x, y, status_text); - y += text_height; - }; - - // Driving Personality / Gap Adjust Cruise - if (longitudinal) { - drawFeatureStatusElement(longitudinalPersonality, feature_text.gac_list_text, feature_color.gac_list_color, longitudinal, "N/A", "GAP"); - } - - // Dynamic Lane Profile - if (drivingModelGen == cereal::ModelGeneration::ONE) { - drawFeatureStatusElement(dynamicLaneProfile, feature_text.dlp_list_text, feature_color.dlp_list_color, true, "OFF", "DLP"); - } - - // TODO: Add toggle variables to cereal, and parse from cereal - if (longitudinal) { - QColor dec_color((cruiseStateEnabled && dynamicExperimentalControlToggle) ? "#4bff66" : "#ffffff"); - QRect dec_btn(x - eclipse_x_offset, y - eclipse_y_offset, w, h); - QRect dec_btn_shadow(x - eclipse_x_offset + drop_shadow_size, y - eclipse_y_offset + drop_shadow_size, w, h); - p.setPen(Qt::NoPen); - p.setBrush(shadow_color); - p.drawEllipse(dec_btn_shadow); - p.setBrush(dec_color); - p.drawEllipse(dec_btn); - QString dec_status_text; - dec_status_text.sprintf("DEC: %s\n", dynamicExperimentalControlToggle ? (experimentalMode ? QString(mpcMode).toStdString().c_str() : QString("Inactive").toStdString().c_str()) : "OFF"); - p.setPen(QPen(shadow_color, 2)); - p.drawText(x + drop_shadow_size, y + drop_shadow_size, dec_status_text); - p.setPen(QPen(text_color, 2)); - p.drawText(x, y, dec_status_text); - y += text_height; - } - - // TODO: Add toggle variables to cereal, and parse from cereal - // Speed Limit Control - if (longitudinal || !car_params.getPcmCruiseSpeed()) { - drawFeatureStatusElement(int(slcState), feature_text.slc_list_text, feature_color.slc_list_color, speedLimitControlToggle, "OFF", "SLC"); - } -} - -void AnnotatedCameraWidget::speedLimitWarning(QPainter &p, QRect sign_rect, const int sign_margin) { - // PRE ACTIVE - if (slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::PRE_ACTIVE) { - int set_speed = std::nearbyint(setSpeed); - int speed_limit_offsetted = std::nearbyint(speedLimitSLC + speedLimitSLCOffset); - - // Calculate the vertical offset using a sinusoidal function for smooth bouncing - double bounce_frequency = 2.0 * M_PI / 20.0; // 20 frames for one full oscillation - int bounce_offset = 20 * sin(speed_limit_frame * bounce_frequency); // Adjust the amplitude (20 pixels) as needed - - if (set_speed < speed_limit_offsetted) { - QPoint iconPosition(sign_rect.right() + sign_margin * 3, sign_rect.center().y() - plus_arrow_up_img.height() / 2 + bounce_offset); - p.drawPixmap(iconPosition, plus_arrow_up_img); - } else if (set_speed > speed_limit_offsetted) { - QPoint iconPosition(sign_rect.right() + sign_margin * 3, sign_rect.center().y() - minus_arrow_down_img.height() / 2 - bounce_offset); - p.drawPixmap(iconPosition, minus_arrow_down_img); - } - - speed_limit_frame++; - speedLimitSignPulse(speed_limit_frame); - } - - // current speed over speed limit - else if (overSpeedLimit && speedLimitWarningFlash) { - speed_limit_frame++; - speedLimitSignPulse(speed_limit_frame); - } - - else { - speed_limit_frame = 0; - slcShowSign = true; - } -} - - void AnnotatedCameraWidget::initializeGL() { CameraWidget::initializeGL(); qInfo() << "OpenGL version:" << QString((const char*)glGetString(GL_VERSION)); @@ -1309,29 +155,12 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { const UIScene &scene = s->scene; SubMaster &sm = *(s->sm); - const auto car_state = sm["carState"].getCarState(); - - // Shane's colored lanelines + // lanelines for (int i = 0; i < std::size(scene.lane_line_vertices); ++i) { - if (i == 1 || i == 2) { - // TODO: can we just use the projected vertices somehow? - const cereal::XYZTData::Reader &line = (*s->sm)["modelV2"].getModelV2().getLaneLines()[i]; - const float default_pos = 1.4; // when lane poly isn't available - const float lane_pos = line.getY().size() > 0 ? std::abs(line.getY()[5]) : default_pos; // get redder when line is closer to car - float hue = 332.5 * lane_pos - 332.5; // equivalent to {1.4, 1.0}: {133, 0} (green to red) - hue = std::fmin(133, fmax(0, hue)) / 360.; // clip and normalize - painter.setBrush(QColor::fromHslF(hue, 1.0, 0.50, std::clamp(scene.lane_line_probs[i], 0.0, 0.7))); - } else { - painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp(scene.lane_line_probs[i], 0.0, 0.7))); - } + painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp(scene.lane_line_probs[i], 0.0, 0.7))); painter.drawPolygon(scene.lane_line_vertices[i]); } - // TODO: Fix empty spaces when curiving back on itself - painter.setBrush(QColor::fromRgbF(1.0, 0.0, 0.0, 0.2)); - if (left_blindspot) painter.drawPolygon(scene.lane_barrier_vertices[0]); - if (right_blindspot) painter.drawPolygon(scene.lane_barrier_vertices[1]); - // road edges for (int i = 0; i < std::size(scene.road_edge_vertices); ++i) { painter.setBrush(QColor::fromRgbF(1.0, 0, 0, std::clamp(1.0 - scene.road_edge_stds[i], 0.0, 1.0))); @@ -1340,70 +169,41 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { // paint path QLinearGradient bg(0, height(), 0, 0); - if (madsEnabled || car_state.getCruiseState().getEnabled()) { - if (steerOverride && latActive) { - bg.setColorAt(0.0, QColor::fromHslF(20 / 360., 0.94, 0.51, 0.17)); - bg.setColorAt(0.5, QColor::fromHslF(20 / 360., 1.0, 0.68, 0.17)); - bg.setColorAt(1.0, QColor::fromHslF(20 / 360., 1.0, 0.68, 0.0)); - } else if (!(latActive || car_state.getCruiseState().getEnabled())) { - bg.setColorAt(0, whiteColor()); - bg.setColorAt(1, whiteColor(0)); - } else if (sm["controlsState"].getControlsState().getExperimentalMode()) { - // The first half of track_vertices are the points for the right side of the path - const auto &acceleration = sm["modelV2"].getModelV2().getAcceleration().getX(); - const int max_len = std::min(scene.track_vertices.length() / 2, acceleration.size()); + if (sm["controlsState"].getControlsState().getExperimentalMode()) { + // The first half of track_vertices are the points for the right side of the path + const auto &acceleration = sm["modelV2"].getModelV2().getAcceleration().getX(); + const int max_len = std::min(scene.track_vertices.length() / 2, acceleration.size()); - for (int i = 0; i < max_len; ++i) { - // Some points are out of frame - if (scene.track_vertices[i].y() < 0 || scene.track_vertices[i].y() > height()) continue; + for (int i = 0; i < max_len; ++i) { + // Some points are out of frame + if (scene.track_vertices[i].y() < 0 || scene.track_vertices[i].y() > height()) continue; - // Flip so 0 is bottom of frame - float lin_grad_point = (height() - scene.track_vertices[i].y()) / height(); + // Flip so 0 is bottom of frame + float lin_grad_point = (height() - scene.track_vertices[i].y()) / height(); - // speed up: 120, slow down: 0 - float path_hue = fmax(fmin(60 + acceleration[i] * 35, 120), 0); - // FIXME: painter.drawPolygon can be slow if hue is not rounded - path_hue = int(path_hue * 100 + 0.5) / 100; + // speed up: 120, slow down: 0 + float path_hue = fmax(fmin(60 + acceleration[i] * 35, 120), 0); + // FIXME: painter.drawPolygon can be slow if hue is not rounded + path_hue = int(path_hue * 100 + 0.5) / 100; - float saturation = fmin(fabs(acceleration[i] * 1.5), 1); - float lightness = util::map_val(saturation, 0.0f, 1.0f, 0.95f, 0.62f); // lighter when grey - float alpha = util::map_val(lin_grad_point, 0.75f / 2.f, 0.75f, 0.4f, 0.0f); // matches previous alpha fade - bg.setColorAt(lin_grad_point, QColor::fromHslF(path_hue / 360., saturation, lightness, alpha)); + float saturation = fmin(fabs(acceleration[i] * 1.5), 1); + float lightness = util::map_val(saturation, 0.0f, 1.0f, 0.95f, 0.62f); // lighter when grey + float alpha = util::map_val(lin_grad_point, 0.75f / 2.f, 0.75f, 0.4f, 0.0f); // matches previous alpha fade + bg.setColorAt(lin_grad_point, QColor::fromHslF(path_hue / 360., saturation, lightness, alpha)); - // Skip a point, unless next is last - i += (i + 2) < max_len ? 1 : 0; - } - - } else { - bg.setColorAt(0.0, QColor::fromHslF(148 / 360., 0.94, 0.51, 0.4)); - bg.setColorAt(0.5, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.35)); - bg.setColorAt(1.0, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.0)); + // Skip a point, unless next is last + i += (i + 2) < max_len ? 1 : 0; } + } else { - bg.setColorAt(0.0, whiteColor(102)); - bg.setColorAt(0.5, whiteColor(89)); - bg.setColorAt(1.0, whiteColor(0)); + bg.setColorAt(0.0, QColor::fromHslF(148 / 360., 0.94, 0.51, 0.4)); + bg.setColorAt(0.5, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.35)); + bg.setColorAt(1.0, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.0)); } painter.setBrush(bg); painter.drawPolygon(scene.track_vertices); - // create path combining track vertices and track edge vertices - QPainterPath path; - path.addPolygon(scene.track_vertices); - path.addPolygon(scene.track_edge_vertices); - - // paint path edges - QLinearGradient pe(0, height(), 0, height() / 4); - if (!scene.dynamic_lane_profile_status) { - pe.setColorAt(0.0, QColor::fromHslF(240 / 360., 0.94, 0.51, 1.0)); - pe.setColorAt(0.5, QColor::fromHslF(204 / 360., 1.0, 0.68, 0.5)); - pe.setColorAt(1.0, QColor::fromHslF(204 / 360., 1.0, 0.68, 0.0)); - - painter.setBrush(pe); - painter.drawPath(path); - } - painter.restore(); } @@ -1415,7 +215,7 @@ void AnnotatedCameraWidget::drawDriverState(QPainter &painter, const UIState *s) // base icon int offset = UI_BORDER_SIZE + btn_size / 2; int x = rightHandDM ? width() - offset : offset; - int y = height() - offset - rn_offset; + int y = height() - offset; float opacity = dmActive ? 0.65 : 0.2; drawIcon(painter, QPoint(x, y), dm_img, blackColor(70), opacity); @@ -1448,56 +248,13 @@ void AnnotatedCameraWidget::drawDriverState(QPainter &painter, const UIState *s) painter.restore(); } -void AnnotatedCameraWidget::rocketFuel(QPainter &p) { - - static const int ct_n = 1; - static float ct; - - int rect_w = rect().width(); - int rect_h = rect().height(); - - const int n = 15 + 1; //Add one off screen due to timing issues - static float t[n]; - int dim_n = (sin(ct / 5) + 1) * (n - 0.01); - t[dim_n] = 1.0; - t[(int)(ct/ct_n)] = 1.0; - - UIState *s = uiState(); - float vc_accel0 = (*s->sm)["carState"].getCarState().getAEgo(); - static float vc_accel; - vc_accel = vc_accel + (vc_accel0 - vc_accel) / 5; - float hha = 0; - if (vc_accel > 0) { - hha = 0.85 - 0.1 / vc_accel; // only extend up to 85% - p.setBrush(QColor(0, 245, 0, 200)); - } - if (vc_accel < 0) { - hha = 0.85 + 0.1 / vc_accel; // only extend up to 85% - p.setBrush(QColor(245, 0, 0, 200)); - } - if (hha < 0) { - hha = 0; - } - hha = hha * rect_h; - float wp = 28; - if (vc_accel > 0) { - QRect ra = QRect(rect_w - wp, rect_h / 2 - hha / 2, wp, hha / 2); - p.drawRect(ra); - } else { - QRect ra = QRect(rect_w - wp, rect_h / 2, wp, hha / 2); - p.drawRect(ra); - } -} - -void AnnotatedCameraWidget::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 AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd) { painter.save(); const float speedBuff = 10.; const float leadBuff = 40.; const float d_rel = lead_data.getDRel(); const float v_rel = lead_data.getVRel(); - const float v_ego = car_data.getVEgo(); float fillAlpha = 0; if (d_rel < leadBuff) { @@ -1524,62 +281,15 @@ void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState painter.setBrush(redColor(fillAlpha)); painter.drawPolygon(chevron, std::size(chevron)); - if (num == 0) { // Display metrics to the 0th lead car - const int chevron_types = 3; - const int chevron_all = chevron_types + 1; // All metrics - QStringList chevron_text[chevron_types]; - int position; - float val; - if (chevron_data == 1 || chevron_data == chevron_all) { - position = 0; - val = std::max(0.0f, d_rel); - chevron_text[position].append(QString::number(val,'f', 0) + " " + "m"); - } - if (chevron_data == 2 || chevron_data == chevron_all) { - position = (chevron_data == 2) ? 0 : 1; - val = std::max(0.0f, (v_rel + v_ego) * (is_metric ? static_cast(MS_TO_KPH) : static_cast(MS_TO_MPH))); - chevron_text[position].append(QString::number(val,'f', 0) + " " + (is_metric ? "km/h" : "mph")); - } - if (chevron_data == 3 || chevron_data == chevron_all) { - position = (chevron_data == 3) ? 0 : 2; - val = (d_rel > 0 && v_ego > 0) ? std::max(0.0f, d_rel / v_ego) : 0.0f; - - QString ttc_str = (val > 0 && val < 200) ? QString::number(val, 'f', 1) + "s" : "---"; - chevron_text[position].append(ttc_str); - } - - float str_w = 200; // Width of the text box, might need adjustment - float str_h = 50; // Height of the text box, adjust as necessary - painter.setFont(InterFont(45, QFont::Bold)); - // Calculate the center of the chevron and adjust the text box position - float text_y = y + sz + 12; // Position the text at the bottom of the chevron - QRect textRect(x - str_w / 2, text_y, str_w, str_h); // Adjust the rectangle to center the text horizontally at the chevron's bottom - QPoint shadow_offset(2, 2); - for (int i = 0; i < chevron_types; ++i) { - if (!chevron_text[i].isEmpty()) { - painter.setPen(QColor(0x0, 0x0, 0x0, 200)); // Draw shadow - painter.drawText(textRect.translated(shadow_offset.x(), shadow_offset.y() + i * str_h), Qt::AlignBottom | Qt::AlignHCenter, chevron_text[i].at(0)); - painter.setPen(QColor(0xff, 0xff, 0xff)); // Draw text - painter.drawText(textRect.translated(0, i * str_h), Qt::AlignBottom | Qt::AlignHCenter, chevron_text[i].at(0)); - painter.setPen(Qt::NoPen); // Reset pen to default - } - } - } - painter.restore(); } void AnnotatedCameraWidget::paintGL() { -} - -void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { UIState *s = uiState(); SubMaster &sm = *(s->sm); const double start_draw_t = millis_since_boot(); const cereal::ModelDataV2::Reader &model = sm["modelV2"].getModelV2(); - QPainter painter(this); - // draw camera frame { std::lock_guard lk(frame_lock); @@ -1601,10 +311,9 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { bool has_wide_cam = available_streams.count(VISION_STREAM_WIDE_ROAD); if (has_wide_cam) { float v_ego = sm["carState"].getCarState().getVEgo(); - float steer_angle = sm["carState"].getCarState().getSteeringAngleDeg(); - if ((v_ego < 10) || available_streams.size() == 1 || (std::fabs(steer_angle) > 45)) { + if ((v_ego < 10) || available_streams.size() == 1) { wide_cam_requested = true; - } else if ((v_ego > 15) && (std::fabs(steer_angle) < 30)) { + } else if (v_ego > 15) { wide_cam_requested = false; } wide_cam_requested = wide_cam_requested && sm["controlsState"].getControlsState().getExperimentalMode(); @@ -1613,10 +322,6 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { } CameraWidget::setStreamType(wide_cam_requested ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD); - if (reversing && s->scene.reverse_dm_cam) { - CameraWidget::setStreamType(VISION_STREAM_DRIVER, s->scene.reverse_dm_cam); - } - s->scene.wide_cam = CameraWidget::getStreamType() == VISION_STREAM_WIDE_ROAD; if (s->scene.calibration_valid) { auto calib = s->scene.wide_cam ? s->scene.view_from_wide_calib : s->scene.view_from_calib; @@ -1624,12 +329,11 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { } else { CameraWidget::updateCalibration(DEFAULT_CALIBRATION); } - painter.beginNativePainting(); CameraWidget::setFrameId(model.getFrameId()); CameraWidget::paintGL(); - painter.endNativePainting(); } + QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::NoPen); @@ -1639,18 +343,15 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { if (s->scene.longitudinal_control && sm.rcv_frame("radarState") > s->scene.started_frame) { auto radar_state = sm["radarState"].getRadarState(); - auto car_state = sm["carState"].getCarState(); update_leads(s, radar_state, model.getPosition()); auto lead_one = radar_state.getLeadOne(); auto lead_two = radar_state.getLeadTwo(); if (lead_one.getStatus()) { - drawLead(painter, lead_one, s->scene.lead_vertices[0], 0, car_state, s->scene.chevron_data); + drawLead(painter, lead_one, s->scene.lead_vertices[0]); } if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) { - drawLead(painter, lead_two, s->scene.lead_vertices[1], 1, car_state, s->scene.chevron_data); + drawLead(painter, lead_two, s->scene.lead_vertices[1]); } - - rocketFuel(painter); } } @@ -1662,21 +363,6 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { drawHud(painter); - if (left_blinker || right_blinker) { - blinker_frame++; - int state = blinkerPulse(blinker_frame); - int blinker_x = splitPanelVisible ? 150 : 180; - int blinker_y = splitPanelVisible ? 220 : 90; - if (left_blinker) { - drawLeftTurnSignal(painter, rect().center().x() - (blinker_x + blinker_size), blinker_y, blinker_size, state); - } - if (right_blinker) { - drawRightTurnSignal(painter, rect().center().x() + blinker_x, blinker_y, blinker_size, state); - } - } else { - blinker_frame = 0; - } - double cur_draw_t = millis_since_boot(); double dt = cur_draw_t - prev_draw_t; double fps = fps_filter.update(1. / dt * 1000); @@ -1690,11 +376,6 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { auto m = msg.initEvent().initUiDebug(); m.setDrawTimeMillis(cur_draw_t - start_draw_t); pm->send("uiDebug", msg); - - MessageBuilder e2e_long_msg; - auto e2eLongStatus = e2e_long_msg.initEvent().initE2eLongStateSP(); - e2eLongStatus.setStatus(e2eStatus); - e2e_state->send("e2eLongStateSP", e2e_long_msg); } void AnnotatedCameraWidget::showEvent(QShowEvent *event) { diff --git a/selfdrive/ui/qt/onroad/annotated_camera.h b/selfdrive/ui/qt/onroad/annotated_camera.h index 29b9c4833b..465ba23e04 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.h +++ b/selfdrive/ui/qt/onroad/annotated_camera.h @@ -2,14 +2,10 @@ #include #include -#include #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 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 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; }; diff --git a/selfdrive/ui/qt/onroad/buttons.cc b/selfdrive/ui/qt/onroad/buttons.cc index fbca42da7b..92bcea11b5 100644 --- a/selfdrive/ui/qt/onroad/buttons.cc +++ b/selfdrive/ui/qt/onroad/buttons.cc @@ -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); -} diff --git a/selfdrive/ui/qt/onroad/buttons.h b/selfdrive/ui/qt/onroad/buttons.h index feee1ba639..e999480d5c 100644 --- a/selfdrive/ui/qt/onroad/buttons.h +++ b/selfdrive/ui/qt/onroad/buttons.h @@ -2,7 +2,11 @@ #include +#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); diff --git a/selfdrive/ui/qt/onroad/onroad_home.cc b/selfdrive/ui/qt/onroad/onroad_home.cc index 7413cdff7e..e93ded6cc2 100644 --- a/selfdrive/ui/qt/onroad/onroad_home.cc +++ b/selfdrive/ui/qt/onroad/onroad_home.cc @@ -3,12 +3,6 @@ #include #include -#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)); diff --git a/selfdrive/ui/qt/onroad/onroad_home.h b/selfdrive/ui/qt/onroad/onroad_home.h index 1beadcd13b..4ac37678fa 100644 --- a/selfdrive/ui/qt/onroad/onroad_home.h +++ b/selfdrive/ui/qt/onroad/onroad_home.h @@ -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); }; diff --git a/selfdrive/ui/qt/onroad_settings_panel.cc b/selfdrive/ui/qt/onroad_settings_panel.cc deleted file mode 100644 index 163f0fb31f..0000000000 --- a/selfdrive/ui/qt/onroad_settings_panel.cc +++ /dev/null @@ -1,26 +0,0 @@ -#include "selfdrive/ui/qt/onroad_settings_panel.h" - -#include -#include - -#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(); - } -} diff --git a/selfdrive/ui/qt/onroad_settings_panel.h b/selfdrive/ui/qt/onroad_settings_panel.h deleted file mode 100644 index 8b81aef25a..0000000000 --- a/selfdrive/ui/qt/onroad_settings_panel.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -#ifdef ENABLE_MAPS -#include -#endif -#include - -class OnroadSettingsPanel : public QFrame { - Q_OBJECT - -public: - explicit OnroadSettingsPanel(QWidget *parent = nullptr); - -signals: - void onroadSettingsPanelRequested(); - -public slots: - void toggleOnroadSettings(); - -private: - QStackedLayout *content_stack; -}; diff --git a/selfdrive/ui/qt/request_repeater.cc b/selfdrive/ui/qt/request_repeater.cc index 8212d3db94..d215fe42ca 100644 --- a/selfdrive/ui/qt/request_repeater.cc +++ b/selfdrive/ui/qt/request_repeater.cc @@ -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); - } } diff --git a/selfdrive/ui/qt/request_repeater.h b/selfdrive/ui/qt/request_repeater.h index 45cb643b13..32e714b1cb 100644 --- a/selfdrive/ui/qt/request_repeater.h +++ b/selfdrive/ui/qt/request_repeater.h @@ -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(); - } }; diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index 8d45e62ce4..615276be00 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -1,11 +1,8 @@ #include "selfdrive/ui/qt/sidebar.h" -#include - #include #include "selfdrive/ui/qt/util.h" -#include "common/params.h" void Sidebar::drawMetric(QPainter &p, const QPair &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::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::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); } diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index 9012f25135..62a5486809 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -4,8 +4,13 @@ #include #include +#include "cereal/messaging/messaging.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/ui.h" +#else #include "selfdrive/ui/ui.h" +#endif typedef QPair, 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 &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 pm; - - QString sidebar_temp = "0"; - QString sidebar_temp_str = "0"; }; diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 4b5f8ee21e..21ec5eedcf 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -4,41 +4,12 @@ #include #include #include -#include -#include -#include -#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 buffer{}; - std::ostringstream result; - std::unique_ptr 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 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 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::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(); diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc index 173d9ba970..886402b157 100644 --- a/selfdrive/ui/qt/util.cc +++ b/selfdrive/ui/qt/util.cc @@ -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 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 getDongleId() { - std::string id = Params().get("DongleId"); - - if (!id.empty() && (id != "UnregisteredDevice")) { - return QString::fromStdString(id); - } else { - return {}; - } + return getParamIgnoringDefault("DongleId", "UnregisteredDevice"); } -std::optional getSunnylinkDongleId() { - std::string id = Params().get("SunnylinkDongleId"); +QMap 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 map; + for (auto key : obj.keys()) { + map[key] = obj[key].toString(); } + return map; } QMap getSupportedLanguages() { - QFile f(":/languages.json"); - f.open(QIODevice::ReadOnly | QIODevice::Text); - QString val = f.readAll(); - - QJsonObject obj = QJsonDocument::fromJson(val.toUtf8()).object(); - QMap map; - for (auto key : obj.keys()) { - map[key] = obj[key].toString(); - } - return map; -} - -QMap 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 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 xp, std::vector 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 load_bootstrap_icons() { QHash 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()))); -} +} \ No newline at end of file diff --git a/selfdrive/ui/qt/util.h b/selfdrive/ui/qt/util.h index 438b9677e7..20443db468 100644 --- a/selfdrive/ui/qt/util.h +++ b/selfdrive/ui/qt/util.h @@ -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 getParamIgnoringDefault(const std::string ¶m_name, const std::string &default_value); std::optional getDongleId(); -std::optional getSunnylinkDongleId(); +QMap getFromJsonFile(const QString &path); QMap getSupportedLanguages(); -QMap 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 xp, std::vector fp); bool hasLongitudinalControl(const cereal::CarParams::Reader &car_params); struct InterFont : public QFont { diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index 526c17e77c..62999b2aa4 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -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); diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 238051cf5e..8af48185aa 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -2,20 +2,10 @@ #include #include - -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 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) { diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 3466b67f5e..aa304e0df6 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -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 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 &button_texts, const int minimum_button_width = 300) : SPAbstractControl(title, desc, icon), button_texts(button_texts) { + const std::vector &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::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(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 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 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(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 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); - } -}; diff --git a/selfdrive/ui/qt/widgets/scrollview.cc b/selfdrive/ui/qt/widgets/scrollview.cc index 7f6a5ff1cc..978bf83a63 100644 --- a/selfdrive/ui/qt/widgets/scrollview.cc +++ b/selfdrive/ui/qt/widgets/scrollview.cc @@ -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); -} diff --git a/selfdrive/ui/qt/widgets/scrollview.h b/selfdrive/ui/qt/widgets/scrollview.h index 7e4084412c..024331aa39 100644 --- a/selfdrive/ui/qt/widgets/scrollview.h +++ b/selfdrive/ui/qt/widgets/scrollview.h @@ -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; }; diff --git a/selfdrive/ui/qt/widgets/ssh_keys.h b/selfdrive/ui/qt/widgets/ssh_keys.h index 920bd651e2..2834164702 100644 --- a/selfdrive/ui/qt/widgets/ssh_keys.h +++ b/selfdrive/ui/qt/widgets/ssh_keys.h @@ -3,7 +3,13 @@ #include #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 { diff --git a/selfdrive/ui/qt/widgets/sunnypilot/drive_stats.h b/selfdrive/ui/qt/widgets/sunnypilot/drive_stats.h deleted file mode 100644 index 5e2d96b240..0000000000 --- a/selfdrive/ui/qt/widgets/sunnypilot/drive_stats.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include -#include - -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); -}; diff --git a/selfdrive/ui/qt/widgets/toggle.cc b/selfdrive/ui/qt/widgets/toggle.cc index c8f12bc272..82302ad5bc 100644 --- a/selfdrive/ui/qt/widgets/toggle.cc +++ b/selfdrive/ui/qt/widgets/toggle.cc @@ -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); } } diff --git a/selfdrive/ui/qt/widgets/toggle.h b/selfdrive/ui/qt/widgets/toggle.h index e7263a008f..a0fa434a4c 100644 --- a/selfdrive/ui/qt/widgets/toggle.h +++ b/selfdrive/ui/qt/widgets/toggle.h @@ -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; diff --git a/selfdrive/ui/qt/widgets/wifi.h b/selfdrive/ui/qt/widgets/wifi.h index 60c865f2b8..3010abc6ff 100644 --- a/selfdrive/ui/qt/widgets/wifi.h +++ b/selfdrive/ui/qt/widgets/wifi.h @@ -4,7 +4,11 @@ #include #include +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/ui.h" +#else #include "selfdrive/ui/ui.h" +#endif class WiFiPromptWidget : public QFrame { Q_OBJECT diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 6b6f939af4..d9ff51763b 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -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); - } } } diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h index 05b61e1f76..fa0c43486e 100644 --- a/selfdrive/ui/qt/window.h +++ b/selfdrive/ui/qt/window.h @@ -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; }; diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 9290fcea58..945c9ccdb2 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -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") diff --git a/selfdrive/ui/sunnypilot/qt/api.cc b/selfdrive/ui/sunnypilot/qt/api.cc new file mode 100644 index 0000000000..fcf925fd1a --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/api.cc @@ -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 +#include +#include + +#include +#include + +#include +#include + +#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(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(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); +} diff --git a/selfdrive/ui/sunnypilot/qt/api.h b/selfdrive/ui/sunnypilot/qt/api.h new file mode 100644 index 0000000000..b8e835dc91 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/api.h @@ -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); } +}; diff --git a/selfdrive/ui/sunnypilot/qt/common/json_fetcher.h b/selfdrive/ui/sunnypilot/qt/common/json_fetcher.h new file mode 100644 index 0000000000..2002e72aa0 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/common/json_fetcher.h @@ -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 +#include +#include +#include + +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; + } +}; diff --git a/selfdrive/ui/sunnypilot/qt/home.cc b/selfdrive/ui/sunnypilot/qt/home.cc new file mode 100644 index 0000000000..61abbff7dc --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/home.cc @@ -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 +#include +#include + +#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()); + } + } +} diff --git a/selfdrive/ui/sunnypilot/qt/home.h b/selfdrive/ui/sunnypilot/qt/home.h new file mode 100644 index 0000000000..8bb44d81b4 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/home.h @@ -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 +#include + +#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; +}; diff --git a/selfdrive/ui/sunnypilot/qt/maps/map.cc b/selfdrive/ui/sunnypilot/qt/maps/map.cc new file mode 100644 index 0000000000..f1d649980a --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/maps/map.cc @@ -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(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(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(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); +} diff --git a/selfdrive/ui/sunnypilot/qt/maps/map.h b/selfdrive/ui/sunnypilot/qt/maps/map.h new file mode 100644 index 0000000000..00c20e2ae7 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/maps/map.h @@ -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 + +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); +}; diff --git a/selfdrive/ui/sunnypilot/qt/maps/map_helpers.h b/selfdrive/ui/sunnypilot/qt/maps/map_helpers.h new file mode 100644 index 0000000000..464b35995c --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/maps/map_helpers.h @@ -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(); diff --git a/selfdrive/ui/sunnypilot/qt/network/networking.cc b/selfdrive/ui/sunnypilot/qt/network/networking.cc new file mode 100644 index 0000000000..bbe2cfb79c --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/networking.cc @@ -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 + +#include +#include +#include +#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 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); +} diff --git a/selfdrive/ui/sunnypilot/qt/network/networking.h b/selfdrive/ui/sunnypilot/qt/network/networking.h new file mode 100644 index 0000000000..931fe3443e --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/networking.h @@ -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 +#include + +#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 strengths; + ListWidgetSP *wifi_list_widget = nullptr; + std::vector 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); +}; diff --git a/selfdrive/ui/qt/network/sunnylink/models/role_model.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h similarity index 53% rename from selfdrive/ui/qt/network/sunnylink/models/role_model.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h index d7f3a98281..c1e28a5f9a 100644 --- a/selfdrive/ui/qt/network/sunnylink/models/role_model.h +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h @@ -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 @@ -56,5 +81,3 @@ public: return T(m_raw_json_object); } }; - -#endif diff --git a/selfdrive/ui/qt/network/sunnylink/models/sponsor_role_model.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h similarity index 67% rename from selfdrive/ui/qt/network/sunnylink/models/sponsor_role_model.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h index 64aad1ca46..6b6470f0cd 100644 --- a/selfdrive/ui/qt/network/sunnylink/models/sponsor_role_model.h +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h @@ -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 @@ -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 diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h new file mode 100644 index 0000000000..259d4b39b9 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h @@ -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 + +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; + } +}; diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.cc new file mode 100644 index 0000000000..13764c5749 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.cc @@ -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; + } +} diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h new file mode 100644 index 0000000000..dced4edb52 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h @@ -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(); +}; diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.cc new file mode 100644 index 0000000000..c835bf6f33 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.cc @@ -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 +#include + +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 roles; + for (const auto &value : jsonArray) { + roles.emplace_back(value.toObject()); + } + + emit rolesReady(roles); + uiStateSP()->setSunnylinkRoles(roles); +} diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h new file mode 100644 index 0000000000..91d59fe6a9 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h @@ -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 + +#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 &roles); + +protected: + void handleResponse(const QString&response, bool success) override; + +private: + QString url = "/roles"; +}; diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.cc new file mode 100644 index 0000000000..c0ce22ac89 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.cc @@ -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 +#include + +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 users; + for (const auto &value : jsonArray) { + users.emplace_back(value.toObject()); + } + + emit usersReady(users); + uiStateSP()->setSunnylinkDeviceUsers(users); +} diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h new file mode 100644 index 0000000000..aaac6c5b37 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h @@ -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 + +#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&users); + +protected: + void handleResponse(const QString&response, bool success) override; + +private: + QString url = "/users"; +}; diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.cc new file mode 100644 index 0000000000..3217e44dbd --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.cc @@ -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); +} diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h new file mode 100644 index 0000000000..625f99f1f4 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h @@ -0,0 +1,41 @@ +/** +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 + +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h" + +class SunnylinkClient : public QObject { + Q_OBJECT + +public: + explicit SunnylinkClient(QObject* parent); + RoleService* role_service; + UserService* user_service; +}; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc new file mode 100644 index 0000000000..99964c4681 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc @@ -0,0 +1,189 @@ +/** +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/offroad/settings/device_panel.h" + +#include +#include +#include + +#include "selfdrive/ui/qt/qt_window.h" +#include "selfdrive/ui/qt/widgets/prime.h" + +DevicePanelSP::DevicePanelSP(SettingsWindow *parent) : DevicePanel(parent) { + fleetManagerPin = new ButtonControlSP( + pin_title + pin, tr("TOGGLE"), + tr("Enable or disable PIN requirement for Fleet Manager access.")); + connect(fleetManagerPin, &ButtonControlSP::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(); + } + }); + AddWidgetAt(2, fleetManagerPin); + + fs_watch = new QFileSystemWatcher(this); + connect(fs_watch, &QFileSystemWatcher::fileChanged, this, &DevicePanelSP::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 ButtonControlSP( + 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, &ButtonControlSP::clicked, [=]() { + const std::string txt = util::read_file("/data/community/crashes/error.txt"); + ConfirmationDialog::rich(modified_time + QString::fromStdString(txt), this); + }); + AddWidgetAt(3, errorBtn); + + + auto resetMapboxTokenBtn = new ButtonControlSP(tr("Reset Access Tokens for Map Services"), tr("RESET"), tr("Reset self-service access tokens for Mapbox, Amap, and Google Maps.")); + connect(resetMapboxTokenBtn, &ButtonControlSP::clicked, [=]() { + if (ConfirmationDialog::confirm(tr("Are you sure you want to reset access tokens for all map services?"), tr("Reset"), this)) { + std::vector tokens = { + "CustomMapboxTokenPk", + "CustomMapboxTokenSk", + "AmapKey1", + "AmapKey2", + "GmapKey" + }; + for (const auto& token : tokens) { + params.remove(token); + } + } + }); + AddWidgetAt(6, resetMapboxTokenBtn); + + auto resetParamsBtn = new ButtonControlSP(tr("Reset sunnypilot Settings"), tr("RESET"), ""); + connect(resetParamsBtn, &ButtonControlSP::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(); + } + }); + AddWidgetAt(6, resetParamsBtn); + + QObject::connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { + for (auto btn : findChildren()) { + if (btn != pair_device && btn != errorBtn) { + btn->setEnabled(offroad); + } + } + }); + + offroad_btn = new QPushButton(tr("Toggle Onroad/Offroad")); + offroad_btn->setObjectName("offroad_btn"); + QObject::connect(offroad_btn, &QPushButton::clicked, this, &DevicePanelSP::forceoffroad); + + QVBoxLayout *buttons_layout = new QVBoxLayout(); + buttons_layout->setSpacing(24); + buttons_layout->addLayout(power_layout); + buttons_layout->addWidget(offroad_btn); + addItem(buttons_layout); + + updateLabels(); +} + +void DevicePanelSP::onPinFileChanged(const QString &file_path) { + if (file_path == "/data/params/d/FleetManagerPin") { + refreshPin(); + } else if (file_path == "/data/otp/otp.conf") { + refreshPin(); + } +} + +void DevicePanelSP::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); + } +} + +void DevicePanelSP::forceoffroad() { + if (!uiStateSP()->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 (!uiStateSP()->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 (!uiStateSP()->engaged()) { + params.putBool("ForceOffroad", true); + } + } + } + } else { + ConfirmationDialog::alert(tr("Disengage to Force Offroad"), this); + } + + updateLabels(); +} + +void DevicePanelSP::showEvent(QShowEvent *event) { + DevicePanel::showEvent(event); + updateLabels(); +} + +void DevicePanelSP::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); +} diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h new file mode 100644 index 0000000000..14319612b2 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h @@ -0,0 +1,52 @@ +/** +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/offroad/settings/settings.h" + +class DevicePanelSP : public DevicePanel { + Q_OBJECT + +public: + explicit DevicePanelSP(SettingsWindow *parent); + void showEvent(QShowEvent *event) override; + +private slots: + void onPinFileChanged(const QString &file_path); + void refreshPin(); + void forceoffroad(); + + void updateLabels(); + +private: + ButtonControlSP *fleetManagerPin; + QString pin_title = tr("Fleet Manager PIN:") + " "; + QString pin = "OFF"; + QFileSystemWatcher *fs_watch; + + QPushButton *offroad_btn; +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.cc similarity index 71% rename from selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.cc index 5d11c8526c..9000c87910 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.cc @@ -1,6 +1,35 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/display_settings.h" +/** +The MIT License -DisplayPanel::DisplayPanel(QWidget *parent) : ListWidget(parent, false) { +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/offroad/settings/display_settings.h" + +#include +#include + +DisplayPanel::DisplayPanel(QWidget *parent) : ListWidgetSP(parent, false) { // param, title, desc, icon std::vector> toggle_defs{ { @@ -18,27 +47,27 @@ DisplayPanel::DisplayPanel(QWidget *parent) : ListWidget(parent, false) { // General: Max Time Offroad (Shutdown timer) auto max_time_offroad = new MaxTimeOffroad(); - connect(max_time_offroad, &SPOptionControl::updateLabels, max_time_offroad, &MaxTimeOffroad::refresh); + connect(max_time_offroad, &OptionControlSP::updateLabels, max_time_offroad, &MaxTimeOffroad::refresh); addItem(max_time_offroad); // General: Onroad Screen Off (Auto Onroad Screen Timer) onroad_screen_off = new OnroadScreenOff(); onroad_screen_off->setUpdateOtherToggles(true); - connect(onroad_screen_off, &SPOptionControl::updateLabels, onroad_screen_off, &OnroadScreenOff::refresh); - connect(onroad_screen_off, &SPOptionControl::updateOtherToggles, this, &DisplayPanel::updateToggles); + connect(onroad_screen_off, &OptionControlSP::updateLabels, onroad_screen_off, &OnroadScreenOff::refresh); + connect(onroad_screen_off, &OptionControlSP::updateOtherToggles, this, &DisplayPanel::updateToggles); addItem(onroad_screen_off); // General: Onroad Screen Off Brightness onroad_screen_off_brightness = new OnroadScreenOffBrightness(); - connect(onroad_screen_off_brightness, &SPOptionControl::updateLabels, onroad_screen_off_brightness, &OnroadScreenOffBrightness::refresh); + connect(onroad_screen_off_brightness, &OptionControlSP::updateLabels, onroad_screen_off_brightness, &OnroadScreenOffBrightness::refresh); addItem(onroad_screen_off_brightness); // General: Brightness Control (Global) auto brightness_control = new BrightnessControl(); - connect(brightness_control, &SPOptionControl::updateLabels, brightness_control, &BrightnessControl::refresh); + connect(brightness_control, &OptionControlSP::updateLabels, brightness_control, &BrightnessControl::refresh); for (auto &[param, title, desc, icon] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); + auto toggle = new ParamControlSP(param, title, desc, icon, this); addItem(toggle); toggles[param.toStdString()] = toggle; @@ -65,7 +94,7 @@ void DisplayPanel::updateToggles() { } // Max Time Offroad (Shutdown timer) -MaxTimeOffroad::MaxTimeOffroad() : SPOptionControl ( +MaxTimeOffroad::MaxTimeOffroad() : OptionControlSP( "MaxTimeOffroad", tr("Max Time Offroad"), tr("Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road)."), @@ -110,7 +139,7 @@ void MaxTimeOffroad::refresh() { } // Onroad Screen Off (Auto Onroad Screen Timer) -OnroadScreenOff::OnroadScreenOff() : SPOptionControl ( +OnroadScreenOff::OnroadScreenOff() : OptionControlSP( "OnroadScreenOff", tr("Driving Screen Off Timer"), tr("Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs."), @@ -136,7 +165,7 @@ void OnroadScreenOff::refresh() { } // Onroad Screen Off Brightness -OnroadScreenOffBrightness::OnroadScreenOffBrightness() : SPOptionControl ( +OnroadScreenOffBrightness::OnroadScreenOffBrightness() : OptionControlSP( "OnroadScreenOffBrightness", tr("Driving Screen Off Brightness (%)"), tr("When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio."), @@ -157,7 +186,7 @@ void OnroadScreenOffBrightness::refresh() { } // Brightness Control (Global) -BrightnessControl::BrightnessControl() : SPOptionControl ( +BrightnessControl::BrightnessControl() : OptionControlSP( "BrightnessControl", tr("Brightness"), tr("Manually adjusts the global brightness of the screen."), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.h new file mode 100644 index 0000000000..5298b66fd6 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.h @@ -0,0 +1,98 @@ +/** +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 +#include + +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" + +class OnroadScreenOff : public OptionControlSP { + Q_OBJECT + +public: + OnroadScreenOff(); + + void refresh(); + +private: + Params params; +}; + +class OnroadScreenOffBrightness : public OptionControlSP { + Q_OBJECT + +public: + OnroadScreenOffBrightness(); + + void refresh(); + +private: + Params params; +}; + +class MaxTimeOffroad : public OptionControlSP { + Q_OBJECT + +public: + MaxTimeOffroad(); + + void refresh(); + +private: + Params params; +}; + +class BrightnessControl : public OptionControlSP { + Q_OBJECT + +public: + BrightnessControl(); + + void refresh(); + +private: + Params params; +}; + +class DisplayPanel : public ListWidgetSP { + Q_OBJECT + +public: + explicit DisplayPanel(QWidget *parent = nullptr); + void showEvent(QShowEvent *event) override; + +public slots: + void updateToggles(); + +private: + Params params; + std::map toggles; + + OnroadScreenOff *onroad_screen_off; + OnroadScreenOffBrightness *onroad_screen_off_brightness; +}; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/monitoring_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/monitoring_settings.cc new file mode 100644 index 0000000000..88a69842c7 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/monitoring_settings.cc @@ -0,0 +1,58 @@ +/** +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/offroad/settings/monitoring_settings.h" + +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" + +MonitoringPanel::MonitoringPanel(QWidget *parent) : QFrame(parent) { + main_layout = new QStackedLayout(this); + + ListWidgetSP *list = new ListWidgetSP(this, false); + // param, title, desc, icon + std::vector> 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 ParamControlSP(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 ScrollViewSP(list, this), 1); + main_layout->addWidget(monitoringScreen); +} diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/monitoring_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/monitoring_settings.h new file mode 100644 index 0000000000..091b2fd9f5 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/monitoring_settings.h @@ -0,0 +1,46 @@ +/** +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 +#include +#include + +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.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 toggles; +}; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/onboarding.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/onboarding.cc new file mode 100644 index 0000000000..dd642054d9 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/onboarding.cc @@ -0,0 +1,123 @@ +/** +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/offroad/settings/onboarding.h" + +#include + +#include +#include +#include + +#include "common/util.h" +#include "common/params.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/widgets/input.h" + +void TermsPageSP::showEvent(QShowEvent *event) { + // late init, building QML widget takes 200ms + if (layout()) { + return; + } + + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(45, 35, 45, 45); + main_layout->setSpacing(0); + + QLabel *title = new QLabel(tr("Terms & Conditions")); + title->setStyleSheet("font-size: 90px; font-weight: 600;"); + main_layout->addWidget(title); + + main_layout->addSpacing(30); + + QQuickWidget *text = new QQuickWidget(this); + text->setResizeMode(QQuickWidget::SizeRootObjectToView); + text->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + 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(); + text->rootContext()->setContextProperty("text_view", text_view); + + text->setSource(QUrl::fromLocalFile("qt/offroad/text_view.qml")); + + main_layout->addWidget(text, 1); + main_layout->addSpacing(50); + + QObject *obj = (QObject*)text->rootObject(); + QObject::connect(obj, SIGNAL(scroll()), SLOT(enableAccept())); + + QHBoxLayout* buttons = new QHBoxLayout; + buttons->setMargin(0); + buttons->setSpacing(45); + main_layout->addLayout(buttons); + + QPushButton *decline_btn = new QPushButton(tr("Decline")); + buttons->addWidget(decline_btn); + QObject::connect(decline_btn, &QPushButton::clicked, this, &TermsPage::declinedTerms); + + accept_btn = new QPushButton(tr("Scroll to accept")); + accept_btn->setEnabled(false); + accept_btn->setStyleSheet(R"( + QPushButton { + background-color: #465BEA; + } + QPushButton:pressed { + background-color: #3049F4; + } + QPushButton:disabled { + background-color: #4F4F4F; + } + )"); + buttons->addWidget(accept_btn); + QObject::connect(accept_btn, &QPushButton::clicked, this, &TermsPage::acceptedTerms); +} + +void OnboardingWindowSP::updateActiveScreen() { + if(accepted_terms && training_done && !accepted_terms_sp) { + setCurrentIndex(3); + } else { + OnboardingWindow::updateActiveScreen(); + } +} + +OnboardingWindowSP::OnboardingWindowSP(QWidget *parent) : OnboardingWindow(parent) { + std::string current_terms_version_sp = params.get("TermsVersionSunnypilot"); + accepted_terms_sp = params.get("HasAcceptedTermsSP") == current_terms_version_sp; + LOGD("accepted_terms_sp: %s", params.get("HasAcceptedTermsSP").c_str()); + + auto* terms_sp = new TermsPageSP(true, parent); + addWidget(terms_sp); // index = 3 + connect(terms_sp, &TermsPageSP::acceptedTerms, [=]() { + params.put("HasAcceptedTermsSP", current_terms_version_sp); + accepted_terms_sp = true; + updateActiveScreen(); + }); + connect(terms_sp, &TermsPageSP::declinedTerms, [=]() { setCurrentIndex(2); }); + + OnboardingWindowSP::updateActiveScreen(); +} diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/onboarding.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/onboarding.h new file mode 100644 index 0000000000..f7e9bfccde --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/onboarding.h @@ -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 +***/ + +#pragma once + +#include + +#include "common/params.h" +#include "selfdrive/ui/qt/qt_window.h" + +class TermsPageSP : public TermsPage { + Q_OBJECT + + +public: + explicit TermsPageSP(bool sunnypilot = false, QWidget *parent = 0) : TermsPage(parent), sunnypilot_tc(sunnypilot) {} + + +private: + bool sunnypilot_tc = false; + void showEvent(QShowEvent *event) override; +}; + +class OnboardingWindowSP : public OnboardingWindow { + Q_OBJECT + +public: + explicit OnboardingWindowSP(QWidget *parent = 0); + inline bool completed() const override { return accepted_terms && accepted_terms_sp && training_done; } + +private: + bool accepted_terms_sp = false; + void updateActiveScreen() override; +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/locations_fetcher.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/locations_fetcher.h similarity index 64% rename from selfdrive/ui/qt/offroad/sunnypilot/locations_fetcher.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/osm/locations_fetcher.h index 1e5b20139f..f3142cfc69 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/locations_fetcher.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/locations_fetcher.h @@ -1,11 +1,40 @@ +/** +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 #include // for std::sort #include -#include +#include +#include -#include "selfdrive/ui/qt/offroad/sunnypilot/json_fetcher.h" +#include +#include + +#include "selfdrive/ui/sunnypilot/qt/common/json_fetcher.h" static const std::tuple defaultLocation = std::make_tuple("== None ==", ""); // New class LocationsFetcher that handles web requests and JSON parsing diff --git a/selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/models_fetcher.cc similarity index 80% rename from selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/osm/models_fetcher.cc index 3df9c09fe3..b6736c7424 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/models_fetcher.cc @@ -1,4 +1,31 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.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 +***/ + +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm/models_fetcher.h" + #include ModelsFetcher::ModelsFetcher(QObject* parent) : QObject(parent) { @@ -88,7 +115,7 @@ void ModelsFetcher::onFinished(QNetworkReply* reply, const QString& destinationP QFile file(finalPath); //ensure if the path exists and if not create it - if(!QDir().mkpath(destinationPath)) + if (!QDir().mkpath(destinationPath)) { LOGE("Unable to create directory: %s", destinationPath.toStdString().c_str()); emit downloadFailed(filename); diff --git a/selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/models_fetcher.h similarity index 75% rename from selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/osm/models_fetcher.h index 22ab81e541..c813deedec 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/models_fetcher.h @@ -1,18 +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 // for std::sort #include #include +#include + #include #include #include #include "common/swaglog.h" #include "common/util.h" -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/json_fetcher.h" +#include "selfdrive/ui/sunnypilot/qt/common/json_fetcher.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#else #include "selfdrive/ui/qt/widgets/controls.h" +#endif #include "system/hardware/hw.h" static const QString MODELS_PATH = Hardware::PC() ? QDir::homePath() + "/.comma/media/0/models/" : "/data/media/0/models/"; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/osm_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm_settings.cc similarity index 76% rename from selfdrive/ui/qt/offroad/sunnypilot/osm_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/osm_settings.cc index 236e4406cf..8420181d16 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/osm_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm_settings.cc @@ -1,18 +1,51 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/osm_settings.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 +***/ + +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm_settings.h" + +#include +#include +#include + +#include "common/swaglog.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" OsmPanel::OsmPanel(QWidget *parent) : QFrame(parent) { main_layout = new QStackedLayout(this); - const auto list = new ListWidget(this, false); - list->addItem(mapdVersion = new LabelControl(tr("Mapd Version"), "Loading...")); + const auto list = new ListWidgetSP(this, false); + list->addItem(mapdVersion = new LabelControlSP(tr("Mapd Version"), "Loading...")); list->addItem(setupOsmDeleteMapsButton(parent)); - list->addItem(offlineMapsETA = new LabelControl(tr("Offline Maps ETA"), "")); - list->addItem(offlineMapsElapsed = new LabelControl(tr("Time Elapsed"), "")); + list->addItem(offlineMapsETA = new LabelControlSP(tr("Offline Maps ETA"), "")); + list->addItem(offlineMapsElapsed = new LabelControlSP(tr("Time Elapsed"), "")); list->addItem(setupOsmUpdateButton(parent)); list->addItem(setupOsmDownloadButton(parent)); list->addItem(setupUsStatesButton(parent)); - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { updateLabels(); }); @@ -24,13 +57,13 @@ OsmPanel::OsmPanel(QWidget *parent) : QFrame(parent) { osmScreen = new QWidget(this); auto *vlayout = new QVBoxLayout(osmScreen); vlayout->setContentsMargins(50, 20, 50, 20); - vlayout->addWidget(new ScrollView(list, this), 1); + vlayout->addWidget(new ScrollViewSP(list, this), 1); main_layout->addWidget(osmScreen); } -ButtonControl *OsmPanel::setupOsmDeleteMapsButton(QWidget *parent) { - osmDeleteMapsBtn = new ButtonControl(tr("Downloaded Maps"), tr("DELETE")); // Updated on updateLabels() - connect(osmDeleteMapsBtn, &ButtonControl::clicked, [=]() { +ButtonControlSP *OsmPanel::setupOsmDeleteMapsButton(QWidget *parent) { + osmDeleteMapsBtn = new ButtonControlSP(tr("Downloaded Maps"), tr("DELETE")); // Updated on updateLabels() + connect(osmDeleteMapsBtn, &ButtonControlSP::clicked, [=]() { if (showConfirmationDialog(parent, tr("This will delete ALL downloaded maps\n\nAre you sure you want to delete all the maps?"), tr("Yes, delete all the maps."))) { QtConcurrent::run([=]() { QDir dir(MAP_PATH); @@ -47,9 +80,9 @@ ButtonControl *OsmPanel::setupOsmDeleteMapsButton(QWidget *parent) { return osmDeleteMapsBtn; } -ButtonControl *OsmPanel::setupOsmUpdateButton(QWidget *parent) { - osmUpdateBtn = new ButtonControl(tr("Database Update"), tr("CHECK")); // Updated on updateLabels() - connect(osmUpdateBtn, &ButtonControl::clicked, [=]() { +ButtonControlSP *OsmPanel::setupOsmUpdateButton(QWidget *parent) { + osmUpdateBtn = new ButtonControlSP(tr("Database Update"), tr("CHECK")); // Updated on updateLabels() + connect(osmUpdateBtn, &ButtonControlSP::clicked, [=]() { if (osm_download_in_progress && !download_failed_state) { updateLabels(); } else if (showConfirmationDialog(parent)) { @@ -61,9 +94,9 @@ ButtonControl *OsmPanel::setupOsmUpdateButton(QWidget *parent) { return osmUpdateBtn; } -ButtonControl *OsmPanel::setupOsmDownloadButton(QWidget *parent) { - osmDownloadBtn = new ButtonControl(tr("Country"), tr("SELECT")); - connect(osmDownloadBtn, &ButtonControl::clicked, [=]() { +ButtonControlSP *OsmPanel::setupOsmDownloadButton(QWidget *parent) { + osmDownloadBtn = new ButtonControlSP(tr("Country"), tr("SELECT")); + connect(osmDownloadBtn, &ButtonControlSP::clicked, [=]() { osmDownloadBtn->setEnabled(false); osmDownloadBtn->setValue(tr("Fetching Country list...")); const std::vector> locations = getOsmLocations(); @@ -73,7 +106,7 @@ ButtonControl *OsmPanel::setupOsmDownloadButton(QWidget *parent) { const QString currentTitle = ((initTitle == "== None ==") || (initTitle.length() == 0)) ? "== None ==" : initTitle; QStringList locationTitles; - for (auto &loc: locations) { + for (auto &loc : locations) { locationTitles.push_back(std::get<0>(loc)); } @@ -81,7 +114,7 @@ ButtonControl *OsmPanel::setupOsmDownloadButton(QWidget *parent) { if (!selection.isEmpty()) { params.put("OsmLocal", "1"); params.put("OsmLocationTitle", selection.toStdString()); - for (auto &loc: locations) { + for (auto &loc : locations) { if (std::get<0>(loc) == selection) { params.put("OsmLocationName", std::get<1>(loc).toStdString()); break; @@ -103,9 +136,9 @@ ButtonControl *OsmPanel::setupOsmDownloadButton(QWidget *parent) { return osmDownloadBtn; } -ButtonControl *OsmPanel::setupUsStatesButton(QWidget *parent) { - usStatesBtn = new ButtonControl(tr("State"), tr("SELECT")); - connect(usStatesBtn, &ButtonControl::clicked, [=]() { +ButtonControlSP *OsmPanel::setupUsStatesButton(QWidget *parent) { + usStatesBtn = new ButtonControlSP(tr("State"), tr("SELECT")); + connect(usStatesBtn, &ButtonControlSP::clicked, [=]() { const std::tuple allStatesOption = std::make_tuple("All States (~4.8 GB)", "All"); usStatesBtn->setEnabled(false); usStatesBtn->setValue(tr("Fetching State list...")); @@ -116,14 +149,14 @@ ButtonControl *OsmPanel::setupUsStatesButton(QWidget *parent) { const QString currentTitle = ((initTitle == std::get<0>(allStatesOption)) || (initTitle.length() == 0)) ? tr("All") : initTitle; QStringList locationTitles; - for (auto &loc: locations) { + for (auto &loc : locations) { locationTitles.push_back(std::get<0>(loc)); } const QString selection = MultiOptionDialog::getSelection(tr("State"), locationTitles, currentTitle, this); if (!selection.isEmpty()) { params.put("OsmStateTitle", selection.toStdString()); - for (auto &loc: locations) { + for (auto &loc : locations) { if (std::get<0>(loc) == selection) { params.put("OsmStateName", std::get<1>(loc).toStdString()); break; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/osm_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm_settings.h similarity index 77% rename from selfdrive/ui/qt/offroad/sunnypilot/osm_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/osm_settings.h index 2f9cf8455f..b250e2cecd 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/osm_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm_settings.h @@ -1,19 +1,47 @@ +/** +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 #include +#include #include +#include +#include +#include #include #include #include -#include "common/swaglog.h" #include "selfdrive/ui/qt/network/wifi_manager.h" -#include "selfdrive/ui/qt/widgets/controls.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/locations_fetcher.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm/locations_fetcher.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #include "system/hardware/hw.h" constexpr int FAST_REFRESH_INTERVAL = 1000; // ms @@ -31,9 +59,9 @@ private: QWidget* osmScreen = nullptr; Params params; Params mem_params{ Hardware::PC() ? "": "/dev/shm/params"}; - std::map toggles; + std::map toggles; std::optional> mapSizeFuture; - const SubMaster &sm = *uiState()->sm; + const SubMaster &sm = *uiStateSP()->sm; bool is_onroad = false; @@ -45,17 +73,17 @@ private: quint64 mapsDirSize = 0; QLabel *osmUpdateLbl; - ButtonControl *osmDownloadBtn; - ButtonControl *osmUpdateBtn; - ButtonControl *usStatesBtn; - ButtonControl *osmDeleteMapsBtn; - ButtonControl *setupOsmDeleteMapsButton(QWidget *parent);; - ButtonControl* setupOsmUpdateButton(QWidget *parent); - ButtonControl* setupOsmDownloadButton(QWidget *parent); - ButtonControl* setupUsStatesButton(QWidget *parent); + ButtonControlSP *osmDownloadBtn; + ButtonControlSP *osmUpdateBtn; + ButtonControlSP *usStatesBtn; + ButtonControlSP *osmDeleteMapsBtn; + ButtonControlSP *setupOsmDeleteMapsButton(QWidget *parent);; + ButtonControlSP* setupOsmUpdateButton(QWidget *parent); + ButtonControlSP* setupOsmDownloadButton(QWidget *parent); + ButtonControlSP* setupUsStatesButton(QWidget *parent); QTimer *timer; std::string osm_download_locations; -// void updateButtonControl(ButtonControl *btnControl, QWidget *parent, const QString &initTitle, const QString &allStatesOption); +// void updateButtonControlSP(ButtonControlSP *btnControl, QWidget *parent, const QString &initTitle, const QString &allStatesOption); void showEvent(QShowEvent *event) override; void hideEvent(QHideEvent* event) override; @@ -65,10 +93,10 @@ private: QString processUpdateStatus(bool pending_update_check, int total_files, int downloaded_files, const QJsonObject& json, bool failed_state); ConfirmationDialog* confirmationDialog; - LabelControl *mapdVersion; - LabelControl *offlineMapsStatus; - LabelControl *offlineMapsETA; - LabelControl *offlineMapsElapsed; + LabelControlSP *mapdVersion; + LabelControlSP *offlineMapsStatus; + LabelControlSP *offlineMapsETA; + LabelControlSP *offlineMapsElapsed; std::optional lastDownloadedTimePoint; LocationsFetcher locationsFetcher; void updateMapSize(); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc new file mode 100644 index 0000000000..f99b90fb57 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc @@ -0,0 +1,446 @@ +/** +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/offroad/settings/settings.h" + +#include +#include + +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h" +#include "selfdrive/ui/sunnypilot/qt/network/networking.h" +#include "selfdrive/ui/sunnypilot/sunnypilot_main.h" + +TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { + // param, title, desc, icon + std::vector> 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", + }, + { + "ExperimentalLongitudinalEnabled", + tr("openpilot Longitudinal Control (Alpha)"), + QString("%1

%2") + .arg(tr("WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).")) + .arg(tr("On this car, sunnypilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. " + "Enable this to switch to sunnypilot longitudinal control. Enabling Experimental mode is recommended when enabling sunnypilot 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", + }, + { + "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 sunnypilot ACC or sunnypilot 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", + }, + { + "DisengageOnAccelerator", + tr("Disengage on Accelerator Pedal"), + tr("When enabled, pressing the accelerator pedal will disengage openpilot."), + "../assets/offroad/icon_blank.png", + }, + { + "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", + }, + { + "AlwaysOnDM", + tr("Always-On Driver Monitoring"), + tr("Enable driver monitoring even when sunnypilot is not engaged."), + "../assets/offroad/icon_blank.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", + }, + { + "IsMetric", + tr("Use Metric System"), + tr("Display speed in km/h instead of mph."), + "../assets/offroad/icon_blank.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 longi_button_texts{tr("Aggressive"), tr("Moderate"), tr("Standard"), tr("Relaxed")}; + long_personality_setting = new ButtonParamControlSP("LongitudinalPersonality", tr("Driving Personality"), + tr("Standard is recommended. In moderate/aggressive mode, sunnypilot will follow lead cars closer and be more aggressive with the gas and brake. " + "In relaxed mode sunnypilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " + "your steering wheel distance button."), + "", + longi_button_texts, + 380); + long_personality_setting->showDescription(); + + // accel controller + std::vector accel_personality_texts{tr("Sport"), tr("Normal"), tr("Eco"), tr("Stock")}; + accel_personality_setting = new ButtonParamControlSP("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."), + "", + accel_personality_texts); + accel_personality_setting->showDescription(); + + // set up uiState update for personality setting + QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &TogglesPanelSP::updateState); + + for (auto &[param, title, desc, icon] : toggle_defs) { + auto toggle = new ParamControlSP(param, title, desc, icon, this); + + bool locked = params.getBool((param + "Lock").toStdString()); + toggle->setEnabled(!locked); + + addItem(toggle); + toggles[param.toStdString()] = toggle; + + // 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"]->setConfirmation(true, true); + toggles["ExperimentalLongitudinalEnabled"]->setConfirmation(true, false); + toggles["CustomStockLong"]->setConfirmation(true, false); + + connect(toggles["ExperimentalLongitudinalEnabled"], &ToggleControlSP::toggleFlipped, [=]() { + updateToggles(); + }); + connect(toggles["CustomStockLong"], &ToggleControlSP::toggleFlipped, [=]() { + updateToggles(); + }); + + param_watcher = new ParamWatcher(this); + + QObject::connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) { + updateToggles(); + }); +} + +void TogglesPanelSP::updateState(const UIStateSP &s) { + const SubMaster &sm = *(s.sm); + + if (sm.updated("controlsState")) { + auto personality = sm["controlsState"].getControlsState().getPersonality(); + if (personality != s.scene.personality && s.scene.started && isVisible()) { + long_personality_setting->setCheckedButton(static_cast(personality)); + } + uiStateSP()->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(accel_personality)); + } + uiStateSP()->scene.accel_personality = accel_personality; + } +} + +void TogglesPanelSP::showEvent(QShowEvent *event) { + updateToggles(); +} + +void TogglesPanelSP::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
" + "

%2


" + "%3
" + "

%4


" + "%5
") + .arg(tr("openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. Experimental features are listed below:")) + .arg(tr("End-to-End Longitudinal Control")) + .arg(tr("Let the driving model control the gas and brakes. sunnypilot will drive as it thinks a human would, including stopping for red lights and stop signs. " + "Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; " + "mistakes should be expected.")) + .arg(tr("New Driving Visualization")) + .arg(tr("The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.")); + + const bool is_release = params.getBool("IsReleaseBranch"); + auto cp_bytes = params.get("CarParamsPersistent"); + if (!cp_bytes.empty()) { + AlignedBuffer aligned_buf; + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size())); + cereal::CarParams::Reader CP = cmsg.getRoot(); + + if (!CP.getExperimentalLongitudinalAvailable() || is_release) { + 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."); + + QString long_desc = unavailable + " " + \ + tr("openpilot longitudinal control may come in a future update."); + if (CP.getExperimentalLongitudinalAvailable()) { + if (is_release) { + long_desc = unavailable + " " + tr("An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches."); + } else { + long_desc = tr("Enable the sunnypilot longitudinal control (alpha) toggle to allow Experimental mode."); + } + } + experimental_mode_toggle->setDescription("" + long_desc + "

" + 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); + } +} + +SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) { + + // 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; + background-color: #292929; + font-weight: 400; + } + QPushButton:pressed { + background-color: #3B3B3B; + } + )"); + close_btn->setFixedSize(152, 152); + close_btn_layout->addWidget(close_btn, 0, Qt::AlignLeft); + 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 + DevicePanelSP *device = new DevicePanelSP(this); + QObject::connect(device, &DevicePanelSP::reviewTrainingGuide, this, &SettingsWindow::reviewTrainingGuide); + QObject::connect(device, &DevicePanelSP::showDriverView, this, &SettingsWindow::showDriverView); + + TogglesPanelSP *toggles = new TogglesPanelSP(this); + QObject::connect(this, &SettingsWindow::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription); + + QList panels = { + PanelInfo(" " + tr("Device"), device, "../assets/navigation/icon_home.svg"), + PanelInfo(" " + tr("Network"), new NetworkingSP(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"), + }; + + nav_btns = new QButtonGroup(this); + for (auto &[name, panel, icon] : 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; + border: none; + background: none; + font-size: 50px; + font-weight: 500; + text-align: left; + padding-left: 22px; + } + QPushButton:checked { + background-color: #696868; + color: white; + } + QPushButton:pressed { + color: #ADADAD; + } + )"); + btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + nav_btns->addButton(btn); + buttons_layout->addWidget(btn, 0, Qt::AlignLeft | Qt::AlignBottom); + + const int lr_margin = (name != (" " + tr("Network")) || (name != (" " + tr("sunnypilot")))) ? 50 : 0; // Network and sunnypilot panel handles its own margins + panel->setContentsMargins(lr_margin, 25, lr_margin, 25); + + ScrollViewSP *panel_frame = new ScrollViewSP(panel, this); + panel_widget->addWidget(panel_frame); + + QObject::connect(btn, &QPushButton::clicked, [=, w = panel_frame]() { + btn->setChecked(true); + panel_widget->setCurrentWidget(w); + }); + } + sidebar_layout->setContentsMargins(50, 50, 25, 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 + ScrollViewSP *buttons_scrollview = new ScrollViewSP(buttons_widget, this); + sidebar_layout->addWidget(buttons_scrollview); + + sidebar_widget->setFixedWidth(500); + main_layout->addWidget(sidebar_widget); + main_layout->addWidget(panel_widget); + + setStyleSheet(R"( + * { + color: white; + font-size: 50px; + } + SettingsWindow { + background-color: black; + } + QStackedWidget, ScrollViewSP { + background-color: black; + border-radius: 30px; + } + )"); +} diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h new file mode 100644 index 0000000000..297a092400 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h @@ -0,0 +1,66 @@ +/** +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 +#include + +#include "selfdrive/ui/qt/offroad/settings.h" + +class TogglesPanelSP : public TogglesPanel { + Q_OBJECT + +public: + explicit TogglesPanelSP(SettingsWindow *parent); + void showEvent(QShowEvent *event) override; + +private slots: + void updateState(const UIStateSP &s); + +private: + ButtonParamControlSP *long_personality_setting; + ButtonParamControlSP *accel_personality_setting; + + ParamWatcher *param_watcher; + void updateToggles() override; +}; + +class SettingsWindowSP : public SettingsWindow { + Q_OBJECT + +public: + explicit SettingsWindowSP(QWidget* parent = nullptr); + +protected: + struct PanelInfo { + QString name; + QWidget *widget; + QString icon; + + PanelInfo(const QString &name, QWidget *widget, const QString &icon) : name(name), widget(widget), icon(icon) {} + }; +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/software_settings.cc similarity index 86% rename from selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/software_settings.cc index bbe8572797..ffe047ba4d 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/software_settings.cc @@ -1,9 +1,39 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.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 +***/ + +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/software_settings.h" + +#include + +#include "common/model.h" SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) { - // Get current model name and create new ButtonControl + // Get current model name and create new ButtonControlSP const auto current_model = GetModelName(); - currentModelLblBtn = new ButtonControl(tr("Driving Model"), tr("SELECT"), current_model); + currentModelLblBtn = new ButtonControlSP(tr("Driving Model"), tr("SELECT"), current_model); currentModelLblBtn->setValue(current_model); connect(&models_fetcher, &ModelsFetcher::downloadProgress, this, [this](const double progress) { @@ -61,9 +91,9 @@ SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) { // Connect click event from currentModelLblBtn to local slot - connect(currentModelLblBtn, &ButtonControl::clicked, this, &SoftwarePanelSP::handleCurrentModelLblBtnClicked); + connect(currentModelLblBtn, &ButtonControlSP::clicked, this, &SoftwarePanelSP::handleCurrentModelLblBtnClicked); - ReplaceOrAddWidget(currentModelLbl, currentModelLblBtn); + AddWidgetAt(0, currentModelLblBtn); } void SoftwarePanelSP::handleDownloadFailed(const QString &modelType) { @@ -185,7 +215,7 @@ void SoftwarePanelSP::handleCurrentModelLblBtnClicked() { QMap index_to_model; // Collecting indices with display names - for (const auto &model: models) { + for (const auto &model : models) { if ((is_release_sp && model.environment == "release") || !is_release_sp) { index_to_model.insert(model.index, model.displayName); } @@ -197,7 +227,7 @@ void SoftwarePanelSP::handleCurrentModelLblBtnClicked() { return index1.toInt() > index2.toInt(); }); - for (const QString &index: indices) { + for (const QString &index : indices) { modelNames.push_back(index_to_model[index]); } @@ -213,7 +243,7 @@ void SoftwarePanelSP::handleCurrentModelLblBtnClicked() { } // Finding and setting the selected model - for (auto &model: models) { + for (auto &model : models) { if (model.displayName == selectedModelName) { selectedModelToDownload = model; selectedNavModelToDownload = model; @@ -254,7 +284,7 @@ void SoftwarePanelSP::handleCurrentModelLblBtnClicked() { } void SoftwarePanelSP::checkNetwork() { - const SubMaster &sm = *(uiState()->sm); + const SubMaster &sm = *(uiStateSP()->sm); const auto device_state = sm["deviceState"].getDeviceState(); const auto network_type = device_state.getNetworkType(); is_wifi = network_type == cereal::DeviceState::NetworkType::WIFI; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/software_settings.h similarity index 64% rename from selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/software_settings.h index 384a592de4..17b6b0bb9d 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/software_settings.h @@ -1,9 +1,34 @@ +/** +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 "common/model.h" -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #include "selfdrive/ui/qt/offroad/settings.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm/models_fetcher.h" class SoftwarePanelSP final : public SoftwarePanel { Q_OBJECT @@ -19,17 +44,17 @@ private: void checkNetwork(); bool isDownloadingModel() const { - LOGD("isDownloadingModel: selectedModelToDownload.has_value() [%s] && modelDownloadProgress [%f]",selectedModelToDownload.has_value() ?"true": "false", modelDownloadProgress.value_or(0.0)); + LOGD("isDownloadingModel: selectedModelToDownload.has_value() [%s] && modelDownloadProgress [%f]", selectedModelToDownload.has_value() ? "true": "false", modelDownloadProgress.value_or(0.0)); return selectedModelToDownload.has_value() && modelDownloadProgress.value_or(0.0) > 0.0 && modelDownloadProgress.value_or(0.0) < 100.0; } bool isDownloadingNavModel() const { - LOGD("isDownloadingNavModel: selectedNavModelToDownload.has_value() [%s] && navModelDownloadProgress [%f]",selectedNavModelToDownload.has_value() ?"true": "false", navModelDownloadProgress.value_or(0.0)); + LOGD("isDownloadingNavModel: selectedNavModelToDownload.has_value() [%s] && navModelDownloadProgress [%f]", selectedNavModelToDownload.has_value() ? "true": "false", navModelDownloadProgress.value_or(0.0)); return selectedNavModelToDownload.has_value() && navModelDownloadProgress.value_or(0.0) > 0.0 && navModelDownloadProgress.value_or(0.0) < 100.0; } bool isDownloadingMetadata() const { - LOGD("isDownloadingMetadata: selectedMetadataToDownload.has_value() [%s] && metadataDownloadProgress [%f]",selectedMetadataToDownload.has_value() ?"true": "false", metadataDownloadProgress.value_or(0.0)); + LOGD("isDownloadingMetadata: selectedMetadataToDownload.has_value() [%s] && metadataDownloadProgress [%f]", selectedMetadataToDownload.has_value() ? "true": "false", metadataDownloadProgress.value_or(0.0)); return selectedMetadataToDownload.has_value() && metadataDownloadProgress.value_or(0.0) > 0.0 && metadataDownloadProgress.value_or(0.0) < 100.0; } @@ -69,7 +94,7 @@ private: std::optional selectedModelToDownload; std::optional selectedNavModelToDownload; std::optional selectedMetadataToDownload; - ButtonControl *currentModelLblBtn; + ButtonControlSP *currentModelLblBtn; ModelsFetcher models_fetcher; ModelsFetcher nav_models_fetcher; ModelsFetcher metadata_fetcher; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.cc similarity index 85% rename from selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.cc index 1ebaf4f14e..cf5ec4a4da 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.cc @@ -1,7 +1,38 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.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 +***/ + +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.h" +#include "selfdrive/ui/sunnypilot/qt/api.h" + +#include +#include + +#include #include -#include SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { main_layout = new QStackedLayout(this); @@ -13,28 +44,27 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { }); is_sunnylink_enabled = Params().getBool("SunnylinkEnabled"); - connect(uiState(), &UIState::sunnylinkRolesChanged, this, &SunnylinkPanel::updateLabels); - connect(uiState(), &UIState::sunnylinkDeviceUsersChanged, this, &SunnylinkPanel::updateLabels); + connect(uiStateSP(), &UIStateSP::sunnylinkRolesChanged, this, &SunnylinkPanel::updateLabels); + connect(uiStateSP(), &UIStateSP::sunnylinkDeviceUsersChanged, this, &SunnylinkPanel::updateLabels); - auto list = new ListWidget(this, false); - sunnylinkEnabledBtn = new ParamControl( + auto list = new ListWidgetSP(this, false); + sunnylinkEnabledBtn = new ParamControlSP( "SunnylinkEnabled", tr("Enable sunnylink"), sunnylinkBtnDescription, - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); sunnylinkEnabledBtn->setValue(tr("Device ID ")+ getSunnylinkDongleId().value_or(tr("N/A"))); list->addItem(sunnylinkEnabledBtn); list->addItem(horizontal_line()); sunnylinkBtnDescription = tr("This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that."); - connect(sunnylinkEnabledBtn, &ParamControl::showDescriptionEvent, [=]() { + connect(sunnylinkEnabledBtn, &ParamControlSP::showDescriptionEvent, [=]() { //resets the description to the default one for the easter egg sunnylinkEnabledBtn->setDescription(sunnylinkBtnDescription); }); - connect(sunnylinkEnabledBtn, &ParamControl::toggleFlipped, [=](bool enabled) { + connect(sunnylinkEnabledBtn, &ParamControlSP::toggleFlipped, [=](bool enabled) { if (enabled) { auto proud_description = ""+ tr("🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀")+ ""; sunnylinkEnabledBtn->showDescription(); @@ -49,23 +79,21 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { }); status_popup = new SunnylinkSponsorPopup(false, this); - sponsorBtn = new ButtonControl( + sponsorBtn = new ButtonControlSP( tr("Sponsor Status"), tr("SPONSOR"), - tr("Become a sponsor of sunnypilot to get early access to sunnylink features when they become available.") - ); + tr("Become a sponsor of sunnypilot to get early access to sunnylink features when they become available.")); list->addItem(sponsorBtn); - connect(sponsorBtn, &ButtonControl::clicked, [=]() { + connect(sponsorBtn, &ButtonControlSP::clicked, [=]() { status_popup->exec(); }); list->addItem(horizontal_line()); pair_popup = new SunnylinkSponsorPopup(true, this); - pairSponsorBtn = new ButtonControl( + pairSponsorBtn = new ButtonControlSP( tr("Pair GitHub Account"), tr("PAIR"), - tr("Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink.") + "🌟" - ); + tr("Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink.") + "🌟"); list->addItem(pairSponsorBtn); - connect(pairSponsorBtn, &ButtonControl::clicked, [=]() { + connect(pairSponsorBtn, &ButtonControlSP::clicked, [=]() { if (getSunnylinkDongleId().value_or(tr("N/A")) == "N/A") { ConfirmationDialog::alert(tr("sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again."), this); } else { @@ -74,7 +102,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { }); list->addItem(horizontal_line()); - list->addItem(new LabelControl(tr("Manage Settings"))); + list->addItem(new LabelControlSP(tr("Manage Settings"))); backup_settings = new BackupSettings; @@ -125,7 +153,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { settings_layout->setAlignment(Qt::AlignLeft); list->addItem(settings_layout); - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { is_onroad = !offroad; updateLabels(); }); @@ -134,7 +162,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { auto vlayout = new QVBoxLayout(sunnylinkScreen); vlayout->setContentsMargins(50, 20, 50, 20); - vlayout->addWidget(new ScrollView(list, this), 1); + vlayout->addWidget(new ScrollViewSP(list, this), 1); main_layout->addWidget(sunnylinkScreen); if (is_sunnylink_enabled) { startSunnylink(); @@ -186,17 +214,12 @@ void SunnylinkPanel::updateLabels() { is_sunnylink_enabled = Params().getBool("SunnylinkEnabled"); const auto sunnylinkDongleId = getSunnylinkDongleId().value_or(tr("N/A")); - bool is_sub = uiState()->isSunnylinkSponsor() && is_sunnylink_enabled; - auto max_current_sponsor_rule = uiState()->sunnylinkSponsorRole(); + bool is_sub = uiStateSP()->isSunnylinkSponsor() && is_sunnylink_enabled; + auto max_current_sponsor_rule = uiStateSP()->sunnylinkSponsorRole(); auto role_name = max_current_sponsor_rule.getSponsorTierString(); std::optional role_color = max_current_sponsor_rule.getSponsorTierColor(); - bool is_paired = uiState()->isSunnylinkPaired(); - auto paired_users = uiState()->sunnylinkDeviceUsers(); - - //little easter egg for Panda :D - if(sunnylinkDongleId == "d689627422cefcbc") { - role_name = "Panda 🐼"; - } + bool is_paired = uiStateSP()->isSunnylinkPaired(); + auto paired_users = uiStateSP()->sunnylinkDeviceUsers(); sunnylinkEnabledBtn->setEnabled(!is_onroad); sunnylinkEnabledBtn->setValue(tr("Device ID ")+ sunnylinkDongleId); @@ -252,7 +275,7 @@ QByteArray BackupSettings::backupParams(const bool encrypt) { // Compress the QByteArray. QByteArray compressedData = qCompress(jsonData); - QByteArray processed_data = encrypt ? CommaApi::rsa_encrypt(compressedData) : compressedData; + QByteArray processed_data = encrypt ? SunnylinkApi::rsa_encrypt(compressedData) : compressedData; // Encode the compressed QByteArray in Base64. auto converted_params_processed_base64_data = QString(processed_data.toBase64()); @@ -278,8 +301,8 @@ QByteArray BackupSettings::backupParams(const bool encrypt) { void BackupSettings::sendParams(const QByteArray &payload) { if (auto sl_dongle_id = getSunnylinkDongleId()) { QString url = SUNNYLINK_BASE_URL + "/backup/" + *sl_dongle_id; - auto request = new HttpRequest(this, true, 10000, true); - connect(request, &HttpRequest::requestDone, [=](const QString &resp, bool success) { + auto request = new HttpRequestSP(this, true, 10000, true); + connect(request, &HttpRequestSP::requestDone, [=](const QString &resp, bool success) { if (success && resp != "[]") { ConfirmationDialog::alert(tr("Settings backed up for sunnylink Device ID:") + " " + *sl_dongle_id, this); } else if (resp == "[]") { @@ -318,7 +341,7 @@ void BackupSettings::restoreParams(const QString &resp) { if (configValue.isString()) { // Decode the Base64-encoded "config" value const QByteArray configJsonDataDecoded = QByteArray::fromBase64(configValue.toString().toUtf8()); - const QByteArray configJsonDataCompressed = isEncrypted ? CommaApi::rsa_decrypt(configJsonDataDecoded) : configJsonDataDecoded; + const QByteArray configJsonDataCompressed = isEncrypted ? SunnylinkApi::rsa_decrypt(configJsonDataDecoded) : configJsonDataDecoded; const QByteArray configJsonData = qUncompress(configJsonDataCompressed); // Convert the decompressed JSON data to a QJsonDocument @@ -346,8 +369,8 @@ void BackupSettings::restoreParams(const QString &resp) { void BackupSettings::getParams() { if (auto sl_dongle_id = getSunnylinkDongleId()) { QString url = SUNNYLINK_BASE_URL + "/backup/" + *sl_dongle_id; - auto request = new HttpRequest(this, true, 10000, true); - connect(request, &HttpRequest::requestDone, [=](const QString &resp, bool success) { + auto request = new HttpRequestSP(this, true, 10000, true); + connect(request, &HttpRequestSP::requestDone, [=](const QString &resp, bool success) { bool restart_ui = false; if (success && resp != "[]") { restoreParams(resp); @@ -408,7 +431,7 @@ void SunnylinkSponsorQRWidget::refresh() { QString qrString; if (sponsor_pair) { - QString token = CommaApi::create_jwt({}, 3600, true); + QString token = SunnylinkApi::create_jwt({}, 3600, true); auto sl_dongle_id = getSunnylinkDongleId(); QByteArray payload = QString("1|" + sl_dongle_id.value_or("") + "|" + token).toUtf8().toBase64(); qrString = SUNNYLINK_BASE_URL + "/sso?state=" + payload; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.h similarity index 58% rename from selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.h index fdca62d1c5..2f0885107f 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.h @@ -1,14 +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 #include -#include "selfdrive/ui/qt/network/sunnylink/sunnylink_client.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.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" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" const QString SUNNYLINK_BASE_URL = util::getenv("SUNNYLINK_API_HOST", "https://stg.api.sunnypilot.ai").c_str(); // sponsor QR code @@ -78,18 +102,18 @@ public slots: private: - ParamControl* sunnylinkEnabledBtn; + ParamControlSP* sunnylinkEnabledBtn; QStackedLayout* main_layout = nullptr; QWidget* sunnylinkScreen = nullptr; - ScrollView *scrollView = nullptr; + ScrollViewSP *scrollView = nullptr; BackupSettings *backup_settings; SubPanelButton *restoreSettings; SubPanelButton *backupSettings; SunnylinkSponsorPopup *status_popup; SunnylinkSponsorPopup *pair_popup; - ButtonControl* sponsorBtn; - ButtonControl* pairSponsorBtn; + ButtonControlSP* sponsorBtn; + ButtonControlSP* pairSponsorBtn; SunnylinkClient* sunnylink_client; bool is_onroad = false; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.cc similarity index 54% rename from selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.cc index 405eca50ce..ed1561be1d 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.cc @@ -1,4 +1,30 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.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 +***/ + +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.h" CustomOffsetsSettings::CustomOffsetsSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); @@ -10,25 +36,25 @@ CustomOffsetsSettings::CustomOffsetsSettings(QWidget* parent) : QWidget(parent) connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); main_layout->addWidget(back, 0, Qt::AlignLeft); - ListWidget *list = new ListWidget(this, false); + ListWidgetSP *list = new ListWidgetSP(this, false); // Controls: Camera Offset (cm) camera_offset = new CameraOffset(); - connect(camera_offset, &SPOptionControl::updateLabels, camera_offset, &CameraOffset::refresh); + connect(camera_offset, &OptionControlSP::updateLabels, camera_offset, &CameraOffset::refresh); camera_offset->showDescription(); list->addItem(camera_offset); // Controls: Path Offset (cm) path_offset = new PathOffset(); - connect(path_offset, &SPOptionControl::updateLabels, path_offset, &PathOffset::refresh); + connect(path_offset, &OptionControlSP::updateLabels, path_offset, &PathOffset::refresh); path_offset->showDescription(); list->addItem(path_offset); - main_layout->addWidget(new ScrollView(list, this)); + main_layout->addWidget(new ScrollViewSP(list, this)); } // Camera Offset Value -CameraOffset::CameraOffset() : SPOptionControl ( +CameraOffset::CameraOffset() : OptionControlSP( "CameraOffset", tr("Camera Offset - Laneful Only"), tr("Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm"), @@ -48,7 +74,7 @@ void CameraOffset::refresh() { } // Path Offset Value -PathOffset::PathOffset() : SPOptionControl ( +PathOffset::PathOffset() : OptionControlSP( "PathOffset", tr("Path Offset"), tr("Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately."), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.h new file mode 100644 index 0000000000..780f5fc233 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.h @@ -0,0 +1,74 @@ +/** +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 +#include + +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" + +class CameraOffset : public OptionControlSP { + Q_OBJECT + +public: + CameraOffset(); + + void refresh(); + +private: + Params params; +}; + +class PathOffset : public OptionControlSP { + 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 toggles; + + CameraOffset *camera_offset; + PathOffset *path_offset; +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.cc similarity index 68% rename from selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.cc index 8f16091460..5d47349837 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.cc @@ -1,4 +1,35 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.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 +***/ + +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.h" + +#include +#include +#include +#include LaneChangeSettings::LaneChangeSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); @@ -10,7 +41,7 @@ LaneChangeSettings::LaneChangeSettings(QWidget* parent) : QWidget(parent) { connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); main_layout->addWidget(back, 0, Qt::AlignLeft); - ListWidget *list = new ListWidget(this, false); + ListWidgetSP *list = new ListWidgetSP(this, false); // param, title, desc, icon std::vector> toggle_defs{ { @@ -37,17 +68,17 @@ LaneChangeSettings::LaneChangeSettings(QWidget* parent) : QWidget(parent) { auto_lane_change_timer = new AutoLaneChangeTimer(); auto_lane_change_timer->setUpdateOtherToggles(true); auto_lane_change_timer->showDescription(); - connect(auto_lane_change_timer, &SPOptionControl::updateLabels, auto_lane_change_timer, &AutoLaneChangeTimer::refresh); + connect(auto_lane_change_timer, &OptionControlSP::updateLabels, auto_lane_change_timer, &AutoLaneChangeTimer::refresh); connect(auto_lane_change_timer, &AutoLaneChangeTimer::updateOtherToggles, this, &LaneChangeSettings::updateToggles); list->addItem(auto_lane_change_timer); // Pause Lateral Below Speed w/ Blinker pause_lateral_speed = new PauseLateralSpeed(); pause_lateral_speed->showDescription(); - connect(pause_lateral_speed, &SPOptionControl::updateLabels, pause_lateral_speed, &PauseLateralSpeed::refresh); + connect(pause_lateral_speed, &OptionControlSP::updateLabels, pause_lateral_speed, &PauseLateralSpeed::refresh); for (auto &[param, title, desc, icon] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); + auto toggle = new ParamControlSP(param, title, desc, icon, this); list->addItem(toggle); toggles[param.toStdString()] = toggle; @@ -57,14 +88,14 @@ LaneChangeSettings::LaneChangeSettings(QWidget* parent) : QWidget(parent) { } } - connect(toggles["BelowSpeedPause"], &ToggleControl::toggleFlipped, [=](bool state) { + connect(toggles["BelowSpeedPause"], &ToggleControlSP::toggleFlipped, [=](bool state) { pause_lateral_speed->setEnabled(state); pause_lateral_speed->setVisible(state); }); pause_lateral_speed->setEnabled(toggles["BelowSpeedPause"]->isToggled()); pause_lateral_speed->setVisible(toggles["BelowSpeedPause"]->isToggled()); - main_layout->addWidget(new ScrollView(list, this)); + main_layout->addWidget(new ScrollViewSP(list, this)); } void LaneChangeSettings::showEvent(QShowEvent *event) { @@ -97,10 +128,12 @@ void LaneChangeSettings::updateToggles() { } // Auto Lane Change Timer (ALCT) -AutoLaneChangeTimer::AutoLaneChangeTimer() : SPOptionControl ( +AutoLaneChangeTimer::AutoLaneChangeTimer() : OptionControlSP( "AutoLaneChangeTimer", tr("Auto Lane Change by Blinker"), - tr("Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge.\nPlease use caution when using this feature. Only use the blinker when traffic and road conditions permit."), + tr("Set a timer to delay the auto lane change operation when the blinker is used. " + "No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge.\n" + "Please use caution when using this feature. Only use the blinker when traffic and road conditions permit."), "../assets/offroad/icon_blank.png", {-1, 5}) { @@ -127,7 +160,7 @@ void AutoLaneChangeTimer::refresh() { } } -PauseLateralSpeed::PauseLateralSpeed() : SPOptionControl ( +PauseLateralSpeed::PauseLateralSpeed() : OptionControlSP( "PauseLateralSpeed", "", tr("Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h."), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.h new file mode 100644 index 0000000000..f3cf0be588 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.h @@ -0,0 +1,86 @@ +/** +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 +#include + +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" + +class AutoLaneChangeTimer : public OptionControlSP { + Q_OBJECT + +public: + AutoLaneChangeTimer(); + + void refresh(); + +signals: + void toggleUpdated(); + +private: + Params params; +}; + +class PauseLateralSpeed : public OptionControlSP { + 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 toggles; + + AutoLaneChangeTimer *auto_lane_change_timer; + PauseLateralSpeed *pause_lateral_speed; +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.cc similarity index 52% rename from selfdrive/ui/qt/offroad/sunnypilot/mads_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.cc index e213cc19a7..610ee160bc 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.cc @@ -1,4 +1,33 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/mads_settings.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 +***/ + +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.h" + +#include +#include MadsSettings::MadsSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); @@ -10,7 +39,7 @@ MadsSettings::MadsSettings(QWidget* parent) : QWidget(parent) { connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); main_layout->addWidget(back, 0, Qt::AlignLeft); - ListWidget *list = new ListWidget(this, false); + ListWidgetSP *list = new ListWidgetSP(this, false); // param, title, desc, icon std::vector> toggle_defs{ { @@ -32,31 +61,31 @@ MadsSettings::MadsSettings(QWidget* parent) : QWidget(parent) { // Disengage Lateral on Brake (DLOB) std::vector dlob_settings_texts{tr("Remain Active"), tr("Pause Steering")}; - dlob_settings = new ButtonParamControl( + dlob_settings = new ButtonParamControlSP( "DisengageLateralOnBrake", tr("Steering Mode After Braking"), - tr("Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot.\n\nRemain Active: ALC will remain active even after the brake pedal is pressed.\nPause Steering: ALC will be paused after the brake pedal is manually pressed."), - "../assets/offroad/icon_blank.png", + tr("Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot.\n\n" + "Remain Active: ALC will remain active even after the brake pedal is pressed.\nPause Steering: ALC will be paused after the brake pedal is manually pressed."), + "", dlob_settings_texts, - 500 - ); + 500); dlob_settings->showDescription(); list->addItem(dlob_settings); for (auto &[param, title, desc, icon] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); + auto toggle = new ParamControlSP(param, title, desc, icon, this); list->addItem(toggle); toggles[param.toStdString()] = toggle; // trigger offroadTransition when going onroad/offroad - connect(uiState(), &UIState::offroadTransition, toggle, &ParamControl::setEnabled); + connect(uiStateSP(), &UIStateSP::offroadTransition, toggle, &ParamControlSP::setEnabled); } // trigger offroadTransition when going onroad/offroad - connect(uiState(), &UIState::offroadTransition, dlob_settings, &ButtonParamControl::setEnabled); + connect(uiStateSP(), &UIStateSP::offroadTransition, dlob_settings, &ButtonParamControlSP::setEnabled); - main_layout->addWidget(new ScrollView(list, this)); + main_layout->addWidget(new ScrollViewSP(list, this)); } void MadsSettings::showEvent(QShowEvent *event) { @@ -68,7 +97,7 @@ void MadsSettings::updateToggles() { return; } - const bool is_offroad = !uiState()->scene.started; + const bool is_offroad = !uiStateSP()->scene.started; const bool enable_mads = params.getBool("EnableMads"); const bool enabled = is_offroad && enable_mads; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.h new file mode 100644 index 0000000000..e88bf6a90d --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.h @@ -0,0 +1,54 @@ +/** +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 +#include + +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/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 toggles; + + ButtonParamControlSP *dlob_settings; +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.cc similarity index 68% rename from selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.cc index 7686f0a873..29c1191998 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.cc @@ -1,4 +1,32 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.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 +***/ + +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.h" + +#include SlcSettings::SlcSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); @@ -10,34 +38,32 @@ SlcSettings::SlcSettings(QWidget* parent) : QWidget(parent) { connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); main_layout->addWidget(back, 0, Qt::AlignLeft); - ListWidget *list = new ListWidget(this, false); + ListWidgetSP *list = new ListWidgetSP(this, false); std::vector speed_limit_engage_texts{tr("Auto"), tr("User Confirm")}; - speed_limit_engage_settings = new ButtonParamControl( + speed_limit_engage_settings = new ButtonParamControlSP( "SpeedLimitEngageType", tr("Engage Mode"), "", - "../assets/offroad/icon_blank.png", + "", speed_limit_engage_texts, - 440 - ); + 440); speed_limit_engage_settings->showDescription(); list->addItem(speed_limit_engage_settings); std::vector speed_limit_offset_settings_texts{tr("Default"), tr("Fixed"), tr("Percentage")}; - speed_limit_offset_settings = new ButtonParamControl( + speed_limit_offset_settings = new ButtonParamControlSP( "SpeedLimitOffsetType", tr("Limit Offset"), tr("Set speed limit slightly higher than actual speed limit for a more natural drive."), - "../assets/offroad/icon_blank.png", + "", speed_limit_offset_settings_texts, - 380 - ); + 380); list->addItem(speed_limit_offset_settings); slvo = new SpeedLimitValueOffset(); - connect(slvo, &SPOptionControl::updateLabels, slvo, &SpeedLimitValueOffset::refresh); + connect(slvo, &OptionControlSP::updateLabels, slvo, &SpeedLimitValueOffset::refresh); list->addItem(slvo); - connect(speed_limit_offset_settings, &ButtonParamControl::buttonToggled, this, &SlcSettings::updateToggles); - connect(speed_limit_offset_settings, &ButtonParamControl::buttonToggled, slvo, &SpeedLimitValueOffset::refresh); + connect(speed_limit_offset_settings, &ButtonParamControlSP::buttonToggled, this, &SlcSettings::updateToggles); + connect(speed_limit_offset_settings, &ButtonParamControlSP::buttonToggled, slvo, &SpeedLimitValueOffset::refresh); param_watcher = new ParamWatcher(this); @@ -45,7 +71,7 @@ SlcSettings::SlcSettings(QWidget* parent) : QWidget(parent) { updateToggles(); }); - main_layout->addWidget(new ScrollView(list, this)); + main_layout->addWidget(new ScrollViewSP(list, this)); } void SlcSettings::showEvent(QShowEvent *event) { @@ -101,7 +127,7 @@ void SlcSettings::updateToggles() { } // Speed Limit Control Custom Offset -SpeedLimitValueOffset::SpeedLimitValueOffset() : SPOptionControl ( +SpeedLimitValueOffset::SpeedLimitValueOffset() : OptionControlSP( "SpeedLimitValueOffset", "", "", diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.h similarity index 50% rename from selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.h index 877064e577..a03de75b45 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.h @@ -1,11 +1,41 @@ +/** +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/ui.h" -#include "selfdrive/ui/qt/widgets/controls.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" +#include +#include +#include + +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/util.h" -class SpeedLimitValueOffset : public SPOptionControl { +class SpeedLimitValueOffset : public OptionControlSP { Q_OBJECT public: @@ -33,11 +63,11 @@ public slots: private: Params params; - std::map toggles; + std::map toggles; SpeedLimitValueOffset *slvo; - ButtonParamControl *speed_limit_offset_settings; - ButtonParamControl *speed_limit_engage_settings; + ButtonParamControlSP *speed_limit_offset_settings; + ButtonParamControlSP *speed_limit_engage_settings; ParamWatcher *param_watcher; QString slcDescriptionBuilder(QString param, std::vector descriptions) { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.cc new file mode 100644 index 0000000000..d3e3eda41c --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.cc @@ -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/offroad/settings/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); + + ListWidgetSP *list = new ListWidgetSP(this, false); + + speed_limit_policy = new ButtonParamControlSP( + "SpeedLimitControlPolicy", + tr("Speed Limit Source Policy"), + "", + "", + speed_limit_policy_texts, + 250); + speed_limit_policy->showDescription(); + connect(speed_limit_policy, &ButtonParamControlSP::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 ScrollViewSP(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)); +} diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.h similarity index 59% rename from selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.h index 5b7ef66a68..68337af596 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.h @@ -1,8 +1,37 @@ +/** +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/ui.h" -#include "selfdrive/ui/qt/widgets/controls.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" +#include +#include + +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/util.h" class SpeedLimitPolicySettings : public QWidget { @@ -23,7 +52,7 @@ public slots: private: Params params; - ButtonParamControl *speed_limit_policy; + ButtonParamControlSP *speed_limit_policy; ParamWatcher *param_watcher; QString speedLimitPolicyDescriptionBuilder(QString param, std::vector descriptions) { diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_warning_settings.cc similarity index 64% rename from selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_warning_settings.cc index 6b355e2740..a521b3458b 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_warning_settings.cc @@ -1,4 +1,30 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.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 +***/ + +#include "../../settings/sunnypilot/speed_limit_warning_settings.h" SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); @@ -10,45 +36,42 @@ SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget( connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); main_layout->addWidget(back, 0, Qt::AlignLeft); - ListWidget *list = new ListWidget(this, false); + ListWidgetSP *list = new ListWidgetSP(this, false); std::vector speed_limit_warning_texts{tr("Off"), tr("Display"), tr("Chime")}; - speed_limit_warning_settings = new ButtonParamControl( + speed_limit_warning_settings = new ButtonParamControlSP( "SpeedLimitWarningType", tr("Speed Limit Warning"), "", - "../assets/offroad/icon_blank.png", + "", speed_limit_warning_texts, - 380 - ); + 380); speed_limit_warning_settings->showDescription(); list->addItem(speed_limit_warning_settings); - speed_limit_warning_flash = new ParamControl( + speed_limit_warning_flash = new ParamControlSP( "SpeedLimitWarningFlash", tr("Warning with speed limit flash"), tr("When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset."), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); list->addItem(speed_limit_warning_flash); std::vector speed_limit_warning_offset_settings_texts{tr("Default"), tr("Fixed"), tr("Percentage")}; - speed_limit_warning_offset_settings = new ButtonParamControl( + speed_limit_warning_offset_settings = new ButtonParamControlSP( "SpeedLimitWarningOffsetType", tr("Warning Offset"), tr("Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit."), - "../assets/offroad/icon_blank.png", + "", speed_limit_warning_offset_settings_texts, - 380 - ); + 380); list->addItem(speed_limit_warning_offset_settings); slwvo = new SpeedLimitWarningValueOffset(); - connect(slwvo, &SPOptionControl::updateLabels, slwvo, &SpeedLimitWarningValueOffset::refresh); + connect(slwvo, &OptionControlSP::updateLabels, slwvo, &SpeedLimitWarningValueOffset::refresh); list->addItem(slwvo); - connect(speed_limit_warning_settings, &ButtonParamControl::buttonToggled, this, &SpeedLimitWarningSettings::updateToggles); + connect(speed_limit_warning_settings, &ButtonParamControlSP::buttonToggled, this, &SpeedLimitWarningSettings::updateToggles); - connect(speed_limit_warning_offset_settings, &ButtonParamControl::buttonToggled, this, &SpeedLimitWarningSettings::updateToggles); - connect(speed_limit_warning_offset_settings, &ButtonParamControl::buttonToggled, slwvo, &SpeedLimitWarningValueOffset::refresh); + connect(speed_limit_warning_offset_settings, &ButtonParamControlSP::buttonToggled, this, &SpeedLimitWarningSettings::updateToggles); + connect(speed_limit_warning_offset_settings, &ButtonParamControlSP::buttonToggled, slwvo, &SpeedLimitWarningValueOffset::refresh); param_watcher = new ParamWatcher(this); @@ -56,7 +79,7 @@ SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget( updateToggles(); }); - main_layout->addWidget(new ScrollView(list, this)); + main_layout->addWidget(new ScrollViewSP(list, this)); } void SpeedLimitWarningSettings::showEvent(QShowEvent *event) { @@ -86,7 +109,7 @@ void SpeedLimitWarningSettings::updateToggles() { } // Speed Limit Control Custom Offset -SpeedLimitWarningValueOffset::SpeedLimitWarningValueOffset() : SPOptionControl ( +SpeedLimitWarningValueOffset::SpeedLimitWarningValueOffset() : OptionControlSP( "SpeedLimitWarningValueOffset", "", "", diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_warning_settings.h similarity index 50% rename from selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_warning_settings.h index ce3c48ac3f..95510ad0ab 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_warning_settings.h @@ -1,11 +1,41 @@ +/** +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/ui.h" -#include "selfdrive/ui/qt/widgets/controls.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" +#include +#include +#include + +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/util.h" -class SpeedLimitWarningValueOffset : public SPOptionControl { +class SpeedLimitWarningValueOffset : public OptionControlSP { Q_OBJECT public: @@ -33,12 +63,12 @@ public slots: private: Params params; - std::map toggles; + std::map toggles; SpeedLimitWarningValueOffset *slwvo; - ButtonParamControl *speed_limit_warning_offset_settings; - ParamControl *speed_limit_warning_flash; - ButtonParamControl *speed_limit_warning_settings; + ButtonParamControlSP *speed_limit_warning_offset_settings; + ParamControlSP *speed_limit_warning_flash; + ButtonParamControlSP *speed_limit_warning_settings; ParamWatcher *param_watcher; QString speedLimitWarningDescriptionBuilder(QString param, std::vector descriptions) { diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc similarity index 85% rename from selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc index 8db23500b4..c363e78844 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc @@ -1,9 +1,40 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.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 +***/ + +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.h" + +#include +#include + +#include "common/model.h" SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { main_layout = new QStackedLayout(this); - ListWidget *list = new ListWidget(this, false); + ListWidgetSP *list = new ListWidgetSP(this, false); // param, title, desc, icon std::vector> toggle_defs{ { @@ -21,7 +52,8 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { { "EnableSlc", tr("Speed Limit Control (SLC)"), - tr("When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed."), + tr("When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. " + "The maximum cruising speed will always be the MAX set speed."), "../assets/offroad/icon_blank.png", }, { @@ -80,7 +112,8 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { { "CustomTorqueLateral", tr("Enable Custom Tuning"), - tr("Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within \"selfdrive/torque_data\". The values will also be used live when \"Override Self-Tune\" toggle is enabled."), + tr("Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within \"selfdrive/torque_data\". " + "The values will also be used live when \"Override Self-Tune\" toggle is enabled."), "../assets/offroad/icon_blank.png", }, { @@ -100,7 +133,8 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { tr("Green Traffic Light Chime (Beta)"), QString("%1
" "

%2


") - .arg(tr("A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged.")) + .arg(tr("A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. " + "If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged.")) .arg(tr("Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly.")), "../assets/offroad/icon_blank.png", }, @@ -235,23 +269,22 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { // Controls: Torque - FRICTION friction = new TorqueFriction(); - connect(friction, &SPOptionControl::updateLabels, friction, &TorqueFriction::refresh); + connect(friction, &OptionControlSP::updateLabels, friction, &TorqueFriction::refresh); // Controls: Torque - LAT_ACCEL_FACTOR lat_accel_factor = new TorqueMaxLatAccel(); - connect(lat_accel_factor, &SPOptionControl::updateLabels, lat_accel_factor, &TorqueMaxLatAccel::refresh); + connect(lat_accel_factor, &OptionControlSP::updateLabels, lat_accel_factor, &TorqueMaxLatAccel::refresh); std::vector dlp_settings_texts{tr("Laneful"), tr("Laneless"), tr("Auto")}; - dlp_settings = new ButtonParamControl( + dlp_settings = new ButtonParamControlSP( "DynamicLaneProfile", tr("Dynamic Lane Profile"), "", - "../assets/offroad/icon_blank.png", + "", dlp_settings_texts, - 340 - ); + 340); dlp_settings->showDescription(); for (auto &[param, title, desc, icon] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); + auto toggle = new ParamControlSP(param, title, desc, icon, this); list->addItem(toggle); toggles[param.toStdString()] = toggle; @@ -269,7 +302,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { list->addItem(laneChangeSettingsLayout); list->addItem(horizontal_line()); - list->addItem(new LabelControl(tr("Speed Limit Assist"))); + list->addItem(new LabelControlSP(tr("Speed Limit Assist"))); } if (param == "EnableSlc") { @@ -299,7 +332,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { } } - connect(toggles["NNFF"], &ToggleControl::toggleFlipped, [=](bool state) { + connect(toggles["NNFF"], &ToggleControlSP::toggleFlipped, [=](bool state) { if (state) { toggles["EnforceTorqueLateral"]->setEnabled(false); params.putBool("EnforceTorqueLateral", false); @@ -315,7 +348,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { // trigger updateToggles() when toggleFlipped for (const auto& updateToggleName : updateTogglesNames) { if (toggles.find(updateToggleName) != toggles.end()) { - connect(toggles[updateToggleName], &ToggleControl::toggleFlipped, [=](bool state) { + connect(toggles[updateToggleName], &ToggleControlSP::toggleFlipped, [=](bool state) { updateToggles(); }); } @@ -324,7 +357,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { // trigger offroadTransition when going onroad/offroad for (const auto& offroadName : toggleOffroad) { if (toggles.find(offroadName) != toggles.end()) { - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { toggles[offroadName]->setEnabled(offroad); }); } @@ -334,27 +367,27 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { toggles["EndToEndLongAlertLight"]->setConfirmation(true, false); toggles["CustomOffsets"]->showDescription(); - connect(toggles["EnableMads"], &ToggleControl::toggleFlipped, mads_settings, &MadsSettings::updateToggles); - connect(toggles["EnableMads"], &ToggleControl::toggleFlipped, [=](bool state) { + connect(toggles["EnableMads"], &ToggleControlSP::toggleFlipped, mads_settings, &MadsSettings::updateToggles); + connect(toggles["EnableMads"], &ToggleControlSP::toggleFlipped, [=](bool state) { madsSettings->setEnabled(state); }); madsSettings->setEnabled(toggles["EnableMads"]->isToggled()); - connect(toggles["EnableSlc"], &ToggleControl::toggleFlipped, slc_settings, &SlcSettings::updateToggles); - connect(toggles["EnableSlc"], &ToggleControl::toggleFlipped, [=](bool state) { + connect(toggles["EnableSlc"], &ToggleControlSP::toggleFlipped, slc_settings, &SlcSettings::updateToggles); + connect(toggles["EnableSlc"], &ToggleControlSP::toggleFlipped, [=](bool state) { slcSettings->setEnabled(state); slcSettings->setVisible(state); }); slcSettings->setEnabled(toggles["EnableSlc"]->isToggled()); slcSettings->setVisible(toggles["EnableSlc"]->isToggled()); - connect(toggles["CustomOffsets"], &ToggleControl::toggleFlipped, [=](bool state) { + connect(toggles["CustomOffsets"], &ToggleControlSP::toggleFlipped, [=](bool state) { customOffsetsSettings->setEnabled(state); }); customOffsetsSettings->setEnabled(toggles["CustomOffsets"]->isToggled()); // update "FRICTION" and "LAT_ACCEL_FACTOR" titles when going onroad/offroad - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { friction->setEnabled(offroad || toggles["TorquedOverride"]->isToggled()); lat_accel_factor->setEnabled(offroad || toggles["TorquedOverride"]->isToggled()); @@ -363,7 +396,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { }); // update "FRICTION" and "LAT_ACCEL_FACTOR" titles when TorquedOverride is flipped - connect(toggles["TorquedOverride"], &ToggleControl::toggleFlipped, [=](bool state) { + connect(toggles["TorquedOverride"], &ToggleControlSP::toggleFlipped, [=](bool state) { friction->setEnabled(params.getBool("IsOffroad") || state); lat_accel_factor->setEnabled(params.getBool("IsOffroad") || state); @@ -384,7 +417,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { QVBoxLayout* vlayout = new QVBoxLayout(sunnypilotScreen); vlayout->setContentsMargins(50, 20, 50, 20); - scrollView = new ScrollView(list, this); + scrollView = new ScrollViewSP(list, this); vlayout->addWidget(scrollView, 1); main_layout->addWidget(sunnypilotScreen); main_layout->addWidget(mads_settings); @@ -467,7 +500,8 @@ void SunnypilotPanel::updateToggles() { // NNLC/NNFF QString nnff_available_desc = tr("NNLC is currently not available on this platform."); - QString nnff_fuzzy_desc = tr("Match: \"Exact\" is ideal, but \"Fuzzy\" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: ") + "#tuning-nnlc"; + QString nnff_fuzzy_desc = tr("Match: \"Exact\" is ideal, but \"Fuzzy\" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: ") + + "#tuning-nnlc"; QString nnff_status_init = "⚠️ " + tr("Start the car to check car compatibility") + ""; QString nnff_not_loaded = "⚠️ " + tr("NNLC Not Loaded") + ""; QString nnff_loaded = "✅ " + tr("NNLC Loaded") + ""; @@ -493,9 +527,11 @@ void SunnypilotPanel::updateToggles() { QString nn_model_name = QString::fromStdString(CP.getLateralTuning().getTorque().getNnModelName()); QString nn_fuzzy = CP.getLateralTuning().getTorque().getNnModelFuzzyMatch() ? tr("Fuzzy") : tr("Exact"); - nnff_toggle->setDescription(nnffDescriptionBuilder((nn_model_name == "") ? nnff_status_init : - (nn_model_name == "mock") ? (nnff_not_loaded + "
" + tr("Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: ") + "#tuning-nnlc") : - (nnff_loaded + " | " + tr("Match") + " = " + nn_fuzzy + " | " + _car_model + "

" + nnff_fuzzy_desc))); + nnff_toggle->setDescription(nnffDescriptionBuilder( + (nn_model_name == "") ? nnff_status_init : + (nn_model_name == "mock") ? (nnff_not_loaded + "
" + tr("Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: ") + + "#tuning-nnlc") : + (nnff_loaded + " | " + tr("Match") + " = " + nn_fuzzy + " | " + _car_model + "

" + nnff_fuzzy_desc))); enforce_torque_lateral->setEnabled(false); } else { nnff_toggle->setDescription(nnffDescriptionBuilder(nnff_status_init)); @@ -567,7 +603,7 @@ void SunnypilotPanel::updateToggles() { } // toggle names to update when CustomTorqueLateral is flipped - std::vector customTorqueGroup{friction, lat_accel_factor}; + std::vector customTorqueGroup{friction, lat_accel_factor}; for (const auto& customTorqueControl : customTorqueGroup) { customTorqueControl->setVisible(!(nnff_toggle->isToggled() || !custom_torque_lateral->isToggled())); customTorqueControl->setEnabled(!(nnff_toggle->isToggled() || !custom_torque_lateral->isToggled())); @@ -581,7 +617,7 @@ void SunnypilotPanel::updateToggles() { dlp_settings->setDescription((model_use_lateral_planner ? "" : dlp_incompatible_desc + "

") + dlp_description); } -TorqueFriction::TorqueFriction() : SPOptionControl ( +TorqueFriction::TorqueFriction() : OptionControlSP( "TorqueFriction", tr("FRICTION"), tr("Adjust Friction for the Torque Lateral Controller. Live: Override self-tune values; Offline: Override self-tune offline values at car restart."), @@ -597,7 +633,7 @@ void TorqueFriction::refresh() { setLabel(QString::number(torqueFrictionVal)); } -TorqueMaxLatAccel::TorqueMaxLatAccel() : SPOptionControl ( +TorqueMaxLatAccel::TorqueMaxLatAccel() : OptionControlSP( "TorqueMaxLatAccel", tr("LAT_ACCEL_FACTOR"), tr("Adjust Max Lateral Acceleration for the Torque Lateral Controller. Live: Override self-tune values; Offline: Override self-tune offline values at car restart."), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.h new file mode 100644 index 0000000000..b87854492c --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.h @@ -0,0 +1,115 @@ +/** +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 +#include + +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_warning_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" + +class TorqueFriction : public OptionControlSP { + Q_OBJECT + +public: + TorqueFriction(); + + void refresh(); + +private: + Params params; +}; + +class TorqueMaxLatAccel : public OptionControlSP { + 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 toggles; + ParamWatcher *param_watcher; + + TorqueFriction *friction; + TorqueMaxLatAccel *lat_accel_factor; + ButtonParamControlSP *dlp_settings; + + ScrollViewSP *scrollView = nullptr; + + const QString nnff_description = QString("%1

" + "%2") + .arg(tr("Formerly known as \"NNFF\", this replaces the lateral \"torque\" 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: ") + "#tuning-nnlc"); + + QString nnffDescriptionBuilder(const QString &custom_description) { + QString description = "" + custom_description + "

" + 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.")); +}; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/trips_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/trips_settings.cc new file mode 100644 index 0000000000..b69be20ded --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/trips_settings.cc @@ -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/offroad/settings/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; + } + )"); +} diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/trips_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/trips_settings.h new file mode 100644 index 0000000000..11c0798364 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/trips_settings.h @@ -0,0 +1,47 @@ +/** +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 + +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#else +#include "selfdrive/ui/qt/widgets/controls.h" +#endif +#include "selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h" + +class TripsPanel : public QFrame { + Q_OBJECT + +public: + explicit TripsPanel(QWidget* parent = 0); + +private: + Params params; + + QStackedLayout* center_layout; + DriveStats *driveStatsWidget; +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.cc similarity index 75% rename from selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.cc index dd5d457926..10ff22c028 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.cc @@ -1,4 +1,33 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.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 +***/ + +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.h" + +#include "selfdrive/ui/sunnypilot/qt/util.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" VehiclePanel::VehiclePanel(QWidget *parent) : QWidget(parent) { main_layout = new QStackedLayout(this); @@ -29,7 +58,7 @@ VehiclePanel::VehiclePanel(QWidget *parent) : QWidget(parent) { QVBoxLayout* toggle_layout = new QVBoxLayout(home_widget); home_widget->setObjectName("homeWidget"); - ScrollView *scroller = new ScrollView(home_widget, this); + ScrollViewSP *scroller = new ScrollViewSP(home_widget, this); scroller->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); fcr_layout->addWidget(scroller, 1); @@ -67,60 +96,55 @@ void VehiclePanel::updateToggles() { setCarBtn->setText(((set == "=== Not Selected ===") || (set.length() == 0)) ? prompt_select : set); } -SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidget(parent, false) { +SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidgetSP(parent, false) { setSpacing(50); // Hyundai/Kia/Genesis - addItem(new LabelControl(tr("Hyundai/Kia/Genesis"))); - auto hkgSmoothStop = new ParamControl( + addItem(new LabelControlSP(tr("Hyundai/Kia/Genesis"))); + auto hkgSmoothStop = new ParamControlSP( "HkgSmoothStop", tr("HKG CAN: Smoother Stopping Performance (Beta)"), tr("Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control."), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); hkgSmoothStop->setConfirmation(true, false); addItem(hkgSmoothStop); // Subaru - addItem(new LabelControl(tr("Subaru"))); - auto subaruManualParkingBrakeSng = new ParamControl( + addItem(new LabelControlSP(tr("Subaru"))); + auto subaruManualParkingBrakeSng = new ParamControlSP( "SubaruManualParkingBrakeSng", tr("Manual Parking Brake: Stop and Go (Beta)"), tr("Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation!"), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); subaruManualParkingBrakeSng->setConfirmation(true, false); addItem(subaruManualParkingBrakeSng); // Toyota/Lexus - addItem(new LabelControl(tr("Toyota/Lexus"))); - stockLongToyota = new ParamControl( + addItem(new LabelControlSP(tr("Toyota/Lexus"))); + stockLongToyota = new ParamControlSP( "StockLongToyota", tr("Enable Stock Toyota Longitudinal Control"), tr("sunnypilot will not take over control of gas and brakes. Stock Toyota longitudinal control will be used."), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); stockLongToyota->setConfirmation(true, false); addItem(stockLongToyota); - auto lkasToggle = new ParamControl( + auto lkasToggle = new ParamControlSP( "LkasToggle", tr("Allow M.A.D.S. toggling w/ LKAS Button (Beta)"), QString("%1
" "

%2


") .arg(tr("Allows M.A.D.S. engagement/disengagement with \"LKAS\" button from the steering wheel.")) .arg(tr("Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly.")), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); lkasToggle->setConfirmation(true, false); addItem(lkasToggle); - auto toyotaTss2LongTune = new ParamControl( + auto toyotaTss2LongTune = new ParamControlSP( "ToyotaTSS2Long", tr("Toyota TSS2 Longitudinal: Custom Tuning"), tr("Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation."), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); toyotaTss2LongTune->setConfirmation(true, false); addItem(toyotaTss2LongTune); @@ -128,51 +152,46 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge "ToyotaEnhancedBsm", tr("Enable Enhanced Blind Spot Monitor"), "", - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); toyotaEnhancedBsm->setConfirmation(true, false); addItem(toyotaEnhancedBsm); - auto toyotaSngHack = new ParamControl( + auto toyotaSngHack = new ParamControlSP( "ToyotaSnG", tr("Enable Toyota Stop and Go Hack"), tr("sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk."), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); toyotaSngHack->setConfirmation(true, false); addItem(toyotaSngHack); - auto toyotaAutoLock = new ParamControl( + auto toyotaAutoLock = new ParamControlSP( "ToyotaAutoLock", tr("Enable Toyota Door Auto Locking"), tr("sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph).\nReboot Required."), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); toyotaAutoLock->setConfirmation(true, false); addItem(toyotaAutoLock); - auto toyotaAutoUnlock = new ParamControl( + auto toyotaAutoUnlock = new ParamControlSP( "ToyotaAutoUnlockByShifter", tr("Enable Toyota Door Auto Unlocking"), tr("sunnypilot will attempt to unlock the doors when shift to gear P.\nReboot Required."), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); toyotaAutoUnlock->setConfirmation(true, false); addItem(toyotaAutoUnlock); // Volkswagen - addItem(new LabelControl(tr("Volkswagen"))); - auto volkswagenCCOnly = new ParamControl( + addItem(new LabelControlSP(tr("Volkswagen"))); + auto volkswagenCCOnly = new ParamControlSP( "VwCCOnly", tr("Enable CC Only support"), tr("sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory."), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); volkswagenCCOnly->setConfirmation(true, false); addItem(volkswagenCCOnly); // trigger offroadTransition when going onroad/offroad - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { is_onroad = !offroad; hkgSmoothStop->setEnabled(offroad); toyotaTss2LongTune->setEnabled(offroad); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.h new file mode 100644 index 0000000000..ddf61f6e76 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.h @@ -0,0 +1,82 @@ +/** +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 + +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.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 ListWidgetSP { + Q_OBJECT +public: + explicit SPVehiclesTogglesPanel(VehiclePanel *parent); + void showEvent(QShowEvent *event) override; + +public slots: + void updateToggles(); + +private: + Params params; + bool is_onroad = false; + + ParamControlSP *stockLongToyota; + ParamControlSP *toyotaEnhancedBsm; + + const QString toyotaEnhancedBsmDescription = QString("%1

" + "%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 = "" + custom_description + "

" + toyotaEnhancedBsmDescription; + return description; + } +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_settings.cc similarity index 68% rename from selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_settings.cc index 742725e8ab..a444b546cb 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_settings.cc @@ -1,6 +1,35 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.h" +/** +The MIT License -VisualsPanel::VisualsPanel(QWidget *parent) : ListWidget(parent) { +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/offroad/settings/visuals_settings.h" + +#include +#include + +VisualsPanel::VisualsPanel(QWidget *parent) : ListWidgetSP(parent) { // param, title, desc, icon std::vector> toggle_defs{ { @@ -73,26 +102,24 @@ VisualsPanel::VisualsPanel(QWidget *parent) : ListWidget(parent) { // Visuals: Developer UI Info (Dev UI) std::vector dev_ui_settings_texts{tr("Off"), tr("5 Metrics"), tr("10 Metrics")}; - dev_ui_settings = new ButtonParamControl( + dev_ui_settings = new ButtonParamControlSP( "DevUIInfo", tr("Developer UI"), tr("Display real-time parameters and metrics from various sources."), - "../assets/offroad/icon_blank.png", + "", dev_ui_settings_texts, - 380 - ); + 380); dev_ui_settings->showDescription(); // Visuals: Display Metrics above Chevron std::vector chevron_info_settings_texts{tr("Off"), tr("Distance"), tr("Speed"), tr("Time"), tr("All")}; - chevron_info_settings = new ButtonParamControl( + chevron_info_settings = new ButtonParamControlSP( "ChevronInfo", tr("Display Metrics Below Chevron"), tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control)."), - "../assets/offroad/icon_blank.png", + "", chevron_info_settings_texts, - 300 - ); + 300); chevron_info_settings->showDescription(); for (auto &[param, title, desc, icon] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); + auto toggle = new ParamControlSP(param, title, desc, icon, this); addItem(toggle); toggles[param.toStdString()] = toggle; @@ -107,20 +134,19 @@ VisualsPanel::VisualsPanel(QWidget *parent) : ListWidget(parent) { } std::vector sidebar_temp_texts{tr("Off"), tr("RAM"), tr("CPU"), tr("GPU"), tr("Max")}; - sidebar_temp_setting = new ButtonParamControl( + sidebar_temp_setting = new ButtonParamControlSP( "SidebarTemperatureOptions", tr("Display Temperature on Sidebar"), "", - "../assets/offroad/icon_blank.png", + "", sidebar_temp_texts, - 255 - ); + 255); sidebar_temp_setting->showDescription(); addItem(sidebar_temp_setting); // trigger offroadTransition when going onroad/offroad - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { }); - QObject::connect(toggles["MapboxFullScreen"], &ToggleControl::toggleFlipped, [=](bool state) { + QObject::connect(toggles["MapboxFullScreen"], &ToggleControlSP::toggleFlipped, [=](bool state) { toggles["MapboxFullScreen"]->showDescription(); }); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_settings.h new file mode 100644 index 0000000000..d90b36e21d --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_settings.h @@ -0,0 +1,48 @@ +/** +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 +#include + +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" + +class VisualsPanel : public ListWidgetSP { + Q_OBJECT + +public: + explicit VisualsPanel(QWidget *parent = nullptr); + +private: + Params params; + std::map toggles; + + ButtonParamControlSP *dev_ui_settings; + ButtonParamControlSP *chevron_info_settings; + ButtonParamControlSP *sidebar_temp_setting; +}; diff --git a/selfdrive/ui/sunnypilot/qt/offroad_home.cc b/selfdrive/ui/sunnypilot/qt/offroad_home.cc new file mode 100644 index 0000000000..e0791bd11e --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad_home.cc @@ -0,0 +1,58 @@ +/** +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/offroad_home.h" + +#include + +#include "selfdrive/ui/qt/offroad/experimental_mode.h" +#include "selfdrive/ui/qt/widgets/prime.h" +#include "selfdrive/ui/sunnypilot/sunnypilot_main.h" + +#ifdef ENABLE_MAPS +#define LEFT_WIDGET MapSettings +#else +#define LEFT_WIDGET QWidget +#endif + +void OffroadHomeSP::replaceLeftWidget(){ + auto* new_left_widget = new QStackedWidget(left_widget->parentWidget()); + new_left_widget->addWidget(new LEFT_WIDGET); + if (!custom_mapbox) + new_left_widget->addWidget(new PrimeAdWidget); + + new_left_widget->setStyleSheet("border-radius: 10px;"); + new_left_widget->setCurrentIndex((uiStateSP()->hasPrime() || custom_mapbox) ? 0 : 1); + connect(uiStateSP(), &UIStateSP::primeChanged, [=](bool prime) { + new_left_widget->setCurrentIndex((prime || custom_mapbox) ? 0 : 1); + }); + ReplaceWidget(left_widget, new_left_widget); +} + +OffroadHomeSP::OffroadHomeSP(QWidget* parent) : OffroadHome(parent){ + custom_mapbox = QString::fromStdString(params.get("CustomMapboxTokenSk")) != ""; + replaceLeftWidget(); +} diff --git a/selfdrive/ui/sunnypilot/qt/offroad_home.h b/selfdrive/ui/sunnypilot/qt/offroad_home.h new file mode 100644 index 0000000000..a7c1a36ef2 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad_home.h @@ -0,0 +1,45 @@ +/** +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/offroad_home.h" + +#ifdef ENABLE_MAPS +#include "selfdrive/ui/qt/maps/map_settings.h" +#endif + +class OffroadHomeSP : public OffroadHome { + Q_OBJECT + +public: + void replaceLeftWidget(); + explicit OffroadHomeSP(QWidget* parent = 0); + +private: + // static void replaceWidget(QWidget* old_widget, QWidget* new_widget); + Params params; + bool custom_mapbox; +}; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc new file mode 100644 index 0000000000..bebac31d90 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc @@ -0,0 +1,1535 @@ +/** +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/onroad/annotated_camera.h" + +#include +#include +#include +#include + +#include "common/swaglog.h" +#include "selfdrive/ui/qt/onroad/buttons.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/sunnypilot/qt/util.h" + +static std::pair getFeatureStatus(int value, QStringList text_list, QStringList color_list, + bool condition, QString off_text) { + + QString text("Error"); + QColor color("#ffffff"); + + for (int i = 0; i < text_list.size() && i < color_list.size(); ++i) { + if (value == i) { + text = condition ? text_list[i] : off_text; + color = condition ? QColor(color_list[i]) : QColor("#ffffff"); + break; // Exit the loop once a match is found + } + } + + return {text, color}; +} + + +// Window that shows camera view and variety of info drawn on top +AnnotatedCameraWidgetSP::AnnotatedCameraWidgetSP(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) { + pm = std::make_unique>({"uiDebug"}); + e2e_state = std::make_unique>({"e2eLongStateSP"}); + + main_layout = new QVBoxLayout(this); + main_layout->setMargin(UI_BORDER_SIZE); + main_layout->setSpacing(0); + + experimental_btn = new ExperimentalButtonSP(this); + main_layout->addWidget(experimental_btn, 0, Qt::AlignTop | Qt::AlignRight); + + onroad_settings_btn = new OnroadSettingsButton(this); + + map_settings_btn = new MapSettingsButton(this); + + dm_img = loadPixmap("../assets/img_driver_face.png", {img_size + 5, img_size + 5}); + map_img = loadPixmap("../assets/img_world_icon.png", {subsign_img_size, subsign_img_size}); + left_img = loadPixmap("../assets/img_turn_left_icon.png", {subsign_img_size, subsign_img_size}); + right_img = loadPixmap("../assets/img_turn_right_icon.png", {subsign_img_size, subsign_img_size}); + + buttons_layout = new QHBoxLayout(); + buttons_layout->setContentsMargins(0, 0, 10, 20); + main_layout->addLayout(buttons_layout); + updateButtonsLayout(false); +} + +void AnnotatedCameraWidgetSP::mousePressEvent(QMouseEvent* e) { + bool propagate_event = true; + + UIStateSP *s = uiStateSP(); + UISceneSP &scene = s->scene; + const SubMaster &sm = *(s->sm); + const auto longitudinal_plan_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP(); + + if (longitudinal_plan_sp.getSpeedLimit() > 0.0 && sl_sign_rect.contains(e->x(), e->y())) { + // If touching the speed limit sign area when visible + scene.last_speed_limit_sign_tap = seconds_since_boot(); + params.putBool("LastSpeedLimitSignTap", true); + scene.speed_limit_control_enabled = !scene.speed_limit_control_enabled; + params.putBool("EnableSlc", scene.speed_limit_control_enabled); + propagate_event = false; + } + + if (propagate_event) { + QWidget::mousePressEvent(e); + } +} + +void AnnotatedCameraWidgetSP::updateButtonsLayout(bool is_rhd) { + QLayoutItem *item; + while ((item = buttons_layout->takeAt(0)) != nullptr) { + delete item; + } + + buttons_layout->setContentsMargins(0, 0, 10, rn_offset != 0 ? rn_offset + 10 : 20); + + if (is_rhd) { + buttons_layout->addSpacing(map_settings_btn->isVisible() ? 30 : 0); + buttons_layout->addWidget(map_settings_btn, 0, Qt::AlignBottom | Qt::AlignLeft); + + buttons_layout->addStretch(1); + + buttons_layout->addWidget(onroad_settings_btn, 0, Qt::AlignBottom | Qt::AlignRight); + buttons_layout->addSpacing(onroad_settings_btn->isVisible() ? 216 : 0); + } else { + buttons_layout->addSpacing(onroad_settings_btn->isVisible() ? 216 : 0); + buttons_layout->addWidget(onroad_settings_btn, 0, Qt::AlignBottom | Qt::AlignLeft); + + buttons_layout->addStretch(1); + + buttons_layout->addWidget(map_settings_btn, 0, Qt::AlignBottom | Qt::AlignRight); + buttons_layout->addSpacing(map_settings_btn->isVisible() ? 30 : 0); // Add spacing to the right + } +} + +void AnnotatedCameraWidgetSP::updateState(const UIStateSP &s) { + const int SET_SPEED_NA = 255; + const SubMaster &sm = *(s.sm); + + const bool cs_alive = sm.alive("controlsState"); + const bool nav_alive = sm.alive("navInstruction") && sm["navInstruction"].getValid(); + const auto cs = sm["controlsState"].getControlsState(); + const auto cs_sp = sm["controlsStateSP"].getControlsStateSP(); + const auto car_state = sm["carState"].getCarState(); + const auto nav_instruction = sm["navInstruction"].getNavInstruction(); + const auto car_control = sm["carControl"].getCarControl(); + const auto radar_state = sm["radarState"].getRadarState(); + const auto is_gps_location_external = sm.rcv_frame("gpsLocationExternal") > 1; + const auto gpsLocation = is_gps_location_external ? sm["gpsLocationExternal"].getGpsLocationExternal() : sm["gpsLocation"].getGpsLocation(); + const auto ltp = sm["liveTorqueParameters"].getLiveTorqueParameters(); + const auto lateral_plan_sp = sm["lateralPlanSPDEPRECATED"].getLateralPlanSPDEPRECATED(); + car_params = sm["carParams"].getCarParams(); + + // Handle older routes where vCruiseCluster is not set + float v_cruise = cs.getVCruiseCluster() == 0.0 ? cs.getVCruise() : cs.getVCruiseCluster(); + setSpeed = cs_alive ? v_cruise : SET_SPEED_NA; + is_cruise_set = setSpeed > 0 && (int)setSpeed != SET_SPEED_NA; + if (is_cruise_set && !s.scene.is_metric) { + setSpeed *= KM_TO_MILE; + } + + // Handle older routes where vEgoCluster is not set + v_ego_cluster_seen = v_ego_cluster_seen || car_state.getVEgoCluster() != 0.0; + float v_ego = v_ego_cluster_seen ? car_state.getVEgoCluster() : car_state.getVEgo(); + v_ego = s.scene.true_vego_ui ? car_state.getVEgo() : v_ego; + speed = cs_alive ? std::max(0.0, v_ego) : 0.0; + speed *= s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH; + + auto speed_limit_sign = nav_instruction.getSpeedLimitSign(); + speedLimit = nav_alive ? nav_instruction.getSpeedLimit() : 0.0; + speedLimit *= (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); + + has_us_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::MUTCD); + has_eu_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA); + is_metric = s.scene.is_metric; + speedUnit = s.scene.is_metric ? tr("km/h") : tr("mph"); + hideBottomIcons = (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE); + status = s.status; + + // TODO: Add minimum speed? + left_blindspot = cs_alive && car_state.getLeftBlindspot(); + right_blindspot = cs_alive && car_state.getRightBlindspot(); + + steerOverride = car_state.getSteeringPressed(); + gasOverride = car_state.getGasPressed(); + latActive = car_control.getLatActive(); + madsEnabled = car_state.getMadsEnabled(); + + brakeLights = car_state.getBrakeLightsDEPRECATED() && s.scene.visual_brake_lights; + + standStillTimer = s.scene.stand_still_timer; + standStill = car_state.getStandstill(); + standstillElapsedTime = lateral_plan_sp.getStandstillElapsed(); + + hideVEgoUi = s.scene.hide_vego_ui; + + splitPanelVisible = s.scene.map_visible || s.scene.onroad_settings_visible; + + // ############################## DEV UI START ############################## + lead_d_rel = radar_state.getLeadOne().getDRel(); + lead_v_rel = radar_state.getLeadOne().getVRel(); + lead_status = radar_state.getLeadOne().getStatus(); + lateralState = QString::fromStdString(cs_sp.getLateralState()); + angleSteers = car_state.getSteeringAngleDeg(); + steerAngleDesired = cs.getLateralControlState().getPidState().getSteeringAngleDesiredDeg(); + curvature = cs.getCurvature(); + roll = sm["liveParameters"].getLiveParameters().getRoll(); + memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent(); + devUiInfo = s.scene.dev_ui_info; + gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0; //External reports accuracy, internal does not. + altitude = gpsLocation.getAltitude(); + vEgo = car_state.getVEgo(); + aEgo = car_state.getAEgo(); + steeringTorqueEps = car_state.getSteeringTorqueEps(); + bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg(); + bearingDeg = gpsLocation.getBearingDeg(); + torquedUseParams = (ltp.getUseParams() || s.scene.live_torque_toggle) && !s.scene.torqued_override; + latAccelFactorFiltered = ltp.getLatAccelFactorFiltered(); + frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered(); + liveValid = ltp.getLiveValid(); + // ############################## DEV UI END ############################## + + btnPerc = s.scene.sleep_btn_opacity * 0.05; + + left_blinker = car_state.getLeftBlinker(); + right_blinker = car_state.getRightBlinker(); + lane_change_edge_block = lateral_plan_sp.getLaneChangeEdgeBlockDEPRECATED(); + + // update engageability/experimental mode button + experimental_btn->updateState(s); + + // update onroad settings button state + onroad_settings_btn->updateState(s); + + // update DM icon + auto dm_state = sm["driverMonitoringState"].getDriverMonitoringState(); + dmActive = dm_state.getIsActiveMode(); + rightHandDM = dm_state.getIsRHD(); + // DM icon transition + dm_fade_state = std::clamp(dm_fade_state+0.2*(0.5-dmActive), 0.0, 1.0); + + // update buttons layout + updateButtonsLayout(rightHandDM); + + // hide map settings button for alerts and flip for right hand DM + if (map_settings_btn->isEnabled()) { + map_settings_btn->setVisible(!hideBottomIcons); + buttons_layout->setAlignment(map_settings_btn, (rightHandDM ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignBottom); + } + + // hide onroad settings button for alerts and flip for right hand DM + if (onroad_settings_btn->isEnabled()) { + onroad_settings_btn->setVisible(!hideBottomIcons); + buttons_layout->setAlignment(onroad_settings_btn, (rightHandDM ? Qt::AlignRight : Qt::AlignLeft) | Qt::AlignBottom); + } + + const auto lp_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP(); + slcState = lp_sp.getSpeedLimitControlState(); + + speedLimitControlToggle = s.scene.speed_limit_control_enabled; + + const auto vtcState = lp_sp.getVisionTurnControllerState(); + const float vtc_speed = lp_sp.getVisionTurnSpeed() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); + const auto lpSoruce = lp_sp.getLongitudinalPlanSource(); + QColor vtc_color = tcs_colors[int(vtcState)]; + vtc_color.setAlpha(lpSoruce == cereal::LongitudinalPlanSP::LongitudinalPlanSource::TURN ? 255 : 100); + + showVTC = vtcState > cereal::LongitudinalPlanSP::VisionTurnControllerState::DISABLED; + vtcSpeed = QString::number(std::nearbyint(vtc_speed)); + vtcColor = vtc_color; + showDebugUI = s.scene.show_debug_ui; + + const auto lmd_sp = sm["liveMapDataSP"].getLiveMapDataSP(); + + const auto data_type = int(lmd_sp.getDataType()); + const QString data_type_draw(data_type == 2 ? "🌐 " : ""); + roadName = QString::fromStdString(lmd_sp.getCurrentRoadName()); + roadName = !roadName.isEmpty() ? data_type_draw + roadName : ""; + + float speed_limit_slc = lp_sp.getSpeedLimit() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); + const float speed_limit_offset = lp_sp.getSpeedLimitOffset() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); + const bool sl_force_active = speedLimitControlToggle && + seconds_since_boot() < s.scene.last_speed_limit_sign_tap + 2.0; + const bool sl_inactive = !sl_force_active && (!speedLimitControlToggle || + slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::INACTIVE); + const bool sl_temp_inactive = !sl_force_active && (speedLimitControlToggle && + slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::TEMP_INACTIVE); + const bool sl_pre_active = !sl_force_active && (speedLimitControlToggle && + slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::PRE_ACTIVE); + const int sl_distance = int(lp_sp.getDistToSpeedLimit() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH) / 10.0) * 10; + const QString sl_distance_str(QString::number(sl_distance) + (s.scene.is_metric ? "m" : "f")); + const QString sl_offset_str(speed_limit_offset > 0.0 ? speed_limit_offset < 0.0 ? + "-" + QString::number(std::nearbyint(std::abs(speed_limit_offset))) : + "+" + QString::number(std::nearbyint(speed_limit_offset)) : ""); + const QString sl_inactive_str(sl_temp_inactive && s.scene.speed_limit_control_engage_type == 0 ? "TEMP" : ""); + const QString sl_substring(sl_inactive || sl_temp_inactive || sl_pre_active ? sl_inactive_str : + sl_distance > 0 ? sl_distance_str : sl_offset_str); + + showSpeedLimit = speed_limit_slc > 0.0; + speedLimitSLC = speed_limit_slc; + speedLimitSLCOffset = speed_limit_offset; + slcSubText = sl_substring; + slcSubTextSize = sl_inactive || sl_temp_inactive || sl_distance > 0 ? 25.0 : 27.0; + mapSourcedSpeedLimit = lp_sp.getIsMapSpeedLimit(); + slcActive = !sl_inactive && !sl_temp_inactive; + overSpeedLimit = showSpeedLimit && s.scene.speed_limit_warning_type != 0 && + (std::nearbyint(speed_limit_slc + s.scene.speed_limit_warning_value_offset) < std::nearbyint(speed)); + plus_arrow_up_img = loadPixmap("../assets/img_plus_arrow_up", {105, 105}); + minus_arrow_down_img = loadPixmap("../assets/img_minus_arrow_down", {105, 105}); + + const float tsc_speed = lp_sp.getTurnSpeed() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); + const auto tscState = lp_sp.getTurnSpeedControlState(); + const int t_distance = int(lp_sp.getDistToTurn() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH) / 10.0) * 10; + const QString t_distance_str(QString::number(t_distance) + (s.scene.is_metric ? "m" : "f")); + + showTurnSpeedLimit = tsc_speed > 0.0 && std::round(tsc_speed) < 224 && (tsc_speed < speed || s.scene.show_debug_ui); + turnSpeedLimit = QString::number(std::nearbyint(tsc_speed)); + tscSubText = t_distance > 0 ? t_distance_str : QString(""); + tscActive = tscState > cereal::LongitudinalPlanSP::SpeedLimitControlState::TEMP_INACTIVE; + curveSign = lp_sp.getTurnSign(); + + // TODO: Add toggle variables to cereal, and parse from cereal + longitudinalPersonality = s.scene.longitudinal_personality; + dynamicLaneProfile = s.scene.dynamic_lane_profile; + mpcMode = QString::fromStdString(lp_sp.getE2eBlended()); + mpcMode = (mpcMode == "blended") ? mpcMode.replace(0, 1, mpcMode[0].toUpper()) : mpcMode.toUpper(); + + static int reverse_delay = 0; + bool reverse_allowed = false; + if (int(car_state.getGearShifter()) != 4) { + reverse_delay = 0; + reverse_allowed = false; + } else { + reverse_delay += 50; + if (reverse_delay >= 1000) { + reverse_allowed = true; + } + } + + reversing = reverse_allowed; + + cruiseStateEnabled = car_state.getCruiseState().getEnabled(); + + int e2eLStatus = 0; + static bool chime_sent = false; + static int chime_count = 0; + int chime_prompt = 0; + static float last_lead_distance = -1; + const float lead_distance = radar_state.getLeadOne().getDRel(); + + if (s.scene.e2eX[12] > 30 && car_state.getVEgo() < 1.0) { + e2eLStatus = 2; + } else if ((s.scene.e2eX[12] > 0 && s.scene.e2eX[12] < 80) || s.scene.e2eX[12] < 0) { + e2eLStatus = 1; + } else { + e2eLStatus = 0; + } + + if (!car_state.getStandstill()) { + chime_prompt = 0; + chime_sent = false; + chime_count = 0; + + if (last_lead_distance != -1) { + last_lead_distance = -1; + } + } + + if ((cruiseStateEnabled || car_state.getBrakeLightsDEPRECATED()) && !car_state.getGasPressed() && car_state.getStandstill()) { + if (e2eLStatus == 2 && !radar_state.getLeadOne().getStatus()) { + if (chime_sent) { + chime_count = 0; + } else { + chime_count += 1; + } + if (s.scene.e2e_long_alert_light && chime_count >= 2 && !chime_sent) { + chime_prompt = 1; + chime_sent = true; + } else { + chime_prompt = 0; + } + } else if (radar_state.getLeadOne().getStatus()) { + if ((last_lead_distance == -1) || (lead_distance < last_lead_distance)) { + last_lead_distance = lead_distance; + } + if (s.scene.e2e_long_alert_lead && (lead_distance - last_lead_distance > 1.0) && !chime_sent) { + chime_prompt = 2; + chime_sent = true; + } else { + chime_prompt = 0; + } + } else { + chime_prompt = 0; + } + } else { + } + + e2eStatus = chime_prompt; + e2eState = e2eLStatus; + e2eLongAlertUi = s.scene.e2e_long_alert_ui; + dynamicExperimentalControlToggle = s.scene.dynamic_experimental_control; + speedLimitWarningFlash = s.scene.speed_limit_warning_flash; + experimentalMode = cs.getExperimentalMode(); + + featureStatusToggle = s.scene.feature_status_toggle; + + experimental_btn->setVisible(!(showDebugUI && showVTC)); + drivingModelGen = s.scene.driving_model_generation; +} + +void AnnotatedCameraWidgetSP::drawHud(QPainter &p) { + p.save(); + + // Header gradient + QLinearGradient bg(0, UI_HEADER_HEIGHT - (UI_HEADER_HEIGHT / 2.5), 0, UI_HEADER_HEIGHT); + bg.setColorAt(0, QColor::fromRgbF(0, 0, 0, 0.45)); + bg.setColorAt(1, QColor::fromRgbF(0, 0, 0, 0)); + p.fillRect(0, 0, width(), UI_HEADER_HEIGHT, bg); + + QString speedLimitStr = (speedLimit > 1) ? QString::number(std::nearbyint(speedLimit)) : "–"; + QString speedLimitStrSlc = showSpeedLimit ? QString::number(std::nearbyint(speedLimitSLC)) : "–"; + QString speedStr = QString::number(std::nearbyint(speed)); + QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(setSpeed)) : "–"; + const bool isNavSpeedLimit = has_us_speed_limit || has_eu_speed_limit; + + // Draw outer box + border to contain set speed and speed limit + const int sign_margin = 12; + const int us_sign_height = slcSubText == "" ? 186 : 216; + const int eu_sign_size = 176; + + const QSize default_size = {172, 204}; + QSize set_speed_size = default_size; + if (is_metric || has_eu_speed_limit) set_speed_size.rwidth() = 200; + if ((mapSourcedSpeedLimit && !is_metric && speedLimitStrSlc.size() >= 3) || + (has_us_speed_limit && speedLimitStr.size() >= 3)) set_speed_size.rwidth() = 223; + + if ((mapSourcedSpeedLimit && !is_metric) || has_us_speed_limit) set_speed_size.rheight() += us_sign_height + sign_margin; + else if ((mapSourcedSpeedLimit && is_metric) || has_eu_speed_limit) set_speed_size.rheight() += eu_sign_size + sign_margin; + + int top_radius = 32; + int bottom_radius = ((mapSourcedSpeedLimit && is_metric) || has_eu_speed_limit) ? 100 : 32; + + QRect set_speed_rect(QPoint(60 + (default_size.width() - set_speed_size.width()) / 2, 45), set_speed_size); + p.setPen(QPen(whiteColor(75), 6)); + p.setBrush(blackColor(166)); + drawRoundedRect(p, set_speed_rect, top_radius, top_radius, bottom_radius, bottom_radius); + + // Draw MAX + QColor max_color = QColor(0x80, 0xd8, 0xa6, 0xff); + QColor set_speed_color = whiteColor(); + if (is_cruise_set) { + if (status == STATUS_DISENGAGED) { + max_color = whiteColor(); + } else if (status == STATUS_OVERRIDE && gasOverride) { + max_color = QColor(0x91, 0x9b, 0x95, 0xff); + } else if (speedLimitSLC > 0) { + auto interp_color = [=](QColor c1, QColor c2, QColor c3) { + return speedLimitSLC > 0 ? interpColor(setSpeed, {speedLimitSLC + 5, speedLimitSLC + 15, speedLimitSLC + 25}, {c1, c2, c3}) : c1; + }; + max_color = interp_color(max_color, QColor(0xff, 0xe4, 0xbf), QColor(0xff, 0xbf, 0xbf)); + set_speed_color = interp_color(set_speed_color, QColor(0xff, 0x95, 0x00), QColor(0xff, 0x00, 0x00)); + } else if (speedLimit > 0) { + auto interp_color = [=](QColor c1, QColor c2, QColor c3) { + return speedLimit > 0 ? interpColor(setSpeed, {speedLimit + 5, speedLimit + 15, speedLimit + 25}, {c1, c2, c3}) : c1; + }; + max_color = interp_color(max_color, QColor(0xff, 0xe4, 0xbf), QColor(0xff, 0xbf, 0xbf)); + set_speed_color = interp_color(set_speed_color, QColor(0xff, 0x95, 0x00), QColor(0xff, 0x00, 0x00)); + } + } else { + max_color = QColor(0xa6, 0xa6, 0xa6, 0xff); + set_speed_color = QColor(0x72, 0x72, 0x72, 0xff); + } + p.setFont(InterFont(40, QFont::DemiBold)); + p.setPen(max_color); + p.drawText(set_speed_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("MAX")); + p.setFont(InterFont(90, QFont::Bold)); + p.setPen(set_speed_color); + p.drawText(set_speed_rect.adjusted(0, 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, setSpeedStr); + + const QRect sign_rect = set_speed_rect.adjusted(sign_margin, default_size.height(), -sign_margin, -sign_margin); + sl_sign_rect = sign_rect; + + speedLimitWarning(p, sign_rect, sign_margin); + + // US/Canada (MUTCD style) sign + if (((mapSourcedSpeedLimit && !is_metric && !isNavSpeedLimit) || has_us_speed_limit) && slcShowSign) { + p.setPen(Qt::NoPen); + p.setBrush(whiteColor()); + p.drawRoundedRect(sign_rect, 24, 24); + p.setPen(QPen(blackColor(), 6)); + p.drawRoundedRect(sign_rect.adjusted(9, 9, -9, -9), 16, 16); + + p.setFont(InterFont(28, QFont::DemiBold)); + p.drawText(sign_rect.adjusted(0, 22, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("SPEED")); + p.drawText(sign_rect.adjusted(0, 51, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("LIMIT")); + p.setFont(InterFont(70, QFont::Bold)); + if (overSpeedLimit) p.setPen(QColor(255, 0, 0, 255)); + else p.setPen(blackColor()); + p.drawText(sign_rect.adjusted(0, 85, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitStrSlc); + + // Speed limit offset value + p.setFont(InterFont(32, QFont::Bold)); + p.setPen(blackColor()); + p.drawText(sign_rect.adjusted(0, 85 + 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, slcSubText); + } + + // EU (Vienna style) sign + if (((mapSourcedSpeedLimit && is_metric && !isNavSpeedLimit) || has_eu_speed_limit) && slcShowSign) { + p.setPen(Qt::NoPen); + p.setBrush(whiteColor()); + p.drawEllipse(sign_rect); + p.setPen(QPen(Qt::red, 20)); + p.drawEllipse(sign_rect.adjusted(16, 16, -16, -16)); + + p.setFont(InterFont((speedLimitStrSlc.size() >= 3) ? 60 : 70, QFont::Bold)); + if (overSpeedLimit) p.setPen(QColor(255, 0, 0, 255)); + else p.setPen(blackColor()); + p.drawText(sign_rect, Qt::AlignCenter, speedLimitStrSlc); + + // Speed limit offset value + p.setFont(InterFont(slcSubTextSize, QFont::Bold)); + p.setPen(blackColor()); + p.drawText(sign_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, slcSubText); + } + + // current speed + if (!hideVEgoUi) { + p.setFont(InterFont(176, QFont::Bold)); + drawColoredText(p, rect().center().x(), 210, speedStr, brakeLights ? QColor(0xff, 0, 0, 255) : QColor(0xff, 0xff, 0xff, 255)); + p.setFont(InterFont(66)); + drawText(p, rect().center().x(), 290, speedUnit, 200); + } + + if (!reversing) { + // ####### 1 ROW ####### + QRect bar_rect1(rect().left(), rect().bottom() - 60, rect().width(), 61); + if (!splitPanelVisible && devUiInfo == 2) { + p.setPen(Qt::NoPen); + p.setBrush(QColor(0, 0, 0, 100)); + p.drawRect(bar_rect1); + drawNewDevUi2(p, bar_rect1.left(), bar_rect1.center().y()); + } + + // ####### 1 COLUMN ######## + QRect rc2(rect().right() - (UI_BORDER_SIZE * 2), UI_BORDER_SIZE * 1.5, 184, 152); + if (devUiInfo != 0) { + drawRightDevUi(p, rect().right() - 184 - UI_BORDER_SIZE * 2, UI_BORDER_SIZE * 2 + rc2.height()); + } + + int rn_btn = 0; + rn_btn = !splitPanelVisible && devUiInfo == 2 ? 35 : 0; + rn_offset = rn_btn; + + // Stand Still Timer + if (standStillTimer && standStill && !splitPanelVisible) { + drawStandstillTimer(p, rect().right() - 650, 30 + 160 + 250); + } + + // V-TSC + if (showDebugUI && showVTC) { + drawVisionTurnControllerUI(p, rect().right() - 184 - (UI_BORDER_SIZE * 1.5), int(UI_BORDER_SIZE * 1.5), 184, vtcColor, vtcSpeed, 100); + } + + // Bottom bar road name + if (showDebugUI && !roadName.isEmpty()) { + int font_size = splitPanelVisible ? 38 : 50; + int h = splitPanelVisible ? 18 : 26; + p.setFont(InterFont(font_size, QFont::Bold)); + drawRoadNameText(p, rect().center().x(), h, roadName, QColor(255, 255, 255, 255)); + } + + // Turn Speed Sign + if (showTurnSpeedLimit) { + QRect rc = sign_rect; + rc.moveTop(sign_rect.bottom() + UI_BORDER_SIZE); + drawTrunSpeedSign(p, rc, turnSpeedLimit, tscSubText, curveSign, tscActive); + } + } + + // E2E Status + if (e2eLongAlertUi && e2eState != 0) { + drawE2eStatus(p, UI_BORDER_SIZE * 2 + 190, 45, 150, 150, e2eState); + } + + if (!hideBottomIcons && featureStatusToggle) { + int x = UI_BORDER_SIZE * 2 + (rightHandDM ? 600 : 370); + int feature_status_text_x = rightHandDM ? rect().right() - x : x; + drawFeatureStatusText(p, feature_status_text_x, rect().bottom() - 160 - rn_offset); + } + + p.restore(); +} + +void AnnotatedCameraWidgetSP::drawText(QPainter &p, int x, int y, const QString &text, int alpha) { + QRect real_rect = p.fontMetrics().boundingRect(text); + real_rect.moveCenter({x, y - real_rect.height() / 2}); + + p.setPen(QColor(0xff, 0xff, 0xff, alpha)); + p.drawText(real_rect.x(), real_rect.bottom(), text); +} + +void AnnotatedCameraWidgetSP::drawColoredText(QPainter &p, int x, int y, const QString &text, QColor color) { + QRect real_rect = p.fontMetrics().boundingRect(text); + real_rect.moveCenter({x, y - real_rect.height() / 2}); + + p.setPen(color); + p.drawText(real_rect.x(), real_rect.bottom(), text); +} + +void AnnotatedCameraWidgetSP::drawCenteredText(QPainter &p, int x, int y, const QString &text, QColor color) { + QRect real_rect = p.fontMetrics().boundingRect(text); + real_rect.moveCenter({x, y}); + + p.setPen(color); + p.drawText(real_rect, Qt::AlignCenter, text); +} + +void AnnotatedCameraWidgetSP::drawRoadNameText(QPainter &p, int x, int y, const QString &text, QColor color) { + QRect real_rect = p.fontMetrics().boundingRect(text); + real_rect.moveCenter({x, y}); + + QRect real_rect_adjusted(real_rect); + real_rect_adjusted.adjust(-UI_ROAD_NAME_MARGIN_X, 5, UI_ROAD_NAME_MARGIN_X, 0); + QPainterPath path; + path.addRoundedRect(real_rect_adjusted, 10, 10); + p.setPen(Qt::NoPen); + p.setBrush(QColor(0, 0, 0, 100)); + p.drawPath(path); + + p.setPen(color); + p.drawText(real_rect, Qt::AlignCenter, text); +} + +void AnnotatedCameraWidgetSP::drawVisionTurnControllerUI(QPainter &p, int x, int y, int size, const QColor &color, + const QString &vision_speed, int alpha) { + QRect rvtc(x, y, size, size); + p.setPen(QPen(color, 10)); + p.setBrush(QColor(0, 0, 0, alpha)); + p.drawRoundedRect(rvtc, 20, 20); + p.setPen(Qt::NoPen); + + p.setFont(InterFont(56, QFont::DemiBold)); + drawCenteredText(p, rvtc.center().x(), rvtc.center().y(), vision_speed, color); +} + +void AnnotatedCameraWidgetSP::drawStandstillTimer(QPainter &p, int x, int y) { + char lab_str[16]; + char val_str[16]; + int minute = (int)(standstillElapsedTime / 60); + int second = (int)((standstillElapsedTime) - (minute * 60)); + + if (standStill) { + snprintf(lab_str, sizeof(lab_str), "STOP"); + snprintf(val_str, sizeof(val_str), "%01d:%02d", minute, second); + } + + p.setFont(InterFont(125, QFont::DemiBold)); + drawColoredText(p, x, y, QString(lab_str), QColor(255, 175, 3, 240)); + p.setFont(InterFont(150, QFont::DemiBold)); + drawColoredText(p, x, y + 150, QString(val_str), QColor(255, 255, 255, 240)); +} + +void AnnotatedCameraWidgetSP::drawCircle(QPainter &p, int x, int y, int r, QBrush bg) { + p.setPen(Qt::NoPen); + p.setBrush(bg); + p.drawEllipse(x - r, y - r, 2 * r, 2 * r); +} + +void AnnotatedCameraWidgetSP::drawSpeedSign(QPainter &p, QRect rc, const QString &speed_limit, const QString &sub_text, + int subtext_size, bool is_map_sourced, bool is_active) { + const QColor ring_color = is_active ? QColor(255, 0, 0, 255) : QColor(0, 0, 0, 50); + const QColor inner_color = QColor(255, 255, 255, is_active ? 255 : 85); + const QColor text_color = QColor(0, 0, 0, is_active ? 255 : 85); + + const int x = rc.center().x(); + const int y = rc.center().y(); + const int r = rc.width() / 2.0f; + + drawCircle(p, x, y, r, ring_color); + drawCircle(p, x, y, int(r * 0.8f), inner_color); + + p.setFont(InterFont(89, QFont::Bold)); + drawCenteredText(p, x, y, speed_limit, text_color); + p.setFont(InterFont(subtext_size, QFont::Bold)); + drawCenteredText(p, x, y + 55, sub_text, text_color); + + if (is_map_sourced) { + p.setPen(Qt::NoPen); + p.setOpacity(is_active ? 1.0 : 0.3); + p.drawPixmap(x - subsign_img_size / 2, y - 55 - subsign_img_size / 2, map_img); + p.setOpacity(1.0); + } +} + +void AnnotatedCameraWidgetSP::drawTrunSpeedSign(QPainter &p, QRect rc, const QString &turn_speed, const QString &sub_text, + int curv_sign, bool is_active) { + const QColor border_color = is_active ? QColor(255, 0, 0, 255) : QColor(0, 0, 0, 50); + const QColor inner_color = QColor(255, 255, 255, is_active ? 255 : 85); + const QColor text_color = QColor(0, 0, 0, is_active ? 255 : 85); + + const int x = rc.center().x(); + const int y = 184 * 2 + UI_BORDER_SIZE + 202; + const int width = 184; + + const float stroke_w = 15.0; + const float cS = stroke_w / 2.0 + 4.5; // half width of the stroke on the corners of the triangle + const float R = width / 2.0 - stroke_w / 2.0; + const float A = 0.73205; + const float h2 = 2.0 * R / (1.0 + A); + const float h1 = A * h2; + const float L = 4.0 * R / sqrt(3.0); + + // Draw the internal triangle, compensate for stroke width. Needed to improve rendering when in inactive + // state due to stroke transparency being different from inner transparency. + QPainterPath path; + path.moveTo(x, y - R + cS); + path.lineTo(x - L / 2.0 + cS, y + h1 + h2 - R - stroke_w / 2.0); + path.lineTo(x + L / 2.0 - cS, y + h1 + h2 - R - stroke_w / 2.0); + path.lineTo(x, y - R + cS); + p.setPen(Qt::NoPen); + p.setBrush(inner_color); + p.drawPath(path); + + // Draw the stroke + QPainterPath stroke_path; + stroke_path.moveTo(x, y - R); + stroke_path.lineTo(x - L / 2.0, y + h1 + h2 - R); + stroke_path.lineTo(x + L / 2.0, y + h1 + h2 - R); + stroke_path.lineTo(x, y - R); + p.setPen(QPen(border_color, stroke_w, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); + p.setBrush(Qt::NoBrush); + p.drawPath(stroke_path); + + // Draw the turn sign + if (curv_sign != 0) { + p.setPen(Qt::NoPen); + p.setOpacity(is_active ? 1.0 : 0.3); + p.drawPixmap(int(x - (subsign_img_size / 2)), int(y - R + stroke_w + 30), curv_sign > 0 ? left_img : right_img); + p.setOpacity(1.0); + } + + // Draw the texts. + p.setFont(InterFont(67, QFont::Bold)); + drawCenteredText(p, x, y + 25, turn_speed, text_color); + p.setFont(InterFont(22, QFont::Bold)); + drawCenteredText(p, x, y + 65, sub_text, text_color); +} + +// ############################## DEV UI START ############################## +void AnnotatedCameraWidgetSP::drawCenteredLeftText(QPainter &p, int x, int y, const QString &text1, QColor color1, const QString &text2, const QString &text3, QColor color2) { + QFontMetrics fm(p.font()); + QRect init_rect = fm.boundingRect(text1 + " "); + QRect real_rect = fm.boundingRect(init_rect, 0, text1 + " "); + real_rect.moveCenter({x, y}); + + QRect init_rect3 = fm.boundingRect(text3); + QRect real_rect3 = fm.boundingRect(init_rect3, 0, text3); + real_rect3.moveTop(real_rect.top()); + real_rect3.moveLeft(real_rect.right() + 135); + + QRect init_rect2 = fm.boundingRect(text2); + QRect real_rect2 = fm.boundingRect(init_rect2, 0, text2); + real_rect2.moveTop(real_rect.top()); + real_rect2.moveRight(real_rect.right() + 125); + + p.setPen(color1); + p.drawText(real_rect, Qt::AlignLeft | Qt::AlignVCenter, text1); + + p.setPen(color2); + p.drawText(real_rect2, Qt::AlignRight | Qt::AlignVCenter, text2); + p.drawText(real_rect3, Qt::AlignLeft | Qt::AlignVCenter, text3); +} + +int AnnotatedCameraWidgetSP::drawDevUiRight(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) { + p.setFont(InterFont(30 * 2, QFont::Bold)); + drawColoredText(p, x + 92, y + 80, value, color); + + p.setFont(InterFont(28, QFont::Bold)); + drawText(p, x + 92, y + 80 + 42, label, 255); + + if (units.length() > 0) { + p.save(); + p.translate(x + 54 + 30 - 3 + 92 + 30, y + 37 + 25); + p.rotate(-90); + drawText(p, 0, 0, units, 255); + p.restore(); + } + + return 110; +} + +void AnnotatedCameraWidgetSP::drawRightDevUi(QPainter &p, int x, int y) { + int rh = 5; + int ry = y; + + UiElement dRelElement = DeveloperUi::getDRel(lead_status, lead_d_rel); + rh += drawDevUiRight(p, x, ry, dRelElement.value, dRelElement.label, dRelElement.units, dRelElement.color); + ry = y + rh; + + UiElement vRelElement = DeveloperUi::getVRel(lead_status, lead_v_rel, is_metric, speedUnit); + rh += drawDevUiRight(p, x, ry, vRelElement.value, vRelElement.label, vRelElement.units, vRelElement.color); + ry = y + rh; + + UiElement steeringAngleDegElement = DeveloperUi::getSteeringAngleDeg(angleSteers, madsEnabled, latActive); + rh += drawDevUiRight(p, x, ry, steeringAngleDegElement.value, steeringAngleDegElement.label, steeringAngleDegElement.units, steeringAngleDegElement.color); + ry = y + rh; + + if (lateralState == "torque") { + UiElement actualLateralAccelElement = DeveloperUi::getActualLateralAccel(curvature, vEgo, roll, madsEnabled, latActive); + rh += drawDevUiRight(p, x, ry, actualLateralAccelElement.value, actualLateralAccelElement.label, actualLateralAccelElement.units, actualLateralAccelElement.color); + } else { + UiElement steeringAngleDesiredDegElement = DeveloperUi::getSteeringAngleDesiredDeg(madsEnabled, latActive, steerAngleDesired, angleSteers); + rh += drawDevUiRight(p, x, ry, steeringAngleDesiredDegElement.value, steeringAngleDesiredDegElement.label, steeringAngleDesiredDegElement.units, steeringAngleDesiredDegElement.color); + } + ry = y + rh; + + UiElement memoryUsagePercentElement = DeveloperUi::getMemoryUsagePercent(memoryUsagePercent); + rh += drawDevUiRight(p, x, ry, memoryUsagePercentElement.value, memoryUsagePercentElement.label, memoryUsagePercentElement.units, memoryUsagePercentElement.color); + ry = y + rh; + + rh += 25; + p.setBrush(QColor(0, 0, 0, 0)); + QRect ldu(x, y, 184, rh); +} + +int AnnotatedCameraWidgetSP::drawNewDevUi(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) { + p.setFont(InterFont(38, QFont::Bold)); + drawCenteredLeftText(p, x, y, label, whiteColor(), value, units, color); + + return 430; +} + +void AnnotatedCameraWidgetSP::drawNewDevUi2(QPainter &p, int x, int y) { + int rw = 90; + + UiElement aEgoElement = DeveloperUi::getAEgo(aEgo); + rw += drawNewDevUi(p, rw, y, aEgoElement.value, aEgoElement.label, aEgoElement.units, aEgoElement.color); + + UiElement vEgoLeadElement = DeveloperUi::getVEgoLead(lead_status, lead_v_rel, vEgo, is_metric, speedUnit); + rw += drawNewDevUi(p, rw, y, vEgoLeadElement.value, vEgoLeadElement.label, vEgoLeadElement.units, vEgoLeadElement.color); + + if (torquedUseParams) { + UiElement frictionCoefficientFilteredElement = DeveloperUi::getFrictionCoefficientFiltered(frictionCoefficientFiltered, liveValid); + rw += drawNewDevUi(p, rw, y, frictionCoefficientFilteredElement.value, frictionCoefficientFilteredElement.label, frictionCoefficientFilteredElement.units, frictionCoefficientFilteredElement.color); + + UiElement latAccelFactorFilteredElement = DeveloperUi::getLatAccelFactorFiltered(latAccelFactorFiltered, liveValid); + rw += drawNewDevUi(p, rw, y, latAccelFactorFilteredElement.value, latAccelFactorFilteredElement.label, latAccelFactorFilteredElement.units, latAccelFactorFilteredElement.color); + } else { + UiElement steeringTorqueEpsElement = DeveloperUi::getSteeringTorqueEps(steeringTorqueEps); + rw += drawNewDevUi(p, rw, y, steeringTorqueEpsElement.value, steeringTorqueEpsElement.label, steeringTorqueEpsElement.units, steeringTorqueEpsElement.color); + + UiElement bearingDegElement = DeveloperUi::getBearingDeg(bearingAccuracyDeg, bearingDeg); + rw += drawNewDevUi(p, rw, y, bearingDegElement.value, bearingDegElement.label, bearingDegElement.units, bearingDegElement.color); + } + + UiElement altitudeElement = DeveloperUi::getAltitude(gpsAccuracy, altitude); + rw += drawNewDevUi(p, rw, y, altitudeElement.value, altitudeElement.label, altitudeElement.units, altitudeElement.color); +} + +// ############################## DEV UI END ############################## + +void AnnotatedCameraWidgetSP::drawE2eStatus(QPainter &p, int x, int y, int w, int h, int e2e_long_status) { + QColor status_color; + QRect e2eStatusIcon(x, y, w, h); + p.setPen(Qt::NoPen); + p.setBrush(QBrush(blackColor(70))); + p.drawEllipse(e2eStatusIcon); + e2eStatusIcon -= QMargins(25, 25, 25, 25); + p.setPen(Qt::NoPen); + if (e2e_long_status == 2) { + status_color = QColor::fromRgbF(0.0, 1.0, 0.0, 0.9); + } else if (e2e_long_status == 1) { + status_color = QColor::fromRgbF(1.0, 0.0, 0.0, 0.9); + } + p.setBrush(QBrush(status_color)); + p.drawEllipse(e2eStatusIcon); +} + +void AnnotatedCameraWidgetSP::drawLeftTurnSignal(QPainter &painter, int x, int y, int circle_size, int state) { + painter.setRenderHint(QPainter::Antialiasing, true); + + QColor circle_color, circle_color_0, circle_color_1; + QColor arrow_color, arrow_color_0, arrow_color_1; + if ((left_blindspot || lane_change_edge_block) && !(left_blinker && right_blinker)) { + circle_color_0 = QColor(164, 0, 1); + circle_color_1 = QColor(204, 0, 1); + arrow_color_0 = QColor(72, 1, 1); + arrow_color_1 = QColor(255, 255, 255); + } else { + circle_color_0 = QColor(22, 156, 69); + circle_color_1 = QColor(30, 215, 96); + arrow_color_0 = QColor(9, 56, 27); + arrow_color_1 = QColor(255, 255, 255); + } + + if (state == 1) { + circle_color = circle_color_1; + arrow_color = arrow_color_1; + } else if (state == 0) { + circle_color = circle_color_0; + arrow_color = arrow_color_0; + } + + // Draw the circle + int circleX = x; + int circleY = y; + painter.setPen(Qt::NoPen); + painter.setBrush(circle_color); + painter.drawEllipse(circleX, circleY, circle_size, circle_size); + + // Draw the arrow + int arrowSize = 50; + int arrowX = circleX + (circle_size - arrowSize) / 4; + int arrowY = circleY + (circle_size - arrowSize) / 2; + painter.setPen(Qt::NoPen); + painter.setBrush(arrow_color); + + // Draw the arrow shape + QPolygon arrowPolygon; + arrowPolygon << QPoint(arrowX + 10, arrowY + arrowSize / 2) + << QPoint(arrowX + arrowSize - 3, arrowY) + << QPoint(arrowX + arrowSize, arrowY) + << QPoint(arrowX + arrowSize, arrowY + arrowSize) + << QPoint(arrowX + arrowSize - 3, arrowY + arrowSize) + << QPoint(arrowX + 10, arrowY + arrowSize / 2); + painter.drawPolygon(arrowPolygon); + + // Draw the tail rectangle + int tailWidth = arrowSize / 2.25; + int tailHeight = arrowSize / 2; + QRect tailRect(arrowX + arrowSize - 3, arrowY + arrowSize / 4, tailWidth, tailHeight); + painter.fillRect(tailRect, arrow_color); +} + +void AnnotatedCameraWidgetSP::drawRightTurnSignal(QPainter &painter, int x, int y, int circle_size, int state) { + painter.setRenderHint(QPainter::Antialiasing, true); + + QColor circle_color, circle_color_0, circle_color_1; + QColor arrow_color, arrow_color_0, arrow_color_1; + if ((right_blindspot || lane_change_edge_block) && !(left_blinker && right_blinker)) { + circle_color_0 = QColor(164, 0, 1); + circle_color_1 = QColor(204, 0, 1); + arrow_color_0 = QColor(72, 1, 1); + arrow_color_1 = QColor(255, 255, 255); + } else { + circle_color_0 = QColor(22, 156, 69); + circle_color_1 = QColor(30, 215, 96); + arrow_color_0 = QColor(9, 56, 27); + arrow_color_1 = QColor(255, 255, 255); + } + + if (state == 1) { + circle_color = circle_color_1; + arrow_color = arrow_color_1; + } else if (state == 0) { + circle_color = circle_color_0; + arrow_color = arrow_color_0; + } + + + // Draw the circle + int circleX = x; + int circleY = y; + painter.setPen(Qt::NoPen); + painter.setBrush(circle_color); + painter.drawEllipse(circleX, circleY, circle_size, circle_size); + + // Draw the arrow + int arrowSize = 50; + int arrowX = circleX + (circle_size - arrowSize) / 2 + (arrowSize / 2.5) - 3; + int arrowY = circleY + (circle_size - arrowSize) / 2; + painter.setPen(Qt::NoPen); + painter.setBrush(arrow_color); + + // Draw the arrow shape + QPolygon arrowPolygon; + arrowPolygon << QPoint(arrowX + arrowSize - 10, arrowY + arrowSize / 2) + << QPoint(arrowX + 3, arrowY) + << QPoint(arrowX, arrowY) + << QPoint(arrowX, arrowY + arrowSize) + << QPoint(arrowX + 3, arrowY + arrowSize) + << QPoint(arrowX + arrowSize - 10, arrowY + arrowSize / 2); + painter.drawPolygon(arrowPolygon); + + // Draw the tail rectangle + int tailWidth = arrowSize / 2.25; + int tailHeight = arrowSize / 2; + QRect tailRect(arrowX - tailWidth + 3, arrowY + arrowSize / 4, tailWidth, tailHeight); + painter.fillRect(tailRect, arrow_color); +} + +int AnnotatedCameraWidgetSP::blinkerPulse(int frame) { + if (frame % UI_FREQ < (UI_FREQ / 2)) { + blinker_state = 1; + } else { + blinker_state = 0; + } + + return blinker_state; +} + +void AnnotatedCameraWidgetSP::speedLimitSignPulse(int frame) { + if (frame % UI_FREQ < (UI_FREQ / 2.5)) { + slcShowSign = false; + } else { + slcShowSign = true; + } +} + +void AnnotatedCameraWidgetSP::drawFeatureStatusText(QPainter &p, int x, int y) { + const FeatureStatusText feature_text; + const FeatureStatusColor feature_color; + const QColor text_color = whiteColor(); + const QColor shadow_color = blackColor(38); + const int text_height = 34; + const int drop_shadow_size = 2; + const int eclipse_x_offset = 25; + const int eclipse_y_offset = 20; + const int w = 16; + const int h = 16; + + const bool longitudinal = hasLongitudinalControl(car_params); + + p.setFont(InterFont(32, QFont::Bold)); + + // Define a function to draw a feature status button + auto drawFeatureStatusElement = [&](int value, const QStringList& text_list, const QStringList& color_list, bool condition, const QString& off_text, const QString& label) { + std::pair feature_status = getFeatureStatus(value, text_list, color_list, condition, off_text); + QRect btn(x - eclipse_x_offset, y - eclipse_y_offset, w, h); + QRect btn_shadow(x - eclipse_x_offset + drop_shadow_size, y - eclipse_y_offset + drop_shadow_size, w, h); + p.setPen(Qt::NoPen); + p.setBrush(shadow_color); + p.drawEllipse(btn_shadow); + p.setBrush(feature_status.second); + p.drawEllipse(btn); + QString status_text; + status_text.sprintf("%s: %s", label.toStdString().c_str(), (feature_status.first).toStdString().c_str()); + p.setPen(QPen(shadow_color, 2)); + p.drawText(x + drop_shadow_size, y + drop_shadow_size, status_text); + p.setPen(QPen(text_color, 2)); + p.drawText(x, y, status_text); + y += text_height; + }; + + // Driving Personality / Gap Adjust Cruise + if (longitudinal) { + drawFeatureStatusElement(longitudinalPersonality, feature_text.gac_list_text, feature_color.gac_list_color, longitudinal, "N/A", "GAP"); + } + + // Dynamic Lane Profile + if (drivingModelGen == cereal::ModelGeneration::ONE) { + drawFeatureStatusElement(dynamicLaneProfile, feature_text.dlp_list_text, feature_color.dlp_list_color, true, "OFF", "DLP"); + } + + // TODO: Add toggle variables to cereal, and parse from cereal + if (longitudinal) { + QColor dec_color((cruiseStateEnabled && dynamicExperimentalControlToggle) ? "#4bff66" : "#ffffff"); + QRect dec_btn(x - eclipse_x_offset, y - eclipse_y_offset, w, h); + QRect dec_btn_shadow(x - eclipse_x_offset + drop_shadow_size, y - eclipse_y_offset + drop_shadow_size, w, h); + p.setPen(Qt::NoPen); + p.setBrush(shadow_color); + p.drawEllipse(dec_btn_shadow); + p.setBrush(dec_color); + p.drawEllipse(dec_btn); + QString dec_status_text; + dec_status_text.sprintf("DEC: %s\n", dynamicExperimentalControlToggle ? (experimentalMode ? QString(mpcMode).toStdString().c_str() : QString("Inactive").toStdString().c_str()) : "OFF"); + p.setPen(QPen(shadow_color, 2)); + p.drawText(x + drop_shadow_size, y + drop_shadow_size, dec_status_text); + p.setPen(QPen(text_color, 2)); + p.drawText(x, y, dec_status_text); + y += text_height; + } + + // TODO: Add toggle variables to cereal, and parse from cereal + // Speed Limit Control + if (longitudinal || !car_params.getPcmCruiseSpeed()) { + drawFeatureStatusElement(int(slcState), feature_text.slc_list_text, feature_color.slc_list_color, speedLimitControlToggle, "OFF", "SLC"); + } +} + +void AnnotatedCameraWidgetSP::speedLimitWarning(QPainter &p, QRect sign_rect, const int sign_margin) { + // PRE ACTIVE + if (slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::PRE_ACTIVE) { + int set_speed = std::nearbyint(setSpeed); + int speed_limit_offsetted = std::nearbyint(speedLimitSLC + speedLimitSLCOffset); + + // Calculate the vertical offset using a sinusoidal function for smooth bouncing + double bounce_frequency = 2.0 * M_PI / 20.0; // 20 frames for one full oscillation + int bounce_offset = 20 * sin(speed_limit_frame * bounce_frequency); // Adjust the amplitude (20 pixels) as needed + + if (set_speed < speed_limit_offsetted) { + QPoint iconPosition(sign_rect.right() + sign_margin * 3, sign_rect.center().y() - plus_arrow_up_img.height() / 2 + bounce_offset); + p.drawPixmap(iconPosition, plus_arrow_up_img); + } else if (set_speed > speed_limit_offsetted) { + QPoint iconPosition(sign_rect.right() + sign_margin * 3, sign_rect.center().y() - minus_arrow_down_img.height() / 2 - bounce_offset); + p.drawPixmap(iconPosition, minus_arrow_down_img); + } + + speed_limit_frame++; + speedLimitSignPulse(speed_limit_frame); + } + + // current speed over speed limit + else if (overSpeedLimit && speedLimitWarningFlash) { + speed_limit_frame++; + speedLimitSignPulse(speed_limit_frame); + } + + else { + speed_limit_frame = 0; + slcShowSign = true; + } +} + + +void AnnotatedCameraWidgetSP::initializeGL() { + CameraWidget::initializeGL(); + qInfo() << "OpenGL version:" << QString((const char*)glGetString(GL_VERSION)); + qInfo() << "OpenGL vendor:" << QString((const char*)glGetString(GL_VENDOR)); + qInfo() << "OpenGL renderer:" << QString((const char*)glGetString(GL_RENDERER)); + qInfo() << "OpenGL language version:" << QString((const char*)glGetString(GL_SHADING_LANGUAGE_VERSION)); + + prev_draw_t = millis_since_boot(); + setBackgroundColor(bg_colors[STATUS_DISENGAGED]); +} + +void AnnotatedCameraWidgetSP::updateFrameMat() { + CameraWidget::updateFrameMat(); + UIStateSP *s = uiStateSP(); + int w = width(), h = height(); + + s->fb_w = w; + s->fb_h = h; + + // Apply transformation such that video pixel coordinates match video + // 1) Put (0, 0) in the middle of the video + // 2) Apply same scaling as video + // 3) Put (0, 0) in top left corner of video + s->car_space_transform.reset(); + s->car_space_transform.translate(w / 2 - x_offset, h / 2 - y_offset) + .scale(zoom, zoom) + .translate(-intrinsic_matrix.v[2], -intrinsic_matrix.v[5]); +} + +void AnnotatedCameraWidgetSP::drawLaneLines(QPainter &painter, const UIStateSP *s) { + painter.save(); + + const UISceneSP &scene = s->scene; + SubMaster &sm = *(s->sm); + + const auto car_state = sm["carState"].getCarState(); + + // Shane's colored lanelines + for (int i = 0; i < std::size(scene.lane_line_vertices); ++i) { + if (i == 1 || i == 2) { + // TODO: can we just use the projected vertices somehow? + const cereal::XYZTData::Reader &line = (*s->sm)["modelV2"].getModelV2().getLaneLines()[i]; + const float default_pos = 1.4; // when lane poly isn't available + const float lane_pos = line.getY().size() > 0 ? std::abs(line.getY()[5]) : default_pos; // get redder when line is closer to car + float hue = 332.5 * lane_pos - 332.5; // equivalent to {1.4, 1.0}: {133, 0} (green to red) + hue = std::fmin(133, fmax(0, hue)) / 360.; // clip and normalize + painter.setBrush(QColor::fromHslF(hue, 1.0, 0.50, std::clamp(scene.lane_line_probs[i], 0.0, 0.7))); + } else { + painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp(scene.lane_line_probs[i], 0.0, 0.7))); + } + painter.drawPolygon(scene.lane_line_vertices[i]); + } + + // TODO: Fix empty spaces when curiving back on itself + painter.setBrush(QColor::fromRgbF(1.0, 0.0, 0.0, 0.2)); + if (left_blindspot) painter.drawPolygon(scene.lane_barrier_vertices[0]); + if (right_blindspot) painter.drawPolygon(scene.lane_barrier_vertices[1]); + + // road edges + for (int i = 0; i < std::size(scene.road_edge_vertices); ++i) { + painter.setBrush(QColor::fromRgbF(1.0, 0, 0, std::clamp(1.0 - scene.road_edge_stds[i], 0.0, 1.0))); + painter.drawPolygon(scene.road_edge_vertices[i]); + } + + // paint path + QLinearGradient bg(0, height(), 0, 0); + if (madsEnabled || car_state.getCruiseState().getEnabled()) { + if (steerOverride && latActive) { + bg.setColorAt(0.0, QColor::fromHslF(20 / 360., 0.94, 0.51, 0.17)); + bg.setColorAt(0.5, QColor::fromHslF(20 / 360., 1.0, 0.68, 0.17)); + bg.setColorAt(1.0, QColor::fromHslF(20 / 360., 1.0, 0.68, 0.0)); + } else if (!(latActive || car_state.getCruiseState().getEnabled())) { + bg.setColorAt(0, whiteColor()); + bg.setColorAt(1, whiteColor(0)); + } else if (sm["controlsState"].getControlsState().getExperimentalMode()) { + // The first half of track_vertices are the points for the right side of the path + const auto &acceleration = sm["modelV2"].getModelV2().getAcceleration().getX(); + const int max_len = std::min(scene.track_vertices.length() / 2, acceleration.size()); + + for (int i = 0; i < max_len; ++i) { + // Some points are out of frame + if (scene.track_vertices[i].y() < 0 || scene.track_vertices[i].y() > height()) continue; + + // Flip so 0 is bottom of frame + float lin_grad_point = (height() - scene.track_vertices[i].y()) / height(); + + // speed up: 120, slow down: 0 + float path_hue = fmax(fmin(60 + acceleration[i] * 35, 120), 0); + // FIXME: painter.drawPolygon can be slow if hue is not rounded + path_hue = int(path_hue * 100 + 0.5) / 100; + + float saturation = fmin(fabs(acceleration[i] * 1.5), 1); + float lightness = util::map_val(saturation, 0.0f, 1.0f, 0.95f, 0.62f); // lighter when grey + float alpha = util::map_val(lin_grad_point, 0.75f / 2.f, 0.75f, 0.4f, 0.0f); // matches previous alpha fade + bg.setColorAt(lin_grad_point, QColor::fromHslF(path_hue / 360., saturation, lightness, alpha)); + + // Skip a point, unless next is last + i += (i + 2) < max_len ? 1 : 0; + } + + } else { + bg.setColorAt(0.0, QColor::fromHslF(148 / 360., 0.94, 0.51, 0.4)); + bg.setColorAt(0.5, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.35)); + bg.setColorAt(1.0, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.0)); + } + } else { + bg.setColorAt(0.0, whiteColor(102)); + bg.setColorAt(0.5, whiteColor(89)); + bg.setColorAt(1.0, whiteColor(0)); + } + + painter.setBrush(bg); + painter.drawPolygon(scene.track_vertices); + + // create path combining track vertices and track edge vertices + QPainterPath path; + path.addPolygon(scene.track_vertices); + path.addPolygon(scene.track_edge_vertices); + + // paint path edges + QLinearGradient pe(0, height(), 0, height() / 4); + if (!scene.dynamic_lane_profile_status) { + pe.setColorAt(0.0, QColor::fromHslF(240 / 360., 0.94, 0.51, 1.0)); + pe.setColorAt(0.5, QColor::fromHslF(204 / 360., 1.0, 0.68, 0.5)); + pe.setColorAt(1.0, QColor::fromHslF(204 / 360., 1.0, 0.68, 0.0)); + + painter.setBrush(pe); + painter.drawPath(path); + } + + painter.restore(); +} + +void AnnotatedCameraWidgetSP::drawDriverState(QPainter &painter, const UIStateSP *s) { + const UIScene &scene = s->scene; + + painter.save(); + + // base icon + int offset = UI_BORDER_SIZE + btn_size / 2; + int x = rightHandDM ? width() - offset : offset; + int y = height() - offset - rn_offset; + float opacity = dmActive ? 0.65 : 0.2; + drawIcon(painter, QPoint(x, y), dm_img, blackColor(70), opacity); + + // face + QPointF face_kpts_draw[std::size(default_face_kpts_3d)]; + float kp; + for (int i = 0; i < std::size(default_face_kpts_3d); ++i) { + kp = (scene.face_kpts_draw[i].v[2] - 8) / 120 + 1.0; + face_kpts_draw[i] = QPointF(scene.face_kpts_draw[i].v[0] * kp + x, scene.face_kpts_draw[i].v[1] * kp + y); + } + + painter.setPen(QPen(QColor::fromRgbF(1.0, 1.0, 1.0, opacity), 5.2, Qt::SolidLine, Qt::RoundCap)); + painter.drawPolyline(face_kpts_draw, std::size(default_face_kpts_3d)); + + // tracking arcs + const int arc_l = 133; + const float arc_t_default = 6.7; + const float arc_t_extend = 12.0; + QColor arc_color = QColor::fromRgbF(0.545 - 0.445 * s->engaged(), + 0.545 + 0.4 * s->engaged(), + 0.545 - 0.285 * s->engaged(), + 0.4 * (1.0 - dm_fade_state)); + float delta_x = -scene.driver_pose_sins[1] * arc_l / 2; + float delta_y = -scene.driver_pose_sins[0] * arc_l / 2; + painter.setPen(QPen(arc_color, arc_t_default+arc_t_extend*fmin(1.0, scene.driver_pose_diff[1] * 5.0), Qt::SolidLine, Qt::RoundCap)); + painter.drawArc(QRectF(std::fmin(x + delta_x, x), y - arc_l / 2, fabs(delta_x), arc_l), (scene.driver_pose_sins[1]>0 ? 90 : -90) * 16, 180 * 16); + painter.setPen(QPen(arc_color, arc_t_default+arc_t_extend*fmin(1.0, scene.driver_pose_diff[0] * 5.0), Qt::SolidLine, Qt::RoundCap)); + painter.drawArc(QRectF(x - arc_l / 2, std::fmin(y + delta_y, y), arc_l, fabs(delta_y)), (scene.driver_pose_sins[0]>0 ? 0 : 180) * 16, 180 * 16); + + painter.restore(); +} + +void AnnotatedCameraWidgetSP::rocketFuel(QPainter &p) { + + static const int ct_n = 1; + static float ct; + + int rect_w = rect().width(); + int rect_h = rect().height(); + + const int n = 15 + 1; //Add one off screen due to timing issues + static float t[n]; + int dim_n = (sin(ct / 5) + 1) * (n - 0.01); + t[dim_n] = 1.0; + t[(int)(ct/ct_n)] = 1.0; + + UIStateSP *s = uiStateSP(); + float vc_accel0 = (*s->sm)["carState"].getCarState().getAEgo(); + static float vc_accel; + vc_accel = vc_accel + (vc_accel0 - vc_accel) / 5; + float hha = 0; + if (vc_accel > 0) { + hha = 0.85 - 0.1 / vc_accel; // only extend up to 85% + p.setBrush(QColor(0, 245, 0, 200)); + } + if (vc_accel < 0) { + hha = 0.85 + 0.1 / vc_accel; // only extend up to 85% + p.setBrush(QColor(245, 0, 0, 200)); + } + if (hha < 0) { + hha = 0; + } + hha = hha * rect_h; + float wp = 28; + if (vc_accel > 0) { + QRect ra = QRect(rect_w - wp, rect_h / 2 - hha / 2, wp, hha / 2); + p.drawRect(ra); + } else { + QRect ra = QRect(rect_w - wp, rect_h / 2, wp, hha / 2); + p.drawRect(ra); + } +} + +void AnnotatedCameraWidgetSP::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, + int num, const cereal::CarState::Reader &car_data, int chevron_data) { + painter.save(); + + const float speedBuff = 10.; + const float leadBuff = 40.; + const float d_rel = lead_data.getDRel(); + const float v_rel = lead_data.getVRel(); + const float v_ego = car_data.getVEgo(); + + float fillAlpha = 0; + if (d_rel < leadBuff) { + fillAlpha = 255 * (1.0 - (d_rel / leadBuff)); + if (v_rel < 0) { + fillAlpha += 255 * (-1 * (v_rel / speedBuff)); + } + fillAlpha = (int)(fmin(fillAlpha, 255)); + } + + float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35; + float x = std::clamp((float)vd.x(), 0.f, width() - sz / 2); + float y = std::fmin(height() - sz * .6, (float)vd.y()); + + float g_xo = sz / 5; + float g_yo = sz / 10; + + QPointF glow[] = {{x + (sz * 1.35) + g_xo, y + sz + g_yo}, {x, y - g_yo}, {x - (sz * 1.35) - g_xo, y + sz + g_yo}}; + painter.setBrush(QColor(218, 202, 37, 255)); + painter.drawPolygon(glow, std::size(glow)); + + // chevron + QPointF chevron[] = {{x + (sz * 1.25), y + sz}, {x, y}, {x - (sz * 1.25), y + sz}}; + painter.setBrush(redColor(fillAlpha)); + painter.drawPolygon(chevron, std::size(chevron)); + + if (num == 0) { // Display metrics to the 0th lead car + const int chevron_types = 3; + const int chevron_all = chevron_types + 1; // All metrics + QStringList chevron_text[chevron_types]; + int position; + float val; + if (chevron_data == 1 || chevron_data == chevron_all) { + position = 0; + val = std::max(0.0f, d_rel); + chevron_text[position].append(QString::number(val,'f', 0) + " " + "m"); + } + if (chevron_data == 2 || chevron_data == chevron_all) { + position = (chevron_data == 2) ? 0 : 1; + val = std::max(0.0f, (v_rel + v_ego) * (is_metric ? static_cast(MS_TO_KPH) : static_cast(MS_TO_MPH))); + chevron_text[position].append(QString::number(val,'f', 0) + " " + (is_metric ? "km/h" : "mph")); + } + if (chevron_data == 3 || chevron_data == chevron_all) { + position = (chevron_data == 3) ? 0 : 2; + val = (d_rel > 0 && v_ego > 0) ? std::max(0.0f, d_rel / v_ego) : 0.0f; + + QString ttc_str = (val > 0 && val < 200) ? QString::number(val, 'f', 1) + "s" : "---"; + chevron_text[position].append(ttc_str); + } + + float str_w = 200; // Width of the text box, might need adjustment + float str_h = 50; // Height of the text box, adjust as necessary + painter.setFont(InterFont(45, QFont::Bold)); + // Calculate the center of the chevron and adjust the text box position + float text_y = y + sz + 12; // Position the text at the bottom of the chevron + QRect textRect(x - str_w / 2, text_y, str_w, str_h); // Adjust the rectangle to center the text horizontally at the chevron's bottom + QPoint shadow_offset(2, 2); + for (int i = 0; i < chevron_types; ++i) { + if (!chevron_text[i].isEmpty()) { + painter.setPen(QColor(0x0, 0x0, 0x0, 200)); // Draw shadow + painter.drawText(textRect.translated(shadow_offset.x(), shadow_offset.y() + i * str_h), Qt::AlignBottom | Qt::AlignHCenter, chevron_text[i].at(0)); + painter.setPen(QColor(0xff, 0xff, 0xff)); // Draw text + painter.drawText(textRect.translated(0, i * str_h), Qt::AlignBottom | Qt::AlignHCenter, chevron_text[i].at(0)); + painter.setPen(Qt::NoPen); // Reset pen to default + } + } + } + + painter.restore(); +} + +void AnnotatedCameraWidgetSP::paintGL() { + UIStateSP *s = uiStateSP(); + SubMaster &sm = *(s->sm); + const double start_draw_t = millis_since_boot(); + const cereal::ModelDataV2::Reader &model = sm["modelV2"].getModelV2(); + + // draw camera frame + { + std::lock_guard lk(frame_lock); + + if (frames.empty()) { + if (skip_frame_count > 0) { + skip_frame_count--; + qDebug() << "skipping frame, not ready"; + return; + } + } else { + // skip drawing up to this many frames if we're + // missing camera frames. this smooths out the + // transitions from the narrow and wide cameras + skip_frame_count = 5; + } + + // Wide or narrow cam dependent on speed + bool has_wide_cam = available_streams.count(VISION_STREAM_WIDE_ROAD); + if (has_wide_cam) { + float v_ego = sm["carState"].getCarState().getVEgo(); + float steer_angle = sm["carState"].getCarState().getSteeringAngleDeg(); + if ((v_ego < 10) || available_streams.size() == 1 || (std::fabs(steer_angle) > 45)) { + wide_cam_requested = true; + } else if ((v_ego > 15) && (std::fabs(steer_angle) < 30)) { + wide_cam_requested = false; + } + wide_cam_requested = wide_cam_requested && sm["controlsState"].getControlsState().getExperimentalMode(); + // for replay of old routes, never go to widecam + wide_cam_requested = wide_cam_requested && s->scene.calibration_wide_valid; + } + CameraWidget::setStreamType(wide_cam_requested ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD); + + if (reversing && s->scene.reverse_dm_cam) { + CameraWidget::setStreamType(VISION_STREAM_DRIVER, s->scene.reverse_dm_cam); + } + + s->scene.wide_cam = CameraWidget::getStreamType() == VISION_STREAM_WIDE_ROAD; + if (s->scene.calibration_valid) { + auto calib = s->scene.wide_cam ? s->scene.view_from_wide_calib : s->scene.view_from_calib; + CameraWidget::updateCalibration(calib); + } else { + CameraWidget::updateCalibration(DEFAULT_CALIBRATION); + } + CameraWidget::setFrameId(model.getFrameId()); + CameraWidget::paintGL(); + } + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(Qt::NoPen); + + if (s->scene.world_objects_visible) { + sp_update_model(s, model); + drawLaneLines(painter, s); + + if (s->scene.longitudinal_control && sm.rcv_frame("radarState") > s->scene.started_frame) { + auto radar_state = sm["radarState"].getRadarState(); + auto car_state = sm["carState"].getCarState(); + update_leads(s, radar_state, model.getPosition()); + auto lead_one = radar_state.getLeadOne(); + auto lead_two = radar_state.getLeadTwo(); + if (lead_one.getStatus()) { + drawLead(painter, lead_one, s->scene.lead_vertices[0], 0, car_state, s->scene.chevron_data); + } + if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) { + drawLead(painter, lead_two, s->scene.lead_vertices[1], 1, car_state, s->scene.chevron_data); + } + + rocketFuel(painter); + } + } + + // DMoji + if (!hideBottomIcons && (sm.rcv_frame("driverStateV2") > s->scene.started_frame)) { + update_dmonitoring(s, sm["driverStateV2"].getDriverStateV2(), dm_fade_state, rightHandDM); + drawDriverState(painter, s); + } + + drawHud(painter); + + if (left_blinker || right_blinker) { + blinker_frame++; + int state = blinkerPulse(blinker_frame); + int blinker_x = splitPanelVisible ? 150 : 180; + int blinker_y = splitPanelVisible ? 220 : 90; + if (left_blinker) { + drawLeftTurnSignal(painter, rect().center().x() - (blinker_x + blinker_size), blinker_y, blinker_size, state); + } + if (right_blinker) { + drawRightTurnSignal(painter, rect().center().x() + blinker_x, blinker_y, blinker_size, state); + } + } else { + blinker_frame = 0; + } + + double cur_draw_t = millis_since_boot(); + double dt = cur_draw_t - prev_draw_t; + double fps = fps_filter.update(1. / dt * 1000); + if (fps < 15) { + LOGW("slow frame rate: %.2f fps", fps); + } + prev_draw_t = cur_draw_t; + + // publish debug msg + MessageBuilder msg; + auto m = msg.initEvent().initUiDebug(); + m.setDrawTimeMillis(cur_draw_t - start_draw_t); + pm->send("uiDebug", msg); + + MessageBuilder e2e_long_msg; + auto e2eLongStatus = e2e_long_msg.initEvent().initE2eLongStateSP(); + e2eLongStatus.setStatus(e2eStatus); + e2e_state->send("e2eLongStateSP", e2e_long_msg); +} + +void AnnotatedCameraWidgetSP::showEvent(QShowEvent *event) { + CameraWidget::showEvent(event); + + sp_ui_update_params(uiStateSP()); + prev_draw_t = millis_since_boot(); +} diff --git a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h new file mode 100644 index 0000000000..92bf5056b3 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h @@ -0,0 +1,230 @@ +/** +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 +#include +#include + +#include "selfdrive/ui/qt/onroad/buttons.h" +#include "selfdrive/ui/qt/widgets/cameraview.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/buttons.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.h" + +const int subsign_img_size = 35; +const int blinker_size = 120; + +class AnnotatedCameraWidgetSP : public CameraWidget { + Q_OBJECT + +public: + explicit AnnotatedCameraWidgetSP(VisionStreamType type, QWidget* parent = 0); + void updateState(const UIStateSP &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 drawDevUiRight(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color); + int drawNewDevUi(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); + + // ############################## 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; + ExperimentalButtonSP *experimental_btn; + QPixmap dm_img; + 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 pm; + + 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 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 UIStateSP *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 drawHud(QPainter &p); + void drawDriverState(QPainter &painter, const UIStateSP *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 rocketFuel(QPainter &p); + + double prev_draw_t = 0; + FirstOrderFilter fps_filter; +}; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/buttons.cc b/selfdrive/ui/sunnypilot/qt/onroad/buttons.cc new file mode 100644 index 0000000000..3a4fe68695 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/buttons.cc @@ -0,0 +1,96 @@ +/** +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/onroad/buttons.h" + +#include "selfdrive/ui/qt/util.h" + +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); +} + +// ExperimentalButtonSP +void ExperimentalButtonSP::updateState(const UIStateSP &s) { + const auto cs = (*s.sm)["controlsState"].getControlsState(); + bool eng = cs.getEngageable() || cs.getEnabled(); + if ((cs.getExperimentalMode() != experimental_mode) || (eng != engageable)) { + engageable = eng; + experimental_mode = cs.getExperimentalMode(); + update(); + } +} + + +// 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 UIStateSP &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); +} + +// 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); +} diff --git a/selfdrive/ui/sunnypilot/qt/onroad/buttons.h b/selfdrive/ui/sunnypilot/qt/onroad/buttons.h new file mode 100644 index 0000000000..055f3dab32 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/buttons.h @@ -0,0 +1,68 @@ +/** +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 +#include "selfdrive/ui/qt/onroad/buttons.h" + +#include "selfdrive/ui/sunnypilot/ui.h" + +#include "selfdrive/ui/ui.h" + +class ExperimentalButtonSP : public ExperimentalButton { + Q_OBJECT + +public: + explicit ExperimentalButtonSP(QWidget *parent = 0) : ExperimentalButton(parent) {}; + void updateState(const UIStateSP &s); +}; + +class OnroadSettingsButton : public QPushButton { + Q_OBJECT + +public: + explicit OnroadSettingsButton(QWidget *parent = 0); + void updateState(const UIStateSP &s); + +private: + void paintEvent(QPaintEvent *event) override; + + QPixmap settings_img; +}; + + +class MapSettingsButton : public QPushButton { + Q_OBJECT + +public: + explicit MapSettingsButton(QWidget *parent = 0); + +private: + void paintEvent(QPaintEvent *event) override; + + QPixmap settings_img; +}; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.cc b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.cc new file mode 100644 index 0000000000..dd22ebdcb8 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.cc @@ -0,0 +1,224 @@ +/** +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/onroad/developer_ui/developer_ui.h" + +#include + +#include "common/util.h" + +// ********** metrics ********** + +// Add Relative Distance to Primary Lead Car +// Unit: Meters +UiElement DeveloperUi::getDRel(bool lead_status, float lead_d_rel) { + QString value = lead_status ? QString::number(lead_d_rel, 'f', 0) : "-"; + QColor color = QColor(255, 255, 255, 255); + + if (lead_status) { + // Orange if close, Red if very close + if (lead_d_rel < 5) { + color = QColor(255, 0, 0, 255); + } else if (lead_d_rel < 15) { + color = QColor(255, 188, 0, 255); + } + } + + return UiElement(value, "REL DIST", "m", color); +} + +// Add Relative Velocity vs Primary Lead Car +// Unit: kph if metric, else mph +UiElement DeveloperUi::getVRel(bool lead_status, float lead_v_rel, bool is_metric, const QString &speed_unit) { + QString value = lead_status ? QString::number(lead_v_rel * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-"; + QColor color = QColor(255, 255, 255, 255); + + if (lead_status) { + // Red if approaching faster than 10mph + // Orange if approaching (negative) + if (lead_v_rel < -4.4704) { + color = QColor(255, 0, 0, 255); + } else if (lead_v_rel < 0) { + color = QColor(255, 188, 0, 255); + } + } + + return UiElement(value, "REL SPEED", speed_unit, color); +} + +// Add Real Steering Angle +// Unit: Degrees +UiElement DeveloperUi::getSteeringAngleDeg(float angle_steers, bool mads_enabled, bool lat_active) { + QString value = QString("%1%2%3").arg(QString::number(angle_steers, 'f', 1)).arg("°").arg(""); + QColor color = (mads_enabled && lat_active) ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); + + // Red if large steering angle + // Orange if moderate steering angle + if (std::fabs(angle_steers) > 180) { + color = QColor(255, 0, 0, 255); + } else if (std::fabs(angle_steers) > 90) { + color = QColor(255, 188, 0, 255); + } + + return UiElement(value, "REAL STEER", "", color); +} + +// Add Actual Lateral Acceleration (roll compensated) when using Torque +// Unit: m/s² +UiElement DeveloperUi::getActualLateralAccel(float curvature, float v_ego, float roll, bool mads_enabled, bool lat_active) { + double actualLateralAccel = (curvature * pow(v_ego, 2)) - (roll * 9.81); + + QString value = QString::number(actualLateralAccel, 'f', 2); + QColor color = (mads_enabled && lat_active) ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); + + return UiElement(value, "ACTUAL LAT", "m/s²", color); +} + +// Add Desired Steering Angle when using PID +// Unit: Degrees +UiElement DeveloperUi::getSteeringAngleDesiredDeg(bool mads_enabled, bool lat_active, float steer_angle_desired, float angle_steers) { + QString value = (mads_enabled && lat_active) ? QString("%1%2%3").arg(QString::number(steer_angle_desired, 'f', 1)).arg("°").arg("") : "-"; + QColor color = QColor(255, 255, 255, 255); + + if (mads_enabled && lat_active) { + // Red if large steering angle + // Orange if moderate steering angle + if (std::fabs(angle_steers) > 180) { + color = QColor(255, 0, 0, 255); + } else if (std::fabs(angle_steers) > 90) { + color = QColor(255, 188, 0, 255); + } else { + color = QColor(0, 255, 0, 255); + } + } + + return UiElement(value, "DESIRED STEER", "", color); +} + +// Add Device Memory (RAM) Usage +// Unit: Percent +UiElement DeveloperUi::getMemoryUsagePercent(int memory_usage_percent) { + QString value = QString("%1%2").arg(QString::number(memory_usage_percent, 'd', 0)).arg("%"); + QColor color = (memory_usage_percent > 85) ? QColor(255, 188, 0, 255) : QColor(255, 255, 255, 255); + + return UiElement(value, "RAM", "", color); +} + +// Add Vehicle Current Acceleration +// Unit: m/s² +UiElement DeveloperUi::getAEgo(float a_ego) { + QString value = QString::number(a_ego, 'f', 1); + QColor color = QColor(255, 255, 255, 255); + + return UiElement(value, "ACC.", "m/s²", color); +} + +// Add Relative Velocity to Primary Lead Car +// Unit: kph if metric, else mph +UiElement DeveloperUi::getVEgoLead(bool lead_status, float lead_v_rel, float v_ego, bool is_metric, const QString &speed_unit) { + QString value = lead_status ? QString::number((lead_v_rel + v_ego) * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-"; + QColor color = QColor(255, 255, 255, 255); + + if (lead_status) { + // Red if approaching faster than 10mph + // Orange if approaching (negative) + if (lead_v_rel < -4.4704) { + color = QColor(255, 0, 0, 255); + } else if (lead_v_rel < 0) { + color = QColor(255, 188, 0, 255); + } + } + + return UiElement(value, "L.S.", speed_unit, color); +} + +// Add Friction Coefficient Raw from torqued +// Unit: None +UiElement DeveloperUi::getFrictionCoefficientFiltered(float friction_coefficient_filtered, bool live_valid) { + QString value = QString::number(friction_coefficient_filtered, 'f', 3); + QColor color = live_valid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); + + return UiElement(value, "FRIC.", "", color); +} + +// Add Lateral Acceleration Factor Raw from torqued +// Unit: m/s² +UiElement DeveloperUi::getLatAccelFactorFiltered(float lat_accel_factor_filtered, bool live_valid) { + QString value = QString::number(lat_accel_factor_filtered, 'f', 3); + QColor color = live_valid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); + + return UiElement(value, "L.A.", "m/s²", color); +} + +// Add Steering Torque from Car EPS +// Unit: Newton Meters +UiElement DeveloperUi::getSteeringTorqueEps(float steering_torque_eps) { + QString value = QString::number(std::fabs(steering_torque_eps), 'f', 1); + QColor color = QColor(255, 255, 255, 255); + + return UiElement(value, "E.T.", "N·dm", color); +} + +// Add Bearing Degree and Direction from Car (Compass) +// Unit: Meters +UiElement DeveloperUi::getBearingDeg(float bearing_accuracy_deg, float bearing_deg) { + QString value = (bearing_accuracy_deg != 180.00) ? QString("%1%2%3").arg(QString::number(bearing_deg, 'd', 0)).arg("°").arg("") : "-"; + QColor color = QColor(255, 255, 255, 255); + QString dir_value; + + if (bearing_accuracy_deg != 180.00) { + if (((bearing_deg >= 337.5) && (bearing_deg <= 360)) || ((bearing_deg >= 0) && (bearing_deg <= 22.5))) { + dir_value = "N"; + } else if ((bearing_deg > 22.5) && (bearing_deg < 67.5)) { + dir_value = "NE"; + } else if ((bearing_deg >= 67.5) && (bearing_deg <= 112.5)) { + dir_value = "E"; + } else if ((bearing_deg > 112.5) && (bearing_deg < 157.5)) { + dir_value = "SE"; + } else if ((bearing_deg >= 157.5) && (bearing_deg <= 202.5)) { + dir_value = "S"; + } else if ((bearing_deg > 202.5) && (bearing_deg < 247.5)) { + dir_value = "SW"; + } else if ((bearing_deg >= 247.5) && (bearing_deg <= 292.5)) { + dir_value = "W"; + } else if ((bearing_deg > 292.5) && (bearing_deg < 337.5)) { + dir_value = "NW"; + } + } else { + dir_value = "OFF"; + } + + return UiElement(QString("%1 | %2").arg(dir_value).arg(value), "B.D.", "", color); +} + +// Add Altitude of Current Location +// Unit: Meters +UiElement DeveloperUi::getAltitude(float gps_accuracy, float altitude) { + QString value = (gps_accuracy != 0.00) ? QString::number(altitude, 'f', 1) : "-"; + QColor color = QColor(255, 255, 255, 255); + + return UiElement(value, "ALT.", "m", color); +} diff --git a/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.h b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.h new file mode 100644 index 0000000000..2cb9b9a76a --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.h @@ -0,0 +1,46 @@ +/** +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/onroad/developer_ui/ui_elements.h" + +class DeveloperUi { +public: + static UiElement getDRel(bool lead_status, float lead_d_rel); + static UiElement getVRel(bool lead_status, float lead_v_rel, bool is_metric, const QString &speed_unit); + static UiElement getSteeringAngleDeg(float angle_steers, bool mads_enabled, bool lat_active); + static UiElement getActualLateralAccel(float curvature, float v_ego, float roll, bool mads_enabled, bool lat_active); + static UiElement getSteeringAngleDesiredDeg(bool mads_enabled, bool lat_active, float steer_angle_desired, float angle_steers); + static UiElement getMemoryUsagePercent(int memory_usage_percent); + static UiElement getAEgo(float a_ego); + static UiElement getVEgoLead(bool lead_status, float lead_v_rel, float v_ego, bool is_metric, const QString &speed_unit); + static UiElement getFrictionCoefficientFiltered(float friction_coefficient_filtered, bool live_valid); + static UiElement getLatAccelFactorFiltered(float lat_accel_factor_filtered, bool live_valid); + static UiElement getSteeringTorqueEps(float steering_torque_eps); + static UiElement getBearingDeg(float bearing_accuracy_deg, float bearing_deg); + static UiElement getAltitude(float gps_accuracy, float altitude); +}; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/ui_elements.h b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/ui_elements.h new file mode 100644 index 0000000000..3910c83022 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/ui_elements.h @@ -0,0 +1,39 @@ +/** +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 + +struct UiElement { + QString value{}; + QString label{}; + QString units{}; + QColor color{}; + + explicit 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) {} +}; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_home.cc b/selfdrive/ui/sunnypilot/qt/onroad/onroad_home.cc new file mode 100644 index 0000000000..a7acf4865c --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_home.cc @@ -0,0 +1,163 @@ +/** +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/onroad/onroad_home.h" + + +#ifdef ENABLE_MAPS +#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h" +#include "selfdrive/ui/qt/maps/map_panel.h" +#endif +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h" +#endif + +#include "common/swaglog.h" +#include "selfdrive/ui/qt/util.h" + +OnroadWindowSP::OnroadWindowSP(QWidget *parent) : OnroadWindow(parent) { + // QObject::disconnect(uiState(), &UIState::uiUpdate, this, &OnroadWindow::updateState); + // QObject::disconnect(uiState(), &UIState::offroadTransition, this, &OnroadWindow::offroadTransition); + + if (getenv("MAP_RENDER_VIEW")) { + CameraWidget *map_render = new CameraWidget("navd", VISION_STREAM_MAP, false, this); + split->insertWidget(0, map_render); //TODO: We MIGHT to override the split variable because it is added on onroad_home.cc and we need to access it before. I am not sure so we will need to test it before + } + QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &OnroadWindowSP::updateState); + QObject::connect(uiStateSP(), &UIStateSP::offroadTransition, this, &OnroadWindowSP::offroadTransition); +} + +void OnroadWindowSP::updateState(const UIStateSP &s) { + if (!s.scene.started) { + return; + } + + if (s.scene.map_on_left || s.scene.mapbox_fullscreen) { + split->setDirection(QBoxLayout::LeftToRight); + } else { + split->setDirection(QBoxLayout::RightToLeft); + } + + OnroadWindow::updateState(s); // Carry on with the parent class updateState method +} + + +void OnroadWindowSP::mousePressEvent(QMouseEvent* e) { +#ifdef ENABLE_MAPS + UIStateSP *s = uiStateSP(); + UISceneSP &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 = uiStateSP()->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 OnroadWindowSP::createMapWidget() { +#ifdef ENABLE_MAPS + LOGD("Creating map widget"); + auto m = new MapPanel(get_mapbox_settings()); + map = m; + + QObject::connect(m, &MapPanel::mapPanelRequested, this, &OnroadWindowSP::mapPanelRequested); + QObject::connect(m, &MapPanel::mapPanelRequested, this, &OnroadWindowSP::onroadSettingsPanelNotRequested); + QObject::connect(nvg->map_settings_btn, &MapSettingsButton::clicked, m, &MapPanel::toggleMapSettings); + nvg->map_settings_btn->setEnabled(true); + + m->setFixedWidth(uiStateSP()->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 OnroadWindowSP::createOnroadSettingsWidget() { + auto os = new OnroadSettingsPanel(this); + onroad_settings = os; + + QObject::connect(os, &OnroadSettingsPanel::onroadSettingsPanelRequested, this, &OnroadWindowSP::onroadSettingsPanelRequested); + QObject::connect(os, &OnroadSettingsPanel::onroadSettingsPanelRequested, this, &OnroadWindowSP::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 OnroadWindowSP::offroadTransition(bool offroad) { + + if (!offroad) { +#ifdef ENABLE_MAPS + LOGD("We'd like to create the map widget, the condition is map is [%s] and hasPrime is [%s] and MAPBOX_TOKEN is [%s]", map == nullptr ? "null" : "not null", uiStateSP()->hasPrime() ? "true" : "false", MAPBOX_TOKEN.isEmpty() ? "empty" : "not empty"); + if (map == nullptr && (uiStateSP()->hasPrime() || !MAPBOX_TOKEN.isEmpty())) { + createMapWidget(); + } +#else + LOGD("Maps are disabled"); +#endif + if (onroad_settings == nullptr) { + createOnroadSettingsWidget(); + } + } + + OnroadWindow::offroadTransition(offroad); // Carry on with the parent class offroadTransition method +} + +void OnroadWindowSP::updateMapSize(const UISceneSP &scene) { + map->setFixedWidth(scene.mapbox_fullscreen ? topWidget(this)->width() : + topWidget(this)->width() / 2 - UI_BORDER_SIZE); + split->insertWidget(0, map); +} + +void OnroadWindowSP::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 +} diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_home.h b/selfdrive/ui/sunnypilot/qt/onroad/onroad_home.h new file mode 100644 index 0000000000..121c0ba75d --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_home.h @@ -0,0 +1,72 @@ +/** +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/onroad/onroad_home.h" + +#include "common/params.h" + +class OnroadWindowSP : public OnroadWindow { + Q_OBJECT + +public: + OnroadWindowSP(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 ((uiStateSP()->scene.sleep_btn != 0 && uiStateSP()->scene.sleep_btn_opacity != 0) || + (uiStateSP()->scene.sleep_time != 0 && uiStateSP()->scene.onroadScreenOff != -2)) { + return true; + } + return false; + } + +signals: + void mapPanelRequested(); + void onroadSettingsPanelRequested(); + +private: + void createMapWidget(); + void createOnroadSettingsWidget(); + void mousePressEvent(QMouseEvent* e) override; + QWidget *map = nullptr; + QWidget *onroad_settings = nullptr; + + Params params; + +protected slots: + void offroadTransition(bool offroad) override; + void updateState(const UIStateSP &s) override; + void primeChanged(bool prime); + void updateMapSize(const UISceneSP &scene); +}; diff --git a/selfdrive/ui/qt/onroad_settings.cc b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc similarity index 86% rename from selfdrive/ui/qt/onroad_settings.cc rename to selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc index 00810294ba..e886e4b43e 100644 --- a/selfdrive/ui/qt/onroad_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc @@ -1,13 +1,40 @@ -#include "selfdrive/ui/qt/onroad_settings.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 +***/ + +#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h" + +#include +#include #include #include -#include #include "common/util.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.h" OnroadSettings::OnroadSettings(bool closeable, QWidget *parent) : QFrame(parent) { setContentsMargins(0, 0, 0, 0); @@ -87,7 +114,7 @@ OnroadSettings::OnroadSettings(bool closeable, QWidget *parent) : QFrame(parent) options_layout->addStretch(); - ScrollView *options_scroller = new ScrollView(options_container, this); + ScrollViewSP *options_scroller = new ScrollViewSP(options_container, this); options_scroller->setFrameShape(QFrame::NoFrame); frame->addWidget(options_scroller); @@ -118,7 +145,7 @@ OnroadSettings::OnroadSettings(bool closeable, QWidget *parent) : QFrame(parent) } void OnroadSettings::changeDynamicLaneProfile() { - UIScene &scene = uiState()->scene; + UISceneSP &scene = uiStateSP()->scene; bool can_change = scene.driving_model_generation == cereal::ModelGeneration::ONE; if (can_change) { scene.dynamic_lane_profile++; @@ -129,8 +156,8 @@ void OnroadSettings::changeDynamicLaneProfile() { } void OnroadSettings::changeGapAdjustCruise() { - UIScene &scene = uiState()->scene; - const auto cp = (*uiState()->sm)["carParams"].getCarParams(); + UISceneSP &scene = uiStateSP()->scene; + const auto cp = (*uiStateSP()->sm)["carParams"].getCarParams(); bool can_change = hasLongitudinalControl(cp); if (can_change) { scene.longitudinal_personality--; @@ -141,8 +168,8 @@ void OnroadSettings::changeGapAdjustCruise() { } void OnroadSettings::changeAccelerationPersonality() { - UIScene &scene = uiState()->scene; - const auto cp = (*uiState()->sm)["carParams"].getCarParams(); + UISceneSP &scene = uiStateSP()->scene; + const auto cp = (*uiStateSP()->sm)["carParams"].getCarParams(); bool can_change = hasLongitudinalControl(cp); if (can_change) { scene.longitudinal_accel_personality--; @@ -153,8 +180,8 @@ void OnroadSettings::changeAccelerationPersonality() { } void OnroadSettings::changeDynamicPersonality() { - UIScene &scene = uiState()->scene; - const auto cp = (*uiState()->sm)["carParams"].getCarParams(); + UISceneSP &scene = uiStateSP()->scene; + const auto cp = (*uiStateSP()->sm)["carParams"].getCarParams(); bool can_change = hasLongitudinalControl(cp); if (can_change) { scene.dynamic_personality = !scene.dynamic_personality; @@ -164,8 +191,8 @@ void OnroadSettings::changeDynamicPersonality() { } void OnroadSettings::changeDynamicExperimentalControl() { - UIScene &scene = uiState()->scene; - const auto cp = (*uiState()->sm)["carParams"].getCarParams(); + UISceneSP &scene = uiStateSP()->scene; + const auto cp = (*uiStateSP()->sm)["carParams"].getCarParams(); bool can_change = hasLongitudinalControl(cp); if (can_change) { scene.dynamic_experimental_control = !scene.dynamic_experimental_control; @@ -175,8 +202,8 @@ void OnroadSettings::changeDynamicExperimentalControl() { } void OnroadSettings::changeSpeedLimitControl() { - UIScene &scene = uiState()->scene; - const auto cp = (*uiState()->sm)["carParams"].getCarParams(); + UISceneSP &scene = uiStateSP()->scene; + const auto cp = (*uiStateSP()->sm)["carParams"].getCarParams(); bool can_change = hasLongitudinalControl(cp) || !cp.getPcmCruiseSpeed(); int max_policy = SpeedLimitPolicySettings::speed_limit_policy_texts.size() - 1; @@ -185,7 +212,7 @@ void OnroadSettings::changeSpeedLimitControl() { // If EnableSlc is OFF, set it to ON and reset SpeedLimitControlPolicy scene.speed_limit_control_enabled = true; scene.speed_limit_control_policy = 0; - } else if(scene.speed_limit_control_policy < max_policy) { + } else if (scene.speed_limit_control_policy < max_policy) { // If EnableSlc is already ON then increase SpeedLimitControlPolicy till it reaches 6 scene.speed_limit_control_policy++; } else { @@ -211,7 +238,7 @@ void OnroadSettings::refresh() { param_watcher->addParam("DynamicExperimentalControl"); param_watcher->addParam("EnableSlc"); - UIScene &scene = uiState()->scene; + UISceneSP &scene = uiStateSP()->scene; // Update live params on Feature Status on camera view scene.dynamic_lane_profile = std::atoi(params.get("DynamicLaneProfile").c_str()); scene.longitudinal_personality = std::atoi(params.get("LongitudinalPersonality").c_str()); @@ -225,7 +252,7 @@ void OnroadSettings::refresh() { setUpdatesEnabled(false); - const auto cp = (*uiState()->sm)["carParams"].getCarParams(); + const auto cp = (*uiStateSP()->sm)["carParams"].getCarParams(); // Dynamic Lane Profile dlp_widget->updateDynamicLaneProfile("DynamicLaneProfile"); @@ -271,11 +298,11 @@ OptionWidget::OptionWidget(QWidget *parent) : QPushButton(parent) { inner_frame->setContentsMargins(0, 0, 0, 0); inner_frame->setSpacing(0); { - title = new ElidedLabel(this); + title = new ElidedLabelSP(this); title->setAttribute(Qt::WA_TransparentForMouseEvents); inner_frame->addWidget(title); - subtitle = new ElidedLabel(this); + subtitle = new ElidedLabelSP(this); subtitle->setAttribute(Qt::WA_TransparentForMouseEvents); subtitle->setObjectName("subtitle"); inner_frame->addWidget(subtitle); @@ -304,12 +331,10 @@ void OptionWidget::updateDynamicLaneProfile(QString param) { if (dlp == 0) { title_text = "Laneful"; icon_color = "#2020f8"; - } - else if (dlp == 1) { + } else if (dlp == 1) { title_text = "Laneless"; icon_color = "#0df87a"; - } - else if (dlp == 2) { + } else if (dlp == 2) { title_text = "Auto"; icon_color = "#0df8f8"; } @@ -451,8 +476,7 @@ void OptionWidget::updateSpeedLimitControl(QString param) { if (enable_slc_status == 0) { title_text = "Disabled"; icon_color = "#3B4356"; // Color for "Disabled status" - } - else if (enable_slc_status == 1) { + } else if (enable_slc_status == 1) { title_text = title_text; icon_color = color_map[speed_limit_control_policy_status]; // Color for "Enabled status" } diff --git a/selfdrive/ui/qt/onroad_settings.h b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h similarity index 51% rename from selfdrive/ui/qt/onroad_settings.h rename to selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h index fe881d1f07..372688bfa5 100644 --- a/selfdrive/ui/qt/onroad_settings.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h @@ -1,14 +1,39 @@ +/** +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 -#include -#include -#include - #include "common/params.h" -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/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 class OptionWidget; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc new file mode 100644 index 0000000000..47a6f06235 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc @@ -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 +***/ + +#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h" + +#include + +#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/sunnypilot/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(); + } +} diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h new file mode 100644 index 0000000000..57603c4e88 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h @@ -0,0 +1,49 @@ +/** +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 +#ifdef ENABLE_MAPS +#include +#endif +#include + +class OnroadSettingsPanel : public QFrame { + Q_OBJECT + +public: + explicit OnroadSettingsPanel(QWidget *parent = nullptr); + +signals: + void onroadSettingsPanelRequested(); + +public slots: + void toggleOnroadSettings(); + +private: + QStackedLayout *content_stack; +}; diff --git a/selfdrive/ui/sunnypilot/qt/request_repeater.cc b/selfdrive/ui/sunnypilot/qt/request_repeater.cc new file mode 100644 index 0000000000..91ac3cbff8 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/request_repeater.cc @@ -0,0 +1,62 @@ +/** +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/request_repeater.h" + +#include "common/swaglog.h" + +RequestRepeaterSP::RequestRepeaterSP(QObject *parent, const QString &requestURL, const QString &cacheKey, + int period, bool whileOnroad, bool sunnylink) : HttpRequestSP(parent, true, 20000, sunnylink) { + request_url = requestURL; + while_onroad = whileOnroad; + timer = new QTimer(this); + timer->setTimerType(Qt::VeryCoarseTimer); + connect(timer, &QTimer::timeout, [=]() { this->timerTick(); }); + timer->start(period * 1000); + + 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) { + 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 RequestRepeaterSP::timerTick() { + if ((!uiState()->scene.started || while_onroad) && device()->isAwake() && !active()) { + LOGD("Sending request for %s", qPrintable(request_url)); + sendRequest(request_url); + } +} diff --git a/selfdrive/ui/sunnypilot/qt/request_repeater.h b/selfdrive/ui/sunnypilot/qt/request_repeater.h new file mode 100644 index 0000000000..2af97a0fca --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/request_repeater.h @@ -0,0 +1,52 @@ +/** +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/request_repeater.h" + +#include "common/swaglog.h" +#include "common/util.h" +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/api.h" + +class RequestRepeaterSP : public HttpRequestSP { + +private: + Params params; + QTimer *timer; + QString prevResp; + QString request_url; + bool while_onroad; + void timerTick(); + +public: + RequestRepeaterSP(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(); + } +}; diff --git a/selfdrive/ui/sunnypilot/qt/sidebar.cc b/selfdrive/ui/sunnypilot/qt/sidebar.cc new file mode 100644 index 0000000000..67447a57e6 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sidebar.cc @@ -0,0 +1,132 @@ +/** +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/sidebar.h" + +#include + +#include + +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/sunnypilot/qt/util.h" +#include "common/params.h" + +SidebarSP::SidebarSP(QWidget *parent) : Sidebar(parent) { + // Because I know that stock sidebar makes this connection, I will disconnect it and connect it to the new updateState function + QObject::disconnect(uiState(), &UIState::uiUpdate, this, &Sidebar::updateState); + QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &SidebarSP::updateState); +} + +void SidebarSP::updateState(const UIStateSP &s) { + if (!isVisible()) return; + Sidebar::updateState(s); + auto &sm = *(s.sm); + auto deviceState = sm["deviceState"].getDeviceState(); + + 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::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::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}; + auto ts = deviceState.getThermalStatus(); + if (ts == cereal::DeviceState::ThermalStatus::GREEN) { + tempStatus = {{tr("TEMP"), show_sidebar_temp ? sidebar_temp_str : tr("GOOD")}, good_color}; + } else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) { + tempStatus = {{tr("TEMP"), show_sidebar_temp ? sidebar_temp_str : tr("OK")}, warning_color}; + } + setProperty("tempStatus", QVariant::fromValue(tempStatus)); + + 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 SidebarSP::DrawSidebar(QPainter &p){ + Sidebar::DrawSidebar(p); + // 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); +} diff --git a/selfdrive/ui/sunnypilot/qt/sidebar.h b/selfdrive/ui/sunnypilot/qt/sidebar.h new file mode 100644 index 0000000000..69de76f7a0 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sidebar.h @@ -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 +***/ + +#pragma once + +#include + +#include "selfdrive/ui/qt/sidebar.h" + +#include "selfdrive/ui/sunnypilot/ui.h" + +class SidebarSP : public Sidebar { + Q_OBJECT + Q_PROPERTY(ItemStatus sunnylinkStatus MEMBER sunnylink_status NOTIFY valueChanged); + Q_PROPERTY(QString sidebarTemp MEMBER sidebar_temp_str NOTIFY valueChanged); + +public: + explicit SidebarSP(QWidget* parent = 0); + +public slots: + void updateState(const UIStateSP &s); + +protected: + const QColor progress_color = QColor(3, 132, 252); + const QColor disabled_color = QColor(128, 128, 128); + + ItemStatus sunnylink_status; + +private: + Params params; + void DrawSidebar(QPainter &p) override; + QString sidebar_temp = "0"; + QString sidebar_temp_str = "0"; +}; diff --git a/selfdrive/ui/sunnypilot/qt/text.cc b/selfdrive/ui/sunnypilot/qt/text.cc new file mode 100644 index 0000000000..50e4174ece --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/text.cc @@ -0,0 +1,162 @@ +/** +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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 buffer{}; + std::ostringstream result; + std::unique_ptr 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); + QWidget window; + setMainWindow(&window); + + QGridLayout *main_layout = new QGridLayout(&window); + main_layout->setMargin(50); + + 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); + + // Scroll to the bottom + QObject::connect(scroll->verticalScrollBar(), &QAbstractSlider::rangeChanged, [=]() { + scroll->verticalScrollBar()->setValue(scroll->verticalScrollBar()->maximum()); + }); + + QPushButton *btn = new QPushButton(); + QPushButton *update_btn = new QPushButton(); + update_btn->setText(QObject::tr("Update")); +#ifdef __aarch64__ + btn->setText(QObject::tr("Reboot")); + QFutureWatcher 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 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::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"( + * { + outline: none; + color: white; + background-color: black; + font-size: 60px; + } + QPushButton { + padding: 50px; + padding-right: 100px; + padding-left: 100px; + border: 2px solid white; + border-radius: 20px; + margin-right: 40px; + } + QPushButton:disabled { + color: #33FFFFFF; + border: 2px solid #33FFFFFF; + } + )"); + + return a.exec(); +} diff --git a/selfdrive/ui/sunnypilot/qt/ui_scene.h b/selfdrive/ui/sunnypilot/qt/ui_scene.h new file mode 100644 index 0000000000..e590c94046 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/ui_scene.h @@ -0,0 +1,115 @@ +/** +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 + +const float DRIVING_PATH_WIDE = 0.9; +const float DRIVING_PATH_NARROW = 0.25; + +typedef struct UISceneSP : UIScene { + cereal::ControlsState::Reader controlsState; + + // Debug UI + bool show_debug_ui; + + // Speed limit control + bool speed_limit_control_enabled; + int speed_limit_control_policy; + double last_speed_limit_sign_tap; + + QPolygonF track_edge_vertices; + QPolygonF lane_barrier_vertices[2]; + + bool navigate_on_openpilot_deprecated = false; + cereal::AccelerationPersonality accel_personality; + + bool map_on_left; + + int dynamic_lane_profile; + bool dynamic_lane_profile_status = true; + + bool visual_brake_lights; + + int onroadScreenOff, osoTimer, brightness, onroadScreenOffBrightness, awake; + bool onroadScreenOffEvent; + int sleep_time = -1; + bool touched2 = false; + + bool stand_still_timer; + + bool hide_vego_ui, true_vego_ui; + + int chevron_data; + + bool gac; + int longitudinal_personality; + int longitudinal_accel_personality; + + bool map_visible; + int dev_ui_info; + bool live_torque_toggle; + + bool touch_to_wake = false; + int sleep_btn = -1; + bool sleep_btn_fading_in = false; + int sleep_btn_opacity = 20; + bool button_auto_hide; + + bool reverse_dm_cam; + + bool e2e_long_alert_light, e2e_long_alert_lead, e2e_long_alert_ui; + float e2eX[13] = {0}; + + int sidebar_temp_options; + + float mads_path_scale = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; + float mads_path_range = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; // 0.9 - 0.25 = 0.65 + + bool onroad_settings_visible; + + bool map_3d_buildings; + + bool torqued_override; + + bool dynamic_experimental_control; + + int speed_limit_control_engage_type; + + bool mapbox_fullscreen; + + bool speed_limit_warning_flash; + int speed_limit_warning_type; + int speed_limit_warning_value_offset; + + bool custom_driving_model_valid; + cereal::ModelGeneration driving_model_generation; + uint32_t driving_model_capabilities; + + bool feature_status_toggle; + bool onroad_settings_toggle; + + bool dynamic_personality; +} UISceneSP; diff --git a/selfdrive/ui/sunnypilot/qt/util.cc b/selfdrive/ui/sunnypilot/qt/util.cc new file mode 100644 index 0000000000..871a22f04e --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/util.cc @@ -0,0 +1,104 @@ +/** +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/util.h" +#include "selfdrive/ui/qt/util.h" + +#include +#include + +#include +#include +#include +#include + +#include "system/hardware/hw.h" + +QString getUserAgent(bool sunnylink) { + return (sunnylink ? "sunnypilot-" : "openpilot-") + getVersion(); +} + +std::optional getSunnylinkDongleId() { + return getParamIgnoringDefault("SunnylinkDongleId", "UnregisteredDevice"); +} + +QMap getCarNames() { + return getFromJsonFile("../car/sunnypilot_carname.json"); +} + +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 xp, std::vector 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()); + } +} \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/qt/util.h b/selfdrive/ui/sunnypilot/qt/util.h new file mode 100644 index 0000000000..cafcfc6201 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/util.h @@ -0,0 +1,39 @@ +/** +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 +#include + +#include +#include + +QString getUserAgent(bool sunnylink = false); +std::optional getSunnylinkDongleId(); +QMap getCarNames(); +void drawRoundedRect(QPainter &painter, const QRectF &rect, qreal xRadiusTop, qreal yRadiusTop, qreal xRadiusBottom, qreal yRadiusBottom); +QColor interpColor(float xv, std::vector xp, std::vector fp); diff --git a/selfdrive/ui/sunnypilot/qt/widgets/controls.cc b/selfdrive/ui/sunnypilot/qt/widgets/controls.cc new file mode 100644 index 0000000000..f1168e96e8 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/widgets/controls.cc @@ -0,0 +1,247 @@ +/** +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/widgets/controls.h" + +#include +#include +#include "selfdrive/ui/sunnypilot/sunnypilot_main.h" + +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; +} + +AbstractControlSP::AbstractControlSP(const QString &title, const QString &desc, const QString &icon, QWidget *parent) + : AbstractControl(title, desc, icon, parent) { + + main_layout = new QVBoxLayout(this); + main_layout->setMargin(0); + + hlayout = new QHBoxLayout; + hlayout->setMargin(0); + hlayout->setSpacing(20); + + // left icon + icon_label = new QLabel(this); + hlayout->addWidget(icon_label); + if (!icon.isEmpty()) { + icon_pixmap = QPixmap(icon).scaledToWidth(80, Qt::SmoothTransformation); + icon_label->setPixmap(icon_pixmap); + icon_label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + } + icon_label->setVisible(!icon.isEmpty()); + + // title + title_label = new QPushButton(title); + title_label->setFixedHeight(120); + title_label->setStyleSheet("font-size: 50px; font-weight: 450; text-align: left; border: none;"); + hlayout->addWidget(title_label, 1); + + // value next to control button + value = new ElidedLabelSP(); + value->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + value->setStyleSheet("color: #aaaaaa"); + hlayout->addWidget(value); + + main_layout->addLayout(hlayout); + + // description + description = new QLabel(desc); + description->setContentsMargins(40, 20, 40, 20); + description->setStyleSheet("font-size: 40px; color: grey"); + description->setWordWrap(true); + description->setVisible(false); + main_layout->addWidget(description); + + connect(title_label, &QPushButton::clicked, [=]() { + if (!description->isVisible()) { + emit showDescriptionEvent(); + } + + if (!description->text().isEmpty()) { + description->setVisible(!description->isVisible()); + } + }); + + main_layout->addStretch(); +} + +void AbstractControlSP::hideEvent(QHideEvent *e) { + if (description != nullptr) { + description->hide(); + } +} + +AbstractControlSP_SELECTOR::AbstractControlSP_SELECTOR(const QString &title, const QString &desc, const QString &icon, QWidget *parent) + : AbstractControlSP(title, desc, icon, parent) { + + if (value != nullptr) { + ReplaceWidget(value, new QWidget()); + value = nullptr; + } + + QLayoutItem* item; + while ((item = main_layout->takeAt(0)) != nullptr) { + if (item->widget()) { + delete item->widget(); + } + delete item; + } + + 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(); +} + +// controls + +ButtonControlSP::ButtonControlSP(const QString &title, const QString &text, const QString &desc, QWidget *parent) : AbstractControlSP(title, desc, "", parent) { + btn.setText(text); + btn.setStyleSheet(R"( + QPushButton { + padding: 0; + border-radius: 50px; + font-size: 35px; + font-weight: 500; + color: #E4E4E4; + background-color: #393939; + } + QPushButton:pressed { + background-color: #4a4a4a; + } + QPushButton:disabled { + color: #33E4E4E4; + } + )"); + btn.setFixedSize(250, 100); + QObject::connect(&btn, &QPushButton::clicked, this, &ButtonControlSP::clicked); + hlayout->addWidget(&btn); +} + +// ElidedLabelSP + +ElidedLabelSP::ElidedLabelSP(QWidget *parent) : ElidedLabelSP({}, parent) {} + +ElidedLabelSP::ElidedLabelSP(const QString &text, QWidget *parent) : QLabel(text.trimmed(), parent) { + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + setMinimumWidth(1); +} + +void ElidedLabelSP::resizeEvent(QResizeEvent* event) { + QLabel::resizeEvent(event); + lastText_ = elidedText_ = ""; +} + +void ElidedLabelSP::paintEvent(QPaintEvent *event) { + const QString curText = text(); + if (curText != lastText_) { + elidedText_ = fontMetrics().elidedText(curText, Qt::ElideRight, contentsRect().width()); + lastText_ = curText; + } + + QPainter painter(this); + drawFrame(&painter); + QStyleOption opt; + opt.initFrom(this); + style()->drawItemText(&painter, contentsRect(), alignment(), opt.palette, isEnabled(), elidedText_, foregroundRole()); +} + +// ParamControlSP + +ParamControlSP::ParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent) + : ToggleControlSP(title, desc, icon, false, parent) { + key = param.toStdString(); + QObject::connect(this, &ParamControlSP::toggleFlipped, this, &ParamControlSP::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 ParamControlSP::toggleClicked(bool state) { + auto do_confirm = [this]() { + QString content("

" + title_label->text() + "


" + "

" + getDescription() + "

"); + return ConfirmationDialog(content, tr("Enable"), tr("Cancel"), true, this).exec(); + }; + + bool confirmed = store_confirm && params.getBool(key + "Confirmed"); + if (!confirm || confirmed || !state || do_confirm()) { + if (store_confirm && state) params.putBool(key + "Confirmed", true); + params.putBool(key, state); + setIcon(state); + } else { + toggle.togglePosition(); + } +} diff --git a/selfdrive/ui/sunnypilot/qt/widgets/controls.h b/selfdrive/ui/sunnypilot/qt/widgets/controls.h new file mode 100644 index 0000000000..c7784a36c8 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/widgets/controls.h @@ -0,0 +1,582 @@ +/** +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 +#include +#include +#include + +#include "common/params.h" +#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/qt/widgets/input.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/toggle.h" + +QFrame *horizontal_line(QWidget *parent = nullptr); + +class ElidedLabelSP : public QLabel { + Q_OBJECT + +public: + explicit ElidedLabelSP(QWidget *parent = 0); + explicit ElidedLabelSP(const QString &text, QWidget *parent = 0); + + void setColor(const QString &color) { + setStyleSheet("QLabel { color : " + color + "; }"); + } + +signals: + void clicked(); + +protected: + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent* event) override; + void mouseReleaseEvent(QMouseEvent *event) override { + if (rect().contains(event->pos())) { + emit clicked(); + } + } + QString lastText_, elidedText_; +}; + +class AbstractControlSP : public AbstractControl { + Q_OBJECT + +public: + void setDescription(const QString &desc) { + if (description) description->setText(desc); + } + + void setValue(const QString &val, std::optional color = std::nullopt) { + value->setText(val); + if (color.has_value()) { + value->setColor(color.value()); + } + } + + const QString getDescription() { + return description->text(); + } + + void hideDescription() { + description->setVisible(false); + } + +public slots: + void showDescription() { + description->setVisible(true); + } + +protected: + AbstractControlSP(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr); + void hideEvent(QHideEvent *e) override; + + QVBoxLayout *main_layout; + ElidedLabelSP *value; + QLabel *description = nullptr; +}; + +class AbstractControlSP_SELECTOR : public AbstractControlSP { + Q_OBJECT + +protected: + AbstractControlSP_SELECTOR(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr); +}; + +// widget to display a value +class LabelControlSP : public AbstractControlSP { + Q_OBJECT + +public: + LabelControlSP(const QString &title, const QString &text = "", const QString &desc = "", QWidget *parent = nullptr) : AbstractControlSP(title, desc, "", parent) { + label.setText(text); + label.setAlignment(Qt::AlignRight | Qt::AlignVCenter); + hlayout->addWidget(&label); + } + void setText(const QString &text) { label.setText(text); } + +private: + ElidedLabelSP label; +}; + +// widget for a button with a label +class ButtonControlSP : public AbstractControlSP { + Q_OBJECT + +public: + ButtonControlSP(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(); + +public slots: + void setEnabled(bool enabled) { btn.setEnabled(enabled); } + +private: + QPushButton btn; +}; + +class ToggleControlSP : public AbstractControlSP { + Q_OBJECT + +public: + ToggleControlSP(const QString &title, const QString &desc = "", const QString &icon = "", const bool state = false, QWidget *parent = nullptr) : AbstractControlSP(title, desc, icon, parent) { + toggle.setFixedSize(150, 100); + if (state) { + toggle.togglePosition(); + } + hlayout->addWidget(&toggle); + QObject::connect(&toggle, &ToggleSP::stateChanged, this, &ToggleControlSP::toggleFlipped); + } + + void setEnabled(bool enabled) { + toggle.setEnabled(enabled); + toggle.update(); + } + +signals: + void toggleFlipped(bool state); + +protected: + ToggleSP toggle; +}; + +// widget to toggle params +class ParamControlSP : public ToggleControlSP { + Q_OBJECT + +public: + ParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr); + void setConfirmation(bool _confirm, bool _store_confirm) { + confirm = _confirm; + store_confirm = _store_confirm; + } + + void setActiveIcon(const QString &icon) { + active_icon_pixmap = QPixmap(icon).scaledToWidth(80, Qt::SmoothTransformation); + } + + void refresh() { + bool state = params.getBool(key); + if (state != toggle.on) { + toggle.togglePosition(); + setIcon(state); + } + } + + void showEvent(QShowEvent *event) override { + refresh(); + } + + bool isToggled() { return params.getBool(key); } + +private: + void toggleClicked(bool state); + void setIcon(bool state) { + if (state && !active_icon_pixmap.isNull()) { + icon_label->setPixmap(active_icon_pixmap); + } else if (!icon_pixmap.isNull()) { + icon_label->setPixmap(icon_pixmap); + } + } + + std::string key; + Params params; + QPixmap active_icon_pixmap; + bool confirm = false; + bool store_confirm = false; +}; + +class ButtonParamControlSP : public AbstractControlSP_SELECTOR { + Q_OBJECT +public: + ButtonParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, + const std::vector &button_texts, const int minimum_button_width = 300) : AbstractControlSP_SELECTOR(title, desc, icon), button_texts(button_texts) { + const QString style = R"( + QPushButton { + border-radius: 20px; + font-size: 50px; + font-weight: 450; + height:150px; + padding: 0 25 0 25; + color: #FFFFFF; + } + QPushButton:pressed { + background-color: #4a4a4a; + } + QPushButton:checked:enabled { + background-color: #696868; + } + QPushButton:disabled { + color: #33FFFFFF; + } + QPushButton:checked:disabled { + background-color: #121212; + color: #33FFFFFF; + } + )"; + key = param.toStdString(); + int 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->setCheckable(true); + 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::of(&QButtonGroup::buttonClicked), [=](int id) { + params.put(key, std::to_string(id)); + emit buttonToggled(id); + }); + } + + void setEnabled(bool enable) { + for (auto btn : button_group->buttons()) { + btn->setEnabled(enable); + } + button_group_enabled = enable; + + update(); + } + + void setCheckedButton(int id) { + button_group->button(id)->setChecked(true); + } + + 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); + } + + void showEvent(QShowEvent *event) override { + 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(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 button_texts; + + bool button_group_enabled = true; +}; + +class ListWidgetSP : public QWidget { + Q_OBJECT + public: + explicit ListWidgetSP(QWidget *parent = 0, const bool split_line = true) : QWidget(parent), _split_line(split_line), outer_layout(this) { + outer_layout.setMargin(0); + outer_layout.setSpacing(0); + outer_layout.addLayout(&inner_layout); + inner_layout.setMargin(0); + inner_layout.setSpacing(25); // default spacing is 25 + outer_layout.addStretch(); + } + inline void addItem(QWidget *w) { inner_layout.addWidget(w); } + 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) { + 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); + } + } + } + QVBoxLayout outer_layout; + QVBoxLayout inner_layout; + + bool _split_line; +}; + +// convenience class for wrapping layouts +class LayoutWidgetSP : public QWidget { + Q_OBJECT + +public: + LayoutWidgetSP(QLayout *l, QWidget *parent = nullptr) : QWidget(parent) { + setLayout(l); + } +}; + +class OptionControlSP : public AbstractControlSP_SELECTOR { + Q_OBJECT + +private: + struct MinMaxValue { + int min_value; + int max_value; + }; + +public: + OptionControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, + const MinMaxValue &range, const int per_value_change = 1) : _title(title), AbstractControlSP_SELECTOR(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 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(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 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); + } +}; diff --git a/selfdrive/ui/qt/widgets/sunnypilot/drive_stats.cc b/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc similarity index 73% rename from selfdrive/ui/qt/widgets/sunnypilot/drive_stats.cc rename to selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc index e4c127e6b5..8d54ec4f6c 100644 --- a/selfdrive/ui/qt/widgets/sunnypilot/drive_stats.cc +++ b/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc @@ -1,8 +1,33 @@ -#include "selfdrive/ui/qt/widgets/sunnypilot/drive_stats.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 +***/ + +#include "selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h" #include #include -#include #include #include "common/params.h" diff --git a/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h b/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h new file mode 100644 index 0000000000..7bd1d39cfe --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h @@ -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 +#include + +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); +}; diff --git a/selfdrive/ui/sunnypilot/qt/widgets/scrollview.cc b/selfdrive/ui/sunnypilot/qt/widgets/scrollview.cc new file mode 100644 index 0000000000..5b87a9f6dd --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/widgets/scrollview.cc @@ -0,0 +1,37 @@ +/** +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/widgets/scrollview.h" + +#include + +void ScrollViewSP::setLastScrollPosition() { + lastScrollPosition = verticalScrollBar()->value(); +} + +void ScrollViewSP::restoreScrollPosition() { + verticalScrollBar()->setValue(lastScrollPosition); +} diff --git a/selfdrive/ui/sunnypilot/qt/widgets/scrollview.h b/selfdrive/ui/sunnypilot/qt/widgets/scrollview.h new file mode 100644 index 0000000000..37d3ae4457 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/widgets/scrollview.h @@ -0,0 +1,43 @@ +/** +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/widgets/scrollview.h" + +class ScrollViewSP : public ScrollView { + Q_OBJECT + +public: + explicit ScrollViewSP(QWidget *w = nullptr, QWidget *parent = nullptr) : ScrollView(w, parent) {} + +public slots: + void setLastScrollPosition(); + void restoreScrollPosition(); + +private: + int lastScrollPosition = 0; +}; diff --git a/selfdrive/ui/sunnypilot/qt/widgets/toggle.cc b/selfdrive/ui/sunnypilot/qt/widgets/toggle.cc new file mode 100644 index 0000000000..b6737babd2 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/widgets/toggle.cc @@ -0,0 +1,49 @@ +/** +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/widgets/toggle.h" + +#include + +ToggleSP::ToggleSP(QWidget *parent) : Toggle(parent) { + _height_rect = 80; +} + +void ToggleSP::paintEvent(QPaintEvent *e) { + this->setFixedHeight(100); + QPainter p(this); + p.setPen(Qt::NoPen); + p.setRenderHint(QPainter::Antialiasing, true); + + // Draw toggle background + enabled ? green.setRgb(0x1e79e8) : green.setRgb(0x125db8); + p.setBrush(on ? green : QColor(0x292929)); + p.drawRoundedRect(QRect(0, 10, width(), _height_rect),_height_rect/2, _height_rect/2); + + // Draw toggle circle + p.setBrush(circleColor); + p.drawEllipse(QRectF(_x_circle - _radius + 6, 16, 68, 68)); +} diff --git a/selfdrive/ui/sunnypilot/qt/widgets/toggle.h b/selfdrive/ui/sunnypilot/qt/widgets/toggle.h new file mode 100644 index 0000000000..34f41a0c29 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/widgets/toggle.h @@ -0,0 +1,39 @@ +/** +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/widgets/toggle.h" + +class ToggleSP : public Toggle { + Q_OBJECT + +public: + explicit ToggleSP(QWidget* parent = nullptr); + +protected: + void paintEvent(QPaintEvent*) override; +}; diff --git a/selfdrive/ui/sunnypilot/qt/window.cc b/selfdrive/ui/sunnypilot/qt/window.cc new file mode 100644 index 0000000000..43fe9a9f25 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/window.cc @@ -0,0 +1,44 @@ +/** +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 "window.h" + +MainWindowSP::MainWindowSP(QWidget *parent) : MainWindow(parent, new HomeWindowSP(parent), + new SettingsWindowSP(parent), new OnboardingWindowSP(parent)) { + homeWindow = dynamic_cast(MainWindow::homeWindow); + settingsWindow = dynamic_cast(MainWindow::settingsWindow); + onboardingWindow = dynamic_cast(MainWindow::onboardingWindow); +} + +void MainWindowSP::closeSettings() { + MainWindow::closeSettings(); + if (uiState()->scene.started) { + homeWindow->showSidebar(false); + if (uiStateSP()->scene.navigate_on_openpilot_deprecated) { + homeWindow->showMapPanel(true); + } + } +} diff --git a/selfdrive/ui/sunnypilot/qt/window.h b/selfdrive/ui/sunnypilot/qt/window.h new file mode 100644 index 0000000000..1c0e6a81cb --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/window.h @@ -0,0 +1,45 @@ +/** +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/window.h" +#include "selfdrive/ui/sunnypilot/qt/home.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h" +#include "offroad/settings/onboarding.h" + +class MainWindowSP : public MainWindow { + Q_OBJECT + +public: + explicit MainWindowSP(QWidget *parent = 0); + +private: + HomeWindowSP *homeWindow; + SettingsWindowSP *settingsWindow; + OnboardingWindowSP *onboardingWindow; + void closeSettings() override; +}; diff --git a/selfdrive/ui/sunnypilot/sunnypilot_main.h b/selfdrive/ui/sunnypilot/sunnypilot_main.h new file mode 100644 index 0000000000..a1732da764 --- /dev/null +++ b/selfdrive/ui/sunnypilot/sunnypilot_main.h @@ -0,0 +1,45 @@ +/** +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/offroad/settings/display_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/trips_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/monitoring_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/software_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.h" + +inline void ReplaceWidget(QWidget* old_widget, QWidget* new_widget) { + if (old_widget && old_widget->parentWidget() && old_widget->parentWidget()->layout()) { + old_widget->parentWidget()->layout()->replaceWidget(old_widget, new_widget); + old_widget->hide(); + old_widget->deleteLater(); + } +} diff --git a/selfdrive/ui/sunnypilot/ui.cc b/selfdrive/ui/sunnypilot/ui.cc new file mode 100644 index 0000000000..ce490431d1 --- /dev/null +++ b/selfdrive/ui/sunnypilot/ui.cc @@ -0,0 +1,348 @@ +/** +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/ui.h" + +#include +#include +#include + +#include + +#include "common/transformations/orientation.hpp" +#include "common/params.h" +#include "common/util.h" +#include "common/watchdog.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h" +#include "system/hardware/hw.h" + +#define BACKLIGHT_DT 0.05 +#define BACKLIGHT_TS 10.00 + +// Projects a point in car to space to the corresponding point in full frame +// image space. +static bool sp_calib_frame_to_full_frame(const UIStateSP *s, float in_x, float in_y, float in_z, QPointF *out) { + return calib_frame_to_full_frame(s, in_x, in_y, in_z, out, 1000.0f); +} + +//todo: revisit, we could reuse from OG update_model. But this is an overload in this case. +void update_line_data(const UIStateSP *s, const cereal::XYZTData::Reader &line, + float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert=true) { + const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ(); + QPointF left, right; + pvd->clear(); + for (int i = 0; i <= max_idx; i++) { + // highly negative x positions are drawn above the frame and cause flickering, clip to zy plane of camera + if (line_x[i] < 0) continue; + + bool l = sp_calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off_left, &left); + bool r = sp_calib_frame_to_full_frame(s, line_x[i], line_y[i] + y_off, line_z[i] + z_off_right, &right); + if (l && r) { + // For wider lines the drawn polygon will "invert" when going over a hill and cause artifacts + if (!allow_invert && pvd->size() && left.y() > pvd->back().y()) { + continue; + } + pvd->push_back(left); + pvd->push_front(right); + } + } +} + +//todo: revisit, we could reuse from OG update_model +void sp_update_model(UIStateSP *s, const cereal::ModelDataV2::Reader &model) { + UISceneSP &scene = s->scene; + auto model_position = model.getPosition(); + float max_distance = std::clamp(*(model_position.getX().end() - 1), + MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE); + + // update lane lines + const auto lane_lines = model.getLaneLines(); + const auto lane_line_probs = model.getLaneLineProbs(); + int max_idx = get_path_length_idx(lane_lines[0], max_distance); + for (int i = 0; i < std::size(scene.lane_line_vertices); i++) { + scene.lane_line_probs[i] = lane_line_probs[i]; + update_line_data(s, lane_lines[i], 0.025 * scene.lane_line_probs[i], 0, 0, &scene.lane_line_vertices[i], max_idx); + } + + // lane barriers for blind spot + int max_distance_barrier = 40; + int max_idx_barrier = std::min(max_idx, get_path_length_idx(lane_lines[0], max_distance_barrier)); + update_line_data(s, lane_lines[1], 0, -0.05, -0.6, &scene.lane_barrier_vertices[0], max_idx_barrier, false); + update_line_data(s, lane_lines[2], 0, -0.05, -0.6, &scene.lane_barrier_vertices[1], max_idx_barrier, false); + + // update road edges + const auto road_edges = model.getRoadEdges(); + const auto road_edge_stds = model.getRoadEdgeStds(); + for (int i = 0; i < std::size(scene.road_edge_vertices); i++) { + scene.road_edge_stds[i] = road_edge_stds[i]; + update_line_data(s, road_edges[i], 0.025, 0, 0, &scene.road_edge_vertices[i], max_idx); + } + + // update path + auto lead_one = (*s->sm)["radarState"].getRadarState().getLeadOne(); + if (lead_one.getStatus()) { + const float lead_d = lead_one.getDRel() * 2.; + max_distance = std::clamp((float)(lead_d - fmin(lead_d * 0.35, 10.)), 0.0f, max_distance); + } + max_idx = get_path_length_idx(model_position, max_distance); + update_line_data(s, model_position, 0.9 - scene.mads_path_range * scene.mads_path_scale, 1.22, 1.22, &scene.track_vertices, max_idx, false); + update_line_data(s, model_position, 1.0 - scene.mads_path_range * scene.mads_path_scale - 0.1 * scene.mads_path_scale, 1.22, 1.22, &scene.track_edge_vertices, max_idx, false); +} + +static void sp_update_state(UIStateSP *s) { + update_state(s); + SubMaster &sm = *(s->sm); + UISceneSP &scene = s->scene; + auto params = Params(); + scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition && !params.getBool("ForceOffroad"); + // TODO: SP - Set this dynamically on init with manual toggle or driving model selection + if (sm.updated("lateralPlanSPDEPRECATED")) { + scene.dynamic_lane_profile_status = sm["lateralPlanSPDEPRECATED"].getLateralPlanSPDEPRECATED().getDynamicLaneProfileStatus(); + } + if (sm.updated("controlsState")) { + scene.controlsState = sm["controlsState"].getControlsState(); + } + if (sm.updated("longitudinalPlanSP")) { + for (int i = 0; i < std::size(scene.e2eX); i++) { + scene.e2eX[i] = sm["longitudinalPlanSP"].getLongitudinalPlanSP().getE2eX()[i]; + } + } + if (sm.updated("modelV2SP")) { + auto model_v2_sp = sm["modelV2SP"].getModelV2SP(); + scene.custom_driving_model_valid = model_v2_sp.getCustomModel(); + scene.driving_model_generation = model_v2_sp.getModelGeneration(); + scene.driving_model_capabilities = model_v2_sp.getModelCapabilities(); + } +} + +void sp_ui_update_params(UIStateSP *s) { + ui_update_params(s); + auto params = Params(); + s->scene.map_on_left = params.getBool("NavSettingLeftSide"); + + s->scene.visual_brake_lights = params.getBool("BrakeLights"); + s->scene.onroadScreenOff = std::atoi(params.get("OnroadScreenOff").c_str()); + s->scene.onroadScreenOffBrightness = std::atoi(params.get("OnroadScreenOffBrightness").c_str()); + s->scene.onroadScreenOffEvent = params.getBool("OnroadScreenOffEvent"); + s->scene.stand_still_timer = params.getBool("StandStillTimer"); + s->scene.show_debug_ui = params.getBool("ShowDebugUI"); + s->scene.hide_vego_ui = params.getBool("HideVEgoUi"); + s->scene.true_vego_ui = params.getBool("TrueVEgoUi"); + s->scene.chevron_data = std::atoi(params.get("ChevronInfo").c_str()); + s->scene.dev_ui_info = std::atoi(params.get("DevUIInfo").c_str()); + s->scene.button_auto_hide = params.getBool("ButtonAutoHide"); + s->scene.reverse_dm_cam = params.getBool("ReverseDmCam"); + s->scene.e2e_long_alert_light = params.getBool("EndToEndLongAlertLight"); + s->scene.e2e_long_alert_lead = params.getBool("EndToEndLongAlertLead"); + s->scene.e2e_long_alert_ui = params.getBool("EndToEndLongAlertUI"); + s->scene.map_3d_buildings = params.getBool("Map3DBuildings"); + s->scene.live_torque_toggle = params.getBool("LiveTorque"); + s->scene.torqued_override = params.getBool("TorquedOverride"); + s->scene.speed_limit_control_engage_type = std::atoi(params.get("SpeedLimitEngageType").c_str()); + s->scene.mapbox_fullscreen = params.getBool("MapboxFullScreen"); + s->scene.speed_limit_warning_flash = params.getBool("SpeedLimitWarningFlash"); + s->scene.speed_limit_warning_type = std::atoi(params.get("SpeedLimitWarningType").c_str()); + s->scene.speed_limit_warning_value_offset = std::atoi(params.get("SpeedLimitWarningValueOffset").c_str()); + s->scene.speed_limit_control_enabled = params.getBool("EnableSlc"); + s->scene.feature_status_toggle = params.getBool("FeatureStatus"); + s->scene.onroad_settings_toggle = params.getBool("OnroadSettings"); + + // Handle Onroad Screen Off params + if (s->scene.onroadScreenOff > 0) { + s->scene.osoTimer = s->scene.onroadScreenOff * 60 * UI_FREQ; + } else if (s->scene.onroadScreenOff == 0) { + s->scene.osoTimer = 30 * UI_FREQ; + } else if (s->scene.onroadScreenOff == -1) { + s->scene.osoTimer = 15 * UI_FREQ; + } else { + s->scene.osoTimer = -1; + } +} + +void UIStateSP::updateStatus() { + UIState::updateStatus(); + auto params = Params(); + if (scene.started && sm->updated("controlsState")) { + auto car_control = (*sm)["carControl"].getCarControl(); + auto car_state = (*sm)["carState"].getCarState(); + auto mads_enabled = car_state.getMadsEnabled(); + if (status != STATUS_OVERRIDE) { + status = mads_enabled && car_control.getLongActive() ? STATUS_ENGAGED : mads_enabled ? STATUS_MADS : STATUS_DISENGAGED; + } + + if (mads_enabled != last_mads_enabled) { + mads_path_state = true; + } + last_mads_enabled = mads_enabled; + if (mads_path_state) { + if (mads_enabled) { + mads_path_count = fmax(mads_path_count - 1, 0); + if (mads_path_count == 0) { + mads_path_state = false; + } + } else { + mads_path_count = fmin(mads_path_count + 1, mads_path_timestep); + if (mads_path_count == mads_path_timestep) { + mads_path_state = false; + } + } + } + scene.mads_path_scale = mads_path_count * (1 / mads_path_timestep); + } + + if (scene.started) { + // Auto hide UI button state machine + { + if (scene.button_auto_hide) { + if (scene.touch_to_wake) { + scene.sleep_btn = 30 * UI_FREQ; + } else if (scene.sleep_btn > 0) { + scene.sleep_btn--; + } else if (scene.sleep_btn == -1) { + scene.sleep_btn = 30 * UI_FREQ; + } + // Check if the sleep button should be fading in + if (scene.sleep_btn_fading_in) { + // Increase the opacity of the sleep button by a small amount + if (scene.sleep_btn_opacity < 20) { + scene.sleep_btn_opacity+= 10; + } + if (scene.sleep_btn_opacity >= 20) { + // If the opacity has reached its maximum value, stop fading in + scene.sleep_btn_fading_in = false; + scene.sleep_btn_opacity = 20; + } + } else if (scene.sleep_btn == 0) { + // Fade out the sleep button as before + if (scene.sleep_btn_opacity > 0) { + scene.sleep_btn_opacity-= 2; + } + } else { + // Set the opacity of the sleep button to its maximum value + scene.sleep_btn_opacity = 20; + } + } else { + scene.sleep_btn_opacity = 20; + } + } + + // Onroad Screen Off Brightness + Timer + Global Brightness + { + if (scene.onroadScreenOff != -2 && scene.touched2) { + scene.sleep_time = scene.osoTimer; + } else if (scene.onroadScreenOff != -2 && + ((scene.controlsState.getAlertSize() != cereal::ControlsState::AlertSize::NONE) && + ((scene.controlsState.getAlertStatus() == cereal::ControlsState::AlertStatus::NORMAL && scene.onroadScreenOffEvent) || + (scene.controlsState.getAlertStatus() != cereal::ControlsState::AlertStatus::NORMAL)))) { + scene.sleep_time = scene.osoTimer; + } else if (scene.sleep_time > 0 && scene.onroadScreenOff != -2) { + scene.sleep_time--; + } else if (scene.sleep_time == -1 && scene.onroadScreenOff != -2) { + scene.sleep_time = scene.osoTimer; + } + } + } + + if (sm->frame % UI_FREQ == 0) { // Update every 1 Hz + scene.sidebar_temp_options = std::atoi(params.get("SidebarTemperatureOptions").c_str()); + } + + scene.brightness = std::atoi(params.get("BrightnessControl").c_str()); +} + +UIStateSP::UIStateSP(QObject *parent) : UIState(parent) { +// todo: Can we simply append to SM? + sm = std::make_unique>({ + "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", + "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", + "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "clocks", "longitudinalPlanSP", "liveMapDataSP", + "carControl", "lateralPlanSPDEPRECATED", "gpsLocation", "gpsLocationExternal", "liveParameters", "liveTorqueParameters", + "controlsStateSP", "modelV2SP" + }); + + // update timer + timer = new QTimer(this); + QObject::connect(timer, &QTimer::timeout, this, &UIStateSP::update); + timer->start(1000 / UI_FREQ); +} + +// Note: This method overrides completely the update method from the parent class intentionally. +void UIStateSP::update() { + update_sockets(this); + sp_update_state(this); + updateStatus(); + + if (sm->frame % UI_FREQ == 0) { + watchdog_kick(nanos_since_boot()); + } + emit uiUpdate(*this); +} + + +void UIStateSP::setSunnylinkRoles(const std::vector& roles) { + sunnylinkRoles = roles; + emit sunnylinkRolesChanged(roles); +} + +void UIStateSP::setSunnylinkDeviceUsers(const std::vector& users) { + sunnylinkUsers = users; + emit sunnylinkDeviceUsersChanged(users); +} + +DeviceSP::DeviceSP(QObject *parent) : Device(parent){ + QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &DeviceSP::update); +} + +//todo: revisit this +void DeviceSP::updateBrightness(const UIStateSP &s) { + Device::updateBrightness(s); + int brightness = brightness_filter.update(clipped_brightness); + if (!awake) { + brightness = 0; + } else if (s.scene.started && s.scene.sleep_time == 0 && s.scene.onroadScreenOff != -2) { + brightness = s.scene.onroadScreenOffBrightness * 0.01 * brightness; + } else if (s.scene.brightness) { + brightness = s.scene.brightness * 0.99; + } + + if (brightness != last_brightness) { + if (!brightness_future.isRunning()) { + brightness_future = QtConcurrent::run(Hardware::set_brightness, brightness); + last_brightness = brightness; + } + } +} + +UIStateSP *uiStateSP() { + static UIStateSP ui_state; + return &ui_state; +} +UIStateSP *uiState() { return uiStateSP(); } + +DeviceSP *deviceSP() { + static DeviceSP _device; + return &_device; +} diff --git a/selfdrive/ui/sunnypilot/ui.h b/selfdrive/ui/sunnypilot/ui.h new file mode 100644 index 0000000000..434b334f75 --- /dev/null +++ b/selfdrive/ui/sunnypilot/ui.h @@ -0,0 +1,154 @@ +/** +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 +#include +#include + +#include "selfdrive/ui/ui.h" + +#include "cereal/messaging/messaging.h" +#include "qt/network/sunnylink/models/role_model.h" +#include "qt/network/sunnylink/models/sponsor_role_model.h" +#include "qt/network/sunnylink/models/user_model.h" +#include "system/hardware/hw.h" + +const int UI_ROAD_NAME_MARGIN_X = 14; + +struct FeatureStatusText { + const QStringList dlp_list_text = { "Laneful", "Laneless", "Auto"}; + const QStringList gac_list_text = { "Aggressive", "Moderate", "Standard", "Relaxed"}; + const QStringList slc_list_text = { "Inactive", "Temp Off", "Adapting", "Active", "Pre Active"}; +}; + +struct FeatureStatusColor { + const QStringList dlp_list_color = { "#2020f8", "#0df87a", "#0df8f8" }; + const QStringList gac_list_color = { "#ff4b4b", "#fcff4b", "#4bff66", "#6a0ac9" }; + const QStringList slc_list_color = { "#ffffff", "#ffffff", "#fcff4b", "#4bff66", "#fcff4b" }; +}; + + +const QColor sp_bg_colors [] = { + [STATUS_DISENGAGED] = bg_colors[STATUS_DISENGAGED], + [STATUS_OVERRIDE] = bg_colors[STATUS_OVERRIDE], + [STATUS_ENGAGED] = QColor(0x00, 0xc8, 0x00, 0xf1), + [STATUS_MADS] = QColor(0x00, 0xc8, 0xc8, 0xf1), +}; +#define bg_colors sp_bg_colors // Override the bg_colors array with the sp_bg_colors array + +const QColor tcs_colors [] = { + [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::DISABLED)] = QColor(0x0, 0x0, 0x0, 0xff), + [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::ENTERING)] = QColor(0xC9, 0x22, 0x31, 0xf1), + [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::TURNING)] = QColor(0xDA, 0x6F, 0x25, 0xf1), + [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::LEAVING)] = QColor(0x17, 0x86, 0x44, 0xf1), +}; + +class UIStateSP : public UIState { + Q_OBJECT + +public: + UIStateSP(QObject* parent = 0); + void updateStatus() override; + + void setSunnylinkRoles(const std::vector &roles); + void setSunnylinkDeviceUsers(const std::vector &users); + + inline std::vector sunnylinkDeviceRoles() const { return sunnylinkRoles; } + inline bool isSunnylinkAdmin() const { + return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) { + return role.roleType == RoleType::Admin; + }); + } + inline bool isSunnylinkSponsor() const { + return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) { + return role.roleType == RoleType::Sponsor && role.as().roleTier != SponsorTier::Free; + }); + } + inline SponsorRoleModel sunnylinkSponsorRole() const { + std::optional sponsorRoleWithHighestTier = std::nullopt; + for (const auto &role : sunnylinkRoles) { + if(role.roleType != RoleType::Sponsor) + continue; + + if (auto sponsorRole = role.as(); !sponsorRoleWithHighestTier.has_value() || sponsorRoleWithHighestTier->roleTier < sponsorRole.roleTier) { + sponsorRoleWithHighestTier = sponsorRole; + } + } + return sponsorRoleWithHighestTier.value_or(SponsorRoleModel(RoleType::Sponsor, SponsorTier::Free)); + } + inline SponsorTier sunnylinkSponsorTier() const { + return sunnylinkSponsorRole().roleTier; + } + inline std::vector sunnylinkDeviceUsers() const { return sunnylinkUsers; } + inline bool isSunnylinkPaired() const { + return std::any_of(sunnylinkUsers.begin(), sunnylinkUsers.end(), [](const UserModel &user) { + return user.user_id.toLower() != "unregisteredsponsor" && user.user_id.toLower() != "temporarysponsor"; + }); + } + +signals: + void sunnylinkRoleChanged(bool subscriber); + void sunnylinkRolesChanged(std::vector roles); + void sunnylinkDeviceUsersChanged(std::vector users); + void uiUpdate(const UIStateSP &s); + +private slots: + void update() override; + + +private: + std::vector sunnylinkRoles = {}; + std::vector sunnylinkUsers = {}; + + bool last_mads_enabled = false; + bool mads_path_state = false; + float mads_path_timestep = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] + float mads_path_count = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] +}; + +UIStateSP *uiStateSP(); +UIStateSP *uiState(); + +// device management class +class DeviceSP : public Device { + Q_OBJECT + +public: + DeviceSP(QObject *parent = 0); +protected: + void updateBrightness(const UIStateSP &s); + void updateBrightness(const UIState &s) override { updateBrightness(dynamic_cast(s)); } +}; + +DeviceSP *deviceSP(); +inline DeviceSP *device() { return deviceSP(); } + +void sp_update_model(UIStateSP *s, const cereal::ModelDataV2::Reader &model); +void sp_ui_update_params(UIStateSP *s); +void update_line_data(const UIStateSP *s, const cereal::XYZTData::Reader &line, + float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert); diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts index 7dae31a53e..28610a671e 100644 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -1517,6 +1517,22 @@ This may take up to a minute. Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + Enable Enhanced Blind Spot Monitor diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index 6b8fd1efc1..ab80c261fd 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -1499,6 +1499,22 @@ This may take up to a minute. Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + Enable Enhanced Blind Spot Monitor diff --git a/selfdrive/ui/translations/main_es.ts b/selfdrive/ui/translations/main_es.ts index 6527ddfc12..90fc8381c9 100644 --- a/selfdrive/ui/translations/main_es.ts +++ b/selfdrive/ui/translations/main_es.ts @@ -1505,6 +1505,22 @@ Esto puede tardar hasta un minuto. Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + Enable Enhanced Blind Spot Monitor diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index 0071a33e47..22b5808a62 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -1501,6 +1501,22 @@ Cela peut prendre jusqu'à une minute. Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + Enable Enhanced Blind Spot Monitor diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 5db645c9f4..0ffade1416 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -1495,6 +1495,22 @@ This may take up to a minute. Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + Enable Enhanced Blind Spot Monitor diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 1a55d59a3b..337f338633 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -1497,6 +1497,22 @@ This may take up to a minute. Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + Enable Enhanced Blind Spot Monitor diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 856ef26ca8..63d83d7448 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -1501,6 +1501,22 @@ Isso pode levar até um minuto. Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + Enable Enhanced Blind Spot Monitor diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index 220b6575c4..a0ed3caf37 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -1497,6 +1497,22 @@ This may take up to a minute. Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + Enable Enhanced Blind Spot Monitor diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts index a8c3992fd2..07205a2b12 100644 --- a/selfdrive/ui/translations/main_tr.ts +++ b/selfdrive/ui/translations/main_tr.ts @@ -1495,6 +1495,22 @@ This may take up to a minute. Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + Enable Enhanced Blind Spot Monitor diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index fddf413a50..da8e79536c 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -1497,6 +1497,22 @@ This may take up to a minute. Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + Enable Enhanced Blind Spot Monitor diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 684447b113..6c93b6239a 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -1497,6 +1497,22 @@ This may take up to a minute. Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + Enable Enhanced Blind Spot Monitor diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 126be0d6b1..5d6b6c5722 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -11,7 +11,7 @@ #include "common/swaglog.h" #include "common/util.h" #include "common/watchdog.h" -#include "qt/network/sunnylink/models/role_model.h" +#include "qt/util.h" #include "system/hardware/hw.h" #define BACKLIGHT_DT 0.05 @@ -19,8 +19,7 @@ // Projects a point in car to space to the corresponding point in full frame // image space. -static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, float in_z, QPointF *out) { - const float margin = 1000.0f; +bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, float in_z, QPointF *out, const float margin) { const QRectF clip_region{-margin, -margin, s->fb_w + 2 * margin, s->fb_h + 2 * margin}; const vec3 pt = (vec3){{in_x, in_y, in_z}}; @@ -56,7 +55,7 @@ void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, con } void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, - float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert=true) { + float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert=true) { const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ(); QPointF left, right; pvd->clear(); @@ -64,8 +63,8 @@ void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, // highly negative x positions are drawn above the frame and cause flickering, clip to zy plane of camera if (line_x[i] < 0) continue; - bool l = calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off_left, &left); - bool r = calib_frame_to_full_frame(s, line_x[i], line_y[i] + y_off, line_z[i] + z_off_right, &right); + bool l = calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off, &left); + bool r = calib_frame_to_full_frame(s, line_x[i], line_y[i] + y_off, line_z[i] + z_off, &right); if (l && r) { // For wider lines the drawn polygon will "invert" when going over a hill and cause artifacts if (!allow_invert && pvd->size() && left.y() > pvd->back().y()) { @@ -90,21 +89,15 @@ void update_model(UIState *s, int max_idx = get_path_length_idx(lane_lines[0], max_distance); for (int i = 0; i < std::size(scene.lane_line_vertices); i++) { scene.lane_line_probs[i] = lane_line_probs[i]; - update_line_data(s, lane_lines[i], 0.025 * scene.lane_line_probs[i], 0, 0, &scene.lane_line_vertices[i], max_idx); + update_line_data(s, lane_lines[i], 0.025 * scene.lane_line_probs[i], 0, &scene.lane_line_vertices[i], max_idx); } - // lane barriers for blind spot - int max_distance_barrier = 40; - int max_idx_barrier = std::min(max_idx, get_path_length_idx(lane_lines[0], max_distance_barrier)); - update_line_data(s, lane_lines[1], 0, -0.05, -0.6, &scene.lane_barrier_vertices[0], max_idx_barrier, false); - update_line_data(s, lane_lines[2], 0, -0.05, -0.6, &scene.lane_barrier_vertices[1], max_idx_barrier, false); - // update road edges const auto road_edges = model.getRoadEdges(); const auto road_edge_stds = model.getRoadEdgeStds(); for (int i = 0; i < std::size(scene.road_edge_vertices); i++) { scene.road_edge_stds[i] = road_edge_stds[i]; - update_line_data(s, road_edges[i], 0.025, 0, 0, &scene.road_edge_vertices[i], max_idx); + update_line_data(s, road_edges[i], 0.025, 0, &scene.road_edge_vertices[i], max_idx); } // update path @@ -114,8 +107,7 @@ void update_model(UIState *s, max_distance = std::clamp((float)(lead_d - fmin(lead_d * 0.35, 10.)), 0.0f, max_distance); } max_idx = get_path_length_idx(model_position, max_distance); - update_line_data(s, model_position, 0.9 - scene.mads_path_range * scene.mads_path_scale, 1.22, 1.22, &scene.track_vertices, max_idx, false); - update_line_data(s, model_position, 1.0 - scene.mads_path_range * scene.mads_path_scale - 0.1 * scene.mads_path_scale, 1.22, 1.22, &scene.track_edge_vertices, max_idx, false); + update_line_data(s, model_position, 0.9, 1.22, &scene.track_vertices, max_idx, false); } void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd) { @@ -153,14 +145,13 @@ void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &drivers } } -static void update_sockets(UIState *s) { +void update_sockets(UIState *s) { s->sm->update(0); } -static void update_state(UIState *s) { +void update_state(UIState *s) { SubMaster &sm = *(s->sm); UIScene &scene = s->scene; - auto params = Params(); if (sm.updated("liveCalibration")) { auto live_calib = sm["liveCalibration"].getLiveCalibration(); @@ -212,108 +203,28 @@ static void update_state(UIState *s) { } else if (!sm.allAliveAndValid({"wideRoadCameraState"})) { scene.light_sensor = -1; } - scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition && !params.getBool("ForceOffroad"); + scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition; scene.world_objects_visible = scene.world_objects_visible || (scene.started && sm.rcv_frame("liveCalibration") > scene.started_frame && sm.rcv_frame("modelV2") > scene.started_frame); - // TODO: SP - Set this dynamically on init with manual toggle or driving model selection - if (sm.updated("lateralPlanSPDEPRECATED")) { - scene.dynamic_lane_profile_status = sm["lateralPlanSPDEPRECATED"].getLateralPlanSPDEPRECATED().getDynamicLaneProfileStatus(); - } - if (sm.updated("controlsState")) { - scene.controlsState = sm["controlsState"].getControlsState(); - } - if (sm.updated("longitudinalPlanSP")) { - for (int i = 0; i < std::size(scene.e2eX); i++) { - scene.e2eX[i] = sm["longitudinalPlanSP"].getLongitudinalPlanSP().getE2eX()[i]; - } - } - if (sm.updated("modelV2SP")) { - auto model_v2_sp = sm["modelV2SP"].getModelV2SP(); - scene.custom_driving_model_valid = model_v2_sp.getCustomModel(); - scene.driving_model_generation = model_v2_sp.getModelGeneration(); - scene.driving_model_capabilities = model_v2_sp.getModelCapabilities(); - } } void ui_update_params(UIState *s) { auto params = Params(); s->scene.is_metric = params.getBool("IsMetric"); - s->scene.map_on_left = params.getBool("NavSettingLeftSide"); - - s->scene.visual_brake_lights = params.getBool("BrakeLights"); - s->scene.onroadScreenOff = std::atoi(params.get("OnroadScreenOff").c_str()); - s->scene.onroadScreenOffBrightness = std::atoi(params.get("OnroadScreenOffBrightness").c_str()); - s->scene.onroadScreenOffEvent = params.getBool("OnroadScreenOffEvent"); - s->scene.stand_still_timer = params.getBool("StandStillTimer"); - s->scene.show_debug_ui = params.getBool("ShowDebugUI"); - s->scene.hide_vego_ui = params.getBool("HideVEgoUi"); - s->scene.true_vego_ui = params.getBool("TrueVEgoUi"); - s->scene.chevron_data = std::atoi(params.get("ChevronInfo").c_str()); - s->scene.dev_ui_info = std::atoi(params.get("DevUIInfo").c_str()); - s->scene.button_auto_hide = params.getBool("ButtonAutoHide"); - s->scene.reverse_dm_cam = params.getBool("ReverseDmCam"); - s->scene.e2e_long_alert_light = params.getBool("EndToEndLongAlertLight"); - s->scene.e2e_long_alert_lead = params.getBool("EndToEndLongAlertLead"); - s->scene.e2e_long_alert_ui = params.getBool("EndToEndLongAlertUI"); - s->scene.map_3d_buildings = params.getBool("Map3DBuildings"); - s->scene.live_torque_toggle = params.getBool("LiveTorque"); - s->scene.torqued_override = params.getBool("TorquedOverride"); - s->scene.speed_limit_control_engage_type = std::atoi(params.get("SpeedLimitEngageType").c_str()); - s->scene.mapbox_fullscreen = params.getBool("MapboxFullScreen"); - s->scene.speed_limit_warning_flash = params.getBool("SpeedLimitWarningFlash"); - s->scene.speed_limit_warning_type = std::atoi(params.get("SpeedLimitWarningType").c_str()); - s->scene.speed_limit_warning_value_offset = std::atoi(params.get("SpeedLimitWarningValueOffset").c_str()); - s->scene.speed_limit_control_enabled = params.getBool("EnableSlc"); - s->scene.feature_status_toggle = params.getBool("FeatureStatus"); - s->scene.onroad_settings_toggle = params.getBool("OnroadSettings"); - - // Handle Onroad Screen Off params - if (s->scene.onroadScreenOff > 0) { - s->scene.osoTimer = s->scene.onroadScreenOff * 60 * UI_FREQ; - } else if (s->scene.onroadScreenOff == 0) { - s->scene.osoTimer = 30 * UI_FREQ; - } else if (s->scene.onroadScreenOff == -1) { - s->scene.osoTimer = 15 * UI_FREQ; - } else { - s->scene.osoTimer = -1; - } } void UIState::updateStatus() { - auto params = Params(); if (scene.started && sm->updated("controlsState")) { auto controls_state = (*sm)["controlsState"].getControlsState(); - auto car_control = (*sm)["carControl"].getCarControl(); - auto car_state = (*sm)["carState"].getCarState(); - auto mads_enabled = car_state.getMadsEnabled(); auto state = controls_state.getState(); if (state == cereal::ControlsState::OpenpilotState::PRE_ENABLED || state == cereal::ControlsState::OpenpilotState::OVERRIDING) { status = STATUS_OVERRIDE; } else { - status = car_state.getMadsEnabled() ? car_control.getLongActive() ? STATUS_ENGAGED : STATUS_MADS : STATUS_DISENGAGED; + status = controls_state.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED; } - - if (mads_enabled != last_mads_enabled) { - mads_path_state = true; - } - last_mads_enabled = mads_enabled; - if (mads_path_state) { - if (mads_enabled) { - mads_path_count = fmax(mads_path_count - 1, 0); - if (mads_path_count == 0) { - mads_path_state = false; - } - } else { - mads_path_count = fmin(mads_path_count + 1, mads_path_timestep); - if (mads_path_count == mads_path_timestep) { - mads_path_state = false; - } - } - } - scene.mads_path_scale = mads_path_count * (1 / mads_path_timestep); } // Handle onroad/offroad transition @@ -326,74 +237,17 @@ void UIState::updateStatus() { scene.world_objects_visible = false; emit offroadTransition(!scene.started); } - - if (scene.started) { - // Auto hide UI button state machine - { - if (scene.button_auto_hide) { - if (scene.touch_to_wake) { - scene.sleep_btn = 30 * UI_FREQ; - } else if (scene.sleep_btn > 0) { - scene.sleep_btn--; - } else if (scene.sleep_btn == -1) { - scene.sleep_btn = 30 * UI_FREQ; - } - // Check if the sleep button should be fading in - if (scene.sleep_btn_fading_in) { - // Increase the opacity of the sleep button by a small amount - if (scene.sleep_btn_opacity < 20) { - scene.sleep_btn_opacity+= 10; - } - if (scene.sleep_btn_opacity >= 20) { - // If the opacity has reached its maximum value, stop fading in - scene.sleep_btn_fading_in = false; - scene.sleep_btn_opacity = 20; - } - } else if (scene.sleep_btn == 0) { - // Fade out the sleep button as before - if (scene.sleep_btn_opacity > 0) { - scene.sleep_btn_opacity-= 2; - } - } else { - // Set the opacity of the sleep button to its maximum value - scene.sleep_btn_opacity = 20; - } - } else { - scene.sleep_btn_opacity = 20; - } - } - - // Onroad Screen Off Brightness + Timer + Global Brightness - { - if (scene.onroadScreenOff != -2 && scene.touched2) { - scene.sleep_time = scene.osoTimer; - } else if (scene.onroadScreenOff != -2 && - ((scene.controlsState.getAlertSize() != cereal::ControlsState::AlertSize::NONE) && - ((scene.controlsState.getAlertStatus() == cereal::ControlsState::AlertStatus::NORMAL && scene.onroadScreenOffEvent) || - (scene.controlsState.getAlertStatus() != cereal::ControlsState::AlertStatus::NORMAL)))) { - scene.sleep_time = scene.osoTimer; - } else if (scene.sleep_time > 0 && scene.onroadScreenOff != -2) { - scene.sleep_time--; - } else if (scene.sleep_time == -1 && scene.onroadScreenOff != -2) { - scene.sleep_time = scene.osoTimer; - } - } - } - - if (sm->frame % UI_FREQ == 0) { // Update every 1 Hz - scene.sidebar_temp_options = std::atoi(params.get("SidebarTemperatureOptions").c_str()); - } - - scene.brightness = std::atoi(params.get("BrightnessControl").c_str()); } +// #ifdef SUNNYPILOT +// #include "selfdrive/ui/sunnypilot/qt/ui_scene.h" +// #define UIScene UISceneSP +// #endif UIState::UIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", - "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "clocks", "longitudinalPlanSP", "liveMapDataSP", - "carControl", "lateralPlanSPDEPRECATED", "gpsLocation", "gpsLocationExternal", "liveParameters", "liveTorqueParameters", - "controlsStateSP", "modelV2SP" + "wideRoadCameraState", "managerState", "clocks", }); Params params; @@ -403,6 +257,7 @@ UIState::UIState(QObject *parent) : QObject(parent) { prime_type = static_cast(std::atoi(prime_value.c_str())); } + RETURN_IF_SUNNYPILOT // update timer timer = new QTimer(this); QObject::connect(timer, &QTimer::timeout, this, &UIState::update); @@ -410,10 +265,15 @@ UIState::UIState(QObject *parent) : QObject(parent) { } void UIState::update() { + RETURN_IF_SUNNYPILOT update_sockets(this); update_state(this); updateStatus(); + if (std::getenv("PRIME_TYPE")) { + setPrimeType((PrimeType)atoi(std::getenv("PRIME_TYPE"))); + } + if (sm->frame % UI_FREQ == 0) { watchdog_kick(nanos_since_boot()); } @@ -435,21 +295,12 @@ void UIState::setPrimeType(PrimeType type) { } } -void UIState::setSunnylinkRoles(const std::vector& roles) { - sunnylinkRoles = roles; - emit sunnylinkRolesChanged(roles); -} - -void UIState::setSunnylinkDeviceUsers(const std::vector& users) { - sunnylinkUsers = users; - emit sunnylinkDeviceUsersChanged(users); -} - Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT_TS, BACKLIGHT_DT), QObject(parent) { setAwake(true); resetInteractiveTimeout(); - +#ifndef SUNNYPILOT QObject::connect(uiState(), &UIState::uiUpdate, this, &Device::update); +#endif } void Device::update(const UIState &s) { @@ -474,7 +325,7 @@ void Device::resetInteractiveTimeout(int timeout) { } void Device::updateBrightness(const UIState &s) { - float clipped_brightness = offroad_brightness; + clipped_brightness = offroad_brightness; if (s.scene.started && s.scene.light_sensor > 0) { clipped_brightness = s.scene.light_sensor; @@ -488,14 +339,11 @@ void Device::updateBrightness(const UIState &s) { // Scale back to 10% to 100% clipped_brightness = std::clamp(100.0f * clipped_brightness, 10.0f, 100.0f); } - + RETURN_IF_SUNNYPILOT + int brightness = brightness_filter.update(clipped_brightness); if (!awake) { brightness = 0; - } else if (s.scene.started && s.scene.sleep_time == 0 && s.scene.onroadScreenOff != -2) { - brightness = s.scene.onroadScreenOffBrightness * 0.01 * brightness; - } else if (s.scene.brightness) { - brightness = s.scene.brightness * 0.99; } if (brightness != last_brightness) { @@ -519,6 +367,7 @@ void Device::updateWakefulness(const UIState &s) { setAwake(s.scene.ignition || interactive_timeout > 0); } +#ifndef SUNNYPILOT UIState *uiState() { static UIState ui_state; return &ui_state; @@ -528,3 +377,4 @@ Device *device() { static Device _device; return &_device; } +#endif \ No newline at end of file diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index a9fb6f0671..5626af6a91 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include @@ -15,31 +14,11 @@ #include "common/mat.h" #include "common/params.h" #include "common/timing.h" -#include "qt/network/sunnylink/models/role_model.h" -#include "qt/network/sunnylink/models/sponsor_role_model.h" -#include "qt/network/sunnylink/models/user_model.h" #include "system/hardware/hw.h" const int UI_BORDER_SIZE = 30; const int UI_HEADER_HEIGHT = 420; -const int UI_ROAD_NAME_MARGIN_X = 14; - -struct FeatureStatusText { - const QStringList dlp_list_text = { "Laneful", "Laneless", "Auto"}; - const QStringList gac_list_text = { "Aggressive", "Moderate", "Standard", "Relaxed"}; - const QStringList slc_list_text = { "Inactive", "Temp Off", "Adapting", "Active", "Pre Active"}; -}; - -struct FeatureStatusColor { - const QStringList dlp_list_color = { "#2020f8", "#0df87a", "#0df8f8" }; - const QStringList gac_list_color = { "#ff4b4b", "#fcff4b", "#4bff66", "#6a0ac9" }; - const QStringList slc_list_color = { "#ffffff", "#ffffff", "#fcff4b", "#4bff66", "#fcff4b" }; -}; - -const float DRIVING_PATH_WIDE = 0.9; -const float DRIVING_PATH_NARROW = 0.25; - const int UI_FREQ = 20; // Hz const int BACKLIGHT_OFFROAD = 50; @@ -66,12 +45,18 @@ constexpr vec3 default_face_kpts_3d[] = { {18.02, -49.14, 8.00}, {6.36, -51.20, 8.00}, {-5.98, -51.20, 8.00}, }; +//Example of a macro +#ifdef SUNNYPILOT +#define EXTRA_UI_STATES STATUS_MADS +#else +#define EXTRA_UI_STATES +#endif typedef enum UIStatus { STATUS_DISENGAGED, STATUS_OVERRIDE, STATUS_ENGAGED, - STATUS_MADS, + EXTRA_UI_STATES } UIStatus; enum PrimeType { @@ -88,18 +73,10 @@ enum PrimeType { const QColor bg_colors [] = { [STATUS_DISENGAGED] = QColor(0x17, 0x33, 0x49, 0xc8), [STATUS_OVERRIDE] = QColor(0x91, 0x9b, 0x95, 0xf1), - [STATUS_ENGAGED] = QColor(0x00, 0xc8, 0x00, 0xf1), - [STATUS_MADS] = QColor(0x00, 0xc8, 0xc8, 0xf1), + [STATUS_ENGAGED] = QColor(0x17, 0x86, 0x44, 0xf1), }; -const QColor tcs_colors [] = { - [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::DISABLED)] = QColor(0x0, 0x0, 0x0, 0xff), - [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::ENTERING)] = QColor(0xC9, 0x22, 0x31, 0xf1), - [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::TURNING)] = QColor(0xDA, 0x6F, 0x25, 0xf1), - [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::LEAVING)] = QColor(0x17, 0x86, 0x44, 0xf1), -}; - typedef struct UIScene { bool calibration_valid = false; bool calibration_wide_valid = false; @@ -107,24 +84,13 @@ typedef struct UIScene { mat3 view_from_calib = DEFAULT_CALIBRATION; mat3 view_from_wide_calib = DEFAULT_CALIBRATION; cereal::PandaState::PandaType pandaType; - cereal::ControlsState::Reader controlsState; - - // Debug UI - bool show_debug_ui; - - // Speed limit control - bool speed_limit_control_enabled; - int speed_limit_control_policy; - double last_speed_limit_sign_tap; // modelV2 float lane_line_probs[4]; float road_edge_stds[2]; QPolygonF track_vertices; - QPolygonF track_edge_vertices; QPolygonF lane_line_vertices[4]; QPolygonF road_edge_vertices[2]; - QPolygonF lane_barrier_vertices[2]; // lead QPointF lead_vertices[2]; @@ -136,87 +102,24 @@ typedef struct UIScene { float driver_pose_coss[3]; vec3 face_kpts_draw[std::size(default_face_kpts_3d)]; - bool navigate_on_openpilot_deprecated = false; cereal::LongitudinalPersonality personality; - cereal::AccelerationPersonality accel_personality; float light_sensor = -1; - bool started, ignition, is_metric, map_on_left, longitudinal_control; + bool started, ignition, is_metric, longitudinal_control; bool world_objects_visible = false; uint64_t started_frame; - - int dynamic_lane_profile; - bool dynamic_lane_profile_status = true; - - bool visual_brake_lights; - - int onroadScreenOff, osoTimer, brightness, onroadScreenOffBrightness, awake; - bool onroadScreenOffEvent; - int sleep_time = -1; - bool touched2 = false; - - bool stand_still_timer; - - bool hide_vego_ui, true_vego_ui; - - int chevron_data; - - bool gac; - int longitudinal_personality; - int longitudinal_accel_personality; - - bool map_visible; - int dev_ui_info; - bool live_torque_toggle; - - bool touch_to_wake = false; - int sleep_btn = -1; - bool sleep_btn_fading_in = false; - int sleep_btn_opacity = 20; - bool button_auto_hide; - - bool reverse_dm_cam; - - bool e2e_long_alert_light, e2e_long_alert_lead, e2e_long_alert_ui; - float e2eX[13] = {0}; - - int sidebar_temp_options; - - float mads_path_scale = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; - float mads_path_range = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; // 0.9 - 0.25 = 0.65 - - bool onroad_settings_visible; - - bool map_3d_buildings; - - bool torqued_override; - - bool dynamic_experimental_control; - - int speed_limit_control_engage_type; - - bool mapbox_fullscreen; - - bool speed_limit_warning_flash; - int speed_limit_warning_type; - int speed_limit_warning_value_offset; - - bool custom_driving_model_valid; - cereal::ModelGeneration driving_model_generation; - uint32_t driving_model_capabilities; - - bool feature_status_toggle; - bool onroad_settings_toggle; - - bool dynamic_personality; } UIScene; +#ifdef SUNNYPILOT +#include "sunnypilot/qt/ui_scene.h" +#define UIScene UISceneSP +#endif class UIState : public QObject { Q_OBJECT public: UIState(QObject* parent = 0); - void updateStatus(); + virtual void updateStatus(); inline bool engaged() const { return scene.started && (*sm)["controlsState"].getControlsState().getEnabled(); } @@ -225,42 +128,6 @@ public: inline PrimeType primeType() const { return prime_type; } inline bool hasPrime() const { return prime_type > PrimeType::NONE; } - void setSunnylinkRoles(const std::vector &roles); - void setSunnylinkDeviceUsers(const std::vector &users); - - inline std::vector sunnylinkDeviceRoles() const { return sunnylinkRoles; } - inline bool isSunnylinkAdmin() const { - return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) { - return role.roleType == RoleType::Admin; - }); - } - inline bool isSunnylinkSponsor() const { - return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) { - return role.roleType == RoleType::Sponsor && role.as().roleTier != SponsorTier::Free; - }); - } - inline SponsorRoleModel sunnylinkSponsorRole() const { - std::optional sponsorRoleWithHighestTier = std::nullopt; - for (const auto &role : sunnylinkRoles) { - if(role.roleType != RoleType::Sponsor) - continue; - - if (auto sponsorRole = role.as(); !sponsorRoleWithHighestTier.has_value() || sponsorRoleWithHighestTier->roleTier < sponsorRole.roleTier) { - sponsorRoleWithHighestTier = sponsorRole; - } - } - return sponsorRoleWithHighestTier.value_or(SponsorRoleModel(RoleType::Sponsor, SponsorTier::Free)); - } - inline SponsorTier sunnylinkSponsorTier() const { - return sunnylinkSponsorRole().roleTier; - } - inline std::vector sunnylinkDeviceUsers() const { return sunnylinkUsers; } - inline bool isSunnylinkPaired() const { - return std::any_of(sunnylinkUsers.begin(), sunnylinkUsers.end(), [](const UserModel &user) { - return user.user_id.toLower() != "unregisteredsponsor" && user.user_id.toLower() != "temporarysponsor"; - }); - } - int fb_w = 0, fb_h = 0; std::unique_ptr sm; @@ -278,27 +145,21 @@ signals: void primeChanged(bool prime); void primeTypeChanged(PrimeType prime_type); - void sunnylinkRoleChanged(bool subscriber); - void sunnylinkRolesChanged(std::vector roles); - void sunnylinkDeviceUsersChanged(std::vector users); +protected slots: + virtual void update(); -private slots: - void update(); - -private: +protected: QTimer *timer; - bool started_prev = false; PrimeType prime_type = PrimeType::UNKNOWN; - std::vector sunnylinkRoles = {}; - std::vector sunnylinkUsers = {}; - bool last_mads_enabled = false; - bool mads_path_state = false; - float mads_path_timestep = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] - float mads_path_count = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] +private: + bool started_prev = false; }; +#undef UIScene +#ifndef SUNNYPILOT UIState *uiState(); +#endif // device management class class Device : public QObject { @@ -311,7 +172,7 @@ public: offroad_brightness = std::clamp(brightness, 0, 100); } -private: +protected: bool awake = false; int interactive_timeout = 0; bool ignition_on = false; @@ -321,9 +182,10 @@ private: FirstOrderFilter brightness_filter; QFuture brightness_future; - void updateBrightness(const UIState &s); + virtual void updateBrightness(const UIState &s); void updateWakefulness(const UIState &s); void setAwake(bool on); + float clipped_brightness; signals: void displayPowerChanged(bool on); @@ -334,7 +196,9 @@ public slots: void update(const UIState &s); }; +#ifndef SUNNYPILOT Device *device(); +#endif void ui_update_params(UIState *s); int get_path_length_idx(const cereal::XYZTData::Reader &line, const float path_height); @@ -343,4 +207,8 @@ void update_model(UIState *s, void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd); void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line); void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, - float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert); + float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert); + +bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, float in_z, QPointF *out, float margin=500.0f); +void update_state(UIState *s); +void update_sockets(UIState *s); diff --git a/system/manager/process_config.py b/system/manager/process_config.py index a718ea021b..1db62dc2c4 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -45,8 +45,7 @@ def only_offroad(started, params, CP: car.CarParams) -> bool: return not started def use_gitlab_runner(started, params, CP: car.CarParams) -> bool: - return (not PC and params.get_bool("EnableGitlabRunner") and only_offroad(started, params, CP) - and os.path.exists("./gitlab_runner.sh")) + return not PC and params.get_bool("EnableGitlabRunner") and only_offroad(started, params, CP) def model_use_nav(started, params, CP: car.CarParams) -> bool: custom_model_metadata = CustomModelMetadata(params=params, init_only=True) @@ -62,7 +61,7 @@ def sunnylink_need_register_shim(started, params, CP: car.CarParams) -> bool: def use_sunnylink_uploader_shim(started, params, CP: car.CarParams) -> bool: """Shim for use_sunnylink_uploader to match the process manager signature.""" - return use_sunnylink_uploader(params) and os.path.exists("../loggerd/sunnylink_uploader.py") + return use_sunnylink_uploader(params) procs = [ DaemonProcess("manage_athenad", "system.athena.manage_athenad", "AthenadPid"), @@ -120,12 +119,15 @@ procs = [ PythonProcess("webrtcd", "system.webrtc.webrtcd", notcar), PythonProcess("webjoystick", "tools.bodyteleop.web", notcar), - # Sunnypilot devs - NativeProcess("gitlab_runner_start", "system/manager", ["./gitlab_runner.sh", "start"], use_gitlab_runner, sigkill=False), - # Sunnylink <3 + # sunnylink <3 DaemonProcess("manage_sunnylinkd", "system.athena.manage_sunnylinkd", "SunnylinkdPid"), PythonProcess("sunnylink_registration", "system.manager.sunnylink", sunnylink_need_register_shim), - PythonProcess("sunnylink_uploader", "system.loggerd.sunnylink_uploader", use_sunnylink_uploader_shim), ] +if os.path.exists("./gitlab_runner.sh"): + procs += [NativeProcess("gitlab_runner_start", "system/manager", ["./gitlab_runner.sh", "start"], use_gitlab_runner, sigkill=False)] + +if os.path.exists("../loggerd/sunnylink_uploader.py"): + procs += [PythonProcess("sunnylink_uploader", "system.loggerd.sunnylink_uploader", use_sunnylink_uploader_shim)] + managed_processes = {p.name: p for p in procs} diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 15e1ee5f2f..f1700ebe20 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -6,7 +6,12 @@ #include #include +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#define ElidedLabel ElidedLabelSP +#else #include "selfdrive/ui/qt/widgets/controls.h" +#endif #include "tools/cabana/binaryview.h" #include "tools/cabana/chart/chartswidget.h" #include "tools/cabana/historylog.h"