diff --git a/CLAUDE.md b/CLAUDE.md index 3f04eb2..fdcbac0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/common/params.cc b/common/params.cc index e24b719..d9f8dc8 100755 --- a/common/params.cc +++ b/common/params.cc @@ -199,7 +199,7 @@ std::unordered_map 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}, diff --git a/selfdrive/clearpilot/dashcamd b/selfdrive/clearpilot/dashcamd index 15aa9d6..f2ee6ef 100755 Binary files a/selfdrive/clearpilot/dashcamd and b/selfdrive/clearpilot/dashcamd differ diff --git a/selfdrive/clearpilot/telemetryd.py b/selfdrive/clearpilot/telemetryd.py index bda081a..d2600a4 100644 --- a/selfdrive/clearpilot/telemetryd.py +++ b/selfdrive/clearpilot/telemetryd.py @@ -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) diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 6be9c8a..b4f11b8 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -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(): diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 4971845..128cf21 100755 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -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), ] diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index 11e07bc..f60c339 100755 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -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 { diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index b89d7f7..5f1e885 100755 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -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]; diff --git a/selfdrive/ui/qt/spinner b/selfdrive/ui/qt/spinner index afdb1c3..99058b2 100755 Binary files a/selfdrive/ui/qt/spinner and b/selfdrive/ui/qt/spinner differ