telemetry overlay, memory params fix, nightrider border, GPS logging

- Telemetry status bar in onroad UI: temp, fan %, model FPS, standstill
- Fix paramsMemory usage: Params("/dev/shm/params") not "/dev/shm/params/d"
- Telemetry/VPN toggles use ToggleControl with manual paramsMemory writes
- TelemetryEnabled/VpnEnabled registered PERSISTENT, written to memory path
- GPS telemetry: telemetryd subscribes to gpsLocation at 1Hz via cereal
- Nightrider: force CameraWidget bg black to eliminate color bleed border
- Suppress "Always On Lateral active" status bar message
- Re-enable gpsd and dashcamd
- CLAUDE.md: document memory params pattern, speed_limit.calculated usage

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-14 00:36:29 -05:00
parent 698a1647a0
commit 3d9143c41b
9 changed files with 97 additions and 15 deletions

View File

@@ -92,6 +92,17 @@ The params system uses a C++ whitelist. Adding a new param name in `manager.py`
2. Add the default value in `selfdrive/manager/manager.py` in `manager_init()`
3. Remove `prebuilt`, `common/params.o`, and `common/libcommon.a` to force rebuild
### Memory Params (paramsMemory)
ClearPilot uses memory params (`/dev/shm/params/d/`) for UI toggles that should reset on boot. Key rules:
- **Registration**: Register the key in `common/params.cc` as `PERSISTENT` (same as FrogPilot pattern — the registration flag does NOT control which path the param lives at)
- **C++ access**: Use `Params{"/dev/shm/params"}` — stored as a class member `paramsMemory` in onroad.h. Do NOT use `Params("/dev/shm/params/d")` — the Params class appends `/d/` internally, so that would resolve to `/dev/shm/params/d/d/`
- **Python access**: Use `Params("/dev/shm/params")`
- **Defaults**: Set in `manager_init()` via `Params("/dev/shm/params").put(key, value)`
- **UI toggles**: Use `ToggleControl` with manual `toggleFlipped` lambda that writes via `Params("/dev/shm/params")`. Do NOT use `ParamControl` for memory params — it reads/writes persistent params only
- **Current memory params**: `TelemetryEnabled` (default "0"), `VpnEnabled` (default "1"), `ScreenDisplayMode`
### Building Native (C++) Processes
- SCons is the build system. Static libraries (`common`, `messaging`, `cereal`, `visionipc`) must be imported as SCons objects, not `-l` flags
@@ -482,10 +493,13 @@ Power On
- **Client**: `selfdrive/clearpilot/telemetry.py``tlog(group, data)` sends JSON over ZMQ PUSH
- **Collector**: `selfdrive/clearpilot/telemetryd.py` — diffs against previous state, writes changed values to CSV
- **Toggle**: `TelemetryEnabled` param, controlled from Debug panel in ClearPilot menu
- **Toggle**: `TelemetryEnabled` memory param, controlled from Debug panel in ClearPilot menu
- **Auto-disable**: disabled on every manager start; disabled if `/data` free < 5GB
- **Hyundai CAN-FD data**: logged from `selfdrive/car/hyundai/carstate.py` `update_canfd()` — groups: `car`, `cruise`, `speed_limit`, `buttons`
- **GPS data**: logged directly by telemetryd via cereal `gpsLocation` subscription at 1Hz — group: `gps` (latitude, longitude, speed, altitude, bearing, accuracy)
- **CSV location**: `/data/log2/current/telemetry.csv` (or session directory)
- **Onroad overlay**: when telemetry enabled, status bar shows temp, fan %, model FPS, and STANDSTILL indicator
- **Speed limit**: `speed_limit.calculated` is the final computed speed limit value (in vehicle units, MPH when `is_metric=False`). This is the value that will be used for the future speed limit warning chime feature
### Key Dependencies

View File

