Compare commits

...

10 Commits

Author SHA1 Message Date
55f73fd421 ready screen: hide READY! text after first drive, reset on ignition cycle
Some checks failed
prebuilt / build prebuilt (push) Has been cancelled
badges / create badges (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 02:51:13 -05:00
2b522021d5 modeld 3-mode power saving, tlog disabled, dashcamd boot fix
- modeld: full 20fps (lat active), 4fps (driving no lateral), 0fps
  (standstill). Standby timestamp written in both standby and 4fps modes
  to suppress transient errors on engagement transition
- tlog: all calls commented out (was causing 100Hz CPU load in controlsd
  and carstate). tlog client now checks TelemetryEnabled param before
  sending — zero cost when disabled
- dashcamd: wait for valid frame dimensions on startup (fix SIGSEGV),
  always_run (manages own trip lifecycle)
- Fan: driving range 15-100%

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 02:40:18 -05:00
4756d8e32c disable tlog calls, add param gate to tlog client
- Comment out all tlog() calls in controlsd (100Hz) and carstate (100Hz)
  — was causing controlsd to lag from JSON serialization + ZMQ overhead
- tlog() now checks TelemetryEnabled memory param (1/sec file read),
  returns immediately when disabled — zero cost when telemetry is off

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 02:28:19 -05:00
73472e5fab docs: cruise control desync investigation notes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 02:19:31 -05:00
b85ce8176d modeld standby on lat inactive, dashcamd always-on, alert suppression
- modeld: enter standby when latActive=false (not just standstill),
  exception for lane changes (no_lat_lane_change). Fix Python capnp
  property access (.latActive not getLatActive())
- controlsd: move model_suppress computation early, suppress radarFault,
  posenetInvalid, locationdTemporaryError, paramsdTemporaryError during
  model standby + 2s grace period. All cascade from modeld not publishing
- dashcamd: always_run (manages own trip lifecycle), wait for valid frame
  dimensions before encoding (fix SIGSEGV on early start)
- Fan: driving range 15-100% (was 30-100%)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 02:13:40 -05:00
24f29dcfb7 dashcam: reduce recording to 10fps (skip every other frame)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 01:40:06 -05:00
6de3a8c68f disable reverse SSH — using VPN for remote access
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 01:36:57 -05:00
bf030db9fe modeld standby, fan standstill clamping, log rotation, diagnostic logging
- modeld standby: skip inference at standstill (model stays loaded in GPU),
  ModelStandby param + ModelStandbyTs heartbeat for race-free suppression
- controlsd: suppress commIssue/modeldLagging when ModelStandbyTs < 2s old,
  ignore telemetryd/dashcamd in process_not_running check
- Fan controller: standstill below 74°C clamps to 0-10% (near silent),
  standstill above 74°C allows 0-100%, thermald reads carState.standstill
- Deleter: enforce 4GB quota on /data/log2 session logs, oldest-first cleanup
- Diagnostic logging: steerTempUnavailable and controlsdLagging log to stderr
  with full context (steering angle, torque, speed, remaining time)
- CLAUDE.md: document memory params method name difference (C++ camelCase
  vs Python snake_case)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 01:35:14 -05:00
3d9143c41b 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>
2026-04-14 00:36:29 -05:00
698a1647a0 nightrider mode improvements, nice monitor, ready text 1x
- Nightrider: lines 1px wider (3px outline), engagement border hidden,
  planner lines hidden when disengaged, stay on onroad view in park
- Normal mode only: return to ready splash on park
- Ready text sprite at native 1x size
- Nice monitor: keeps claude processes at nice 19, runs every 30s

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 23:24:37 -05:00
24 changed files with 389 additions and 116 deletions

View File

@@ -0,0 +1,29 @@
# CAN-FD Issues to Investigate
## Cruise Control Desync on Hot Start
### Problem
When the car is already in cruise control mode before openpilot starts (or before controlsd initializes), pressing the cruise control button causes a desync:
1. Car has active cruise control when openpilot comes online
2. Driver presses cruise button — car interprets as "toggle off" (cruise was already on)
3. openpilot interprets it as "engage" (from its perspective, it hasn't seen a cruise enable transition)
4. Result: car disengages cruise, but openpilot thinks it just engaged lateral mode
### Proposed Fix
When controlsd first reads carState and discovers `cruiseState.enabled` is already true:
1. Set a flag `cruise_was_active_at_start = True`
2. While this flag is set, do NOT allow openpilot to transition to engaged state on button press
3. Only clear the flag after seeing `cruiseState.enabled` go to `False` at least once (a full stop of cruise, not just standstill/pause)
4. After the first full off cycle, normal engagement logic resumes
### Status
- Could not reproduce on 2026-04-14 — may require specific timing (openpilot restart while driving with cruise active)
- Need to capture telemetry data during reproduction to see exact signal sequence
- Key telemetry groups to watch: `cruise` (enabled, available, ACCMode, VSetDis), `buttons` (cruise_button)
### Relevant Code
- `selfdrive/controls/controlsd.py` — engagement state machine
- `selfdrive/car/hyundai/carstate.py` — CAN-FD cruise state parsing
- `selfdrive/car/interfaces.py` — pcm_enable / cruise state transitions

View File

@@ -92,6 +92,18 @@ 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"), `ModelStandby` (default "0"), `ScreenDisplayMode`
- **IMPORTANT — method names differ between C++ and Python**: C++ uses camelCase (`putBool`, `getBool`, `getInt`), Python uses snake_case (`put_bool`, `get_bool`, `get_int`). This is a common source of silent failures — the wrong casing compiles/runs but doesn't work.
### 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 +494,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

View File

@@ -198,8 +198,10 @@ std::unordered_map<std::string, uint32_t> keys = {
{"TelemetryEnabled", PERSISTENT}, {"TelemetryEnabled", PERSISTENT},
{"Timezone", PERSISTENT}, {"Timezone", PERSISTENT},
{"TrainingVersion", PERSISTENT}, {"TrainingVersion", PERSISTENT},
{"ModelStandby", PERSISTENT},
{"ModelStandbyTs", 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},

View File

@@ -20,6 +20,9 @@ bash /data/openpilot/system/clearpilot/on_start.sh
# CLEARPILOT: start VPN monitor (kills previous instances, runs as root) # CLEARPILOT: start VPN monitor (kills previous instances, runs as root)
sudo bash -c 'nohup /data/openpilot/system/clearpilot/vpn-monitor.sh >> /tmp/vpn-monitor.log 2>&1 &' sudo bash -c 'nohup /data/openpilot/system/clearpilot/vpn-monitor.sh >> /tmp/vpn-monitor.log 2>&1 &'
# CLEARPILOT: start nice monitor (keeps claude at nice 19)
sudo bash -c 'nohup /data/openpilot/system/clearpilot/nice-monitor.sh > /dev/null 2>&1 &'
# CLEARPILOT: pass --bench flag through to manager via env var # CLEARPILOT: pass --bench flag through to manager via env var
if [ "$1" = "--bench" ]; then if [ "$1" = "--bench" ]; then
export BENCH_MODE=1 export BENCH_MODE=1

View File

@@ -421,59 +421,14 @@ class CarState(CarStateBase):
# self.params_memory.put_float("CarSpeedLimitLiteral", self.calculate_speed_limit(cp, cp_cam)) # self.params_memory.put_float("CarSpeedLimitLiteral", self.calculate_speed_limit(cp, cp_cam))
self.params_memory.put_float("CarSpeedLimit", self.calculate_speed_limit(cp, cp_cam) * speed_factor) self.params_memory.put_float("CarSpeedLimit", self.calculate_speed_limit(cp, cp_cam) * speed_factor)
# CLEARPILOT: telemetry logging # CLEARPILOT: telemetry logging — disabled, re-enable when needed
speed_limit_bus = cp if self.CP.flags & HyundaiFlags.CANFD_HDA2 else cp_cam # speed_limit_bus = cp if self.CP.flags & HyundaiFlags.CANFD_HDA2 else cp_cam
scc = cp_cam.vl["SCC_CONTROL"] if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp.vl["SCC_CONTROL"] # scc = cp_cam.vl["SCC_CONTROL"] if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp.vl["SCC_CONTROL"]
cluster = speed_limit_bus.vl["CLUSTER_SPEED_LIMIT"] # cluster = speed_limit_bus.vl["CLUSTER_SPEED_LIMIT"]
# tlog("car", { ... })
tlog("car", { # tlog("cruise", { ... })
"vEgo": round(ret.vEgo, 3), # tlog("speed_limit", { ... })
"vEgoRaw": round(ret.vEgoRaw, 3), # tlog("buttons", { ... })
"aEgo": round(ret.aEgo, 3),
"steeringAngleDeg": round(ret.steeringAngleDeg, 1),
"gear": str(ret.gearShifter),
"brakePressed": ret.brakePressed,
"gasPressed": ret.gasPressed,
"standstill": ret.standstill,
"leftBlinker": ret.leftBlinker,
"rightBlinker": ret.rightBlinker,
})
tlog("cruise", {
"enabled": ret.cruiseState.enabled,
"available": ret.cruiseState.available,
"speed": round(ret.cruiseState.speed, 3),
"standstill": ret.cruiseState.standstill,
"accFaulted": ret.accFaulted,
"brakePressed": ret.brakePressed,
"ACCMode": scc.get("ACCMode", 0),
"VSetDis": scc.get("VSetDis", 0),
"aReqRaw": round(scc.get("aReqRaw", 0), 3),
"aReqValue": round(scc.get("aReqValue", 0), 3),
"DISTANCE_SETTING": scc.get("DISTANCE_SETTING", 0),
"ACC_ObjDist": round(scc.get("ACC_ObjDist", 0), 1),
})
tlog("speed_limit", {
"SPEED_LIMIT_1": cluster.get("SPEED_LIMIT_1", 0),
"SPEED_LIMIT_2": cluster.get("SPEED_LIMIT_2", 0),
"SPEED_LIMIT_3": cluster.get("SPEED_LIMIT_3", 0),
"SCHOOL_ZONE": cluster.get("SCHOOL_ZONE", 0),
"CHIME_1": cluster.get("CHIME_1", 0),
"CHIME_2": cluster.get("CHIME_2", 0),
"SPEED_CHANGE_BLINKING": cluster.get("SPEED_CHANGE_BLINKING", 0),
"calculated": self.calculate_speed_limit(cp, cp_cam),
"is_metric": self.is_metric,
})
tlog("buttons", {
"cruise_button": self.cruise_buttons[-1],
"prev_cruise_button": self.prev_cruise_buttons,
"main_button": self.main_buttons[-1],
"prev_main_button": self.prev_main_buttons,
"lkas_enabled": self.lkas_enabled,
"main_enabled": self.main_enabled,
})
return ret return ret

View File

@@ -484,10 +484,19 @@ class CarInterfaceBase(ABC):
self.silent_steer_warning = True self.silent_steer_warning = True
events.add(EventName.steerTempUnavailableSilent) events.add(EventName.steerTempUnavailableSilent)
else: else:
# CLEARPILOT: log once per instance of this warning
if not getattr(self, '_steer_fault_logged', False):
import sys
print(f"CLP steerTempUnavailable: steerFaultTemporary={cs_out.steerFaultTemporary} "
f"steeringPressed={cs_out.steeringPressed} standstill={cs_out.standstill} "
f"steering_unpressed={self.steering_unpressed} steeringAngleDeg={cs_out.steeringAngleDeg:.1f} "
f"steeringTorque={cs_out.steeringTorque:.1f} vEgo={cs_out.vEgo:.2f}", file=sys.stderr)
self._steer_fault_logged = True
events.add(EventName.steerTempUnavailable) events.add(EventName.steerTempUnavailable)
else: else:
self.no_steer_warning = False self.no_steer_warning = False
self.silent_steer_warning = False self.silent_steer_warning = False
self._steer_fault_logged = False
if cs_out.steerFaultPermanent: if cs_out.steerFaultPermanent:
events.add(EventName.steerUnavailable) events.add(EventName.steerUnavailable)

Binary file not shown.

View File

@@ -64,7 +64,8 @@
const std::string VIDEOS_BASE = "/data/media/0/videos"; const std::string VIDEOS_BASE = "/data/media/0/videos";
const int SEGMENT_SECONDS = 180; // 3 minutes const int SEGMENT_SECONDS = 180; // 3 minutes
const int CAMERA_FPS = 20; const int SOURCE_FPS = 20;
const int CAMERA_FPS = 10;
const int FRAMES_PER_SEGMENT = SEGMENT_SECONDS * CAMERA_FPS; const int FRAMES_PER_SEGMENT = SEGMENT_SECONDS * CAMERA_FPS;
const int BITRATE = 2500 * 1024; // 2500 kbps const int BITRATE = 2500 * 1024; // 2500 kbps
const double IDLE_TIMEOUT_SECONDS = 600.0; // 10 minutes const double IDLE_TIMEOUT_SECONDS = 600.0; // 10 minutes
@@ -129,18 +130,28 @@ int main(int argc, char *argv[]) {
if (do_exit) return 0; if (do_exit) return 0;
LOGW("dashcamd: system time valid"); LOGW("dashcamd: system time valid");
LOGW("dashcamd: connecting to camerad road stream"); LOGW("dashcamd: started, connecting to camerad road stream");
VisionIpcClient vipc("camerad", VISION_STREAM_ROAD, false); VisionIpcClient vipc("camerad", VISION_STREAM_ROAD, false);
while (!do_exit && !vipc.connect(false)) { while (!do_exit && !vipc.connect(false)) {
usleep(100000); usleep(100000);
} }
if (do_exit) return 0; if (do_exit) return 0;
LOGW("dashcamd: vipc connected, waiting for valid frame");
int width = vipc.buffers[0].width; // Wait for a frame with valid dimensions (camerad may still be initializing)
int height = vipc.buffers[0].height; VisionBuf *init_buf = nullptr;
int y_stride = vipc.buffers[0].stride; while (!do_exit) {
init_buf = vipc.recv();
if (init_buf != nullptr && init_buf->width > 0 && init_buf->height > 0) break;
usleep(100000);
}
if (do_exit) return 0;
int width = init_buf->width;
int height = init_buf->height;
int y_stride = init_buf->stride;
int uv_stride = y_stride; int uv_stride = y_stride;
LOGW("dashcamd: connected %dx%d, stride=%d", width, height, y_stride); LOGW("dashcamd: first valid frame %dx%d stride=%d", width, height, y_stride);
// Subscribe to carState (gear), deviceState (ignition), gpsLocation (subtitles) // Subscribe to carState (gear), deviceState (ignition), gpsLocation (subtitles)
SubMaster sm({"carState", "deviceState", "gpsLocation"}); SubMaster sm({"carState", "deviceState", "gpsLocation"});
@@ -151,6 +162,7 @@ int main(int argc, char *argv[]) {
OmxEncoder *encoder = nullptr; OmxEncoder *encoder = nullptr;
std::string trip_dir; std::string trip_dir;
int frame_count = 0; int frame_count = 0;
int recv_count = 0;
uint64_t segment_start_ts = 0; uint64_t segment_start_ts = 0;
double idle_timer_start = 0.0; double idle_timer_start = 0.0;
@@ -227,6 +239,10 @@ int main(int argc, char *argv[]) {
VisionBuf *buf = vipc.recv(); VisionBuf *buf = vipc.recv();
if (buf == nullptr) continue; if (buf == nullptr) continue;
// CLEARPILOT: skip frames to match target FPS (SOURCE_FPS -> CAMERA_FPS)
recv_count++;
if (SOURCE_FPS > CAMERA_FPS && (recv_count % (SOURCE_FPS / CAMERA_FPS)) != 0) continue;
sm.update(0); sm.update(0);
double now = nanos_since_boot() / 1e9; double now = nanos_since_boot() / 1e9;

View File

@@ -6,20 +6,39 @@ Usage from any process:
tlog("canbus", {"speed": 45.2, "speed_limit": 60}) tlog("canbus", {"speed": 45.2, "speed_limit": 60})
Sends JSON packets over ZMQ PUSH to telemetryd, which diffs and writes CSV. Sends JSON packets over ZMQ PUSH to telemetryd, which diffs and writes CSV.
Only sends when TelemetryEnabled memory param is set — zero cost when disabled.
""" """
import json import json
import os
import time import time
import zmq import zmq
TELEMETRY_SOCK = "ipc:///tmp/clearpilot_telemetry" TELEMETRY_SOCK = "ipc:///tmp/clearpilot_telemetry"
_PARAM_PATH = "/dev/shm/params/d/TelemetryEnabled"
_ctx = None _ctx = None
_sock = None _sock = None
_last_check = 0
_enabled = False
def tlog(group: str, data: dict): def tlog(group: str, data: dict):
"""Log a telemetry packet. Only changed values will be written to CSV by telemetryd.""" """Log a telemetry packet. Only changed values will be written to CSV by telemetryd."""
global _ctx, _sock global _ctx, _sock, _last_check, _enabled
# Check param at most once per second to avoid filesystem overhead
now = time.monotonic()
if now - _last_check > 1.0:
_last_check = now
try:
with open(_PARAM_PATH, 'r') as f:
_enabled = f.read().strip() == "1"
except (FileNotFoundError, IOError):
_enabled = False
if not _enabled:
return
if _sock is None: if _sock is None:
_ctx = zmq.Context.instance() _ctx = zmq.Context.instance()
_sock = _ctx.socket(zmq.PUSH) _sock = _ctx.socket(zmq.PUSH)

View File

@@ -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)

View File

@@ -48,7 +48,7 @@ CAMERA_OFFSET = 0.04
REPLAY = "REPLAY" in os.environ REPLAY = "REPLAY" in os.environ
SIMULATION = "SIMULATION" in os.environ SIMULATION = "SIMULATION" in os.environ
TESTING_CLOSET = "TESTING_CLOSET" in os.environ TESTING_CLOSET = "TESTING_CLOSET" in os.environ
IGNORE_PROCESSES = {"loggerd", "encoderd", "statsd"} IGNORE_PROCESSES = {"loggerd", "encoderd", "statsd", "telemetryd", "dashcamd"}
ThermalStatus = log.DeviceState.ThermalStatus ThermalStatus = log.DeviceState.ThermalStatus
State = log.ControlsState.OpenpilotState State = log.ControlsState.OpenpilotState
@@ -442,6 +442,13 @@ class Controls:
# All events here should at least have NO_ENTRY and SOFT_DISABLE. # All events here should at least have NO_ENTRY and SOFT_DISABLE.
num_events = len(self.events) num_events = len(self.events)
# CLEARPILOT: compute model standby suppression early — used by multiple checks below
try:
standby_ts = float(self.params_memory.get("ModelStandbyTs") or "0")
except (ValueError, TypeError):
standby_ts = 0
model_suppress = (time.monotonic() - standby_ts) < 2.0
not_running = {p.name for p in self.sm['managerState'].processes if not p.running and p.shouldBeRunning} not_running = {p.name for p in self.sm['managerState'].processes if not p.running and p.shouldBeRunning}
if self.sm.recv_frame['managerState'] and (not_running - IGNORE_PROCESSES): if self.sm.recv_frame['managerState'] and (not_running - IGNORE_PROCESSES):
self.events.add(EventName.processNotRunning) self.events.add(EventName.processNotRunning)
@@ -455,9 +462,11 @@ class Controls:
elif not self.sm.all_freq_ok(self.camera_packets): elif not self.sm.all_freq_ok(self.camera_packets):
self.events.add(EventName.cameraFrameRate) self.events.add(EventName.cameraFrameRate)
if not REPLAY and self.rk.lagging: if not REPLAY and self.rk.lagging:
import sys
print(f"CLP controlsdLagging: remaining={self.rk.remaining:.4f} standstill={CS.standstill} vEgo={CS.vEgo:.2f}", file=sys.stderr)
self.events.add(EventName.controlsdLagging) self.events.add(EventName.controlsdLagging)
if not self.radarless_model: if not self.radarless_model:
if len(self.sm['radarState'].radarErrors) or (not self.rk.lagging and not self.sm.all_checks(['radarState'])): if not model_suppress and (len(self.sm['radarState'].radarErrors) or (not self.rk.lagging and not self.sm.all_checks(['radarState']))):
self.events.add(EventName.radarFault) self.events.add(EventName.radarFault)
if not self.sm.valid['pandaStates']: if not self.sm.valid['pandaStates']:
self.events.add(EventName.usbError) self.events.add(EventName.usbError)
@@ -469,7 +478,7 @@ class Controls:
# generic catch-all. ideally, a more specific event should be added above instead # generic catch-all. ideally, a more specific event should be added above instead
has_disable_events = self.events.contains(ET.NO_ENTRY) and (self.events.contains(ET.SOFT_DISABLE) or self.events.contains(ET.IMMEDIATE_DISABLE)) has_disable_events = self.events.contains(ET.NO_ENTRY) and (self.events.contains(ET.SOFT_DISABLE) or self.events.contains(ET.IMMEDIATE_DISABLE))
no_system_errors = (not has_disable_events) or (len(self.events) == num_events) no_system_errors = (not has_disable_events) or (len(self.events) == num_events)
if (not self.sm.all_checks() or self.card.can_rcv_timeout) and no_system_errors: if (not self.sm.all_checks() or self.card.can_rcv_timeout) and no_system_errors and not model_suppress:
if not self.sm.all_alive(): if not self.sm.all_alive():
self.events.add(EventName.commIssue) self.events.add(EventName.commIssue)
elif not self.sm.all_freq_ok(): elif not self.sm.all_freq_ok():
@@ -490,13 +499,13 @@ class Controls:
self.logged_comm_issue = None self.logged_comm_issue = None
if not (self.CP.notCar and self.joystick_mode): if not (self.CP.notCar and self.joystick_mode):
if not self.sm['liveLocationKalman'].posenetOK: if not self.sm['liveLocationKalman'].posenetOK and not model_suppress:
self.events.add(EventName.posenetInvalid) self.events.add(EventName.posenetInvalid)
if not self.sm['liveLocationKalman'].deviceStable: if not self.sm['liveLocationKalman'].deviceStable:
self.events.add(EventName.deviceFalling) self.events.add(EventName.deviceFalling)
if not self.sm['liveLocationKalman'].inputsOK: if not self.sm['liveLocationKalman'].inputsOK and not model_suppress:
self.events.add(EventName.locationdTemporaryError) self.events.add(EventName.locationdTemporaryError)
if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY): if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY) and not model_suppress:
self.events.add(EventName.paramsdTemporaryError) self.events.add(EventName.paramsdTemporaryError)
# conservative HW alert. if the data or frequency are off, locationd will throw an error # conservative HW alert. if the data or frequency are off, locationd will throw an error
@@ -542,7 +551,7 @@ class Controls:
self.distance_traveled = 0 self.distance_traveled = 0
self.distance_traveled += CS.vEgo * DT_CTRL self.distance_traveled += CS.vEgo * DT_CTRL
if self.sm['modelV2'].frameDropPerc > 20: if self.sm['modelV2'].frameDropPerc > 20 and not model_suppress:
self.events.add(EventName.modeldLagging) self.events.add(EventName.modeldLagging)
@@ -630,15 +639,13 @@ class Controls:
self.enabled = self.state in ENABLED_STATES self.enabled = self.state in ENABLED_STATES
self.active = self.state in ACTIVE_STATES self.active = self.state in ACTIVE_STATES
# CLEARPILOT: log engagement state for debugging cruise desync issues # CLEARPILOT: engagement telemetry disabled — was running at 100Hz, causing CPU load
tlog("engage", { # tlog("engage", {
"state": self.state.name if hasattr(self.state, 'name') else str(self.state), # "state": self.state.name if hasattr(self.state, 'name') else str(self.state),
"enabled": self.enabled, # "enabled": self.enabled, "active": self.active,
"active": self.active, # "cruise_enabled": CS.cruiseState.enabled, "cruise_available": CS.cruiseState.available,
"cruise_enabled": CS.cruiseState.enabled, # "brakePressed": CS.brakePressed,
"cruise_available": CS.cruiseState.available, # })
"brakePressed": CS.brakePressed,
})
if self.active: if self.active:
self.current_alert_types.append(ET.WARNING) self.current_alert_types.append(ET.WARNING)

View File

@@ -83,9 +83,12 @@ 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_memory.put("ModelStandby", "0")
params_memory.put("ModelStandbyTs", "0")
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():

View File

@@ -54,7 +54,7 @@ def allow_uploads(started, params, CP: car.CarParams) -> bool:
# ClearPilot functions # ClearPilot functions
def dashcam_should_run(started, params, CP: car.CarParams) -> bool: def dashcam_should_run(started, params, CP: car.CarParams) -> bool:
return started or params.get_bool("DashcamDebug") return True # CLEARPILOT: dashcamd manages its own trip lifecycle
procs = [ procs = [
DaemonProcess("manage_athenad", "selfdrive.athena.manage_athenad", "AthenadPid"), DaemonProcess("manage_athenad", "selfdrive.athena.manage_athenad", "AthenadPid"),
@@ -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),
] ]

View File

@@ -134,11 +134,15 @@ def main(demo=False):
setproctitle(PROCESS_NAME) setproctitle(PROCESS_NAME)
config_realtime_process(7, 54) config_realtime_process(7, 54)
import time as _time
cloudlog.warning("setting up CL context") cloudlog.warning("setting up CL context")
_t0 = _time.monotonic()
cl_context = CLContext() cl_context = CLContext()
cloudlog.warning("CL context ready; loading model") _t1 = _time.monotonic()
cloudlog.warning("CL context ready in %.3fs; loading model", _t1 - _t0)
model = ModelState(cl_context) model = ModelState(cl_context)
cloudlog.warning("models loaded, modeld starting") _t2 = _time.monotonic()
cloudlog.warning("model loaded in %.3fs (total init %.3fs), modeld starting", _t2 - _t1, _t2 - _t0)
# visionipc clients # visionipc clients
while True: while True:
@@ -179,6 +183,9 @@ def main(demo=False):
model_transform_main = np.zeros((3, 3), dtype=np.float32) model_transform_main = np.zeros((3, 3), dtype=np.float32)
model_transform_extra = np.zeros((3, 3), dtype=np.float32) model_transform_extra = np.zeros((3, 3), dtype=np.float32)
live_calib_seen = False live_calib_seen = False
model_standby = False
last_standby_ts_write = 0
params_memory = Params("/dev/shm/params")
nav_features = np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32) nav_features = np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32)
nav_instructions = np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32) nav_instructions = np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32)
buf_main, buf_extra = None, None buf_main, buf_extra = None, None
@@ -233,6 +240,49 @@ def main(demo=False):
meta_extra = meta_main meta_extra = meta_main
sm.update(0) sm.update(0)
# CLEARPILOT: power saving — three modes based on driving state
# Full 20fps: lat active or lane changing
# Reduced 4fps: not lat active, not standstill (driving without cruise)
# Standby 0fps: standstill
lat_active = sm['carControl'].latActive
lane_changing = params_memory.get_bool("no_lat_lane_change")
standstill = sm['carState'].standstill
full_rate = lat_active or lane_changing
# Standby transitions (standstill only)
should_standby = standstill and not full_rate
if should_standby and not model_standby:
params_memory.put_bool("ModelStandby", True)
model_standby = True
cloudlog.warning("modeld: standby ON (standstill)")
elif not should_standby and model_standby:
params_memory.put_bool("ModelStandby", False)
model_standby = False
run_count = 0
frame_dropped_filter.x = 0.
cloudlog.warning("modeld: standby OFF")
if model_standby:
now = _time.monotonic()
if now - last_standby_ts_write > 1.0:
params_memory.put("ModelStandbyTs", str(now))
last_standby_ts_write = now
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)
# Write standby timestamp so controlsd suppresses transient errors
if not full_rate:
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:
last_vipc_frame_id = meta_main.frame_id
run_count += 1
continue
desire = DH.desire desire = DH.desire
is_rhd = sm["driverMonitoringState"].isRHD is_rhd = sm["driverMonitoringState"].isRHD
frame_id = sm["roadCameraState"].frameId frame_id = sm["roadCameraState"].frameId

