6 Commits

Author SHA1 Message Date
brianhansonxyz cea422b075 locationd: ignore GPS as Kalman input; expand park-cached-output to onroad consumers
locationd: ignore gpsLocation observations entirely (clearpilot_disable_gps
const at the top of handle_gps; falls through to determine_gps_mode's
no-GPS path). gpsd.py keeps publishing real GPS for UI / dashcam / clock
/ night-mode — only locationd ignores it. The previous gpsd.py path was
hard-coding vNED=[0,0,0] while the car was moving 28 m/s, feeding the
Kalman contradictory GPS-vs-IMU velocity observations that propagated into
latcontrol_torque through liveLocationKalman.angularVelocityCalibrated and
caused a real "drift right on straight roads" symptom.

controlsd: short-circuit state_control() while parked. Skips LaC/LoC PID,
MPC, model_v2 reads, lane-change logic — all wasted work when the car
isn't moving. Returns the same enabled=False/latActive=False/longActive=False
CC the original code would have produced, so publish_logs runs unchanged
and card.controls_update keeps the sendcan / tester-present heartbeat
flowing at 100Hz. controlsd CPU dropped from ~59% to ~3% in park.

controlsd: also wire self.FPCC.noLatLaneChange = True/False in the
existing lane-change suppression branch. The Hyundai carcontroller already
reads off frogpilot_variables.no_lat_lane_change for the no-steer signal,
but the UI's distinctive yellow CHANGE_LANE_PATH_COLOR (onroad.cc:901)
reads from frogpilotCarControl.NoLatLaneChange — which nobody was setting,
so the yellow line never appeared during lane-change suppression.

plannerd: skip longitudinal_planner.update + publish + publish_ui_plan
while parked. Downstream controlsd already short-circuits in park, so
longitudinalPlan / uiPlan staleness is fine.

frogpilot_process: same idea — skip frogpilot_planner.update + publish
while parked.

dmonitoringmodeld: mirror the cached-output trick from modeld. Add carState
to the SubMaster, cache the last (model_output, dsp_execution_time) tuple,
and serve it on every frame while gear is in park instead of running DSP
inference on the driver camera.

Together with the existing modeld park-cached-output, the heavy onroad
processing pipeline (modeld → plannerd → controlsd → carcontroller) now
all idles in park and only fires when the car leaves park.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 12:12:38 -05:00
brianhansonxyz dc7e0a2db7 controlsd+calibrationd: suppress commIssue from valid=False cascade
Two fixes that reinstate the pre-revert defenses against the "TAKE CONTROL
IMMEDIATELY / Communication Issue" banner that fires when self-driving
on the baseline modelrevert stack:

calibrationd: publish valid based on calStatus == calibrated, not
sm.all_checks(). Original gate cascaded upstream freq glitches into
liveCalibration.valid=False, which kept locationd.filterInitialized
False, which fed garbage into paramsd, which corrupted steerRatio
(erratic steering). "valid" here is a question about convergence, not
input freshness.

controlsd: narrow the commIssue trigger to genuine comm failures —
not_alive OR can_rcv_timeout. The `not sm.all_checks()` branch also
picked up valid=False, but paramsd / torqued / plannerd / frogpilot_planner
/ dmonitoringd all propagate their sm.all_checks() into msg.valid via
a polling-pattern artifact (freq_ok inside poll='...' subscribers
tracks gaps between drain bursts rather than the publish rate), so
the whole stack flaps valid and trips the banner during normal
driving. Content and rate are fine; just the flag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 11:00:45 -05:00
brianhansonxyz f896dfe25a modeld: cached-output standby while gear is in park
Narrow re-introduction of the power-save mode: when carState reports
gearShifter == park, serve the last rendered model_output instead of
running GPU inference. First cycle (or before carState is flowing)
still runs one real inference so we have something to cache and serve.
Out-of-park returns to per-frame inference immediately.

