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.
		
		
		
		
		
			
		
			
				
					
					
						
							394 lines
						
					
					
						
							12 KiB
						
					
					
				
			
		
		
	
	
							394 lines
						
					
					
						
							12 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 DASHCAM_URL = "https://dashcam.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) {
 | |
|   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;
 | |
|   list = curl_slist_append(list, ("X-openpilot-serial: " + Hardware::get_serial()).c_str());
 | |
| 
 | |
|   char tmpfile[] = "/tmp/installer_XXXXXX";
 | |
|   FILE *fp = fdopen(mkstemp(tmpfile), "w");
 | |
| 
 | |
|   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);
 | |
|   QObject::connect(cont, &QPushButton::clicked, [=]() {
 | |
|     auto w = currentWidget();
 | |
|     QTimer::singleShot(0, [=]() {
 | |
|       setCurrentWidget(downloading_widget);
 | |
|     });
 | |
|     QString url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software"));
 | |
|     if (!url.isEmpty()) {
 | |
|       QTimer::singleShot(1000, this, [=]() {
 | |
|         download(url);
 | |
|       });
 | |
|     } else {
 | |
|       setCurrentWidget(w);
 | |
|     }
 | |
|   });
 | |
|   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(DASHCAM_URL);
 | |
|   QTimer *timer = new QTimer(this);
 | |
|   QObject::connect(timer, &QTimer::timeout, [=]() {
 | |
|     if (!request->active() && cont->isVisible()) {
 | |
|       request->sendRequest(DASHCAM_URL);
 | |
|     }
 | |
|   });
 | |
|   timer->start(1000);
 | |
| 
 | |
|   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());
 | |
| 
 | |
|   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();
 | |
| }
 | |
| 
 |