View File

@@ -8,7 +8,7 @@ from openpilot.selfdrive.controls.lib.pid import PIDController
class BaseFanController(ABC): class BaseFanController(ABC):
@abstractmethod @abstractmethod
def update(self, cur_temp: float, ignition: bool) -> int: def update(self, cur_temp: float, ignition: bool, standstill: bool = False) -> int:
pass pass
@@ -20,9 +20,21 @@ class TiciFanController(BaseFanController):
self.last_ignition = False self.last_ignition = False
self.controller = PIDController(k_p=0, k_i=4e-3, k_f=1, rate=(1 / DT_TRML)) self.controller = PIDController(k_p=0, k_i=4e-3, k_f=1, rate=(1 / DT_TRML))
def update(self, cur_temp: float, ignition: bool) -> int: def update(self, cur_temp: float, ignition: bool, standstill: bool = False) -> int:
self.controller.neg_limit = -(100 if ignition else 30) # CLEARPILOT: at standstill below 74°C, clamp to 0-10% (near silent)
self.controller.pos_limit = -(30 if ignition else 0) # at standstill above 74°C, allow full 0-100% range
if ignition and standstill and cur_temp < 74:
self.controller.neg_limit = -10
self.controller.pos_limit = 0
elif ignition and standstill:
self.controller.neg_limit = -100
self.controller.pos_limit = 0
elif ignition:
self.controller.neg_limit = -100
self.controller.pos_limit = -15
else:
self.controller.neg_limit = -30
self.controller.pos_limit = 0
if ignition != self.last_ignition: if ignition != self.last_ignition:
self.controller.reset() self.controller.reset()