Avoids the pitfall of the earlier variable-rate path: this doesn't
change the publish rate on modelV2/cameraOdometry (we still publish
every frame), so downstream freq_ok / valid checks in calibrationd /
locationd / paramsd stay happy. Only the GPU inference is skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:49:44 -05:00
brianhansonxyz 2ce6e8fe0c modelrevert: boot fixes + strip remaining variable-rate cruft
controlsd:
- stop writing the removed 'no_lat_lane_change' persistent param
  (key isn't in the whitelist anymore). Write to
  frogpilot_variables.no_lat_lane_change instead so the carcontroller
  read still works and the lane-change-disengage feature is preserved.
- initialize self.driving_gear = False in __init__; it's set at the
  end of step() in update_frogpilot_variables but clearpilot_state_control
  reads it on the first iteration before that's run.

modeld:
- remove the remaining CLEARPILOT variable-rate / standby code path
  that was baked into the initial-import commit (the one our first
  commit captured). Constant 20fps, no ModelStandby/ModelStandbyTs
  writes, no params_memory reads for 'no_lat_lane_change' (which
  isn't registered anymore).
- drop now-unused locals (model_standby, last_standby_ts_write,
  params_memory).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:42:11 -05:00
brianhansonxyz 3bd0c942e8 controlsd: re-apply QoL hooks on top of reverted baseline
Re-adds the non-self-drive controlsd integrations that the UI and
memory-param pipeline need:

- import SpeedState from clearpilot.speed_logic
- self.speed_state, speed_state_frame, was_driving_gear init
- subscribe to gpsLocation (ignored in alive/freq_ok/valid gates — OK if
  the GPS daemon isn't publishing)
- clearpilot_state_control:
  - auto-reset ScreenDisplayMode 3→0 on park→drive transition
  - ~2Hz speed-state update driving the speed-limit sign and cruise
    over/under warning sign via ClearpilotCruiseWarning /
    ClearpilotSpeedLimitDisplay memory params

The debug-button (LFA) ScreenDisplayMode cycling already lived in the
reverted baseline (it was in the first commit), so it's preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:31:51 -05:00
brianhansonxyz 12da9acfdd revert modeld/controlsd/plannerd/locationd to first-commit baseline
Starting point for rebuilding self-drive from a known-good baseline.
Reverts the following to their state at f46339c:
- selfdrive/modeld/modeld.py (constant 20fps, no variable-rate / standby skip)
- selfdrive/modeld/dmonitoringmodeld.py (no carState sub, no standstill skip)
- selfdrive/controls/controlsd.py (no parked-cycle skip, no FPCC hoisting, no MDPS split)
- selfdrive/controls/lib/longitudinal_planner.py
- selfdrive/locationd/calibrationd.py (valid = sm.all_checks again)
- selfdrive/locationd/paramsd.py
- selfdrive/locationd/torqued.py

All non-self-drive features (thermald fan control, speed limit controller,
cruise warning signs, UI state transitions, GPS fixes, ClearPilot menu,
dashcamd, telemetry, etc.) remain as-is on this branch — only the 4 core
self-drive processes are reverted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:26:36 -05:00
10 changed files with 143 additions and 284 deletions
+70 -160
View File
@@ -81,9 +81,11 @@ class Controls:
self.params_memory.put_bool("CPTLkasButtonAction", False)
self.params_memory.put_int("ScreenDisplayMode", 0)
# ClearPilot speed processing
# CLEARPILOT: speed-limit/cruise-warning state machine + park→drive auto-reset
self.speed_state = SpeedState()
self.speed_state_frame = 0
self.was_driving_gear = False
self.driving_gear = False
self.radarless_model = self.params.get("Model", encoding='utf-8') in RADARLESS_MODELS
@@ -114,8 +116,7 @@ class Controls:
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'liveLocationKalman',
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
'testJoystick', 'frogpilotPlan', 'gpsLocation'] + self.camera_packets + self.sensor_packets,
ignore_alive=ignore+['gpsLocation'], ignore_avg_freq=ignore+['radarState', 'testJoystick', 'gpsLocation'],
ignore_valid=['testJoystick', 'gpsLocation'],
ignore_alive=ignore + ['gpsLocation'], ignore_avg_freq=ignore+['radarState', 'testJoystick', 'gpsLocation'], ignore_valid=['testJoystick', 'gpsLocation'],
frequency=int(1/DT_CTRL))
self.joystick_mode = self.params.get_bool("JoystickDebugMode")
@@ -170,26 +171,6 @@ class Controls:
self.current_alert_types = [ET.PERMANENT]
self.logged_comm_issue = None
self.not_running_prev = None
self.lat_requested_ts = 0 # CLEARPILOT: timestamp when lat was first requested (ramp-up delay + comm issue suppression)
self.prev_lat_requested = False
# CLEARPILOT: gate frogpilotCarControl send on change (3 static bools, was publishing 100Hz)
self._prev_fpcc = (None, None, None, None, None)
self._last_fpcc_send_frame = 0
# CLEARPILOT: hysteresis counters for transient-filter on cascade alerts. A blip
# under 50ms (5 cycles at 100Hz) is typical of MSGQ conflate + slow-polling-consumer
# freq-measurement artifacts — not a real comm issue. Persistent failure still
# alerts after 50ms, well inside a driver's reaction window.
self._hyst_commissue = 0
self._hyst_locationd_tmp = 0
self._hyst_paramsd_tmp = 0
self._hyst_posenet = 0
self.HYST_CYCLES = 5
# CLEARPILOT: parked-cycle skip — in park+ignition-on, run the full step() only
# every 10th cycle. CAN parse + CAN TX still happen every cycle; outer logic
# (events, state machine, PID/MPC, publishing) runs at 10Hz. Cached message
# bytes are re-published on skipped cycles so downstream freq_ok stays OK.
self._cached_controlsState_bytes = None
self._cached_carControl_bytes = None
self.steer_limited = False
self.desired_curvature = 0.0
self.experimental_mode = False
@@ -224,8 +205,6 @@ class Controls:
self.always_on_lateral_main = self.always_on_lateral and self.params.get_bool("AlwaysOnLateralMain")
self.drive_added = False
self.driving_gear = False
self.was_driving_gear = False
self.fcw_random_event_triggered = False
self.holiday_theme_alerted = False
self.onroad_distance_pressed = False
@@ -264,21 +243,6 @@ class Controls:
CS = self.data_sample()
cloudlog.timestamp("Data sampled")
# CLEARPILOT: handle debug-button press + display-mode transitions on every
# cycle — button edge events only live in the cycle's CS.buttonEvents and
# would otherwise be dropped on skipped cycles.
self.handle_screen_mode(CS)
# CLEARPILOT: in park, only run the full step() every 10th cycle. data_sample
# above still runs (CAN parse, button detection). Below, card.controls_update
# still runs (CAN TX heartbeat, counters increment). The skipped outer logic
# (events, state machine, PID/MPC, publishing) causes at most ~100ms lag on
# button→state transitions, which is fine in park. Cached message bytes are
# re-sent so downstream consumers see steady 100Hz.
parked = CS.gearShifter == car.CarState.GearShifter.park
full_cycle = (not parked) or (self.sm.frame % 10 == 0) or (self._cached_controlsState_bytes is None)
if full_cycle:
self.update_events(CS)
self.update_frogpilot_events(CS)
self.update_clearpilot_events(CS)
@@ -293,22 +257,13 @@ class Controls:
CC, lac_log = self.state_control(CS)
CC = self.clearpilot_state_control(CC, CS)
# Publish data (also sends CAN TX via card.controls_update inside)
# Publish data
self.publish_logs(CS, start_time, CC, lac_log)
self.CS_prev = CS
# Update FrogPilot variables
self.update_frogpilot_variables(CS)
else:
# CAN TX heartbeat: keep counters incrementing and CAN frames flowing to the car
if not self.CP.passive and self.initialized:
self.card.controls_update(self.CC, self.frogpilot_variables)
# Re-publish cached messages so downstream freq_ok checks don't trip
self.pm.send('controlsState', self._cached_controlsState_bytes)
self.pm.send('carControl', self._cached_carControl_bytes)
self.CS_prev = CS
def data_sample(self):
"""Receive data from sockets and update carState"""
@@ -499,18 +454,7 @@ class Controls:
standby_ts = float(self.params_memory.get("ModelStandbyTs") or "0")
except (ValueError, TypeError):
standby_ts = 0
now = time.monotonic()
model_suppress = (now - standby_ts) < 2.0
# CLEARPILOT: suppress commIssue/location/params errors for 2s after lat was requested
# Model FPS jumps from 4 to 20 on engage, causing transient freq check failures
# lat_requested_ts is set in state_control on the False->True transition of LatRequested
# CLEARPILOT: 2s window — long enough to cover the modeld rate transition burst
# without hiding real comms issues. Extending this further risks masking a genuine
# fault that demands immediate driver takeover.
lat_engage_suppress = (now - self.lat_requested_ts) < 2.0
# CLEARPILOT: expose suppress flags + standby_ts for the debug dumper in step()
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}
if self.sm.recv_frame['managerState'] and (not_running - IGNORE_PROCESSES):
@@ -524,14 +468,9 @@ class Controls:
self.events.add(EventName.cameraMalfunction)
elif not self.sm.all_freq_ok(self.camera_packets):
self.events.add(EventName.cameraFrameRate)
# CLEARPILOT: alert only when avg cycle time exceeds 20ms (≈50Hz effective).
# Stock rk.lagging fires at 11.1ms (90% of 10ms) which the Hyundai CAN load
# routinely crosses while driving — that's normal, not a fault. 50Hz control is
# still plenty responsive. `rk.lagging` is still used defensively elsewhere
# (lines ~479, 492) to skip secondary checks when slightly overloaded.
avg_dt = sum(self.rk._dts) / len(self.rk._dts)
alert_lagging = avg_dt > 0.020
if not REPLAY and alert_lagging and not model_suppress and not lat_engage_suppress:
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)
if not self.radarless_model:
if not model_suppress and (len(self.sm['radarState'].radarErrors) or (not self.rk.lagging and not self.sm.all_checks(['radarState']))):
@@ -546,60 +485,37 @@ class Controls:
# 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))
no_system_errors = (not has_disable_events) or (len(self.events) == num_events)
# CLEARPILOT: commIssue fires ONLY when messages actually aren't flowing. If all
# subscribers are alive and CAN isn't timing out, comms are fine — any `valid=False`
# is an upstream self-declaration that cascades from the MSGQ conflate +
# slow-polling-consumer freq_ok measurement artifact. The car drives correctly
# during these cascades (content is still usable), so we stop raising the banner.
# Genuine comms failures — missing messages or CAN RX timeout — still fire.
# CLEARPILOT: fire commIssue ONLY when messages actually aren't flowing (not_alive)
# or CAN RX is timing out. Don't fire on self-declared valid=False — that's the
# polling-pattern / all_checks cascade that paramsd/torqued/plannerd/frogpilot
# propagate even while their publish rate and content are fine.
comms_really_broken = (not self.sm.all_alive()) or self.card.can_rcv_timeout
commissue_cond = comms_really_broken and no_system_errors and not model_suppress and not lat_engage_suppress
if commissue_cond:
self._hyst_commissue += 1
else:
self._hyst_commissue = 0
if self._hyst_commissue >= self.HYST_CYCLES:
if comms_really_broken and no_system_errors and not model_suppress:
if not self.sm.all_alive():
self.events.add(EventName.commIssue)
if commissue_cond:
# Log ONCE per entry into comms-really-broken state — no hyst counter so it doesn't
# spam every cycle (hyst changes every cycle and would make logs dict always differ).
else:
self.events.add(EventName.commIssue) # can_rcv_timeout path
logs = {
'invalid': [s for s, valid in self.sm.valid.items() if not valid],
'not_alive': [s for s, alive in self.sm.alive.items() if not alive],
'not_freq_ok': [s for s, freq_ok in self.sm.freq_ok.items() if not freq_ok],
'can_rcv_timeout': self.card.can_rcv_timeout,
}
if logs != self.logged_comm_issue:
import sys, json
print("CLP commIssue " + json.dumps(logs), file=sys.stderr, flush=True)
cloudlog.event("commIssue", error=True, **logs)
self.logged_comm_issue = logs
else:
self.logged_comm_issue = None
if not (self.CP.notCar and self.joystick_mode):
# CLEARPILOT: suppress locationd/paramsd "temporary error" when we're in the
# freq-only cascade (same root as the commIssue suppression above). These events
# cascade from upstream freq_ok artifacts — locationd's filterInitialized latches
# False if calibrationd tips invalid due to freq, which cascades here.
# Real failures still fire commIssue above via alive/valid/can_rcv_timeout gate.
freq_only_cascade = (not self.sm.all_checks()) and not comms_really_broken
# deviceFalling is real-physics (shock sensor), no hysteresis, no cascade suppression.
if not self.sm['liveLocationKalman'].posenetOK and not model_suppress:
self.events.add(EventName.posenetInvalid)
if not self.sm['liveLocationKalman'].deviceStable:
self.events.add(EventName.deviceFalling)
posenet_bad = not self.sm['liveLocationKalman'].posenetOK and not model_suppress and not lat_engage_suppress and not freq_only_cascade
self._hyst_posenet = self._hyst_posenet + 1 if posenet_bad else 0
if self._hyst_posenet >= self.HYST_CYCLES:
self.events.add(EventName.posenetInvalid)
locd_bad = not self.sm['liveLocationKalman'].inputsOK and not model_suppress and not lat_engage_suppress and not freq_only_cascade
self._hyst_locationd_tmp = self._hyst_locationd_tmp + 1 if locd_bad else 0
if self._hyst_locationd_tmp >= self.HYST_CYCLES:
if not self.sm['liveLocationKalman'].inputsOK and not model_suppress:
self.events.add(EventName.locationdTemporaryError)
paramsd_bad = not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY) and not model_suppress and not lat_engage_suppress and not freq_only_cascade
self._hyst_paramsd_tmp = self._hyst_paramsd_tmp + 1 if paramsd_bad else 0
if self._hyst_paramsd_tmp >= self.HYST_CYCLES:
if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY) and not model_suppress:
self.events.add(EventName.paramsdTemporaryError)
# conservative HW alert. if the data or frequency are off, locationd will throw an error
@@ -613,8 +529,16 @@ class Controls:
if self.cruise_mismatch_counter > int(6. / DT_CTRL):
self.events.add(EventName.cruiseMismatch)
# CLEARPILOT: FCW disabled — car's own radar AEB works better and triggers reliably.
# The comma FCW was producing false positives and adds nothing over the stock system.
# Check for FCW
stock_long_is_braking = self.enabled and not self.CP.openpilotLongitudinalControl and CS.aEgo < -1.25
model_fcw = self.sm['modelV2'].meta.hardBrakePredicted and not CS.brakePressed and not stock_long_is_braking
planner_fcw = self.sm['longitudinalPlan'].fcw and self.enabled
if planner_fcw or model_fcw:
self.events.add(EventName.fcw)
# self.fcw_random_event_triggered = True
# elif self.fcw_random_event_triggered and self.random_events:
# self.events.add(EventName.yourFrogTriedToKillMe)
# self.fcw_random_event_triggered = False
for m in messaging.drain_sock(self.log_sock, wait_for_one=False):
try:
@@ -741,6 +665,25 @@ class Controls:
def state_control(self, CS):
"""Given the state, this function returns a CarControl packet"""
# CLEARPILOT: short-circuit while parked. Skip LaC/LoC PID, MPC, model_v2
# reads, lane-change logic — none of it matters when the car isn't moving.
# publish_logs still runs and still triggers carcontroller.apply via
# card.controls_update, so the sendcan heartbeats / tester-present messages
# keep flowing at 100Hz and the car doesn't fault. Saves ~30% controlsd CPU
# in park.
if CS.gearShifter == car.CarState.GearShifter.park:
CC = car.CarControl.new_message()
CC.enabled = False
CC.latActive = False
CC.longActive = False
CC.actuators.longControlState = self.LoC.long_control_state
self.LaC.reset()
self.LoC.reset(v_pid=CS.vEgo)
self.frogpilot_variables.no_lat_lane_change = False
self.FPCC.noLatLaneChange = False
lac_log = log.ControlsState.LateralDebugState.new_message()
return CC, lac_log
# Update VehicleModel
lp = self.sm['liveParameters']
x = max(lp.stiffnessFactor, 0.1)
@@ -762,19 +705,8 @@ class Controls:
# Check which actuators can be enabled
standstill = CS.vEgo <= max(self.CP.minSteerSpeed, MIN_LATERAL_CONTROL_SPEED) or CS.standstill
# CLEARPILOT: lat_requested ignores momentary MDPS faults so modeld doesn't drop rate during
# brief steerFaultTemporary blips (stale predictions on recovery caused a fault feedback loop).
# lat_engaged gates the actual steering command and still respects the fault.
lat_requested = (self.active or self.FPCC.alwaysOnLateral) and self.speed_check and \
CC.latActive = (self.active or self.FPCC.alwaysOnLateral) and self.speed_check and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \
(not standstill or self.joystick_mode)
lat_engaged = lat_requested and not CS.steerFaultTemporary and not CS.steerFaultPermanent
now_ts = time.monotonic()
if lat_requested and not self.prev_lat_requested:
self.lat_requested_ts = now_ts
self.prev_lat_requested = lat_requested
self.FPCC.latRequested = lat_requested
CC.latActive = lat_engaged and (now_ts - self.lat_requested_ts) >= 0.25
CC.longActive = self.enabled and not self.events.contains(ET.OVERRIDE_LONGITUDINAL) and self.CP.openpilotLongitudinalControl
actuators = CC.actuators
@@ -789,13 +721,13 @@ class Controls:
CC.leftBlinker = model_v2.meta.laneChangeDirection == LaneChangeDirection.left
CC.rightBlinker = model_v2.meta.laneChangeDirection == LaneChangeDirection.right
no_lat_lane_change = model_v2.meta.laneChangeState == LaneChangeState.laneChangeStarting and clearpilot_disable_lat_on_lane_change
if no_lat_lane_change:
if model_v2.meta.laneChangeState == LaneChangeState.laneChangeStarting and clearpilot_disable_lat_on_lane_change:
CC.latActive = False
# CLEARPILOT: noLatLaneChange now lives on frogpilotCarControl (see update_frogpilot_variables).
# Also mirrored to frogpilot_variables so in-process carcontroller reads it without IPC.
self.FPCC.noLatLaneChange = no_lat_lane_change
self.frogpilot_variables.no_lat_lane_change = no_lat_lane_change
self.frogpilot_variables.no_lat_lane_change = True
self.FPCC.noLatLaneChange = True
else:
self.frogpilot_variables.no_lat_lane_change = False
self.FPCC.noLatLaneChange = False
if CS.leftBlinker or CS.rightBlinker:
self.last_blinker_frame = self.sm.frame
@@ -1031,8 +963,6 @@ class Controls:
controlsState.lateralControlState.torqueState = lac_log
self.pm.send('controlsState', dat)
# CLEARPILOT: cache for re-publication on parked-skip cycles
self._cached_controlsState_bytes = dat.to_bytes()
# onroadEvents - logged every second or on change
if (self.sm.frame % int(1. / DT_CTRL) == 0) or (self.events.names != self.events_prev):
@@ -1047,7 +977,6 @@ class Controls:
cc_send.valid = CS.canValid
cc_send.carControl = CC
self.pm.send('carControl', cc_send)
self._cached_carControl_bytes = cc_send.to_bytes()
# copy CarControl to pass to CarInterface on the next iteration
self.CC = CC
@@ -1078,11 +1007,6 @@ class Controls:
while True:
self.step()
self.rk.monitor_time()
# CLEARPILOT: accumulated debt from startup/blocking calls never recovers.
# Reset the rate keeper when catastrophically behind — prevents a one-time
# ~8s fingerprinting stall from poisoning the lag metric for the whole session.
if self.rk.remaining < -1.0:
self.rk._next_frame_time = time.monotonic() + self.rk._interval
except SystemExit:
e.set()
t.join()
@@ -1153,11 +1077,13 @@ class Controls:
elif self.sm['modelV2'].meta.turnDirection == Desire.turnRight:
self.events.add(EventName.turningRight)
# CLEARPILOT: check crash file once per second, not every cycle (stat syscall at 100Hz hurts)
if self.sm.frame % 100 == 0:
self._crashed_file_exists = os.path.isfile(os.path.join(sentry.CRASHES_DIR, 'error.txt'))
if getattr(self, '_crashed_file_exists', False) and self.crashed_timer < 10:
if os.path.isfile(os.path.join(sentry.CRASHES_DIR, 'error.txt')) and self.crashed_timer < 10:
self.events.add(EventName.openpilotCrashed)
# if self.random_events and not self.openpilot_crashed_triggered:
# self.events.add(EventName.openpilotCrashedRandomEvents)
# self.openpilot_crashed_triggered = True
self.crashed_timer += DT_CTRL
# if self.speed_limit_alert or self.speed_limit_confirmation:
@@ -1280,18 +1206,10 @@ class Controls:
self.FPCC.trafficModeActive = self.frogpilot_variables.traffic_mode and self.params_memory.get_bool("TrafficModeActive")
# CLEARPILOT: send only when any field changes, with 1Hz keepalive
# Saves ~7ms/sec of capnp+zmq work (was sending 100Hz despite static contents)
# latRequested and noLatLaneChange are edge-triggered too (rare flips)
fpcc_tuple = (self.FPCC.alwaysOnLateral, self.FPCC.speedLimitChanged, self.FPCC.trafficModeActive,
self.FPCC.latRequested, self.FPCC.noLatLaneChange)
if fpcc_tuple != self._prev_fpcc or (self.sm.frame - self._last_fpcc_send_frame) >= 100:
fpcc_send = messaging.new_message('frogpilotCarControl')
fpcc_send.valid = CS.canValid
fpcc_send.frogpilotCarControl = self.FPCC
self.pm.send('frogpilotCarControl', fpcc_send)
self._prev_fpcc = fpcc_tuple
self._last_fpcc_send_frame = self.sm.frame
if self.params_memory.get_bool("FrogPilotTogglesUpdated"):
self.update_frogpilot_params()
@@ -1369,14 +1287,8 @@ class Controls:
if any(be.pressed and be.type == FrogPilotButtonType.lkas for be in CS.buttonEvents):
self.events.add(EventName.clpDebug)
def handle_screen_mode(self, CS):
"""CLEARPILOT: tracks driving_gear, auto-resets display, and cycles
ScreenDisplayMode on debug-button presses. Must run every cycle so button
edge events aren't lost during parked-skip mode (edges happen in carstate
edge detection and only appear in that one cycle's CS.buttonEvents)."""
self.driving_gear = CS.gearShifter not in (GearShifter.neutral, GearShifter.park, GearShifter.reverse, GearShifter.unknown)
# auto-reset display when shifting into drive from screen-off
def clearpilot_state_control(self, CC, CS):
# CLEARPILOT: auto-reset display when shifting into drive from screen-off
if self.driving_gear and not self.was_driving_gear:
if self.params_memory.get_int("ScreenDisplayMode") == 3:
self.params_memory.put_int("ScreenDisplayMode", 0)
@@ -1395,9 +1307,8 @@ class Controls:
self.params_memory.put_int("ScreenDisplayMode", new_mode)
def clearpilot_state_control(self, CC, CS):
# ClearPilot speed processing (~2 Hz at 100 Hz loop)
# ClearPilot speed processing (~2 Hz at 100 Hz loop) — drives speed-limit sign
# and cruise over/under warning sign via memory params read by the UI.
self.speed_state_frame += 1
if self.speed_state_frame % 50 == 0:
gps = self.sm['gpsLocation']
@@ -1410,7 +1321,6 @@ class Controls:
cruise_standstill = CS.cruiseState.standstill
self.speed_state.update(speed_ms, has_gps, speed_limit_ms, is_metric,
cruise_speed_ms, cruise_active, cruise_standstill)
return CC
def main():
+2 -33
View File
@@ -143,10 +143,6 @@ class LongitudinalPlanner:
self.params = Params()
self.params_memory = Params("/dev/shm/params")
# CLEARPILOT: track model FPS for dynamic dt adjustment
self.model_fps = 20
self._dbg_prev_valid = True # CLEARPILOT: diagnostic logging on valid transitions
self.radarless_model = self.params.get("Model", encoding='utf-8') in RADARLESS_MODELS
self.release = get_short_branch() == "clearpilot"
@@ -177,19 +173,6 @@ class LongitudinalPlanner:
return x, v, a, j
def update(self, sm):
# CLEARPILOT: read actual model FPS and adjust dt accordingly
model_fps_raw = self.params_memory.get("ModelFps")
if model_fps_raw is not None:
try:
fps = int(model_fps_raw)
if fps > 0 and fps != self.model_fps:
self.model_fps = fps
self.dt = 1.0 / fps
self.v_desired_filter.dt = self.dt
self.v_desired_filter.update_alpha(2.0) # rc=2.0, same as __init__
except (ValueError, ZeroDivisionError):
pass
self.mpc.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc'
v_ego = sm['carState'].vEgo
@@ -256,10 +239,7 @@ class LongitudinalPlanner:
self.j_desired_trajectory = np.interp(ModelConstants.T_IDXS[:CONTROL_N], T_IDXS_MPC[:-1], self.mpc.j_solution)
# TODO counter is only needed because radar is glitchy, remove once radar is gone
# CLEARPILOT: scale crash_cnt threshold by FPS — at 20fps need 3 frames (0.15s),
# at 4fps need 1 frame (0.25s already exceeds 0.15s window)
fcw_threshold = max(0, int(0.15 * self.model_fps) - 1) # 20fps->2, 4fps->0
self.fcw = self.mpc.crash_cnt > fcw_threshold and not sm['carState'].standstill
self.fcw = self.mpc.crash_cnt > 2 and not sm['carState'].standstill
if self.fcw:
cloudlog.info("FCW triggered")
@@ -278,18 +258,7 @@ class LongitudinalPlanner:
def publish(self, sm, pm):
plan_send = messaging.new_message('longitudinalPlan')
valid = sm.all_checks(service_list=['carState', 'controlsState'])
# CLEARPILOT: log on transition into invalid — stderr goes to our plannerd.log
if valid != self._dbg_prev_valid and not valid:
import sys
print(
"CLP longitudinalPlan valid=False: "
f"carState(a={sm.alive['carState']},v={sm.valid['carState']},f={sm.freq_ok['carState']}) "
f"controlsState(a={sm.alive['controlsState']},v={sm.valid['controlsState']},f={sm.freq_ok['controlsState']})",
file=sys.stderr, flush=True
)
self._dbg_prev_valid = valid
plan_send.valid = valid
plan_send.valid = sm.all_checks(service_list=['carState', 'controlsState'])
longitudinalPlan = plan_send.longitudinalPlan
longitudinalPlan.modelMonoTime = sm.logMonoTime['modelV2']
+4
View File
@@ -34,6 +34,10 @@ def plannerd_thread():
while True:
sm.update()
if sm.updated['modelV2']:
# CLEARPILOT: skip planning while parked. The downstream consumer (controlsd)
# already short-circuits in park, so longitudinalPlan/uiPlan staleness is fine.
if sm['carState'].gearShifter == car.CarState.GearShifter.park:
continue
longitudinal_planner.update(sm)
longitudinal_planner.publish(sm, pm)
publish_ui_plan(sm, pm, longitudinal_planner)
+3 -1
View File
@@ -85,7 +85,9 @@ def frogpilot_thread():
frogpilot_planner = FrogPilotPlanner(CP)
frogpilot_planner.update_frogpilot_params()
if sm.updated['modelV2']:
# CLEARPILOT: skip planner work while parked.
parked = sm['carState'].gearShifter == car.CarState.GearShifter.park
if sm.updated['modelV2'] and not parked:
frogpilot_planner.update(sm['carState'], sm['controlsState'], sm['frogpilotCarControl'], sm['frogpilotNavigation'],
sm['liveLocationKalman'], sm['modelV2'], sm['radarState'])
frogpilot_planner.publish(sm, pm)
+5 -5
View File
@@ -285,11 +285,11 @@ def main() -> NoReturn:
# 4Hz driven by cameraOdometry
if sm.frame % 5 == 0:
# CLEARPILOT: publish valid based on calibration status, not upstream sm.all_checks().
# Original openpilot gated valid on fresh inputs, but that caused a cascade:
# upstream freq glitches → liveCalibration.valid=False → locationd stays
# uninitialized → paramsd fed garbage → bogus steerRatio/stiffnessFactor → erratic
# steering. "valid" semantically means "calibration data is trustworthy"; that's a
# question about calibration convergence, not input freshness.
# The original gate cascaded upstream freq glitches into liveCalibration.valid=False,
# which kept locationd.filterInitialized False, which fed garbage into paramsd, which
# corrupted steerRatio and caused erratic steering (and controlsd commIssue banners).
# "valid" here semantically means "the calibration data is trustworthy" — a question
# about convergence, not input freshness.
cal_valid = calibrator.cal_status == log.LiveCalibrationData.Status.calibrated
calibrator.send_data(pm, cal_valid)
+7 -1
View File
@@ -308,12 +308,18 @@ void Localizer::input_fake_gps_observations(double current_time) {
}
void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::Reader& log, const double sensor_time_offset) {
// CLEARPILOT: GPS disabled as a Kalman input. gpsd still publishes real GPS for
// UI / dashcam / clock / night-mode; only locationd ignores it. Falls through to
// determine_gps_mode() which is openpilot's existing no-GPS path (fake observations
// to bound position uncertainty). To re-enable, set this to false.
const bool clearpilot_disable_gps = true;
bool gps_unreasonable = (Vector2d(log.getHorizontalAccuracy(), log.getVerticalAccuracy()).norm() >= SANE_GPS_UNCERTAINTY);
bool gps_accuracy_insane = ((log.getVerticalAccuracy() <= 0) || (log.getSpeedAccuracy() <= 0) || (log.getBearingAccuracyDeg() <= 0));
bool gps_lat_lng_alt_insane = ((std::abs(log.getLatitude()) > 90) || (std::abs(log.getLongitude()) > 180) || (std::abs(log.getAltitude()) > ALTITUDE_SANITY_CHECK));
bool gps_vel_insane = (floatlist2vector(log.getVNED()).norm() > TRANS_SANITY_CHECK);
if (!log.getHasFix() || gps_unreasonable || gps_accuracy_insane || gps_lat_lng_alt_insane || gps_vel_insane) {
if (clearpilot_disable_gps || !log.getHasFix() || gps_unreasonable || gps_accuracy_insane || gps_lat_lng_alt_insane || gps_vel_insane) {
//this->gps_valid = false;
this->determine_gps_mode(current_time);
return;
+1 -10
View File
@@ -187,7 +187,6 @@ def main():
avg_offset_valid = True
total_offset_valid = True
roll_valid = True
dbg_prev_valid = True # CLEARPILOT: track valid transitions
while True:
sm.update()
@@ -257,15 +256,7 @@ def main():
liveParameters.filterState.std = P.tolist()
liveParameters.filterState.valid = True
lp_valid = sm.all_checks()
# CLEARPILOT: on transition to invalid, log per-subscriber state to paramsd.log
if lp_valid != dbg_prev_valid and not lp_valid:
import sys
bad = [s for s in sm.alive if not (sm.alive[s] and sm.valid[s] and sm.freq_ok.get(s, True))]
details = [f"{s}(a={sm.alive[s]},v={sm.valid[s]},f={sm.freq_ok[s]})" for s in bad]
print(f"CLP liveParameters valid=False: {' '.join(details)}", file=sys.stderr, flush=True)
dbg_prev_valid = lp_valid
msg.valid = lp_valid
msg.valid = sm.all_checks()
if sm.frame % 1200 == 0: # once a minute
params = {
+1 -11
View File
@@ -226,8 +226,6 @@ def main(demo=False):
with car.CarParams.from_bytes(params.get("CarParams", block=True)) as CP:
estimator = TorqueEstimator(CP)
dbg_prev_valid = True # CLEARPILOT: track valid transitions
while True:
sm.update()
if sm.all_checks():
@@ -238,15 +236,7 @@ def main(demo=False):
# 4Hz driven by liveLocationKalman
if sm.frame % 5 == 0:
tq_valid = sm.all_checks()
# CLEARPILOT: log per-sub detail on transition to invalid — goes to torqued.log
if tq_valid != dbg_prev_valid and not tq_valid:
import sys
bad = [s for s in sm.alive if not (sm.alive[s] and sm.valid[s] and sm.freq_ok.get(s, True))]
details = [f"{s}(a={sm.alive[s]},v={sm.valid[s]},f={sm.freq_ok[s]})" for s in bad]
print(f"CLP liveTorqueParameters valid=False: {' '.join(details)}", file=sys.stderr, flush=True)
dbg_prev_valid = tq_valid
pm.send('liveTorqueParameters', estimator.get_msg(valid=tq_valid))
pm.send('liveTorqueParameters', estimator.get_msg(valid=sm.all_checks()))
# Cache points every 60 seconds while onroad
if sm.frame % 240 == 0:
+9 -11
View File
@@ -7,7 +7,7 @@ import ctypes
import numpy as np
from pathlib import Path
from cereal import messaging
from cereal import car, messaging
from cereal.messaging import PubMaster, SubMaster
from cereal.visionipc import VisionIpcClient, VisionStreamType, VisionBuf
from openpilot.common.swaglog import cloudlog
@@ -132,9 +132,10 @@ def main():
pm = PubMaster(["driverStateV2"])
calib = np.zeros(CALIB_LEN, dtype=np.float32)
# CLEARPILOT: cache last model output so we can republish (not re-infer) at standstill.
# Saves ~7% CPU; downstream dmonitoringd sees a steady 10Hz stream with known-good
# last readings (driver can't become distracted relative to a stopped car).
# CLEARPILOT: cache last model output to serve while gear is in park —
# mirrors the same trick modeld uses. Skips DSP inference on the driver
# camera when the car is stationary; downstream dmonitoringd still gets
# a fresh publish each frame.
last_model_output = None
# last = 0
@@ -147,16 +148,13 @@ def main():
if sm.updated["liveCalibration"]:
calib[:] = np.array(sm["liveCalibration"].rpyCalib)
standstill = sm["carState"].standstill
parked = sm["carState"].gearShifter == car.CarState.GearShifter.park
t1 = time.perf_counter()
if standstill and last_model_output is not None:
# CLEARPILOT: reuse last inference at standstill
model_output = last_model_output
dsp_execution_time = 0.0
if parked and last_model_output is not None:
model_output, dsp_execution_time = last_model_output
else:
model_output, dsp_execution_time = model.run(buf, calib)
last_model_output = model_output
last_model_output = (model_output, dsp_execution_time)
t2 = time.perf_counter()
pm.send("driverStateV2", get_driverstate_packet(model_output, vipc_client.frame_id, vipc_client.timestamp_sof, t2 - t1, dsp_execution_time))
+17 -28
View File
@@ -183,9 +183,10 @@ def main(demo=False):
model_transform_main = np.zeros((3, 3), dtype=np.float32)
model_transform_extra = np.zeros((3, 3), dtype=np.float32)
live_calib_seen = False
model_standby = False
last_standby_ts_write = 0
params_memory = Params("/dev/shm/params")
# CLEARPILOT: cache last model output to serve while gear is in park — saves
# GPU inference cost while still giving downstream a constant publish rate so
# freq_ok / valid checks don't cascade.
last_model_output = None
nav_features = np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32)
nav_instructions = np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32)
buf_main, buf_extra = None, None
@@ -241,31 +242,10 @@ def main(demo=False):
sm.update(0)
# CLEARPILOT: two-state modeld — 0fps only when parked (ignition-on means
# engine running in park; ignition-off stops modeld at the manager level).
# Standstill in drive (red light) keeps running so lateral stays responsive
# and liveCalibration/paramsd observations continue.
parked = sm['carState'].gearShifter == car.CarState.GearShifter.park
should_standby = parked
if should_standby and not model_standby:
params_memory.put_bool("ModelStandby", True)
params_memory.put("ModelFps", "0")
model_standby = True
cloudlog.warning("modeld: standby ON")
elif not should_standby and model_standby:
params_memory.put_bool("ModelStandby", False)
params_memory.put("ModelFps", "20")
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
# CLEARPILOT: constant 20fps. Variable-rate + standby logic removed — the
# variable-rate path caused freq_ok cascades in downstream consumers
# (calibrationd/locationd/paramsd). Running at the camera's native rate is
# simpler and keeps the full-stack localization chain happy.
desire = DH.desire
is_rhd = sm["driverMonitoringState"].isRHD
@@ -338,12 +318,21 @@ def main(demo=False):
**({'radar_tracks': radar_tracks,} if DISABLE_RADAR else {}),
}
# CLEARPILOT: in park, serve the cached last model output instead of running
# GPU inference. First cycle (no cache yet) still runs once so we have
# something to serve. Out-of-park resumes fresh inference every frame.
parked = sm['carState'].gearShifter == car.CarState.GearShifter.park
if parked and last_model_output is not None:
model_output = last_model_output
model_execution_time = 0.0
else:
mt1 = time.perf_counter()
model_output = model.run(buf_main, buf_extra, model_transform_main, model_transform_extra, inputs, prepare_only)
mt2 = time.perf_counter()
model_execution_time = mt2 - mt1
if model_output is not None:
last_model_output = model_output
modelv2_send = messaging.new_message('modelV2')
posenet_send = messaging.new_message('cameraOdometry')
fill_model_msg(modelv2_send, model_output, publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id, frame_drop_ratio,