diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore index de2e57918d..f7178d8061 100644 --- a/selfdrive/ui/.gitignore +++ b/selfdrive/ui/.gitignore @@ -3,3 +3,5 @@ moc_* qt/text qt/spinner +qt/setup/setup +qt/setup/installer diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 1090a32af2..808d2094f0 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,3 +1,4 @@ +import os Import('env', 'arch', 'real_arch', 'common', 'messaging', 'gpucommon', 'visionipc', 'cereal') qt_env = None @@ -75,3 +76,8 @@ else: # spinner and text window qt_env.Program("qt/text", ["qt/text.cc"], LIBS=qt_libs + libs) qt_env.Program("qt/spinner", ["qt/spinner.cc"], LIBS=qt_libs + libs) + + # setup and installer + if "BUILD_SETUP" in os.environ: + qt_env.Program("qt/setup/setup", ["qt/setup/setup.cc"], LIBS=qt_libs + libs + ['curl']) + qt_env.Program("qt/setup/installer", ["qt/setup/installer.cc"], LIBS=qt_libs + libs) diff --git a/selfdrive/ui/qt/setup/installer.cc b/selfdrive/ui/qt/setup/installer.cc new file mode 100644 index 0000000000..46497b63ea --- /dev/null +++ b/selfdrive/ui/qt/setup/installer.cc @@ -0,0 +1,68 @@ +#include +#include +#include +#include + +#ifndef BRANCH +#define BRANCH "master" +#endif + +#define GIT_CLONE_COMMAND "git clone https://github.com/commaai/openpilot.git" + +#define CONTINUE_PATH "/home/comma/continue.sh" + +bool time_valid() { + time_t rawtime; + time(&rawtime); + + struct tm * sys_time = gmtime(&rawtime); + return (1900 + sys_time->tm_year) >= 2019; +} + +int fresh_clone() { + int err; + + // Cleanup + err = std::system("rm -rf /tmp/openpilot"); + if(err) return 1; + err = std::system("rm -rf /data/openpilot"); + if(err) return 1; + + // Clone + err = std::system(GIT_CLONE_COMMAND " -b " BRANCH " --depth=1 /tmp/openpilot"); + if(err) return 1; + err = std::system("cd /tmp/openpilot && git submodule update --init"); + if(err) return 1; + + err = std::system("mv /tmp/openpilot /data"); + if(err) return 1; + + return 0; +} + +int install() { + int err; + + // TODO: Disable SSH after install done + + // Wait for valid time + while (!time_valid()) { + usleep(500 * 1000); + std::cout << "Waiting for valid time\n"; + } + + std::cout << "Doing fresh clone\n"; + err = fresh_clone(); + if(err) return 1; + + // Write continue.sh + err = std::system("cp /data/openpilot/installer/continue_openpilot.sh " CONTINUE_PATH); + if(err == -1) return 1; + + return 0; +} + +int main(int argc, char *argv[]) { + // TODO: make a small installation UI + return install(); +} diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc new file mode 100644 index 0000000000..1be4097be4 --- /dev/null +++ b/selfdrive/ui/qt/setup/setup.cc @@ -0,0 +1,184 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "setup.hpp" + +#ifdef QCOM2 +#include +#include +#include +#endif + + +int download(std::string url) { + CURL *curl; + curl = curl_easy_init(); + if (!curl) return -1; + + FILE *fp; + fp = fopen("/tmp/installer", "wb"); + curl_easy_setopt(curl, CURLOPT_URL, url.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_perform(curl); + curl_easy_cleanup(curl); + fclose(fp); + return 0; +} + +QLabel * title_label(QString text) { + QLabel *l = new QLabel(text); + l->setStyleSheet(R"( + QLabel { + font-size: 100px; + font-weight: bold; + } + )"); + return l; +} + +QWidget * Setup::getting_started() { + QVBoxLayout *main_layout = new QVBoxLayout(); + main_layout->setContentsMargins(200, 100, 200, 100); + + main_layout->addWidget(title_label("Getting Started"), 0, Qt::AlignCenter); + + QLabel *body = new QLabel("Before we get on the road, let's finish\ninstallation and cover some details."); + body->setStyleSheet(R"(font-size: 65px;)"); + main_layout->addWidget(body, 0, Qt::AlignCenter); + + main_layout->addSpacing(100); + + QPushButton *btn = new QPushButton("Continue"); + main_layout->addWidget(btn); + QObject::connect(btn, SIGNAL(released()), this, SLOT(nextPage())); + + main_layout->addSpacing(100); + + QWidget *widget = new QWidget(); + widget->setLayout(main_layout); + return widget; +} + +QWidget * Setup::network_setup() { + QVBoxLayout *main_layout = new QVBoxLayout(); + main_layout->setMargin(100); + + main_layout->addWidget(title_label("Connect to WiFi"), 0, Qt::AlignCenter); + + QPushButton *btn = new QPushButton("Continue"); + main_layout->addWidget(btn); + QObject::connect(btn, SIGNAL(released()), this, SLOT(nextPage())); + + QWidget *widget = new QWidget(); + widget->setLayout(main_layout); + return widget; +} + +QWidget * Setup::software_selection() { + QVBoxLayout *main_layout = new QVBoxLayout(); + main_layout->setMargin(100); + + main_layout->addWidget(title_label("Choose Software"), 0, Qt::AlignCenter); + + QPushButton *dashcam_btn = new QPushButton("Dashcam"); + main_layout->addWidget(dashcam_btn); + QObject::connect(dashcam_btn, SIGNAL(released()), this, SLOT(nextPage())); + + main_layout->addSpacing(50); + + const char* env_url = getenv("CUSTOM_URL"); + QString default_url = env_url == NULL ? "" : QString::fromStdString(env_url); + url_input = new QLineEdit(default_url); + url_input->setStyleSheet(R"( + color: black; + background-color: white; + font-size: 55px; + padding: 40px; + )"); + main_layout->addWidget(url_input); + + QPushButton *custom_btn = new QPushButton("Custom"); + main_layout->addWidget(custom_btn); + QObject::connect(custom_btn, SIGNAL(released()), this, SLOT(nextPage())); + + QWidget *widget = new QWidget(); + widget->setLayout(main_layout); + return widget; +} + +QWidget * Setup::downloading() { + QVBoxLayout *main_layout = new QVBoxLayout(); + + main_layout->addWidget(title_label("Downloading..."), 0, Qt::AlignCenter); + + QWidget *widget = new QWidget(); + widget->setLayout(main_layout); + return widget; +} + +void Setup::nextPage() { + layout->setCurrentIndex(layout->currentIndex() + 1); + + // start download + if (layout->currentIndex() == layout->count() - 1) { + std::string url = url_input->text().toStdString(); + download(url); + } +} + +Setup::Setup(QWidget *parent) { + layout = new QStackedLayout(); + layout->addWidget(getting_started()); + layout->addWidget(network_setup()); + layout->addWidget(software_selection()); + layout->addWidget(downloading()); + + setLayout(layout); + setStyleSheet(R"( + QWidget { + color: white; + background-color: black; + } + QPushButton { + font-size: 60px; + padding: 60px; + width: 800px; + color: white; + background-color: blue; + } + )"); +} + +int main(int argc, char *argv[]) { +#ifdef QCOM2 + int w = 2160, h = 1080; +#else + int w = 1920, h = 1080; +#endif + + QApplication a(argc, argv); + + Setup setup; + setup.setFixedSize(w, h); + setup.show(); + +#ifdef QCOM2 + QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); + wl_surface *s = reinterpret_cast(native->nativeResourceForWindow("surface", setup.windowHandle())); + wl_surface_set_buffer_transform(s, WL_OUTPUT_TRANSFORM_270); + wl_surface_commit(s); + setup.showFullScreen(); +#endif + + return a.exec(); +} diff --git a/selfdrive/ui/qt/setup/setup.hpp b/selfdrive/ui/qt/setup/setup.hpp new file mode 100644 index 0000000000..fb42e7fc38 --- /dev/null +++ b/selfdrive/ui/qt/setup/setup.hpp @@ -0,0 +1,23 @@ +#include +#include +#include + +class Setup : public QWidget { + Q_OBJECT + +public: + explicit Setup(QWidget *parent = 0); + +private: + QStackedLayout *layout; + + QLineEdit *url_input; + + QWidget *getting_started(); + QWidget *network_setup(); + QWidget *software_selection(); + QWidget *downloading(); + +public slots: + void nextPage(); +};