View File

@@ -164,7 +164,7 @@ def hw_state_thread(end_event, hw_queue):
def thermald_thread(end_event, hw_queue) -> None: def thermald_thread(end_event, hw_queue) -> None:
pm = messaging.PubMaster(['deviceState', 'frogpilotDeviceState']) pm = messaging.PubMaster(['deviceState', 'frogpilotDeviceState'])
sm = messaging.SubMaster(["peripheralState", "gpsLocationExternal", "controlsState", "pandaStates"], poll="pandaStates") sm = messaging.SubMaster(["peripheralState", "gpsLocationExternal", "controlsState", "pandaStates", "carState"], poll="pandaStates")
count = 0 count = 0
@@ -290,7 +290,8 @@ def thermald_thread(end_event, hw_queue) -> None:
msg.deviceState.maxTempC = all_comp_temp msg.deviceState.maxTempC = all_comp_temp
if fan_controller is not None: if fan_controller is not None:
msg.deviceState.fanSpeedPercentDesired = fan_controller.update(all_comp_temp, onroad_conditions["ignition"]) standstill = sm['carState'].standstill if sm.seen['carState'] else False
msg.deviceState.fanSpeedPercentDesired = fan_controller.update(all_comp_temp, onroad_conditions["ignition"], standstill)
is_offroad_for_5_min = (started_ts is None) and ((not started_seen) or (off_ts is None) or (time.monotonic() - off_ts > 60 * 5)) is_offroad_for_5_min = (started_ts is None) and ((not started_seen) or (off_ts is None) or (time.monotonic() - off_ts > 60 * 5))
if is_offroad_for_5_min and offroad_comp_temp > OFFROAD_DANGER_TEMP: if is_offroad_for_5_min and offroad_comp_temp > OFFROAD_DANGER_TEMP:

