UI overhaul, VPN management, controlsd fix, screen recorder removal
- Fix controlsd crash: self.state.name AttributeError when state is int - Move telemetry tlog import to module level in carstate.py (was per-frame) - Remove FrogPilot screen recorder from UI (was crashing OMX on init) - Ready screen: boot logo background, 8-bit READY! sprite, error states (panda not connected, car not recognized) with 10s startup grace period - ClearPilot menu: always opens to General, QButtonGroup for sidebar, System Status uses ButtonControl, VPN toggle with process control - Sidebar hidden on construction (no flash before splash) - Status window: threaded data collection (QtConcurrent), panda detection via scene.pandaType (SPI, not USB), only refreshes when visible - VPN: moved to system/clearpilot/, SIGTERM graceful shutdown, keepalive ping through tunnel, killall openvpn on disable, launched from launch_openpilot.sh instead of continue.sh - Disable gpsd and dashcamd temporarily for perf testing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -182,11 +182,12 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
|
||||
return ignore;
|
||||
}
|
||||
|
||||
// CLEARPILOT: Status window — live system stats, refreshed every second
|
||||
// CLEARPILOT: Status window — live system stats, collected on background thread
|
||||
|
||||
#include <QFile>
|
||||
#include <QProcess>
|
||||
#include <QDateTime>
|
||||
#include <QtConcurrent>
|
||||
|
||||
static QString readFile(const QString &path) {
|
||||
QFile f(path);
|
||||
@@ -201,6 +202,65 @@ static QString shellCmd(const QString &cmd) {
|
||||
return QString(p.readAllStandardOutput()).trimmed();
|
||||
}
|
||||
|
||||
static StatusWindow::StatusData collectStatus() {
|
||||
StatusWindow::StatusData d;
|
||||
|
||||
d.time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
|
||||
d.storage = shellCmd("df -h /data | tail -1 | awk '{print $3 \" / \" $2 \" (\" $5 \" used)\"}'");
|
||||
|
||||
// RAM from /proc/meminfo (no subprocess needed)
|
||||
QString meminfo = readFile("/proc/meminfo");
|
||||
long total = 0, avail = 0;
|
||||
for (const QString &line : meminfo.split('\n')) {
|
||||
if (line.startsWith("MemTotal:")) total = line.split(QRegExp("\\s+"))[1].toLong();
|
||||
if (line.startsWith("MemAvailable:")) avail = line.split(QRegExp("\\s+"))[1].toLong();
|
||||
}
|
||||
if (total > 0) {
|
||||
long used = total - avail;
|
||||
d.ram = QString("%1 / %2 MB").arg(used / 1024).arg(total / 1024);
|
||||
}
|
||||
|
||||
// Load from /proc/loadavg
|
||||
QString loadavg = readFile("/proc/loadavg");
|
||||
QStringList parts = loadavg.split(' ');
|
||||
if (parts.size() >= 3) {
|
||||
d.load = QString("%1 %2 %3").arg(parts[0], parts[1], parts[2]);
|
||||
}
|
||||
|
||||
// Temperature
|
||||
QString temps = shellCmd("cat /sys/class/thermal/thermal_zone*/temp 2>/dev/null | sort -rn | head -1");
|
||||
if (!temps.isEmpty()) {
|
||||
d.temp_c = temps.toLong() / 1000.0f;
|
||||
d.temp = QString("%1\u00B0C").arg(d.temp_c, 0, 'f', 1);
|
||||
}
|
||||
|
||||
// Fan
|
||||
QString fan = shellCmd("cat /sys/class/hwmon/hwmon*/fan1_input 2>/dev/null | head -1");
|
||||
if (fan.isEmpty()) fan = readFile("/dev/shm/params/d/LastFanSpeed");
|
||||
d.fan = fan.isEmpty() ? QString::fromUtf8("\u2014") : fan + " RPM";
|
||||
|
||||
// Network
|
||||
d.ip = shellCmd("ip route get 1.1.1.1 2>/dev/null | head -1 | awk '{print $7}'");
|
||||
d.wifi = shellCmd("iwconfig wlan0 2>/dev/null | grep -oP 'ESSID:\"\\K[^\"]*'");
|
||||
|
||||
// VPN
|
||||
QString tun = shellCmd("ip link show tun0 2>/dev/null | head -1");
|
||||
if (tun.contains("UP")) {
|
||||
d.vpn_status = "up";
|
||||
d.vpn_ip = shellCmd("ip addr show tun0 2>/dev/null | grep 'inet ' | awk '{print $2}'");
|
||||
}
|
||||
|
||||
// GPS
|
||||
d.gps = readFile("/data/params/d/LastGPSPosition");
|
||||
|
||||
// Telemetry
|
||||
d.telemetry = readFile("/data/params/d/TelemetryEnabled");
|
||||
|
||||
// Panda: checked on UI thread in applyResults() via scene.pandaType
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
StatusWindow::StatusWindow(QWidget *parent) : QFrame(parent) {
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(50, 60, 50, 40);
|
||||
@@ -232,6 +292,7 @@ StatusWindow::StatusWindow(QWidget *parent) : QFrame(parent) {
|
||||
load_label = makeRow("Load");
|
||||
temp_label = makeRow("Temperature");
|
||||
fan_label = makeRow("Fan Speed");
|
||||
panda_label = makeRow("Panda");
|
||||
ip_label = makeRow("IP Address");
|
||||
wifi_label = makeRow("WiFi");
|
||||
vpn_label = makeRow("VPN");
|
||||
@@ -242,89 +303,66 @@ StatusWindow::StatusWindow(QWidget *parent) : QFrame(parent) {
|
||||
|
||||
setStyleSheet("StatusWindow { background-color: black; }");
|
||||
|
||||
// Refresh every second
|
||||
connect(&watcher, &QFutureWatcher<StatusData>::finished, this, &StatusWindow::applyResults);
|
||||
|
||||
QTimer *timer = new QTimer(this);
|
||||
connect(timer, &QTimer::timeout, this, &StatusWindow::refresh);
|
||||
connect(timer, &QTimer::timeout, this, &StatusWindow::kickRefresh);
|
||||
timer->start(1000);
|
||||
refresh();
|
||||
}
|
||||
|
||||
void StatusWindow::refresh() {
|
||||
// Time
|
||||
time_label->setText(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));
|
||||
void StatusWindow::kickRefresh() {
|
||||
if (!isVisible() || collecting) return;
|
||||
collecting = true;
|
||||
watcher.setFuture(QtConcurrent::run(collectStatus));
|
||||
}
|
||||
|
||||
// Storage
|
||||
QString df = shellCmd("df -h /data | tail -1 | awk '{print $3 \" / \" $2 \" (\" $5 \" used)\"}'");
|
||||
storage_label->setText(df);
|
||||
void StatusWindow::applyResults() {
|
||||
collecting = false;
|
||||
StatusData d = watcher.result();
|
||||
|
||||
// RAM
|
||||
QString meminfo = readFile("/proc/meminfo");
|
||||
long total = 0, avail = 0;
|
||||
for (const QString &line : meminfo.split('\n')) {
|
||||
if (line.startsWith("MemTotal:")) total = line.split(QRegExp("\\s+"))[1].toLong();
|
||||
if (line.startsWith("MemAvailable:")) avail = line.split(QRegExp("\\s+"))[1].toLong();
|
||||
}
|
||||
if (total > 0) {
|
||||
long used = total - avail;
|
||||
ram_label->setText(QString("%1 / %2 MB").arg(used / 1024).arg(total / 1024));
|
||||
time_label->setText(d.time);
|
||||
storage_label->setText(d.storage);
|
||||
if (!d.ram.isEmpty()) ram_label->setText(d.ram);
|
||||
if (!d.load.isEmpty()) load_label->setText(d.load);
|
||||
|
||||
if (!d.temp.isEmpty()) {
|
||||
temp_label->setText(d.temp);
|
||||
temp_label->setStyleSheet(d.temp_c > 70 ? "color: #ff4444; font-size: 38px;" :
|
||||
d.temp_c > 55 ? "color: #ffaa00; font-size: 38px;" :
|
||||
"color: white; font-size: 38px;");
|
||||
}
|
||||
|
||||
// Load
|
||||
QString loadavg = readFile("/proc/loadavg");
|
||||
QStringList parts = loadavg.split(' ');
|
||||
if (parts.size() >= 3) {
|
||||
load_label->setText(QString("%1 %2 %3").arg(parts[0], parts[1], parts[2]));
|
||||
fan_label->setText(d.fan);
|
||||
|
||||
// Panda: same check as sidebar — read scene.pandaType on UI thread
|
||||
if (uiState()->scene.pandaType != cereal::PandaState::PandaType::UNKNOWN) {
|
||||
panda_label->setText("Connected");
|
||||
panda_label->setStyleSheet("color: #17c44d; font-size: 38px;");
|
||||
} else {
|
||||
panda_label->setText("Not connected");
|
||||
panda_label->setStyleSheet("color: #ff4444; font-size: 38px;");
|
||||
}
|
||||
|
||||
// Temperature
|
||||
QString temps = shellCmd("cat /sys/class/thermal/thermal_zone*/temp 2>/dev/null | sort -rn | head -1");
|
||||
if (!temps.isEmpty()) {
|
||||
float temp_c = temps.toLong() / 1000.0;
|
||||
temp_label->setText(QString("%1°C").arg(temp_c, 0, 'f', 1));
|
||||
temp_label->setStyleSheet(temp_c > 70 ? "color: #ff4444; font-size: 38px;" :
|
||||
temp_c > 55 ? "color: #ffaa00; font-size: 38px;" :
|
||||
"color: white; font-size: 38px;");
|
||||
}
|
||||
ip_label->setText(d.ip.isEmpty() ? "No connection" : d.ip);
|
||||
wifi_label->setText(d.wifi.isEmpty() ? "Not connected" : d.wifi);
|
||||
|
||||
// Fan speed
|
||||
QString fan = shellCmd("cat /sys/class/hwmon/hwmon*/fan1_input 2>/dev/null | head -1");
|
||||
if (fan.isEmpty()) {
|
||||
// Try reading from deviceState param as fallback
|
||||
fan = shellCmd("cat /dev/shm/params/d/LastFanSpeed 2>/dev/null");
|
||||
}
|
||||
fan_label->setText(fan.isEmpty() ? "—" : fan + " RPM");
|
||||
|
||||
// IP + WiFi
|
||||
QString ip = shellCmd("ip route get 1.1.1.1 2>/dev/null | head -1 | awk '{print $7}'");
|
||||
ip_label->setText(ip.isEmpty() ? "No connection" : ip);
|
||||
|
||||
QString essid = shellCmd("iwconfig wlan0 2>/dev/null | grep -oP 'ESSID:\"\\K[^\"]*'");
|
||||
wifi_label->setText(essid.isEmpty() ? "Not connected" : essid);
|
||||
|
||||
// VPN
|
||||
QString tun = shellCmd("ip link show tun0 2>/dev/null | head -1");
|
||||
if (tun.contains("UP")) {
|
||||
QString vpn_ip = shellCmd("ip addr show tun0 2>/dev/null | grep 'inet ' | awk '{print $2}'");
|
||||
vpn_label->setText("Connected (" + vpn_ip + ")");
|
||||
if (d.vpn_status == "up") {
|
||||
vpn_label->setText("Connected (" + d.vpn_ip + ")");
|
||||
vpn_label->setStyleSheet("color: #17c44d; font-size: 38px;");
|
||||
} else {
|
||||
vpn_label->setText("Not connected");
|
||||
vpn_label->setStyleSheet("color: #ff4444; font-size: 38px;");
|
||||
}
|
||||
|
||||
// GPS
|
||||
QString gps_raw = shellCmd("cat /data/params/d/LastGPSPosition 2>/dev/null");
|
||||
if (gps_raw.isEmpty()) {
|
||||
if (d.gps.isEmpty()) {
|
||||
gps_label->setText("No fix");
|
||||
gps_label->setStyleSheet("color: #ff4444; font-size: 38px;");
|
||||
} else {
|
||||
gps_label->setText(gps_raw);
|
||||
gps_label->setText(d.gps);
|
||||
gps_label->setStyleSheet("color: white; font-size: 38px;");
|
||||
}
|
||||
|
||||
// Telemetry
|
||||
QString telem = shellCmd("cat /data/params/d/TelemetryEnabled 2>/dev/null");
|
||||
if (telem == "1") {
|
||||
if (d.telemetry == "1") {
|
||||
telemetry_label->setText("Enabled");
|
||||
telemetry_label->setStyleSheet("color: #17c44d; font-size: 38px;");
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user