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.
		
		
		
		
		
			
		
			
				
					
					
						
							296 lines
						
					
					
						
							8.0 KiB
						
					
					
				
			
		
		
	
	
							296 lines
						
					
					
						
							8.0 KiB
						
					
					
				| #include <cmath>
 | |
| #include <fstream>
 | |
| #include <iostream>
 | |
| #include <thread>
 | |
| 
 | |
| #include <QDateTime>
 | |
| #include <QHBoxLayout>
 | |
| #include <QLayout>
 | |
| #include <QMouseEvent>
 | |
| #include <QStackedLayout>
 | |
| #include <QVBoxLayout>
 | |
| #include <QWidget>
 | |
| 
 | |
| #include "common/params.h"
 | |
| #include "common/timing.h"
 | |
| #include "common/swaglog.h"
 | |
| 
 | |
| #include "home.hpp"
 | |
| #include "paint.hpp"
 | |
| #include "qt_window.hpp"
 | |
| #include "widgets/drive_stats.hpp"
 | |
| #include "widgets/setup.hpp"
 | |
| 
 | |
| #define BACKLIGHT_DT 0.25
 | |
| #define BACKLIGHT_TS 2.00
 | |
| 
 | |
| OffroadHome::OffroadHome(QWidget* parent) : QWidget(parent) {
 | |
|   QVBoxLayout* main_layout = new QVBoxLayout();
 | |
|   main_layout->setContentsMargins(sbr_w + 50, 50, 50, 50);
 | |
| 
 | |
|   // top header
 | |
|   QHBoxLayout* header_layout = new QHBoxLayout();
 | |
| 
 | |
|   date = new QLabel();
 | |
|   date->setStyleSheet(R"(font-size: 55px;)");
 | |
|   header_layout->addWidget(date, 0, Qt::AlignTop | Qt::AlignLeft);
 | |
| 
 | |
|   QLabel* version = new QLabel(QString::fromStdString("openpilot v" + Params().get("Version")));
 | |
|   version->setStyleSheet(R"(font-size: 45px;)");
 | |
|   header_layout->addWidget(version, 0, Qt::AlignTop | Qt::AlignRight);
 | |
| 
 | |
|   main_layout->addLayout(header_layout);
 | |
| 
 | |
|   alert_notification = new QPushButton();
 | |
|   QObject::connect(alert_notification, SIGNAL(released()), this, SLOT(openAlerts()));
 | |
|   main_layout->addWidget(alert_notification, 0, Qt::AlignTop | Qt::AlignRight);
 | |
| 
 | |
|   // main content
 | |
|   main_layout->addSpacing(25);
 | |
|   center_layout = new QStackedLayout();
 | |
| 
 | |
|   QHBoxLayout* statsAndSetup = new QHBoxLayout();
 | |
| 
 | |
|   DriveStats* drive = new DriveStats;
 | |
|   drive->setFixedSize(1000, 800);
 | |
|   statsAndSetup->addWidget(drive);
 | |
| 
 | |
|   SetupWidget* setup = new SetupWidget;
 | |
|   statsAndSetup->addWidget(setup);
 | |
| 
 | |
|   QWidget* statsAndSetupWidget = new QWidget();
 | |
|   statsAndSetupWidget->setLayout(statsAndSetup);
 | |
| 
 | |
|   center_layout->addWidget(statsAndSetupWidget);
 | |
| 
 | |
|   alerts_widget = new OffroadAlert();
 | |
|   QObject::connect(alerts_widget, SIGNAL(closeAlerts()), this, SLOT(closeAlerts()));
 | |
|   center_layout->addWidget(alerts_widget);
 | |
|   center_layout->setAlignment(alerts_widget, Qt::AlignCenter);
 | |
| 
 | |
|   main_layout->addLayout(center_layout, 1);
 | |
| 
 | |
|   // set up refresh timer
 | |
|   timer = new QTimer(this);
 | |
|   QObject::connect(timer, SIGNAL(timeout()), this, SLOT(refresh()));
 | |
|   refresh();
 | |
|   timer->start(10 * 1000);
 | |
| 
 | |
|   setLayout(main_layout);
 | |
|   setStyleSheet(R"(background-color: none;)");
 | |
| }
 | |
| 
 | |
| void OffroadHome::openAlerts() {
 | |
|   center_layout->setCurrentIndex(1);
 | |
| }
 | |
| 
 | |
| void OffroadHome::closeAlerts() {
 | |
|   center_layout->setCurrentIndex(0);
 | |
| }
 | |
