diff --git a/selfdrive/assets/img_circled_check.svg b/selfdrive/assets/img_circled_check.svg
new file mode 100644
index 0000000000..27c37395b2
--- /dev/null
+++ b/selfdrive/assets/img_circled_check.svg
@@ -0,0 +1,4 @@
+
diff --git a/selfdrive/assets/img_continue_triangle.svg b/selfdrive/assets/img_continue_triangle.svg
new file mode 100644
index 0000000000..20f9e45dcf
--- /dev/null
+++ b/selfdrive/assets/img_continue_triangle.svg
@@ -0,0 +1,3 @@
+
diff --git a/selfdrive/ui/qt/api.cc b/selfdrive/ui/qt/api.cc
index e604a5c09b..330a436a68 100644
--- a/selfdrive/ui/qt/api.cc
+++ b/selfdrive/ui/qt/api.cc
@@ -67,18 +67,23 @@ QString create_jwt(const QJsonObject &payloads, int expiry) {
} // namespace CommaApi
-HttpRequest::HttpRequest(QObject *parent, const QString &requestURL, bool create_jwt_) : create_jwt(create_jwt_), QObject(parent) {
+HttpRequest::HttpRequest(QObject *parent, const QString &requestURL, bool create_jwt_,
+ int timeout) : create_jwt(create_jwt_), QObject(parent) {
networkAccessManager = new QNetworkAccessManager(this);
reply = NULL;
networkTimer = new QTimer(this);
networkTimer->setSingleShot(true);
- networkTimer->setInterval(20000);
+ networkTimer->setInterval(timeout);
connect(networkTimer, &QTimer::timeout, this, &HttpRequest::requestTimeout);
sendRequest(requestURL);
}
+bool HttpRequest::active() {
+ return reply != NULL;
+}
+
void HttpRequest::sendRequest(const QString &requestURL) {
QString token;
if(create_jwt) {
@@ -105,11 +110,13 @@ void HttpRequest::requestTimeout() {
// This function should always emit something
void HttpRequest::requestFinished() {
+ bool success = false;
if (reply->error() != QNetworkReply::OperationCanceledError) {
networkTimer->stop();
QString response = reply->readAll();
if (reply->error() == QNetworkReply::NoError) {
+ success = true;
emit receivedResponse(response);
} else {
qDebug() << reply->errorString();
@@ -118,6 +125,7 @@ void HttpRequest::requestFinished() {
} else {
emit timeoutResponse("timeout");
}
+ emit requestDone(success);
reply->deleteLater();
reply = NULL;
}
diff --git a/selfdrive/ui/qt/api.h b/selfdrive/ui/qt/api.h
index 8aa01ae2b5..584697a893 100644
--- a/selfdrive/ui/qt/api.h
+++ b/selfdrive/ui/qt/api.h
@@ -20,8 +20,9 @@ class HttpRequest : public QObject {
Q_OBJECT
public:
- explicit HttpRequest(QObject* parent, const QString &requestURL, bool create_jwt_ = true);
+ explicit HttpRequest(QObject* parent, const QString &requestURL, bool create_jwt_ = true, int timeout = 20000);
void sendRequest(const QString &requestURL);
+ bool active();
protected:
QNetworkReply *reply;
@@ -36,6 +37,7 @@ private slots:
void requestFinished();
signals:
+ void requestDone(bool success);
void receivedResponse(const QString &response);
void failedResponse(const QString &errorString);
void timeoutResponse(const QString &errorString);
diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc
index e6bbf64f0e..e06e677a6f 100644
--- a/selfdrive/ui/qt/setup/setup.cc
+++ b/selfdrive/ui/qt/setup/setup.cc
@@ -10,20 +10,21 @@
#include
#include "selfdrive/hardware/hw.h"
-
+#include "selfdrive/ui/qt/api.h"
+#include "selfdrive/ui/qt/qt_window.h"
#include "selfdrive/ui/qt/offroad/networking.h"
#include "selfdrive/ui/qt/widgets/input.h"
-#include "selfdrive/ui/qt/qt_window.h"
-#define USER_AGENT "AGNOSSetup-0.1"
+const char* USER_AGENT = "AGNOSSetup-0.1";
+const QString DASHCAM_URL = "https://dashcam.comma.ai";
-void Setup::download(QString url) {
- QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);
- setCurrentIndex(count() - 2);
+const QString TEST_URL = "https://api.commadotai.com/v1/me";
+void Setup::download(QString url) {
CURL *curl = curl_easy_init();
if (!curl) {
- emit downloadFailed();
+ emit finished(false);
+ return;
}
char tmpfile[] = "/tmp/installer_XXXXXX";
@@ -38,121 +39,245 @@ void Setup::download(QString url) {
int ret = curl_easy_perform(curl);
if (ret != CURLE_OK) {
- emit downloadFailed();
+ emit finished(false);
+ return;
}
curl_easy_cleanup(curl);
fclose(fp);
rename(tmpfile, "/tmp/installer");
+ emit finished(true);
}
-QLabel * title_label(QString text) {
- QLabel *l = new QLabel(text);
- l->setStyleSheet(R"(
- font-size: 100px;
- font-weight: 500;
- )");
- return l;
-}
-
-QWidget * Setup::build_page(QString title, QWidget *content, bool next, bool prev) {
+QWidget * Setup::getting_started() {
QWidget *widget = new QWidget();
- QVBoxLayout *main_layout = new QVBoxLayout(widget);
- main_layout->setMargin(50);
- main_layout->addWidget(title_label(title), 0, Qt::AlignLeft | Qt::AlignTop);
+ QHBoxLayout *main_layout = new QHBoxLayout(widget);
+ main_layout->setMargin(0);
- main_layout->addWidget(content);
+ QVBoxLayout *vlayout = new QVBoxLayout();
+ vlayout->setContentsMargins(165, 280, 100, 0);
+ main_layout->addLayout(vlayout);
- QHBoxLayout *nav_layout = new QHBoxLayout();
+ QLabel *title = new QLabel("Getting Started");
+ title->setStyleSheet("font-size: 90px; font-weight: 500;");
+ vlayout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
- if (prev) {
- QPushButton *back_btn = new QPushButton("Back");
- nav_layout->addWidget(back_btn, 1, Qt::AlignBottom | Qt::AlignLeft);
- QObject::connect(back_btn, &QPushButton::released, this, &Setup::prevPage);
- }
+ vlayout->addSpacing(90);
+ QLabel *desc = new QLabel("Before we get on the road, let’s finish installation and cover some details.");
+ desc->setWordWrap(true);
+ desc->setStyleSheet("font-size: 80px; font-weight: 300;");
+ vlayout->addWidget(desc, 0, Qt::AlignTop | Qt::AlignLeft);
- if (next) {
- QPushButton *continue_btn = new QPushButton("Continue");
- nav_layout->addWidget(continue_btn, 0, Qt::AlignBottom | Qt::AlignRight);
- QObject::connect(continue_btn, &QPushButton::released, this, &Setup::nextPage);
- }
+ vlayout->addStretch();
- main_layout->addLayout(nav_layout, 0);
- return widget;
-}
+ QPushButton *btn = new QPushButton();
+ btn->setIcon(QIcon("../../../assets/img_continue_triangle.svg"));
+ btn->setIconSize(QSize(54, 106));
+ btn->setFixedSize(310, 1080);
+ btn->setProperty("primary", true);
+ btn->setStyleSheet("border: none;");
+ main_layout->addWidget(btn, 0, Qt::AlignRight);
+ QObject::connect(btn, &QPushButton::clicked, this, &Setup::nextPage);
-QWidget * Setup::getting_started() {
- QLabel *body = new QLabel("Before we get on the road, let's finish\ninstallation and cover some details.");
- body->setAlignment(Qt::AlignHCenter);
- body->setStyleSheet(R"(font-size: 80px;)");
- return build_page("Getting Started", body, true, false);
+ return widget;
}
QWidget * Setup::network_setup() {
+ QWidget *widget = new QWidget();
+ QVBoxLayout *main_layout = new QVBoxLayout(widget);
+ main_layout->setContentsMargins(55, 50, 55, 50);
+
+ // title
+ QLabel *title = new QLabel("Connect to WiFi");
+ title->setStyleSheet("font-size: 90px; font-weight: 500;");
+ main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
+
+ // wifi widget
Networking *wifi = new Networking(this, false);
- return build_page("Connect to WiFi", wifi, true, true);
+ main_layout->addWidget(wifi, 1);
+
+ // back + continue buttons
+ QHBoxLayout *blayout = new QHBoxLayout;
+ main_layout->addLayout(blayout);
+ blayout->setSpacing(50);
+
+ QPushButton *back = new QPushButton("Back");
+ back->setObjectName("navBtn");
+ QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
+ blayout->addWidget(back);
+
+ QPushButton *cont = new QPushButton();
+ cont->setObjectName("navBtn");
+ QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage);
+ blayout->addWidget(cont);
+
+ // setup timer for testing internet connection
+ HttpRequest *request = new HttpRequest(this, TEST_URL, false, 2500);
+ QObject::connect(request, &HttpRequest::requestDone, [=](bool success) {
+ cont->setEnabled(success);
+ cont->setText(success ? "Continue" : "Waiting for internet");
+ repaint();
+ });
+ QTimer *timer = new QTimer(this);
+ QObject::connect(timer, &QTimer::timeout, [=]() {
+ if (!request->active() && cont->isVisible()) {
+ request->sendRequest(TEST_URL);
+ }
+ });
+ timer->start(1000);
+
+ return widget;
+}
+
+QWidget * radio_button(QString title, QButtonGroup *group) {
+ QPushButton *btn = new QPushButton(title);
+ btn->setCheckable(true);
+ group->addButton(btn);
+ btn->setStyleSheet(R"(
+ QPushButton {
+ height: 230;
+ padding-left: 100px;
+ padding-right: 100px;
+ text-align: left;
+ font-size: 80px;
+ font-weight: 400;
+ border-radius: 10px;
+ background-color: #4F4F4F;
+ }
+ QPushButton:checked {
+ background-color: #465BEA;
+ }
+ )");
+
+ // checkmark icon
+ QPixmap pix("../../../assets/img_circled_check.svg");
+ btn->setIcon(pix);
+ btn->setIconSize(QSize(0, 0));
+ btn->setLayoutDirection(Qt::RightToLeft);
+ QObject::connect(btn, &QPushButton::toggled, [=](bool checked) {
+ btn->setIconSize(checked ? QSize(104, 104) : QSize(0, 0));
+ });
+ return btn;
}
QWidget * Setup::software_selection() {
QWidget *widget = new QWidget();
QVBoxLayout *main_layout = new QVBoxLayout(widget);
+ main_layout->setContentsMargins(55, 50, 55, 50);
+ main_layout->setSpacing(0);
- QPushButton *dashcam_btn = new QPushButton("Dashcam");
- main_layout->addWidget(dashcam_btn);
- QObject::connect(dashcam_btn, &QPushButton::released, this, [=]() {
- this->download("https://dashcam.comma.ai");
- });
+ // title
+ QLabel *title = new QLabel("Choose Software to Install");
+ title->setStyleSheet("font-size: 90px; font-weight: 500;");
+ main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
main_layout->addSpacing(50);
- QPushButton *custom_btn = new QPushButton("Custom");
- main_layout->addWidget(custom_btn);
- QObject::connect(custom_btn, &QPushButton::released, this, [=]() {
- QString input_url = InputDialog::getText("Enter URL", this);
- if (input_url.size()) {
- this->download(input_url);
+ // dashcam + custom radio buttons
+ QButtonGroup *group = new QButtonGroup(widget);
+ group->setExclusive(true);
+
+ QWidget *dashcam = radio_button("Dashcam", group);
+ main_layout->addWidget(dashcam);
+
+ main_layout->addSpacing(30);
+
+ QWidget *custom = radio_button("Custom Software", group);
+ main_layout->addWidget(custom);
+
+ main_layout->addStretch();
+
+ // back + continue buttons
+ QHBoxLayout *blayout = new QHBoxLayout;
+ main_layout->addLayout(blayout);
+ blayout->setSpacing(50);
+
+ QPushButton *back = new QPushButton("Back");
+ back->setObjectName("navBtn");
+ QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
+ blayout->addWidget(back);
+
+ QPushButton *cont = new QPushButton("Continue");
+ cont->setObjectName("navBtn");
+ cont->setEnabled(false);
+ blayout->addWidget(cont);
+
+ QObject::connect(cont, &QPushButton::clicked, [=]() {
+ QString url = DASHCAM_URL;
+ if (group->checkedButton() != dashcam) {
+ url = InputDialog::getText("Enter URL", this);
+ }
+ if (!url.isEmpty()) {
+ setCurrentWidget(downloading_widget);
+ QTimer::singleShot(100, this, [=]() {
+ download(url);
+ });
}
});
- return build_page("Choose Software", widget, false, true);
+
+ connect(group, QOverload::of(&QButtonGroup::buttonClicked), [=](QAbstractButton *btn) {
+ btn->setChecked(true);
+ cont->setEnabled(true);
+ });
+
+ return widget;
}
QWidget * Setup::downloading() {
QWidget *widget = new QWidget();
QVBoxLayout *main_layout = new QVBoxLayout(widget);
- main_layout->addWidget(title_label("Downloading..."), 0, Qt::AlignCenter);
+ QLabel *txt = new QLabel("Downloading...");
+ txt->setStyleSheet("font-size: 90px; font-weight: 500;");
+ main_layout->addWidget(txt, 0, Qt::AlignCenter);
return widget;
}
QWidget * Setup::download_failed() {
QWidget *widget = new QWidget();
QVBoxLayout *main_layout = new QVBoxLayout(widget);
- main_layout->setContentsMargins(50, 50, 50, 50);
- main_layout->addWidget(title_label("Download Failed"), 0, Qt::AlignLeft | Qt::AlignTop);
+ main_layout->setContentsMargins(55, 225, 55, 55);
+ main_layout->setSpacing(0);
+
+ QLabel *title = new QLabel("Download Failed");
+ title->setStyleSheet("font-size: 90px; font-weight: 500;");
+ main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
+
+ main_layout->addSpacing(67);
- QLabel *body = new QLabel("Ensure the entered URL is valid, and the device's network connection is good.");
+ QLabel *body = new QLabel("Ensure the entered URL is valid, and the device’s internet connection is good.");
body->setWordWrap(true);
- body->setAlignment(Qt::AlignHCenter);
- body->setStyleSheet(R"(font-size: 80px;)");
+ body->setAlignment(Qt::AlignTop | Qt::AlignLeft);
+ body->setStyleSheet("font-size: 80px; font-weight: 300; margin-right: 100px;");
main_layout->addWidget(body);
- QHBoxLayout *nav_layout = new QHBoxLayout();
+ main_layout->addStretch();
- QPushButton *reboot_btn = new QPushButton("Reboot");
- nav_layout->addWidget(reboot_btn, 0, Qt::AlignBottom | Qt::AlignLeft);
- QObject::connect(reboot_btn, &QPushButton::released, this, [=]() {
- if (Hardware::TICI()) {
- std::system("sudo reboot");
- }
+ // reboot + start over buttons
+ QHBoxLayout *blayout = new QHBoxLayout();
+ blayout->setSpacing(50);
+ main_layout->addLayout(blayout, 0);
+
+ QPushButton *reboot = new QPushButton("Reboot device");
+ reboot->setObjectName("navBtn");
+ blayout->addWidget(reboot);
+ QObject::connect(reboot, &QPushButton::released, this, [=]() {
+ Hardware::reboot();
});
- QPushButton *restart_btn = new QPushButton("Start over");
- nav_layout->addWidget(restart_btn, 0, Qt::AlignBottom | Qt::AlignRight);
- QObject::connect(restart_btn, &QPushButton::released, this, [=]() {
+ QPushButton *restart = new QPushButton("Start over");
+ restart->setObjectName("navBtn");
+ restart->setProperty("primary", true);
+ blayout->addWidget(restart);
+ QObject::connect(restart, &QPushButton::released, this, [=]() {
setCurrentIndex(0);
});
- main_layout->addLayout(nav_layout, 0);
+ widget->setStyleSheet(R"(
+ QLabel {
+ margin-left: 117;
+ }
+ )");
return widget;
}
@@ -168,24 +293,47 @@ Setup::Setup(QWidget *parent) : QStackedWidget(parent) {
addWidget(getting_started());
addWidget(network_setup());
addWidget(software_selection());
- addWidget(downloading());
- addWidget(download_failed());
- QObject::connect(this, &Setup::downloadFailed, this, &Setup::nextPage);
+ downloading_widget = downloading();
+ addWidget(downloading_widget);
+
+ failed_widget = download_failed();
+ addWidget(failed_widget);
+ QObject::connect(this, &Setup::finished, [=](bool success) {
+ // hide setup on success
+ qDebug() << "finished" << success;
+ setVisible(!success);
+ setCurrentWidget(failed_widget);
+ });
+
+ // TODO: revisit pressed bg color
setStyleSheet(R"(
* {
- font-family: Inter;
color: white;
+ font-family: Inter;
+ }
+ Setup {
background-color: black;
}
- QPushButton {
- padding: 50px;
- padding-right: 100px;
- padding-left: 100px;
- border: 7px solid white;
- border-radius: 20px;
- font-size: 50px;
+ QPushButton#navBtn {
+ height: 160;
+ font-size: 55px;
+ font-weight: 400;
+ border-radius: 10px;
+ background-color: #333333;
+ }
+ QPushButton#navBtn:disabled {
+ color: #808080;
+ }
+ QPushButton#navBtn:pressed {
+ background-color: #444444;
+ }
+ QPushButton[primary='true'], #navBtn[primary='true'] {
+ background-color: #465BEA;
+ }
+ QPushButton[primary='true']:pressed, #navBtn:pressed[primary='true'] {
+ background-color: #3049F4;
}
)");
}
diff --git a/selfdrive/ui/qt/setup/setup.h b/selfdrive/ui/qt/setup/setup.h
index 92a8d98180..af5786da9b 100644
--- a/selfdrive/ui/qt/setup/setup.h
+++ b/selfdrive/ui/qt/setup/setup.h
@@ -14,14 +14,14 @@ private:
QWidget *getting_started();
QWidget *network_setup();
QWidget *software_selection();
- QWidget *custom_software();
QWidget *downloading();
QWidget *download_failed();
- QWidget *build_page(QString title, QWidget *content, bool next, bool prev);
+ QWidget *failed_widget;
+ QWidget *downloading_widget;
signals:
- void downloadFailed();
+ void finished(bool success);
public slots:
void nextPage();