# include "selfdrive/ui/qt/offroad/settings.h"
# include <cassert>
# include <string>
# include <QDebug>
# ifndef QCOM
# include "selfdrive/ui/qt/offroad/networking.h"
# endif
# ifdef ENABLE_MAPS
# include "selfdrive/ui/qt/maps/map_settings.h"
# endif
# include "selfdrive/common/params.h"
# include "selfdrive/common/util.h"
# include "selfdrive/hardware/hw.h"
# include "selfdrive/ui/qt/widgets/controls.h"
# include "selfdrive/ui/qt/widgets/input.h"
# include "selfdrive/ui/qt/widgets/scrollview.h"
# include "selfdrive/ui/qt/widgets/ssh_keys.h"
# include "selfdrive/ui/qt/widgets/toggle.h"
# include "selfdrive/ui/ui.h"
# include "selfdrive/ui/qt/util.h"
# include "selfdrive/ui/qt/qt_window.h"
TogglesPanel : : TogglesPanel ( QWidget * parent ) : ListWidget ( parent ) {
auto params = Params ( ) ;
addItem ( new ParamControl ( " OpenpilotEnabledToggle " ,
" Enable openpilot " ,
" Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. " ,
" ../assets/offroad/icon_openpilot.png " ,
this ) ) ;
addItem ( new ParamControl ( " IsLdwEnabled " ,
" Enable Lane Departure Warnings " ,
" Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31mph (50kph). " ,
" ../assets/offroad/icon_warning.png " ,
this ) ) ;
addItem ( new ParamControl ( " IsRHD " ,
" Enable Right-Hand Drive " ,
" Allow openpilot to obey left-hand traffic conventions and perform driver monitoring on right driver seat. " ,
" ../assets/offroad/icon_openpilot_mirrored.png " ,
this ) ) ;
addItem ( new ParamControl ( " IsMetric " ,
" Use Metric System " ,
" Display speed in km/h instead of mph. " ,
" ../assets/offroad/icon_metric.png " ,
this ) ) ;
addItem ( new ParamControl ( " CommunityFeaturesToggle " ,
" Enable Community Features " ,
" Use features, such as community supported hardware, from the open source community that are not maintained or supported by comma.ai and have not been confirmed to meet the standard safety model. Be extra cautious when using these features " ,
" ../assets/offroad/icon_shell.png " ,
this ) ) ;
addItem ( new ParamControl ( " UploadRaw " ,
" Upload Raw Logs " ,
" Upload full logs and full resolution video by default while on Wi-Fi. If not enabled, individual logs can be marked for upload at useradmin.comma.ai. " ,
" ../assets/offroad/icon_network.png " ,
this ) ) ;
ParamControl * record_toggle = new ParamControl ( " RecordFront " ,
" Record and Upload Driver Camera " ,
" Upload data from the driver facing camera and help improve the driver monitoring algorithm. " ,
" ../assets/offroad/icon_monitoring.png " ,
this ) ;
addItem ( record_toggle ) ;
addItem ( new ParamControl ( " EndToEndToggle " ,
" \U0001f96c Disable use of lanelines (Alpha) \U0001f96c " ,
" In this mode openpilot will ignore lanelines and just drive how it thinks a human would. " ,
" ../assets/offroad/icon_road.png " ,
this ) ) ;
# ifdef ENABLE_MAPS
addItem ( new ParamControl ( " NavSettingTime24h " ,
" Show ETA in 24h format " ,
" Use 24h format instead of am/pm " ,
" ../assets/offroad/icon_metric.png " ,
this ) ) ;
# endif
if ( params . getBool ( " DisableRadar_Allow " ) ) {
addItem ( new ParamControl ( " DisableRadar " ,
" openpilot Longitudinal Control " ,
" openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB! " ,
" ../assets/offroad/icon_speed_limit.png " ,
this ) ) ;
}
bool record_lock = params . getBool ( " RecordFrontLock " ) ;
record_toggle - > setEnabled ( ! record_lock ) ;
}
DevicePanel : : DevicePanel ( QWidget * parent ) : ListWidget ( parent ) {
setSpacing ( 50 ) ;
Params params = Params ( ) ;
addItem ( new LabelControl ( " Dongle ID " , getDongleId ( ) . value_or ( " N/A " ) ) ) ;
QString serial = QString : : fromStdString ( params . get ( " HardwareSerial " , false ) ) ;
addItem ( new LabelControl ( " Serial " , serial ) ) ;
// offroad-only buttons
auto dcamBtn = new ButtonControl ( " Driver Camera " , " PREVIEW " ,
" Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off) " ) ;
connect ( dcamBtn , & ButtonControl : : clicked , [ = ] ( ) { emit showDriverView ( ) ; } ) ;
QString resetCalibDesc = " openpilot requires the device to be mounted within 4° left or right and within 5° up or down. openpilot is continuously calibrating, resetting is rarely required. " ;
auto resetCalibBtn = new ButtonControl ( " Reset Calibration " , " RESET " , resetCalibDesc ) ;
connect ( resetCalibBtn , & ButtonControl : : clicked , [ = ] ( ) {
if ( ConfirmationDialog : : confirm ( " Are you sure you want to reset calibration? " , this ) ) {
Params ( ) . remove ( " CalibrationParams " ) ;
}
} ) ;
connect ( resetCalibBtn , & ButtonControl : : showDescription , [ = ] ( ) {
QString desc = resetCalibDesc ;
std : : string calib_bytes = Params ( ) . get ( " CalibrationParams " ) ;
if ( ! calib_bytes . empty ( ) ) {
try {
AlignedBuffer aligned_buf ;
capnp : : FlatArrayMessageReader cmsg ( aligned_buf . align ( calib_bytes . data ( ) , calib_bytes . size ( ) ) ) ;
auto calib = cmsg . getRoot < cereal : : Event > ( ) . getLiveCalibration ( ) ;
if ( calib . getCalStatus ( ) ! = 0 ) {
double pitch = calib . getRpyCalib ( ) [ 1 ] * ( 180 / M_PI ) ;
double yaw = calib . getRpyCalib ( ) [ 2 ] * ( 180 / M_PI ) ;
desc + = QString ( " Your device is pointed %1° %2 and %3° %4. " )
. arg ( QString : : number ( std : : abs ( pitch ) , ' g ' , 1 ) , pitch > 0 ? " up " : " down " ,
QString : : number ( std : : abs ( yaw ) , ' g ' , 1 ) , yaw > 0 ? " right " : " left " ) ;
}
} catch ( kj : : Exception ) {
qInfo ( ) < < " invalid CalibrationParams " ;
}
}
resetCalibBtn - > setDescription ( desc ) ;
} ) ;
ButtonControl * retrainingBtn = nullptr ;
if ( ! params . getBool ( " Passive " ) ) {
retrainingBtn = new ButtonControl ( " Review Training Guide " , " REVIEW " , " Review the rules, features, and limitations of openpilot " ) ;
connect ( retrainingBtn , & ButtonControl : : clicked , [ = ] ( ) {
if ( ConfirmationDialog : : confirm ( " Are you sure you want to review the training guide? " , this ) ) {
emit reviewTrainingGuide ( ) ;
}
} ) ;
}
ButtonControl * regulatoryBtn = nullptr ;
if ( Hardware : : TICI ( ) ) {
regulatoryBtn = new ButtonControl ( " Regulatory " , " VIEW " , " " ) ;
connect ( regulatoryBtn , & ButtonControl : : clicked , [ = ] ( ) {
const std : : string txt = util : : read_file ( ASSET_PATH . toStdString ( ) + " /offroad/fcc.html " ) ;
RichTextDialog : : alert ( QString : : fromStdString ( txt ) , this ) ;
} ) ;
}
for ( auto btn : { dcamBtn , resetCalibBtn , retrainingBtn , regulatoryBtn } ) {
if ( btn ) {
connect ( parent , SIGNAL ( offroadTransition ( bool ) ) , btn , SLOT ( setEnabled ( bool ) ) ) ;
addItem ( btn ) ;
}
}
// power buttons
QHBoxLayout * power_layout = new QHBoxLayout ( ) ;
power_layout - > setSpacing ( 30 ) ;
QPushButton * reboot_btn = new QPushButton ( " Reboot " ) ;
reboot_btn - > setObjectName ( " reboot_btn " ) ;
power_layout - > addWidget ( reboot_btn ) ;
QObject : : connect ( reboot_btn , & QPushButton : : clicked , [ = ] ( ) {
if ( ConfirmationDialog : : confirm ( " Are you sure you want to reboot? " , this ) ) {
Hardware : : reboot ( ) ;
}
} ) ;
QPushButton * poweroff_btn = new QPushButton ( " Power Off " ) ;
poweroff_btn - > setObjectName ( " poweroff_btn " ) ;
power_layout - > addWidget ( poweroff_btn ) ;
QObject : : connect ( poweroff_btn , & QPushButton : : clicked , [ = ] ( ) {
if ( ConfirmationDialog : : confirm ( " Are you sure you want to power off? " , this ) ) {
Hardware : : poweroff ( ) ;
}
} ) ;
setStyleSheet ( R " (
QPushButton {
height : 120 px ;
border - radius : 15 px ;
}
# reboot_btn { background-color: #393939; }
# reboot_btn:pressed { background-color: #4a4a4a; }
# poweroff_btn { background-color: #E22C2C; }
# poweroff_btn:pressed { background-color: #FF2424; }
) " );
addItem ( power_layout ) ;
}
SoftwarePanel : : SoftwarePanel ( QWidget * parent ) : ListWidget ( parent ) {
gitBranchLbl = new LabelControl ( " Git Branch " ) ;
gitCommitLbl = new LabelControl ( " Git Commit " ) ;
osVersionLbl = new LabelControl ( " OS Version " ) ;
versionLbl = new LabelControl ( " Version " , " " , QString : : fromStdString ( params . get ( " ReleaseNotes " ) ) . trimmed ( ) ) ;
lastUpdateLbl = new LabelControl ( " Last Update Check " , " " , " The last time openpilot successfully checked for an update. The updater only runs while the car is off. " ) ;
updateBtn = new ButtonControl ( " Check for Update " , " " ) ;
connect ( updateBtn , & ButtonControl : : clicked , [ = ] ( ) {
if ( params . getBool ( " IsOffroad " ) ) {
fs_watch - > addPath ( QString : : fromStdString ( params . getParamPath ( " LastUpdateTime " ) ) ) ;
fs_watch - > addPath ( QString : : fromStdString ( params . getParamPath ( " UpdateFailedCount " ) ) ) ;
updateBtn - > setText ( " CHECKING " ) ;
updateBtn - > setEnabled ( false ) ;
}
std : : system ( " pkill -1 -f selfdrive.updated " ) ;
} ) ;
auto uninstallBtn = new ButtonControl ( " Uninstall " + getBrand ( ) , " UNINSTALL " ) ;
connect ( uninstallBtn , & ButtonControl : : clicked , [ = ] ( ) {
if ( ConfirmationDialog : : confirm ( " Are you sure you want to uninstall? " , this ) ) {
Params ( ) . putBool ( " DoUninstall " , true ) ;
}
} ) ;
connect ( parent , SIGNAL ( offroadTransition ( bool ) ) , uninstallBtn , SLOT ( setEnabled ( bool ) ) ) ;
QWidget * widgets [ ] = { versionLbl , lastUpdateLbl , updateBtn , gitBranchLbl , gitCommitLbl , osVersionLbl , uninstallBtn } ;
for ( QWidget * w : widgets ) {
addItem ( w ) ;
}
fs_watch = new QFileSystemWatcher ( this ) ;
QObject : : connect ( fs_watch , & QFileSystemWatcher : : fileChanged , [ = ] ( const QString path ) {
int update_failed_count = params . get < int > ( " UpdateFailedCount " ) . value_or ( 0 ) ;
if ( path . contains ( " UpdateFailedCount " ) & & update_failed_count > 0 ) {
lastUpdateLbl - > setText ( " failed to fetch update " ) ;
updateBtn - > setText ( " CHECK " ) ;
updateBtn - > setEnabled ( true ) ;
} else if ( path . contains ( " LastUpdateTime " ) ) {
updateLabels ( ) ;
}
} ) ;
}
void SoftwarePanel : : showEvent ( QShowEvent * event ) {
updateLabels ( ) ;
}
void SoftwarePanel : : updateLabels ( ) {
QString lastUpdate = " " ;
auto tm = params . get ( " LastUpdateTime " ) ;
if ( ! tm . empty ( ) ) {
lastUpdate = timeAgo ( QDateTime : : fromString ( QString : : fromStdString ( tm + " Z " ) , Qt : : ISODate ) ) ;
}
versionLbl - > setText ( getBrandVersion ( ) ) ;
lastUpdateLbl - > setText ( lastUpdate ) ;
updateBtn - > setText ( " CHECK " ) ;
updateBtn - > setEnabled ( true ) ;
gitBranchLbl - > setText ( QString : : fromStdString ( params . get ( " GitBranch " ) ) ) ;
gitCommitLbl - > setText ( QString : : fromStdString ( params . get ( " GitCommit " ) ) . left ( 10 ) ) ;
osVersionLbl - > setText ( QString : : fromStdString ( Hardware : : get_os_version ( ) ) . trimmed ( ) ) ;
}
QWidget * network_panel ( QWidget * parent ) {
# ifdef QCOM
QWidget * w = new QWidget ( parent ) ;
QVBoxLayout * layout = new QVBoxLayout ( w ) ;
layout - > setContentsMargins ( 50 , 0 , 50 , 0 ) ;
ListWidget * list = new ListWidget ( ) ;
list - > setSpacing ( 30 ) ;
// wifi + tethering buttons
auto wifiBtn = new ButtonControl ( " Wi-Fi Settings " , " OPEN " ) ;
QObject : : connect ( wifiBtn , & ButtonControl : : clicked , [ = ] ( ) { HardwareEon : : launch_wifi ( ) ; } ) ;
list - > addItem ( wifiBtn ) ;
auto tetheringBtn = new ButtonControl ( " Tethering Settings " , " OPEN " ) ;
QObject : : connect ( tetheringBtn , & ButtonControl : : clicked , [ = ] ( ) { HardwareEon : : launch_tethering ( ) ; } ) ;
list - > addItem ( tetheringBtn ) ;
// SSH key management
list - > addItem ( new SshToggle ( ) ) ;
list - > addItem ( new SshControl ( ) ) ;
layout - > addWidget ( list ) ;
layout - > addStretch ( 1 ) ;
# else
Networking * w = new Networking ( parent ) ;
# endif
return w ;
}
void SettingsWindow : : showEvent ( QShowEvent * event ) {
panel_widget - > setCurrentIndex ( 0 ) ;
nav_btns - > buttons ( ) [ 0 ] - > setChecked ( true ) ;
}
SettingsWindow : : SettingsWindow ( QWidget * parent ) : QFrame ( parent ) {
// setup two main layouts
sidebar_widget = new QWidget ;
QVBoxLayout * sidebar_layout = new QVBoxLayout ( sidebar_widget ) ;
sidebar_layout - > setMargin ( 0 ) ;
panel_widget = new QStackedWidget ( ) ;
panel_widget - > setStyleSheet ( R " (
border - radius : 30 px ;
background - color : # 292929 ;
) " );
// close button
QPushButton * close_btn = new QPushButton ( " × " ) ;
close_btn - > setStyleSheet ( R " (
QPushButton {
font - size : 140 px ;
padding - bottom : 20 px ;
font - weight : bold ;
border 1 px grey solid ;
border - radius : 100 px ;
background - color : # 292929 ;
font - weight : 400 ;
}
QPushButton : pressed {
background - color : # 3 B3B3B ;
}
) " );
close_btn - > setFixedSize ( 200 , 200 ) ;
sidebar_layout - > addSpacing ( 45 ) ;
sidebar_layout - > addWidget ( close_btn , 0 , Qt : : AlignCenter ) ;
QObject : : connect ( close_btn , & QPushButton : : clicked , this , & SettingsWindow : : closeSettings ) ;
// setup panels
DevicePanel * device = new DevicePanel ( this ) ;
QObject : : connect ( device , & DevicePanel : : reviewTrainingGuide , this , & SettingsWindow : : reviewTrainingGuide ) ;
QObject : : connect ( device , & DevicePanel : : showDriverView , this , & SettingsWindow : : showDriverView ) ;
QList < QPair < QString , QWidget * > > panels = {
{ " Device " , device } ,
{ " Network " , network_panel ( this ) } ,
{ " Toggles " , new TogglesPanel ( this ) } ,
{ " Software " , new SoftwarePanel ( this ) } ,
} ;
# ifdef ENABLE_MAPS
auto map_panel = new MapPanel ( this ) ;
panels . push_back ( { " Navigation " , map_panel } ) ;
QObject : : connect ( map_panel , & MapPanel : : closeSettings , this , & SettingsWindow : : closeSettings ) ;
# endif
const int padding = panels . size ( ) > 3 ? 25 : 35 ;
nav_btns = new QButtonGroup ( ) ;
for ( auto & [ name , panel ] : panels ) {
QPushButton * btn = new QPushButton ( name ) ;
btn - > setCheckable ( true ) ;
btn - > setChecked ( nav_btns - > buttons ( ) . size ( ) = = 0 ) ;
btn - > setStyleSheet ( QString ( R " (
QPushButton {
color : grey ;
border : none ;
background : none ;
font - size : 65 px ;
font - weight : 500 ;
padding - top : % 1 px ;
padding - bottom : % 1 px ;
}
QPushButton : checked {
color : white ;
}
QPushButton : pressed {
color : # ADADAD ;
}
) " ).arg(padding));
nav_btns - > addButton ( btn ) ;
sidebar_layout - > addWidget ( btn , 0 , Qt : : AlignRight ) ;
const int lr_margin = name ! = " Network " ? 50 : 0 ; // Network panel handles its own margins
panel - > setContentsMargins ( lr_margin , 25 , lr_margin , 25 ) ;
ScrollView * panel_frame = new ScrollView ( panel , this ) ;
panel_widget - > addWidget ( panel_frame ) ;
QObject : : connect ( btn , & QPushButton : : clicked , [ = , w = panel_frame ] ( ) {
btn - > setChecked ( true ) ;
panel_widget - > setCurrentWidget ( w ) ;
} ) ;
}
sidebar_layout - > setContentsMargins ( 50 , 50 , 100 , 50 ) ;
// main settings layout, sidebar + main panel
QHBoxLayout * main_layout = new QHBoxLayout ( this ) ;
sidebar_widget - > setFixedWidth ( 500 ) ;
main_layout - > addWidget ( sidebar_widget ) ;
main_layout - > addWidget ( panel_widget ) ;
setStyleSheet ( R " (
* {
color : white ;
font - size : 50 px ;
}
SettingsWindow {
background - color : black ;
}
) " );
}
void SettingsWindow : : hideEvent ( QHideEvent * event ) {
# ifdef QCOM
HardwareEon : : close_activities ( ) ;
# endif
}