| 
 | |
| void OffroadHome::refresh() {
 | |
|   bool first_refresh = !date->text().size();
 | |
|   if (!isVisible() && !first_refresh) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   date->setText(QDateTime::currentDateTime().toString("dddd, MMMM d"));
 | |
| 
 | |
|   // update alerts
 | |
| 
 | |
|   alerts_widget->refresh();
 | |
|   if (!alerts_widget->alerts.size() && !alerts_widget->updateAvailable) {
 | |
|     alert_notification->setVisible(false);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (alerts_widget->updateAvailable) {
 | |
|     // There is a new release
 | |
|     alert_notification->setText("UPDATE");
 | |
|   } else {
 | |
|     int alerts = alerts_widget->alerts.size();
 | |
|     alert_notification->setText(QString::number(alerts) + " ALERT" + (alerts == 1 ? "" : "S"));
 | |
|   }
 | |
| 
 | |
|   alert_notification->setVisible(true);
 | |
| 
 | |
|   // Red background for alerts, blue for update available
 | |
|   QString style = QString(R"(
 | |
|     padding: 15px;
 | |
|     padding-left: 30px;
 | |
|     padding-right: 30px;
 | |
|     border: 1px solid;
 | |
|     border-radius: 5px;
 | |
|     font-size: 40px;
 | |
|     font-weight: bold;
 | |
|     background-color: #E22C2C;
 | |
|   )");
 | |
|   if (alerts_widget->updateAvailable) {
 | |
|     style.replace("#E22C2C", "#364DEF");
 | |
|   }
 | |
|   alert_notification->setStyleSheet(style);
 | |
| }
 | |
| 
 | |
| HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) {
 | |
|   layout = new QGridLayout;
 | |
|   layout->setMargin(0);
 | |
| 
 | |
|   // onroad UI
 | |
|   glWindow = new GLWindow(this);
 | |
|   layout->addWidget(glWindow, 0, 0);
 | |
| 
 | |
|   // draw offroad UI on top of onroad UI
 | |
|   home = new OffroadHome();
 | |
|   layout->addWidget(home, 0, 0);
 | |
|   QObject::connect(glWindow, SIGNAL(offroadTransition(bool)), this, SLOT(setVisibility(bool)));
 | |
|   QObject::connect(this, SIGNAL(openSettings()), home, SLOT(refresh()));
 | |
|   setLayout(layout);
 | |
|   setStyleSheet(R"(
 | |
|     * {
 | |
|       color: white;
 | |
|     }
 | |
|   )");
 | |
| }
 | |
| 
 | |
| void HomeWindow::setVisibility(bool offroad) {
 | |
|   home->setVisible(offroad);
 | |
| }
 | |
| 
 | |
