# 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 TEST_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 ;
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 ( TEST_URL ) ;
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 * 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 : 55 px ;
font - weight : 400 ;
border - radius : 10 px ;
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 : # 465 BEA ;
}
QPushButton [ primary = ' true ' ] : pressed , # navBtn : pressed [ primary = ' true ' ] {
background - color : # 3049F 4 ;
}
) " );
}
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 ( ) ;
}