modeld: revert to constant 20fps (except standby at standstill)
Some checks failed
prebuilt / build prebuilt (push) Has been cancelled
badges / create badges (push) Has been cancelled

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>
This commit is contained in:
2026-04-17 18:21:46 -05:00
parent 5b67d4798b
commit cf2b3fc637
3 changed files with 120 additions and 27 deletions

View File

@@ -186,6 +186,11 @@ def main(demo=False):
model_standby = False
last_standby_ts_write = 0
params_memory = Params("/dev/shm/params")
# CLEARPILOT: cache last model output for republishing on skip cycles. Keeps downstream
# cameraOdometry/modelV2 rate constant at 20Hz so services.py freq checks never fail
# during reduced-rate mode. Content is stale but we only reduce rate when not engaged,
# so no one is actually using it for control.
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,22 +246,19 @@ def main(demo=False):
sm.update(0)
# CLEARPILOT: power saving — three modes based on driving state
# Full 20fps: lat requested or lane changing
# Reduced 4fps: not lat requested, not standstill (driving without cruise)
# Standby 0fps: standstill
# Uses FPCC.latRequested (not carControl.latActive) so modeld ramps to 20fps BEFORE
# controlsd actually engages steering — gives downstream services time to stabilize.
# CLEARPILOT: both fields migrated from paramsMemory to cereal (no per-cycle file reads)
# CLEARPILOT: reverted to two modes only — constant 20fps while moving, standby at
# standstill. Variable-rate (4/10fps) caused downstream freq/valid cascades that
# broke paramsd's angleOffset learner, calibrationd, and locationd's filter init.
# Keep the signals plumbed through frogpilotCarControl for when we re-enable.
fpcc = sm['frogpilotCarControl']
lat_active = fpcc.latRequested
lane_changing = fpcc.noLatLaneChange
lat_active = fpcc.latRequested # noqa: F841
lane_changing = fpcc.noLatLaneChange # noqa: F841
standstill = sm['carState'].standstill
calibrating = sm['liveCalibration'].calStatus != log.LiveCalibrationData.Status.calibrated
full_rate = lat_active or lane_changing or calibrating
calibrating = sm['liveCalibration'].calStatus != log.LiveCalibrationData.Status.calibrated # noqa: F841
full_rate = True # always 20fps when not standstill
# Standby transitions (standstill only)
should_standby = standstill and not full_rate
# Standby transitions: standstill → 0fps (parked), otherwise → 20fps
should_standby = standstill
if should_standby and not model_standby:
params_memory.put_bool("ModelStandby", True)
model_standby = True
@@ -275,10 +277,12 @@ def main(demo=False):
last_vipc_frame_id = meta_main.frame_id
continue
# Reduced framerate: daylight 10fps (skip 1/2), night 4fps (skip 4/5)
# Daytime runs twice as fast — better model responsiveness when lighting is good
# and the neural net has more signal. Night stays conservative for power.
# Write standby timestamp so controlsd suppresses transient errors
# CLEARPILOT: reduced framerate: skip GPU inference on most frames but still
# republish cached output at full 20Hz so downstream services never see a rate
# drop (avoids freq_ok → valid cascade that causes "Communication Issue" false
# positives on engage). Daylight: skip 1/2 (compute at 10fps), night: skip 4/5
# (compute at 4fps). ModelStandbyTs still written for model_suppress window.
republish_only = False
if not full_rate:
is_daylight = params_memory.get_bool("IsDaylight")
skip_interval = 2 if is_daylight else 5
@@ -290,9 +294,7 @@ def main(demo=False):
params_memory.put("ModelStandbyTs", str(now))
last_standby_ts_write = now
if run_count % skip_interval != 0:
last_vipc_frame_id = meta_main.frame_id
run_count += 1
continue
republish_only = True
else:
if params_memory.get("ModelFps") != b"20":
params_memory.put("ModelFps", "20")
@@ -368,12 +370,21 @@ def main(demo=False):
**({'radar_tracks': radar_tracks,} if DISABLE_RADAR 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
# CLEARPILOT: on republish cycles, skip the GPU inference and reuse the last
# model output. Publish rate stays at 20Hz, compute rate is reduced.
if republish_only 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:
# cache for next republish cycle
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,