From 01847278916f5caafc87f9961d7ad0c9e17ec39c Mon Sep 17 00:00:00 2001 From: FrogAi <91348155+FrogAi@users.noreply.github.com> Date: Fri, 19 Jul 2024 00:10:35 -0700 Subject: [PATCH] Controls - Model Management - Model Randomizer --- .../controls/lib/frogpilot_variables.py | 9 +- .../frogpilot/ui/qt/widgets/model_reviewer.cc | 235 ++++++++++++++++++ .../frogpilot/ui/qt/widgets/model_reviewer.h | 62 +++++ selfdrive/ui/SConscript | 2 +- selfdrive/ui/qt/home.cc | 12 + selfdrive/ui/ui.cc | 11 + selfdrive/ui/ui.h | 4 + 7 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 selfdrive/frogpilot/ui/qt/widgets/model_reviewer.cc create mode 100644 selfdrive/frogpilot/ui/qt/widgets/model_reviewer.h diff --git a/selfdrive/frogpilot/controls/lib/frogpilot_variables.py b/selfdrive/frogpilot/controls/lib/frogpilot_variables.py index bdd49d2fc..4c99b8756 100644 --- a/selfdrive/frogpilot/controls/lib/frogpilot_variables.py +++ b/selfdrive/frogpilot/controls/lib/frogpilot_variables.py @@ -1,4 +1,5 @@ import os +import random from types import SimpleNamespace @@ -186,7 +187,13 @@ class FrogPilotVariables: current_model = self.params_memory.get("CurrentModel", encoding='utf-8') current_model_name = self.params_memory.get("CurrentModelName", encoding='utf-8') if toggle.model_manager and available_models and current_model is None: - toggle.model = self.params.get("Model", block=True, encoding='utf-8') + toggle.model_randomizer = self.params.get_bool("ModelRandomizer") + if toggle.model_randomizer: + blacklisted_models = (self.params.get("BlacklistedModels", encoding='utf-8') or '').split(',') + existing_models = [model for model in available_models.split(',') if model not in blacklisted_models and os.path.exists(os.path.join(MODELS_PATH, f"{model}.thneed"))] + toggle.model = random.choice(existing_models) if existing_models else DEFAULT_MODEL + else: + toggle.model = self.params.get("Model", block=True, encoding='utf-8') else: toggle.model = current_model if not os.path.exists(os.path.join(MODELS_PATH, f"{toggle.model}.thneed")): diff --git a/selfdrive/frogpilot/ui/qt/widgets/model_reviewer.cc b/selfdrive/frogpilot/ui/qt/widgets/model_reviewer.cc new file mode 100644 index 000000000..f788ad157 --- /dev/null +++ b/selfdrive/frogpilot/ui/qt/widgets/model_reviewer.cc @@ -0,0 +1,235 @@ +#include "selfdrive/frogpilot/ui/qt/widgets/model_reviewer.h" + +ModelReview::ModelReview(QWidget *parent) : QFrame(parent) { + mainLayout = new QStackedLayout(this); + + ratingLayout = new QVBoxLayout(); + ratingLayout->setContentsMargins(50, 25, 50, 20); + + questionLabel = addLabel(ratingLayout, "What would you rate that drive?", "question"); + + QHBoxLayout *ratingButtonsLayout = new QHBoxLayout(); + QStringList emojis = {"🀩", "πŸ™‚", "πŸ€”", "πŸ™", "πŸ€•"}; + QList scores = {100, 80, 60, 40, 20}; + + for (int i = 0; i < emojis.size(); ++i) { + QPushButton *ratingButton = createButton(emojis[i], "rating_button", scores[i], 150, 150); + connect(ratingButton, &QPushButton::clicked, this, &ModelReview::onRatingButtonClicked); + ratingButtons.append(ratingButton); + ratingButtonsLayout->addWidget(ratingButton); + } + + ratingLayout->addLayout(ratingButtonsLayout); + + blacklistButton = createButton("Blacklist this model", "blacklist_button", 0, 600, 100); + connect(blacklistButton, &QPushButton::clicked, this, &ModelReview::onBlacklistButtonClicked); + ratingLayout->addWidget(blacklistButton, 0, Qt::AlignCenter); + + QWidget *ratingWidget = new QWidget(this); + ratingWidget->setLayout(ratingLayout); + + mainLayout->addWidget(ratingWidget); + + modelInfoLayout = new QVBoxLayout(); + modelInfoLayout->setContentsMargins(50, 25, 50, 20); + + titleLabel = addLabel(modelInfoLayout, "The model used during that drive was:", "title"); + modelLabel = addLabel(modelInfoLayout, "", "model"); + + QSpacerItem *spacer = new QSpacerItem(20, 75, QSizePolicy::Minimum, QSizePolicy::Fixed); + modelInfoLayout->addItem(spacer); + + QVBoxLayout *bottomLayout = new QVBoxLayout(); + modelScoreLabel = addLabel(bottomLayout, "Current Model Score: 0", "score"); + modelRankLabel = addLabel(bottomLayout, "Current Model Rank: 0", "rank"); + totalDrivesLabel = addLabel(bottomLayout, "Total Model Drives: 0", "drives"); + totalOverallDrivesLabel = addLabel(bottomLayout, "Total Overall Model Drives: 0", "drives"); + blacklistMessageLabel = addLabel(bottomLayout, "", "blacklist_message"); + + modelInfoLayout->addLayout(bottomLayout); + + QWidget *modelInfoWidget = new QWidget(this); + modelInfoWidget->setLayout(modelInfoLayout); + + mainLayout->addWidget(modelInfoWidget); + + setStyleSheet(R"( + ModelReview { + background-color: #333333; + border-radius: 5px; + } + QLabel[type="drives"], QLabel[type="question"], QLabel[type="rank"], QLabel[type="score"], QLabel[type="title"] { + font-size: 50px; + font-weight: semi-bold; + color: #FFFFFF; + } + QLabel[type="model"] { + font-size: 75px; + font-weight: bold; + color: #FFFFFF; + } + QLabel[type="blacklist_message"] { + font-size: 40px; + font-weight: bold; + color: #C92231; + } + QPushButton[type="rating_button"] { + font-size: 75px; + font-weight: bold; + padding: 10px; + color: #FFFFFF; + background-color: #555555; + border: 2px solid #FFFFFF; + border-radius: 5px; + } + QPushButton[type="rating_button"]:hover { + background-color: #777777; + } + QPushButton[type="blacklist_button"] { + font-size: 50px; + font-weight: bold; + padding: 10px; + color: #C92231; + background-color: #000000; + border: 2px solid #FFFFFF; + border-radius: 5px; + } + )"); +} + +QLabel *ModelReview::addLabel(QVBoxLayout *layout, const QString &text, const QString &type) { + QLabel *label = new QLabel(text, this); + label->setProperty("type", type); + label->setAlignment(Qt::AlignCenter); + layout->addWidget(label); + return label; +} + +QPushButton *ModelReview::createButton(const QString &text, const QString &type, int rating, int width, int height) { + QPushButton *button = new QPushButton(text, this); + button->setProperty("type", type); + button->setProperty("rating", rating); + button->setFixedSize(width, height); + return button; +} + +void ModelReview::showEvent(QShowEvent *event) { + currentModel = QString::fromStdString(paramsMemory.get("CurrentModelName")); + currentModelFiltered = processModelName(currentModel); + + if (modelRated) { + mainLayout->setCurrentIndex(1); + } else { + mainLayout->setCurrentIndex(0); + } + + checkBlacklistButtonVisibility(); +} + +void ModelReview::mousePressEvent(QMouseEvent *e) { + if (mainLayout->currentIndex() != 1) { + return; + } + + paramsMemory.putBool("DriveRated", true); +} + +void ModelReview::updateLabel() { + modelLabel->setText(currentModel.remove(QRegularExpression("[πŸ—ΊοΈπŸ‘€πŸ“‘]")).remove(QRegularExpression(" \\(Default\\)"))); + modelScoreLabel->setText(QString("Current Model Score: %1").arg(finalRating)); + totalDrivesLabel->setText(QString("Total Model Drives: %1").arg(totalDrives)); + modelRankLabel->setText(QString("Current Model Rank: %1").arg(getModelRank())); + totalOverallDrivesLabel->setText(QString("Total Overall Drives: %1").arg(totalOverallDrives)); + + mainLayout->setCurrentIndex(1); + + QTimer::singleShot(30000, this, [this]() { + paramsMemory.putBool("DriveRated", true); + modelRated = false; + }); +} + +void ModelReview::onRatingButtonClicked() { + int newRating = qobject_cast(sender())->property("rating").toInt(); + + QString drivesParam = QString("%1Drives").arg(currentModelFiltered); + std::string drivesParamStd = drivesParam.toStdString(); + totalDrives = params.getInt(drivesParamStd) + 1; + + QString ratingParam = QString("%1Score").arg(currentModelFiltered); + std::string ratingParamStd = ratingParam.toStdString(); + int currentRating = params.getInt(ratingParamStd); + + finalRating = (currentRating * (totalDrives - 1) + newRating) / totalDrives; + + params.putIntNonBlocking(drivesParamStd, totalDrives); + params.putIntNonBlocking(ratingParamStd, finalRating); + + modelRated = true; + + updateLabel(); +} + +void ModelReview::onBlacklistButtonClicked() { + params.putIntNonBlocking(QString("%1Score").arg(currentModelFiltered).toStdString(), 0); + + if (!blacklistedModels.contains(currentModel)) { + blacklistedModels.append(currentModel); + params.putNonBlocking("BlacklistedModels", blacklistedModels.join(",").toStdString()); + } + + blacklistMessageLabel->setText("Model successfully blacklisted!"); + updateLabel(); +} + +void ModelReview::checkBlacklistButtonVisibility() { + QStringList availableModels = QString::fromStdString(params.get("AvailableModels")).split(","); + blacklistedModels = QString::fromStdString(params.get("BlacklistedModels")).split(",", QString::SkipEmptyParts); + QStringList selectableModels; + + for (const QString &model : availableModels) { + if (!blacklistedModels.contains(model)) { + selectableModels.append(model); + } + } + + blacklistButton->setVisible(selectableModels.size() > 1); +} + +int ModelReview::getModelRank() { + QStringList availableModels = QString::fromStdString(params.get("AvailableModelsNames")).split(","); + QList> modelScores; + totalOverallDrives = 0; + + for (const QString &model : availableModels) { + QString scoreParam = QString("%1Score").arg(processModelName(model)); + int modelScore = params.getInt(scoreParam.toStdString()); + + QString drivesParam = QString("%1Drives").arg(processModelName(model)); + int modelDrives = params.getInt(drivesParam.toStdString()); + totalOverallDrives += modelDrives; + + modelScores.append(qMakePair(processModelName(model), modelScore)); + } + + std::sort(modelScores.begin(), modelScores.end(), [](const QPair &a, const QPair &b) { + return a.second > b.second; + }); + + QString processedCurrentModel = processModelName(currentModel); + for (int i = 0; i < modelScores.size(); ++i) { + if (modelScores[i].first == processedCurrentModel) { + return i + 1; + } + } + + return -1; +} + +QString ModelReview::processModelName(const QString &modelName) { + QString modelCleaned = modelName; + modelCleaned = modelCleaned.remove(QRegularExpression("[πŸ—ΊοΈπŸ‘€πŸ“‘]")).simplified(); + QString scoreParam = modelCleaned.remove(QRegularExpression("[^a-zA-Z0-9()-]")).replace(" ", "").simplified(); + scoreParam = scoreParam.replace("(Default)", "").replace("-", ""); + return scoreParam; +} diff --git a/selfdrive/frogpilot/ui/qt/widgets/model_reviewer.h b/selfdrive/frogpilot/ui/qt/widgets/model_reviewer.h new file mode 100644 index 000000000..ba8bff3e2 --- /dev/null +++ b/selfdrive/frogpilot/ui/qt/widgets/model_reviewer.h @@ -0,0 +1,62 @@ +#pragma once + +#include + +#include "selfdrive/ui/ui.h" + +class ModelReview : public QFrame { + Q_OBJECT + +public: + explicit ModelReview(QWidget *parent = nullptr); + +private slots: + void onBlacklistButtonClicked(); + void onRatingButtonClicked(); + +private: + int getModelRank(); + + QLabel *addLabel(QVBoxLayout *layout, const QString &text, const QString &type); + + QPushButton *createButton(const QString &text, const QString &type, int rating, int width, int height); + + QString processModelName(const QString &modelName); + + void checkBlacklistButtonVisibility(); + void mousePressEvent(QMouseEvent *e) override; + void showEvent(QShowEvent *event) override; + void updateLabel(); + + QStackedLayout *mainLayout; + + QVBoxLayout *ratingLayout; + QVBoxLayout *modelInfoLayout; + + Params params; + Params paramsMemory{"/dev/shm/params"}; + + QLabel *blacklistMessageLabel; + QLabel *modelLabel; + QLabel *modelRankLabel; + QLabel *modelScoreLabel; + QLabel *questionLabel; + QLabel *titleLabel; + QLabel *totalDrivesLabel; + QLabel *totalOverallDrivesLabel; + + QString currentModel; + QString currentModelFiltered; + + QStringList blacklistedModels; + + QPushButton *blacklistButton; + + QList ratingButtons; + + bool modelRated; + + int finalRating; + int totalDrives; + int totalOverallDrives; +}; diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index be7e5c53c..b2ac5eb6a 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -24,7 +24,7 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc", "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc", "../frogpilot/ui/qt/widgets/drive_stats.cc", "../frogpilot/ui/qt/widgets/frogpilot_controls.cc", - "../frogpilot/navigation/ui/navigation_settings.cc", + "../frogpilot/ui/qt/widgets/model_reviewer.cc", "../frogpilot/navigation/ui/navigation_settings.cc", "../frogpilot/ui/qt/offroad/control_settings.cc", "../frogpilot/ui/qt/offroad/vehicle_settings.cc", "../frogpilot/ui/qt/offroad/visual_settings.cc"] diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index 1cd7f3cd8..eade15942 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -14,6 +14,7 @@ #endif #include "selfdrive/frogpilot/ui/qt/widgets/drive_stats.h" +#include "selfdrive/frogpilot/ui/qt/widgets/model_reviewer.h" // HomeWindow: the container for the offroad and onroad UIs @@ -172,9 +173,16 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { left_widget->addWidget(new QWidget); #endif left_widget->addWidget(new DriveStats); + left_widget->addWidget(new ModelReview); left_widget->setStyleSheet("border-radius: 10px;"); left_widget->setCurrentIndex(1); + connect(uiState(), &UIState::driveRated, [=]() { + left_widget->setCurrentIndex(1); + }); + connect(uiState(), &UIState::reviewModel, [=]() { + left_widget->setCurrentIndex(2); + }); home_layout->addWidget(left_widget, 1); @@ -246,6 +254,10 @@ void OffroadHome::refresh() { model = model.remove("(Default)").trimmed(); } + if (uiState()->scene.model_randomizer) { + model = "Mystery Model πŸ‘»"; + } + date->setText(QLocale(uiState()->language.mid(5)).toString(QDateTime::currentDateTime(), "dddd, MMMM d")); version->setText(getBrand() + " v" + getVersion().left(14).trimmed() + " - " + model); diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 617615cce..aa17531dd 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -299,6 +299,9 @@ void ui_update_frogpilot_params(UIState *s, Params ¶ms) { bool radarless_model = params.get("Model") == "radical-turtle"; scene.lead_detection_threshold = longitudinal_tune && !radarless_model ? params.getInt("LeadDetectionThreshold") / 100.0f : 0.5; + bool model_manager = params.getBool("ModelManagement"); + scene.model_randomizer = model_manager && params.getBool("ModelRandomizer"); + scene.tethering_config = params.getInt("TetheringEnabled"); if (scene.tethering_config == 2) { WifiManager(s).setTetheringEnabled(true); @@ -328,6 +331,8 @@ void UIState::updateStatus() { if (scene.started) { status = STATUS_DISENGAGED; scene.started_frame = sm->frame; + } else if (scene.started_timer > 15*60*UI_FREQ && scene.model_randomizer) { + emit reviewModel(); } started_prev = scene.started; scene.world_objects_visible = false; @@ -385,8 +390,14 @@ void UIState::update() { update_toggles = false; } + if (paramsMemory.getBool("DriveRated")) { + emit driveRated(); + paramsMemory.remove("DriveRated"); + } + // FrogPilot variables that need to be constantly updated scene.conditional_status = scene.conditional_experimental && scene.enabled ? paramsMemory.getInt("CEStatus") : 0; + scene.started_timer = scene.started || started_prev ? scene.started_timer + 1 : 0; } void UIState::setPrimeType(PrimeType type) { diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 7e2554a23..43aec27ea 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -130,6 +130,7 @@ typedef struct UIScene { bool experimental_mode_via_screen; bool has_lead; bool map_open; + bool model_randomizer; bool online; bool onroad_distance_button; bool parked; @@ -186,6 +187,9 @@ signals: void primeChanged(bool prime); void primeTypeChanged(PrimeType prime_type); + // FrogPilot signals + void driveRated(); + void reviewModel(); private slots: void update();