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:
16
CLAUDE.md
16
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
|
||||
|
||||
|
||||
@@ -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.
@@ -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)
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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),
|
||||
]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
Reference in New Issue
Block a user