3 Commits

Author SHA1 Message Date
brianhansonxyz 2ddb7fc764 feat: onroad health overlay + 2x tire path in nightrider
System health overlay:
- Lower-right 5-metric panel: LAG (controlsState.cumLagMs), DROP
  (modelV2.frameDropPerc), TEMP (deviceState.maxTempC), CPU (max core of
  deviceState.cpuUsagePercent), MEM (deviceState.memoryUsagePercent)
- Color-coded white→yellow→red by severity (LAG: 50/200ms, DROP: 5/15%,
  TEMP: 75/88°C, CPU: 75/90%, MEM: 70/85%)
- Toggle in ClearPilot → Debug → "System Health Overlay"
- New param ClearpilotShowHealthMetrics, PERSISTENT (disk, survives
  reboots), default false — re-polled every ~2s so toggle takes effect
  without process restart
- InterFont(90, Bold) to match speed limit numeric styling, 30px margin,
  40px between rows, black rounded background

Nightrider center lane path (the "tire track" polygon from
scene.track_vertices) is now drawn at 2x the width of other lines —
highlights the planned path distinctly against the otherwise stark
outline-only rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 22:03:30 -05:00
brianhansonxyz 22ced0c558 cleanup: remove dead CarCruiseDisplayActual param
Audit of post-fork param additions found CarCruiseDisplayActual was
written every CAN cycle (gated) but only consumed by hyundaicanfd.py::
create_buttons_alt, which has `return` on line 1 and no active callers.
Write was pure waste. Removed registration, write path, cache field,
and the dead read.

Also dropped the now-unused `from openpilot.common.params import Params`
in hyundaicanfd.py.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 22:03:01 -05:00
brianhansonxyz ef4e02e354 feat: 10fps daytime / 4fps night modeld + 2Hz GPS
Reduced-rate modeld path now branches on IsDaylight:
- daylight: skip 1/2 frames → 10fps (better model responsiveness when
  lighting gives the net more signal)
- night: skip 4/5 frames → 4fps (unchanged, conservative for power)

IsDaylight is already in /dev/shm (memory) via gpsd.py. Gated the
IsDaylight write on change — it flips twice a day, no reason to rewrite
every 30s. GPS polling bumped from 1Hz → 2Hz.

