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()`
|
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
|
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
|
### Building Native (C++) Processes
|
||||||
|
|
||||||
- SCons is the build system. Static libraries (`common`, `messaging`, `cereal`, `visionipc`) must be imported as SCons objects, not `-l` flags
|
- 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
|
- **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
|
- **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
|
- **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`
|
- **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)
|
- **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
|
### Key Dependencies
|
||||||
|
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ std::unordered_map<std::string, uint32_t> keys = {
|
|||||||
{"Timezone", PERSISTENT},
|
{"Timezone", PERSISTENT},
|
||||||
{"TrainingVersion", PERSISTENT},
|
{"TrainingVersion", PERSISTENT},
|
||||||
{"UbloxAvailable", PERSISTENT},
|
{"UbloxAvailable", PERSISTENT},
|
||||||
{"VpnEnabled", CLEAR_ON_MANAGER_START},
|
{"VpnEnabled", PERSISTENT},
|
||||||
{"UpdateAvailable", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
|
{"UpdateAvailable", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
|
||||||
{"UpdateFailedCount", CLEAR_ON_MANAGER_START},
|
{"UpdateFailedCount", CLEAR_ON_MANAGER_START},
|
||||||
{"UpdaterAvailableBranches", PERSISTENT},
|
{"UpdaterAvailableBranches", PERSISTENT},
|
||||||
|
|||||||
Binary file not shown.
@@ -17,6 +17,7 @@ import shutil
|
|||||||
import time
|
import time
|
||||||
import zmq
|
import zmq
|
||||||
|
|
||||||
|
import cereal.messaging as messaging
|
||||||
from openpilot.common.params import Params
|
from openpilot.common.params import Params
|
||||||
from openpilot.selfdrive.clearpilot.telemetry import TELEMETRY_SOCK
|
from openpilot.selfdrive.clearpilot.telemetry import TELEMETRY_SOCK
|
||||||
from openpilot.selfdrive.manager.process import _log_dir, session_log
|
from openpilot.selfdrive.manager.process import _log_dir, session_log
|
||||||
@@ -26,12 +27,16 @@ DISK_CHECK_INTERVAL = 10 # seconds
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
params = Params()
|
params = Params("/dev/shm/params")
|
||||||
ctx = zmq.Context.instance()
|
ctx = zmq.Context.instance()
|
||||||
sock = ctx.socket(zmq.PULL)
|
sock = ctx.socket(zmq.PULL)
|
||||||
sock.setsockopt(zmq.RCVHWM, 1000)
|
sock.setsockopt(zmq.RCVHWM, 1000)
|
||||||
sock.bind(TELEMETRY_SOCK)
|
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")
|
csv_path = os.path.join(_log_dir, "telemetry.csv")
|
||||||
state: dict[str, dict] = {} # group -> {key: last_value}
|
state: dict[str, dict] = {} # group -> {key: last_value}
|
||||||
was_enabled = False
|
was_enabled = False
|
||||||
@@ -71,6 +76,34 @@ def main():
|
|||||||
|
|
||||||
was_enabled = enabled
|
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
|
# Always drain the socket (even when disabled) to avoid backpressure
|
||||||
try:
|
try:
|
||||||
raw = sock.recv_string(zmq.NOBLOCK)
|
raw = sock.recv_string(zmq.NOBLOCK)
|
||||||
|
|||||||
@@ -83,9 +83,10 @@ def manager_init(frogpilot_functions) -> None:
|
|||||||
params_storage = Params("/persist/params")
|
params_storage = Params("/persist/params")
|
||||||
params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START)
|
params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START)
|
||||||
|
|
||||||
# CLEARPILOT: always start with telemetry disabled, VPN enabled
|
# CLEARPILOT: always start with telemetry disabled, VPN enabled (memory params)
|
||||||
params.put("TelemetryEnabled", "0")
|
params_memory = Params("/dev/shm/params")
|
||||||
params.put("VpnEnabled", "1")
|
params_memory.put("TelemetryEnabled", "0")
|
||||||
|
params_memory.put("VpnEnabled", "1")
|
||||||
params.clear_all(ParamKeyType.CLEAR_ON_ONROAD_TRANSITION)
|
params.clear_all(ParamKeyType.CLEAR_ON_ONROAD_TRANSITION)
|
||||||
params.clear_all(ParamKeyType.CLEAR_ON_OFFROAD_TRANSITION)
|
params.clear_all(ParamKeyType.CLEAR_ON_OFFROAD_TRANSITION)
|
||||||
if is_release_branch():
|
if is_release_branch():
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ procs = [
|
|||||||
PythonProcess("deleter", "system.loggerd.deleter", always_run),
|
PythonProcess("deleter", "system.loggerd.deleter", always_run),
|
||||||
PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", driverview, enabled=(not PC or WEBCAM)),
|
PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", driverview, enabled=(not PC or WEBCAM)),
|
||||||
# PythonProcess("qcomgpsd", "system.qcomgpsd.qcomgpsd", qcomgps, enabled=TICI),
|
# 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("ugpsd", "system.ugpsd", only_onroad, enabled=TICI),
|
||||||
#PythonProcess("navd", "selfdrive.navd.navd", only_onroad),
|
#PythonProcess("navd", "selfdrive.navd.navd", only_onroad),
|
||||||
PythonProcess("pandad", "selfdrive.boardd.pandad", always_run),
|
PythonProcess("pandad", "selfdrive.boardd.pandad", always_run),
|
||||||
@@ -110,7 +110,7 @@ procs = [
|
|||||||
PythonProcess("frogpilot_process", "selfdrive.frogpilot.frogpilot_process", always_run),
|
PythonProcess("frogpilot_process", "selfdrive.frogpilot.frogpilot_process", always_run),
|
||||||
|
|
||||||
# ClearPilot processes
|
# 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("telemetryd", "selfdrive.clearpilot.telemetryd", always_run),
|
||||||
PythonProcess("bench_onroad", "selfdrive.clearpilot.bench_onroad", always_run, enabled=BENCH_MODE),
|
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);
|
ListWidget *debug_panel = new ListWidget(this);
|
||||||
debug_panel->setContentsMargins(50, 25, 50, 25);
|
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. "
|
"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);
|
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. "
|
"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) {
|
QObject::connect(vpn_toggle, &ToggleControl::toggleFlipped, [](bool on) {
|
||||||
|
Params("/dev/shm/params").putBool("VpnEnabled", on);
|
||||||
if (on) {
|
if (on) {
|
||||||
std::system("sudo bash -c 'nohup /data/openpilot/system/clearpilot/vpn-monitor.sh >> /tmp/vpn-monitor.log 2>&1 &'");
|
std::system("sudo bash -c 'nohup /data/openpilot/system/clearpilot/vpn-monitor.sh >> /tmp/vpn-monitor.log 2>&1 &'");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -411,7 +411,7 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CLEARPILOT: blinking blue circle when telemetry is recording
|
// CLEARPILOT: blinking blue circle when telemetry is recording
|
||||||
if (Params().getBool("TelemetryEnabled")) {
|
if (paramsMemory.getBool("TelemetryEnabled")) {
|
||||||
// Blink: visible for 500ms, hidden for 500ms
|
// Blink: visible for 500ms, hidden for 500ms
|
||||||
int phase = (QDateTime::currentMSecsSinceEpoch() / 500) % 2;
|
int phase = (QDateTime::currentMSecsSinceEpoch() / 500) % 2;
|
||||||
if (phase == 0) {
|
if (phase == 0) {
|
||||||
@@ -904,6 +904,10 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) {
|
|||||||
} else {
|
} else {
|
||||||
CameraWidget::updateCalibration(DEFAULT_CALIBRATION);
|
CameraWidget::updateCalibration(DEFAULT_CALIBRATION);
|
||||||
}
|
}
|
||||||
|
// CLEARPILOT: force CameraWidget bg to black in nightrider to prevent color bleed
|
||||||
|
if (nightriderMode) {
|
||||||
|
CameraWidget::setBackgroundColor(Qt::black);
|
||||||
|
}
|
||||||
painter.beginNativePainting();
|
painter.beginNativePainting();
|
||||||
if (nightriderMode) {
|
if (nightriderMode) {
|
||||||
// CLEARPILOT: black background, no camera feed
|
// CLEARPILOT: black background, no camera feed
|
||||||
@@ -1075,7 +1079,8 @@ void AnnotatedCameraWidget::updateFrogPilotWidgets() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &p) {
|
void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &p) {
|
||||||
if ((showAlwaysOnLateralStatusBar || showConditionalExperimentalStatusBar || roadNameUI)) {
|
// CLEARPILOT: only show status bar when telemetry is enabled
|
||||||
|
if (paramsMemory.getBool("TelemetryEnabled")) {
|
||||||
drawStatusBar(p);
|
drawStatusBar(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1276,7 +1281,30 @@ void AnnotatedCameraWidget::drawStatusBar(QPainter &p) {
|
|||||||
|
|
||||||
QString roadName = roadNameUI ? QString::fromStdString(paramsMemory.get("RoadName")) : QString();
|
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"));
|
newStatus = tr("Always On Lateral active") + (tr(". Press the \"Cruise Control\" button to disable"));
|
||||||
} else if (showConditionalExperimentalStatusBar) {
|
} else if (showConditionalExperimentalStatusBar) {
|
||||||
newStatus = conditionalStatusMap[status != STATUS_DISENGAGED ? conditionalStatus : 0];
|
newStatus = conditionalStatusMap[status != STATUS_DISENGAGED ? conditionalStatus : 0];
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user