View File

@@ -85,19 +85,25 @@ void HomeWindow::updateState(const UIState &s) {
showDriverView(s.scene.driver_camera_timer >= 10, true); showDriverView(s.scene.driver_camera_timer >= 10, true);
// CLEARPILOT: show splash screen when onroad but in park // CLEARPILOT: show splash screen when onroad but in park
// In nightrider mode (states 1,4), stay on onroad view in park — only offroad transition shows splash
bool parked = s.scene.parked; bool parked = s.scene.parked;
int screenMode = paramsMemory.getInt("ScreenDisplayMode");
bool nightrider = (screenMode == 1 || screenMode == 4);
if (parked && !was_parked_onroad) { if (parked && !was_parked_onroad) {
LOGW("CLP UI: park transition -> showing splash"); if (!nightrider) {
slayout->setCurrentWidget(ready); LOGW("CLP UI: park transition -> showing splash");
slayout->setCurrentWidget(ready);
}
} else if (!parked && was_parked_onroad) { } else if (!parked && was_parked_onroad) {
LOGW("CLP UI: drive transition -> showing onroad"); LOGW("CLP UI: drive transition -> showing onroad");
slayout->setCurrentWidget(onroad); slayout->setCurrentWidget(onroad);
ready->has_driven = true;
} }
was_parked_onroad = parked; was_parked_onroad = parked;
// CLEARPILOT: honor display on/off while showing splash in park // CLEARPILOT: honor display on/off while showing splash in park (normal mode only)
if (parked && ready->isVisible()) { if (parked && ready->isVisible()) {
int screenMode = paramsMemory.getInt("ScreenDisplayMode");
if (screenMode == 3) { if (screenMode == 3) {
Hardware::set_display_power(false); Hardware::set_display_power(false);
} else { } else {
@@ -350,15 +356,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 {

View File

@@ -177,7 +177,14 @@ void OnroadWindow::offroadTransition(bool offroad) {
void OnroadWindow::paintEvent(QPaintEvent *event) { void OnroadWindow::paintEvent(QPaintEvent *event) {
QPainter p(this); QPainter p(this);
p.fillRect(rect(), QColor(bg.red(), bg.green(), bg.blue(), 255)); // CLEARPILOT: hide engagement border in nightrider mode
int dm = paramsMemory.getInt("ScreenDisplayMode");
bool nightrider = (dm == 1 || dm == 4);
if (nightrider) {
p.fillRect(rect(), Qt::black);
} else {
p.fillRect(rect(), QColor(bg.red(), bg.green(), bg.blue(), 255));
}
QString logicsDisplayString = QString(); QString logicsDisplayString = QString();
if (scene.show_jerk) { if (scene.show_jerk) {
@@ -404,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) {
@@ -596,11 +603,20 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) {
// CLEARPILOT: nightrider mode — outline only, no fill // CLEARPILOT: nightrider mode — outline only, no fill
bool outlineOnly = nightriderMode; bool outlineOnly = nightriderMode;
// CLEARPILOT: in nightrider mode, hide all lines when not engaged
if (outlineOnly && edgeColor == bg_colors[STATUS_DISENGAGED]) {
painter.restore();
return;
}
// CLEARPILOT: nightrider lines are 1px wider (3 instead of 2)
int outlineWidth = outlineOnly ? 3 : 2;
// lanelines // lanelines
for (int i = 0; i < std::size(scene.lane_line_vertices); ++i) { for (int i = 0; i < std::size(scene.lane_line_vertices); ++i) {
QColor lineColor = QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp<float>(scene.lane_line_probs[i], 0.0, 0.7)); QColor lineColor = QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp<float>(scene.lane_line_probs[i], 0.0, 0.7));
if (outlineOnly) { if (outlineOnly) {
painter.setPen(QPen(lineColor, 2)); painter.setPen(QPen(lineColor, outlineWidth));
painter.setBrush(Qt::NoBrush); painter.setBrush(Qt::NoBrush);
} else { } else {
painter.setPen(Qt::NoPen); painter.setPen(Qt::NoPen);
@@ -614,7 +630,7 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) {
for (int i = 0; i < std::size(scene.road_edge_vertices); ++i) { for (int i = 0; i < std::size(scene.road_edge_vertices); ++i) {
QColor edgeCol = QColor::fromRgbF(1.0, 0, 0, std::clamp<float>(1.0 - scene.road_edge_stds[i], 0.0, 1.0)); QColor edgeCol = QColor::fromRgbF(1.0, 0, 0, std::clamp<float>(1.0 - scene.road_edge_stds[i], 0.0, 1.0));
if (outlineOnly) { if (outlineOnly) {
painter.setPen(QPen(edgeCol, 2)); painter.setPen(QPen(edgeCol, outlineWidth));
painter.setBrush(Qt::NoBrush); painter.setBrush(Qt::NoBrush);
} else { } else {
painter.setPen(Qt::NoPen); painter.setPen(Qt::NoPen);
@@ -689,7 +705,7 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) {
if (outlineOnly) { if (outlineOnly) {
painter.setPen(QPen(QColor(center_lane_color.red(), center_lane_color.green(), painter.setPen(QPen(QColor(center_lane_color.red(), center_lane_color.green(),
center_lane_color.blue(), 180), 2)); center_lane_color.blue(), 180), outlineWidth));
painter.setBrush(Qt::NoBrush); painter.setBrush(Qt::NoBrush);
} else { } else {
painter.setPen(Qt::NoPen); painter.setPen(Qt::NoPen);
@@ -718,7 +734,7 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) {
if (scene.blind_spot_path) { if (scene.blind_spot_path) {
QColor bsColor = QColor::fromHslF(0 / 360., 0.75, 0.50, 0.6); QColor bsColor = QColor::fromHslF(0 / 360., 0.75, 0.50, 0.6);
if (outlineOnly) { if (outlineOnly) {
painter.setPen(QPen(bsColor, 2)); painter.setPen(QPen(bsColor, outlineWidth));
painter.setBrush(Qt::NoBrush); painter.setBrush(Qt::NoBrush);
} else { } else {
QLinearGradient bs(0, height(), 0, 0); QLinearGradient bs(0, height(), 0, 0);
@@ -888,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
@@ -1059,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);
} }
@@ -1260,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];

View File

@@ -70,14 +70,13 @@ void ReadyWindow::paintEvent(QPaintEvent *event) {
painter.drawPixmap(x, y, scaled); painter.drawPixmap(x, y, scaled);
} }
if (error_msg.isEmpty()) { if (error_msg.isEmpty() && !has_driven) {
// "READY!" 8-bit text sprite, 2x size, 15% below center // "READY!" 8-bit text sprite, 15% below center — only before first drive
static QPixmap ready_text("/data/openpilot/selfdrive/clearpilot/theme/clearpilot/images/ready_text.png"); static QPixmap ready_text("/data/openpilot/selfdrive/clearpilot/theme/clearpilot/images/ready_text.png");
if (!ready_text.isNull()) { if (!ready_text.isNull()) {
QPixmap scaled = ready_text.scaled(ready_text.width() * 3 / 2, ready_text.height() * 3 / 2, Qt::KeepAspectRatio, Qt::FastTransformation); int tx = (width() - ready_text.width()) / 2;
int tx = (width() - scaled.width()) / 2;
int ty = height() / 2 + height() * 15 / 100; int ty = height() / 2 + height() * 15 / 100;
painter.drawPixmap(tx, ty, scaled); painter.drawPixmap(tx, ty, ready_text);
} }
} else { } else {
// Error state: red text at 25% below center // Error state: red text at 25% below center
@@ -126,5 +125,13 @@ void ReadyWindow::refresh() {
if (error_msg != prev_error) changed = true; if (error_msg != prev_error) changed = true;
} }
// Reset has_driven on ignition off→on (power cycle)
bool started = uiState()->scene.started;
if (!last_started && started) {
has_driven = false;
changed = true;
}
last_started = started;
if (changed) update(); if (changed) update();
} }

View File

@@ -18,6 +18,7 @@ class ReadyWindow : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
ReadyWindow(QWidget* parent = nullptr); ReadyWindow(QWidget* parent = nullptr);
bool has_driven = false;
private: private:
void showEvent(QShowEvent *event) override; void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent *event) override; void hideEvent(QHideEvent *event) override;
@@ -29,6 +30,7 @@ private:
QString cur_temp; QString cur_temp;
QString error_msg; // non-empty = show red error instead of READY! QString error_msg; // non-empty = show red error instead of READY!
QElapsedTimer uptime; QElapsedTimer uptime;
bool last_started = false;
QPixmap img_bg; QPixmap img_bg;
QPixmap img_hot; QPixmap img_hot;
}; };

Binary file not shown.

View File

@@ -0,0 +1,19 @@
#!/usr/bin/bash
# Nice monitor — ensures claude processes run at lowest CPU priority.
# Checks every 30 seconds and renices any claude process not already at nice 19.
# Kill other instances of this script
for pid in $(pgrep -f 'nice-monitor.sh' | grep -v $$); do
kill "$pid" 2>/dev/null
done
while true; do
for pid in $(pgrep -f 'claude' 2>/dev/null); do
cur=$(awk '{print $19}' /proc/$pid/stat 2>/dev/null)
if [ -n "$cur" ] && [ "$cur" != "19" ]; then
renice 19 -p "$pid" > /dev/null 2>&1
fi
done
sleep 30
done

View File

@@ -3,5 +3,5 @@
# Install logo # Install logo
bash /data/openpilot/system/clearpilot/startup_logo/set_logo.sh bash /data/openpilot/system/clearpilot/startup_logo/set_logo.sh
# Reverse ssh if brianbot dongle id # Reverse ssh disabled — using VPN for remote access instead
bash /data/openpilot/system/clearpilot/dev/on_start.sh # bash /data/openpilot/system/clearpilot/dev/on_start.sh

View File

@@ -101,6 +101,39 @@ def delete_oldest_video():
return False return False
# CLEARPILOT: max total size for /data/log2 session logs
LOG2_MAX_BYTES = 4 * 1024 * 1024 * 1024
def cleanup_log2():
"""Delete oldest session log directories until /data/log2 is under LOG2_MAX_BYTES."""
log_base = "/data/log2"
if not os.path.isdir(log_base):
return
# Get all session dirs sorted oldest first (by name = timestamp)
dirs = []
for entry in sorted(os.listdir(log_base)):
if entry == "current":
continue
path = os.path.join(log_base, entry)
if os.path.isdir(path) and not os.path.islink(path):
size = sum(f.stat().st_size for f in os.scandir(path) if f.is_file())
dirs.append((entry, path, size))
total = sum(s for _, _, s in dirs)
# Also count current session
current = os.path.join(log_base, "current")
if os.path.isdir(current):
total += sum(f.stat().st_size for f in os.scandir(current) if f.is_file())
# Delete oldest until under quota
while total > LOG2_MAX_BYTES and dirs:
entry, path, size = dirs.pop(0)
try:
cloudlog.info(f"deleting log session {path} ({size // 1024 // 1024} MB)")
shutil.rmtree(path)
total -= size
except OSError:
cloudlog.exception(f"issue deleting log {path}")
def deleter_thread(exit_event): def deleter_thread(exit_event):
while not exit_event.is_set(): while not exit_event.is_set():
out_of_bytes = get_available_bytes(default=MIN_BYTES + 1) < MIN_BYTES out_of_bytes = get_available_bytes(default=MIN_BYTES + 1) < MIN_BYTES
@@ -135,6 +168,8 @@ def deleter_thread(exit_event):
cloudlog.exception(f"issue deleting {delete_path}") cloudlog.exception(f"issue deleting {delete_path}")
exit_event.wait(.1) exit_event.wait(.1)
else: else:
# CLEARPILOT: enforce log2 size quota even when disk space is fine
cleanup_log2()
exit_event.wait(30) exit_event.wait(30)