The state transition into State.enabled briefly bumps the controlsd
loop time over its budget, which trips rk.lagging and triggers the
'Controls Process Lagging: Reboot Your Device' NoEntryAlert right as
the user takes their hands off the wheel. The lag clears within a
couple cycles and self-driving continues fine.
Track the rising edge of self.enabled, and skip the controlsdLagging
event for the first 3 seconds after engagement. All other lag triggers
(unrelated to engagement) still fire normally.
Park mode is now suppressed until two things happen:
1. self.initialized flips True (controlsd's normal init path runs and
calls self.card.initialize() — which is what wires CarInterface up
so controls_update actually generates CAN sends).
2. 10 s of normal step() execution after init completes.
Without (1), park_mode_tick's controls_update was a silent no-op — the
"keepalive heartbeat" never reached the car's ECU, and downstream
publishes (carState, carParams, carOutput) never went out either,
which is why the UI was stuck on the splash even after a gear shift
to drive (scene.parked reads the same carState that wasn't being
published).
The 10 s buffer (2) also gives all only_onroad_active processes a
clean cold-start window before manager starts pausing them — no
risk of catching them mid-init when ParkMode flips on.
When the car is in park (ignition still on), pause everything that
isn't strictly needed and keep controlsd in a minimal-cycle keepalive
mode so the car's steering ECU keeps seeing the LFA/LKAS CAN-FD
messages and stays in tester mode (no steering fault on shift to drive).
controlsd:
- Detects park gear, writes ParkMode to /dev/shm/params.
- park_mode_tick(): publishes a do-nothing CarControl through
self.card (CarController.update unconditionally appends the
steering messages every cycle, which is the actual heartbeat).
Still runs clearpilot_state_control so the LFA/debug button +
ScreenDisplayMode keep working in park.
- After park→drive, stay in keepalive-tick mode until SubMaster
reports all services healthy (an 8s hard cap as safety net).
Avoids the burst of commIssue alerts that would otherwise fire
while modeld/plannerd/paramsd/torqued/dmonitoringd/calibrationd/
frogpilot_process spin back up from cold.
manager / process_config:
- New gating helpers _park_mode(), only_onroad_active,
driverview_active, always_run_unless_parked.
- Re-gated to only_onroad_active: modeld, sensord, soundd, locationd,
calibrationd, torqued, paramsd, plannerd, radard, speed_logicd.
- Re-gated to driverview_active: dmonitoringmodeld, dmonitoringd.
- frogpilot_process → always_run_unless_parked (preserves offroad
behavior, only pauses when ignition+parked).
- controlsd stays plain only_onroad — it's the writer + heartbeat.
- ParkMode registered in params.cc, defaulted to "0" in manager_init.
thermald + fan_controller (broken-tree rule, ported):
- thermald subscribes to carState; passes standstill, is_parked,
cruise_engaged into fan_controller.update.
- New fan range rules:
parked → 0-100% (no floor, full cooling)
cruise on → 30-100%
standstill → 10-100%
moving → 30-100%
ignition off → 0-30% (existing)
dashcamd: already park-aware via gear-driven trip lifecycle — no
change needed.
Verified: build clean. Launched on bench: park mode kicks in on
startup (gearShifter=unknown initially → eventually park),
modeld/plannerd/paramsd/torqued/calibrationd/frogpilot_process
correctly disappear from the manager process list, gpsd/dashcamd/
ui/controlsd/pandad stay alive, ParkMode=1 in /dev/shm/params.
Replaces the 3-state cycle in clearpilot_state_control with broken's
5-state machine:
- Onroad (drive gear): 0→4, 1→2, 2→3, 3→4, 4→2 (auto modes never
reached via the button — gpsd's day/night calc is the only path back)
- Not in drive: anything except 3 → 3 (screen off), state 3 → 0 (auto)
Also tracks park→drive edge to auto-wake from screen-off (mode 3 → 0)
when shifting into drive, regardless of how the screen got turned off.
Speed/cruise overlay state intentionally left out — owned by
speed_logicd, not controlsd.
events.py: remove clp_debug_notice + EventName.clpDebug binding
(no more "Clearpilot Debug Function Executed (N)" on-screen message).
The dead commented-out events.add(EventName.clpDebug) and stray print
of buttonEvents in controlsd are also cleaned up.
Every other reference treats ScreenDisplayMode as an int (range 0-4).
The init line was using put_bool(0) which happened to write the same
"0" string and read back fine via get_int, but the type mismatch was
misleading.
Restoring the working tree to the pristine pre-Claude baseline previously
preserved at /data/clearpilot (now /data/clearpilot-baseline). The prior
modified-but-broken tree is snapshotted at /data/openpilot-broken-2026-05-03
and tagged here as pre-reset-2026-05-03 for reference.
From here, features (UI changes, dashcam, telemetry, GPS, display modes,
speed logic, standstill power saving, etc.) will be re-introduced one at
a time with proper testing.
Adds back the UI plumbing that the baseline controlsd doesn't have, so
the existing UI features keep working without changing driving logic:
- ScreenDisplayMode 5-state machine (auto-normal, auto-nightrider,
manual normal, screen-off, manual nightrider) driven by the LFA
debug button + gear edges. Replaces baseline's simple 0..2 cycle.
Pure params write — no actuator effect.
- Speed/cruise-warning overlay tick at ~2Hz feeding SpeedState, which
writes ClearpilotSpeedDisplay / ClearpilotSpeedLimitDisplay /
ClearpilotCruiseWarning for the onroad UI. Reads gpsLocation,
CarSpeedLimit, and CS.cruiseState — no actuator effect.
- frogpilot_variables.no_lat_lane_change is now populated alongside
the existing Params write, so the perf-optimized carcontroller (which
takes the bit as an argument instead of re-reading Params on the
100Hz hot path) still sees the lane-change suppression signal.
- ScreenDisplayMode init switched from put_bool to put_int (UI reads
it as int).
- gpsLocation added to SubMaster (ignore_alive/avg_freq/valid set,
since gpsd is a ClearPilot addition not present everywhere).
No changes to controlsd's lateral or longitudinal control paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
controlsd: in park+ignition-on, run full step() only every 10th cycle.
data_sample still runs every cycle (CAN parse, button detection) and
card.controls_update still runs every cycle (CAN TX heartbeat, counter
increments). Skipped cycles re-send cached controlsState/carControl so
downstream freq_ok stays OK. Button edge handling + display-mode
transitions extracted to handle_screen_mode() and called every cycle so
debug-button presses aren't dropped. controlsd: 55% → 30% CPU in park.
dmonitoringmodeld: subscribe to carState; at standstill, skip model.run
and re-publish last inference. driverStateV2 continues flowing at 10Hz
with known-good last face data (driver can't become distracted relative
to a stopped car). ~5% CPU saved.
power_monitoring: remove the `car_battery_capacity_uWh <= 0` shutdown
trigger. That virtual capacity counter floor-limits to 3e6 µWh on boot
and drains in ~12 min at typical device power, so a short drive (that
doesn't fully recharge the 30e6 µWh virtual cap) followed by a quick
store stop would trip shutdown well before the 30-min idle timer. The
real car-battery-voltage protection (low_voltage_shutdown at 11.8V with
60s debounce) is kept.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- commIssue now fires only on real comms failure (not_alive or CAN RX
timeout), not on self-declared valid=False cascades from MSGQ-conflate +
slow-polling freq_ok artifacts. Car drives fine through these and the
banner was false-positive.
- Add 5-cycle hysteresis (50ms) on commIssue / posenetInvalid /
locationdTemporaryError / paramsdTemporaryError.
- Cascade-aware suppression: skip posenet/locationd/paramsd temporary
errors when the only problem is freq_only_cascade (all alive, just
freq/valid tripped).
- Remove debug _dbg_dump_alert_triggers helper and EVENTS/EVENT_NAME
imports.
- Re-enable variable-rate modeld (4/10fps idle, 20fps when lat_active /
lane_changing / calibrating) with republish caching so consumers get
constant publish rate.
- Split lat_requested (modeld signal) from lat_engaged (actuator gate).
Momentary steerFaultTemporary no longer drops modeld rate, preventing
stale-prediction feedback loop on MDPS recovery. CC.latActive still
respects the fault.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Disabled the variable-rate (4/10/20fps) logic — it caused persistent
downstream freq/valid cascades that broke paramsd's angle-offset
learner, calibrationd, and locationd's one-shot filterInitialized latch.
Symptom: user's "TAKE CONTROL IMMEDIATELY / Communication Issue"
banner during engaged driving, plus subtle right-pull (paramsd's fast
angle-offset adaptation was frozen all session).
Simpler model now:
standstill → standby (0fps, paused)
otherwise → 20fps (no variable rate)
Republish-caching and FPCC.latRequested/noLatLaneChange plumbing left in
place so re-enabling variable rate later is a one-line change
(full_rate = True → the original full_rate expression).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fan control rework (thermald → 4Hz):
- DT_TRML 0.5s → 0.25s (thermald loop + fan PID now at 4Hz)
- New clamp rules based on (gear, cruise_engaged, standstill):
parked → 0-100%
in drive + cruise engaged (any speed) → 30-100%
in drive + cruise off + standstill → 10-100%
in drive + cruise off + moving → 30-100%
- thermald now reads gearShifter (via carState) and controlsState.enabled,
passes them to fan_controller.update()
- Removed BENCH_MODE special case — new rules cover bench automatically
- Removed ignition-based branches — gear is the correct signal
System health overlay:
- Subscribed UI to peripheralState so we can read fanSpeedRpm
- Added FAN row: actual fan% (RPM / 65) to sit alongside LAG/DROP/TEMP/CPU/MEM.
Shows the real fan output vs. what the PID is asking for.
Migrate hot signals from paramsMemory to cereal (frogpilotCarControl):
- Added latRequested @3 and noLatLaneChange @4 to FrogPilotCarControl schema
- controlsd sets FPCC.latRequested / FPCC.noLatLaneChange (send-on-change
already gates the IPC)
- modeld reads from sm['frogpilotCarControl'] (added to its subscribers)
instead of paramsMemory (saves ~20 file-read syscalls/sec)
- carcontroller reads from frogpilot_variables (set in-process by controlsd)
instead of paramsMemory (saves ~100 file-read syscalls/sec in 100Hz path).
Dropped carcontroller's now-unused Params instance and import.
- UI (ui.cc, onroad.cc) reads from sm['frogpilotCarControl'].noLatLaneChange
- Removed LatRequested and no_lat_lane_change param registrations + defaults
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Stock rk.lagging fires at 11.1ms (90% of 10ms interval), which the
Hyundai CAN load routinely crosses during normal driving as carstate
parses 50-200 msgs/sec. That's normal, not a fault — the same code
behaved the same way at the fork baseline; we just made it visible with
the LAG overlay.
50Hz effective control is still tighter than any human reaction loop.
rk.lagging remains wired to the defensive skip paths (secondary camera
and radar checks) at the original tighter threshold, so we still avoid
false-alarming dependent events when briefly overloaded.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two related fixes for the post-engage lag spike:
1. controlsdLagging suppressed during lat_engage_suppress window.
Ratekeeper.lagging triggers when avg cycle duration over 100 cycles
exceeds 11.1ms (90% of 10ms budget). The modeld 10→20fps ramp causes
a legitimate transient where downstream services (plannerd, locationd,
calibrationd, paramsd) each drain 2x the message rate, briefly pushing
avg cycle time past the threshold. The underlying system isn't broken —
it's correctly absorbing a scheduled workload transition.
2. frogpilotCarControl now sends only on change (+ 1Hz keepalive) instead
of every 10ms. The message has 3 bool fields, of which speedLimitChanged
code is entirely commented out, trafficModeActive flips only on UI
button press, and alwaysOnLateral changes only on cruise/gear/brake
edges. plannerd doesn't include frogpilotCarControl in its all_checks
list so stale-freq detection isn't a concern. Saves ~7ms/sec of
capnp build + zmq send work.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tucson's radar-based collision warning is more reliable than the comma
model/planner FCW and was producing false positives. Single-user fork in
a single car, so no need to keep both.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
py-spy showed per-cycle atomic param writes were the dominant cost. Each
put() is mkstemp+fsync+flock+rename+fsync_dir — fine when rare, ruinous at
100Hz. At park with no state changes, these writes were running anyway and
the flock contention was poisoning the whole system.
carstate.py (update + update_canfd): CarSpeedLimit, CarIsMetric,
CarCruiseDisplayActual were written every CAN update. Now cached and
written only on change.
controlsd.py: same fix for LatRequested and no_lat_lane_change. Also
throttle the sentry crash-file stat() from 100Hz to 1Hz.
Also: suppress locationdTemporaryError/paramsdTemporaryError/posenetInvalid
on lat engage (same 2s window as commIssue), and tie suppression to the
LatRequested edge instead of CC.latActive (fires immediately, not after
the 250ms ramp-up delay).
Also: reset Ratekeeper when it falls >1s behind — the ~6s fingerprinting
stall at startup was poisoning the lag metric for the entire session.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Decouples "tell modeld to go fast" from "steering actually active":
- New LatRequested memory param — controlsd writes when lat would be active
- modeld reads LatRequested (not carControl.latActive) for FPS decision,
so it switches to 20fps immediately on engage request
- controlsd delays CC.latActive becoming true by 250ms (5 frames @ 20fps)
after LatRequested goes true, giving downstream services
(longitudinalPlan, liveCalibration, etc.) time to stabilize at the new rate
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fan controller: allow full 100% fan when offroad temp >= 75°C (startup cooling)
- ModelFps memory param: modeld publishes actual FPS (20 or 4) so downstream
consumers can adjust frame-rate-dependent logic
- Longitudinal planner: dynamically adjusts dt and v_desired_filter based on
ModelFps; FCW crash_cnt threshold scales with FPS to maintain consistent
0.15s trigger window at both 20fps and 4fps
- controlsd: suppress commIssue alerts for 2s after lateral control engages
(FPS transition from 4->20 causes transient freq check failures)
- Shutdown timer: hardcoded to 10 minutes (was 45min via FrogPilot param),
screen taps reset the countdown via ShutdownTouchReset memory param,
removed Shutdown Timer UI selector from ClearPilot menu
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
updateWakefulness was overriding display power every frame when ignition
was on, fighting the screen-off set by home.cc. Now respects
ScreenDisplayMode 3 unconditionally. Also auto-resets to mode 0 when
shifting into drive from screen-off.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cruise warning sign appears above speed limit sign when cruise set
speed is too far from the speed limit:
- Red (over): cruise >= limit + 10 (if limit >= 50) or + 5 (if < 50)
- Green (under): cruise <= limit - 5
- Only when cruise active (not paused/disabled) and limit >= 20
- Nightrider mode: colored text/border on black background
Speed limit sign enlarged 5%. 20px gap between signs. Bench mode
gains cruiseactive command (0=disabled, 1=active, 2=paused).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New speed_logic.py module converts raw CAN speed limit and GPS speed
into display-ready params. Called from controlsd (live) and
bench_onroad (bench) at ~2Hz. UI reads params to render:
- Current speed (top center, hidden when 0 or no GPS)
- MUTCD speed limit sign (lower-left, normal + nightrider styles)
- Unit-aware display (mph/kph based on CAN DISTANCE_UNIT)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>
- 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>
- Fix controlsd crash: self.state.name AttributeError when state is int
- Move telemetry tlog import to module level in carstate.py (was per-frame)
- Remove FrogPilot screen recorder from UI (was crashing OMX on init)
- Ready screen: boot logo background, 8-bit READY! sprite, error states
(panda not connected, car not recognized) with 10s startup grace period
- ClearPilot menu: always opens to General, QButtonGroup for sidebar,
System Status uses ButtonControl, VPN toggle with process control
- Sidebar hidden on construction (no flash before splash)
- Status window: threaded data collection (QtConcurrent), panda detection
via scene.pandaType (SPI, not USB), only refreshes when visible
- VPN: moved to system/clearpilot/, SIGTERM graceful shutdown, keepalive
ping through tunnel, killall openvpn on disable, launched from
launch_openpilot.sh instead of continue.sh
- Disable gpsd and dashcamd temporarily for perf testing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New ScreenDisplayMode param (fixes ScreenDisaplayMode typo):
0=auto-normal, 1=auto-nightrider, 2=normal, 3=screen-off, 4=nightrider
Nightrider mode: black background (no camera feed), path/lane lines
drawn as 2px outlines only. Auto mode uses NOAA solar position calc
in gpsd to switch between day/night based on GPS lat/lon and UTC time.
First calc on GPS fix, then every 30 seconds.
Button cycle onroad: 0→4, 1→2, 2→3, 3→4, 4→2 (never back to auto).
Offroad: any→3, 3→0. bench_cmd debugbutton simulates the button press.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added brakePressed to cruise group, prev_cruise_button and
prev_main_button to buttons group in carstate telemetry. New "engage"
group in controlsd logs state machine state, enabled/active flags, and
stock cruise state every frame for tracing engagement desync bugs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New dashcamd: connects to camerad via VisionIPC, feeds raw NV12
frames directly to OMX H.264 encoder. Full 1928x1208 resolution,
4Mbps, 3-minute MP4 segments. Works regardless of UI state.
- Added encode_frame_nv12() to OmxEncoder — skips RGBA->NV12 conversion
- Suspends recording after 10 minutes of standstill
- Disabled old screen recorder timer in onroad.cc
- Suppress debug button alert (clpDebug event still fires for screen toggle)
- launch_openpilot.sh self-cleans other instances before starting
- Register DashcamDebug param in params.cc and manager.py
- Add dashcamd to build system (SConscript) and process_config
- Updated CLAUDE.md with all session changes
- Added GOALS.md feature roadmap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>