From 3c6d9cc2091eb647492cd2a644afcb9befce5b15 Mon Sep 17 00:00:00 2001 From: FrogAi <91348155+FrogAi@users.noreply.github.com> Date: Thu, 9 May 2024 22:09:15 -0700 Subject: [PATCH] FrogPilot features - Track FrogPilot drives --- selfdrive/controls/controlsd.py | 25 ++++++ selfdrive/manager/manager.py | 3 +- selfdrive/ui/SConscript | 2 +- selfdrive/ui/qt/home.cc | 6 +- selfdrive/ui/qt/widgets/drive_stats.cc | 105 +++++++++++++++++++++++++ selfdrive/ui/qt/widgets/drive_stats.h | 29 +++++++ 6 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 selfdrive/ui/qt/widgets/drive_stats.cc create mode 100644 selfdrive/ui/qt/widgets/drive_stats.h diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index d282fca..c774114 100644 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -70,15 +70,19 @@ class Controls: # FrogPilot variables self.frogpilot_toggles = FrogPilotVariables.toggles + self.drive_added = False self.openpilot_crashed_triggered = False self.display_timer = 0 + self.drive_distance = 0 + self.drive_time = 0 self.card = CarD(CI) self.params = Params() self.params_memory = Params("/dev/shm/params") self.params_storage = Params("/persist/params") + self.params_tracking = Params("/persist/tracking") with car.CarParams.from_bytes(self.params.get("CarParams", block=True)) as msg: # TODO: this shouldn't need to be a builder @@ -909,6 +913,27 @@ class Controls: def update_frogpilot_variables(self, CS): self.driving_gear = CS.gearShifter not in (GearShifter.neutral, GearShifter.park, GearShifter.reverse, GearShifter.unknown) + self.drive_distance += CS.vEgo * DT_CTRL + self.drive_time += DT_CTRL + + if self.drive_time > 60 and CS.standstill: + current_total_distance = self.params_tracking.get_float("FrogPilotKilometers") + distance_to_add = self.drive_distance / 1000 + new_total_distance = current_total_distance + distance_to_add + self.params_tracking.put_float_nonblocking("FrogPilotKilometers", new_total_distance) + self.drive_distance = 0 + + current_total_time = self.params_tracking.get_float("FrogPilotMinutes") + time_to_add = self.drive_time / 60 + new_total_time = current_total_time + time_to_add + self.params_tracking.put_float_nonblocking("FrogPilotMinutes", new_total_time) + self.drive_time = 0 + + if self.sm.frame * DT_CTRL > 60 * 5 and not self.drive_added: + new_total_drives = self.params_tracking.get_int("FrogPilotDrives") + 1 + self.params_tracking.put_int_nonblocking("FrogPilotDrives", new_total_drives) + self.drive_added = True + fpcc_send = messaging.new_message('frogpilotCarControl') fpcc_send.valid = CS.canValid fpcc_send.frogpilotCarControl = self.FPCC diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index a235ea2..9ed8523 100644 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -56,13 +56,14 @@ def manager_init(frogpilot_functions) -> None: params = Params() params_storage = Params("/persist/params") + params_tracking = Params("/persist/tracking") params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START) params.clear_all(ParamKeyType.CLEAR_ON_ONROAD_TRANSITION) params.clear_all(ParamKeyType.CLEAR_ON_OFFROAD_TRANSITION) if is_release_branch(): params.clear_all(ParamKeyType.DEVELOPMENT_ONLY) - frogpilot_functions.convert_params(params, params_storage) + frogpilot_functions.convert_params(params, params_storage, params_tracking) default_params: list[tuple[str, str | bytes]] = [ ("CarParamsPersistent", ""), diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 8539eb8..6115625 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -19,7 +19,7 @@ if arch == "Darwin": qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"], LIBS=base_libs) -widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc", +widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/drive_stats.cc", "qt/widgets/wifi.cc", "qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc", "qt/widgets/offroad_alerts.cc", "qt/widgets/prime.cc", "qt/widgets/keyboard.cc", "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc", diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index d87ceb8..e711e73 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -7,6 +7,7 @@ #include "selfdrive/ui/qt/offroad/experimental_mode.h" #include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/widgets/drive_stats.h" #include "selfdrive/ui/qt/widgets/prime.h" #ifdef ENABLE_MAPS @@ -166,11 +167,12 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { left_widget->addWidget(new QWidget); #endif left_widget->addWidget(new PrimeAdWidget); + left_widget->addWidget(new DriveStats); left_widget->setStyleSheet("border-radius: 10px;"); - left_widget->setCurrentIndex(uiState()->hasPrime() ? 0 : 1); + left_widget->setCurrentIndex(2); connect(uiState(), &UIState::primeChanged, [=](bool prime) { - left_widget->setCurrentIndex(prime ? 0 : 1); + left_widget->setCurrentIndex(2); }); home_layout->addWidget(left_widget, 1); diff --git a/selfdrive/ui/qt/widgets/drive_stats.cc b/selfdrive/ui/qt/widgets/drive_stats.cc new file mode 100644 index 0000000..0236a76 --- /dev/null +++ b/selfdrive/ui/qt/widgets/drive_stats.cc @@ -0,0 +1,105 @@ +#include "selfdrive/ui/qt/widgets/drive_stats.h" + +#include +#include +#include +#include + +#include "selfdrive/ui/qt/request_repeater.h" +#include "selfdrive/ui/qt/util.h" + +static QLabel* newLabel(const QString& text, const QString &type) { + QLabel* label = new QLabel(text); + label->setProperty("type", type); + return label; +} + +DriveStats::DriveStats(QWidget* parent) : QFrame(parent) { + metric_ = params.getBool("IsMetric"); + + QVBoxLayout* main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(50, 25, 50, 20); + + auto add_stats_layouts = [=](const QString &title, StatsLabels& labels, bool FrogPilot=false) { + QGridLayout* grid_layout = new QGridLayout; + grid_layout->setVerticalSpacing(10); + grid_layout->setContentsMargins(0, 10, 0, 10); + + int row = 0; + grid_layout->addWidget(newLabel(title, FrogPilot ? "frogpilot_title" : "title"), row++, 0, 1, 3); + grid_layout->addItem(new QSpacerItem(0, 10), row++, 0, 1, 1); + + grid_layout->addWidget(labels.routes = newLabel("0", "number"), row, 0, Qt::AlignLeft); + grid_layout->addWidget(labels.distance = newLabel("0", "number"), row, 1, Qt::AlignLeft); + grid_layout->addWidget(labels.hours = newLabel("0", "number"), row, 2, Qt::AlignLeft); + + grid_layout->addWidget(newLabel((tr("Drives")), "unit"), row + 1, 0, Qt::AlignLeft); + grid_layout->addWidget(labels.distance_unit = newLabel(getDistanceUnit(), "unit"), row + 1, 1, Qt::AlignLeft); + grid_layout->addWidget(newLabel(tr("Hours"), "unit"), row + 1, 2, Qt::AlignLeft); + + main_layout->addLayout(grid_layout); + main_layout->addStretch(1); + }; + + add_stats_layouts(tr("ALL TIME"), all_); + add_stats_layouts(tr("PAST WEEK"), week_); + add_stats_layouts(tr("FROGPILOT"), frogPilot_, true); + + if (auto dongleId = getDongleId()) { + QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/stats"; + RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_DriveStats", 30); + QObject::connect(repeater, &RequestRepeater::requestDone, this, &DriveStats::parseResponse); + } + + setStyleSheet(R"( + DriveStats { + background-color: #333333; + border-radius: 10px; + } + + QLabel[type="title"] { font-size: 50px; font-weight: 500; } + QLabel[type="frogpilot_title"] { font-size: 50px; font-weight: 500; color: #178643; } + QLabel[type="number"] { font-size: 65px; font-weight: 400; } + QLabel[type="unit"] { font-size: 50px; font-weight: 300; color: #A0A0A0; } + )"); +} + +void DriveStats::updateStats() { + QJsonObject json = stats_.object(); + + auto updateFrogPilot = [this](const QJsonObject& obj, StatsLabels& labels) { + labels.routes->setText(QString::number(paramsTracking.getInt("FrogPilotDrives"))); + labels.distance->setText(QString::number(int(paramsTracking.getFloat("FrogPilotKilometers") * (metric_ ? 1 : KM_TO_MILE)))); + labels.distance_unit->setText(getDistanceUnit()); + labels.hours->setText(QString::number(int(paramsTracking.getFloat("FrogPilotMinutes") / 60))); + }; + + updateFrogPilot(json["frogpilot"].toObject(), frogPilot_); + + auto update = [=](const QJsonObject& obj, StatsLabels& labels) { + labels.routes->setText(QString::number((int)obj["routes"].toDouble())); + labels.distance->setText(QString::number(int(obj["distance"].toDouble() * (metric_ ? MILE_TO_KM : 1)))); + labels.distance_unit->setText(getDistanceUnit()); + labels.hours->setText(QString::number((int)(obj["minutes"].toDouble() / 60))); + }; + + update(json["all"].toObject(), all_); + update(json["week"].toObject(), week_); +} + +void DriveStats::parseResponse(const QString& response, bool success) { + if (!success) return; + + QJsonDocument doc = QJsonDocument::fromJson(response.trimmed().toUtf8()); + if (doc.isNull()) { + qDebug() << "JSON Parse failed on getting past drives statistics"; + return; + } + stats_ = doc; + updateStats(); +} + +void DriveStats::showEvent(QShowEvent* event) { + metric_ = params.getBool("IsMetric"); + updateStats(); +} diff --git a/selfdrive/ui/qt/widgets/drive_stats.h b/selfdrive/ui/qt/widgets/drive_stats.h new file mode 100644 index 0000000..25fd9b3 --- /dev/null +++ b/selfdrive/ui/qt/widgets/drive_stats.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +#include "common/params.h" + +class DriveStats : public QFrame { + Q_OBJECT + +public: + explicit DriveStats(QWidget* parent = 0); + +private: + void showEvent(QShowEvent *event) override; + void updateStats(); + inline QString getDistanceUnit() const { return metric_ ? tr("KM") : tr("Miles"); } + + bool metric_; + Params params; + Params paramsTracking{"/persist/tracking"}; + QJsonDocument stats_; + struct StatsLabels { + QLabel *routes, *distance, *distance_unit, *hours; + } all_, week_, frogPilot_; + +private slots: + void parseResponse(const QString &response, bool success); +};