From 80ae6a7e1b1d1ec231c4719b677fd08b22818ce1 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 26 Mar 2021 20:29:30 +0800 Subject: [PATCH] Qt: cache home screen state (#20395) * cache drive stats * don't parse response if it's the same as previous * remove blank line * cleanup * update stats in place * validate json * add DriveStats to params.pyx * cleanup * cleanup * remove //#include * simplify * add cache to RequestRepeater * rename CachedDriveStats to ApiCacheDriveStats * rename ApiCacheDriveStats to ApiCache_DriveStats Co-authored-by: Willem Melching --- common/params_pyx.pyx | 1 + selfdrive/ui/qt/api.cc | 14 +++- selfdrive/ui/qt/api.hpp | 3 +- selfdrive/ui/qt/widgets/drive_stats.cc | 100 ++++++++---------------- selfdrive/ui/qt/widgets/drive_stats.hpp | 8 +- 5 files changed, 52 insertions(+), 74 deletions(-) diff --git a/common/params_pyx.pyx b/common/params_pyx.pyx index 2add3a077..81368a401 100755 --- a/common/params_pyx.pyx +++ b/common/params_pyx.pyx @@ -15,6 +15,7 @@ cdef enum TxType: keys = { b"AccessToken": [TxType.CLEAR_ON_MANAGER_START], + b"ApiCache_DriveStats": [TxType.PERSISTENT], b"AthenadPid": [TxType.PERSISTENT], b"CalibrationParams": [TxType.PERSISTENT], b"CarBatteryCapacity": [TxType.PERSISTENT], diff --git a/selfdrive/ui/qt/api.cc b/selfdrive/ui/qt/api.cc index 48726b989..c20a68872 100644 --- a/selfdrive/ui/qt/api.cc +++ b/selfdrive/ui/qt/api.cc @@ -76,8 +76,8 @@ QString CommaApi::create_jwt() { return create_jwt(*(new QVector>())); } -RequestRepeater::RequestRepeater(QWidget* parent, QString requestURL, int period_seconds, QVector> payloads, bool disableWithScreen) - : disableWithScreen(disableWithScreen), QObject(parent) { +RequestRepeater::RequestRepeater(QWidget* parent, QString requestURL, int period_seconds, const QString &cache_key, QVector> payloads, bool disableWithScreen) + : disableWithScreen(disableWithScreen), cache_key(cache_key), QObject(parent) { networkAccessManager = new QNetworkAccessManager(this); reply = NULL; @@ -90,6 +90,12 @@ RequestRepeater::RequestRepeater(QWidget* parent, QString requestURL, int period networkTimer->setSingleShot(true); networkTimer->setInterval(20000); connect(networkTimer, SIGNAL(timeout()), this, SLOT(requestTimeout())); + + if (!cache_key.isEmpty()) { + if (std::string cached_resp = Params().get(cache_key.toStdString()); !cached_resp.empty()) { + QTimer::singleShot(0, [=]() { emit receivedResponse(QString::fromStdString(cached_resp)); }); + } + } } void RequestRepeater::sendRequest(QString requestURL, QVector> payloads){ @@ -126,6 +132,10 @@ void RequestRepeater::requestFinished(){ networkTimer->stop(); QString response = reply->readAll(); if (reply->error() == QNetworkReply::NoError) { + // save to cache + if (!cache_key.isEmpty()) { + Params().write_db_value(cache_key.toStdString(), response.toStdString()); + } emit receivedResponse(response); } else { qDebug() << reply->errorString(); diff --git a/selfdrive/ui/qt/api.hpp b/selfdrive/ui/qt/api.hpp index 43bf7b13b..408adcac9 100644 --- a/selfdrive/ui/qt/api.hpp +++ b/selfdrive/ui/qt/api.hpp @@ -33,7 +33,7 @@ class RequestRepeater : public QObject { Q_OBJECT public: - explicit RequestRepeater(QWidget* parent, QString requestURL, int period = 10, QVector> payloads = *(new QVector>()), bool disableWithScreen = true); + explicit RequestRepeater(QWidget* parent, QString requestURL, int period = 10, const QString &cache_key = "", QVector> payloads = *(new QVector>()), bool disableWithScreen = true); bool active = true; private: @@ -41,6 +41,7 @@ private: QNetworkReply* reply; QNetworkAccessManager* networkAccessManager; QTimer* networkTimer; + QString cache_key; void sendRequest(QString requestURL, QVector> payloads); private slots: diff --git a/selfdrive/ui/qt/widgets/drive_stats.cc b/selfdrive/ui/qt/widgets/drive_stats.cc index 1c2af95a9..b745291d9 100644 --- a/selfdrive/ui/qt/widgets/drive_stats.cc +++ b/selfdrive/ui/qt/widgets/drive_stats.cc @@ -1,8 +1,6 @@ #include #include #include -#include -#include #include #include "api.hpp" @@ -11,94 +9,62 @@ const double MILE_TO_KM = 1.60934; -void clearLayouts(QLayout* layout) { - while (QLayoutItem* item = layout->takeAt(0)) { - if (QWidget* widget = item->widget()) { - widget->deleteLater(); - } - if (QLayout* childLayout = item->layout()) { - clearLayouts(childLayout); - } - delete item; - } -} - -QLayout* build_stat(QString name, int stat) { +static QLayout* build_stat_layout(QLabel** metric, const QString& name) { QVBoxLayout* layout = new QVBoxLayout; layout->setMargin(0); - - QLabel* metric = new QLabel(QString("%1").arg(stat)); - metric->setStyleSheet(R"( - font-size: 80px; - font-weight: 600; - )"); - layout->addWidget(metric, 0, Qt::AlignLeft); + *metric = new QLabel("0"); + (*metric)->setStyleSheet("font-size: 80px; font-weight: 600;"); + layout->addWidget(*metric, 0, Qt::AlignLeft); QLabel* label = new QLabel(name); - label->setStyleSheet(R"( - font-size: 45px; - font-weight: 500; - )"); + label->setStyleSheet("font-size: 45px; font-weight: 500;"); layout->addWidget(label, 0, Qt::AlignLeft); - return layout; } -void DriveStats::parseError(QString response) { - clearLayouts(vlayout); - vlayout->addWidget(new QLabel("No Internet connection"), 0, Qt::AlignCenter); -} - void DriveStats::parseResponse(QString response) { - response.chop(1); - clearLayouts(vlayout); + response = response.trimmed(); QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8()); if (doc.isNull()) { qDebug() << "JSON Parse failed on getting past drives statistics"; return; } + auto update = [](const QJsonObject &obj, StatsLabels& labels, bool metric) { + labels.routes->setText(QString::number((int)obj["routes"].toDouble())); + labels.distance->setText(QString::number(obj["distance"].toDouble() * (metric ? MILE_TO_KM : 1))); + labels.hours->setText(QString::number((int)(obj["minutes"].toDouble() / 60))); + }; + bool metric = Params().read_db_bool("IsMetric"); - QJsonObject json = doc.object(); - auto all = json["all"].toObject(); - auto week = json["week"].toObject(); - - QGridLayout* gl = new QGridLayout(); - gl->setMargin(0); - - int all_distance = all["distance"].toDouble() * (metric ? MILE_TO_KM : 1); - gl->addWidget(new QLabel("ALL TIME"), 0, 0, 1, 3); - gl->addLayout(build_stat("DRIVES", all["routes"].toDouble()), 1, 0, 3, 1); - gl->addLayout(build_stat(metric ? "KM" : "MILES", all_distance), 1, 1, 3, 1); - gl->addLayout(build_stat("HOURS", all["minutes"].toDouble() / 60), 1, 2, 3, 1); - - int week_distance = week["distance"].toDouble() * (metric ? MILE_TO_KM : 1); - gl->addWidget(new QLabel("PAST WEEK"), 6, 0, 1, 3); - gl->addLayout(build_stat("DRIVES", week["routes"].toDouble()), 7, 0, 3, 1); - gl->addLayout(build_stat(metric ? "KM" : "MILES", week_distance), 7, 1, 3, 1); - gl->addLayout(build_stat("HOURS", week["minutes"].toDouble() / 60), 7, 2, 3, 1); - - QWidget* q = new QWidget; - q->setLayout(gl); - vlayout->addWidget(q); + update(json["all"].toObject(), all_, metric); + update(json["week"].toObject(), week_, metric); } DriveStats::DriveStats(QWidget* parent) : QWidget(parent) { - vlayout = new QVBoxLayout(this); - vlayout->setMargin(0); - setLayout(vlayout); - setStyleSheet(R"( - QLabel { - font-size: 48px; - font-weight: 500; - } - )"); + setStyleSheet("QLabel {font-size: 48px; font-weight: 500;}"); + + auto add_stats_layouts = [&](QGridLayout* gl, StatsLabels& labels, int row, const char* distance_unit) { + gl->addLayout(build_stat_layout(&labels.routes, "DRIVES"), row, 0, 3, 1); + gl->addLayout(build_stat_layout(&labels.distance, distance_unit), row, 1, 3, 1); + gl->addLayout(build_stat_layout(&labels.hours, "HOURS"), row, 2, 3, 1); + }; + + const char* distance_unit = Params().read_db_bool("IsMetric") ? "KM" : "MILES"; + QGridLayout* gl = new QGridLayout(); + gl->setMargin(0); + gl->addWidget(new QLabel("ALL TIME"), 0, 0, 1, 3); + add_stats_layouts(gl, all_, 1, distance_unit); + gl->addWidget(new QLabel("PAST WEEK"), 6, 0, 1, 3); + add_stats_layouts(gl, week_, 7, distance_unit); + + QVBoxLayout* vlayout = new QVBoxLayout(this); + vlayout->addLayout(gl); // TODO: do we really need to update this frequently? QString dongleId = QString::fromStdString(Params().get("DongleId")); QString url = "https://api.commadotai.com/v1.1/devices/" + dongleId + "/stats"; - RequestRepeater* repeater = new RequestRepeater(this, url, 13); + RequestRepeater* repeater = new RequestRepeater(this, url, 13, "ApiCache_DriveStats"); QObject::connect(repeater, SIGNAL(receivedResponse(QString)), this, SLOT(parseResponse(QString))); - QObject::connect(repeater, SIGNAL(failedResponse(QString)), this, SLOT(parseError(QString))); } diff --git a/selfdrive/ui/qt/widgets/drive_stats.hpp b/selfdrive/ui/qt/widgets/drive_stats.hpp index 40cefbb4d..64b93cfe7 100644 --- a/selfdrive/ui/qt/widgets/drive_stats.hpp +++ b/selfdrive/ui/qt/widgets/drive_stats.hpp @@ -1,7 +1,6 @@ #pragma once -#include -#include +#include class DriveStats : public QWidget { Q_OBJECT @@ -10,9 +9,10 @@ public: explicit DriveStats(QWidget* parent = 0); private: - QVBoxLayout* vlayout; + struct StatsLabels { + QLabel *routes, *distance, *hours; + } all_, week_; private slots: - void parseError(QString response); void parseResponse(QString response); };