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>
This commit is contained in:
2026-04-19 13:26:36 -05:00
parent 62a403d0f1
commit 12da9acfdd
7 changed files with 94 additions and 289 deletions
+62 -204
View File
@@ -38,7 +38,6 @@ from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import CRUIS
from openpilot.selfdrive.frogpilot.controls.lib.model_manager import RADARLESS_MODELS from openpilot.selfdrive.frogpilot.controls.lib.model_manager import RADARLESS_MODELS
from openpilot.selfdrive.clearpilot.telemetry import tlog from openpilot.selfdrive.clearpilot.telemetry import tlog
from openpilot.selfdrive.clearpilot.speed_logic import SpeedState
from openpilot.selfdrive.frogpilot.controls.lib.speed_limit_controller import SpeedLimitController from openpilot.selfdrive.frogpilot.controls.lib.speed_limit_controller import SpeedLimitController
SOFT_DISABLE_TIME = 3 # seconds SOFT_DISABLE_TIME = 3 # seconds
@@ -81,10 +80,6 @@ class Controls:
self.params_memory.put_bool("CPTLkasButtonAction", False) self.params_memory.put_bool("CPTLkasButtonAction", False)
self.params_memory.put_int("ScreenDisplayMode", 0) self.params_memory.put_int("ScreenDisplayMode", 0)
# ClearPilot speed processing
self.speed_state = SpeedState()
self.speed_state_frame = 0
self.radarless_model = self.params.get("Model", encoding='utf-8') in RADARLESS_MODELS self.radarless_model = self.params.get("Model", encoding='utf-8') in RADARLESS_MODELS
with car.CarParams.from_bytes(self.params.get("CarParams", block=True)) as msg: with car.CarParams.from_bytes(self.params.get("CarParams", block=True)) as msg:
@@ -113,9 +108,8 @@ class Controls:
self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration', self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration',
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'liveLocationKalman', 'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'liveLocationKalman',
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters', 'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
'testJoystick', 'frogpilotPlan', 'gpsLocation'] + self.camera_packets + self.sensor_packets, 'testJoystick', 'frogpilotPlan'] + self.camera_packets + self.sensor_packets,
ignore_alive=ignore+['gpsLocation'], ignore_avg_freq=ignore+['radarState', 'testJoystick', 'gpsLocation'], ignore_alive=ignore, ignore_avg_freq=ignore+['radarState', 'testJoystick'], ignore_valid=['testJoystick', ],
ignore_valid=['testJoystick', 'gpsLocation'],
frequency=int(1/DT_CTRL)) frequency=int(1/DT_CTRL))
self.joystick_mode = self.params.get_bool("JoystickDebugMode") self.joystick_mode = self.params.get_bool("JoystickDebugMode")
@@ -170,26 +164,6 @@ class Controls:
self.current_alert_types = [ET.PERMANENT] self.current_alert_types = [ET.PERMANENT]
self.logged_comm_issue = None self.logged_comm_issue = None
self.not_running_prev = 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.steer_limited = False
self.desired_curvature = 0.0 self.desired_curvature = 0.0
self.experimental_mode = False self.experimental_mode = False
@@ -224,8 +198,6 @@ class Controls:
self.always_on_lateral_main = self.always_on_lateral and self.params.get_bool("AlwaysOnLateralMain") self.always_on_lateral_main = self.always_on_lateral and self.params.get_bool("AlwaysOnLateralMain")
self.drive_added = False self.drive_added = False
self.driving_gear = False
self.was_driving_gear = False
self.fcw_random_event_triggered = False self.fcw_random_event_triggered = False
self.holiday_theme_alerted = False self.holiday_theme_alerted = False
self.onroad_distance_pressed = False self.onroad_distance_pressed = False
@@ -264,51 +236,27 @@ class Controls:
CS = self.data_sample() CS = self.data_sample()
cloudlog.timestamp("Data sampled") cloudlog.timestamp("Data sampled")
# CLEARPILOT: handle debug-button press + display-mode transitions on every self.update_events(CS)
# cycle — button edge events only live in the cycle's CS.buttonEvents and self.update_frogpilot_events(CS)
# would otherwise be dropped on skipped cycles. self.update_clearpilot_events(CS)
self.handle_screen_mode(CS)
# CLEARPILOT: in park, only run the full step() every 10th cycle. data_sample cloudlog.timestamp("Events updated")
# 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: if not self.CP.passive and self.initialized:
self.update_events(CS) # Update control state
self.update_frogpilot_events(CS) self.state_transition(CS)
self.update_clearpilot_events(CS)
cloudlog.timestamp("Events updated") # Compute actuators (runs PID loops and lateral MPC)
CC, lac_log = self.state_control(CS)
CC = self.clearpilot_state_control(CC, CS)
if not self.CP.passive and self.initialized: # Publish data
# Update control state self.publish_logs(CS, start_time, CC, lac_log)
self.state_transition(CS)
# Compute actuators (runs PID loops and lateral MPC) self.CS_prev = CS
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)
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
# Update FrogPilot variables
self.update_frogpilot_variables(CS)
def data_sample(self): def data_sample(self):
"""Receive data from sockets and update carState""" """Receive data from sockets and update carState"""
@@ -499,18 +447,7 @@ class Controls:
standby_ts = float(self.params_memory.get("ModelStandbyTs") or "0") standby_ts = float(self.params_memory.get("ModelStandbyTs") or "0")
except (ValueError, TypeError): except (ValueError, TypeError):
standby_ts = 0 standby_ts = 0
now = time.monotonic() model_suppress = (time.monotonic() - standby_ts) < 2.0
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()
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):
@@ -524,14 +461,9 @@ class Controls:
self.events.add(EventName.cameraMalfunction) self.events.add(EventName.cameraMalfunction)
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)
# CLEARPILOT: alert only when avg cycle time exceeds 20ms (≈50Hz effective). if not REPLAY and self.rk.lagging:
# Stock rk.lagging fires at 11.1ms (90% of 10ms) which the Hyundai CAN load import sys
# routinely crosses while driving — that's normal, not a fault. 50Hz control is print(f"CLP controlsdLagging: remaining={self.rk.remaining:.4f} standstill={CS.standstill} vEgo={CS.vEgo:.2f}", file=sys.stderr)
# 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:
self.events.add(EventName.controlsdLagging) self.events.add(EventName.controlsdLagging)
if not self.radarless_model: 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']))): if not model_suppress and (len(self.sm['radarState'].radarErrors) or (not self.rk.lagging and not self.sm.all_checks(['radarState']))):
@@ -546,60 +478,34 @@ 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)
# CLEARPILOT: commIssue fires ONLY when messages actually aren't flowing. If all if (not self.sm.all_checks() or self.card.can_rcv_timeout) and no_system_errors and not model_suppress:
# subscribers are alive and CAN isn't timing out, comms are fine — any `valid=False` if not self.sm.all_alive():
# is an upstream self-declaration that cascades from the MSGQ conflate + self.events.add(EventName.commIssue)
# slow-polling-consumer freq_ok measurement artifact. The car drives correctly elif not self.sm.all_freq_ok():
# during these cascades (content is still usable), so we stop raising the banner. self.events.add(EventName.commIssueAvgFreq)
# Genuine comms failures — missing messages or CAN RX timeout — still fire. else: # invalid or can_rcv_timeout.
comms_really_broken = (not self.sm.all_alive()) or self.card.can_rcv_timeout self.events.add(EventName.commIssue)
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:
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).
logs = { logs = {
'invalid': [s for s, valid in self.sm.valid.items() if not valid], '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_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, 'can_rcv_timeout': self.card.can_rcv_timeout,
} }
if logs != self.logged_comm_issue: if logs != self.logged_comm_issue:
import sys, json cloudlog.event("commIssue", error=True, **logs)
print("CLP commIssue " + json.dumps(logs), file=sys.stderr, flush=True)
self.logged_comm_issue = logs self.logged_comm_issue = logs
else: else:
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):
# CLEARPILOT: suppress locationd/paramsd "temporary error" when we're in the if not self.sm['liveLocationKalman'].posenetOK and not model_suppress:
# freq-only cascade (same root as the commIssue suppression above). These events self.events.add(EventName.posenetInvalid)
# 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'].deviceStable: if not self.sm['liveLocationKalman'].deviceStable:
self.events.add(EventName.deviceFalling) self.events.add(EventName.deviceFalling)
if not self.sm['liveLocationKalman'].inputsOK and not model_suppress:
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:
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) and not model_suppress:
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:
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
@@ -613,8 +519,16 @@ class Controls:
if self.cruise_mismatch_counter > int(6. / DT_CTRL): if self.cruise_mismatch_counter > int(6. / DT_CTRL):
self.events.add(EventName.cruiseMismatch) self.events.add(EventName.cruiseMismatch)
# CLEARPILOT: FCW disabled — car's own radar AEB works better and triggers reliably. # Check for FCW
# The comma FCW was producing false positives and adds nothing over the stock system. 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): for m in messaging.drain_sock(self.log_sock, wait_for_one=False):
try: try:
@@ -762,19 +676,8 @@ class Controls:
# Check which actuators can be enabled # Check which actuators can be enabled
standstill = CS.vEgo <= max(self.CP.minSteerSpeed, MIN_LATERAL_CONTROL_SPEED) or CS.standstill 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 CC.latActive = (self.active or self.FPCC.alwaysOnLateral) and self.speed_check and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \
# brief steerFaultTemporary blips (stale predictions on recovery caused a fault feedback loop). (not standstill or self.joystick_mode)
# 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 \
(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 CC.longActive = self.enabled and not self.events.contains(ET.OVERRIDE_LONGITUDINAL) and self.CP.openpilotLongitudinalControl
actuators = CC.actuators actuators = CC.actuators
@@ -789,13 +692,11 @@ class Controls:
CC.leftBlinker = model_v2.meta.laneChangeDirection == LaneChangeDirection.left CC.leftBlinker = model_v2.meta.laneChangeDirection == LaneChangeDirection.left
CC.rightBlinker = model_v2.meta.laneChangeDirection == LaneChangeDirection.right 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 model_v2.meta.laneChangeState == LaneChangeState.laneChangeStarting and clearpilot_disable_lat_on_lane_change:
if no_lat_lane_change:
CC.latActive = False CC.latActive = False
# CLEARPILOT: noLatLaneChange now lives on frogpilotCarControl (see update_frogpilot_variables). self.params_memory.put_bool("no_lat_lane_change", True)
# Also mirrored to frogpilot_variables so in-process carcontroller reads it without IPC. else:
self.FPCC.noLatLaneChange = no_lat_lane_change self.params_memory.put_bool("no_lat_lane_change", False)
self.frogpilot_variables.no_lat_lane_change = no_lat_lane_change
if CS.leftBlinker or CS.rightBlinker: if CS.leftBlinker or CS.rightBlinker:
self.last_blinker_frame = self.sm.frame self.last_blinker_frame = self.sm.frame
@@ -1031,8 +932,6 @@ class Controls:
controlsState.lateralControlState.torqueState = lac_log controlsState.lateralControlState.torqueState = lac_log
self.pm.send('controlsState', dat) 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 # onroadEvents - logged every second or on change
if (self.sm.frame % int(1. / DT_CTRL) == 0) or (self.events.names != self.events_prev): if (self.sm.frame % int(1. / DT_CTRL) == 0) or (self.events.names != self.events_prev):
@@ -1047,7 +946,6 @@ class Controls:
cc_send.valid = CS.canValid cc_send.valid = CS.canValid
cc_send.carControl = CC cc_send.carControl = CC
self.pm.send('carControl', cc_send) self.pm.send('carControl', cc_send)
self._cached_carControl_bytes = cc_send.to_bytes()
# copy CarControl to pass to CarInterface on the next iteration # copy CarControl to pass to CarInterface on the next iteration
self.CC = CC self.CC = CC
@@ -1078,11 +976,6 @@ class Controls:
while True: while True:
self.step() self.step()
self.rk.monitor_time() 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: except SystemExit:
e.set() e.set()
t.join() t.join()
@@ -1153,11 +1046,13 @@ class Controls:
elif self.sm['modelV2'].meta.turnDirection == Desire.turnRight: elif self.sm['modelV2'].meta.turnDirection == Desire.turnRight:
self.events.add(EventName.turningRight) self.events.add(EventName.turningRight)
# CLEARPILOT: check crash file once per second, not every cycle (stat syscall at 100Hz hurts) if os.path.isfile(os.path.join(sentry.CRASHES_DIR, 'error.txt')) and self.crashed_timer < 10:
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:
self.events.add(EventName.openpilotCrashed) 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 self.crashed_timer += DT_CTRL
# if self.speed_limit_alert or self.speed_limit_confirmation: # if self.speed_limit_alert or self.speed_limit_confirmation:
@@ -1280,18 +1175,10 @@ class Controls:
self.FPCC.trafficModeActive = self.frogpilot_variables.traffic_mode and self.params_memory.get_bool("TrafficModeActive") 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 fpcc_send = messaging.new_message('frogpilotCarControl')
# Saves ~7ms/sec of capnp+zmq work (was sending 100Hz despite static contents) fpcc_send.valid = CS.canValid
# latRequested and noLatLaneChange are edge-triggered too (rare flips) fpcc_send.frogpilotCarControl = self.FPCC
fpcc_tuple = (self.FPCC.alwaysOnLateral, self.FPCC.speedLimitChanged, self.FPCC.trafficModeActive, self.pm.send('frogpilotCarControl', fpcc_send)
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"): if self.params_memory.get_bool("FrogPilotTogglesUpdated"):
self.update_frogpilot_params() self.update_frogpilot_params()
@@ -1369,19 +1256,7 @@ class Controls:
if any(be.pressed and be.type == FrogPilotButtonType.lkas for be in CS.buttonEvents): if any(be.pressed and be.type == FrogPilotButtonType.lkas for be in CS.buttonEvents):
self.events.add(EventName.clpDebug) self.events.add(EventName.clpDebug)
def handle_screen_mode(self, CS): def clearpilot_state_control(self, CC, 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
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)
self.was_driving_gear = self.driving_gear
if any(be.pressed and be.type == FrogPilotButtonType.lkas for be in CS.buttonEvents): if any(be.pressed and be.type == FrogPilotButtonType.lkas for be in CS.buttonEvents):
current = self.params_memory.get_int("ScreenDisplayMode") current = self.params_memory.get_int("ScreenDisplayMode")
@@ -1394,23 +1269,6 @@ class Controls:
new_mode = 0 if current == 3 else 3 new_mode = 0 if current == 3 else 3
self.params_memory.put_int("ScreenDisplayMode", new_mode) self.params_memory.put_int("ScreenDisplayMode", new_mode)
def clearpilot_state_control(self, CC, CS):
# ClearPilot speed processing (~2 Hz at 100 Hz loop)
self.speed_state_frame += 1
if self.speed_state_frame % 50 == 0:
gps = self.sm['gpsLocation']
has_gps = self.sm.valid['gpsLocation'] and gps.hasFix
speed_ms = gps.speed if has_gps else 0.0
speed_limit_ms = self.params_memory.get_float("CarSpeedLimit")
is_metric = (self.params_memory.get("CarIsMetric", encoding="utf-8") or "0") == "1"
cruise_speed_ms = CS.cruiseState.speed
cruise_active = CS.cruiseState.enabled
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 return CC
def main(): def main():
+2 -33
View File
@@ -143,10 +143,6 @@ class LongitudinalPlanner:
self.params = Params() self.params = Params()
self.params_memory = Params("/dev/shm/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.radarless_model = self.params.get("Model", encoding='utf-8') in RADARLESS_MODELS
self.release = get_short_branch() == "clearpilot" self.release = get_short_branch() == "clearpilot"
@@ -177,19 +173,6 @@ class LongitudinalPlanner:
return x, v, a, j return x, v, a, j
def update(self, sm): 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' self.mpc.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc'
v_ego = sm['carState'].vEgo 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) 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 # 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), self.fcw = self.mpc.crash_cnt > 2 and not sm['carState'].standstill
# 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
if self.fcw: if self.fcw:
cloudlog.info("FCW triggered") cloudlog.info("FCW triggered")
@@ -278,18 +258,7 @@ class LongitudinalPlanner:
def publish(self, sm, pm): def publish(self, sm, pm):
plan_send = messaging.new_message('longitudinalPlan') plan_send = messaging.new_message('longitudinalPlan')
valid = sm.all_checks(service_list=['carState', 'controlsState']) plan_send.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
longitudinalPlan = plan_send.longitudinalPlan longitudinalPlan = plan_send.longitudinalPlan
longitudinalPlan.modelMonoTime = sm.logMonoTime['modelV2'] longitudinalPlan.modelMonoTime = sm.logMonoTime['modelV2']
+1 -8
View File
@@ -284,14 +284,7 @@ def main() -> NoReturn:
# 4Hz driven by cameraOdometry # 4Hz driven by cameraOdometry
if sm.frame % 5 == 0: if sm.frame % 5 == 0:
# CLEARPILOT: publish valid based on calibration status, not upstream sm.all_checks(). calibrator.send_data(pm, 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.
cal_valid = calibrator.cal_status == log.LiveCalibrationData.Status.calibrated
calibrator.send_data(pm, cal_valid)
if __name__ == "__main__": if __name__ == "__main__":
+1 -10
View File
@@ -187,7 +187,6 @@ def main():
avg_offset_valid = True avg_offset_valid = True
total_offset_valid = True total_offset_valid = True
roll_valid = True roll_valid = True
dbg_prev_valid = True # CLEARPILOT: track valid transitions
while True: while True:
sm.update() sm.update()
@@ -257,15 +256,7 @@ def main():
liveParameters.filterState.std = P.tolist() liveParameters.filterState.std = P.tolist()
liveParameters.filterState.valid = True liveParameters.filterState.valid = True
lp_valid = sm.all_checks() msg.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
if sm.frame % 1200 == 0: # once a minute if sm.frame % 1200 == 0: # once a minute
params = { 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: with car.CarParams.from_bytes(params.get("CarParams", block=True)) as CP:
estimator = TorqueEstimator(CP) estimator = TorqueEstimator(CP)
dbg_prev_valid = True # CLEARPILOT: track valid transitions
while True: while True:
sm.update() sm.update()
if sm.all_checks(): if sm.all_checks():
@@ -238,15 +236,7 @@ def main(demo=False):
# 4Hz driven by liveLocationKalman # 4Hz driven by liveLocationKalman
if sm.frame % 5 == 0: if sm.frame % 5 == 0:
tq_valid = sm.all_checks() pm.send('liveTorqueParameters', estimator.get_msg(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))
# Cache points every 60 seconds while onroad # Cache points every 60 seconds while onroad
if sm.frame % 240 == 0: if sm.frame % 240 == 0:
+2 -14
View File
@@ -128,14 +128,10 @@ def main():
assert vipc_client.is_connected() assert vipc_client.is_connected()
cloudlog.warning(f"connected with buffer size: {vipc_client.buffer_len}") cloudlog.warning(f"connected with buffer size: {vipc_client.buffer_len}")
sm = SubMaster(["liveCalibration", "carState"]) sm = SubMaster(["liveCalibration"])
pm = PubMaster(["driverStateV2"]) pm = PubMaster(["driverStateV2"])
calib = np.zeros(CALIB_LEN, dtype=np.float32) 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).
last_model_output = None
# last = 0 # last = 0
while True: while True:
@@ -147,16 +143,8 @@ def main():
if sm.updated["liveCalibration"]: if sm.updated["liveCalibration"]:
calib[:] = np.array(sm["liveCalibration"].rpyCalib) calib[:] = np.array(sm["liveCalibration"].rpyCalib)
standstill = sm["carState"].standstill
t1 = time.perf_counter() t1 = time.perf_counter()
if standstill and last_model_output is not None: model_output, dsp_execution_time = model.run(buf, calib)
# CLEARPILOT: reuse last inference at standstill
model_output = last_model_output
dsp_execution_time = 0.0
else:
model_output, dsp_execution_time = model.run(buf, calib)
last_model_output = model_output
t2 = time.perf_counter() 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)) pm.send("driverStateV2", get_driverstate_packet(model_output, vipc_client.frame_id, vipc_client.timestamp_sof, t2 - t1, dsp_execution_time))
+25 -9
View File
@@ -241,20 +241,23 @@ def main(demo=False):
sm.update(0) sm.update(0)
# CLEARPILOT: two-state modeld — 0fps only when parked (ignition-on means # CLEARPILOT: power saving — three modes based on driving state
# engine running in park; ignition-off stops modeld at the manager level). # Full 20fps: lat active or lane changing
# Standstill in drive (red light) keeps running so lateral stays responsive # Reduced 4fps: not lat active, not standstill (driving without cruise)
# and liveCalibration/paramsd observations continue. # Standby 0fps: standstill
parked = sm['carState'].gearShifter == car.CarState.GearShifter.park lat_active = sm['carControl'].latActive
should_standby = parked 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: if should_standby and not model_standby:
params_memory.put_bool("ModelStandby", True) params_memory.put_bool("ModelStandby", True)
params_memory.put("ModelFps", "0")
model_standby = True model_standby = True
cloudlog.warning("modeld: standby ON") cloudlog.warning("modeld: standby ON (standstill)")
elif not should_standby and model_standby: elif not should_standby and model_standby:
params_memory.put_bool("ModelStandby", False) params_memory.put_bool("ModelStandby", False)
params_memory.put("ModelFps", "20")
model_standby = False model_standby = False
run_count = 0 run_count = 0
frame_dropped_filter.x = 0. frame_dropped_filter.x = 0.
@@ -267,6 +270,19 @@ def main(demo=False):
last_vipc_frame_id = meta_main.frame_id last_vipc_frame_id = meta_main.frame_id
continue 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