You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
489 lines
14 KiB
489 lines
14 KiB
#include "selfdrive/ui/qt/setup/setup.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
#include <QApplication>
|
|
#include <QLabel>
|
|
#include <QVBoxLayout>
|
|
|
|
#include <curl/curl.h>
|
|
|
|
#include "common/util.h"
|
|
#include "system/hardware/hw.h"
|
|
#include "selfdrive/ui/qt/api.h"
|
|
#include "selfdrive/ui/qt/qt_window.h"
|
|
#include "selfdrive/ui/qt/network/networking.h"
|
|
#include "selfdrive/ui/qt/util.h"
|
|
#include "selfdrive/ui/qt/widgets/input.h"
|
|
|
|
const std::string USER_AGENT = "AGNOSSetup-";
|
|
const QString OPENPILOT_URL = "https://openpilot.comma.ai";
|
|
|
|
bool is_elf(char *fname) {
|
|
FILE *fp = fopen(fname, "rb");
|
|
if (fp == NULL) {
|
|
return false;
|
|
}
|
|
char buf[4];
|
|
size_t n = fread(buf, 1, 4, fp);
|
|
fclose(fp);
|
|
return n == 4 && buf[0] == 0x7f && buf[1] == 'E' && buf[2] == 'L' && buf[3] == 'F';
|
|
}
|
|
|
|
void Setup::download(QString url) {
|
|
// autocomplete incomplete urls
|
|
if (QRegularExpression("^([^/.]+)/([^/]+)$").match(url).hasMatch()) {
|
|
url.prepend("https://installer.comma.ai/");
|
|
}
|
|
|
|
CURL *curl = curl_easy_init();
|
|
if (!curl) {
|
|
emit finished(url, tr("Something went wrong. Reboot the device."));
|
|
return;
|
|
}
|
|
|
|
auto version = util::read_file("/VERSION");
|
|
|
|
struct curl_slist *list = NULL;
|
|
std::string header = "X-openpilot-serial: " + Hardware::get_serial();
|
|
list = curl_slist_append(list, header.c_str());
|
|
|
|
char tmpfile[] = "/tmp/installer_XXXXXX";
|
|
FILE *fp = fdopen(mkstemp(tmpfile), "wb");
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, url.toStdString().c_str());
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
|
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, (USER_AGENT + version).c_str());
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
|
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
|
|
|
|
int ret = curl_easy_perform(curl);
|
|
long res_status = 0;
|
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &res_status);
|
|
|
|
if (ret != CURLE_OK || res_status != 200) {
|
|
emit finished(url, tr("Ensure the entered URL is valid, and the device’s internet connection is good."));
|
|
} else if (!is_elf(tmpfile)) {
|
|
emit finished(url, tr("No custom software found at this URL."));
|
|
} else {
|
|
rename(tmpfile, "/tmp/installer");
|
|
|
|
FILE *fp_url = fopen("/tmp/installer_url", "w");
|
|
fprintf(fp_url, "%s", url.toStdString().c_str());
|
|
fclose(fp_url);
|
|
|
|
emit finished(url);
|
|
}
|
|
|
|
curl_slist_free_all(list);
|
|
curl_easy_cleanup(curl);
|
|
fclose(fp);
|
|
}
|
|
|
|
QWidget * Setup::low_voltage() {
|
|
QWidget *widget = new QWidget();
|
|
QVBoxLayout *main_layout = new QVBoxLayout(widget);
|
|
main_layout->setContentsMargins(55, 0, 55, 55);
|
|
main_layout->setSpacing(0);
|
|
|
|
// inner text layout: warning icon, title, and body
|
|
QVBoxLayout *inner_layout = new QVBoxLayout();
|
|
inner_layout->setContentsMargins(110, 144, 365, 0);
|
|
main_layout->addLayout(inner_layout);
|
|
|
|
QLabel *triangle = new QLabel();
|
|
triangle->setPixmap(QPixmap(ASSET_PATH + "offroad/icon_warning.png"));
|
|
inner_layout->addWidget(triangle, 0, Qt::AlignTop | Qt::AlignLeft);
|
|
inner_layout->addSpacing(80);
|
|
|
|
QLabel *title = new QLabel(tr("WARNING: Low Voltage"));
|
|
title->setStyleSheet("font-size: 90px; font-weight: 500; color: #FF594F;");
|
|
inner_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
|
|
|
|
inner_layout->addSpacing(25);
|
|
|
|
QLabel *body = new QLabel(tr("Power your device in a car with a harness or proceed at your own risk."));
|
|
body->setWordWrap(true);
|
|
body->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
|
body->setStyleSheet("font-size: 80px; font-weight: 300;");
|
|
inner_layout->addWidget(body);
|
|
|
|
inner_layout->addStretch();
|
|
|
|
// power off + continue buttons
|
|
QHBoxLayout *blayout = new QHBoxLayout();
|
|
blayout->setSpacing(50);
|
|
main_layout->addLayout(blayout, 0);
|
|
|
|
QPushButton *poweroff = new QPushButton(tr("Power off"));
|
|
poweroff->setObjectName("navBtn");
|
|
blayout->addWidget(poweroff);
|
|
QObject::connect(poweroff, &QPushButton::clicked, this, [=]() {
|
|
Hardware::poweroff();
|
|
});
|
|
|
|
QPushButton *cont = new QPushButton(tr("Continue"));
|
|
cont->setObjectName("navBtn");
|
|
blayout->addWidget(cont);
|
|
QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage);
|
|
|
|
return widget;
|
|
}
|
|
|
|
QWidget * Setup::getting_started() {
|
|
QWidget *widget = new QWidget();
|
|
|
|
QHBoxLayout *main_layout = new QHBoxLayout(widget);
|
|
main_layout->setMargin(0);
|
|
|
|
QVBoxLayout *vlayout = new QVBoxLayout();
|
|
vlayout->setContentsMargins(165, 280, 100, 0);
|
|
main_layout->addLayout(vlayout);
|
|
|
|
QLabel *title = new QLabel(tr("Getting Started"));
|
|
title->setStyleSheet("font-size: 90px; font-weight: 500;");
|
|
vlayout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
|
|
|
|
vlayout->addSpacing(90);
|
|
QLabel *desc = new QLabel(tr("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);
|
|
|
|
vlayout->addStretch();
|
|
|
|
QPushButton *btn = new QPushButton();
|
|
btn->setIcon(QIcon(":/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);
|
|
|
|
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(tr("Connect to Wi-Fi"));
|
|
title->setStyleSheet("font-size: 90px; font-weight: 500;");
|
|
main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
|
|
|
|
main_layout->addSpacing(25);
|
|
|
|
// wifi widget
|
|
Networking *networking = new Networking(this, false);
|
|
networking->setStyleSheet("Networking {background-color: #292929; border-radius: 13px;}");
|
|
main_layout->addWidget(networking, 1);
|
|
|
|
main_layout->addSpacing(35);
|
|
|
|
// back + continue buttons
|
|
QHBoxLayout *blayout = new QHBoxLayout;
|
|
main_layout->addLayout(blayout);
|
|
blayout->setSpacing(50);
|
|
|
|
QPushButton *back = new QPushButton(tr("Back"));
|
|
back->setObjectName("navBtn");
|
|
QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
|
|
blayout->addWidget(back);
|
|
|
|
QPushButton *cont = new QPushButton();
|
|
cont->setObjectName("navBtn");
|
|
cont->setProperty("primary", true);
|
|
cont->setEnabled(false);
|
|
QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage);
|
|
blayout->addWidget(cont);
|
|
|
|
// setup timer for testing internet connection
|
|
HttpRequest *request = new HttpRequest(this, false, 2500);
|
|
QObject::connect(request, &HttpRequest::requestDone, [=](const QString &, bool success) {
|
|
cont->setEnabled(success);
|
|
if (success) {
|
|
const bool wifi = networking->wifi->currentNetworkType() == NetworkType::WIFI;
|
|
cont->setText(wifi ? tr("Continue") : tr("Continue without Wi-Fi"));
|
|
} else {
|
|
cont->setText(tr("Waiting for internet"));
|
|
}
|
|
repaint();
|
|
});
|
|
request->sendRequest(OPENPILOT_URL);
|
|
QTimer *timer = new QTimer(this);
|
|
QObject::connect(timer, &QTimer::timeout, [=]() {
|
|
if (!request->active() && cont->isVisible()) {
|
|
request->sendRequest(OPENPILOT_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(":/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);
|
|
|
|
// title
|
|
QLabel *title = new QLabel(tr("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);
|
|
|
|
// openpilot + custom radio buttons
|
|
QButtonGroup *group = new QButtonGroup(widget);
|
|
group->setExclusive(true);
|
|
|
|
QWidget *openpilot = radio_button(tr("openpilot"), group);
|
|
main_layout->addWidget(openpilot);
|
|
|
|
main_layout->addSpacing(30);
|
|
|
|
QWidget *custom = radio_button(tr("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(tr("Back"));
|
|
back->setObjectName("navBtn");
|
|
QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
|
|
blayout->addWidget(back);
|
|
|
|
QPushButton *cont = new QPushButton(tr("Continue"));
|
|
cont->setObjectName("navBtn");
|
|
cont->setEnabled(false);
|
|
cont->setProperty("primary", true);
|
|
blayout->addWidget(cont);
|
|
|
|
QObject::connect(cont, &QPushButton::clicked, [=]() {
|
|
auto w = currentWidget();
|
|
QTimer::singleShot(0, [=]() {
|
|
setCurrentWidget(downloading_widget);
|
|
});
|
|
QString url = OPENPILOT_URL;
|
|
if (group->checkedButton() != openpilot) {
|
|
url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software"));
|
|
}
|
|
if (!url.isEmpty()) {
|
|
QTimer::singleShot(1000, this, [=]() {
|
|
download(url);
|
|
});
|
|
} else {
|
|
setCurrentWidget(w);
|
|
}
|
|
});
|
|
|
|
connect(group, QOverload<QAbstractButton *>::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);
|
|
QLabel *txt = new QLabel(tr("Downloading..."));
|
|
txt->setStyleSheet("font-size: 90px; font-weight: 500;");
|
|
main_layout->addWidget(txt, 0, Qt::AlignCenter);
|
|
return widget;
|
|
}
|
|
|
|
QWidget * Setup::download_failed(QLabel *url, QLabel *body) {
|
|
QWidget *widget = new QWidget();
|
|
QVBoxLayout *main_layout = new QVBoxLayout(widget);
|
|
main_layout->setContentsMargins(55, 185, 55, 55);
|
|
main_layout->setSpacing(0);
|
|
|
|
QLabel *title = new QLabel(tr("Download Failed"));
|
|
title->setStyleSheet("font-size: 90px; font-weight: 500;");
|
|
main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
|
|
|
|
main_layout->addSpacing(67);
|
|
|
|
url->setWordWrap(true);
|
|
url->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
|
url->setStyleSheet("font-family: \"JetBrains Mono\"; font-size: 64px; font-weight: 400; margin-right: 100px;");
|
|
main_layout->addWidget(url);
|
|
|
|
main_layout->addSpacing(48);
|
|
|
|
body->setWordWrap(true);
|
|
body->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
|
body->setStyleSheet("font-size: 80px; font-weight: 300; margin-right: 100px;");
|
|
main_layout->addWidget(body);
|
|
|
|
main_layout->addStretch();
|
|
|
|
// reboot + start over buttons
|
|
QHBoxLayout *blayout = new QHBoxLayout();
|
|
blayout->setSpacing(50);
|
|
main_layout->addLayout(blayout, 0);
|
|
|
|
QPushButton *reboot = new QPushButton(tr("Reboot device"));
|
|
reboot->setObjectName("navBtn");
|
|
blayout->addWidget(reboot);
|
|
QObject::connect(reboot, &QPushButton::clicked, this, [=]() {
|
|
Hardware::reboot();
|
|
});
|
|
|
|
QPushButton *restart = new QPushButton(tr("Start over"));
|
|
restart->setObjectName("navBtn");
|
|
restart->setProperty("primary", true);
|
|
blayout->addWidget(restart);
|
|
QObject::connect(restart, &QPushButton::clicked, this, [=]() {
|
|
setCurrentIndex(1);
|
|
});
|
|
|
|
widget->setStyleSheet(R"(
|
|
QLabel {
|
|
margin-left: 117;
|
|
}
|
|
)");
|
|
return widget;
|
|
}
|
|
|
|
void Setup::prevPage() {
|
|
setCurrentIndex(currentIndex() - 1);
|
|
}
|
|
|
|
void Setup::nextPage() {
|
|
setCurrentIndex(currentIndex() + 1);
|
|
}
|
|
|
|
Setup::Setup(QWidget *parent) : QStackedWidget(parent) {
|
|
if (std::getenv("MULTILANG")) {
|
|
selectLanguage();
|
|
}
|
|
|
|
std::stringstream buffer;
|
|
buffer << std::ifstream("/sys/class/hwmon/hwmon1/in1_input").rdbuf();
|
|
float voltage = (float)std::atoi(buffer.str().c_str()) / 1000.;
|
|
if (voltage < 7) {
|
|
addWidget(low_voltage());
|
|
}
|
|
|
|
addWidget(getting_started());
|
|
addWidget(network_setup());
|
|
addWidget(software_selection());
|
|
|
|
downloading_widget = downloading();
|
|
addWidget(downloading_widget);
|
|
|
|
QLabel *url_label = new QLabel();
|
|
QLabel *body_label = new QLabel();
|
|
failed_widget = download_failed(url_label, body_label);
|
|
addWidget(failed_widget);
|
|
|
|
QObject::connect(this, &Setup::finished, [=](const QString &url, const QString &error) {
|
|
qDebug() << "finished" << url << error;
|
|
if (error.isEmpty()) {
|
|
// hide setup on success
|
|
QTimer::singleShot(3000, this, &QWidget::hide);
|
|
} else {
|
|
url_label->setText(url);
|
|
body_label->setText(error);
|
|
setCurrentWidget(failed_widget);
|
|
}
|
|
});
|
|
|
|
// TODO: revisit pressed bg color
|
|
setStyleSheet(R"(
|
|
* {
|
|
color: white;
|
|
font-family: Inter;
|
|
}
|
|
Setup {
|
|
background-color: black;
|
|
}
|
|
QPushButton#navBtn {
|
|
height: 160;
|
|
font-size: 55px;
|
|
font-weight: 400;
|
|
border-radius: 10px;
|
|
background-color: #333333;
|
|
}
|
|
QPushButton#navBtn:disabled, QPushButton[primary='true']:disabled {
|
|
color: #808080;
|
|
background-color: #333333;
|
|
}
|
|
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;
|
|
}
|
|
)");
|
|
}
|
|
|
|
void Setup::selectLanguage() {
|
|
QMap<QString, QString> langs = getSupportedLanguages();
|
|
QString selection = MultiOptionDialog::getSelection(tr("Select a language"), langs.keys(), "", this);
|
|
if (!selection.isEmpty()) {
|
|
QString selectedLang = langs[selection];
|
|
Params().put("LanguageSetting", selectedLang.toStdString());
|
|
if (translator.load(":/" + selectedLang)) {
|
|
qApp->installTranslator(&translator);
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
QApplication a(argc, argv);
|
|
Setup setup;
|
|
setMainWindow(&setup);
|
|
return a.exec();
|
|
}
|
|
|