@@ -199,7 +199,7 @@ std::unordered_map<std::string, uint32_t> keys = {
{"Timezone", PERSISTENT},
{"TrainingVersion", PERSISTENT},
{"UbloxAvailable", PERSISTENT},
{"VpnEnabled", CLEAR_ON_MANAGER_START},
{"VpnEnabled", PERSISTENT},
{"UpdateAvailable", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
{"UpdateFailedCount", CLEAR_ON_MANAGER_START},
{"UpdaterAvailableBranches", PERSISTENT},

Binary file not shown.

View File

@@ -17,6 +17,7 @@ import shutil
import time
import zmq
import cereal.messaging as messaging
from openpilot.common.params import Params
from openpilot.selfdrive.clearpilot.telemetry import TELEMETRY_SOCK
from openpilot.selfdrive.manager.process import _log_dir, session_log
@@ -26,12 +27,16 @@ DISK_CHECK_INTERVAL = 10 # seconds
def main():
params = Params()
params = Params("/dev/shm/params")
ctx = zmq.Context.instance()
sock = ctx.socket(zmq.PULL)
sock.setsockopt(zmq.RCVHWM, 1000)
sock.bind(TELEMETRY_SOCK)
# GPS subscriber for location telemetry
sm = messaging.SubMaster(['gpsLocation'])
last_gps_log = 0
csv_path = os.path.join(_log_dir, "telemetry.csv")
state: dict[str, dict] = {} # group -> {key: last_value}
was_enabled = False
@@ -71,6 +76,34 @@ def main():
was_enabled = enabled
# GPS telemetry at most 1Hz — fed through the same diff logic
if enabled and writer is not None:
sm.update(0)
now = time.monotonic()
if sm.updated['gpsLocation'] and (now - last_gps_log) >= 1.0:
gps = sm['gpsLocation']
gps_data = {
"latitude": f"{gps.latitude:.7f}",
"longitude": f"{gps.longitude:.7f}",
"speed": f"{gps.speed:.2f}",
"altitude": f"{gps.altitude:.1f}",
"bearing": f"{gps.bearingDeg:.1f}",
"accuracy": f"{gps.accuracy:.1f}",
}
ts = time.time()
if "gps" not in state:
state["gps"] = {}
prev_gps = state["gps"]
gps_changed = False
for key, value in gps_data.items():
if key not in prev_gps or prev_gps[key] != value:
writer.writerow([f"{ts:.6f}", "gps", key, value])
prev_gps[key] = value
gps_changed = True
if gps_changed:
f.flush()
last_gps_log = now
# Always drain the socket (even when disabled) to avoid backpressure
try:
raw = sock.recv_string(zmq.NOBLOCK)

View File

@@ -83,9 +83,10 @@ def manager_init(frogpilot_functions) -> None:
params_storage = Params("/persist/params")
params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START)
# CLEARPILOT: always start with telemetry disabled, VPN enabled
params.put("TelemetryEnabled", "0")
params.put("VpnEnabled", "1")
# CLEARPILOT: always start with telemetry disabled, VPN enabled (memory params)
params_memory = Params("/dev/shm/params")
params_memory.put("TelemetryEnabled", "0")
params_memory.put("VpnEnabled", "1")
params.clear_all(ParamKeyType.CLEAR_ON_ONROAD_TRANSITION)
params.clear_all(ParamKeyType.CLEAR_ON_OFFROAD_TRANSITION)
if is_release_branch():

View File

@@ -85,7 +85,7 @@ procs = [
PythonProcess("deleter", "system.loggerd.deleter", always_run),
PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", driverview, enabled=(not PC or WEBCAM)),
# PythonProcess("qcomgpsd", "system.qcomgpsd.qcomgpsd", qcomgps, enabled=TICI),
# PythonProcess("gpsd", "system.clearpilot.gpsd", qcomgps, enabled=TICI), # DISABLED: testing perf
PythonProcess("gpsd", "system.clearpilot.gpsd", qcomgps, enabled=TICI),
# PythonProcess("ugpsd", "system.ugpsd", only_onroad, enabled=TICI),
#PythonProcess("navd", "selfdrive.navd.navd", only_onroad),
PythonProcess("pandad", "selfdrive.boardd.pandad", always_run),
@@ -110,7 +110,7 @@ procs = [
PythonProcess("frogpilot_process", "selfdrive.frogpilot.frogpilot_process", always_run),
# ClearPilot processes
# NativeProcess("dashcamd", "selfdrive/clearpilot", ["./dashcamd"], dashcam_should_run), # DISABLED: testing perf
NativeProcess("dashcamd", "selfdrive/clearpilot", ["./dashcamd"], dashcam_should_run),
PythonProcess("telemetryd", "selfdrive.clearpilot.telemetryd", always_run),
PythonProcess("bench_onroad", "selfdrive.clearpilot.bench_onroad", always_run, enabled=BENCH_MODE),
]

View File

@@ -355,15 +355,21 @@ ClearPilotPanel::ClearPilotPanel(QWidget* parent) : QFrame(parent) {
ListWidget *debug_panel = new ListWidget(this);
debug_panel->setContentsMargins(50, 25, 50, 25);
auto *telemetry_toggle = new ParamControl("TelemetryEnabled", "Telemetry Logging",
auto *telemetry_toggle = new ToggleControl("Telemetry Logging",
"Record telemetry data to CSV in the session log directory. "
"Captures only changed values for efficiency.", "", this);
"Captures only changed values for efficiency.", "",
Params("/dev/shm/params").getBool("TelemetryEnabled"), this);
QObject::connect(telemetry_toggle, &ToggleControl::toggleFlipped, [](bool on) {
Params("/dev/shm/params").putBool("TelemetryEnabled", on);
});
debug_panel->addItem(telemetry_toggle);
auto *vpn_toggle = new ParamControl("VpnEnabled", "VPN",
auto *vpn_toggle = new ToggleControl("VPN",
"Connect to vpn.hanson.xyz for remote SSH access. "
"Disabling kills the active tunnel and stops reconnection attempts.", "", this);
"Disabling kills the active tunnel and stops reconnection attempts.", "",
Params("/dev/shm/params").getBool("VpnEnabled"), this);
QObject::connect(vpn_toggle, &ToggleControl::toggleFlipped, [](bool on) {
Params("/dev/shm/params").putBool("VpnEnabled", on);
if (on) {
std::system("sudo bash -c 'nohup /data/openpilot/system/clearpilot/vpn-monitor.sh >> /tmp/vpn-monitor.log 2>&1 &'");
} else {

View File

@@ -411,7 +411,7 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) {
}
// CLEARPILOT: blinking blue circle when telemetry is recording
if (Params().getBool("TelemetryEnabled")) {
if (paramsMemory.getBool("TelemetryEnabled")) {
// Blink: visible for 500ms, hidden for 500ms
int phase = (QDateTime::currentMSecsSinceEpoch() / 500) % 2;
if (phase == 0) {
@@ -904,6 +904,10 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) {
} else {
CameraWidget::updateCalibration(DEFAULT_CALIBRATION);
}
// CLEARPILOT: force CameraWidget bg to black in nightrider to prevent color bleed
if (nightriderMode) {
CameraWidget::setBackgroundColor(Qt::black);
}
painter.beginNativePainting();
if (nightriderMode) {
// CLEARPILOT: black background, no camera feed
@@ -1075,7 +1079,8 @@ void AnnotatedCameraWidget::updateFrogPilotWidgets() {
}
void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &p) {
if ((showAlwaysOnLateralStatusBar || showConditionalExperimentalStatusBar || roadNameUI)) {
// CLEARPILOT: only show status bar when telemetry is enabled
if (paramsMemory.getBool("TelemetryEnabled")) {
drawStatusBar(p);
}
@@ -1276,7 +1281,30 @@ void AnnotatedCameraWidget::drawStatusBar(QPainter &p) {
QString roadName = roadNameUI ? QString::fromStdString(paramsMemory.get("RoadName")) : QString();
if (alwaysOnLateralActive && showAlwaysOnLateralStatusBar) {
// CLEARPILOT: telemetry status bar — show live stats when telemetry enabled
if (paramsMemory.getBool("TelemetryEnabled")) {
SubMaster &sm = *(uiState()->sm);
auto deviceState = sm["deviceState"].getDeviceState();
int maxTempC = deviceState.getMaxTempC();
int fanPct = deviceState.getFanSpeedPercentDesired();
bool standstill = sm["carState"].getCarState().getStandstill();
static double last_model_status_t = 0;
static float model_status_fps = 0;
if (sm.updated("modelV2")) {
double now = millis_since_boot();
if (last_model_status_t > 0) {
double dt = now - last_model_status_t;
if (dt > 0) model_status_fps = model_status_fps * 0.8 + (1000.0 / dt) * 0.2;
}
last_model_status_t = now;
}
newStatus = QString("%1\u00B0C FAN %2% MDL %3")
.arg(maxTempC).arg(fanPct).arg(model_status_fps, 0, 'f', 0);
if (standstill) newStatus += " STANDSTILL";
// CLEARPILOT: suppress "Always On Lateral active" status bar message
} else if (false && alwaysOnLateralActive && showAlwaysOnLateralStatusBar) {
newStatus = tr("Always On Lateral active") + (tr(". Press the \"Cruise Control\" button to disable"));
} else if (showConditionalExperimentalStatusBar) {
newStatus = conditionalStatusMap[status != STATUS_DISENGAGED ? conditionalStatus : 0];

Binary file not shown.