ModelFps publishes "10" / "4" / "20" so longitudinal_planner's dt and
FCW-threshold scaling (if re-enabled) still track actual rate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 21:41:29 -05:00
10 changed files with 109 additions and 19 deletions
+1 -1
View File
@@ -251,7 +251,6 @@ std::unordered_map<std::string, uint32_t> keys = {
{"CarMake", PERSISTENT},
{"CarModel", PERSISTENT},
{"CarCruiseDisplayActual", PERSISTENT},
{"CarSpeedLimit", PERSISTENT},
{"CarSpeedLimitWarning", PERSISTENT},
@@ -293,6 +292,7 @@ std::unordered_map<std::string, uint32_t> keys = {
{"CEStopLights", PERSISTENT},
{"CEStopLightsLead", PERSISTENT},
{"Compass", PERSISTENT},
{"ClearpilotShowHealthMetrics", PERSISTENT},
{"ConditionalExperimental", PERSISTENT},
{"CrosstrekTorque", PERSISTENT},
{"CurrentHolidayTheme", PERSISTENT},
+1 -6
View File
@@ -51,7 +51,6 @@ class CarState(CarStateBase):
# CLEARPILOT: cache to avoid per-cycle atomic writes to /dev/shm (eats CPU via fsync/flock)
self._prev_car_speed_limit = None
self._prev_car_is_metric = None
self._prev_car_cruise_display = None
self.cruise_info = {}
@@ -223,11 +222,7 @@ class CarState(CarStateBase):
if self.is_metric != self._prev_car_is_metric:
self.params_memory.put("CarIsMetric", "1" if self.is_metric else "0")
self._prev_car_is_metric = self.is_metric
car_cruise_display = cp_cruise.vl["SCC11"]["VSetDis"]
if car_cruise_display != self._prev_car_cruise_display:
self.params_memory.put_float("CarCruiseDisplayActual", car_cruise_display)
self._prev_car_cruise_display = car_cruise_display
return ret
+1 -4
View File
@@ -2,7 +2,6 @@ from openpilot.common.numpy_fast import clip
from openpilot.selfdrive.car import CanBusBase
from openpilot.selfdrive.car.hyundai.values import HyundaiFlags
from openpilot.common.params import Params
class CanBus(CanBusBase):
def __init__(self, CP, hda2=None, fingerprint=None) -> None:
@@ -127,9 +126,7 @@ def create_buttons(packer, CP, CAN, cnt, btn):
def create_buttons_alt(packer, CP, CAN, cnt, btn, template):
return
params_memory = Params("/dev/shm/params")
CarCruiseDisplayActual = params_memory.get_float("CarCruiseDisplayActual")
values = {
"COUNTER": cnt,
"NEW_SIGNAL_1": 0.0,
Binary file not shown.
+9 -5
View File
@@ -273,17 +273,21 @@ def main(demo=False):
last_vipc_frame_id = meta_main.frame_id
continue
# Reduced framerate: 4fps when not lat active and not standstill
# Skip 4 out of every 5 frames (20fps -> 4fps)
# Reduced framerate: daylight 10fps (skip 1/2), night 4fps (skip 4/5)
# Daytime runs twice as fast — better model responsiveness when lighting is good
# and the neural net has more signal. Night stays conservative for power.
# Write standby timestamp so controlsd suppresses transient errors
if not full_rate:
if params_memory.get("ModelFps") != b"4":
params_memory.put("ModelFps", "4")
is_daylight = params_memory.get_bool("IsDaylight")
skip_interval = 2 if is_daylight else 5
target_fps = b"10" if is_daylight else b"4"
if params_memory.get("ModelFps") != target_fps:
params_memory.put("ModelFps", target_fps.decode())
now = _time.monotonic()
if now - last_standby_ts_write > 1.0:
params_memory.put("ModelStandbyTs", str(now))
last_standby_ts_write = now
if run_count % 5 != 0:
if run_count % skip_interval != 0:
last_vipc_frame_id = meta_main.frame_id
run_count += 1
continue
+9
View File
@@ -338,6 +338,15 @@ ClearPilotPanel::ClearPilotPanel(QWidget* parent) : QFrame(parent) {
});
debug_panel->addItem(telemetry_toggle);
auto *health_metrics_toggle = new ToggleControl("System Health Overlay",
"Show controls lag, model frame drops, temperature, CPU, and memory usage "
"in the lower-right of the onroad UI. For diagnosing slowdown issues.", "",
Params().getBool("ClearpilotShowHealthMetrics"), this);
QObject::connect(health_metrics_toggle, &ToggleControl::toggleFlipped, [](bool on) {
Params().putBool("ClearpilotShowHealthMetrics", on);
});
debug_panel->addItem(health_metrics_toggle);
auto *vpn_toggle = new ToggleControl("VPN",
"Connect to vpn.hanson.xyz for remote SSH access. "
"Disabling kills the active tunnel and stops reconnection attempts.", "",
+81 -1
View File
@@ -468,6 +468,9 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) {
drawSpeedLimitSign(p);
drawCruiseWarningSign(p);
// CLEARPILOT: system health metrics in lower-right (debug overlay)
drawHealthMetrics(p);
// Draw FrogPilot widgets
paintFrogPilotWidgets(p);
}
@@ -727,6 +730,82 @@ void AnnotatedCameraWidget::drawText(QPainter &p, int x, int y, const QString &t
p.drawText(real_rect.x(), real_rect.bottom(), text);
}
// CLEARPILOT: System health overlay — shows metrics that indicate the system
// is overburdened or behind. Toggled via ClearpilotShowHealthMetrics param.
// Metrics (top→bottom): LAG, DROP, TEMP, CPU, MEM
// LAG (ms): controlsd cumLagMs — most direct "is the loop keeping up" indicator
// DROP (%): modelV2 frameDropPerc — modeld losing frames; controlsd errors >20%
// TEMP (°C): deviceState.maxTempC — thermal throttling starts ~75, serious >88
// CPU (%): max core from deviceState.cpuUsagePercent
// MEM (%): deviceState.memoryUsagePercent
// Each value color-codes green/yellow/red by severity.
void AnnotatedCameraWidget::drawHealthMetrics(QPainter &p) {
static bool enabled = Params().getBool("ClearpilotShowHealthMetrics");
static int check_counter = 0;
// re-check the param every ~2s without a toggle signal path
if (++check_counter >= 40) {
check_counter = 0;
enabled = Params().getBool("ClearpilotShowHealthMetrics");
}
if (!enabled) return;
SubMaster &sm = *(uiState()->sm);
auto cs = sm["controlsState"].getControlsState();
auto ds = sm["deviceState"].getDeviceState();
auto mv = sm["modelV2"].getModelV2();
float lag_ms = cs.getCumLagMs();
float drop_pct = mv.getFrameDropPerc();
float temp_c = ds.getMaxTempC();
int mem_pct = ds.getMemoryUsagePercent();
int cpu_pct = 0;
for (auto v : ds.getCpuUsagePercent()) cpu_pct = std::max(cpu_pct, (int)v);
auto color_for = [](float v, float warn, float crit) {
if (v >= crit) return QColor(0xff, 0x50, 0x50); // red
if (v >= warn) return QColor(0xff, 0xd0, 0x40); // yellow
return QColor(0xff, 0xff, 0xff); // white (ok)
};
struct Row { QString label; QString value; QColor color; };
Row rows[] = {
{"LAG", QString::number((int)lag_ms), color_for(lag_ms, 50.f, 200.f)},
{"DROP", QString::number((int)drop_pct),color_for(drop_pct, 5.f, 15.f)},
{"TEMP", QString::number((int)temp_c), color_for(temp_c, 75.f, 88.f)},
{"CPU", QString::number(cpu_pct), color_for((float)cpu_pct, 75.f, 90.f)},
{"MEM", QString::number(mem_pct), color_for((float)mem_pct, 70.f, 85.f)},
};
p.save();
p.setFont(InterFont(90, QFont::Bold));
QFontMetrics fm = p.fontMetrics();
int row_h = fm.height(); // natural line height at 90pt bold
int gap = 40; // requested 40px between values
int margin = 30; // requested 30px margin
int panel_w = 360; // fixed width — fits "TEMP 99"
int n = sizeof(rows) / sizeof(rows[0]);
int panel_h = n * row_h + (n - 1) * gap + 2 * margin;
int x = width() - panel_w - margin;
int y = height() - panel_h - margin;
// black background
p.setPen(Qt::NoPen);
p.setBrush(QColor(0, 0, 0, 200));
p.drawRoundedRect(QRect(x, y, panel_w, panel_h), 20, 20);
// rows
int text_y = y + margin + fm.ascent();
for (int i = 0; i < n; i++) {
p.setPen(rows[i].color);
// label left, value right
p.drawText(x + margin, text_y, rows[i].label);
QRect vrect = fm.boundingRect(rows[i].value);
p.drawText(x + panel_w - margin - vrect.width(), text_y, rows[i].value);
text_y += row_h + gap;
}
p.restore();
}
void AnnotatedCameraWidget::initializeGL() {
CameraWidget::initializeGL();
qInfo() << "OpenGL version:" << QString((const char*)glGetString(GL_VERSION));
@@ -867,8 +946,9 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) {
}
if (outlineOnly) {
// CLEARPILOT: center path (tire track) is 2x wider than other lines in nightrider
painter.setPen(QPen(QColor(center_lane_color.red(), center_lane_color.green(),
center_lane_color.blue(), 180), outlineWidth));
center_lane_color.blue(), 180), outlineWidth * 2));
painter.setBrush(Qt::NoBrush);
} else {
painter.setPen(Qt::NoPen);
+1
View File
@@ -53,6 +53,7 @@ private:
void drawSpeedWidget(QPainter &p, int x, int y, const QString &title, const QString &speedLimitStr, QColor colorSpeed, int width = 176);
void drawSpeedLimitSign(QPainter &p);
void drawCruiseWarningSign(QPainter &p);
void drawHealthMetrics(QPainter &p);
QVBoxLayout *main_layout;
QPixmap dm_img;
Binary file not shown.
+6 -2
View File
@@ -159,6 +159,7 @@ def main():
params_memory = Params("/dev/shm/params")
last_daylight_check = 0.0
daylight_computed = False
prev_daylight = None # CLEARPILOT: gate IsDaylight write on change
print("gpsd: entering main loop", file=sys.stderr, flush=True)
while True:
@@ -205,7 +206,10 @@ def main():
last_daylight_check = now_mono
utc_now = datetime.datetime.utcfromtimestamp(fix["timestamp_ms"] / 1000)
daylight = is_daylight(fix["latitude"], fix["longitude"], utc_now)
params_memory.put_bool("IsDaylight", daylight)
# CLEARPILOT: gate on change — daylight flips twice a day, don't rewrite every 30s
if daylight != prev_daylight:
params_memory.put_bool("IsDaylight", daylight)
prev_daylight = daylight
if not daylight_computed:
daylight_computed = True
@@ -221,7 +225,7 @@ def main():
params_memory.put_int("ScreenDisplayMode", 0)
cloudlog.warning("gpsd: auto-switch to normal (sunrise)")
time.sleep(1.0) # 1 Hz polling
time.sleep(0.5) # 2 Hz polling
if __name__ == "__main__":