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.
		
		
		
		
		
			
		
			
				
					
					
						
							424 lines
						
					
					
						
							12 KiB
						
					
					
				
			
		
		
	
	
							424 lines
						
					
					
						
							12 KiB
						
					
					
				| #include "selfdrive/ui/qt/setup/setup.h"
 | |
| 
 | |
| #include <cstdio>
 | |
| #include <cstdlib>
 | |
| #include <sstream>
 | |
| 
 | |
| #include <QApplication>
 | |
| #include <QLabel>
 | |
| #include <QVBoxLayout>
 | |
| 
 | |
| #include <curl/curl.h>
 | |
| 
 | |
| #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"
 | |
| 
 | |
| const char* USER_AGENT = "AGNOSSetup-0.1";
 | |
| const QString DASHCAM_URL = "https://dashcam.comma.ai";
 | |
| 
 | |
| void Setup::download(QString url) {
 | |
|   CURL *curl = curl_easy_init();
 | |
|   if (!curl) {
 | |
|     emit finished(false);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   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);
 | |
| 
 | |
|   int ret = curl_easy_perform(curl);
 | |
|   if (ret != CURLE_OK) {
 | |
|     emit finished(false);
 | |
|     return;
 | |
|   }
 | |
|   curl_easy_cleanup(curl);
 | |
|   fclose(fp);
 | |
| 
 | |
|   rename(tmpfile, "/tmp/installer");
 | |
|   emit finished(true);
 | |
| }
 | |
| 
 | |
| 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("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("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("Power off");
 | |
|   poweroff->setObjectName("navBtn");
 | |
|   blayout->addWidget(poweroff);
 | |
|   QObject::connect(poweroff, &QPushButton::clicked, this, [=]() {
 | |
|     Hardware::poweroff();
 | |
|   });
 | |
| 
 | |
|   QPushButton *cont = new QPushButton("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("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("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("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("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, 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 cell = networking->wifi->currentNetworkType() == NetworkType::CELL;
 | |
|       cont->setText(cell ? "Continue without Wi-Fi" : "Continue");
 | |
|     } else {
 | |
|       cont->setText("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 * 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("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);
 | |
| 
 | |
|   // 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);
 | |
|   cont->setProperty("primary", true);
 | |
|   blayout->addWidget(cont);
 | |
| 
 | |
|   QObject::connect(cont, &QPushButton::clicked, [=]() {
 | |
|     auto w = currentWidget();
 | |
|     QTimer::singleShot(0, [=]() {
 | |
|       setCurrentWidget(downloading_widget);
 | |
|     });
 | |
|     QString url = DASHCAM_URL;
 | |
|     if (group->checkedButton() != dashcam) {
 | |
|       url = InputDialog::getText("Enter URL", this, "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("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(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 internet connection is good.");
 | |
|   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("Reboot device");
 | |
|   reboot->setObjectName("navBtn");
 | |
|   blayout->addWidget(reboot);
 | |
|   QObject::connect(reboot, &QPushButton::clicked, this, [=]() {
 | |
|     Hardware::reboot();
 | |
|   });
 | |
| 
 | |
|   QPushButton *restart = new QPushButton("Start over");
 | |
|   restart->setObjectName("navBtn");
 | |
|   restart->setProperty("primary", true);
 | |
|   blayout->addWidget(restart);
 | |
|   QObject::connect(restart, &QPushButton::clicked, this, [=]() {
 | |
|     setCurrentIndex(2);
 | |
|   });
 | |
| 
 | |
|   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) {
 | |
|   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);
 | |
| 
 | |
|   failed_widget = download_failed();
 | |
|   addWidget(failed_widget);
 | |
| 
 | |
|   QObject::connect(this, &Setup::finished, [=](bool success) {
 | |
|     // hide setup on success
 | |
|     qDebug() << "finished" << success;
 | |
|     if (success) {
 | |
|       QTimer::singleShot(3000, this, &QWidget::hide);
 | |
|     } else {
 | |
|       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;
 | |
|     }
 | |
|   )");
 | |
| }
 | |
| 
 | |
| int main(int argc, char *argv[]) {
 | |
|   QApplication a(argc, argv);
 | |
|   Setup setup;
 | |
|   setMainWindow(&setup);
 | |
|   return a.exec();
 | |
| }
 | |
| 
 |