From 9f0611507e1f0d54d959a0a6e6ad3ab557f85170 Mon Sep 17 00:00:00 2001 From: grekiki Date: Fri, 18 Dec 2020 13:29:20 +0100 Subject: [PATCH] Qt Offroad stats (#19498) * probably broke a lot, need the commit though * finally works * make it work... * ... * kind of works * better styling * all should work * tiny cleanup * temp * looks nicer * create JWT in C++ * kilometers -> km * use correct free methods * dont put code in assert statement * Build JWT payload dynamically * get dongle id once * include cleanup * Remove qDebug * Update drive_stats.cc Github is a nice editor :) * swap week and all and fix sconscript * install openssl * openssl include dirs on mac * is this where openssl is? * It's here * small cleanup Co-authored-by: Comma Device Co-authored-by: Willem Melching old-commit-hash: 5b26c97141b65e3bcbddaec7e6526d5f2f1c910e --- SConstruct | 3 +- common/basedir.py | 7 +- selfdrive/common/params.cc | 11 +- selfdrive/common/utilpp.h | 10 ++ selfdrive/ui/SConscript | 16 ++- selfdrive/ui/qt/home.cc | 6 +- selfdrive/ui/qt/offroad/wifi.cc | 2 +- selfdrive/ui/qt/widgets/drive_stats.cc | 173 ++++++++++++++++++++++++ selfdrive/ui/qt/widgets/drive_stats.hpp | 20 +++ tools/mac_setup.sh | 1 + 10 files changed, 225 insertions(+), 24 deletions(-) create mode 100644 selfdrive/ui/qt/widgets/drive_stats.cc create mode 100644 selfdrive/ui/qt/widgets/drive_stats.hpp diff --git a/SConstruct b/SConstruct index ddca0fe59e..d2397a471c 100644 --- a/SConstruct +++ b/SConstruct @@ -89,10 +89,12 @@ else: "#cereal", "#selfdrive/common", "/usr/local/lib", + "/usr/local/opt/openssl/lib", "/System/Library/Frameworks/OpenGL.framework/Libraries", ] cflags += ["-DGL_SILENCE_DEPRECATION"] cxxflags += ["-DGL_SILENCE_DEPRECATION"] + cpppath += ["/usr/local/opt/openssl/include"] else: libpath = [ "#phonelibs/snpe/x86_64-linux-clang", @@ -296,4 +298,3 @@ if arch != "Darwin": if arch == "x86_64": SConscript(['tools/lib/index_log/SConscript']) - diff --git a/common/basedir.py b/common/basedir.py index a728eaac19..8be1cf6afa 100644 --- a/common/basedir.py +++ b/common/basedir.py @@ -1,8 +1,11 @@ import os -BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")) +from pathlib import Path from selfdrive.hardware import PC + +BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")) + if PC: - PERSIST = os.path.join(BASEDIR, "persist") + PERSIST = os.path.join(str(Path.home()), ".comma", "persist") else: PERSIST = "/persist" diff --git a/selfdrive/common/params.cc b/selfdrive/common/params.cc index ce079a9962..6a4e9169be 100644 --- a/selfdrive/common/params.cc +++ b/selfdrive/common/params.cc @@ -21,19 +21,10 @@ #include "common/utilpp.h" -std::string getenv_default(const char* env_var, const char * suffix, const char* default_val) { - const char* env_val = getenv(env_var); - if (env_val != NULL){ - return std::string(env_val) + std::string(suffix); - } else { - return std::string(default_val); - } -} - #if defined(QCOM) || defined(QCOM2) const std::string default_params_path = "/data/params"; #else -const std::string default_params_path = getenv_default("HOME", "/.comma/params", "/data/params"); +const std::string default_params_path = util::getenv_default("HOME", "/.comma/params", "/data/params"); #endif #if defined(QCOM) || defined(QCOM2) diff --git a/selfdrive/common/utilpp.h b/selfdrive/common/utilpp.h index e0547e0c16..23dc8433ee 100644 --- a/selfdrive/common/utilpp.h +++ b/selfdrive/common/utilpp.h @@ -60,6 +60,16 @@ inline std::string readlink(std::string path) { return ""; } +inline std::string getenv_default(const char* env_var, const char * suffix, const char* default_val) { + const char* env_val = getenv(env_var); + if (env_val != NULL){ + return std::string(env_val) + std::string(suffix); + } else { + return std::string(default_val); + } +} + + } struct unique_fd { diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index ce39c4fcbf..28b2c2bfee 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -15,6 +15,7 @@ if arch in ["x86_64", "Darwin", "larch64"]: QT_BASE + "include/QtCore", QT_BASE + "include/QtDBus", QT_BASE + "include/QtMultimedia", + QT_BASE + "include/QtNetwork", ] qt_env["LINKFLAGS"] += ["-F" + QT_BASE + "lib"] else: @@ -26,6 +27,7 @@ if arch in ["x86_64", "Darwin", "larch64"]: f"/usr/include/{real_arch}-linux-gnu/qt5/QtCore", f"/usr/include/{real_arch}-linux-gnu/qt5/QtDBus", f"/usr/include/{real_arch}-linux-gnu/qt5/QtMultimedia", + f"/usr/include/{real_arch}-linux-gnu/qt5/QtNetwork", f"/usr/include/{real_arch}-linux-gnu/qt5/QtGui/5.5.1/QtGui", f"/usr/include/{real_arch}-linux-gnu/qt5/QtGui/5.12.8/QtGui", ] @@ -57,9 +59,9 @@ if qt_env is None: LINKFLAGS=linkflags, LIBS=libs) else: - qt_libs = ["pthread"] + qt_libs = ["pthread", "ssl", "crypto"] - qt_modules = ["Widgets", "Gui", "Core", "DBus", "Multimedia"] + qt_modules = ["Widgets", "Gui", "Core", "DBus", "Multimedia", "Network"] if arch == "larch64": qt_libs += ["GLESv2", "wayland-client"] @@ -72,13 +74,13 @@ else: qt_libs += [f"Qt5{m}" for m in qt_modules] - qt_env.Library("qt_widgets", - ["qt/qt_window.cc", "qt/qt_sound.cc", "qt/widgets/keyboard.cc", "qt/widgets/input_field.cc", - "qt/offroad/wifi.cc", "qt/offroad/wifiManager.cc", "qt/widgets/toggle.cc"], + widgets = qt_env.Library("qt_widgets", + ["qt/qt_window.cc", "qt/qt_sound.cc", "qt/widgets/keyboard.cc", "qt/widgets/input_field.cc", "qt/widgets/drive_stats.cc", + "qt/offroad/wifi.cc", "qt/offroad/wifiManager.cc", "qt/widgets/toggle.cc", "qt/widgets/offroad_alerts.cc"], LIBS=qt_libs) - qt_libs.append("qt_widgets") + qt_libs.append(widgets) - qt_src = ["qt/ui.cc", "qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc", "qt/offroad/onboarding.cc", "qt/widgets/offroad_alerts.cc"] + src + qt_src = ["qt/ui.cc", "qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc", "qt/offroad/onboarding.cc"] + src qt_env.Program("_ui", qt_src, LIBS=qt_libs + libs) # spinner and text window diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index bc402eec8c..e300d14a37 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -16,6 +16,7 @@ #include "home.hpp" #include "paint.hpp" #include "qt_window.hpp" +#include "widgets/drive_stats.hpp" #define BACKLIGHT_DT 0.25 #define BACKLIGHT_TS 2.00 @@ -44,9 +45,8 @@ OffroadHome::OffroadHome(QWidget *parent) : QWidget(parent) { QObject::connect(alert_notification, SIGNAL(released()), this, SLOT(openAlerts())); main_layout->addWidget(alert_notification, 0, Qt::AlignTop | Qt::AlignRight); - // center - QLabel *drive = new QLabel("Drive me"); - drive->setStyleSheet(R"(font-size: 175px;)"); + DriveStats *drive = new DriveStats; + drive->setFixedSize(1000,800); center_layout->addWidget(drive); alerts_widget = new OffroadAlert(); diff --git a/selfdrive/ui/qt/offroad/wifi.cc b/selfdrive/ui/qt/offroad/wifi.cc index ae641f5091..c48581c3c5 100644 --- a/selfdrive/ui/qt/offroad/wifi.cc +++ b/selfdrive/ui/qt/offroad/wifi.cc @@ -99,7 +99,7 @@ void WifiUI::refresh() { if (!this->isVisible()) { return; } - + wifi->request_scan(); wifi->refreshNetworks(); ipv4->setText(wifi->ipv4_address); diff --git a/selfdrive/ui/qt/widgets/drive_stats.cc b/selfdrive/ui/qt/widgets/drive_stats.cc new file mode 100644 index 0000000000..937d0359a4 --- /dev/null +++ b/selfdrive/ui/qt/widgets/drive_stats.cc @@ -0,0 +1,173 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "drive_stats.hpp" +#include "common/params.h" +#include "common/utilpp.h" +double MILE_TO_KM = 1.60934; + + +#if defined(QCOM) || defined(QCOM2) +const std::string private_key_path = "/persist/comma/id_rsa"; +#else +const std::string private_key_path = util::getenv_default("HOME", "/.comma/persist/comma/id_rsa", "/persist/comma/id_rsa"); +#endif + +QByteArray rsa_sign(QByteArray data){ + auto file = QFile(private_key_path.c_str()); + bool r = file.open(QIODevice::ReadOnly); + assert(r); + + auto key = file.readAll(); + + BIO *mem = BIO_new_mem_buf(key.data(), key.size()); + assert(mem); + + RSA *rsa_private = PEM_read_bio_RSAPrivateKey(mem, NULL, NULL, NULL); + assert(rsa_private); + + auto sig = QByteArray(); + sig.resize(RSA_size(rsa_private)); + + unsigned int sig_len; + int ret = RSA_sign(NID_sha256, (unsigned char*)data.data(), data.size(), (unsigned char*)sig.data(), &sig_len, rsa_private); + + assert(ret == 1); + assert(sig_len == sig.size()); + + BIO_free(mem); + RSA_free(rsa_private); + + return sig; +} + +QString create_jwt(QString dongle_id, int expiry=3600){ + QJsonObject header; + header.insert("alg", "RS256"); + header.insert("typ", "JWT"); + + auto t = QDateTime::currentSecsSinceEpoch(); + QJsonObject payload; + payload.insert("identity", dongle_id); + payload.insert("nbf", t); + payload.insert("iat", t); + payload.insert("exp", t + expiry); + + QString jwt = + QJsonDocument(header).toJson(QJsonDocument::Compact).toBase64() + + '.' + + QJsonDocument(payload).toJson(QJsonDocument::Compact).toBase64(); + + auto hash = QCryptographicHash::hash(jwt.toUtf8(), QCryptographicHash::Sha256); + auto sig = rsa_sign(hash); + + jwt += '.' + sig.toBase64(); + + return jwt; +} + +QString bold(QString s) { + return "" + s + ""; +} + +QWidget *widget(QLayout *l){ + QWidget *q = new QWidget(); + q->setLayout(l); + return q; +} + +QWidget *build_stat(QString name, int stat){ + QVBoxLayout *layout = new QVBoxLayout; + layout->addWidget(new QLabel(bold(QString("%1").arg(stat))), 1, Qt::AlignCenter); + layout->addWidget(new QLabel(name),1, Qt::AlignCenter); + return widget(layout); +} + +void DriveStats::replyFinished(QNetworkReply *l){ + QString answer = l->readAll(); + answer.chop(1); + + QJsonDocument doc = QJsonDocument::fromJson(answer.toUtf8()); + if (doc.isNull()) { + qDebug() << "JSON Parse failed"; + } + QString IsMetric = QString::fromStdString(Params().get("IsMetric")); + bool metric = (IsMetric =="1"); + + QJsonObject json = doc.object(); + auto all = json["all"].toObject(); + auto week = json["week"].toObject(); + + QGridLayout *gl = new QGridLayout(); + + int all_distance = all["distance"].toDouble()*(metric ? MILE_TO_KM : 1); + gl->addWidget(new QLabel(bold("ALL TIME")), 0, 0, 1, 3); + gl->addWidget(build_stat("DRIVES", all["routes"].toDouble()), 1, 0, 3, 1); + gl->addWidget(build_stat(metric ? "KM" : "MILES", all_distance), 1, 1, 3, 1); + gl->addWidget(build_stat("HOURS", all["minutes"].toDouble() / 60), 1, 2, 3, 1); + + QFrame *lineA = new QFrame; + lineA->setFrameShape(QFrame::HLine); + lineA->setFrameShadow(QFrame::Sunken); + lineA->setProperty("class", "line"); + gl->addWidget(lineA, 5, 0, 1, 3); + + int week_distance = week["distance"].toDouble()*(metric ? MILE_TO_KM : 1); + gl->addWidget(new QLabel(bold("PAST WEEK")), 6, 0, 1, 3); + gl->addWidget(build_stat("DRIVES", week["routes"].toDouble()), 7, 0, 3, 1); + gl->addWidget(build_stat(metric ? "KM" : "MILES", week_distance), 7, 1, 3, 1); + gl->addWidget(build_stat("HOURS", week["minutes"].toDouble() / 60), 7, 2, 3, 1); + + + f->setLayout(gl); + f->setStyleSheet(R"( + [class="line"]{ + border: 2px solid white; + } + [class="outside"]{ + border-radius: 20px; + border: 2px solid white; + padding: 10px; + } + QLabel{ + font-size: 70px; + font-weight: 200; + } + )"); + +} +DriveStats::DriveStats(QWidget *parent) : QWidget(parent){ + f = new QFrame; + f->setProperty("class", "outside"); + QVBoxLayout *v = new QVBoxLayout; + v->addWidget(f); + setLayout(v); + + QString dongle_id = QString::fromStdString(Params().get("DongleId")); + QString token = create_jwt(dongle_id); + + QNetworkAccessManager *manager = new QNetworkAccessManager(this); + connect(manager, &QNetworkAccessManager::finished, this, &DriveStats::replyFinished); + + QNetworkRequest request; + request.setUrl(QUrl("https://api.commadotai.com/v1.1/devices/" + dongle_id + "/stats")); + request.setRawHeader("Authorization", ("JWT "+token).toUtf8()); + + manager->get(request); +} diff --git a/selfdrive/ui/qt/widgets/drive_stats.hpp b/selfdrive/ui/qt/widgets/drive_stats.hpp new file mode 100644 index 0000000000..9b607f999b --- /dev/null +++ b/selfdrive/ui/qt/widgets/drive_stats.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + + +class DriveStats : public QWidget { + Q_OBJECT + +public: + explicit DriveStats(QWidget *parent = 0); + +private: + QFrame *f; + void replyFinished(QNetworkReply *l); +}; diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 5160813954..f2ce98f826 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -15,6 +15,7 @@ brew install capnp \ libusb \ libtool \ llvm \ + openssl \ pyenv \ qt5 \ zeromq