sunnypilot: remove Qt

This commit is contained in:
Jason Wen
2025-11-14 01:53:22 -05:00
parent a6e43a77d9
commit 0c98c33e79
153 changed files with 0 additions and 11833 deletions
-107
View File
@@ -1,107 +0,0 @@
widgets_src = [
"sunnypilot/qt/request_repeater.cc",
"sunnypilot/qt/widgets/toggle.cc",
"sunnypilot/qt/widgets/controls.cc",
"sunnypilot/qt/widgets/drive_stats.cc",
"sunnypilot/qt/widgets/expandable_row.cc",
"sunnypilot/qt/widgets/external_storage.cc",
"sunnypilot/qt/widgets/prime.cc",
"sunnypilot/qt/widgets/scrollview.cc",
"sunnypilot/qt/network/networking.cc",
]
qt_util = [
"sunnypilot/qt/util.cc",
]
qt_src = [
"sunnypilot/ui.cc",
"sunnypilot/qt/api.cc",
"sunnypilot/qt/sidebar.cc",
"sunnypilot/qt/window.cc",
"sunnypilot/qt/home.cc",
"sunnypilot/qt/offroad/exit_offroad_button.cc",
"sunnypilot/qt/offroad/offroad_home.cc",
"sunnypilot/qt/offroad/settings/developer_panel.cc",
"sunnypilot/qt/offroad/settings/device_panel.cc",
"sunnypilot/qt/offroad/settings/display_panel.cc",
"sunnypilot/qt/offroad/settings/lateral_panel.cc",
"sunnypilot/qt/offroad/settings/longitudinal_panel.cc",
"sunnypilot/qt/offroad/settings/max_time_offroad.cc",
"sunnypilot/qt/offroad/settings/brightness.cc",
"sunnypilot/qt/offroad/settings/models_panel.cc",
"sunnypilot/qt/offroad/settings/osm_panel.cc",
"sunnypilot/qt/offroad/settings/settings.cc",
"sunnypilot/qt/offroad/settings/software_panel.cc",
"sunnypilot/qt/offroad/settings/sunnylink_panel.cc",
"sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.cc",
"sunnypilot/qt/offroad/settings/sunnylink/community_widget.cc",
"sunnypilot/qt/offroad/settings/trips_panel.cc",
"sunnypilot/qt/offroad/settings/vehicle_panel.cc",
"sunnypilot/qt/offroad/settings/visuals_panel.cc",
"sunnypilot/qt/onroad/alerts.cc",
"sunnypilot/qt/onroad/annotated_camera.cc",
"sunnypilot/qt/onroad/buttons.cc",
"sunnypilot/qt/onroad/developer_ui/developer_ui.cc",
"sunnypilot/qt/onroad/hud.cc",
"sunnypilot/qt/onroad/model.cc",
"sunnypilot/qt/onroad/onroad_home.cc",
]
lateral_panel_qt_src = [
"sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.cc",
"sunnypilot/qt/offroad/settings/lateral/lane_change_settings.cc",
"sunnypilot/qt/offroad/settings/lateral/mads_settings.cc",
"sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.cc",
"sunnypilot/qt/offroad/settings/lateral/torque_lateral_control_custom_params.cc",
"sunnypilot/qt/offroad/settings/lateral/torque_lateral_control_settings.cc",
]
longitudinal_panel_qt_src = [
"sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.cc",
"sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_policy.cc",
"sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc",
]
network_src = [
"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",
]
osm_panel_qt_src = [
"sunnypilot/qt/offroad/settings/osm/models_fetcher.cc",
]
vehicle_panel_qt_src = [
"sunnypilot/qt/offroad/settings/vehicle/brand_settings_factory.cc",
"sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.cc",
"sunnypilot/qt/offroad/settings/vehicle/platform_selector.cc",
]
brand_settings_qt_src = [
"sunnypilot/qt/offroad/settings/vehicle/chrysler_settings.cc",
"sunnypilot/qt/offroad/settings/vehicle/ford_settings.cc",
"sunnypilot/qt/offroad/settings/vehicle/gm_settings.cc",
"sunnypilot/qt/offroad/settings/vehicle/honda_settings.cc",
"sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.cc",
"sunnypilot/qt/offroad/settings/vehicle/mazda_settings.cc",
"sunnypilot/qt/offroad/settings/vehicle/nissan_settings.cc",
"sunnypilot/qt/offroad/settings/vehicle/rivian_settings.cc",
"sunnypilot/qt/offroad/settings/vehicle/subaru_settings.cc",
"sunnypilot/qt/offroad/settings/vehicle/tesla_settings.cc",
"sunnypilot/qt/offroad/settings/vehicle/toyota_settings.cc",
"sunnypilot/qt/offroad/settings/vehicle/volkswagen_settings.cc",
]
display_panel_qt_src = [
"sunnypilot/qt/offroad/settings/display/onroad_screen_brightness.cc",
]
sp_widgets_src = widgets_src + network_src
sp_qt_src = qt_src + lateral_panel_qt_src + vehicle_panel_qt_src + brand_settings_qt_src + \
longitudinal_panel_qt_src + osm_panel_qt_src + display_panel_qt_src
sp_qt_util = qt_util
Export('sp_widgets_src', 'sp_qt_src', "sp_qt_util")
-63
View File
@@ -1,63 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/api.h"
#include <QApplication>
#include <QJsonDocument>
#include "util.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);
}
} // 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);
}
-36
View File
@@ -1,36 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#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); }
};
@@ -1,41 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonObject>
#include <QEventLoop>
class JsonFetcher {
public:
static QJsonObject getJsonFromURL(const QString &url) {
const auto qurl = QUrl(url);
QNetworkAccessManager manager;
const QNetworkRequest request(qurl);
QNetworkReply *reply = manager.get(request);
QEventLoop loop;
// Send GET request
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Failed to fetch data from URL: " << reply->errorString();
return QJsonObject();
}
const QByteArray responseData = reply->readAll();
const QJsonDocument doc = QJsonDocument::fromJson(responseData);
QJsonObject json = doc.object();
reply->deleteLater();
return json;
}
};
-21
View File
@@ -1,21 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/home.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h"
HomeWindowSP::HomeWindowSP(QWidget *parent) : HomeWindow(parent) {
}
void HomeWindowSP::updateState(const UIState &s) {
HomeWindow::updateState(s);
}
void HomeWindowSP::mousePressEvent(QMouseEvent *e) {
HomeWindow::mousePressEvent(e);
}
-38
View File
@@ -1,38 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <QFrame>
#include <QWidget>
#include "common/params.h"
#include "selfdrive/ui/qt/body.h"
#include "selfdrive/ui/qt/widgets/offroad_alerts.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/qt/home.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/qt/sidebar.h"
#define OnroadWindow OnroadWindowSP
#else
#include "selfdrive/ui/qt/sidebar.h"
#include "selfdrive/ui/qt/onroad/onroad_home.h"
#endif
class HomeWindowSP : public HomeWindow {
Q_OBJECT
public:
explicit HomeWindowSP(QWidget *parent = 0);
protected:
void mousePressEvent(QMouseEvent *e) override;
private slots:
void updateState(const UIState &s) override;
};
@@ -1,45 +0,0 @@
#include "selfdrive/ui/sunnypilot/qt/network/networking.h"
#include <QVBoxLayout>
#include <QPushButton>
#include <QStackedLayout>
NetworkingSP::NetworkingSP(QWidget *parent) : Networking(parent) {
auto vlayout = wifiScreen->findChild<QVBoxLayout*>();
auto hlayout = new QHBoxLayout();
// Create and setup scan button
auto 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);
// Look for an existing Advanced button
QPushButton* existingAdvanced = wifiScreen->findChild<QPushButton*>("advanced_btn");
if (existingAdvanced) {
hlayout->addWidget(existingAdvanced);
}
// Insert our new layout at the top of vlayout
vlayout->setMargin(40);
vlayout->insertLayout(0, hlayout);
// Add our scan button to the existing style selectors
auto newStyleSheet = styleSheet().replace(
", #advanced_btn ",
", #advanced_btn, #scan_btn "
).replace(
", #advanced_btn:pressed",
", #advanced_btn:pressed, #scan_btn:pressed"
).append(R"(
#scan_btn:disabled {
background-color: #121212;
color: #33FFFFFF;
}
)");
setStyleSheet(newStyleSheet);
}
@@ -1,17 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/qt/network/networking.h"
class NetworkingSP : public Networking {
Q_OBJECT
public:
explicit NetworkingSP(QWidget *parent = nullptr);
};
@@ -1,56 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <QJsonObject>
enum class RoleType {
ReadOnly,
Sponsor,
Admin
};
// haha, a role model xD
class RoleModel {
protected:
QJsonObject m_raw_json_object;
public:
RoleType roleType;
explicit RoleModel(const RoleType &roleType) : roleType(roleType) { m_raw_json_object = toJson(); }
explicit RoleModel(const QJsonObject &json) : RoleModel(stringToRoleType(json["role_type"].toString())) { m_raw_json_object = json; }
[[nodiscard]] QJsonObject toJson() const {
QJsonObject json;
json["role_type"] = roleTypeToString(roleType);
return json;
}
static RoleType stringToRoleType(const QString &roleTypeString) {
if (roleTypeString == "ReadOnly") return RoleType::ReadOnly;
if (roleTypeString == "Sponsor") return RoleType::Sponsor;
return RoleType::Admin; // Default to Admin
}
static QString roleTypeToString(const RoleType &roleType) {
switch (roleType) {
case RoleType::ReadOnly:
return "ReadOnly";
case RoleType::Sponsor:
return "Sponsor";
default: // RoleType::Admin
return "Admin";
}
}
template <typename T, typename = typename std::enable_if<std::is_base_of<RoleModel, T>::value>::type> T as() const {
return T(m_raw_json_object);
}
};
@@ -1,83 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <QJsonObject>
enum class SponsorTier {
Free,
Novice,
Supporter,
Contributor,
Benefactor,
Guardian,
};
// haha, a role model xD
class SponsorRoleModel final : RoleModel {
public:
SponsorTier roleTier;
explicit SponsorRoleModel(const RoleType &roleType, const SponsorTier &roleTier) : RoleModel(roleType), roleTier(roleTier) {}
explicit SponsorRoleModel(const QJsonObject &json) : RoleModel(json), roleTier(stringToSponsorTier(json["role_tier"].toString())) {}
[[nodiscard]] QJsonObject toJson() const {
QJsonObject json = RoleModel::toJson();
json["role_tier"] = sponsorTierToString(roleTier);
return json;
}
static SponsorTier stringToSponsorTier(const QString &sponsorTierString) {
const auto sponsorTierStringLower = sponsorTierString.toLower();
if (sponsorTierStringLower == "guardian") return SponsorTier::Guardian;
if (sponsorTierStringLower == "novice") return SponsorTier::Novice;
if (sponsorTierStringLower == "supporter") return SponsorTier::Supporter;
if (sponsorTierStringLower == "contributor") return SponsorTier::Contributor;
if (sponsorTierStringLower == "benefactor") return SponsorTier::Benefactor;
// Default to Free
return SponsorTier::Free;
}
static QString sponsorTierToString(const SponsorTier &sponsorTier) {
switch (sponsorTier) {
case SponsorTier::Guardian:
return "Guardian";
case SponsorTier::Novice:
return "Novice";
case SponsorTier::Supporter:
return "Supporter";
case SponsorTier::Contributor:
return "Contributor";
case SponsorTier::Benefactor:
return "Benefactor";
default: // SponsorTier::Free
return "Free";
}
}
[[nodiscard]] auto getSponsorTierString() const { return sponsorTierToString(roleTier); }
static QString sponsorTierToColor(const SponsorTier &sponsorTier) {
switch (sponsorTier) {
case SponsorTier::Guardian:
return "gold";
case SponsorTier::Benefactor:
return "mediumseagreen";
case SponsorTier::Contributor:
return "steelblue";
case SponsorTier::Supporter:
return "mediumpurple";
case SponsorTier::Novice:
return "white";
default: // SponsorTier::Free
return "silver";
}
}
[[nodiscard]] auto getSponsorTierColor() const { return sponsorTierToColor(roleTier); }
};
@@ -1,37 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <QJsonObject>
class UserModel {
public:
QString device_id;
QString user_id;
qint64 created_at;
qint64 updated_at;
QString token_hash;
explicit UserModel(const QJsonObject &json) {
device_id = json["device_id"].toString();
user_id = json["user_id"].toString();
created_at = json["created_at"].toInt();
updated_at = json["updated_at"].toInt();
token_hash = json["token_hash"].toString();
}
[[nodiscard]] QJsonObject toJson() const {
QJsonObject json;
json["device_id"] = device_id;
json["user_id"] = user_id;
json["created_at"] = created_at;
json["updated_at"] = updated_at;
json["token_hash"] = token_hash;
return json;
}
};
@@ -1,57 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#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_panel.h"
BaseDeviceService::BaseDeviceService(QObject* parent) : QObject(parent) {
param_watcher = new ParamWatcher(this);
connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString &param_name, const QString &param_value) {
paramsRefresh();
});
param_watcher->addParam("SunnylinkEnabled");
}
void BaseDeviceService::paramsRefresh() {
}
void BaseDeviceService::loadDeviceData(const QString &url, bool poll) {
if (!is_sunnylink_enabled()) {
LOGW("Sunnylink is not enabled, refusing to load data.");
return;
}
auto sl_dongle_id = getSunnylinkDongleId();
if (!sl_dongle_id.has_value())
return;
QString fullUrl = SUNNYLINK_BASE_URL + "/device/" + *sl_dongle_id + url;
if (poll && !isCurrentyPolling()) {
LOGD("Polling %s", qPrintable(fullUrl));
LOGD("Cache key: SunnylinkCache_%s", qPrintable(QString(getCacheKey())));
repeater = new RequestRepeaterSP(this, fullUrl, "SunnylinkCache_" + getCacheKey(), 60, false, true);
connect(repeater, &RequestRepeaterSP::requestDone, this, &BaseDeviceService::handleResponse);
} else if (isCurrentyPolling()) {
repeater->ForceUpdate();
} else {
LOGD("Sending one-time %s", qPrintable(fullUrl));
initial_request = new HttpRequestSP(this, true, 10000, true);
connect(initial_request, &HttpRequestSP::requestDone, this, &BaseDeviceService::handleResponse);
}
}
void BaseDeviceService::stopPolling() {
if (repeater != nullptr) {
repeater->deleteLater();
repeater = nullptr;
}
}
@@ -1,31 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#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();
};
@@ -1,36 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h"
#include <QJsonArray>
#include <QJsonDocument>
RoleService::RoleService(QObject* parent) : BaseDeviceService(parent) {}
void RoleService::load() {
loadDeviceData(url);
}
void RoleService::startPolling() {
loadDeviceData(url, true);
}
void RoleService::handleResponse(const QString &response, bool success) {
if (!success) return;
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
QJsonArray jsonArray = doc.array();
std::vector<RoleModel> roles;
for (const auto &value : jsonArray) {
roles.emplace_back(value.toObject());
}
emit rolesReady(roles);
uiStateSP()->setSunnylinkRoles(roles);
}
@@ -1,32 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <vector>
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h"
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h"
class RoleService : public BaseDeviceService {
Q_OBJECT
public:
explicit RoleService(QObject* parent = nullptr);
void load();
void startPolling();
[[nodiscard]] QString getCacheKey() const final { return "Roles"; }
signals:
void rolesReady(const std::vector<RoleModel> &roles);
protected:
void handleResponse(const QString&response, bool success) override;
private:
QString url = "/roles";
};
@@ -1,42 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h"
#include <QJsonArray>
#include <QJsonDocument>
#include "selfdrive/ui/sunnypilot/ui.h"
UserService::UserService(QObject* parent) : BaseDeviceService(parent) {
url = "/users";
}
void UserService::load() {
loadDeviceData(url);
}
void UserService::startPolling() {
loadDeviceData(url, true);
}
void UserService::handleResponse(const QString &response, bool success) {
if (!success) {
return;
}
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
QJsonArray jsonArray = doc.array();
std::vector<UserModel> users;
for (const auto &value : jsonArray) {
users.emplace_back(value.toObject());
}
emit usersReady(users);
uiStateSP()->setSunnylinkDeviceUsers(users);
}
@@ -1,32 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <vector>
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h"
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h"
class UserService : public BaseDeviceService {
Q_OBJECT
public:
explicit UserService(QObject* parent = nullptr);
void load();
void startPolling();
[[nodiscard]] QString getCacheKey() const final { return "Users"; };
signals:
void usersReady(const std::vector<UserModel>&users);
protected:
void handleResponse(const QString&response, bool success) override;
private:
QString url = "/users";
};
@@ -1,14 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#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);
}
@@ -1,22 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <QObject>
#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;
};
@@ -1,100 +0,0 @@
#include <QDebug>
#include <QHBoxLayout>
#include <QPainter>
#include <QPainterPath>
#include <QStyle>
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/widgets/input.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/exit_offroad_button.h"
ExitOffroadButton::ExitOffroadButton(QWidget *parent) : QPushButton(parent), glowTimer(new QTimer(this)) {
setMouseTracking(true);
connect(glowTimer, &QTimer::timeout, this, [this]() {
// Pulse alpha up and down
glowAlpha += glowDelta;
if (glowAlpha > 220 || glowAlpha < 10) {
glowDelta *= -1;
}
update(); // trigger repaint
});
glowTimer->start(45);
pixmap = QPixmap("../../sunnypilot/selfdrive/assets/offroad/icon_exit_offroad.png").scaledToWidth(img_width, Qt::SmoothTransformation);
// go to toggles and expand experimental mode description
connect(this, &QPushButton::clicked, [=]() {
if (ConfirmationDialog::confirm(tr("Are you sure you want to exit Always Offroad mode?"), tr("Confirm"), this)) {
params.remove("OffroadMode");
}
});
setFixedHeight(125);
QHBoxLayout *main_layout = new QHBoxLayout;
main_layout->setContentsMargins(horizontal_padding, 0, horizontal_padding, 0);
mode_label = new QLabel(tr("EXIT ALWAYS OFFROAD MODE"));
mode_icon = new QLabel;
mode_icon->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
mode_icon->setPixmap(pixmap);
main_layout->addWidget(mode_label, 1, Qt::AlignLeft);
main_layout->addWidget(mode_icon, 0, Qt::AlignRight);
setLayout(main_layout);
setStyleSheet(R"(
QPushButton {
border: none;
}
QLabel {
font-size: 45px;
font-weight: 300;
text-align: left;
font-family: JetBrainsMono;
color: #000000;
}
)");
}
void drawPulsingGlowOverlay(QPainter &p, QPainterPath path, int glowAlpha) {
// Draw pulsing glow effect clipped to button area
p.save();
p.setClipPath(path);
p.setCompositionMode(QPainter::CompositionMode_HardLight);
const QColor animatedGlowColor(255, 255, 255, std::min(255, glowAlpha));
QPen glowPen(animatedGlowColor, 8);
glowPen.setJoinStyle(Qt::RoundJoin);
p.setPen(glowPen);
p.drawPath(path);
p.restore();
}
void ExitOffroadButton::paintEvent(QPaintEvent *event) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
QPainterPath path;
path.addRoundedRect(rect(), 10, 10);
// gradient
bool pressed = isDown();
QLinearGradient gradient(rect().left(), 0, rect().right(), 0);
gradient.setColorAt(0, QColor(35, 149, 255, pressed ? 0xcc : 0xff));
gradient.setColorAt(0.3, QColor(35, 149, 255, pressed ? 0xcc : 0xff));
gradient.setColorAt(1, QColor(20, 255, 171, pressed ? 0xcc : 0xff));
p.fillPath(path, gradient);
drawPulsingGlowOverlay(p, path, glowAlpha);
// vertical line
p.setPen(QPen(QColor(0, 0, 0, 0x4d), 3, Qt::SolidLine));
int line_x = rect().right() - img_width - (2 * horizontal_padding);
p.drawLine(line_x, rect().bottom(), line_x, rect().top());
}
@@ -1,29 +0,0 @@
#pragma once
#include <QLabel>
#include <QPushButton>
#include "common/params.h"
class ExitOffroadButton : public QPushButton {
Q_OBJECT
private:
QTimer *glowTimer;
int glowAlpha = 100; // Current alpha of glow
int glowDelta = 10; // Change per tick
public:
explicit ExitOffroadButton(QWidget* parent = 0);
Params params;
bool offroad_mode;
int img_width = 100;
int horizontal_padding = 30;
QPixmap pixmap;
QLabel *mode_label;
QLabel *mode_icon;
protected:
void paintEvent(QPaintEvent *event) override;
};
@@ -1,49 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include <QStackedWidget>
#include "selfdrive/ui/sunnypilot/qt/offroad/offroad_home.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h"
OffroadHomeSP::OffroadHomeSP(QWidget *parent) : OffroadHome(parent) {
QFrame *left_widget = new QFrame(this);
QVBoxLayout *left_layout = new QVBoxLayout(left_widget);
left_layout->setContentsMargins(0, 0, 0, 0);
left_layout->setSpacing(30);
btn_exit_offroad = new ExitOffroadButton(this);
QObject::connect(btn_exit_offroad, &ExitOffroadButton::clicked, [=]() {
refreshOffroadStatus();
});
left_layout->addWidget(btn_exit_offroad);
left_layout->addWidget(new DriveStats(this));
left_widget->setStyleSheet("border-radius: 10px;");
home_layout->insertWidget(0, left_widget);
offroad_notif = new QPushButton(tr("ALWAYS OFFROAD ACTIVE"));
offroad_notif->setVisible(false);
offroad_notif->setStyleSheet("background-color: #E22C2C;");
header_layout->insertWidget(0, offroad_notif, 0, Qt::AlignHCenter | Qt::AlignLeft);
QObject::connect(deviceSP(), &DeviceSP::displayPowerChanged, this, &OffroadHomeSP::refreshOffroadStatus);
}
void OffroadHomeSP::showEvent(QShowEvent *event) {
refreshOffroadStatus();
OffroadHome::showEvent(event);
}
void OffroadHomeSP::refreshOffroadStatus() {
bool is_offroad = params.getBool("OffroadMode");
btn_exit_offroad->setVisible(is_offroad);
offroad_notif->setVisible(is_offroad);
OffroadHome::refresh();
}
@@ -1,26 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/qt/offroad/offroad_home.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/exit_offroad_button.h"
class OffroadHomeSP : public OffroadHome {
Q_OBJECT
public:
explicit OffroadHomeSP(QWidget *parent = 0);
private:
ExitOffroadButton *btn_exit_offroad;
QPushButton *offroad_notif;
Params params;
void showEvent(QShowEvent *event) override;
void refreshOffroadStatus();
};
@@ -1,50 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/brightness.h"
// Map of Brightness Options
const QMap<QString, QString> Brightness::brightness_options = {
{"0", "1"}, // Auto (Dark)
{"1", "0"}, // Auto
{"2", "10"},
{"3", "20"},
{"4", "30"},
{"5", "40"},
{"6", "50"},
{"7", "60"},
{"8", "70"},
{"9", "80"},
{"10", "90"},
{"11", "100"}
};
Brightness::Brightness() : OptionControlSP(
"Brightness",
tr("Global Brightness"),
tr("Overrides the brightness of the device. This applies to both onroad and offroad screens. "),
"../assets/offroad/icon_blank.png",
{0, 11}, 1, true, &brightness_options) {
refresh();
}
void Brightness::refresh() {
const int brightness = QString::fromStdString(params.get("Brightness")).toInt();
QString label;
if (brightness == 1) {
label = tr("Auto (Dark)");
} else if (brightness == 0) {
label = tr("Auto");
} else {
const int value = brightness;
label = QString("%1").arg(value);
}
setLabel(label);
}
@@ -1,25 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
class Brightness : public OptionControlSP {
Q_OBJECT
public:
static const QMap<QString, QString> brightness_options;
Brightness();
void refresh();
private:
Params params;
};
@@ -1,91 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/external_storage.h"
DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(parent) {
#ifndef __APPLE__
addItem(new ExternalStorageControl());
#endif
// Advanced Controls Toggle
showAdvancedControls = new ParamControlSP("ShowAdvancedControls", tr("Show Advanced Controls"), tr("Toggle visibility of advanced sunnypilot controls.\nThis only toggles the visibility of the controls; it does not toggle the actual control enabled/disabled state."), "");
addItem(showAdvancedControls);
QObject::connect(showAdvancedControls, &ParamControlSP::toggleFlipped, this, [=](bool) {
AbstractControlSP::UpdateAllAdvancedControls();
updateToggles(!uiState()->scene.started);
});
showAdvancedControls->showDescription();
// Github Runner Toggle
enableGithubRunner = new ParamControlSP("EnableGithubRunner", tr("Enable GitHub runner service"), tr("Enables or disables the github runner service."), "", this, true);
addItem(enableGithubRunner);
// Copyparty Toggle
enableCopyparty = new ParamControlSP("EnableCopyparty", tr("Enable Copyparty service"), tr("Copyparty is a very capable file server, you can use it to download your routes, view your logs and even make some edits on some files from your browser. Requires you to connect to your comma locally via it's IP."), "", this, false);
addItem(enableCopyparty);
// Quickboot Mode Toggle
prebuiltToggle = new ParamControlSP("QuickBootToggle", tr("Enable Quickboot Mode"), tr(""), "", this, true);
addItem(prebuiltToggle);
QObject::connect(prebuiltToggle, &ParamControl::toggleFlipped, [=](bool state) {
QString prebuiltPath = "/data/openpilot/prebuilt";
state ? QFile(prebuiltPath).open(QIODevice::WriteOnly) : QFile::remove(prebuiltPath);
prebuiltToggle->refresh();
});
prebuiltToggle->setVisible(false);
// Error log button
errorLogBtn = new ButtonControlSP(tr("Error Log"), tr("VIEW"), tr("View the error log for sunnypilot crashes."));
connect(errorLogBtn, &ButtonControlSP::clicked, [=]() {
QFileInfo file("/data/community/crashes/error.log");
QString text;
if (file.exists()) {
text = "<b>" + file.lastModified().toString("dd-MMM-yyyy hh:mm:ss ").toUpper() + "</b><br><br>";
}
text += QString::fromStdString(util::read_file("/data/community/crashes/error.log"));
ConfirmationDialog::rich(text, this);
});
addItem(errorLogBtn);
QObject::connect(uiState(), &UIState::offroadTransition, this, &DeveloperPanelSP::updateToggles);
}
void DeveloperPanelSP::updateToggles(bool offroad) {
bool disable_updates = params.getBool("DisableUpdates");
bool is_release = params.getBool("IsReleaseBranch") || params.getBool("IsReleaseSpBranch");
bool is_tested = params.getBool("IsTestedBranch");
bool is_development = params.getBool("IsDevelopmentBranch");
prebuiltToggle->setVisible(!is_release && !is_tested && !is_development);
prebuiltToggle->setEnabled(disable_updates);
params.putBool("QuickBootToggle", QFile::exists("/data/openpilot/prebuilt"));
prebuiltToggle->refresh();
prebuiltToggle->setDescription(disable_updates
? tr("When toggled on, this creates a prebuilt file to allow accelerated boot times. When toggled off, "
"it immediately removes the prebuilt file so compilation of locally edited cpp files can be made. "
"<br><br><b>To edit C++ files locally on device, you MUST first turn off this toggle so the changes can recompile.</b>")
: tr("Quickboot mode requires updates to be disabled.<br>Enable 'Disable Updates' in the Software panel first."));
prebuiltToggle->showDescription();
enableGithubRunner->setVisible(!is_release);
errorLogBtn->setVisible(!is_release);
showAdvancedControls->setEnabled(true);
joystickToggle->setVisible(!is_release);
longManeuverToggle->setVisible(!is_release);
}
void DeveloperPanelSP::showEvent(QShowEvent *event) {
DeveloperPanel::showEvent(event);
AbstractControlSP::UpdateAllAdvancedControls();
updateToggles(!uiState()->scene.started);
}
@@ -1,31 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <QFile>
#include <QFileInfo>
#include "selfdrive/ui/qt/offroad/developer_panel.h"
class DeveloperPanelSP : public DeveloperPanel {
Q_OBJECT
public:
explicit DeveloperPanelSP(SettingsWindow *parent);
private:
ParamControlSP *enableCopyparty;
ParamControlSP *enableGithubRunner;
ButtonControlSP *errorLogBtn;
ParamControlSP *prebuiltToggle;
Params params;
ParamControlSP *showAdvancedControls;
private slots:
void updateToggles(bool offroad);
protected:
void showEvent(QShowEvent *event) override;
};
@@ -1,203 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h"
#include "common/watchdog.h"
#include "selfdrive/ui/qt/qt_window.h"
DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) {
QGridLayout *device_grid_layout = new QGridLayout();
device_grid_layout->setSpacing(30);
device_grid_layout->setHorizontalSpacing(5);
device_grid_layout->setVerticalSpacing(25);
std::vector<std::tuple<QString, QString, QString>> device_btns = {
{"quietModeBtn", tr("Quiet Mode"), "QuietMode"},
{"dcamBtn", tr("Driver Camera Preview"), ""},
{"retrainingBtn", tr("Training Guide"), ""},
{"regulatoryBtn", tr("Regulatory"), ""},
{"translateBtn", tr("Language"), ""},
{"resetParams", tr("Reset Settings"), ""},
{"onroadUploadsBtn", tr("Onroad Uploads"), "OnroadUploads"}
};
int row = 0, col = 0;
for (const auto &[id, text, param] : device_btns) {
if (id == "regulatoryBtn" && !Hardware::TICI()) {
continue;
}
auto *btn = new PushButtonSP(text, 750, this, param);
btn->setObjectName(id);
buttons[id] = btn;
if (col==0) {
device_grid_layout->addWidget(btn, row, col, Qt::AlignLeft);
col++;
} else {
device_grid_layout->addWidget(btn, row, col, Qt::AlignRight);
col=0;
row++;
}
}
connect(buttons["dcamBtn"], &PushButtonSP::clicked, [=]() { emit showDriverView(); });
connect(buttons["quietModeBtn"], &PushButtonSP::clicked, buttons["quietModeBtn"], &PushButtonSP::updateButton);
connect(buttons["retrainingBtn"], &PushButtonSP::clicked, [=]() {
if (ConfirmationDialog::confirm(tr("Are you sure you want to review the training guide?"), tr("Review"), this)) {
emit reviewTrainingGuide();
}
});
if (Hardware::TICI()) {
connect(buttons["regulatoryBtn"], &PushButtonSP::clicked, [=]() {
const std::string txt = util::read_file("../assets/offroad/fcc.html");
ConfirmationDialog::rich(QString::fromStdString(txt), this);
});
}
connect(buttons["translateBtn"], &PushButtonSP::clicked, [=]() {
QMap<QString, QString> langs = getSupportedLanguages();
QString selection = MultiOptionDialog::getSelection(tr("Select a language"), langs.keys(), langs.key(uiState()->language), this);
if (!selection.isEmpty()) {
// put language setting, exit Qt UI, and trigger fast restart
params.put("LanguageSetting", langs[selection].toStdString());
qApp->exit(18);
watchdog_kick(0);
}
});
connect(buttons["resetParams"], &PushButtonSP::clicked, this, &DevicePanelSP::resetSettings);
connect(buttons["onroadUploadsBtn"], &PushButtonSP::clicked, buttons["onroadUploadsBtn"], &PushButtonSP::updateButton);
// Max Time Offroad
maxTimeOffroad = new MaxTimeOffroad();
connect(maxTimeOffroad, &OptionControlSP::updateLabels, maxTimeOffroad, &MaxTimeOffroad::refresh);
addItem(maxTimeOffroad);
toggleDeviceBootMode = new ButtonParamControlSP("DeviceBootMode", tr("Wake-Up Behavior"), "", "", {"Default", "Offroad"}, 375, true);
addItem(toggleDeviceBootMode);
connect(toggleDeviceBootMode, &ButtonParamControlSP::buttonClicked, this, [=](int index) {
params.put("DeviceBootMode", QString::number(index).toStdString());
updateState(offroad);
});
addItem(device_grid_layout);
// offroad mode and power buttons
QHBoxLayout *power_layout = new QHBoxLayout();
power_layout->setSpacing(25);
PushButtonSP *rebootBtn = new PushButtonSP(tr("Reboot"), 750, this);
rebootBtn->setStyleSheet(rebootButtonStyle);
power_layout->addWidget(rebootBtn);
QObject::connect(rebootBtn, &PushButtonSP::clicked, this, &DevicePanelSP::reboot);
PushButtonSP *poweroffBtn = new PushButtonSP(tr("Power Off"), 750, this);
poweroffBtn->setStyleSheet(powerOffButtonStyle);
power_layout->addWidget(poweroffBtn);
QObject::connect(poweroffBtn, &PushButtonSP::clicked, this, &DevicePanelSP::poweroff);
if (!Hardware::PC()) {
connect(uiState(), &UIState::offroadTransition, poweroffBtn, &PushButtonSP::setVisible);
}
offroadBtn = new PushButtonSP(tr("Offroad Mode"));
offroadBtn->setFixedWidth(power_layout->sizeHint().width());
QObject::connect(offroadBtn, &PushButtonSP::clicked, this, &DevicePanelSP::setOffroadMode);
power_group_layout = new QVBoxLayout();
power_group_layout->setSpacing(25);
power_group_layout->addLayout(power_layout);
addItem(power_group_layout);
always_enabled_btns = {
rebootBtn,
poweroffBtn,
offroadBtn,
buttons["quietModeBtn"],
buttons["onroadUploadsBtn"],
};
QObject::connect(uiState(), &UIState::offroadTransition, this, &DevicePanelSP::updateState);
}
void DevicePanelSP::setOffroadMode() {
if (!uiState()->engaged()) {
if (params.getBool("OffroadMode")) {
if (ConfirmationDialog::confirm(tr("Are you sure you want to exit Always Offroad mode?"), tr("Confirm"), this)) {
// Check engaged again in case it changed while the dialog was open
if (!uiState()->engaged()) {
params.remove("OffroadMode");
}
}
} else {
if (ConfirmationDialog::confirm(tr("Are you sure you want to enter Always Offroad mode?"), tr("Confirm"), this)) {
// Check engaged again in case it changed while the dialog was open
if (!uiState()->engaged()) {
params.putBool("OffroadMode", true);
}
}
}
} else {
ConfirmationDialog::alert(tr("Disengage to Enter Always Offroad Mode"), this);
}
updateState(offroad);
}
void DevicePanelSP::resetSettings() {
if (ConfirmationDialog::confirm(tr("Are you sure you want to reset all sunnypilot settings to default? Once the settings are reset, there is no going back."), tr("Reset"), this)) {
if (ConfirmationDialog::confirm(tr("The reset cannot be undone. You have been warned."), tr("Confirm"), this)) {
const std::vector<std::string> keys = params.allKeys();
for (const auto& key : keys) {
params.remove(key);
}
Hardware::reboot();
}
}
}
void DevicePanelSP::showEvent(QShowEvent *event) {
updateState(offroad);
}
void DevicePanelSP::updateState(bool _offroad) {
for (auto btn : findChildren<PushButtonSP*>()) {
bool always_enabled = std::find(always_enabled_btns.begin(), always_enabled_btns.end(), btn) != always_enabled_btns.end();
if (!always_enabled) {
btn->setEnabled(_offroad);
}
}
bool offroad_mode_param = params.getBool("OffroadMode");
offroadBtn->setText(offroad_mode_param ? tr("Exit Always Offroad") : tr("Enable Always Offroad"));
offroadBtn->setStyleSheet(offroad_mode_param ? alwaysOffroadStyle : autoOffroadStyle);
DeviceSleepModeStatus currStatus = DeviceSleepModeStatus::DEFAULT;
if (params.get("DeviceBootMode") == "1") {
currStatus = DeviceSleepModeStatus::OFFROAD;
}
toggleDeviceBootMode->setDescription(deviceSleepModeDescription(currStatus));
if (offroad and not offroad_mode_param) {
power_group_layout->insertWidget(0, offroadBtn, 0, Qt::AlignHCenter);
} else {
AddWidgetAt(0, offroadBtn);
}
offroad = _offroad;
}
@@ -1,114 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/max_time_offroad.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
enum class DeviceSleepModeStatus {
DEFAULT,
OFFROAD,
};
class DevicePanelSP : public DevicePanel {
Q_OBJECT
public:
explicit DevicePanelSP(SettingsWindowSP *parent = 0);
void showEvent(QShowEvent *event) override;
void setOffroadMode();
void updateState(bool _offroad);
void resetSettings();
private:
std::map<QString, PushButtonSP*> buttons;
PushButtonSP *offroadBtn;
MaxTimeOffroad *maxTimeOffroad;
ButtonParamControlSP *toggleDeviceBootMode;
QVBoxLayout *power_group_layout;
bool offroad;
std::vector<PushButtonSP*> always_enabled_btns = {};
const QString alwaysOffroadStyle = R"(
PushButtonSP {
border-radius: 20px;
font-size: 50px;
font-weight: 450;
height: 150px;
padding: 0 25px 0 25px;
color: #FFFFFF;
background-color: #393939;
}
PushButtonSP:pressed {
background-color: #4A4A4A;
}
)";
const QString autoOffroadStyle = R"(
PushButtonSP {
border-radius: 20px;
font-size: 50px;
font-weight: 450;
height: 150px;
padding: 0 25px 0 25px;
color: #FFFFFF;
background-color: #E22C2C;
}
PushButtonSP:pressed {
background-color: #FF2424;
}
)";
const QString rebootButtonStyle = R"(
PushButtonSP {
border-radius: 20px;
font-size: 50px;
font-weight: 450;
height: 150px;
padding: 0 25px 0 25px;
color: #FFFFFF;
background-color: #393939;
}
PushButtonSP:pressed {
background-color: #4A4A4A;
}
)";
const QString powerOffButtonStyle = R"(
PushButtonSP {
border-radius: 20px;
font-size: 50px;
font-weight: 450;
height: 150px;
padding: 0 25px 0 25px;
color: #FFFFFF;
background-color: #E22C2C;
}
PushButtonSP:pressed {
background-color: #FF2424;
}
)";
static QString deviceSleepModeDescription(DeviceSleepModeStatus status = DeviceSleepModeStatus::DEFAULT) {
QString def_str = tr("⁍ Default: Device will boot/wake-up normally & will be ready to engage.");
QString offrd_str = tr("⁍ Offroad: Device will be in Always Offroad mode after boot/wake-up.");
if (status == DeviceSleepModeStatus::DEFAULT) {
def_str = "<font color='white'><b>" + def_str + "</b></font>";
} else if (status == DeviceSleepModeStatus::OFFROAD) {
offrd_str = "<font color='white'><b>" + offrd_str + "</b></font>";
}
return QString("%1<br><br>%2<br>%3")
.arg(tr("Controls state of the device after boot/sleep."))
.arg(def_str)
.arg(offrd_str);
}
};
@@ -1,54 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/display/onroad_screen_brightness.h"
OnroadScreenBrightnessControl::OnroadScreenBrightnessControl(const QString &param, const QString &title,
const QString &description, const QString &icon,
QWidget *parent)
: ExpandableToggleRow(param, title, description, icon, parent) {
auto *mainFrame = new QFrame(this);
auto *mainFrameLayout = new QVBoxLayout();
mainFrame->setLayout(mainFrameLayout);
mainFrameLayout->setSpacing(30);
mainFrameLayout->setContentsMargins(0, 0, 0, 0);
onroadScreenOffTimer = new OptionControlSP(
"OnroadScreenOffTimer",
"Onroad Brightness Delay",
"",
"",
{0, 11}, 1, true, &onroadScreenOffTimerOptions);
onroadScreenBrightness = new OptionControlSP(
"OnroadScreenOffBrightness",
"Onroad Brightness",
"",
"",
{0, 90}, 10, true);
connect(onroadScreenOffTimer, &OptionControlSP::updateLabels, this, &OnroadScreenBrightnessControl::refresh);
connect(onroadScreenBrightness, &OptionControlSP::updateLabels, this, &OnroadScreenBrightnessControl::refresh);
mainFrameLayout->addWidget(onroadScreenBrightness);
mainFrameLayout->addWidget(onroadScreenOffTimer);
addItem(mainFrame);
refresh();
}
void OnroadScreenBrightnessControl::refresh() {
// Driving Screen Off Timer
int valTimer = std::atoi(params.get("OnroadScreenOffTimer").c_str());
std::string labelTimer = (valTimer < 60 ? std::to_string(valTimer) + "s" : std::to_string(valTimer / 60) + "m");
onroadScreenOffTimer->setLabel(QString::fromStdString(labelTimer));
// Driving Screen Off Brightness
std::string valBrightness = params.get("OnroadScreenOffBrightness");
std::string labelBrightness = (valBrightness == "0" ? " Screen Off" : valBrightness + "%");
onroadScreenBrightness->setLabel(QString::fromStdString(labelBrightness));
}
@@ -1,42 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/expandable_row.h"
static const QMap<QString, QString> onroadScreenOffTimerOptions = {
{"0", "15"},
{"1", "30"},
{"2", "60"},
{"3", "120"},
{"4", "180"},
{"5", "240"},
{"6", "300"},
{"7", "360"},
{"8", "420"},
{"9", "480"},
{"10", "540"},
{"11", "600"}
};
class OnroadScreenBrightnessControl : public ExpandableToggleRow {
Q_OBJECT
public:
OnroadScreenBrightnessControl(const QString &param, const QString &title, const QString &desc, const QString &icon,
QWidget *parent = nullptr);
void refresh();
private:
Params params;
OptionControlSP *onroadScreenOffTimer;
OptionControlSP *onroadScreenBrightness;
};
@@ -1,65 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/display_panel.h"
DisplayPanel::DisplayPanel(QWidget *parent) : QWidget(parent) {
main_layout = new QStackedLayout(this);
ListWidgetSP *list = new ListWidgetSP(this, false);
sunnypilotScreen = new QWidget(this);
QVBoxLayout* vlayout = new QVBoxLayout(sunnypilotScreen);
vlayout->setContentsMargins(50, 20, 50, 20);
// Onroad Screen Off/Brightness
onroadScreenBrightnessControl = new OnroadScreenBrightnessControl(
"OnroadScreenOffControl",
tr("Onroad Screen: Reduced Brightness"),
tr("Turn off device screen or reduce brightness after driving starts. "
"It automatically brightens again when screen is touched or a visible alert is displayed."),
"",
this);
list->addItem(onroadScreenBrightnessControl);
list->addItem(horizontal_line());
// Global Brightness
brightness = new Brightness();
connect(brightness, &OptionControlSP::updateLabels, brightness, &Brightness::refresh);
list->addItem(brightness);
list->addItem(horizontal_line());
// Interactivity Timeout
interactivityTimeout = new OptionControlSP("InteractivityTimeout", tr("Interactivity Timeout"),
tr("Apply a custom timeout for settings UI."
"\nThis is the time after which settings UI closes automatically if user is not interacting with the screen."),
"", {0, 120}, 10, true, nullptr, false);
connect(interactivityTimeout, &OptionControlSP::updateLabels, [=]() {
refresh();
});
list->addItem(interactivityTimeout);
sunnypilotScroller = new ScrollViewSP(list, this);
vlayout->addWidget(sunnypilotScroller);
main_layout->addWidget(sunnypilotScreen);
}
void DisplayPanel::showEvent(QShowEvent *event) {
QWidget::showEvent(event);
refresh();
}
void DisplayPanel::refresh() {
onroadScreenBrightnessControl->refresh();
QString timeoutValue = QString::fromStdString(params.get("InteractivityTimeout"));
if (timeoutValue == "0" || timeoutValue.isEmpty()) {
interactivityTimeout->setLabel("Default");
} else {
interactivityTimeout->setLabel(timeoutValue + "s");
}
}
@@ -1,31 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/brightness.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/display/onroad_screen_brightness.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
class DisplayPanel : public QWidget {
Q_OBJECT
public:
explicit DisplayPanel(QWidget *parent = nullptr);
void showEvent(QShowEvent *event) override;
void refresh();
private:
QStackedLayout* main_layout = nullptr;
QWidget* sunnypilotScreen = nullptr;
ScrollViewSP *sunnypilotScroller = nullptr;
Params params;
OnroadScreenBrightnessControl *onroadScreenBrightnessControl = nullptr;
Brightness *brightness;
OptionControlSP *interactivityTimeout;
};
@@ -1,27 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.h"
BlinkerPauseLateralSettings::BlinkerPauseLateralSettings(const QString &param, const QString &title, const QString &description, const QString &icon, QWidget *parent)
: ExpandableToggleRow(param, title, description, icon, parent) {
pauseLateralSpeed = new OptionControlSP("BlinkerMinLateralControlSpeed", "", "", "", {0, 255}, 5);
connect(pauseLateralSpeed, &OptionControlSP::updateLabels, this, &BlinkerPauseLateralSettings::refresh);
addItem(pauseLateralSpeed);
refresh();
}
void BlinkerPauseLateralSettings::refresh() {
const QString option = QString::fromStdString(params.get("BlinkerMinLateralControlSpeed"));
const bool is_metric = params.getBool("IsMetric");
const QString unit = is_metric ? "km/h" : "mph";
pauseLateralSpeed->setLabel(option + " " + unit);
}
@@ -1,25 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/expandable_row.h"
class BlinkerPauseLateralSettings : public ExpandableToggleRow {
Q_OBJECT
public:
BlinkerPauseLateralSettings(const QString &param, const QString &title, const QString &description, const QString &icon, QWidget *parent = nullptr);
void refresh();
private:
Params params;
OptionControlSP *pauseLateralSpeed;
};
@@ -1,111 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*
* Created by kumar on March 10, 2025
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/lane_change_settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
#include <map>
#include <string>
#include <tuple>
#include <vector>
LaneChangeSettings::LaneChangeSettings(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(tr("Back"));
connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
main_layout->addWidget(back, 0, Qt::AlignLeft);
ListWidgetSP *list = new ListWidgetSP(this, false);
// param, title, desc, icon
std::vector<std::tuple<QString, QString, QString, QString>> toggle_defs{
{
"AutoLaneChangeBsmDelay",
tr("Auto Lane Change: Delay with Blind Spot"),
tr("Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering."),
"../assets/offroad/icon_blank.png",
},
};
// Controls: Auto Lane Change Timer
autoLaneChangeTimer = new AutoLaneChangeTimer();
autoLaneChangeTimer->setUpdateOtherToggles(true);
autoLaneChangeTimer->showDescription();
connect(autoLaneChangeTimer, &OptionControlSP::updateLabels, autoLaneChangeTimer, &AutoLaneChangeTimer::refresh);
connect(autoLaneChangeTimer, &AutoLaneChangeTimer::updateOtherToggles, this, &LaneChangeSettings::updateToggles);
list->addItem(autoLaneChangeTimer);
for (auto &[param, title, desc, icon] : toggle_defs) {
auto toggle = new ParamControlSP(param, title, desc, icon, this);
list->addItem(toggle);
toggles[param.toStdString()] = toggle;
}
main_layout->addWidget(new ScrollViewSP(list, this));
}
void LaneChangeSettings::showEvent(QShowEvent *event) {
updateToggles();
}
void LaneChangeSettings::updateToggles() {
if (!isVisible()) {
return;
}
auto auto_lane_change_bsm_delay_toggle = toggles["AutoLaneChangeBsmDelay"];
auto autoLaneChangeTimer_param = std::atoi(params.get("AutoLaneChangeTimer").c_str());
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<cereal::CarParams>();
if (!CP.getEnableBsm()) {
params.remove("AutoLaneChangeBsmDelay");
}
auto_lane_change_bsm_delay_toggle->setEnabled(CP.getEnableBsm() && (autoLaneChangeTimer_param > 0));
auto_lane_change_bsm_delay_toggle->refresh();
} else {
auto_lane_change_bsm_delay_toggle->setEnabled(false);
}
}
// Auto Lane Change Timer (ALCT)
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.\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}) {
refresh();
}
void AutoLaneChangeTimer::refresh() {
QString option = QString::fromStdString(params.get("AutoLaneChangeTimer"));
const QString second = tr("s");
static const QMap<QString, QString> options = {
{"-1", tr("Off")},
{"0", tr("Nudge")},
{"1", tr("Nudgeless")},
{"2", "0.5 " + second},
{"3", "1 " + second},
{"4", "2 " + second},
{"5", "3 " + second},
};
setLabel(options.value(option, tr("Nudge")));
}
@@ -1,51 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <map>
#include <string>
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
class AutoLaneChangeTimer : public OptionControlSP {
Q_OBJECT
public:
AutoLaneChangeTimer();
void refresh();
signals:
void toggleUpdated();
private:
Params params;
};
class LaneChangeSettings : public QWidget {
Q_OBJECT
public:
explicit LaneChangeSettings(QWidget* parent = nullptr);
void showEvent(QShowEvent *event) override;
signals:
void backPress();
public slots:
void updateToggles();
private:
Params params;
std::map<std::string, ParamControlSP*> toggles;
AutoLaneChangeTimer *autoLaneChangeTimer;
};
@@ -1,106 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
MadsSettings::MadsSettings(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);
// Main cruise
madsMainCruiseToggle = new ParamControl("MadsMainCruiseAllowed", tr("Toggle with Main Cruise"), "", "");
list->addItem(madsMainCruiseToggle);
// Unified Engagement Mode
madsUnifiedEngagementModeToggle = new ParamControl("MadsUnifiedEngagementMode", tr("Unified Engagement Mode (UEM)"), "", "");
list->addItem(madsUnifiedEngagementModeToggle);
// Steering Mode On Brake
madsSteeringMode = new ButtonParamControl("MadsSteeringMode", tr("Steering Mode on Brake Pedal"), "", "", madsSteeringModeTexts(), 500);
QObject::connect(madsSteeringMode, &ButtonParamControl::buttonClicked, [=] {
updateToggles(offroad);
});
list->addItem(madsSteeringMode);
QObject::connect(uiState(), &UIState::offroadTransition, this, &MadsSettings::updateToggles);
main_layout->addWidget(new ScrollViewSP(list, this));
}
void MadsSettings::showEvent(QShowEvent *event) {
updateToggles(offroad);
}
void MadsSettings::updateToggles(bool _offroad) {
auto mads_steering_mode_param = std::atoi(params.get("MadsSteeringMode").c_str());
auto steering_mode = static_cast<MadsSteeringMode>(
std::clamp(mads_steering_mode_param, static_cast<int>(MadsSteeringMode::REMAIN_ACTIVE), static_cast<int>(MadsSteeringMode::DISENGAGE))
);
auto cp_bytes = params.get("CarParamsPersistent");
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
if (!cp_bytes.empty() && !cp_sp_bytes.empty()) {
AlignedBuffer aligned_buf;
AlignedBuffer aligned_buf_sp;
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
capnp::FlatArrayMessageReader cmsg_sp(aligned_buf_sp.align(cp_sp_bytes.data(), cp_sp_bytes.size()));
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
cereal::CarParamsSP::Reader CP_SP = cmsg_sp.getRoot<cereal::CarParamsSP>();
if (madsLimitedSettings(CP, CP_SP)) {
params.remove("MadsMainCruiseAllowed");
params.putBool("MadsUnifiedEngagementMode", true);
params.put("MadsSteeringMode", std::to_string(static_cast<int>(MadsSteeringMode::DISENGAGE)));
madsMainCruiseToggle->setEnabled(false);
madsMainCruiseToggle->setDescription(madsDescriptionBuilder(DEFAULT_TO_OFF, MADS_MAIN_CRUISE_BASE_DESC));
madsMainCruiseToggle->showDescription();
madsUnifiedEngagementModeToggle->setEnabled(false);
madsUnifiedEngagementModeToggle->setDescription(madsDescriptionBuilder(DEFAULT_TO_ON, MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC));
madsUnifiedEngagementModeToggle->showDescription();
madsSteeringModeValues = convertMadsSteeringModeValues({MadsSteeringMode::DISENGAGE});
madsSteeringMode->setDescription(madsDescriptionBuilder(STATUS_DISENGAGE_ONLY, madsSteeringModeDescription(steering_mode)));
} else {
madsMainCruiseToggle->setEnabled(true);
madsMainCruiseToggle->setDescription(MADS_MAIN_CRUISE_BASE_DESC);
madsUnifiedEngagementModeToggle->setEnabled(true);
madsUnifiedEngagementModeToggle->setDescription(MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC);
madsSteeringModeValues = convertMadsSteeringModeValues(getMadsSteeringModeValues());
madsSteeringMode->setDescription(madsSteeringModeDescription(steering_mode));
}
} else {
madsMainCruiseToggle->setEnabled(false);
madsMainCruiseToggle->setDescription(madsDescriptionBuilder(STATUS_CHECK_COMPATIBILITY, MADS_MAIN_CRUISE_BASE_DESC));
madsMainCruiseToggle->showDescription();
madsUnifiedEngagementModeToggle->setEnabled(false);
madsUnifiedEngagementModeToggle->setDescription(madsDescriptionBuilder(STATUS_CHECK_COMPATIBILITY, MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC));
madsUnifiedEngagementModeToggle->showDescription();
madsSteeringModeValues = {};
madsSteeringMode->setDescription(madsDescriptionBuilder(STATUS_CHECK_COMPATIBILITY, madsSteeringModeDescription(steering_mode)));
}
madsSteeringMode->setEnableSelectedButtons(_offroad, madsSteeringModeValues);
madsSteeringMode->showDescription();
offroad = _offroad;
}
@@ -1,124 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
inline bool madsLimitedSettings(const cereal::CarParams::Reader &CP, const cereal::CarParamsSP::Reader &CP_SP) {
if (CP.getBrand() == "rivian") {
return true;
}
if (CP.getBrand() == "tesla") {
return !(CP_SP.getFlags() & 1); // 1 == TeslaFlagsSP.HAS_VEHICLE_BUS
}
return false;
}
enum class MadsSteeringMode {
REMAIN_ACTIVE = 0,
PAUSE = 1,
DISENGAGE = 2,
};
struct MadsSteeringModeOption {
MadsSteeringMode mode;
QString display_text;
QString description;
};
class MadsSettings : public QWidget {
Q_OBJECT
public:
explicit MadsSettings(QWidget *parent = nullptr);
void showEvent(QShowEvent *event) override;
signals:
void backPress();
public slots:
void updateToggles(bool _offroad);
private:
Params params;
bool offroad;
ParamControl *madsMainCruiseToggle;
ParamControl *madsUnifiedEngagementModeToggle;
ButtonParamControl *madsSteeringMode;
std::vector<int> madsSteeringModeValues = {};
const QString MADS_MAIN_CRUISE_BASE_DESC = tr("Note: For vehicles without LFA/LKAS button, disabling this will prevent lateral control engagement.");
const QString MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC = QString("%1<br>"
"<h4>%2</h4>")
.arg(tr("Engage lateral and longitudinal control with cruise control engagement."))
.arg(tr("Note: Once lateral control is engaged via UEM, it will remain engaged until it is manually disabled via the MADS button or car shut off."));
const QString STATUS_CHECK_COMPATIBILITY = tr("Start the vehicle to check vehicle compatibility.");
const QString DEFAULT_TO_OFF = tr("This feature defaults to OFF, and does not allow selection due to vehicle limitations.");
const QString DEFAULT_TO_ON = tr("This feature defaults to ON, and does not allow selection due to vehicle limitations.");
const QString STATUS_DISENGAGE_ONLY = tr("This platform only supports Disengage mode due to vehicle limitations.");
static const std::vector<MadsSteeringModeOption> &madsSteeringModeOptions() {
static const std::vector<MadsSteeringModeOption> options = {
{MadsSteeringMode::REMAIN_ACTIVE, tr("Remain Active"), tr("Remain Active: ALC will remain active when the brake pedal is pressed.")},
{MadsSteeringMode::PAUSE, tr("Pause"), tr("Pause: ALC will pause when the brake pedal is pressed.")},
{MadsSteeringMode::DISENGAGE, tr("Disengage"), tr("Disengage: ALC will disengage when the brake pedal is pressed.")},
};
return options;
}
static std::vector<MadsSteeringMode> getMadsSteeringModeValues() {
std::vector<MadsSteeringMode> values;
for (const auto& option : madsSteeringModeOptions()) {
values.push_back(option.mode);
}
return values;
}
static std::vector<int> convertMadsSteeringModeValues(const std::vector<MadsSteeringMode> &modes) {
std::vector<int> values;
for (const auto& mode : modes) {
values.push_back(static_cast<int>(mode));
}
return values;
}
static std::vector<QString> madsSteeringModeTexts() {
std::vector<QString> texts;
for (const auto& option : madsSteeringModeOptions()) {
texts.push_back(option.display_text);
}
return texts;
}
static QString madsSteeringModeDescription(const MadsSteeringMode mode = MadsSteeringMode::REMAIN_ACTIVE) {
QString base_desc = tr("Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot.");
QString result = base_desc + "<br><br>";
for (const auto& option : madsSteeringModeOptions()) {
QString desc = option.description;
if (option.mode == mode) {
desc = "<font color='white'><b>" + desc + "</b></font>";
}
result += desc + "<br>";
}
return result;
}
static QString madsDescriptionBuilder(const QString &custom_description, const QString &base_description) {
return "<font color='white'><b>" + custom_description + "</b></font><br><br>" + base_description;
}
};
@@ -1,67 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.h"
NeuralNetworkLateralControl::NeuralNetworkLateralControl() :
ParamControl("NeuralNetworkLateralControl", tr("Neural Network Lateral Control (NNLC)"), "", "") {
setConfirmation(true, false);
updateToggle(offroad);
}
void NeuralNetworkLateralControl::updateToggle(bool _offroad) {
QString statusInitText = "<font color='yellow'>" + STATUS_CHECK_COMPATIBILITY + "</font>";
QString notLoadedText = "<font color='yellow'>" + STATUS_NOT_LOADED + "</font>";
QString loadedText = "<font color=#00ff00>" + STATUS_LOADED + "</font>";
bool allowed = true;
auto cp_bytes = params.get("CarParamsPersistent");
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
if (!cp_bytes.empty() && !cp_sp_bytes.empty()) {
AlignedBuffer aligned_buf;
AlignedBuffer aligned_buf_sp;
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
capnp::FlatArrayMessageReader cmsg_sp(aligned_buf_sp.align(cp_sp_bytes.data(), cp_sp_bytes.size()));
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
cereal::CarParamsSP::Reader CP_SP = cmsg_sp.getRoot<cereal::CarParamsSP>();
if (CP.getSteerControlType() == cereal::CarParams::SteerControlType::ANGLE) {
params.remove("NeuralNetworkLateralControl");
setDescription(nnffDescriptionBuilder(STATUS_NOT_AVAILABLE));
allowed = false;
} else {
QString nn_model_name = QString::fromStdString(CP_SP.getNeuralNetworkLateralControl().getModel().getName());
QString nn_fuzzy = CP_SP.getNeuralNetworkLateralControl().getFuzzyFingerprint() ?
STATUS_MATCH_FUZZY : STATUS_MATCH_EXACT;
if (nn_model_name.isEmpty()) {
setDescription(nnffDescriptionBuilder(statusInitText));
} else if (nn_model_name == "MOCK") {
setDescription(nnffDescriptionBuilder(
notLoadedText + "<br>" + buildSupportText(SUPPORT_DONATE_LOGS)
));
} else {
QString statusText = loadedText + " | " + STATUS_MATCH + " = " + nn_fuzzy + " | " + nn_model_name;
QString explanationText = EXPLANATION_MATCH + " " + buildSupportText(SUPPORT_ISSUES);
setDescription(nnffDescriptionBuilder(statusText + "<br><br>" + explanationText));
}
}
} else {
setDescription(nnffDescriptionBuilder(statusInitText));
}
if (getDescription() != getBaseDescription()) {
showDescription();
}
bool enforce_torque_toggle = params.getBool("EnforceTorqueControl");
setEnabled(_offroad && allowed && !enforce_torque_toggle);
refresh();
offroad = _offroad;
}
@@ -1,62 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <map>
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
class NeuralNetworkLateralControl : public ParamControl {
Q_OBJECT
public:
NeuralNetworkLateralControl();
public slots:
void updateToggle(bool _offroad);
private:
Params params;
bool offroad;
// Status messages
const QString STATUS_NOT_AVAILABLE = tr("NNLC is currently not available on this platform.");
const QString STATUS_CHECK_COMPATIBILITY = tr("Start the car to check car compatibility");
const QString STATUS_NOT_LOADED = tr("NNLC Not Loaded");
const QString STATUS_LOADED = tr("NNLC Loaded");
const QString STATUS_MATCH = tr("Match");
const QString STATUS_MATCH_EXACT = tr("Exact");
const QString STATUS_MATCH_FUZZY = tr("Fuzzy");
// Explanations
const QString EXPLANATION_MATCH = tr("Match: \"Exact\" is ideal, but \"Fuzzy\" is fine too.");
const QString EXPLANATION_FEATURE = tr("Formerly known as <b>\"NNFF\"</b>, this replaces the lateral <b>\"torque\"</b> controller, "
"with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy.");
// Support information
const QString SUPPORT_CHANNEL = "<font color='white'><b>#tuning-nnlc</b></font>";
const QString SUPPORT_REACH_OUT = tr("Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server");
const QString SUPPORT_FEEDBACK = tr("with feedback, or to provide log data for your car if your car is currently unsupported:");
const QString SUPPORT_ISSUES = tr("if there are any issues:");
const QString SUPPORT_DONATE_LOGS = tr("and donate logs to get NNLC loaded for your car:");
// Description builders
QString buildSupportText(const QString& context) const {
return SUPPORT_REACH_OUT + " " + context + " " + SUPPORT_CHANNEL;
}
QString nnffDescriptionBuilder(const QString &custom_description) const {
return "<b>" + custom_description + "</b><br><br>" + getBaseDescription();
}
QString getBaseDescription() const {
return EXPLANATION_FEATURE + "<br><br>" + buildSupportText(SUPPORT_FEEDBACK);
}
};
@@ -1,58 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/torque_lateral_control_custom_params.h"
TorqueLateralControlCustomParams::TorqueLateralControlCustomParams(const QString &param, const QString &title, const QString &description, const QString &icon, QWidget *parent)
: ExpandableToggleRow(param, title, description, icon, parent) {
QFrame *frame = new QFrame(this);
QGridLayout *frame_layout = new QGridLayout();
frame->setLayout(frame_layout);
frame_layout->setSpacing(0);
torqueLateralControlParamsOverride = new ParamControl(
"TorqueParamsOverrideEnabled",
tr("Manual Real-Time Tuning"),
tr("Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values."),
"../assets/offroad/icon_blank.png",
this
);
connect(torqueLateralControlParamsOverride, &ParamControl::toggleFlipped, this, &TorqueLateralControlCustomParams::refresh);
torqueParamsOverrideLatAccelFactor = new OptionControlSP("TorqueParamsOverrideLatAccelFactor", tr("Lateral Acceleration Factor"), "", "", {1, 500}, 1, false, nullptr, true, false);
connect(torqueParamsOverrideLatAccelFactor, &OptionControlSP::updateLabels, this, &TorqueLateralControlCustomParams::refresh);
torqueParamsOverrideLatAccelFactor->setFixedWidth(280);
torqueParamsOverrideFriction = new OptionControlSP("TorqueParamsOverrideFriction", tr("Friction"), "", "", {1, 100}, 1, false, nullptr, true, false);
connect(torqueParamsOverrideFriction, &OptionControlSP::updateLabels, this, &TorqueLateralControlCustomParams::refresh);
torqueParamsOverrideFriction->setFixedWidth(280);
frame_layout->addWidget(torqueLateralControlParamsOverride, 0, 0, 1, 2);
QSpacerItem *spacer = new QSpacerItem(20, 40);
frame_layout->addItem(spacer, 1, 0, 1, 2);
frame_layout->addWidget(torqueParamsOverrideLatAccelFactor, 2, 0, Qt::AlignCenter);
frame_layout->addWidget(torqueParamsOverrideFriction, 2, 1, Qt::AlignCenter);
addItem(frame);
refresh();
}
void TorqueLateralControlCustomParams::refresh() {
bool torque_override_param = params.getBool("TorqueParamsOverrideEnabled");
float laf_param = QString::fromStdString(params.get("TorqueParamsOverrideLatAccelFactor")).toFloat();
const QString laf_unit = "m/s²";
float friction_param = QString::fromStdString(params.get("TorqueParamsOverrideFriction")).toFloat();
torqueParamsOverrideLatAccelFactor->setTitle(tr("Lateral Acceleration Factor") + "\n(" + (torque_override_param ? tr("Real-time and Offline") : tr("Offline Only")) + ")");
torqueParamsOverrideFriction->setTitle(tr("Friction") + "\n(" + (torque_override_param ? tr("Real-time and Offline") : tr("Offline Only")) + ")");
torqueParamsOverrideLatAccelFactor->setLabel(QString::number(laf_param, 'f', 2) + " " + laf_unit);
torqueParamsOverrideFriction->setLabel(QString::number(friction_param, 'f', 2));
}
@@ -1,27 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/expandable_row.h"
class TorqueLateralControlCustomParams : public ExpandableToggleRow {
Q_OBJECT
public:
TorqueLateralControlCustomParams(const QString &param, const QString &title, const QString &description, const QString &icon, QWidget *parent = nullptr);
void refresh();
private:
Params params;
ParamControl *torqueLateralControlParamsOverride;
OptionControlSP *torqueParamsOverrideFriction;
OptionControlSP *torqueParamsOverrideLatAccelFactor;
};
@@ -1,81 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/torque_lateral_control_settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
TorqueLateralControlSettings::TorqueLateralControlSettings(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);
// param, title, desc, icon
std::vector<std::tuple<QString, QString, QString, QString>> toggle_defs{
{
"LiveTorqueParamsToggle",
tr("Self-Tune"),
tr("Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default."),
"../assets/offroad/icon_blank.png",
},
{
"LiveTorqueParamsRelaxedToggle",
tr("Less Restrict Settings for Self-Tune (Beta)"),
tr("Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values."),
"../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;
}
torqueLateralControlCustomParams = new TorqueLateralControlCustomParams(
"CustomTorqueParams",
tr("Enable Custom Tuning"),
tr("Enables custom tuning for Torque lateral control. Modifying Lateral Acceleration Factor and Friction below will override the offline values indicated in the YAML files within \"opendbc/car/torque_data\". "
"The values will also be used live when \"Manual Real-Time Tuning\" toggle is enabled."),
"../assets/offroad/icon_blank.png",
this);
list->addItem(torqueLateralControlCustomParams);
QObject::connect(uiState(), &UIState::offroadTransition, this, &TorqueLateralControlSettings::updateToggles);
QObject::connect(toggles["LiveTorqueParamsToggle"], &ParamControlSP::toggleFlipped, [=](bool state) {
if (!state) {
params.remove("LiveTorqueParamsRelaxedToggle");
toggles["LiveTorqueParamsRelaxedToggle"]->refresh();
}
updateToggles(offroad);
});
main_layout->addWidget(new ScrollViewSP(list, this));
}
void TorqueLateralControlSettings::showEvent(QShowEvent *event) {
updateToggles(offroad);
}
void TorqueLateralControlSettings::updateToggles(bool _offroad) {
bool live_toggle = toggles["LiveTorqueParamsToggle"]->isToggled();
toggles["LiveTorqueParamsToggle"]->setEnabled(_offroad);
toggles["LiveTorqueParamsRelaxedToggle"]->setEnabled(_offroad && live_toggle);
torqueLateralControlCustomParams->setEnabled(_offroad);
torqueLateralControlCustomParams->refresh();
offroad = _offroad;
}
@@ -1,36 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/torque_lateral_control_custom_params.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
class TorqueLateralControlSettings : public QWidget {
Q_OBJECT
public:
explicit TorqueLateralControlSettings(QWidget *parent = nullptr);
void showEvent(QShowEvent *event) override;
signals:
void backPress();
public slots:
void updateToggles(bool _offroad);
private:
Params params;
bool offroad;
std::map<std::string, ParamControlSP*> toggles;
TorqueLateralControlCustomParams *torqueLateralControlCustomParams;
};
@@ -1,198 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.h"
#include "common/util.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
LateralPanel::LateralPanel(SettingsWindowSP *parent) : QFrame(parent) {
main_layout = new QStackedLayout(this);
ListWidget *list = new ListWidget(this, false);
sunnypilotScreen = new QWidget(this);
QVBoxLayout* vlayout = new QVBoxLayout(sunnypilotScreen);
vlayout->setContentsMargins(50, 20, 50, 20);
// MADS
madsToggle = new ParamControl(
"Mads",
tr("Modular Assistive Driving System (MADS)"),
tr("Enable the beloved MADS feature. Disable toggle to revert back to stock sunnypilot engagement/disengagement."),
"");
madsToggle->setConfirmation(true, false);
list->addItem(madsToggle);
madsSettingsButton = new PushButtonSP(tr("Customize MADS"));
madsSettingsButton->setObjectName("mads_btn");
connect(madsSettingsButton, &QPushButton::clicked, [=]() {
sunnypilotScroller->setLastScrollPosition();
main_layout->setCurrentWidget(madsWidget);
});
QObject::connect(madsToggle, &ToggleControl::toggleFlipped, madsSettingsButton, &PushButtonSP::setEnabled);
madsWidget = new MadsSettings(this);
connect(madsWidget, &MadsSettings::backPress, [=]() {
sunnypilotScroller->restoreScrollPosition();
main_layout->setCurrentWidget(sunnypilotScreen);
});
list->addItem(madsSettingsButton);
list->addItem(vertical_space());
list->addItem(horizontal_line());
list->addItem(vertical_space());
// Lane Change Settings
laneChangeSettingsButton = new PushButtonSP(tr("Customize Lane Change"));
laneChangeSettingsButton->setObjectName("lane_change_btn");
connect(laneChangeSettingsButton, &QPushButton::clicked, [=]() {
sunnypilotScroller->setLastScrollPosition();
main_layout->setCurrentWidget(laneChangeWidget);
});
laneChangeWidget = new LaneChangeSettings(this);
connect(laneChangeWidget, &LaneChangeSettings::backPress, [=]() {
sunnypilotScroller->restoreScrollPosition();
main_layout->setCurrentWidget(sunnypilotScreen);
});
list->addItem(laneChangeSettingsButton);
list->addItem(vertical_space(0));
list->addItem(horizontal_line());
// Blinker Pause Lateral Control
blinkerPauseLateralSettings = new BlinkerPauseLateralSettings(
"BlinkerPauseLateralControl",
tr("Pause Lateral Control with Blinker"),
tr("Pause lateral control with blinker when traveling below the desired speed selected."),
"",
this);
list->addItem(blinkerPauseLateralSettings);
list->addItem(horizontal_line());
// Customized Torque Lateral Control
torqueLateralControlToggle = new ParamControl(
"EnforceTorqueControl",
tr("Enforce Torque Lateral Control"),
tr("Enable this to enforce sunnypilot to steer with Torque lateral control."),
"");
list->addItem(torqueLateralControlToggle);
torqueLateralControlSettingsButton = new PushButtonSP(tr("Customize Params"));
torqueLateralControlSettingsButton->setObjectName("torque_btn");
connect(torqueLateralControlSettingsButton, &QPushButton::clicked, [=]() {
sunnypilotScroller->setLastScrollPosition();
main_layout->setCurrentWidget(torqueLateralControlWidget);
});
QObject::connect(torqueLateralControlToggle, &ToggleControl::toggleFlipped, [=](bool state) {
torqueLateralControlSettingsButton->setEnabled(state);
nnlcToggle->updateToggle(offroad);
updateToggles(offroad);
});
torqueLateralControlWidget = new TorqueLateralControlSettings(this);
connect(torqueLateralControlWidget, &TorqueLateralControlSettings::backPress, [=]() {
sunnypilotScroller->restoreScrollPosition();
main_layout->setCurrentWidget(sunnypilotScreen);
});
list->addItem(torqueLateralControlSettingsButton);
list->addItem(vertical_space(0));
list->addItem(horizontal_line());
// Neural Network Lateral Control
nnlcToggle = new NeuralNetworkLateralControl();
list->addItem(nnlcToggle);
QObject::connect(nnlcToggle, &ParamControl::toggleFlipped, [=](bool state) {
if (state) {
nnlcToggle->showDescription();
} else {
nnlcToggle->hideDescription();
}
nnlcToggle->updateToggle(offroad);
updateToggles(offroad);
});
QObject::connect(uiState(), &UIState::offroadTransition, this, &LateralPanel::updateToggles);
sunnypilotScroller = new ScrollViewSP(list, this);
vlayout->addWidget(sunnypilotScroller);
main_layout->addWidget(sunnypilotScreen);
main_layout->addWidget(madsWidget);
main_layout->addWidget(laneChangeWidget);
main_layout->addWidget(torqueLateralControlWidget);
setStyleSheet(R"(
#back_btn {
font-size: 50px;
margin: 0px;
padding: 15px;
border-width: 0;
border-radius: 30px;
color: #dddddd;
background-color: #393939;
}
#back_btn:pressed {
background-color: #4a4a4a;
}
)");
main_layout->setCurrentWidget(sunnypilotScreen);
}
void LateralPanel::showEvent(QShowEvent *event) {
nnlcToggle->updateToggle(offroad);
updateToggles(offroad);
}
void LateralPanel::hideEvent(QHideEvent *event) {
main_layout->setCurrentWidget(sunnypilotScreen);
}
void LateralPanel::updateToggles(bool _offroad) {
bool torque_allowed = true;
auto cp_bytes = params.get("CarParamsPersistent");
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
if (!cp_bytes.empty() && !cp_sp_bytes.empty()) {
AlignedBuffer aligned_buf;
AlignedBuffer aligned_buf_sp;
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
capnp::FlatArrayMessageReader cmsg_sp(aligned_buf_sp.align(cp_sp_bytes.data(), cp_sp_bytes.size()));
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
cereal::CarParamsSP::Reader CP_SP = cmsg_sp.getRoot<cereal::CarParamsSP>();
if (madsLimitedSettings(CP, CP_SP)) {
madsToggle->setDescription(descriptionBuilder(STATUS_MADS_SETTINGS_LIMITED_COMPATIBILITY, MADS_BASE_DESC));
} else {
madsToggle->setDescription(descriptionBuilder(STATUS_MADS_SETTINGS_FULL_COMPATIBILITY, MADS_BASE_DESC));
}
if (CP.getSteerControlType() == cereal::CarParams::SteerControlType::ANGLE) {
params.remove("EnforceTorqueControl");
torque_allowed = false;
}
} else {
madsToggle->setDescription(descriptionBuilder(STATUS_MADS_CHECK_COMPATIBILITY, MADS_BASE_DESC));
params.remove("EnforceTorqueControl");
torque_allowed = false;
}
madsToggle->setEnabled(_offroad);
madsSettingsButton->setEnabled(madsToggle->isToggled());
torqueLateralControlToggle->setEnabled(_offroad && torque_allowed && !nnlcToggle->isToggled());
torqueLateralControlSettingsButton->setEnabled(torqueLateralControlToggle->isToggled());
blinkerPauseLateralSettings->refresh();
offroad = _offroad;
}
@@ -1,61 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <map>
#include <string>
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/lane_change_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/torque_lateral_control_settings.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
class LateralPanel : public QFrame {
Q_OBJECT
public:
explicit LateralPanel(SettingsWindowSP *parent = nullptr);
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent* event) override;
public slots:
void updateToggles(bool _offroad);
private:
Params params;
QStackedLayout* main_layout = nullptr;
QWidget* sunnypilotScreen = nullptr;
ScrollViewSP *sunnypilotScroller = nullptr;
bool offroad;
ParamControl *madsToggle;
PushButtonSP *madsSettingsButton;
MadsSettings *madsWidget = nullptr;
PushButtonSP *laneChangeSettingsButton;
LaneChangeSettings *laneChangeWidget = nullptr;
NeuralNetworkLateralControl *nnlcToggle = nullptr;
BlinkerPauseLateralSettings *blinkerPauseLateralSettings = nullptr;
ParamControl *torqueLateralControlToggle;
PushButtonSP *torqueLateralControlSettingsButton;
TorqueLateralControlSettings *torqueLateralControlWidget = nullptr;
const QString MADS_BASE_DESC = tr("Enables independent engagements of Automatic Lane Centering (ALC) and Adaptive Cruise Control (ACC).");
const QString STATUS_MADS_CHECK_COMPATIBILITY = tr("Start the vehicle to check vehicle compatibility.");
const QString STATUS_MADS_SETTINGS_FULL_COMPATIBILITY = tr("This platform supports all MADS settings.");
const QString STATUS_MADS_SETTINGS_LIMITED_COMPATIBILITY = tr("This platform supports limited MADS settings.");
static QString descriptionBuilder(const QString &custom_description, const QString &base_description) {
return "<font color='white'><b>" + custom_description + "</b></font><br><br>" + base_description;
}
};
@@ -1,47 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.h"
CustomAccIncrement::CustomAccIncrement(const QString &param, const QString &title, const QString &desc, const QString &icon, QWidget *parent)
: ExpandableToggleRow(param, title, desc, icon, parent) {
auto *accFrame = new QFrame(this);
auto *accFrameLayout = new QGridLayout();
accFrame->setLayout(accFrameLayout);
accFrameLayout->setSpacing(0);
auto *shortPressControl = new AccIncrementOptionControl("CustomAccShortPressIncrement", {1, 10}, 1);
connect(shortPressControl, &OptionControlSP::updateLabels, shortPressControl, &AccIncrementOptionControl::refresh);
auto *longPressControl = new AccIncrementOptionControl("CustomAccLongPressIncrement", {1, 3}, 1, &customLongValues);
connect(longPressControl, &OptionControlSP::updateLabels, longPressControl, &AccIncrementOptionControl::refresh);
shortPressControl->setFixedWidth(280);
longPressControl->setFixedWidth(280);
accFrameLayout->addWidget(shortPressControl, 0, 0, Qt::AlignLeft);
accFrameLayout->addWidget(longPressControl, 0, 1, Qt::AlignRight);
addItem(accFrame);
}
AccIncrementOptionControl::AccIncrementOptionControl(const QString &param, const MinMaxValue &range, const int per_value_change, const QMap<QString, QString> *valMap)
: OptionControlSP(param, "", "", "", range, per_value_change, true, valMap) {
param_name = param.toStdString();
refresh();
}
void AccIncrementOptionControl::refresh() {
std::string val = params.get(param_name);
std::string label = "<span style='font-size: 45px; font-weight: 450; color: #FFFFFF;'>";
label += param_name == "CustomAccShortPressIncrement" ? "Short Press" : "Long Press";
label += " <br><span style='font-size: 40px; font-weight: 450; color:rgb(174, 255, 195);'>" + val;
label += param_name == "CustomAccShortPressIncrement"
? (val == "1" ? " (Default)" : "")
: (val == "5" ? " (Default)" : "");
label += "</span></span>";
setLabel(QString::fromStdString(label));
}
@@ -1,41 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/expandable_row.h"
class CustomAccIncrement : public ExpandableToggleRow {
Q_OBJECT
public:
CustomAccIncrement(const QString &param, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr);
private:
QMap<QString, QString> customLongValues = {
{"1", "1"},
{"2", "5"}, // Default
{"3", "10"}
};
};
class AccIncrementOptionControl : public OptionControlSP {
Q_OBJECT
public:
AccIncrementOptionControl(const QString &param, const MinMaxValue &range, int per_value_change, const QMap<QString, QString> *valMap = nullptr);
void refresh();
protected:
std::string param_name;
private:
Params params;
};
@@ -1,49 +0,0 @@
/*
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
enum class SpeedLimitOffsetType {
NONE,
FIXED,
PERCENT,
};
inline const QString SpeedLimitOffsetTypeTexts[]{
QT_TRANSLATE_NOOP("SpeedLimitSettings", "None"),
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Fixed"),
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Percent"),
};
enum class SpeedLimitSourcePolicy {
CAR_ONLY,
MAP_ONLY,
CAR_FIRST,
MAP_FIRST,
COMBINED,
};
inline const QString SpeedLimitSourcePolicyTexts[]{
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Car\nOnly"),
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Map\nOnly"),
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Car\nFirst"),
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Map\nFirst"),
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Combined\nData")
};
enum class SpeedLimitMode {
OFF,
INFORMATION,
WARNING,
ASSIST,
};
inline const QString SpeedLimitModeTexts[]{
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Off"),
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Information"),
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Warning"),
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Assist"),
};
@@ -1,56 +0,0 @@
/*
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include <QScrollBar>
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_policy.h"
SpeedLimitPolicy::SpeedLimitPolicy(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0);
main_layout->setSpacing(0);
// Back button
PanelBackButton *back = new PanelBackButton(tr("Back"));
connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
main_layout->addWidget(back, 0, Qt::AlignLeft);
main_layout->addSpacing(10);
ListWidgetSP *list = new ListWidgetSP(this);
std::vector<QString> speed_limit_policy_texts{
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::CAR_ONLY)].toStdString().c_str()),
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::MAP_ONLY)].toStdString().c_str()),
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::CAR_FIRST)].toStdString().c_str()),
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::MAP_FIRST)].toStdString().c_str()),
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::COMBINED)].toStdString().c_str())
};
speed_limit_policy = new ButtonParamControlSP(
"SpeedLimitPolicy",
tr("Speed Limit Source"),
"",
"",
speed_limit_policy_texts,
250);
list->addItem(speed_limit_policy);
connect(speed_limit_policy, &ButtonParamControlSP::buttonClicked, this, &SpeedLimitPolicy::refresh);
speedLimitPolicyScroller = new ScrollViewSP(list, this);
main_layout->addWidget(speedLimitPolicyScroller);
refresh();
};
void SpeedLimitPolicy::refresh() {
SpeedLimitSourcePolicy policy_param = static_cast<SpeedLimitSourcePolicy>(std::atoi(params.get("SpeedLimitPolicy").c_str()));
speed_limit_policy->setDescription(sourceDescription(policy_param));
}
void SpeedLimitPolicy::showEvent(QShowEvent *event) {
speedLimitPolicyScroller->verticalScrollBar()->setValue(0);
refresh();
speed_limit_policy->showDescription();
}
@@ -1,57 +0,0 @@
/*
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/helpers.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
class SpeedLimitPolicy : public QWidget {
Q_OBJECT
public:
explicit SpeedLimitPolicy(QWidget *parent = nullptr);
void refresh();
void showEvent(QShowEvent *event) override;
signals:
void backPress();
private:
Params params;
ButtonParamControlSP *speed_limit_policy;
ScrollViewSP *speedLimitPolicyScroller;
static QString sourceDescription(SpeedLimitSourcePolicy type = SpeedLimitSourcePolicy::CAR_ONLY) {
QString car_only = tr("⦿ Car Only: Use Speed Limit data only from Car");
QString map_only = tr("⦿ Map Only: Use Speed Limit data only from OpenStreetMaps");
QString car_first = tr("⦿ Car First: Use Speed Limit data from Car if available, else use from OpenStreetMaps");
QString map_first = tr("⦿ Map First: Use Speed Limit data from OpenStreetMaps if available, else use from Car");
QString combined = tr("⦿ Combined: Use combined Speed Limit data from Car & OpenStreetMaps");
if (type == SpeedLimitSourcePolicy::CAR_ONLY) {
car_only = "<font color='white'><b>" + car_only + "</b></font>";
} else if (type == SpeedLimitSourcePolicy::MAP_ONLY) {
map_only = "<font color='white'><b>" + map_only + "</b></font>";
} else if (type == SpeedLimitSourcePolicy::CAR_FIRST) {
car_first = "<font color='white'><b>" + car_first + "</b></font>";
} else if (type == SpeedLimitSourcePolicy::MAP_FIRST) {
map_first = "<font color='white'><b>" + map_first + "</b></font>";
} else {
combined = "<font color='white'><b>" + combined + "</b></font>";
}
return QString("%1<br>%2<br>%3<br>%4<br>%5")
.arg(car_only)
.arg(map_only)
.arg(car_first)
.arg(map_first)
.arg(combined);
}
};
@@ -1,180 +0,0 @@
/*
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent) {
subPanelFrame = new QFrame();
QVBoxLayout *subPanelLayout = new QVBoxLayout(subPanelFrame);
subPanelLayout->setContentsMargins(0, 0, 0, 0);
subPanelLayout->setSpacing(0);
// Back button
PanelBackButton *back = new PanelBackButton(tr("Back"));
connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
subPanelLayout->addWidget(back, 0, Qt::AlignLeft);
subPanelLayout->addSpacing(20);
ListWidgetSP *list = new ListWidgetSP(this, false);
speedLimitPolicyScreen = new SpeedLimitPolicy(this);
std::vector<QString> speed_limit_mode_texts{
tr(SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::OFF)].toStdString().c_str()),
tr(SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::INFORMATION)].toStdString().c_str()),
tr(SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::WARNING)].toStdString().c_str()),
tr(SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::ASSIST)].toStdString().c_str())
};
speed_limit_mode_settings = new ButtonParamControlSP(
"SpeedLimitMode",
tr("Speed Limit"),
"",
"",
speed_limit_mode_texts,
380);
list->addItem(speed_limit_mode_settings);
list->addItem(horizontal_line());
list->addItem(vertical_space());
speedLimitSource = new PushButtonSP(tr("Customize Source"));
connect(speedLimitSource, &QPushButton::clicked, [&]() {
speedLimitScroller->setLastScrollPosition();
setCurrentWidget(speedLimitPolicyScreen);
speedLimitPolicyScreen->refresh();
});
connect(speedLimitPolicyScreen, &SpeedLimitPolicy::backPress, [&]() {
speedLimitScroller->restoreScrollPosition();
setCurrentWidget(subPanelFrame);
showEvent(new QShowEvent());
});
speedLimitSource->setFixedWidth(720);
list->addItem(speedLimitSource);
list->addItem(vertical_space(0));
list->addItem(horizontal_line());
QFrame *offsetFrame = new QFrame(this);
QVBoxLayout *offsetLayout = new QVBoxLayout(offsetFrame);
std::vector<QString> speed_limit_offset_texts{
tr(SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::NONE)].toStdString().c_str()),
tr(SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::FIXED)].toStdString().c_str()),
tr(SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::PERCENT)].toStdString().c_str())
};
speed_limit_offset_settings = new ButtonParamControlSP(
"SpeedLimitOffsetType",
tr("Speed Limit Offset"),
"",
"",
speed_limit_offset_texts,
500);
offsetLayout->addWidget(speed_limit_offset_settings);
speed_limit_offset = new OptionControlSP(
"SpeedLimitValueOffset",
"",
"",
"",
{-30, 30}
);
offsetLayout->addWidget(speed_limit_offset);
list->addItem(offsetFrame);
connect(speed_limit_mode_settings, &ButtonParamControlSP::buttonClicked, this, &SpeedLimitSettings::refresh);
connect(speed_limit_offset, &OptionControlSP::updateLabels, this, &SpeedLimitSettings::refresh);
connect(speed_limit_offset_settings, &ButtonParamControlSP::showDescriptionEvent, speed_limit_offset, &OptionControlSP::showDescription);
connect(speed_limit_offset_settings, &ButtonParamControlSP::buttonClicked, this, &SpeedLimitSettings::refresh);
refresh();
speedLimitScroller = new ScrollViewSP(list, this);
subPanelLayout->addWidget(speedLimitScroller);
addWidget(subPanelFrame);
addWidget(speedLimitPolicyScreen);
setCurrentWidget(subPanelFrame);
}
void SpeedLimitSettings::refresh() {
bool is_release = params.getBool("IsReleaseSpBranch");
bool is_metric_param = params.getBool("IsMetric");
SpeedLimitMode speed_limit_mode_param = static_cast<SpeedLimitMode>(std::atoi(params.get("SpeedLimitMode").c_str()));
SpeedLimitOffsetType offset_type_param = static_cast<SpeedLimitOffsetType>(std::atoi(params.get("SpeedLimitOffsetType").c_str()));
QString offsetLabel = QString::fromStdString(params.get("SpeedLimitValueOffset"));
bool sla_available;
auto cp_bytes = params.get("CarParamsPersistent");
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
if (!cp_bytes.empty() && !cp_sp_bytes.empty()) {
AlignedBuffer aligned_buf;
AlignedBuffer aligned_buf_sp;
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
capnp::FlatArrayMessageReader cmsg_sp(aligned_buf_sp.align(cp_sp_bytes.data(), cp_sp_bytes.size()));
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
cereal::CarParamsSP::Reader CP_SP = cmsg_sp.getRoot<cereal::CarParamsSP>();
bool has_longitudinal_control = hasLongitudinalControl(CP);
bool has_icbm = hasIntelligentCruiseButtonManagement(CP_SP);
/*
* Speed Limit Assist is available when:
* - has_longitudinal_control or has_icbm, and
* - is not a release branch or not a disallowed brand, and
* - is not always disallowed
*/
bool sla_disallow_in_release = CP.getBrand() == "tesla" && is_release;
bool sla_always_disallow = CP.getBrand() == "rivian";
sla_available = (has_longitudinal_control || has_icbm) && !sla_disallow_in_release && !sla_always_disallow;
if (!sla_available && speed_limit_mode_param == SpeedLimitMode::ASSIST) {
params.put("SpeedLimitMode", std::to_string(static_cast<int>(SpeedLimitMode::WARNING)));
}
} else {
sla_available = false;
}
speed_limit_mode_settings->setDescription(modeDescription(speed_limit_mode_param));
speed_limit_offset->setDescription(offsetDescription(offset_type_param));
if (offset_type_param == SpeedLimitOffsetType::PERCENT) {
offsetLabel += "%";
} else if (offset_type_param == SpeedLimitOffsetType::FIXED) {
offsetLabel += QString(" %1").arg(is_metric_param ? "km/h" : "mph");
}
if (offset_type_param == SpeedLimitOffsetType::NONE) {
speed_limit_offset->setVisible(false);
} else {
speed_limit_offset->setVisible(true);
speed_limit_offset->setLabel(offsetLabel);
speed_limit_offset->showDescription();
}
if (sla_available) {
speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues(getSpeedLimitModeValues()));
} else {
speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues(
{SpeedLimitMode::OFF, SpeedLimitMode::INFORMATION, SpeedLimitMode::WARNING}));
}
speed_limit_mode_settings->refresh();
speed_limit_mode_settings->showDescription();
speed_limit_offset->showDescription();
}
void SpeedLimitSettings::showEvent(QShowEvent *event) {
refresh();
}
void SpeedLimitSettings::hideEvent(QHideEvent *event) {
setCurrentWidget(subPanelFrame);
}
@@ -1,98 +0,0 @@
/*
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/helpers.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_policy.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
class SpeedLimitSettings : public QStackedWidget {
Q_OBJECT
public:
SpeedLimitSettings(QWidget *parent = nullptr);
void refresh();
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent *event) override;
signals:
void backPress();
private:
Params params;
ScrollViewSP *speedLimitScroller;
QFrame *subPanelFrame;
ButtonParamControlSP *speed_limit_mode_settings;
PushButtonSP *speedLimitSource;
SpeedLimitPolicy *speedLimitPolicyScreen;
ButtonParamControlSP *speed_limit_offset_settings;
OptionControlSP *speed_limit_offset;
bool icbm_available = false;
static QString offsetDescription(SpeedLimitOffsetType type = SpeedLimitOffsetType::NONE) {
QString none_str = tr("⦿ None: No Offset");
QString fixed_str = tr("⦿ Fixed: Adds a fixed offset [Speed Limit + Offset]");
QString percent_str = tr("⦿ Percent: Adds a percent offset [Speed Limit + (Offset % Speed Limit)]");
if (type == SpeedLimitOffsetType::FIXED) {
fixed_str = "<font color='white'><b>" + fixed_str + "</b></font>";
} else if (type == SpeedLimitOffsetType::PERCENT) {
percent_str = "<font color='white'><b>" + percent_str + "</b></font>";
} else {
none_str = "<font color='white'><b>" + none_str + "</b></font>";
}
return QString("%1<br>%2<br>%3")
.arg(none_str)
.arg(fixed_str)
.arg(percent_str);
}
static QString modeDescription(SpeedLimitMode mode = SpeedLimitMode::OFF) {
QString off_str = tr("⦿ Off: Disables the Speed Limit functions.");
QString info_str = tr("⦿ Information: Displays the current road's speed limit.");
QString warning_str = tr("⦿ Warning: Provides a warning when exceeding the current road's speed limit.");
QString assist_str = tr("⦿ Assist: Adjusts the vehicle's cruise speed based on the current road's speed limit when operating the +/- buttons.");
if (mode == SpeedLimitMode::ASSIST) {
assist_str = "<font color='white'><b>" + assist_str + "</b></font>";
} else if (mode == SpeedLimitMode::WARNING) {
warning_str = "<font color='white'><b>" + warning_str + "</b></font>";
} else if (mode == SpeedLimitMode::INFORMATION) {
info_str = "<font color='white'><b>" + info_str + "</b></font>";
} else {
off_str = "<font color='white'><b>" + off_str + "</b></font>";
}
return QString("%1<br>%2<br>%3<br>%4")
.arg(off_str)
.arg(info_str)
.arg(warning_str)
.arg(assist_str);
}
static std::vector<SpeedLimitMode> getSpeedLimitModeValues() {
std::vector<SpeedLimitMode> values;
for (int i = static_cast<int>(SpeedLimitMode::OFF);
i <= static_cast<int>(SpeedLimitMode::ASSIST); ++i) {
values.push_back(static_cast<SpeedLimitMode>(i));
}
return values;
}
static std::vector<int> convertSpeedLimitModeValues(const std::vector<SpeedLimitMode> &modes) {
std::vector<int> values;
for (const auto& mode : modes) {
values.push_back(static_cast<int>(mode));
}
return values;
}
};
@@ -1,198 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
setStyleSheet(R"(
#back_btn {
font-size: 50px;
margin: 0px;
padding: 15px;
border-width: 0;
border-radius: 30px;
color: #dddddd;
background-color: #393939;
}
#back_btn:pressed {
background-color: #4a4a4a;
}
)");
main_layout = new QStackedLayout(this);
ListWidget *list = new ListWidget(this, false);
cruisePanelScreen = new QWidget(this);
QVBoxLayout *vlayout = new QVBoxLayout(cruisePanelScreen);
vlayout->setContentsMargins(0, 0, 0, 0);
cruisePanelScroller = new ScrollViewSP(list, this);
vlayout->addWidget(cruisePanelScroller);
intelligentCruiseButtonManagement = new ParamControlSP(
"IntelligentCruiseButtonManagement",
tr("Intelligent Cruise Button Management (ICBM) (Alpha)"),
"",
"",
this
);
QObject::connect(intelligentCruiseButtonManagement, &ParamControlSP::toggleFlipped, this, [=](bool) {
refresh(offroad);
});
list->addItem(intelligentCruiseButtonManagement);
dynamicExperimentalControl = new ParamControlSP(
"DynamicExperimentalControl",
tr("Dynamic Experimental Control (DEC)"),
tr("Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal."),
"",
this
);
list->addItem(dynamicExperimentalControl);
SmartCruiseControlVision = new ParamControl(
"SmartCruiseControlVision",
tr("Smart Cruise Control - Vision"),
tr("Use vision path predictions to estimate the appropriate speed to drive through turns ahead."),
"");
list->addItem(SmartCruiseControlVision);
SmartCruiseControlMap = new ParamControl(
"SmartCruiseControlMap",
tr("Smart Cruise Control - Map"),
tr("Use map data to estimate the appropriate speed to drive through turns ahead."),
"");
list->addItem(SmartCruiseControlMap);
customAccIncrement = new CustomAccIncrement("CustomAccIncrementsEnabled", tr("Custom ACC Speed Increments"), "", "", this);
list->addItem(customAccIncrement);
QObject::connect(uiState(), &UIState::offroadTransition, this, &LongitudinalPanel::refresh);
speedLimitSettings = new PushButtonSP(tr("Speed Limit"), 750, this);
connect(speedLimitSettings, &QPushButton::clicked, [&]() {
cruisePanelScroller->setLastScrollPosition();
main_layout->setCurrentWidget(speedLimitScreen);
});
list->addItem(speedLimitSettings);
speedLimitScreen = new SpeedLimitSettings(this);
connect(speedLimitScreen, &SpeedLimitSettings::backPress, [=]() {
cruisePanelScroller->restoreScrollPosition();
main_layout->setCurrentWidget(cruisePanelScreen);
});
main_layout->addWidget(cruisePanelScreen);
main_layout->addWidget(speedLimitScreen);
main_layout->setCurrentWidget(cruisePanelScreen);
refresh(offroad);
}
void LongitudinalPanel::showEvent(QShowEvent *event) {
main_layout->setCurrentWidget(cruisePanelScreen);
refresh(offroad);
}
void LongitudinalPanel::hideEvent(QHideEvent *event) {
main_layout->setCurrentWidget(cruisePanelScreen);
}
void LongitudinalPanel::refresh(bool _offroad) {
const QString icbm_description = tr("When enabled, sunnypilot will attempt to manage the built-in cruise control buttons by emulating button presses for limited longitudinal control.");
auto cp_bytes = params.get("CarParamsPersistent");
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
if (!cp_bytes.empty() && !cp_sp_bytes.empty()) {
AlignedBuffer aligned_buf;
AlignedBuffer aligned_buf_sp;
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
capnp::FlatArrayMessageReader cmsg_sp(aligned_buf_sp.align(cp_sp_bytes.data(), cp_sp_bytes.size()));
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
cereal::CarParamsSP::Reader CP_SP = cmsg_sp.getRoot<cereal::CarParamsSP>();
has_longitudinal_control = hasLongitudinalControl(CP);
is_pcm_cruise = CP.getPcmCruise();
has_icbm = hasIntelligentCruiseButtonManagement(CP_SP);
if (CP_SP.getIntelligentCruiseButtonManagementAvailable() && !has_longitudinal_control) {
intelligentCruiseButtonManagement->setEnabled(offroad);
intelligentCruiseButtonManagement->setDescription(icbm_description);
} else {
params.remove("IntelligentCruiseButtonManagement");
intelligentCruiseButtonManagement->setEnabled(false);
const QString icbm_unavaialble = tr("Intelligent Cruise Button Management is currently unavailable on this platform.");
QString long_desc = icbm_unavaialble;
if (has_longitudinal_control) {
if (CP.getAlphaLongitudinalAvailable()) {
long_desc = icbm_unavaialble + " " + tr("Disable the sunnypilot Longitudinal Control (alpha) toggle to allow Intelligent Cruise Button Management.");
} else {
long_desc = icbm_unavaialble + " " + tr("sunnypilot Longitudinal Control is the default longitudinal control for this platform.");
}
}
intelligentCruiseButtonManagement->setDescription("<b>" + long_desc + "</b><br><br>" + icbm_description);
intelligentCruiseButtonManagement->showDescription();
}
if (has_longitudinal_control || has_icbm) {
// enable Custom ACC Increments when long is available and is not PCM cruise
customAccIncrement->setEnabled(((has_longitudinal_control && !is_pcm_cruise) || has_icbm) && offroad);
dynamicExperimentalControl->setEnabled(has_longitudinal_control);
SmartCruiseControlVision->setEnabled(true);
SmartCruiseControlMap->setEnabled(true);
} else {
params.remove("CustomAccIncrementsEnabled");
params.remove("DynamicExperimentalControl");
params.remove("SmartCruiseControlVision");
params.remove("SmartCruiseControlMap");
customAccIncrement->setEnabled(false);
dynamicExperimentalControl->setEnabled(false);
SmartCruiseControlVision->setEnabled(false);
SmartCruiseControlMap->setEnabled(false);
}
intelligentCruiseButtonManagement->refresh();
customAccIncrement->refresh();
dynamicExperimentalControl->refresh();
SmartCruiseControlVision->refresh();
SmartCruiseControlMap->refresh();
} else {
has_longitudinal_control = false;
is_pcm_cruise = false;
has_icbm = false;
intelligentCruiseButtonManagement->setDescription("<b>" + tr("Start the vehicle to check vehicle compatibility.") + "</br><b><b>" + icbm_description);
}
QString accEnabledDescription = tr("Enable custom Short & Long press increments for cruise speed increase/decrease.");
QString accNoLongDescription = tr("This feature can only be used with sunnypilot longitudinal control enabled.");
QString accPcmCruiseDisabledDescription = tr("This feature is not supported on this platform due to vehicle limitations.");
QString onroadOnlyDescription = tr("Start the vehicle to check vehicle compatibility.");
if (offroad) {
customAccIncrement->setDescription(onroadOnlyDescription);
customAccIncrement->showDescription();
} else {
if (has_longitudinal_control || has_icbm) {
if (has_longitudinal_control && is_pcm_cruise) {
customAccIncrement->setDescription(accPcmCruiseDisabledDescription);
customAccIncrement->showDescription();
} else {
customAccIncrement->setDescription(accEnabledDescription);
}
} else {
customAccIncrement->toggleFlipped(false);
customAccIncrement->setDescription(accNoLongDescription);
customAccIncrement->showDescription();
}
}
offroad = _offroad;
}
@@ -1,41 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
class LongitudinalPanel : public QWidget {
Q_OBJECT
public:
explicit LongitudinalPanel(QWidget *parent = nullptr);
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent *event) override;
void refresh(bool _offroad);
private:
Params params;
bool has_longitudinal_control = false;
bool is_pcm_cruise = false;
bool has_icbm = false;
bool offroad = false;
QStackedLayout *main_layout = nullptr;
ScrollViewSP *cruisePanelScroller = nullptr;
QWidget *cruisePanelScreen = nullptr;
CustomAccIncrement *customAccIncrement = nullptr;
ParamControl *SmartCruiseControlVision;
ParamControl *SmartCruiseControlMap;
ParamControl *intelligentCruiseButtonManagement = nullptr;
ParamControl *dynamicExperimentalControl = nullptr;
SpeedLimitSettings *speedLimitScreen;
PushButtonSP *speedLimitSettings;
};
@@ -1,53 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/max_time_offroad.h"
// Map of Max Offroad Time Options (Minutes)
const QMap<QString, QString> MaxTimeOffroad::offroad_time_options = {
{"0", "0"}, // Always On
{"1", "5"},
{"2", "10"},
{"3", "15"},
{"4", "30"},
{"5", "60"},
{"6", "120"},
{"7", "180"},
{"8", "300"},
{"9", "600"},
{"10", "1440"},
{"11", "1800"}
};
MaxTimeOffroad::MaxTimeOffroad() : OptionControlSP(
"MaxTimeOffroad",
tr("Max Time Offroad"),
tr("Device will automatically shutdown after set time once the engine is turned off.<br/>(30h is the default)"),
"../assets/offroad/icon_blank.png",
{0, 11}, 1, true, &offroad_time_options) {
refresh();
}
void MaxTimeOffroad::refresh() {
const int maxOffroadInMinutes = QString::fromStdString(params.get("MaxTimeOffroad")).toInt();
const bool useHours = maxOffroadInMinutes >= 60;
QString label;
if (maxOffroadInMinutes == 0) {
label = tr("Always On");
} else {
const int value = useHours ? maxOffroadInMinutes / 60 : maxOffroadInMinutes;
label = QString("%1%2").arg(value).arg(useHours ? tr("h") : tr("m"));
}
if (maxOffroadInMinutes == 1800) {
label += tr(" (default)");
}
setLabel(label);
}
@@ -1,25 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
class MaxTimeOffroad : public OptionControlSP {
Q_OBJECT
public:
static const QMap<QString, QString> offroad_time_options;
MaxTimeOffroad();
void refresh();
private:
Params params;
};
@@ -1,517 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include <algorithm>
#include <QJsonDocument>
#include <QStyle>
#include <QtConcurrent/QtConcurrent>
#include <QDir>
#include "common/model.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
static const QString progressStyleActive = "QProgressBar {"
" font-size: 40px;"
" font-weight: 200;"
" padding: 1px;"
" border: 3px solid black;"
" border-radius: 10px;"
"}"
"QProgressBar::chunk {"
" background-color: #1e79e8;"
" border-radius: 10px;"
"}";
static const QString progressStyleInactive = progressStyleActive +
"QProgressBar::chunk {"
" background-color: transparent;"
"}";
static const QString progressStyleDone = progressStyleActive +
"QProgressBar {"
" color: #33ab4c;"
"}"
"QProgressBar::chunk {"
" background-color: transparent;"
"}";
static const QString progressStyleError = progressStyleActive +
"QProgressBar {"
" color: red;"
"}"
"QProgressBar::chunk {"
" background-color: transparent;"
"}";
ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(50, 20, 50, 20);
ListWidgetSP *list = new ListWidgetSP(this, false);
ScrollViewSP *scroller = new ScrollViewSP(list, this);
main_layout->addWidget(scroller);
const auto current_model = GetActiveModelName();
currentModelLblBtn = new ButtonControlSP(tr("Current Model"), tr("SELECT"), "", this);
currentModelLblBtn->setValue(current_model);
connect(currentModelLblBtn, &ButtonControlSP::clicked, this, &ModelsPanel::handleCurrentModelLblBtnClicked);
connect(uiState(), &UIState::offroadTransition, [=](bool offroad) {
is_onroad = !offroad;
updateLabels();
});
connect(uiStateSP(), &UIStateSP::uiUpdate, this, &ModelsPanel::updateLabels);
list->addItem(currentModelLblBtn);
refreshAvailableModelsBtn = new ButtonControlSP(tr("Refresh Model List"), tr("REFRESH"), "", this);
connect(refreshAvailableModelsBtn, &ButtonControlSP::clicked, this, [=]() {
params.put("ModelManager_LastSyncTime", "0");
ConfirmationDialog::alert(tr("Fetching Latest Models"), this);
});
list->addItem(refreshAvailableModelsBtn);
clearModelCacheBtn = new ButtonControlSP(tr("Clear Model Cache"), tr("CLEAR"), "", this);
connect(clearModelCacheBtn, &ButtonControlSP::clicked, this, &ModelsPanel::clearModelCache);
list->addItem(clearModelCacheBtn);
// Create progress bars for downloads
supercomboProgressBar = createProgressBar(this);
QString supercomboType = tr("Driving Model");
supercomboFrame = createModelDetailFrame(this, supercomboType, supercomboProgressBar);
list->addItem(supercomboFrame);
navigationProgressBar = createProgressBar(this);
QString navigationType = tr("Navigation Model");
navigationFrame = createModelDetailFrame(this, navigationType, navigationProgressBar);
list->addItem(navigationFrame);
visionProgressBar = createProgressBar(this);
QString visionType = tr("Vision Model");
visionFrame = createModelDetailFrame(this, visionType, visionProgressBar);
list->addItem(visionFrame);
policyProgressBar = createProgressBar(this);
QString policyType = tr("Policy Model");
policyFrame = createModelDetailFrame(this, policyType, policyProgressBar);
list->addItem(policyFrame);
list->addItem(horizontal_line());
// Lane Turn Desire toggle
lane_turn_desire_toggle = new ParamControlSP("LaneTurnDesire", tr("Use Lane Turn Desires"),
"If youre driving at 20 mph (32 km/h) or below and have your blinker on, "
"the car will plan a turn in that direction at the nearest drivable path. "
"This prevents situations (like at red lights) where the car might plan the wrong turn direction.",
"../assets/offroad/icon_shell.png");
list->addItem(lane_turn_desire_toggle);
// Lane Turn Value control
int max_value_mph = 20;
bool is_metric_initial = params.getBool("IsMetric");
const float K = 1.609344f;
int per_value_change_scaled = is_metric_initial ? static_cast<int>(std::round((1.0f / K) * 100.0f)) : 100; // 100 -> 1 mph
lane_turn_value_control = new OptionControlSP("LaneTurnValue", tr("Adjust Lane Turn Speed"),
tr("Set the maximum speed for lane turn desires. Default is 19 %1.").arg(is_metric_initial ? "km/h" : "mph"),
"", {5 * 100, max_value_mph * 100}, per_value_change_scaled, false, nullptr, true, true);
lane_turn_value_control->showDescription();
list->addItem(lane_turn_value_control);
// Show based on toggle
refreshLaneTurnValueControl();
connect(lane_turn_desire_toggle, &ParamControlSP::toggleFlipped, this, &ModelsPanel::refreshLaneTurnValueControl);
connect(lane_turn_value_control, &OptionControlSP::updateLabels, this, &ModelsPanel::refreshLaneTurnValueControl);
// LiveDelay toggle
lagd_toggle_control = new ParamControlSP("LagdToggle", tr("Live Learning Steer Delay"), "", "../assets/offroad/icon_shell.png");
lagd_toggle_control->showDescription();
list->addItem(lagd_toggle_control);
// Software delay control
delay_control = new OptionControlSP("LagdToggleDelay", tr("Adjust Software Delay"),
tr("Adjust the software delay when Live Learning Steer Delay is toggled off."
"\nThe default software delay value is 0.2"),
"", {5, 50}, 1, false, nullptr, true, true);
connect(delay_control, &OptionControlSP::updateLabels, [=]() {
float value = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
delay_control->setLabel(QString::number(value, 'f', 2) + "s");
});
connect(lagd_toggle_control, &ParamControlSP::toggleFlipped, [=](bool state) {
delay_control->setVisible(!state);
});
delay_control->showDescription();
list->addItem(delay_control);
}
QProgressBar* ModelsPanel::createProgressBar(QWidget *parent) {
QProgressBar *progressBar = new QProgressBar(parent);
progressBar->setRange(0, 100);
progressBar->setValue(0);
progressBar->setTextVisible(true);
progressBar->setAlignment(Qt::AlignVCenter);
return progressBar;
}
QFrame* ModelsPanel::createModelDetailFrame(QWidget *parent, QString &typeName, QProgressBar *progressBar) {
QFrame *frame = new QFrame(parent);
QHBoxLayout *layout = new QHBoxLayout(frame);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(50);
layout->addWidget(new QLabel(typeName));
layout->addWidget(progressBar);
frame->setVisible(false);
return frame;
}
void ModelsPanel::refreshLaneTurnValueControl() {
if (!lane_turn_value_control) return;
float stored_mph = QString::fromStdString(params.get("LaneTurnValue")).toFloat();
bool is_metric = params.getBool("IsMetric");
QString unit = is_metric ? "km/h" : "mph";
float display_value = stored_mph;
if (is_metric) {
display_value = stored_mph * 1.609344f;
}
lane_turn_value_control->setLabel(QString::number(static_cast<int>(std::round(display_value))) + " " + unit);
lane_turn_value_control->setVisible(params.getBool("LaneTurnDesire"));
}
/**
* @brief Updates the UI with bundle download progress information
* Reads status from modelManagerSP cereal message and displays status for all models
*/
void ModelsPanel::handleBundleDownloadProgress() {
supercomboFrame->setVisible(false);
visionFrame->setVisible(false);
policyFrame->setVisible(false);
navigationFrame->setVisible(false);
using DS = cereal::ModelManagerSP::DownloadStatus;
if (!model_manager.hasSelectedBundle() && !model_manager.hasActiveBundle()) {
return;
}
const bool showSelectedBundle = model_manager.hasSelectedBundle() && (isDownloading() || model_manager.getSelectedBundle().getStatus() == DS::FAILED);
const auto &bundle = showSelectedBundle ? model_manager.getSelectedBundle() : model_manager.getActiveBundle();
const auto &models = bundle.getModels();
download_status = bundle.getStatus();
const auto download_status_changed = prev_download_status != download_status;
QStringList status;
// Get status for each model type in order
for (const auto &model: models) {
QString modelName = QString::fromStdString(bundle.getDisplayName());
QProgressBar *progressBar = nullptr;
QFrame *modelFrame = nullptr;
switch (model.getType()) {
case cereal::ModelManagerSP::Model::Type::SUPERCOMBO:
progressBar = supercomboProgressBar;
modelFrame = supercomboFrame;
break;
case cereal::ModelManagerSP::Model::Type::NAVIGATION:
progressBar = navigationProgressBar;
modelFrame = navigationFrame;
break;
case cereal::ModelManagerSP::Model::Type::VISION:
progressBar = visionProgressBar;
modelFrame = visionFrame;
break;
case cereal::ModelManagerSP::Model::Type::POLICY:
progressBar = policyProgressBar;
modelFrame = policyFrame;
break;
}
const auto &progress = model.getArtifact().getDownloadProgress();
QString line;
if (progress.getStatus() == cereal::ModelManagerSP::DownloadStatus::DOWNLOADING) {
progressBar->setStyleSheet(progressStyleActive);
progressBar->setValue(progress.getProgress());
progressBar->setFormat(QString(" %1% - %2").arg(static_cast<int>(progress.getProgress())).arg(modelName));
device()->resetInteractiveTimeout();
} else if (progress.getStatus() == cereal::ModelManagerSP::DownloadStatus::DOWNLOADED) {
progressBar->setStyleSheet(progressStyleDone);
progressBar->setFormat(tr(" %1 - %2").arg(modelName, download_status_changed ? tr("downloaded") : tr("ready")));
} else if (progress.getStatus() == cereal::ModelManagerSP::DownloadStatus::CACHED) {
progressBar->setStyleSheet(progressStyleDone);
progressBar->setFormat(tr(" %1 - %2").arg(modelName, download_status_changed ? tr("from cache") : tr("ready")));
} else if (progress.getStatus() == cereal::ModelManagerSP::DownloadStatus::FAILED) {
progressBar->setStyleSheet(progressStyleError);
progressBar->setFormat(tr(" download failed - %1").arg(modelName));
} else {
progressBar->setStyleSheet(progressStyleInactive);
progressBar->setFormat(tr(" pending - %1").arg(modelName));
}
// keep navigation hidden for now to avoid confusion
if (model.getType() != cereal::ModelManagerSP::Model::Type::NAVIGATION) {
modelFrame->setVisible(true);
}
}
prev_download_status = download_status;
}
/**
* @brief Gets the name of the currently selected model bundle
* @return Display name of the selected bundle or default model name
*/
QString ModelsPanel::GetActiveModelName() {
if (model_manager.hasActiveBundle()) {
return QString::fromStdString(model_manager.getActiveBundle().getDisplayName());
}
return DEFAULT_MODEL;
}
/**
* @brief Gets the short name of the currently selected model bundle
* @return Display short name of the selected bundle or default model name
*/
QString ModelsPanel::GetActiveModelInternalName() {
if (model_manager.hasActiveBundle()) {
return QString::fromStdString(model_manager.getActiveBundle().getInternalName());
}
return DEFAULT_MODEL;
}
/**
* @brief Gets the ref of the currently selected model bundle
* @return ref of the selected bundle or default model name
*/
QString ModelsPanel::GetActiveModelRef() {
if (model_manager.hasActiveBundle()) {
return QString::fromStdString(model_manager.getActiveBundle().getRef());
}
return DEFAULT_MODEL;
}
void ModelsPanel::updateModelManagerState() {
const SubMaster &sm = *(uiStateSP()->sm);
model_manager = sm["modelManagerSP"].getModelManagerSP();
}
/**
* @brief Handles the model bundle selection button click
* Displays available bundles, allows selection, and initiates download
*/
void ModelsPanel::handleCurrentModelLblBtnClicked() {
currentModelLblBtn->setEnabled(false);
currentModelLblBtn->setValue(tr("Fetching models..."));
QList<TreeNode> sortedModels;
QSet<QString> modelFolders;
QRegularExpression re("\\(([^)]*)\\)[^(]*$");
for (const auto &bundle : model_manager.getAvailableBundles()) {
auto overrides = bundle.getOverrides();
QString folder;
for (const auto &override : overrides) {
if (override.getKey() == "folder") {
folder = QString::fromStdString(override.getValue().cStr());
}
}
modelFolders.insert(folder);
sortedModels.append(TreeNode{
folder,
QString::fromStdString(bundle.getDisplayName()),
QString::fromStdString(bundle.getRef()),
static_cast<int>(bundle.getIndex())
});
}
std::sort(sortedModels.begin(), sortedModels.end(),
[](const TreeNode &a, const TreeNode &b) {
return a.index > b.index;
});
// Create a list of folder-maxIndex pairs for sorting
QList<QPair<QString, int>> folderMaxIndices;
for (const auto &folder : modelFolders) {
int maxIndex = -1;
for (const auto &model : sortedModels) {
if (model.folder == folder) {
maxIndex = std::max(maxIndex, model.index);
}
}
folderMaxIndices.append(qMakePair(folder, maxIndex));
}
// Sort folders by their highest model index
std::sort(folderMaxIndices.begin(), folderMaxIndices.end(),
[](const QPair<QString, int> &a, const QPair<QString, int> &b) {
return a.second > b.second;
});
// Create the final items list using sorted folders
QList<TreeFolder> items;
for (const auto &folderPair : folderMaxIndices) {
QList<TreeNode> folderModels;
QString folder = folderPair.first;
for (const auto &model : sortedModels) {
if (model.folder == folderPair.first) {
if (model.index == folderPair.second) {
QRegularExpressionMatch match = re.match(model.displayName);
if (match.hasMatch()) {
folder.append(" - (Updated: ").append(match.captured(1)).append(")");
}
}
folderModels.append(model);
}
}
items.append(TreeFolder{folder, folderModels});
}
items.insert(0, TreeFolder{"", {
TreeNode{"", DEFAULT_MODEL, DEFAULT_MODEL, -1}
}});
currentModelLblBtn->setValue(GetActiveModelInternalName());
const QString selectedBundleRef = TreeOptionDialog::getSelection(
tr("Select a Model"), items, GetActiveModelRef(), QString("ModelManager_Favs"), this);
if (selectedBundleRef.isEmpty() || !canContinueOnMeteredDialog()) {
return;
}
// Handle "Stock" selection differently
if (selectedBundleRef == DEFAULT_MODEL) {
params.remove("ModelManager_ActiveBundle");
currentModelLblBtn->setValue(tr("Default"));
showResetParamsDialog();
} else {
// Find selected bundle and initiate download
for (const auto &bundle: model_manager.getAvailableBundles()) {
if (QString::fromStdString(bundle.getRef()) == selectedBundleRef) {
params.put("ModelManager_DownloadIndex", std::to_string(bundle.getIndex()));
if (bundle.getGeneration() != model_manager.getActiveBundle().getGeneration()) {
showResetParamsDialog();
}
break;
}
}
}
updateLabels();
}
/**
* @brief Updates the UI elements based on current state
*/
void ModelsPanel::updateLabels() {
if (!isVisible()) {
return;
}
updateModelManagerState();
handleBundleDownloadProgress();
currentModelLblBtn->setEnabled(!is_onroad && !isDownloading());
currentModelLblBtn->setValue(GetActiveModelInternalName());
// Update lagdToggle description with current value
QString desc = tr("Enable this for the car to learn and adapt its steering response time. "
"Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.");
bool lagdEnabled = params.getBool("LagdToggle");
if (lagdEnabled) {
auto liveDelayBytes = params.get("LiveDelay");
if (!liveDelayBytes.empty()) {
auto LD = loadCerealEvent(params, "LiveDelay");
float lateralDelay = LD->getLiveDelay().getLateralDelay();
desc += QString("<br><br><b><span style=\"color:#e0e0e0\">%1</span></b> <span style=\"color:#e0e0e0\">%2 s</span>")
.arg(tr("Live Steer Delay:")).arg(QString::number(lateralDelay, 'f', 3));
}
} else {
auto carParamsBytes = params.get("CarParamsPersistent");
if (!carParamsBytes.empty()) {
AlignedBuffer aligned_buf_cp;
capnp::FlatArrayMessageReader cmsg(aligned_buf_cp.align(carParamsBytes.data(), carParamsBytes.size()));
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
float steerDelay = CP.getSteerActuatorDelay();
float softwareDelay = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
float totalLag = steerDelay + softwareDelay;
desc += QString("<br><br><span style=\"color:#e0e0e0\">"
"<b>%1</b> %2 s + <b>%3</b> %4 s = <b>%5</b> %6 s</span>")
.arg(tr("Actuator Delay:"), QString::number(steerDelay, 'f', 2),
tr("Software Delay:"), QString::number(softwareDelay, 'f', 2),
tr("Total Delay:"), QString::number(totalLag, 'f', 2));
}
}
lagd_toggle_control->setDescription(desc);
delay_control->setVisible(!params.getBool("LagdToggle"));
if (delay_control->isVisible()) {
float value = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
delay_control->setLabel(QString::number(value, 'f', 2) + "s");
}
// Update lane turn desire label and visibility
refreshLaneTurnValueControl();
clearModelCacheBtn->setValue(QString::number(calculateCacheSize(), 'f', 2) + " MB");
}
/**
* @brief Shows dialog prompting user to reset calibration after model download
*/
void ModelsPanel::showResetParamsDialog() {
const auto confirmMsg = QString("%1<br><br><b>%2</b><br><br><b>%3</b>")
.arg(tr("Model download has started in the background."))
.arg(tr("We STRONGLY suggest you to reset calibration."))
.arg(tr("Would you like to do that now?"));
const auto button_text = tr("Reset Calibration");
QString content("<body><h2 style=\"text-align: center;\">" + tr("Driving Model Selector") + "</h2><br>"
"<p style=\"text-align: center; margin: 0 128px; font-size: 50px;\">" + confirmMsg + "</p></body>");
if (showConfirmationDialog(content, button_text, false)) {
params.remove("CalibrationParams");
params.remove("LiveTorqueParameters");
}
}
void ModelsPanel::clearModelCache() {
QString confirmMsg = tr("This will delete ALL downloaded models from the cache"
"<br/><u>except the currently active model</u>."
"<br/><br/>Are you sure you want to continue?");
QString content("<body><h2 style=\"text-align: center;\">" + tr("Driving Model Selector") + "</h2><br>"
"<p style=\"text-align: center; margin: 0 128px; font-size: 50px;\">" + confirmMsg + "</p></body>");
if (showConfirmationDialog(
content,
tr("Clear Cache"))) {
params.putBool("ModelManager_ClearCache", true);
}
}
double ModelsPanel::calculateCacheSize() {
QFuture<qint64> future_ModelCacheSize = QtConcurrent::run([=]() {
QDir model_dir(QString::fromStdString(Path::model_root()));
QFileInfoList model_files = model_dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
qint64 totalSize = 0;
for (const QFileInfo &model_file : model_files) {
if (model_file.isFile()) {
totalSize += model_file.size();
}
}
return totalSize;
});
return static_cast<double>(future_ModelCacheSize) / (1024.0 * 1024.0);
}
void ModelsPanel::showEvent(QShowEvent *event) {
lagd_toggle_control->showDescription();
if (delay_control->isVisible()) {
delay_control->showDescription();
}
}
@@ -1,88 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <QProgressBar>
#include "selfdrive/ui/sunnypilot/qt/util.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
class ModelsPanel : public QWidget {
Q_OBJECT
public:
explicit ModelsPanel(QWidget *parent = nullptr);
private:
QString GetActiveModelName();
QString GetActiveModelInternalName();
QString GetActiveModelRef();
void updateModelManagerState();
void showEvent(QShowEvent *event) override;
bool isDownloading() const {
if (!model_manager.hasSelectedBundle()) {
return false;
}
const auto &selected_bundle = model_manager.getSelectedBundle();
return selected_bundle.getStatus() == cereal::ModelManagerSP::DownloadStatus::DOWNLOADING;
}
// UI update related methods
void updateLabels();
void handleCurrentModelLblBtnClicked();
void handleBundleDownloadProgress();
void refreshLaneTurnValueControl();
void showResetParamsDialog();
QProgressBar* createProgressBar(QWidget *parent);
QFrame* createModelDetailFrame(QWidget *parent, QString &typeName, QProgressBar *progressBar);
cereal::ModelManagerSP::Reader model_manager;
cereal::ModelManagerSP::DownloadStatus download_status{};
cereal::ModelManagerSP::DownloadStatus prev_download_status{};
void clearModelCache();
double calculateCacheSize();
bool canContinueOnMeteredDialog() {
if (!is_metered) return true;
return showConfirmationDialog(QString(), QString(), is_metered);
}
inline bool showConfirmationDialog(const QString &message = QString(), const QString &confirmButtonText = QString(), const bool show_metered_warning = false) {
return showConfirmationDialog(this, message, confirmButtonText, show_metered_warning);
}
static inline bool showConfirmationDialog(QWidget *parent, const QString &message = QString(), const QString &confirmButtonText = QString(), const bool show_metered_warning = false) {
const QString warning_message = show_metered_warning ? tr("Warning: You are on a metered connection!") : QString();
const QString final_message = QString("%1%2").arg(!message.isEmpty() ? message + "\n" : QString(), warning_message);
const QString final_buttonText = !confirmButtonText.isEmpty() ? confirmButtonText : QString(tr("Continue") + " %1").arg(show_metered_warning ? tr("on Metered") : "");
return ConfirmationDialog(final_message, final_buttonText, tr("Cancel"), true, parent).exec();
}
bool is_metered{};
bool is_wifi{};
bool is_onroad = false;
ButtonControlSP *currentModelLblBtn;
ParamControlSP *lagd_toggle_control;
OptionControlSP *delay_control;
QProgressBar *supercomboProgressBar;
QFrame *supercomboFrame;
QProgressBar *navigationProgressBar;
QFrame *navigationFrame;
QProgressBar *visionProgressBar;
QFrame *visionFrame;
QProgressBar *policyProgressBar;
QFrame *policyFrame;
Params params;
ButtonControlSP *clearModelCacheBtn;
ButtonControlSP *refreshAvailableModelsBtn;
ParamControlSP *lane_turn_desire_toggle;
OptionControlSP *lane_turn_value_control;
};
@@ -1,63 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <algorithm> // for std::sort
#include <deque>
#include <vector>
#include <tuple>
#include <QDir>
#include <QJsonObject>
#include "selfdrive/ui/sunnypilot/qt/common/json_fetcher.h"
static const std::tuple<QString, QString> defaultLocation = std::make_tuple("== None ==", "");
// New class LocationsFetcher that handles web requests and JSON parsing
class LocationsFetcher {
public:
inline std::vector<std::tuple<QString, QString, QString, QString> >
getLocationsFromURL(const QUrl &url, const std::tuple<QString, QString> &customLocation = defaultLocation) const {
// Initialize an empty vector to hold the locations
std::vector<std::tuple<QString, QString, QString, QString> > locations;
JsonFetcher fetcher;
QJsonObject json = fetcher.getJsonFromURL(url.toString());
for (auto it = json.begin(); it != json.end(); ++it) {
QString code = it.key();
QJsonObject obj = it.value().toObject();
QString fullName = obj["full_name"].toString();
locations.push_back(std::make_tuple(fullName, code, QString(), QString()));
}
// Sort locations by full name
std::sort(locations.begin(), locations.end(), [](const auto &lhs, const auto &rhs) {
return std::get<0>(lhs) < std::get<0>(rhs); // Compare full names
});
// Optionally, you can now add defaultName entry at the beginning
locations.insert(locations.begin(), std::tuple_cat(customLocation, std::make_tuple("", "")));
return locations;
}
inline std::vector<std::tuple<QString, QString, QString, QString> >
getLocationsFromURL(const QString &url, const std::tuple<QString, QString> &customLocation = defaultLocation) const {
return getLocationsFromURL(QUrl(url), customLocation);
}
inline std::vector<std::tuple<QString, QString, QString, QString> >
getOsmLocations(const std::tuple<QString, QString> &customLocation = defaultLocation) const {
return getLocationsFromURL( "https://raw.githubusercontent.com/pfeiferj/openpilot-mapd/main/nation_bounding_boxes.json", customLocation);
}
inline std::vector<std::tuple<QString, QString, QString, QString> >
getUsStatesLocations(const std::tuple<QString, QString> &customLocation = defaultLocation) const {
return getLocationsFromURL( "https://raw.githubusercontent.com/pfeiferj/openpilot-mapd/main/us_states_bounding_boxes.json", customLocation);
}
};
@@ -1,158 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm/models_fetcher.h"
#include <QThread>
ModelsFetcher::ModelsFetcher(QObject *parent) : QObject(parent) {
manager = new QNetworkAccessManager(this);
}
QByteArray ModelsFetcher::verifyFileHash(const QString &filePath, const QString &expectedHash, bool &hashMatches) {
hashMatches = false; // Default to false
QByteArray fileData;
if (expectedHash.isEmpty()) {
// If no hash is provided, assume verification isn't required but return the file data
hashMatches = true;
} else {
QFile file(filePath);
if (file.open(QIODevice::ReadOnly)) {
QCryptographicHash hash(QCryptographicHash::Sha256); // Or your chosen algorithm
fileData = file.readAll(); // Read the file data once
hash.addData(fileData);
file.close();
QString currentHash = QString(hash.result().toHex());
hashMatches = (currentHash == expectedHash);
}
}
// Return the file data if hash matches or no hash was provided; empty otherwise
return hashMatches ? fileData : QByteArray();
}
void ModelsFetcher::download(const DownloadInfo &downloadInfo, const QString &filename, const QString &destinationPath) {
QString fullPath = destinationPath + "/" + filename;
QFileInfo fileInfo(fullPath);
bool hashMatches = false;
QByteArray data = verifyFileHash(fullPath, downloadInfo.sha256, hashMatches);
if (fileInfo.exists() && hashMatches) {
// Hash matches or no hash provided, and we have the file data
LOGD("File already downloaded and verified: %s", filename.toStdString().c_str());
emit downloadProgress(100);
emit downloadComplete(data, true); // Use the data returned from verifyFileHash
return; // Exit early
}
// Proceed with download if file does not exist or hash verification failed
QNetworkRequest request(downloadInfo.url);
QNetworkReply *reply = manager->get(request);
connect(reply, &QNetworkReply::downloadProgress, this, &ModelsFetcher::onDownloadProgress);
connect(reply, &QNetworkReply::finished, this, [this, reply, destinationPath, filename, downloadInfo]() {
onFinished(reply, destinationPath, filename, downloadInfo.sha256);
});
}
QString extractFileName(const QString &contentDisposition) {
const QString filenameTag = "filename=";
const int idx = contentDisposition.indexOf(filenameTag);
if (idx < 0) {
return QString();
}
QString filename = contentDisposition.mid(idx + filenameTag.length());
if (filename.startsWith("\"") && filename.endsWith("\"")) {
return filename.mid(1, filename.size() - 2);
}
return filename;
}
void ModelsFetcher::onFinished(QNetworkReply *reply, const QString &destinationPath, const QString &filename, const QString &expectedHash) {
// Handle download error
if (reply->error()) {
return; // Possibly emit a signal or log an error as per your error handling policy
}
const QByteArray data = reply->readAll();
QString finalFilename = filename;
if (finalFilename.isEmpty()) {
finalFilename = extractFileName(reply->header(QNetworkRequest::ContentDispositionHeader).toString());
}
QString finalPath = QDir(destinationPath).filePath(finalFilename);
// Save the downloaded file
QFile file(finalPath);
//ensure if the path exists and if not create it
if (!QDir().mkpath(destinationPath)) {
LOGE("Unable to create directory: %s", destinationPath.toStdString().c_str());
emit downloadFailed(filename);
return; // Stop further processing
}
//Retry the file open and write 3 times with a little delay between each retry
for (int i = 0; i < 3; i++) {
if (file.isOpen()) break;
file.open(QIODevice::WriteOnly);
if (!file.isOpen()) QThread::msleep(100);
}
// If the file is still not open, log an error and emit a failure signal
if (!file.isOpen()) {
LOGE("Unable to open file for writing: %s", finalPath.toStdString().c_str());
emit downloadFailed(filename);
return; // Stop further processing
}
file.write(data);
file.close();
bool hashMatches = false;
verifyFileHash(finalPath, expectedHash, hashMatches);
// Verify the file hash if expectedHash is provided
if (!expectedHash.isEmpty() && !hashMatches) {
LOGE("The downloaded file didn't pass the hash validation!: %s", filename.toStdString().c_str());
// Hash verification failed, handle accordingly
// This could involve deleting the file, logging an error, or emitting a failure signal
QFile::remove(finalPath); // Example action: Remove the invalid file
emit downloadFailed(filename);
return; // Stop further processing
}
emit downloadComplete(data, false); // Emit your success signal
}
void ModelsFetcher::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
const double progress = (bytesReceived * 100.0) / bytesTotal;
emit downloadProgress(progress);
}
std::vector<Model> ModelsFetcher::getModelsFromURL(const QUrl &url) {
std::vector<Model> models;
JsonFetcher fetcher;
QJsonObject json = fetcher.getJsonFromURL(url.toString());
for (auto it = json.begin(); it != json.end(); ++it) {
models.push_back(Model(it.value().toObject()));
}
return models;
}
std::vector<Model> ModelsFetcher::getModelsFromURL(const QString &url) {
return getModelsFromURL(QUrl(url));
}
std::vector<Model> ModelsFetcher::getModelsFromURL() {
return getModelsFromURL("https://docs.sunnypilot.ai/models_v5.json");
}
@@ -1,143 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <algorithm> // for std::sort
#include <cassert>
#include <deque>
#include <vector>
#include <QDir>
#include <QJsonObject>
#include <QTimer>
#include "common/swaglog.h"
#include "common/util.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/qt/util.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/";
struct DownloadInfo {
QString url;
QString sha256;
};
// New class ModelsFetcher with a new function that handles web requests and JSON parsing for the new JSON structure
class Model {
public:
explicit Model(const QJsonObject &json) {
displayName = json["display_name"].toString();
fullName = json["full_name"].toString();
fileName = json["file_name"].toString();
// Parse downloadUri as an object
QJsonObject downloadUriObj = json["download_uri"].toObject();
downloadUri.url = downloadUriObj["url"].toString();
downloadUri.sha256 = downloadUriObj["sha256"].toString();
fullNameNav = json["full_name_nav"].toString();
fileNameNav = json["file_name_nav"].toString();
// Parse downloadUriNav as an object
QJsonObject downloadUriNavObj = json["download_uri_nav"].toObject();
downloadUriNav.url = downloadUriNavObj["url"].toString();
downloadUriNav.sha256 = downloadUriNavObj["sha256"].toString();
fullNameMetadata = json["full_name_metadata"].toString();
fileNameMetadata = json["file_name_metadata"].toString();
// Parse downloadUriMetadata as an object
QJsonObject downloadUriMetadataObj = json["download_uri_metadata"].toObject();
downloadUriMetadata.url = downloadUriMetadataObj["url"].toString();
downloadUriMetadata.sha256 = downloadUriMetadataObj["sha256"].toString();
index = json["index"].toString();
environment = json["environment"].toString();
generation = json["generation"].toString();
}
// Method to convert model back to QJsonObject, if needed
QJsonObject toJson() const {
QJsonObject json;
json["display_name"] = displayName;
json["full_name"] = fullName;
json["file_name"] = fileName;
QJsonObject uriObj;
uriObj["url"] = downloadUri.url;
uriObj["sha256"] = downloadUri.sha256;
json["download_uri"] = uriObj;
QJsonObject uriNavObj;
uriNavObj["url"] = downloadUriNav.url;
uriNavObj["sha256"] = downloadUriNav.sha256;
json["download_uri_nav"] = uriNavObj;
QJsonObject uriMetadataObj;
uriMetadataObj["url"] = downloadUriMetadata.url;
uriMetadataObj["sha256"] = downloadUriMetadata.sha256;
json["download_uri_metadata"] = uriMetadataObj;
json["full_name_nav"] = fullNameNav;
json["file_name_nav"] = fileNameNav;
json["full_name_metadata"] = fullNameMetadata;
json["file_name_metadata"] = fileNameMetadata;
json["index"] = index;
json["environment"] = environment;
json["generation"] = generation;
return json;
}
QString displayName;
QString fullName;
QString fileName;
DownloadInfo downloadUri;
DownloadInfo downloadUriNav;
DownloadInfo downloadUriMetadata;
QString fullNameNav;
QString fileNameNav;
QString fullNameMetadata;
QString fileNameMetadata;
QString index;
QString environment;
QString generation;
};
class ModelsFetcher : public QObject {
Q_OBJECT
public:
explicit ModelsFetcher(QObject *parent = nullptr);
void download(const DownloadInfo &url, const QString &filename = "", const QString &destinationPath = MODELS_PATH);
static std::vector<Model> getModelsFromURL(const QUrl &url);
static std::vector<Model> getModelsFromURL(const QString &url);
static std::vector<Model> getModelsFromURL();
signals:
void downloadProgress(double percentage);
void downloadComplete(const QByteArray &data, bool fromCache = false);
void downloadFailed(const QString &filename);
private:
// static bool verifyFileHash(const QString& filePath, const QString& expectedHash);
static QByteArray verifyFileHash(const QString &filePath, const QString &expectedHash, bool &hashMatches);
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void onFinished(QNetworkReply *reply, const QString &destinationPath, const QString &filename,
const QString &expectedHash);
QNetworkAccessManager *manager;
};
@@ -1,307 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm_panel.h"
#include <tuple>
#include <vector>
#include <string>
#include "common/swaglog.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
OsmPanel::OsmPanel(QWidget *parent) : QFrame(parent) {
main_layout = new QStackedLayout(this);
const auto list = new ListWidgetSP(this, false);
list->addItem(mapdVersion = new LabelControlSP(tr("Mapd Version"), "Loading..."));
list->addItem(setupOsmDeleteMapsButton(parent));
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(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) {
updateLabels();
});
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, QOverload<>::of(&OsmPanel::updateLabels));
timer->start(FAST_REFRESH_INTERVAL); // Time specified in milliseconds.
updateLabels();
osmScreen = new QWidget(this);
auto *vlayout = new QVBoxLayout(osmScreen);
vlayout->setContentsMargins(50, 20, 50, 20);
vlayout->addWidget(new ScrollViewSP(list, this), 1);
main_layout->addWidget(osmScreen);
}
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);
osmDeleteMapsBtn->setEnabled(false);
osmDeleteMapsBtn->setText("");
dir.removeRecursively();
updateMapSize();
osmDeleteMapsBtn->setEnabled(true);
osmDeleteMapsBtn->setText(tr("DELETE"));
});
updateLabels();
}
});
return osmDeleteMapsBtn;
}
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)) {
osm_download_in_progress = true;
params.putBool("OsmDbUpdatesCheck", true);
updateLabels();
}
});
return osmUpdateBtn;
}
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<std::tuple<QString, QString, QString, QString> > locations = getOsmLocations();
osmDownloadBtn->setEnabled(true);
osmDownloadBtn->setValue("");
const QString initTitle = QString::fromStdString(params.get("OsmLocationTitle"));
const QString currentTitle = ((initTitle == "== None ==") || (initTitle.length() == 0)) ? "== None ==" : initTitle;
QStringList locationTitles;
for (auto &loc: locations) {
locationTitles.push_back(std::get<0>(loc));
}
InputDialog d(tr("Search Country"), this, tr("Enter search keywords, or leave blank to list all countries."), false);
d.setMinLength(0);
const int ret = d.exec();
if (ret) {
const QString selection = search(d.text(), locationTitles, tr("Select Country"));
if (!selection.isEmpty()) {
params.put("OsmLocal", "1");
params.put("OsmLocationTitle", selection.toStdString());
for (auto &loc: locations) {
if (std::get<0>(loc) == selection) {
params.put("OsmLocationName", std::get<1>(loc).toStdString());
break;
}
}
if (params.get("OsmLocationName") == "US") {
usStatesBtn->click();
return;
}
if (selection != "== None ==") {
if (showConfirmationDialog(parent)) {
osm_download_in_progress = true;
params.putBool("OsmDbUpdatesCheck", true);
updateLabels();
}
}
}
}
updateLabels();
});
return osmDownloadBtn;
}
ButtonControlSP *OsmPanel::setupUsStatesButton(QWidget *parent) {
usStatesBtn = new ButtonControlSP(tr("State"), tr("SELECT"));
connect(usStatesBtn, &ButtonControlSP::clicked, [=]() {
const std::tuple<QString, QString> allStatesOption = std::make_tuple("All States (~4.8 GB)", "All");
usStatesBtn->setEnabled(false);
usStatesBtn->setValue(tr("Fetching State list..."));
const std::vector<std::tuple<QString, QString, QString, QString> > locations =
getUsStatesLocations(allStatesOption);
usStatesBtn->setEnabled(true);
usStatesBtn->setValue("");
const QString initTitle = QString::fromStdString(params.get("OsmStateTitle"));
const QString currentTitle = ((initTitle == std::get<0>(allStatesOption)) || (initTitle.length() == 0)) ? tr("All") : initTitle;
QStringList locationTitles;
for (auto &loc: locations) {
locationTitles.push_back(std::get<0>(loc));
}
InputDialog d(tr("Search State"), this, tr("Enter search keywords, or leave blank to list all states."), false);
d.setMinLength(0);
const int ret = d.exec();
if (ret) {
const QString selection = search(d.text(), locationTitles, tr("Select State"));
if (!selection.isEmpty()) {
params.put("OsmStateTitle", selection.toStdString());
for (auto &loc: locations) {
if (std::get<0>(loc) == selection) {
params.put("OsmStateName", std::get<1>(loc).toStdString());
break;
}
}
usStatesBtn->setValue(selection);
if (showConfirmationDialog(parent)) {
osm_download_in_progress = true;
params.putBool("OsmDbUpdatesCheck", true);
updateLabels();
}
}
}
updateLabels();
});
usStatesBtn->setVisible(false); // initially hidden
return usStatesBtn;
}
void OsmPanel::showEvent(QShowEvent *event) {
updateLabels(); // For snappier feeling
if (!timer->isActive()) {
timer->start(FAST_REFRESH_INTERVAL);
}
}
void OsmPanel::hideEvent(QHideEvent *event) {
if (timer->isActive()) {
timer->stop();
}
}
void OsmPanel::updateLabels() {
if (!isVisible()) {
return;
}
mapd_version = params.get("MapdVersion");
mapdVersion->setText(mapd_version.c_str());
updateMapSize();
osm_download_locations = mem_params.get("OSMDownloadLocations");
osm_download_in_progress = !osm_download_locations.empty();
timer->setInterval(osm_download_in_progress ? FAST_REFRESH_INTERVAL : SLOW_REFRESH_INTERVAL);
LOGT("Timer Interval %d", timer->interval());
const std::string osmLastDownloadTimeStr = params.get("OsmDownloadedDate");
if (!lastDownloadedTimePoint.has_value() && !osmLastDownloadTimeStr.empty()) {
const double osmLastDownloadTime = std::stod(osmLastDownloadTimeStr);
lastDownloadedTimePoint = std::chrono::system_clock::from_time_t(static_cast<std::time_t>(osmLastDownloadTime));
}
osmDownloadBtn->setEnabled(!osm_download_in_progress);
usStatesBtn->setEnabled(!osm_download_in_progress);
updateDownloadProgress();
const QString locationName = QString::fromStdString(params.get("OsmLocationName"));
const bool isUs = !locationName.isEmpty() && locationName == "US";
usStatesBtn->setVisible(isUs);
if (!locationName.isEmpty()) {
if (!isUs) {
params.remove("OsmStateName");
params.remove("OsmStateTitle");
}
osmUpdateBtn->setVisible(true);
} else {
params.remove("OsmLocal");
params.remove("OsmLocationName");
params.remove("OsmLocationTitle");
params.remove("OsmStateName");
params.remove("OsmStateTitle");
osmUpdateBtn->setVisible(false);
usStatesBtn->setVisible(false);
}
osmDownloadBtn->setValue(QString::fromStdString(params.get("OsmLocationTitle")));
usStatesBtn->setValue(QString::fromStdString(params.get("OsmStateTitle")));
update();
}
void OsmPanel::updateDownloadProgress() {
const auto pending_update_check = params.getBool("OsmDbUpdatesCheck");
const QJsonObject osmDownloadProgress = QJsonDocument::fromJson(params.get("OSMDownloadProgress").c_str()).object();
if (osm_download_in_progress && lastDownloadedTimePoint.has_value()) {
offlineMapsETA->setVisible(true);
offlineMapsElapsed->setVisible(true);
offlineMapsETA->setText(calculateETA(osmDownloadProgress, lastDownloadedTimePoint.value()));
offlineMapsElapsed->setText(calculateElapsedTime(osmDownloadProgress, lastDownloadedTimePoint.value()));
} else {
offlineMapsETA->setVisible(false);
offlineMapsElapsed->setVisible(false);
}
const int total_files = extractIntFromJson(osmDownloadProgress, "total_files");
const int downloaded_files = extractIntFromJson(osmDownloadProgress, "downloaded_files");
download_failed_state = total_files && osm_download_in_progress && !lastDownloadedTimePoint.has_value() && downloaded_files < total_files;
QString updateButtonText = processUpdateStatus(pending_update_check, total_files, downloaded_files, osmDownloadProgress, download_failed_state);
osmUpdateBtn->setValue(updateButtonText);
osmUpdateBtn->setText(osm_download_in_progress && !download_failed_state ? tr("REFRESH") : tr("UPDATE"));
osmDeleteMapsBtn->setValue(formatSize(mapsDirSize));
}
int OsmPanel::extractIntFromJson(const QJsonObject &json, const QString &key) {
return (json.contains(key)) ? json[key].toInt() : 0;
}
QString OsmPanel::processUpdateStatus(bool pending_update, int total_files, int downloaded_files, const QJsonObject &json, bool failed_state) {
if (pending_update && !osm_download_in_progress && !total_files) {
lastDownloadedTimePoint.reset();
return tr("Download starting...");
} else if (failed_state) {
return tr("Error: Invalid download. Retry.");
} else if (osm_download_in_progress && total_files > downloaded_files) {
return formatDownloadStatus(json);
} else if (osm_download_in_progress && downloaded_files >= total_files) {
osm_download_in_progress = false;
lastDownloadedTimePoint.reset();
return tr("Download complete!");
}
if (lastDownloadedTimePoint.has_value()) {
QDateTime dateTime = QDateTime::fromTime_t(std::chrono::system_clock::to_time_t(lastDownloadedTimePoint.value())); //fromMSecsSinceEpoch(duration);
dateTime = dateTime.toLocalTime();
return QString("%1").arg(dateTime.toString("yyyy-MM-dd HH:mm:ss"));
}
return "";
}
void OsmPanel::updateMapSize() {
if (mapSizeFuture.has_value() && mapSizeFuture.value().isFinished()) {
mapsDirSize = mapSizeFuture.value().result();
}
if (!mapSizeFuture.has_value() || !mapSizeFuture.value().isRunning()) {
mapSizeFuture = QtConcurrent::run(getDirSize, MAP_PATH);
}
}
QString OsmPanel::search(const QString &query, const QStringList &list, const QString &prompt_text) {
QStringList lst_results = searchFromList(query, list);
QString selection;
if (lst_results.isEmpty()) {
ConfirmationDialog::alert(tr("No results found for keywords: %1").arg(query), this);
return selection;
}
selection = MultiOptionDialog::getSelection(prompt_text, lst_results, "", this);
return selection;
}
@@ -1,233 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <deque>
#include <chrono>
#include <map>
#include <optional>
#include <string>
#include <tuple>
#include <vector>
#include <QDir>
#include <QFileInfo>
#include <QtConcurrent/QtConcurrent>
#include "selfdrive/ui/qt/network/wifi_manager.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/sunnypilot/ui.h"
#include "system/hardware/hw.h"
constexpr int FAST_REFRESH_INTERVAL = 1000; // ms
constexpr int SLOW_REFRESH_INTERVAL = 5000; // ms
static const QString MAP_PATH = Hardware::PC() ? QDir::homePath() + "/.comma/media/0/osm/offline/" : "/data/media/0/osm/offline/";
class OsmPanel : public QFrame {
Q_OBJECT
public:
explicit OsmPanel(QWidget *parent = nullptr);
private:
QStackedLayout *main_layout = nullptr;
QWidget *osmScreen = nullptr;
Params params;
Params mem_params{Hardware::PC() ? "" : "/dev/shm/params"};
std::map<std::string, ParamControlSP *> toggles;
std::optional<QFuture<quint64> > mapSizeFuture;
const SubMaster &sm = *uiStateSP()->sm;
bool is_onroad = false;
std::string mapd_version;
bool isWifi() const { return sm["deviceState"].getDeviceState().getNetworkType() == cereal::DeviceState::NetworkType::WIFI; }
bool isMetered() const { return sm["deviceState"].getDeviceState().getNetworkMetered(); }
bool osm_download_in_progress = false;
bool download_failed_state = false;
quint64 mapsDirSize = 0;
QLabel *osmUpdateLbl;
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 updateButtonControlSP(ButtonControlSP *btnControl, QWidget *parent, const QString &initTitle, const QString &allStatesOption);
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent *event) override;
void updateLabels();
void updateDownloadProgress();
static int extractIntFromJson(const QJsonObject &json, const QString &key);
QString processUpdateStatus(bool pending_update_check, int total_files, int downloaded_files, const QJsonObject &json, bool failed_state);
QString search(const QString &query, const QStringList &list, const QString &prompt_text);
ConfirmationDialog *confirmationDialog;
LabelControlSP *mapdVersion;
LabelControlSP *offlineMapsStatus;
LabelControlSP *offlineMapsETA;
LabelControlSP *offlineMapsElapsed;
std::optional<std::chrono::system_clock::time_point> lastDownloadedTimePoint;
LocationsFetcher locationsFetcher;
void updateMapSize();
bool showConfirmationDialog(QWidget *parent,
const QString &message = QString(),
const QString &confirmButtonText = QString()) const {
const auto _is_metered = isMetered();
const QString warning_message = _is_metered ? tr("\n\nWarning: You are on a metered connection!") : QString();
QString final_message = message.isEmpty() ? tr("This will start the download process and it might take a while to complete.") : message;
final_message += warning_message; // Append the warning message if the connection is metered
const QString final_buttonText = confirmButtonText.isEmpty() ? (_is_metered ? tr("Continue on Metered") : tr("Start Download")) : confirmButtonText;
return ConfirmationDialog::confirm(final_message, final_buttonText, parent);
}
// Refactored methods
std::vector<std::tuple<QString, QString, QString, QString> > getOsmLocations(const std::tuple<QString, QString> &customLocation = defaultLocation) const {
return locationsFetcher.getOsmLocations(customLocation);
}
std::vector<std::tuple<QString, QString, QString, QString> > getUsStatesLocations(const std::tuple<QString, QString> &customLocation = defaultLocation) const {
return locationsFetcher.getUsStatesLocations(customLocation);
}
static QString formatTime(const long timeInSeconds) {
const long minutes = timeInSeconds / 60;
const long seconds = timeInSeconds % 60;
QString formattedTime;
if (minutes > 0) {
formattedTime = QString::number(minutes) + tr("m ");
}
formattedTime += QString::number(seconds) + tr("s");
return formattedTime;
}
static QString calculateElapsedTime(const QJsonObject &jsonData, const std::chrono::system_clock::time_point &startTime) {
using namespace std::chrono;
if (!jsonData.contains("total_files") || !jsonData.contains("downloaded_files"))
return tr("Calculating...");
const int totalFiles = jsonData["total_files"].toInt();
const int downloadedFiles = jsonData["downloaded_files"].toInt();
if (downloadedFiles >= totalFiles || totalFiles <= 0) return tr("Downloaded");
const long elapsed = duration_cast<seconds>(system_clock::now() - startTime).count();
if (elapsed == 0 || downloadedFiles == 0) return tr("Calculating...");
return formatTime(elapsed);
}
static QString calculateETA(const QJsonObject &jsonData, const std::chrono::system_clock::time_point &startTime) {
using namespace std::chrono;
static steady_clock::time_point lastUpdateTime = steady_clock::now();
static std::deque<double> rateHistory;
constexpr int minDataPoints = 3;
constexpr int historySize = 10;
static QString lastETA = tr("Calculating ETA...");
if (duration_cast<seconds>(steady_clock::now() - lastUpdateTime).count() < 1) {
return lastETA;
}
if (!jsonData.contains("total_files") || !jsonData.contains("downloaded_files"))
return lastETA;
const int totalFiles = jsonData["total_files"].toInt();
const int downloadedFiles = jsonData["downloaded_files"].toInt();
if (totalFiles <= 0 || downloadedFiles >= totalFiles) {
return totalFiles <= 0 ? tr("Ready") : tr("Downloaded");
}
const long elapsed = duration_cast<seconds>(system_clock::now() - startTime).count();
if (elapsed == 0 || downloadedFiles == 0) return lastETA;
const double rate = downloadedFiles / static_cast<double>(elapsed);
if (rateHistory.size() >= historySize) rateHistory.pop_front();
rateHistory.push_back(rate);
if (rateHistory.size() < minDataPoints) return lastETA;
double weightedSum = 0;
for (int i = 0, weight = 1; i < rateHistory.size(); ++i, ++weight) {
weightedSum += rateHistory[i] * weight;
}
const double avgRate = 2 * weightedSum / (rateHistory.size() * (rateHistory.size() + 1));
const long remainingTime = static_cast<long>((totalFiles - downloadedFiles) / avgRate);
if (remainingTime <= 0) return lastETA;
lastETA = tr("Time remaining: ") + formatTime(remainingTime);
lastUpdateTime = steady_clock::now();
return lastETA;
}
static QString formatDownloadStatus(const QJsonObject &json) {
if (!json.contains("total_files") || !json.contains("downloaded_files"))
return "";
const int total_files = json["total_files"].toInt();
const int downloaded_files = json["downloaded_files"].toInt();
if (total_files <= 0) return tr("Ready");
if (downloaded_files >= total_files) return tr("Downloaded");
const int percentage = static_cast<int>(100.0 * downloaded_files / total_files);
return QString::asprintf("%d/%d (%d%%)", downloaded_files, total_files, percentage);
}
QString formatSize(quint64 size) const {
if (size == 0 && (!mapSizeFuture.has_value() || mapSizeFuture.value().isRunning())) {
return tr("Calculating...");
}
constexpr qint64 kb = 1024;
constexpr qint64 mb = 1024 * kb;
constexpr qint64 gb = 1024 * mb;
if (size < gb) {
const double sizeMB = size / static_cast<double>(mb);
return QString::number(sizeMB, 'f', 2) + " MB";
} else {
const double sizeGB = size / static_cast<double>(gb);
return QString::number(sizeGB, 'f', 2) + " GB";
}
}
static quint64 getDirSize(QString dirPath) {
quint64 size = 0;
const QString actualDirPath = dirPath.startsWith("~") ? dirPath.replace(0, 1, QDir::homePath()) : dirPath;
QDirIterator it(actualDirPath, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks, QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
if (it.fileInfo().isFile()) {
size += it.fileInfo().size();
}
}
return size;
}
};
@@ -1,171 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
#include "selfdrive/ui/qt/offroad/firehose.h"
#include "selfdrive/ui/sunnypilot/qt/network/networking.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/display_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/trips_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h"
TogglesPanelSP::TogglesPanelSP(SettingsWindowSP *parent) : TogglesPanel(parent) {
QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &TogglesPanelSP::updateState);
}
void TogglesPanelSP::updateState(const UIStateSP &s) {
TogglesPanel::updateState(s);
}
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, &SettingsWindowSP::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, &SettingsWindowSP::reviewTrainingGuide);
QObject::connect(device, &DevicePanelSP::showDriverView, this, &SettingsWindowSP::showDriverView);
TogglesPanelSP *toggles = new TogglesPanelSP(this);
QObject::connect(this, &SettingsWindowSP::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription);
QObject::connect(this, &SettingsWindowSP::scrollToToggle, toggles, &TogglesPanel::scrollToToggle);
auto networking = new NetworkingSP(this);
QObject::connect(uiState()->prime_state, &PrimeState::changed, networking, &NetworkingSP::setPrimeType);
QList<PanelInfo> panels = {
PanelInfo(" " + tr("Device"), device, "../../sunnypilot/selfdrive/assets/offroad/icon_home.svg"),
PanelInfo(" " + tr("Network"), networking, "../assets/icons/network.png"),
PanelInfo(" " + tr("sunnylink"), new SunnylinkPanel(this), "../assets/icons/wifi_strength_full.svg"),
PanelInfo(" " + tr("Toggles"), toggles, "../../sunnypilot/selfdrive/assets/offroad/icon_toggle.png"),
PanelInfo(" " + tr("Software"), new SoftwarePanelSP(this), "../../sunnypilot/selfdrive/assets/offroad/icon_software.png"),
PanelInfo(" " + tr("Models"), new ModelsPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_models.png"),
PanelInfo(" " + tr("Steering"), new LateralPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_lateral.png"),
PanelInfo(" " + tr("Cruise"), new LongitudinalPanel(this), "../assets/icons/speed_limit.png"),
PanelInfo(" " + tr("Visuals"), new VisualsPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_visuals.png"),
PanelInfo(" " + tr("Display"), new DisplayPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_display.png"),
PanelInfo(" " + tr("OSM"), new OsmPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_map.png"),
PanelInfo(" " + tr("Trips"), new TripsPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_trips.png"),
PanelInfo(" " + tr("Vehicle"), new VehiclePanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_vehicle.png"),
PanelInfo(" " + tr("Firehose"), new FirehosePanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_firehose.svg"),
PanelInfo(" " + tr("Developer"), new DeveloperPanelSP(this), "../assets/icons/shell.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"))) ? 50 : 0; // Network 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;
}
)");
}
@@ -1,43 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <map>
#include <string>
#include "selfdrive/ui/qt/offroad/settings.h"
inline bool isBrandInList(const std::string &brand, const std::vector<std::string> &list) {
return std::find(list.begin(), list.end(), brand) != list.end();
}
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) {}
};
};
class TogglesPanelSP : public TogglesPanel {
Q_OBJECT
public:
explicit TogglesPanelSP(SettingsWindowSP *parent);
private slots:
void updateState(const UIStateSP &s);
};
@@ -1,114 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h"
SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) {
// branch selector
QObject::disconnect(targetBranchBtn, nullptr, nullptr, nullptr);
connect(targetBranchBtn, &ButtonControlSP::clicked, [=]() {
if (Hardware::get_device_type() == cereal::InitData::DeviceType::TICI) {
auto current = params.get("GitBranch");
QStringList allBranches = QString::fromStdString(params.get("UpdaterAvailableBranches")).split(",");
QStringList branches;
for (const QString &b : allBranches) {
if (b.endsWith("-tici")) {
branches.append(b);
}
}
for (QString b : {current.c_str(), "master-tici", "staging-tici", "release-tici"}) {
auto i = branches.indexOf(b);
if (i >= 0) {
branches.removeAt(i);
branches.insert(0, b);
}
}
QString cur = QString::fromStdString(params.get("UpdaterTargetBranch"));
QString selection = MultiOptionDialog::getSelection(tr("Select a branch"), branches, cur, this);
if (!selection.isEmpty()) {
params.put("UpdaterTargetBranch", selection.toStdString());
targetBranchBtn->setValue(QString::fromStdString(params.get("UpdaterTargetBranch")));
checkForUpdates();
}
} else {
InputDialog d(tr("Search Branch"), this, tr("Enter search keywords, or leave blank to list all branches."), false);
d.setMinLength(0);
const int ret = d.exec();
if (ret) {
searchBranches(d.text());
}
}
});
// Disable Updates toggle
disableUpdatesToggle = new ParamControl("DisableUpdates",
tr("Disable Updates"),
tr("When enabled, software updates will be disabled. <b>This requires a reboot to take effect.</b>"),
"../assets/icons/icon_warning.png",
this, true);
disableUpdatesToggle->showDescription();
addItem(disableUpdatesToggle);
connect(disableUpdatesToggle, &ParamControl::toggleFlipped, this, &SoftwarePanelSP::handleDisableUpdatesToggled);
connect(uiState(), &UIState::offroadTransition, this, &SoftwarePanelSP::updateDisableUpdatesToggle);
updateDisableUpdatesToggle(!uiState()->scene.started);
}
/**
* @brief Searches for available branches based on a query string, presents the results in a dialog,
* and updates the target branch if a selection is made.
*
* This function filters the list of branches based on the provided query, and displays the filtered branches in a selection dialog.
* If a branch is selected, the "UpdaterTargetBranch" parameter is updated and a check for updates is triggered.
* If no branches are found matching the query, an alert dialog is displayed.
*
* @param query The search query string.
*/
void SoftwarePanelSP::searchBranches(const QString &query) {
QStringList branches = QString::fromStdString(params.get("UpdaterAvailableBranches")).split(",");
QStringList results = searchFromList(query, branches);
results.sort();
if (results.isEmpty()) {
ConfirmationDialog::alert(tr("No branches found for keywords: %1").arg(query), this);
return;
}
QString selected_branch = MultiOptionDialog::getSelection(tr("Select a branch"), results, "", this);
if (!selected_branch.isEmpty()) {
params.put("UpdaterTargetBranch", selected_branch.toStdString());
targetBranchBtn->setValue(selected_branch);
checkForUpdates();
}
}
void SoftwarePanelSP::handleDisableUpdatesToggled(bool state) {
if (ConfirmationDialog::confirm(tr("%1 updates requires a reboot.<br>Reboot now?")
.arg(state ? "Disabling" : "Enabling"), tr("Reboot"), this)) {
params.putBool("DoReboot", true);
} else {
params.putBool("DisableUpdates", !state);
disableUpdatesToggle->refresh();
}
}
void SoftwarePanelSP::updateDisableUpdatesToggle(bool offroad) {
bool enabled = offroad;
disableUpdatesToggle->setEnabled(enabled);
disableUpdatesToggle->setDescription(enabled
? tr("When enabled, software updates will be disabled.<br><b>This requires a reboot to take effect.</b>")
: tr("Please enable always offroad mode or turn off vehicle to adjust these toggles"));
}
void SoftwarePanelSP::showEvent(QShowEvent *event) {
SoftwarePanel::showEvent(event);
updateDisableUpdatesToggle(!uiState()->scene.started);
disableUpdatesToggle->showDescription();
}
@@ -1,28 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
#include "selfdrive/ui/qt/offroad/settings.h"
class SoftwarePanelSP final : public SoftwarePanel {
Q_OBJECT
public:
explicit SoftwarePanelSP(QWidget *parent = nullptr);
private:
void searchBranches(const QString &query);
ParamControl *disableUpdatesToggle = nullptr;
void handleDisableUpdatesToggled(bool state);
private slots:
void updateDisableUpdatesToggle(bool offroad);
protected:
void showEvent(QShowEvent *event) override;
};
@@ -1,139 +0,0 @@
/**
* Copyright (c) 2025-, sunnypilot contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
using qrcodegen::QrCode;
// --- SunnylinkCommunityQRWidget ---
SunnylinkCommunityQRWidget::SunnylinkCommunityQRWidget(QWidget* parent)
: QWidget(parent) {}
void SunnylinkCommunityQRWidget::showEvent(QShowEvent *event) {
updateQrCode(SUNNYLINK_COMMUNITY_URL);
update();
}
void SunnylinkCommunityQRWidget::updateQrCode(const QString &text) {
QrCode qr = QrCode::encodeText(text.toUtf8().data(), QrCode::Ecc::LOW);
qint32 sz = qr.getSize();
QImage im(sz, sz, QImage::Format_RGB32);
QRgb black = qRgb(0, 0, 0);
QRgb white = qRgb(255, 255, 255);
for (int y = 0; y < sz; y++) {
for (int x = 0; x < sz; x++) {
im.setPixel(x, y, qr.getModule(x, y) ? black : white);
}
}
int final_sz = ((width() / sz) - 1) * sz;
img = QPixmap::fromImage(im.scaled(final_sz, final_sz, Qt::KeepAspectRatio), Qt::MonoOnly);
}
void SunnylinkCommunityQRWidget::paintEvent(QPaintEvent *e) {
QPainter p(this);
p.fillRect(rect(), Qt::white);
if (!img.isNull()) {
QSize s = (size() - img.size()) / 2;
p.drawPixmap(s.width(), s.height(), img);
}
}
// --- SunnylinkCommunityPopup ---
QStringList SunnylinkCommunityPopup::getInstructions() {
QStringList instructions;
instructions << tr("Scan the QR code and join us!");
return instructions;
}
SunnylinkCommunityPopup::SunnylinkCommunityPopup(QWidget* parent)
: DialogBase(parent) {
auto *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
// Solarized Light base3 background
setStyleSheet("SunnylinkCommunityPopup { background-color: #FDF6E3; }");
// Header spanning full width
auto headerWidget = new QWidget(this);
auto headerLayout = new QHBoxLayout(headerWidget);
headerLayout->setContentsMargins(85, 50, 85, 30);
headerLayout->setSpacing(30);
auto close = new QPushButton(QIcon(":/icons/close.svg"), "", this);
close->setIconSize(QSize(80, 80));
close->setStyleSheet("border: none;");
connect(close, &QPushButton::clicked, this, &QDialog::reject);
headerLayout->addWidget(close, 0, Qt::AlignLeft | Qt::AlignVCenter);
const auto title = new QLabel(tr("Join the sunnypilot Community Forum"), this);
// Solarized base02 for text
title->setStyleSheet("font-size: 65px; color: #073642;");
title->setWordWrap(false);
title->setAlignment(Qt::AlignCenter);
headerLayout->addWidget(title, 1);
// Spacer to balance the close button on the right
auto spacer = new QWidget(this);
spacer->setFixedSize(80, 80);
headerLayout->addWidget(spacer, 0);
mainLayout->addWidget(headerWidget);
// Two-column content layout
auto contentLayout = new QHBoxLayout();
contentLayout->setContentsMargins(0, 0, 0, 0);
contentLayout->setSpacing(0);
mainLayout->addLayout(contentLayout, 66);
// Left side: description
auto leftLayout = new QVBoxLayout();
leftLayout->setContentsMargins(85, 40, 50, 70);
leftLayout->setSpacing(35);
contentLayout->addLayout(leftLayout, 40);
// Hype / intro paragraph
const auto desc = new QLabel(tr(
"We're excited to announce our <b>sunnypilot Community Forum</b><br><br>"
"Over the years, Discord just hasn't scaled well for our growing community.<br>"
"It's noisy, unsearchable, and great discussions disappear too easily.<br>"
"Our new community forum aims to fix that by making it easier to <b>find answers, share ideas, track feedback, report bugs, help newcomers</b> and more!<br><br>"
"<b>Here's what's waiting for you:</b><br>"
"• Fully <b>indexable</b> and discoverable through search engines 🔎<br>"
"• <b>AI-powered</b>🤖 topic and chat summaries, spam detection, and more<br>"
"• A <b>trust-level system</b>✅ that rewards meaningful contributions<br>"
"• Designed to work <b>on your own time</b>.🧘<br><br>"
"Scan the QR code on the right and join the discussion!"
), this);
// Solarized base01 for body text
desc->setStyleSheet("font-size: 40px; color: #586E75;");
desc->setWordWrap(true);
leftLayout->addWidget(desc);
leftLayout->addStretch();
// Right side: QR code and instructions
auto rightLayout = new QVBoxLayout();
rightLayout->setContentsMargins(50, 40, 85, 70);
rightLayout->setSpacing(40);
contentLayout->addLayout(rightLayout, 1);
// QR code (smaller, fixed size)
auto *qr = new SunnylinkCommunityQRWidget(this);
qr->setFixedSize(500, 500);
rightLayout->addStretch();
rightLayout->addWidget(qr, 0, Qt::AlignCenter);
rightLayout->addStretch();
}
@@ -1,40 +0,0 @@
/**
* Copyright (c) 2025-, sunnypilot contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <QrCode.hpp>
#include <QtCore/qjsonobject.h>
#include "common/util.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
const QString SUNNYLINK_COMMUNITY_URL = "https://community.sunnypilot.ai/sp-qr";
class SunnylinkCommunityQRWidget : public QWidget {
Q_OBJECT
public:
explicit SunnylinkCommunityQRWidget(QWidget* parent = nullptr);
void paintEvent(QPaintEvent*) override;
private:
QPixmap img;
void updateQrCode(const QString &text);
void showEvent(QShowEvent *event) override;
};
// Popup widget
class SunnylinkCommunityPopup : public DialogBase {
Q_OBJECT
public:
explicit SunnylinkCommunityPopup(QWidget* parent = nullptr);
private:
static QStringList getInstructions();
};
@@ -1,142 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/api.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h"
// Sponsor Upsell
using qrcodegen::QrCode;
SunnylinkSponsorQRWidget::SunnylinkSponsorQRWidget(bool sponsor_pair, QWidget* parent) : QWidget(parent), sponsor_pair(sponsor_pair) {
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &SunnylinkSponsorQRWidget::refresh);
}
void SunnylinkSponsorQRWidget::showEvent(QShowEvent *event) {
refresh();
timer->start(5 * 60 * 1000);
device()->setOffroadBrightness(100);
}
void SunnylinkSponsorQRWidget::hideEvent(QHideEvent *event) {
timer->stop();
device()->setOffroadBrightness(BACKLIGHT_OFFROAD);
}
void SunnylinkSponsorQRWidget::refresh() {
QString qrString;
if (sponsor_pair) {
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;
} else {
qrString = "https://github.com/sponsors/sunnyhaibin";
}
this->updateQrCode(qrString);
update();
}
void SunnylinkSponsorQRWidget::updateQrCode(const QString &text) {
QrCode qr = QrCode::encodeText(text.toUtf8().data(), QrCode::Ecc::LOW);
qint32 sz = qr.getSize();
QImage im(sz, sz, QImage::Format_RGB32);
QRgb black = qRgb(0, 0, 0);
QRgb white = qRgb(255, 255, 255);
for (int y = 0; y < sz; y++) {
for (int x = 0; x < sz; x++) {
im.setPixel(x, y, qr.getModule(x, y) ? black : white);
}
}
// Integer division to prevent anti-aliasing
int final_sz = ((width() / sz) - 1) * sz;
img = QPixmap::fromImage(im.scaled(final_sz, final_sz, Qt::KeepAspectRatio), Qt::MonoOnly);
}
void SunnylinkSponsorQRWidget::paintEvent(QPaintEvent *e) {
QPainter p(this);
p.fillRect(rect(), Qt::white);
QSize s = (size() - img.size()) / 2;
p.drawPixmap(s.width(), s.height(), img);
}
QStringList SunnylinkSponsorPopup::getInstructions(bool sponsor_pair) {
QStringList instructions;
if (sponsor_pair) {
instructions << tr("Scan the QR code to login to your GitHub account")
<< tr("Follow the prompts to complete the pairing process")
<< tr("Re-enter the \"sunnylink\" panel to verify sponsorship status")
<< tr("If sponsorship status was not updated, please contact a moderator on our forum at https://community.sunnypilot.ai");
} else {
instructions << tr("Scan the QR code to visit sunnyhaibin's GitHub Sponsors page")
<< tr("Choose your sponsorship tier and confirm your support")
<< tr("Join our Community Forum at https://community.sunnypilot.ai and reach out to a moderator if you have issues");
}
return instructions;
}
SunnylinkSponsorPopup::SunnylinkSponsorPopup(bool sponsor_pair, QWidget *parent) : DialogBase(parent), sponsor_pair(sponsor_pair) {
auto *hlayout = new QHBoxLayout(this);
auto sunnylink_client = new SunnylinkClient(this);
hlayout->setContentsMargins(0, 0, 0, 0);
hlayout->setSpacing(0);
setStyleSheet("SunnylinkSponsorPopup { background-color: #E0E0E0; }");
// text
auto vlayout = new QVBoxLayout();
vlayout->setContentsMargins(85, 70, 50, 70);
vlayout->setSpacing(50);
hlayout->addLayout(vlayout, 1);
{
auto close = new QPushButton(QIcon(":/icons/close.svg"), "", this);
close->setIconSize(QSize(80, 80));
close->setStyleSheet("border: none;");
vlayout->addWidget(close, 0, Qt::AlignLeft);
connect(close, &QPushButton::clicked, this, [=] {
sunnylink_client->role_service->load();
sunnylink_client->user_service->load();
QDialog::reject();
});
//vlayout->addSpacing(30);
const QString titleText = sponsor_pair ? tr("Pair your GitHub account") : tr("Early Access: Become a sunnypilot Sponsor");
const auto title = new QLabel(titleText, this);
title->setStyleSheet("font-size: 75px; color: black;");
title->setWordWrap(true);
vlayout->addWidget(title);
QStringList instructions = getInstructions(sponsor_pair);
QString instructionsHtml = "<ol type='1' style='margin-left: 15px;'>";
for (const auto & instruction : instructions) {
instructionsHtml += QString("<li style='margin-bottom: 50px;'>%1</li>").arg(instruction);
}
instructionsHtml += "</ol>";
const auto instructionsLabel = new QLabel(instructionsHtml, this);
instructionsLabel->setStyleSheet("font-size: 47px; font-weight: bold; color: black;");
instructionsLabel->setWordWrap(true);
vlayout->addWidget(instructionsLabel);
vlayout->addStretch();
}
// QR code
auto *qr = new SunnylinkSponsorQRWidget(sponsor_pair, this);
hlayout->addWidget(qr, 1);
}
@@ -1,48 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <QrCode.hpp>
#include <QtCore/qjsonobject.h>
#include "common/util.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
const QString SUNNYLINK_BASE_URL = util::getenv("SUNNYLINK_API_HOST", "https://stg.api.sunnypilot.ai").c_str();
class SunnylinkSponsorQRWidget : public QWidget {
Q_OBJECT
public:
explicit SunnylinkSponsorQRWidget(bool sponsor_pair = false, QWidget* parent = 0);
void paintEvent(QPaintEvent*) override;
private:
QPixmap img;
QTimer *timer;
void updateQrCode(const QString &text);
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent *event) override;
bool sponsor_pair = false;
private slots:
void refresh();
};
// sponsor popup widget
class SunnylinkSponsorPopup : public DialogBase {
Q_OBJECT
public:
explicit SunnylinkSponsorPopup(bool sponsor_pair = false, QWidget* parent = 0);
private:
static QStringList getInstructions(bool sponsor_pair);
bool sponsor_pair = false;
};
@@ -1,305 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.h"
#include "common/watchdog.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#include <QtConcurrent>
SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) {
main_layout = new QStackedLayout(this);
sunnylink_client = new SunnylinkClient(this);
param_watcher = new ParamWatcher(this);
param_watcher->addParam("SunnylinkEnabled");
connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString &param_name, const QString &param_value) {
paramsRefresh(param_name, param_value);
});
is_sunnylink_enabled = params.getBool("SunnylinkEnabled");
connect(uiStateSP(), &UIStateSP::sunnylinkRolesChanged, this, &SunnylinkPanel::updatePanel);
connect(uiStateSP(), &UIStateSP::sunnylinkDeviceUsersChanged, this, &SunnylinkPanel::updatePanel);
connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) {
is_onroad = !offroad;
updatePanel();
});
sunnylinkScreen = new QWidget(this);
auto vlayout = new QVBoxLayout(sunnylinkScreen);
vlayout->setContentsMargins(50, 20, 50, 20);
auto *list = new ListWidget(this, false);
QVBoxLayout *titleLayout = new QVBoxLayout;
QLabel *title = new QLabel(tr("🚀 sunnylink 🚀"));
title->setStyleSheet("font-size: 90px; font-weight: 500; font-family: 'Noto Color Emoji';");
titleLayout->addWidget(title, 0, Qt::AlignCenter);
QLabel *sunnylinkDesc = new QLabel("<div align='center'><font color='green'>"+
tr("For secure backup, restore, and remote configuration")+ "</font></div>");
QLabel *sponsorMsg = new QLabel("<div align='center'><font color='orange'>"+
tr("Sponsorship isn't required for basic backup/restore") + "<br>" +
tr("Click the sponsor button for more details")+ "</font></div>");
sunnylinkDesc->setStyleSheet("font-size: 40px; font-weight: 100; font-family: 'Noto';");
sponsorMsg->setStyleSheet("font-size: 35px; font-weight: 100; font-family: 'Noto';");
titleLayout->addWidget(sunnylinkDesc, 0, Qt::AlignCenter);
titleLayout->addWidget(sponsorMsg, 0, Qt::AlignCenter);
list->addItem(titleLayout);
QString sunnylinkEnabledBtnDesc = tr("This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that.");
sunnylinkEnabledBtn = new ParamControl(
"SunnylinkEnabled",
tr("Enable sunnylink"),
sunnylinkEnabledBtnDesc,
"");
list->addItem(sunnylinkEnabledBtn);
status_popup = new SunnylinkSponsorPopup(false, this);
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."));
list->addItem(sponsorBtn);
connect(sponsorBtn, &ButtonControlSP::clicked, [=]() {
status_popup->exec();
});
list->addItem(horizontal_line());
pair_popup = new SunnylinkSponsorPopup(true, this);
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.") + "🌟");
list->addItem(pairSponsorBtn);
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 {
pair_popup->exec();
}
});
list->addItem(horizontal_line());
QString sunnylinkUploaderDesc = tr("Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)");
sunnylinkUploaderEnabledBtn = new ParamControlSP(
"EnableSunnylinkUploader",
tr("Enable sunnylink uploader (infrastructure test)"),
sunnylinkUploaderDesc,
"", nullptr, true);
list->addItem(sunnylinkUploaderEnabledBtn);
connect(sunnylinkEnabledBtn, &ParamControl::showDescriptionEvent, [=]() {
// resets the description to the default one for the Easter egg
sunnylinkEnabledBtn->setDescription(sunnylinkEnabledBtnDesc);
});
connect(sunnylinkEnabledBtn, &ParamControl::toggleFlipped, [=](bool enabled) {
QString description;
if (enabled) {
description = "<font color='SeaGreen'>"+ tr("🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀")+ "</font>";
} else {
description = "<font color='orange'>"+ tr("👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉.")+ "</font>";
}
sunnylinkEnabledBtn->showDescription();
sunnylinkEnabledBtn->setDescription(description);
updatePanel();
});
// Backup Settings
backupSettings = new PushButtonSP(tr("Backup Settings"), 750, this);
backupSettings->setObjectName("backup_btn");
connect(backupSettings, &QPushButton::clicked, [=]() {
backupSettings->setEnabled(false);
if (ConfirmationDialog::confirm(tr("Are you sure you want to backup sunnypilot settings?"), tr("Back Up"), this)) {
params.putBool("BackupManager_CreateBackup", true);
backup_request_pending = true;
}
});
// Restore Settings
restoreSettings = new PushButtonSP(tr("Restore Settings"), 750, this);
restoreSettings->setObjectName("restore_btn");
connect(restoreSettings, &QPushButton::clicked, [=]() {
restoreSettings->setEnabled(false);
if (ConfirmationDialog::confirm(tr("Are you sure you want to restore the last backed up sunnypilot settings?"), tr("Restore"), this)) {
params.put("BackupManager_RestoreVersion", "latest");
restore_request_pending = true;
}
});
// Settings Restore and Settings Backup in the same horizontal space
auto settings_layout = new QHBoxLayout;
settings_layout->setContentsMargins(0, 0, 0, 30);
settings_layout->addWidget(backupSettings, 0, Qt::AlignLeft);
settings_layout->addSpacing(10);
settings_layout->addWidget(restoreSettings, 0, Qt::AlignRight);
list->addItem(settings_layout);
QObject::connect(uiState(), &UIState::offroadTransition, this, &SunnylinkPanel::updatePanel);
QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &SunnylinkPanel::updatePanel);
sunnylinkScroller = new ScrollViewSP(list, this);
vlayout->addWidget(sunnylinkScroller);
main_layout->addWidget(sunnylinkScreen);
if (is_sunnylink_enabled) {
startSunnylink();
}
}
void SunnylinkPanel::updateBackupManagerState() {
const SubMaster &sm = *(uiStateSP()->sm);
backup_manager = sm["backupManagerSP"].getBackupManagerSP();
}
void SunnylinkPanel::handleBackupProgress() {
auto backup_status = backup_manager.getBackupStatus();
auto restore_status = backup_manager.getRestoreStatus();
auto backup_progress = backup_manager.getBackupProgress();
auto restore_progress = backup_manager.getRestoreProgress();
switch (backup_status) {
case cereal::BackupManagerSP::Status::IN_PROGRESS:
backup_request_pending = false;
backup_request_started = true;
backupSettings->setEnabled(false);
backupSettings->setText(QString(tr("Backup in progress %1%").arg(backup_progress)));
break;
case cereal::BackupManagerSP::Status::FAILED:
backup_request_pending = false;
backup_request_started = false;
backupSettings->setEnabled(!is_onroad);
backupSettings->setText(tr("Backup Failed"));
break;
case cereal::BackupManagerSP::Status::COMPLETED:
backup_request_pending = false;
break;
default:
if (!backup_request_pending && backup_request_started) {
backup_request_started = false;
ConfirmationDialog::alert(tr("Settings backup completed."), this);
} else {
backupSettings->setEnabled(!is_onroad && !backup_request_pending && is_sunnylink_enabled);
}
backupSettings->setText(tr("Backup Settings"));
break;
}
switch (restore_status) {
case cereal::BackupManagerSP::Status::IN_PROGRESS:
restore_request_pending = false;
restore_request_started = true;
restoreSettings->setEnabled(false);
restoreSettings->setText(QString(tr("Restore in progress %1%").arg(restore_progress)));
break;
case cereal::BackupManagerSP::Status::FAILED:
restore_request_pending = false;
restore_request_started = false;
restoreSettings->setEnabled(!is_onroad);
restoreSettings->setText(tr("Restore Failed"));
ConfirmationDialog::alert(tr("Unable to restore the settings, try again later."), this);
break;
case cereal::BackupManagerSP::Status::COMPLETED:
restore_request_pending = false;
break;
default:
if (!restore_request_pending && restore_request_started) {
restore_request_started = false;
if (ConfirmationDialog::alert(tr("Settings restored. Confirm to restart the interface."), this)) {
qApp->exit(18);
watchdog_kick(0);
}
} else {
restoreSettings->setEnabled(!is_onroad && !restore_request_pending && is_sunnylink_enabled);
}
restoreSettings->setText(tr("Restore Settings"));
break;
}
}
void SunnylinkPanel::paramsRefresh(const QString &param_name, const QString &param_value) {
// We do it on paramsRefresh because the toggleEvent happens before the value is updated
if (param_name == "SunnylinkEnabled" && param_value == "1") {
startSunnylink();
} else if (param_name == "SunnylinkEnabled" && param_value == "0") {
stopSunnylink();
}
updatePanel();
}
void SunnylinkPanel::startSunnylink() const {
if (!sunnylink_client->role_service->isCurrentyPolling()) {
sunnylink_client->role_service->startPolling();
} else {
sunnylink_client->role_service->load();
}
if (!sunnylink_client->user_service->isCurrentyPolling()) {
sunnylink_client->user_service->startPolling();
} else {
sunnylink_client->user_service->load();
}
}
void SunnylinkPanel::stopSunnylink() const {
sunnylink_client->role_service->stopPolling();
sunnylink_client->user_service->stopPolling();
}
void SunnylinkPanel::showEvent(QShowEvent *event) {
updatePanel();
if (is_sunnylink_enabled) {
startSunnylink();
}
}
void SunnylinkPanel::updatePanel() {
if (!isVisible()) {
return;
}
updateBackupManagerState();
handleBackupProgress();
const auto sunnylinkDongleId = getSunnylinkDongleId().value_or(tr("N/A"));
sunnylinkEnabledBtn->setEnabled(!is_onroad);
is_sunnylink_enabled = params.getBool("SunnylinkEnabled");
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 = uiStateSP()->isSunnylinkPaired();
auto paired_users = uiStateSP()->sunnylinkDeviceUsers();
sunnylinkEnabledBtn->setEnabled(!is_onroad);
sunnylinkEnabledBtn->setValue(tr("Device ID") + " " + sunnylinkDongleId);
sponsorBtn->setEnabled(!is_onroad && is_sunnylink_enabled);
sponsorBtn->setText(is_sub ? tr("THANKS ♥") : tr("SPONSOR"));
sponsorBtn->setValue(is_sub ? tr(role_name.toStdString().c_str()) : tr("Not Sponsor"), role_color);
pairSponsorBtn->setEnabled(!is_onroad && is_sunnylink_enabled);
pairSponsorBtn->setValue(is_paired ? tr("Paired") : tr("Not Paired"));
bool can_do_uploads = max_current_sponsor_rule.roleTier >= SponsorTier::Novice && is_sunnylink_enabled;
sunnylinkUploaderEnabledBtn->setVisible(can_do_uploads);
sunnylinkUploaderEnabledBtn->setEnabled(can_do_uploads);
if (!is_sunnylink_enabled) {
sunnylinkEnabledBtn->setValue("");
sponsorBtn->setValue("");
pairSponsorBtn->setValue("");
}
update();
}
@@ -1,56 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
class SunnylinkPanel : public QFrame {
Q_OBJECT
public:
explicit SunnylinkPanel(QWidget *parent = nullptr);
void showEvent(QShowEvent *event) override;
void paramsRefresh(const QString &param_name, const QString &param_value);
void updateBackupManagerState();
void handleBackupProgress();
public slots:
void updatePanel();
private:
Params params;
QStackedLayout *main_layout = nullptr;
QWidget *sunnylinkScreen = nullptr;
ScrollViewSP *sunnylinkScroller = nullptr;
SunnylinkSponsorPopup *status_popup;
SunnylinkSponsorPopup *pair_popup;
ButtonControlSP *sponsorBtn;
ButtonControlSP *pairSponsorBtn;
SunnylinkClient *sunnylink_client;
cereal::BackupManagerSP::Reader backup_manager;
ParamControl *sunnylinkEnabledBtn;
bool is_onroad = false;
bool is_sunnylink_enabled = false;
bool backup_request_pending = false;
bool backup_request_started = false;
bool restore_request_pending = false;
bool restore_request_started = false;
ParamWatcher *param_watcher;
QString sunnylinkBtnDescription;
PushButtonSP *restoreSettings;
PushButtonSP *backupSettings;
ParamControlSP * sunnylinkUploaderEnabledBtn;
void stopSunnylink() const;
void startSunnylink() const;
};
@@ -1,36 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/trips_panel.h"
TripsPanel::TripsPanel(QWidget* parent) : QFrame(parent) {
QVBoxLayout* main_layout = new QVBoxLayout(this);
main_layout->setMargin(0);
// main content
main_layout->addSpacing(25);
center_layout = new QStackedLayout();
driveStatsWidget = new DriveStats;
driveStatsWidget->setStyleSheet(R"(
QLabel[type="title"] { font-size: 51px; font-weight: 500; }
QLabel[type="number"] { font-size: 78px; font-weight: 500; }
QLabel[type="unit"] { font-size: 51px; font-weight: 300; color: #A0A0A0; }
)");
center_layout->addWidget(driveStatsWidget);
main_layout->addLayout(center_layout, 1);
setStyleSheet(R"(
* {
color: white;
}
TripsPanel > QLabel {
font-size: 55px;
}
)");
}
@@ -1,25 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#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;
};
@@ -1,62 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_factory.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brands.h"
static const QStringList supportedBrands = {
"chrysler",
"ford",
"gm",
"honda",
"hyundai",
"mazda",
"nissan",
"rivian",
"subaru",
"tesla",
"toyota",
"volkswagen",
};
BrandSettingsInterface* BrandSettingsFactory::createBrandSettings(const QString& brand, QWidget* parent) {
if (brand == "chrysler")
return new ChryslerSettings(parent);
if (brand == "ford")
return new FordSettings(parent);
if (brand == "gm")
return new GMSettings(parent);
if (brand == "honda")
return new HondaSettings(parent);
if (brand == "hyundai")
return new HyundaiSettings(parent);
if (brand == "mazda")
return new MazdaSettings(parent);
if (brand == "nissan")
return new NissanSettings(parent);
if (brand == "rivian")
return new RivianSettings(parent);
if (brand == "subaru")
return new SubaruSettings(parent);
if (brand == "tesla")
return new TeslaSettings(parent);
if (brand == "toyota")
return new ToyotaSettings(parent);
if (brand == "volkswagen")
return new VolkswagenSettings(parent);
// Default empty settings if brand not supported
return nullptr;
}
bool BrandSettingsFactory::isBrandSupported(const QString& brand) {
return supportedBrands.contains(brand);
}
QStringList BrandSettingsFactory::getSupportedBrands() {
return supportedBrands;
}
@@ -1,18 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
class BrandSettingsFactory {
public:
static BrandSettingsInterface* createBrandSettings(const QString &brand, QWidget *parent = nullptr);
static bool isBrandSupported(const QString& brand);
static QStringList getSupportedBrands();
};
@@ -1,21 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
BrandSettingsInterface::BrandSettingsInterface(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0);
list = new ListWidget(this, false);
main_layout->addWidget(list);
}
void BrandSettingsInterface::updatePanel(bool _offroad) {
offroad = _offroad;
updateSettings();
}
@@ -1,28 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
class BrandSettingsInterface : public QWidget {
Q_OBJECT
public:
explicit BrandSettingsInterface(QWidget *parent = nullptr);
virtual ~BrandSettingsInterface() = default;
void updatePanel(bool _offroad);
virtual void updateSettings() = 0;
protected:
ListWidget *list = nullptr;
Params params;
bool offroad = false;
};
@@ -1,21 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/chrysler_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/ford_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/gm_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/honda_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/mazda_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/nissan_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/rivian_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/toyota_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/volkswagen_settings.h"
@@ -1,14 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/chrysler_settings.h"
ChryslerSettings::ChryslerSettings(QWidget *parent) : BrandSettingsInterface(parent) {
}
void ChryslerSettings::updateSettings() {
}
@@ -1,26 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
class ChryslerSettings : public BrandSettingsInterface {
Q_OBJECT
public:
explicit ChryslerSettings(QWidget *parent = nullptr);
void updateSettings() override;
private:
bool offroad = false;
};
@@ -1,14 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/ford_settings.h"
FordSettings::FordSettings(QWidget *parent) : BrandSettingsInterface(parent) {
}
void FordSettings::updateSettings() {
}
@@ -1,26 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
class FordSettings : public BrandSettingsInterface {
Q_OBJECT
public:
explicit FordSettings(QWidget *parent = nullptr);
void updateSettings() override;
private:
bool offroad = false;
};
@@ -1,14 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/gm_settings.h"
GMSettings::GMSettings(QWidget *parent) : BrandSettingsInterface(parent) {
}
void GMSettings::updateSettings() {
}
@@ -1,26 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
class GMSettings : public BrandSettingsInterface {
Q_OBJECT
public:
explicit GMSettings(QWidget *parent = nullptr);
void updateSettings() override;
private:
bool offroad = false;
};
@@ -1,14 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/honda_settings.h"
HondaSettings::HondaSettings(QWidget *parent) : BrandSettingsInterface(parent) {
}
void HondaSettings::updateSettings() {
}
@@ -1,26 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
class HondaSettings : public BrandSettingsInterface {
Q_OBJECT
public:
explicit HondaSettings(QWidget *parent = nullptr);
void updateSettings() override;
private:
bool offroad = false;
};
@@ -1,57 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.h"
HyundaiSettings::HyundaiSettings(QWidget *parent) : BrandSettingsInterface(parent) {
std::vector<QString> tuning_texts{ tr("Off"), tr("Dynamic"), tr("Predictive") };
longitudinalTuningToggle = new ButtonParamControl(
"HyundaiLongitudinalTuning",
tr("Custom Longitudinal Tuning"),
"",
"",
tuning_texts,
500
);
QObject::connect(longitudinalTuningToggle, &ButtonParamControlSP::buttonClicked, this, &HyundaiSettings::updateSettings);
list->addItem(longitudinalTuningToggle);
longitudinalTuningToggle->showDescription();
}
void HyundaiSettings::updateSettings() {
auto longitudinal_tuning_param = std::atoi(params.get("HyundaiLongitudinalTuning").c_str());
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<cereal::CarParams>();
has_longitudinal_control = hasLongitudinalControl(CP);
} else {
has_longitudinal_control = false;
}
LongitudinalTuningOption longitudinal_tuning_option;
if (longitudinal_tuning_param == int(LongitudinalTuningOption::PREDICTIVE)) {
longitudinal_tuning_option = LongitudinalTuningOption::PREDICTIVE;
} else if (longitudinal_tuning_param == int(LongitudinalTuningOption::DYNAMIC)) {
longitudinal_tuning_option = LongitudinalTuningOption::DYNAMIC;
} else {
longitudinal_tuning_option = LongitudinalTuningOption::OFF;
}
bool longitudinal_tuning_disabled = !offroad || !has_longitudinal_control;
QString longitudinal_tuning_description = longitudinalTuningDescription(longitudinal_tuning_option);
if (longitudinal_tuning_disabled) {
longitudinal_tuning_description = toggleDisableMsg(offroad, has_longitudinal_control);
}
longitudinalTuningToggle->setEnabled(!longitudinal_tuning_disabled);
longitudinalTuningToggle->setDescription(longitudinal_tuning_description);
longitudinalTuningToggle->showDescription();
}
@@ -1,65 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
enum class LongitudinalTuningOption {
OFF,
DYNAMIC,
PREDICTIVE,
};
class HyundaiSettings : public BrandSettingsInterface {
Q_OBJECT
public:
explicit HyundaiSettings(QWidget *parent = nullptr);
void updateSettings() override;
private:
bool has_longitudinal_control = false;
ButtonParamControl *longitudinalTuningToggle = nullptr;
static QString toggleDisableMsg(bool _offroad, bool _has_longitudinal_control) {
if (!_has_longitudinal_control) {
return tr("This feature can only be used with sunnypilot longitudinal control enabled.");
}
if (!_offroad) {
return tr("Enable \"Always Offroad\" in Device panel, or turn vehicle off to select an option.");
}
return QString();
}
static QString longitudinalTuningDescription(LongitudinalTuningOption option = LongitudinalTuningOption::OFF) {
QString off_str = tr("Off: Uses default tuning");
QString dynamic_str = tr("Dynamic: Adjusts acceleration limits based on current speed");
QString predictive_str = tr("Predictive: Uses future trajectory data to anticipate needed adjustments");
if (option == LongitudinalTuningOption::PREDICTIVE) {
predictive_str = "<font color='white'><b>" + predictive_str + "</b></font>";
} else if (option == LongitudinalTuningOption::DYNAMIC) {
dynamic_str = "<font color='white'><b>" + dynamic_str + "</b></font>";
} else {
off_str = "<font color='white'><b>" + off_str + "</b></font>";
}
return QString("%1<br><br>%2<br>%3<br>%4<br>")
.arg(tr("Fine-tune your driving experience by adjusting acceleration smoothness with sunnypilot longitudinal control."))
.arg(off_str)
.arg(dynamic_str)
.arg(predictive_str);
}
};
@@ -1,14 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/mazda_settings.h"
MazdaSettings::MazdaSettings(QWidget *parent) : BrandSettingsInterface(parent) {
}
void MazdaSettings::updateSettings() {
}
@@ -1,26 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
class MazdaSettings : public BrandSettingsInterface {
Q_OBJECT
public:
explicit MazdaSettings(QWidget *parent = nullptr);
void updateSettings() override;
private:
bool offroad = false;
};
@@ -1,14 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/nissan_settings.h"
NissanSettings::NissanSettings(QWidget *parent) : BrandSettingsInterface(parent) {
}
void NissanSettings::updateSettings() {
}
@@ -1,26 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
class NissanSettings : public BrandSettingsInterface {
Q_OBJECT
public:
explicit NissanSettings(QWidget *parent = nullptr);
void updateSettings() override;
private:
bool offroad = false;
};
@@ -1,235 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/platform_selector.h"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMap>
#include "selfdrive/ui/sunnypilot/qt/util.h"
QVariant PlatformSelector::getPlatformBundle(const QString &key) {
QString platform_bundle = QString::fromStdString(params.get("CarPlatformBundle"));
if (!platform_bundle.isEmpty()) {
QJsonDocument json = QJsonDocument::fromJson(platform_bundle.toUtf8());
if (!json.isNull() && json.isObject()) {
return json.object().value(key).toVariant();
}
}
return {};
}
PlatformSelector::PlatformSelector() : ButtonControl(tr("Vehicle"), "", "") {
platforms = loadPlatformList();
QObject::connect(this, &ButtonControl::clicked, [=]() {
if (text() == tr("SEARCH")) {
QString query = InputDialog::getText(tr("Search your vehicle"), this, tr("Enter model year (e.g., 2021) and model name (Toyota Corolla):"), false);
if (query.length() > 0) {
setText(tr("SEARCHING"));
setEnabled(false);
searchPlatforms(query);
refresh(offroad);
}
} else {
params.remove("CarPlatformBundle");
refresh(offroad);
}
});
main_layout->addStretch(0);
refresh(offroad);
}
void PlatformSelector::refresh(bool _offroad) {
QString name = getPlatformBundle("name").toString();
platform = unrecognized_str;
QString platform_color = YELLOW_PLATFORM;
if (!name.isEmpty()) {
platform = name;
platform_color = BLUE_PLATFORM;
brand = getPlatformBundle("brand").toString();
setText(tr("REMOVE"));
} else {
setText(tr("SEARCH"));
platform = unrecognized_str;
brand = "";
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<cereal::CarParams>();
platform = QString::fromStdString(CP.getCarFingerprint().cStr());
for (auto it = platforms.constBegin(); it != platforms.constEnd(); ++it) {
if (it.value()["platform"].toString() == platform) {
brand = it.value()["brand"].toString();
break;
}
}
if (platform == "MOCK") {
platform = unrecognized_str;
} else {
platform_color = GREEN_PLATFORM;
}
}
}
setValue(platform, platform_color);
setEnabled(true);
emit refreshPanel();
offroad = _offroad;
FingerprintStatus cur_status;
if (platform_color == GREEN_PLATFORM) {
cur_status = FingerprintStatus::AUTO_FINGERPRINT;
} else if (platform_color == BLUE_PLATFORM) {
cur_status = FingerprintStatus::MANUAL_FINGERPRINT;
} else {
cur_status = FingerprintStatus::UNRECOGNIZED;
}
setDescription(platformDescription(cur_status));
showDescription();
}
void PlatformSelector::setPlatform(const QString &_platform) {
QVariantMap platform_data = platforms[_platform];
const QString offroad_msg = offroad ? tr("This setting will take effect immediately.") :
tr("This setting will take effect once the device enters offroad state.");
const QString msg = QString("<b>%1</b><br><br>%2")
.arg(_platform, offroad_msg);
QString content("<body><h2 style=\"text-align: center;\">" + tr("Vehicle Selector") + "</h2><br>"
"<p style=\"text-align: center; margin: 0 128px; font-size: 50px;\">" + msg + "</p></body>");
if (ConfirmationDialog(content, tr("Confirm"), tr("Cancel"), true, this).exec()) {
QJsonObject json_bundle;
json_bundle["platform"] = platform_data["platform"].toString();
json_bundle["name"] = _platform;
json_bundle["make"] = platform_data["make"].toString();
json_bundle["brand"] = platform_data["brand"].toString();
json_bundle["model"] = platform_data["model"].toString();
json_bundle["package"] = platform_data["package"].toString();
QVariantList yearList = platform_data["year"].toList();
QJsonArray yearArray;
for (const QVariant &year : yearList) {
yearArray.append(year.toString());
}
json_bundle["year"] = yearArray;
QString json_bundle_str = QString::fromUtf8(QJsonDocument(json_bundle).toJson(QJsonDocument::Compact));
params.put("CarPlatformBundle", json_bundle_str.toStdString());
}
}
void PlatformSelector::searchPlatforms(const QString &query) {
if (query.isEmpty()) {
return;
}
QSet<QString> matched_cars;
QString normalized_query = query.simplified().toLower();
QStringList tokens = normalized_query.split(" ", QString::SkipEmptyParts);
int search_year = -1;
QStringList search_terms;
for (const QString &token : tokens) {
bool ok;
int year = token.toInt(&ok);
if (ok && year >= 1900 && year <= 2100) {
search_year = year;
} else {
search_terms << token;
}
}
for (auto it = platforms.constBegin(); it != platforms.constEnd(); ++it) {
QString platform_name = it.key();
QVariantMap platform_data = it.value();
if (search_year != -1) {
QVariantList year_list = platform_data["year"].toList();
bool year_match = false;
for (const QVariant &year_var : year_list) {
int year = year_var.toString().toInt();
if (year == search_year) {
year_match = true;
break;
}
}
if (!year_match) continue;
}
QString normalized_make = platform_data["make"].toString().normalized(QString::NormalizationForm_KD).toLower();
QString normalized_model = platform_data["model"].toString().normalized(QString::NormalizationForm_KD).toLower();
normalized_make.remove(QRegExp("[^a-zA-Z0-9\\s]"));
normalized_model.remove(QRegExp("[^a-zA-Z0-9\\s]"));
bool all_terms_match = true;
for (const QString &term : search_terms) {
QString normalized_term = term.normalized(QString::NormalizationForm_KD).toLower();
normalized_term.remove(QRegExp("[^a-zA-Z0-9\\s]"));
bool term_matched = false;
if (normalized_make.contains(normalized_term, Qt::CaseInsensitive)) {
term_matched = true;
}
if (!term_matched) {
if (term.contains(QRegExp("[a-z]\\d|\\d[a-z]", Qt::CaseInsensitive))) {
QString clean_model = normalized_model;
QString clean_term = normalized_term;
clean_model.remove(" ");
clean_term.remove(" ");
if (clean_model.contains(clean_term, Qt::CaseInsensitive)) {
term_matched = true;
}
} else {
if (normalized_model.contains(normalized_term, Qt::CaseInsensitive)) {
term_matched = true;
}
}
}
if (!term_matched) {
all_terms_match = false;
break;
}
}
if (all_terms_match) {
matched_cars.insert(platform_name);
}
}
QStringList results = matched_cars.toList();
results.sort();
if (results.isEmpty()) {
ConfirmationDialog::alert(tr("No vehicles found for query: %1").arg(query), this);
return;
}
QString selected_platform = MultiOptionDialog::getSelection(tr("Select a vehicle"), results, "", this);
if (!selected_platform.isEmpty()) {
setPlatform(selected_platform);
}
}
@@ -1,68 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
static const QString GREEN_PLATFORM = "#00F100";
static const QString BLUE_PLATFORM = "#0086E9";
static const QString YELLOW_PLATFORM = "#FFD500";
enum class FingerprintStatus {
AUTO_FINGERPRINT,
MANUAL_FINGERPRINT,
UNRECOGNIZED,
};
class PlatformSelector : public ButtonControl {
Q_OBJECT
public:
PlatformSelector();
QVariant getPlatformBundle(const QString &key);
QString platform;
QString brand;
public slots:
void refresh(bool _offroad);
signals:
void refreshPanel();
private:
void searchPlatforms(const QString &query);
void setPlatform(const QString &platform = "");
QMap<QString, QVariantMap> platforms;
Params params;
bool offroad;
QString unrecognized_str = tr("Unrecognized Vehicle");
static QString platformDescription(FingerprintStatus status = FingerprintStatus::UNRECOGNIZED) {
QString auto_str = "🟢 - " + tr("Fingerprinted automatically");
QString manual_str = "🔵 - " + tr("Manually selected");
QString unrecognized_str = "🟡 - " + tr("Not fingerprinted or manually selected");
if (status == FingerprintStatus::AUTO_FINGERPRINT) {
auto_str = "<font color='white'><b>" + auto_str + "</b></font>";
} else if (status == FingerprintStatus::MANUAL_FINGERPRINT) {
manual_str = "<font color='white'><b>" + manual_str + "</b></font>";
} else {
unrecognized_str = "<font color='white'><b>" + unrecognized_str + "</b></font>";
}
return QString("%1<br>%2<br><br>%3<br>%4<br>%5")
.arg(tr("Select vehicle to force fingerprint manually."))
.arg(tr("Colors represent fingerprint status:"))
.arg(auto_str)
.arg(manual_str)
.arg(unrecognized_str);
}
};
@@ -1,14 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/rivian_settings.h"
RivianSettings::RivianSettings(QWidget *parent) : BrandSettingsInterface(parent) {
}
void RivianSettings::updateSettings() {
}
@@ -1,26 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
class RivianSettings : public BrandSettingsInterface {
Q_OBJECT
public:
explicit RivianSettings(QWidget *parent = nullptr);
void updateSettings() override;
private:
bool offroad = false;
};

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