Files
clearpilot/selfdrive/ui/qt/ready.cc
Brian Hanson 698a1647a0 nightrider mode improvements, nice monitor, ready text 1x
- Nightrider: lines 1px wider (3px outline), engagement border hidden,
  planner lines hidden when disengaged, stay on onroad view in park
- Normal mode only: return to ready splash on park
- Ready text sprite at native 1x size
- Nice monitor: keeps claude processes at nice 19, runs every 30s

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 23:24:37 -05:00

129 lines
4.0 KiB
C++
Executable File

#include "selfdrive/ui/qt/ready.h"
#include <cmath>
#include <algorithm>
#include <QPainter>
#include <QStackedLayout>
#include <QApplication>
#include <QGridLayout>
#include <QString>
#include <QTransform>
#include <QPixmap>
#include "common/params.h"
#include "common/timing.h"
#include "system/hardware/hw.h"
#include "selfdrive/ui/qt/qt_window.h"
#include "selfdrive/ui/qt/util.h"
ReadyWindow::ReadyWindow(QWidget *parent) : QWidget(parent) {
QGridLayout *layout = new QGridLayout(this);
layout->setSpacing(0);
layout->setMargin(0);
setAttribute(Qt::WA_OpaquePaintEvent);
setStyleSheet("ReadyWindow { background-color: black; }");
timer = new QTimer(this);
timer->callOnTimeout(this, &ReadyWindow::refresh);
uptime.start();
}
void ReadyWindow::showEvent(QShowEvent *event) {
refresh();
timer->start(2 * 1000);
}
void ReadyWindow::hideEvent(QHideEvent *event) {
timer->stop();
}
void ReadyWindow::paintEvent(QPaintEvent *event) {
QPainter painter(this);
painter.fillRect(rect(), Qt::black);
if (is_hot) {
if (img_hot.isNull()) {
img_hot.load("/data/openpilot/selfdrive/clearpilot/theme/clearpilot/images/hot.png");
}
int x = (width() - img_hot.width()) / 2;
int y = (height() - img_hot.height()) / 2;
painter.drawPixmap(x, y, img_hot);
} else {
// Boot logo — same rotation as spinner (bg.jpg is pre-rotated 90° CW for framebuffer)
if (img_bg.isNull()) {
QPixmap raw("/usr/comma/bg.jpg");
if (!raw.isNull()) {
QTransform rot;
rot.rotate(-90);
img_bg = raw.transformed(rot);
}
}
if (!img_bg.isNull()) {
QPixmap scaled = img_bg.scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
int x = (width() - scaled.width()) / 2;
int y = (height() - scaled.height()) / 2;
painter.drawPixmap(x, y, scaled);
}
if (error_msg.isEmpty()) {
// "READY!" 8-bit text sprite, 2x size, 15% below center
static QPixmap ready_text("/data/openpilot/selfdrive/clearpilot/theme/clearpilot/images/ready_text.png");
if (!ready_text.isNull()) {
int tx = (width() - ready_text.width()) / 2;
int ty = height() / 2 + height() * 15 / 100;
painter.drawPixmap(tx, ty, ready_text);
}
} else {
// Error state: red text at 25% below center
QFont font("Inter", 50, QFont::Bold);
painter.setFont(font);
painter.setPen(QColor(0xFF, 0x44, 0x44));
int ty = height() / 2 + height() * 25 / 100;
QRect text_rect(0, ty, width(), 100);
painter.drawText(text_rect, Qt::AlignHCenter | Qt::AlignTop, error_msg);
}
}
}
void ReadyWindow::refresh() {
bool changed = false;
// Temperature check
std::string bytes = params.get("Offroad_TemperatureTooHigh");
bool was_hot = is_hot;
if (!bytes.empty()) {
auto doc = QJsonDocument::fromJson(bytes.data());
is_hot = true;
cur_temp = doc["extra"].toString();
} else {
is_hot = false;
}
if (is_hot != was_hot) changed = true;
// Error state checks (only when not hot — hot has its own display)
if (!is_hot) {
QString prev_error = error_msg;
// Panda check — same logic as sidebar, with 10s grace period on startup
if (uptime.elapsed() > 10000 &&
uiState()->scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) {
error_msg = "PANDA NOT CONNECTED";
}
// Car unrecognized check
else if (!params.get("Offroad_CarUnrecognized").empty()) {
error_msg = "CAR NOT RECOGNIZED";
}
else {
error_msg = "";
}
if (error_msg != prev_error) changed = true;
}
if (changed) update();
}