| void HomeWindow::mousePressEvent(QMouseEvent* e) {
 | |
|   UIState* ui_state = &glWindow->ui_state;
 | |
| 
 | |
|   glWindow->wake();
 | |
| 
 | |
|   // Settings button click
 | |
|   if (!ui_state->scene.sidebar_collapsed && settings_btn.ptInRect(e->x(), e->y())) {
 | |
|     emit openSettings();
 | |
|   }
 | |
| 
 | |
|   // Vision click
 | |
|   if (ui_state->started && (e->x() >= ui_state->scene.viz_rect.x - bdr_s)) {
 | |
|     ui_state->scene.sidebar_collapsed = !ui_state->scene.sidebar_collapsed;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void handle_display_state(UIState* s, bool user_input) {
 | |
|   static int awake_timeout = 0; // Somehow this only gets called on program start
 | |
|   awake_timeout = std::max(awake_timeout - 1, 0);
 | |
| 
 | |
|   if (user_input || s->ignition || s->started) {
 | |
|     s->awake = true;
 | |
|     awake_timeout = 30 * UI_FREQ;
 | |
|   } else if (awake_timeout == 0) {
 | |
|     s->awake = false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void set_backlight(int brightness) {
 | |
|   std::ofstream brightness_control("/sys/class/backlight/panel0-backlight/brightness");
 | |
|   if (brightness_control.is_open()) {
 | |
|     brightness_control << brightness << "\n";
 | |
|     brightness_control.close();
 | |
|   }
 | |
| }
 | |
| 
 | |
| GLWindow::GLWindow(QWidget* parent) : QOpenGLWidget(parent) {
 | |
|   timer = new QTimer(this);
 | |
|   QObject::connect(timer, SIGNAL(timeout()), this, SLOT(timerUpdate()));
 | |
| 
 | |
|   backlight_timer = new QTimer(this);
 | |
|   QObject::connect(backlight_timer, SIGNAL(timeout()), this, SLOT(backlightUpdate()));
 | |
| 
 | |
|   int result = read_param(&brightness_b, "BRIGHTNESS_B", true);
 | |
|   result += read_param(&brightness_m, "BRIGHTNESS_M", true);
 | |
|   if (result != 0) {
 | |
|     brightness_b = 200.0;
 | |
|     brightness_m = 10.0;
 | |
|   }
 | |
|   smooth_brightness = 512;
 | |
| }
 | |
| 
 | |
| GLWindow::~GLWindow() {
 | |
|   makeCurrent();
 | |
|   doneCurrent();
 | |
| }
 | |
| 
 | |
| void GLWindow::initializeGL() {
 | |
|   initializeOpenGLFunctions();
 | |
|   std::cout << "OpenGL version: " << glGetString(GL_VERSION) << std::endl;
 | |
|   std::cout << "OpenGL vendor: " << glGetString(GL_VENDOR) << std::endl;
 | |
|   std::cout << "OpenGL renderer: " << glGetString(GL_RENDERER) << std::endl;
 | |
|   std::cout << "OpenGL language version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl;
 | |
| 
 | |
|   ui_state.sound = &sound;
 | |
|   ui_init(&ui_state);
 | |
| 
 | |
|   wake();
 | |
| 
 | |
|   prev_draw_t = millis_since_boot();
 | |
|   timer->start(1000 / UI_FREQ);
 | |
|   backlight_timer->start(BACKLIGHT_DT * 1000);
 | |
| }
 | |
| 
 | |
| void GLWindow::backlightUpdate() {
 | |
|   // Update brightness
 | |
|   float k = (BACKLIGHT_DT / BACKLIGHT_TS) / (1.0f + BACKLIGHT_DT / BACKLIGHT_TS);
 | |
| 
 | |
|   float clipped_brightness = std::min(1023.0f, (ui_state.light_sensor * brightness_m) + brightness_b);
 | |
|   smooth_brightness = clipped_brightness * k + smooth_brightness * (1.0f - k);
 | |
|   int brightness = smooth_brightness;
 | |
| 
 | |
|   if (!ui_state.awake) {
 | |
|     brightness = 0;
 | |
|   }
 | |
| 
 | |
|   std::thread{set_backlight, brightness}.detach();
 | |
| }
 | |
| 
 | |
| void GLWindow::timerUpdate() {
 | |
|   // Connecting to visionIPC requires opengl to be current
 | |
|   if (!ui_state.vipc_client->connected){
 | |
|     makeCurrent();
 | |
|   }
 | |
| 
 | |
|   if (ui_state.started != onroad) {
 | |
|     onroad = ui_state.started;
 | |
|     emit offroadTransition(!onroad);
 | |
| 
 | |
|     // Change timeout to 0 when onroad, this will call timerUpdate continously.
 | |
|     // This puts visionIPC in charge of update frequency, reducing video latency
 | |
|     timer->start(onroad ? 0 : 1000 / UI_FREQ);
 | |
|   }
 | |
| 
 | |
|   handle_display_state(&ui_state, false);
 | |
| 
 | |
|   ui_update(&ui_state);
 | |
|   repaint();
 | |
| }
 | |
| 
 | |
| void GLWindow::resizeGL(int w, int h) {
 | |
|   std::cout << "resize " << w << "x" << h << std::endl;
 | |
| }
 | |
| 
 | |
| void GLWindow::paintGL() {
 | |
|   if(GLWindow::ui_state.awake){
 | |
|     ui_draw(&ui_state);
 | |
| 
 | |
|     double cur_draw_t = millis_since_boot();
 | |
|     double dt = cur_draw_t - prev_draw_t;
 | |
|     if (dt > 66){
 | |
|       // warn on sub 15fps
 | |
|       LOGW("slow frame(%llu) time: %.2f", ui_state.sm->frame, dt);
 | |
|     }
 | |
|     prev_draw_t = cur_draw_t;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void GLWindow::wake() {
 | |
|   handle_display_state(&ui_state, true);
 | |
| }
 | |
| 
 | |
| FramebufferState* framebuffer_init(const char* name, int32_t layer, int alpha,
 | |
|                                    int *out_w, int *out_h) {
 | |
|   *out_w = vwp_w;
 | |
|   *out_h = vwp_h;
 | |
|   return (FramebufferState*)1; // not null
 | |
| }
 | |
| 
 |