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 <device@comma.ai>
Co-authored-by: Willem Melching <willem.melching@gmail.com>
old-commit-hash: 5b26c97141
commatwo_master
parent
c81ac2c800
commit
9f0611507e
10 changed files with 225 additions and 24 deletions
@ -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" |
||||
|
@ -0,0 +1,173 @@ |
||||
#include <iostream> |
||||
|
||||
#include <QFile> |
||||
#include <QDebug> |
||||
#include <QHBoxLayout> |
||||
#include <QLabel> |
||||
#include <QPixmap> |
||||
#include <QPushButton> |
||||
#include <QLineEdit> |
||||
#include <QJsonDocument> |
||||
#include <QJsonObject> |
||||
#include <QNetworkAccessManager> |
||||
#include <QNetworkRequest> |
||||
#include <QCryptographicHash> |
||||
|
||||
#include <openssl/rsa.h> |
||||
#include <openssl/bio.h> |
||||
#include <openssl/pem.h> |
||||
|
||||
#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 "<b>" + s + "</b>"; |
||||
} |
||||
|
||||
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); |
||||
} |
@ -0,0 +1,20 @@ |
||||
#pragma once |
||||
|
||||
#include <QWidget> |
||||
#include <QButtonGroup> |
||||
#include <QVBoxLayout> |
||||
#include <QStackedWidget> |
||||
#include <QTimer> |
||||
#include <QNetworkReply> |
||||
|
||||
|
||||
class DriveStats : public QWidget { |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit DriveStats(QWidget *parent = 0); |
||||
|
||||
private: |
||||
QFrame *f; |
||||
void replyFinished(QNetworkReply *l); |
||||
}; |
Loading…
Reference in new issue