Files
StarPilot/selfdrive/controls/lib/longitudinal_planner.py
T
2026-06-20 18:04:39 -05:00

3042 lines
139 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
import math
import numpy as np
import time
import cereal.messaging as messaging
from opendbc.car.interfaces import ACCEL_MIN, ACCEL_MAX
from openpilot.common.constants import CV
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.realtime import DT_MDL
from openpilot.selfdrive.modeld.constants import ModelConstants
from openpilot.starpilot.common.model_versions import is_tinygrad_model_version
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import desired_follow_distance
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import should_trigger_planner_fcw
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import STOP_DISTANCE
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC
from openpilot.selfdrive.controls.lib.lead_behavior import is_radarless_matched_follow_window
from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N
from openpilot.selfdrive.car.cruise import V_CRUISE_UNSET
from openpilot.common.swaglog import cloudlog
LON_MPC_STEP = 0.2 # first step is 0.2s
A_CRUISE_MIN = -1.0
A_CRUISE_MAX_BP = [0.0, 5., 10., 15., 20., 25., 40.]
A_CRUISE_MAX_VALS = [1.125, 1.125, 1.125, 1.125, 1.25, 1.25, 1.5]
CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
ALLOW_THROTTLE_THRESHOLD = 0.4
ALLOW_THROTTLE_HYSTERESIS = 0.05
ALLOW_THROTTLE_ENABLE_THRESHOLD = ALLOW_THROTTLE_THRESHOLD + ALLOW_THROTTLE_HYSTERESIS
ALLOW_THROTTLE_DISABLE_THRESHOLD = ALLOW_THROTTLE_THRESHOLD - ALLOW_THROTTLE_HYSTERESIS
MIN_ALLOW_THROTTLE_SPEED = 5.0
RAW_LEAD_SAFETY_MIN_CLOSING_SPEED = 0.5
RAW_LEAD_SAFETY_TTC = 7.0
RAW_LEAD_SAFETY_DISTANCE = 40.0
RAW_LEAD_LOW_SPEED_HOLD_MAX_EGO_SPEED = 4.5
RAW_LEAD_LOW_SPEED_HOLD_MAX_LEAD_SPEED = 3.5
RAW_LEAD_LOW_SPEED_HOLD_MAX_DISTANCE = 10.0
RAW_LEAD_LOW_SPEED_HOLD_MAX_LATERAL_OFFSET = 1.75
RAW_LEAD_LOW_SPEED_HOLD_MIN_CLOSING_SPEED = 0.15
STANDSTILL_LEAD_NUDGE_ACCEL = 0.05
STANDSTILL_LEAD_NUDGE_MIN_SPEED = 0.0
STANDSTILL_LEAD_NUDGE_MIN_LEAD_ACCEL = 0.2
STANDSTILL_LEAD_DEPART_MIN_ACCEL = 0.35
STANDSTILL_LEAD_DEPART_MAX_EGO_SPEED = 1.5
STANDSTILL_LEAD_DEPART_MIN_LEAD_SPEED = 0.6
STANDSTILL_LEAD_DEPART_MIN_GAP_MARGIN = 0.8
STANDSTILL_LEAD_DEPART_MIN_MODEL_ACCEL = 0.08
LEAD_DEPART_CONFIDENT_MIN_GAP = 3.75
LEAD_DEPART_CONFIDENT_MAX_GAP = 5.25
LEAD_DEPART_CONFIDENT_MIN_LEAD_SPEED = 0.3
LEAD_DEPART_CONFIDENT_MIN_LEAD_DELTA = 0.25
LEAD_DEPART_CONFIDENT_MIN_LEAD_ACCEL = 0.2
LEAD_DEPART_CONFIDENT_CONFIRM_TIME = 0.35
STANDSTILL_STOPPED_LEAD_GUARD_MAX_EGO_SPEED = 0.5
STANDSTILL_STOPPED_LEAD_GUARD_MAX_LEAD_SPEED = 0.45
STANDSTILL_STOPPED_LEAD_GUARD_MAX_LEAD_DELTA = 0.35
STANDSTILL_STOPPED_LEAD_GUARD_MIN_MODEL_PROB = 0.95
STANDSTILL_STOPPED_LEAD_GUARD_MAX_LATERAL_OFFSET = 1.75
STANDSTILL_STOPPED_LEAD_GUARD_MIN_DISTANCE = 3.0
STANDSTILL_STOPPED_LEAD_GUARD_DISTANCE_MARGIN = 3.0
STANDSTILL_STOPPED_LEAD_GUARD_MIN_BRAKE = 0.16
STANDSTILL_STOPPED_LEAD_GUARD_MAX_BRAKE = 0.26
RADAR_DEPART_CONFLICT_MAX_EGO_SPEED = 1.6
RADAR_DEPART_CONFLICT_MIN_RADAR_LATERAL = 1.5
RADAR_DEPART_CONFLICT_MAX_RADAR_DISTANCE = 18.0
RADAR_DEPART_CONFLICT_MIN_MODEL_PROB = 0.95
RADAR_DEPART_CONFLICT_MAX_MODEL_DISTANCE = 18.0
RADAR_DEPART_CONFLICT_MAX_MODEL_LATERAL = 0.9
RADAR_DEPART_CONFLICT_MAX_MODEL_LEAD_SPEED = 2.0
RADAR_DEPART_CONFLICT_MAX_DISTANCE_MISMATCH = 4.0
LEAD_DEPART_ACCEL_HOLD_TIME = 1.2
LEAD_DEPART_ACCEL_HOLD_MAX_EGO_SPEED = 1.5
LEAD_DEPART_ACCEL_HOLD_MIN_LEAD_SPEED = 0.6
LEAD_DEPART_ACCEL_HOLD_MIN_LEAD_DELTA = 0.5
LEAD_DEPART_ACCEL_HOLD_MIN_GAP = 3.5
LEAD_DEPART_ACCEL_HOLD_FULL_GAP = 6.0
LEAD_DEPART_ACCEL_HOLD_FULL_LEAD_SPEED = 2.2
LEAD_DEPART_ACCEL_HOLD_MIN_MODEL_PROB = 0.85
LEAD_DEPART_ACCEL_HOLD_MIN_MODEL_ACCEL = 0.12
LEAD_DEPART_ACCEL_HOLD_MAX_LEAD_BRAKE = 0.2
LEAD_DEPART_ACCEL_HOLD_MIN_ACCEL = 0.25
LEAD_DEPART_ACCEL_HOLD_MAX_ACCEL = 0.45
LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MAX_EGO_SPEED = 4.5
LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MIN_DISTANCE = 4.0
LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MAX_DISTANCE = 18.0
LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MAX_LEAD_SPEED = 4.0
LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MAX_LATERAL_OFFSET = 1.75
LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MIN_MODEL_PROB = 0.9
LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MIN_LEAD_DELTA = -0.5
LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MAX_LEAD_DELTA = 0.75
LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MIN_LEAD_ACCEL = -0.4
LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MAX_LEAD_ACCEL = 0.25
LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MIN_ACCEL = 0.08
LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MAX_ACCEL = 0.22
LOW_SPEED_WEAK_LEAD_ACCEL_CAP_STRONG_DEPART_MAX_EGO_SPEED = 1.25
LOW_SPEED_WEAK_LEAD_ACCEL_CAP_STRONG_DEPART_MIN_GAP = 4.0
LOW_SPEED_WEAK_LEAD_ACCEL_CAP_STRONG_DEPART_MIN_LEAD_SPEED = 1.2
LOW_SPEED_WEAK_LEAD_ACCEL_CAP_STRONG_DEPART_MIN_LEAD_DELTA = 0.8
LOW_SPEED_WEAK_LEAD_ACCEL_CAP_STRONG_DEPART_MIN_LEAD_ACCEL = 0.5
CLOSE_LEAD_BRAKE_CAP_MAX_TTC = 25.0
VISION_LEAD_APPROACH_MIN_CLOSING_SPEED = 2.0
VISION_LEAD_APPROACH_TRIGGER_TIME = 4.5
VISION_LEAD_APPROACH_FULL_TIME = 1.0
VISION_LEAD_APPROACH_TIGHT_BUFFER = 2.0
VISION_LEAD_APPROACH_MAX_DECEL = 0.80
VISION_LEAD_APPROACH_MIN_DECEL = 0.15
VISION_LEAD_APPROACH_MIN_MODEL_PROB = 0.85
VISION_LEAD_APPROACH_FULL_MODEL_PROB = 0.98
VISION_LEAD_APPROACH_DEFICIT_MAX_DECEL = 1.30
VISION_LEAD_APPROACH_DEFICIT_BUFFER_MIN = 3.0
VISION_LEAD_APPROACH_DEFICIT_BUFFER_GAIN = 0.20
VISION_LEAD_APPROACH_BRAKING_DEFICIT_MIN = 0.75
VISION_LEAD_APPROACH_BRAKING_MIN_LEAD_BRAKE = 0.45
VISION_LEAD_APPROACH_BRAKING_FULL_LEAD_BRAKE = 1.20
VISION_LEAD_APPROACH_BRAKING_FLOOR_MIN_DECEL = 1.30
VISION_LEAD_APPROACH_BRAKING_FLOOR_MAX_DECEL = 1.75
VISION_LEAD_APPROACH_CONFIRM_TIME = 0.25
VISION_LEAD_APPROACH_CONFIRM_BYPASS_DECEL = 1.0
VISION_LEAD_APPROACH_CONFIRM_BYPASS_CLOSING_SPEED = 4.0
VISION_LEAD_APPROACH_CONFIRM_BYPASS_LEAD_BRAKE = 0.20
VISION_LEAD_APPROACH_CONFIRM_BYPASS_DISTANCE_MIN = 28.0
VISION_LEAD_APPROACH_CONFIRM_BYPASS_DISTANCE_TIME = 0.85
VISION_UNTRACKED_SLOW_LEAD_MIN_MODEL_PROB = 0.9
VISION_UNTRACKED_SLOW_LEAD_FULL_MODEL_PROB = 0.97
VISION_UNTRACKED_SLOW_LEAD_MIN_CLOSING_SPEED = 3.0
VISION_UNTRACKED_SLOW_LEAD_MIN_CLOSING_RATIO = 0.16
VISION_UNTRACKED_SLOW_LEAD_FULL_CLOSING_RATIO = 0.24
VISION_UNTRACKED_SLOW_LEAD_TRIGGER_TTC = 16.0
VISION_UNTRACKED_SLOW_LEAD_FULL_TTC = 8.0
VISION_UNTRACKED_SLOW_LEAD_MAX_DISTANCE_TIME = 4.4
VISION_UNTRACKED_SLOW_LEAD_MIN_DISTANCE = 80.0
VISION_UNTRACKED_SLOW_LEAD_MAX_DISTANCE = 120.0
VISION_UNTRACKED_SLOW_LEAD_RELAXED_MAX_DISTANCE_TIME = 5.7
VISION_UNTRACKED_SLOW_LEAD_MAX_DECEL = 0.85
VISION_UNTRACKED_SLOW_LEAD_MIN_DECEL = 0.1
VISION_UNTRACKED_SLOW_LEAD_RELAXED_MODEL_PROB = 0.68
VISION_UNTRACKED_SLOW_LEAD_RELAXED_MAX_LEAD_SPEED = 8.0
VISION_UNTRACKED_SLOW_LEAD_RELAXED_MAX_TTC = 10.0
VISION_UNTRACKED_SLOW_LEAD_RELAXED_MIN_CLOSING_SPEED = 10.0
VISION_UNTRACKED_SLOW_LEAD_RELAXED_FULL_CLOSING_SPEED = 16.0
VISION_UNTRACKED_SLOW_LEAD_CONFIRM_TIME = 0.30
VISION_UNTRACKED_SLOW_LEAD_IMMEDIATE_DECEL = 0.55
VISION_UNTRACKED_SLOW_LEAD_IMMEDIATE_DISTANCE = 45.0
VISION_UNTRACKED_SLOW_LEAD_IMMEDIATE_LEAD_BRAKE = 0.10
VISION_SLOW_LEAD_MAX_SPEED = 5.0
VISION_SLOW_LEAD_MIN_CLOSING_SPEED = 1.5
VISION_SLOW_LEAD_TRIGGER_TTC = 4.5
VISION_SLOW_LEAD_FULL_TTC = 2.0
VISION_SLOW_LEAD_MAX_DECEL = 1.2
VISION_SLOW_LEAD_MIN_DECEL = 0.18
VISION_SLOW_LEAD_MIN_MODEL_PROB = 0.9
LEAD_APPROACH_TFOLLOW_TRIGGER_TIME = 4.5
LEAD_APPROACH_TFOLLOW_FULL_TIME = 1.5
LEAD_APPROACH_TFOLLOW_MAX_DELTA = 0.18
LEAD_APPROACH_TFOLLOW_MAX_CLOSING_SPEED = 6.0
LEAD_APPROACH_TFOLLOW_MAX_LEAD_BRAKE = 2.5
LEAD_APPROACH_TFOLLOW_MIN_CLOSING_SPEED = 0.75
LEAD_APPROACH_TFOLLOW_MIN_LEAD_BRAKE = 0.2
LEAD_APPROACH_TFOLLOW_WINDOW_MIN = 6.0
LEAD_APPROACH_TFOLLOW_WINDOW_GAIN = 0.35
LEAD_APPROACH_TFOLLOW_RATE_UP = 1.0
LEAD_APPROACH_TFOLLOW_RATE_DOWN = 0.60
VISION_LEAD_TFOLLOW_MAX_EXTRA_DELTA = 0.24
VISION_LEAD_TFOLLOW_SLOW_LEAD_SPEED = 20.0
VISION_LEAD_TFOLLOW_GAP_BUFFER_MIN = 8.0
VISION_LEAD_TFOLLOW_GAP_BUFFER_GAIN = 0.35
VISION_LOW_SPEED_STOP_BUFFER_MAX_EGO_SPEED = 6.5
VISION_LOW_SPEED_STOP_BUFFER_MAX_LEAD_SPEED = 3.25
VISION_LOW_SPEED_STOP_BUFFER_HOLD_MAX_LEAD_SPEED = 4.0
VISION_LOW_SPEED_STOP_BUFFER_MIN_MODEL_PROB = 0.9
VISION_LOW_SPEED_STOP_BUFFER_MIN_CLOSING_SPEED = 0.35
VISION_LOW_SPEED_STOP_BUFFER_MIN_HOLD_REL_SPEED = -0.2
VISION_LOW_SPEED_STOP_BUFFER_BASE = 3.8
VISION_LOW_SPEED_STOP_BUFFER_EGO_GAIN = 0.80
VISION_LOW_SPEED_STOP_BUFFER_LEAD_GAIN = 0.25
VISION_LOW_SPEED_STOP_BUFFER_RELEASE_MARGIN = 0.9
VISION_LOW_SPEED_STOP_BUFFER_HOLD_TIME = 0.8
VISION_LOW_SPEED_STOP_BUFFER_MIN_BRAKE = 1.25
VISION_LOW_SPEED_STOP_BUFFER_BRAKE_GAIN = 0.25
VISION_CLOSE_STOP_HOLD_MAX_EGO_SPEED = 0.75
VISION_CLOSE_STOP_HOLD_MAX_LEAD_SPEED = 0.8
VISION_CLOSE_STOP_HOLD_MAX_DISTANCE = 3.5
VISION_CLOSE_STOP_HOLD_MIN_MODEL_PROB = 0.95
VISION_CLOSE_STOP_HOLD_MIN_BRAKE = 0.20
VISION_CLOSE_STOP_HOLD_MAX_BRAKE = 0.36
VISION_CLOSE_SETTLE_MAX_EGO_SPEED = 0.75
VISION_CLOSE_SETTLE_MAX_LEAD_SPEED = 2.75
VISION_CLOSE_SETTLE_MAX_DISTANCE = 4.2
VISION_CLOSE_SETTLE_MAX_LEAD_DELTA = 2.6
VISION_CLOSE_SETTLE_MIN_BRAKE = 0.16
VISION_CLOSE_SETTLE_MAX_BRAKE = 0.30
VISION_CLOSE_FINAL_GUARD_MAX_EGO_SPEED = 0.5
VISION_CLOSE_FINAL_GUARD_MAX_LEAD_SPEED = 2.75
VISION_CLOSE_FINAL_GUARD_MAX_DISTANCE = 4.5
VISION_CLOSE_FINAL_GUARD_MIN_BRAKE = 0.18
VISION_CLOSE_FINAL_GUARD_MAX_BRAKE = 0.28
VISION_CLOSE_RELEASE_HOLD_MAX_EGO_SPEED = 2.5
VISION_CLOSE_RELEASE_HOLD_MAX_LEAD_SPEED = 3.5
VISION_CLOSE_RELEASE_HOLD_MAX_DISTANCE = 4.2
VISION_CLOSE_RELEASE_HOLD_MIN_MODEL_PROB = 0.98
VISION_CLOSE_RELEASE_HOLD_MIN_LEAD_DELTA = -0.1
VISION_CLOSE_RELEASE_HOLD_MAX_LEAD_DELTA = 1.5
VISION_CLOSE_RELEASE_HOLD_MIN_BRAKE = 0.18
VISION_CLOSE_RELEASE_HOLD_MAX_BRAKE = 0.40
MANUAL_STOP_RESUME_OVERRIDE_TIME = 3.0
MANUAL_STOP_RESUME_OVERRIDE_MAX_SPEED = 2.0
MANUAL_STOP_RESUME_OVERRIDE_MIN_ACCEL = 0.2
FORCE_STOP_HANDOFF_MAX_VCRUISE = 0.5
LEAD_CATCHUP_ACCEL_MIN_EGO = 8.0
LEAD_CATCHUP_ACCEL_MIN_LEAD_DELTA = -0.5
LEAD_CATCHUP_ACCEL_MAX_GAP_BUFFER_MIN = 4.0
LEAD_CATCHUP_ACCEL_MAX_GAP_BUFFER_GAIN = 0.15
RADAR_MATCHED_FOLLOW_CATCHUP_CAP_BUFFER_MARGIN = 0.75
RADAR_MATCHED_FOLLOW_CATCHUP_HOLD_CAP = 0.04
RADAR_MATCHED_FOLLOW_CATCHUP_HOLD_MAX_GAP_ERROR = 0.75
POST_DEPARTURE_FOLLOW_BYPASS_MIN_SPEED = 12.0
POST_DEPARTURE_FOLLOW_BYPASS_MIN_MODEL_PROB = 0.95
POST_DEPARTURE_FOLLOW_BYPASS_MIN_LEAD_DELTA = 0.35
POST_DEPARTURE_FOLLOW_BYPASS_MIN_LEAD_ACCEL = 0.25
POST_DEPARTURE_FOLLOW_BYPASS_MIN_HEADWAY_MARGIN = 0.10
POST_DEPARTURE_FOLLOW_SETTLE_LATCH_TIME = 75.0
POST_DEPARTURE_FOLLOW_SETTLE_MIN_SPEED = 8.0
POST_DEPARTURE_FOLLOW_SETTLE_MIN_MODEL_PROB = 0.9
POST_DEPARTURE_FOLLOW_SETTLE_MAX_LATERAL_OFFSET = 1.15
POST_DEPARTURE_FOLLOW_SETTLE_MAX_CLOSING_SPEED = 0.8
POST_DEPARTURE_FOLLOW_SETTLE_MAX_LEAD_BRAKE = 0.10
POST_DEPARTURE_FOLLOW_SETTLE_MIN_HEADWAY_MARGIN = 0.10
POST_DEPARTURE_FOLLOW_SETTLE_COMPLETE_HEADWAY_MARGIN = 0.05
COMFORTABLE_PULLAWAY_FOLLOW_MIN_MODEL_PROB = 0.95
COMFORTABLE_PULLAWAY_FOLLOW_MIN_LEAD_DELTA = -0.05
COMFORTABLE_PULLAWAY_FOLLOW_MIN_LEAD_ACCEL = 0.20
COMFORTABLE_PULLAWAY_FOLLOW_MIN_HEADWAY_MARGIN = 0.20
SPACIOUS_TRACKED_FOLLOW_MIN_MODEL_PROB = 0.98
SPACIOUS_TRACKED_FOLLOW_MIN_HEADWAY_MARGIN = 0.45
SPACIOUS_TRACKED_FOLLOW_MAX_CLOSING_SPEED = 0.60
SPACIOUS_TRACKED_FOLLOW_MAX_LEAD_BRAKE = 0.10
SPACIOUS_TRACKED_FOLLOW_LATCH_TIME = 1.25
SPACIOUS_TRACKED_FOLLOW_LATCH_MIN_LEAD_DELTA = 0.90
LOW_SPEED_FOLLOW_ACCEL_CAP_MAX_SPEED = 12.0
LOW_SPEED_FOLLOW_ACCEL_CAP_MIN_MODEL_PROB = 0.85
LOW_SPEED_FOLLOW_ACCEL_CAP_MAX_LEAD_BRAKE = 0.20
LOW_SPEED_FOLLOW_ACCEL_CAP_MIN_LEAD_DELTA = -1.2
LOW_SPEED_FOLLOW_ACCEL_CAP_MAX_GAP_BUFFER_MIN = 6.0
LOW_SPEED_FOLLOW_ACCEL_CAP_MAX_GAP_BUFFER_GAIN = 0.35
LOW_SPEED_FOLLOW_TRANSITION_MIN_SPEED = 3.0
LOW_SPEED_FOLLOW_TRANSITION_MAX_SPEED = 12.0
LOW_SPEED_FOLLOW_TRANSITION_MIN_MODEL_PROB = 0.85
LOW_SPEED_FOLLOW_TRANSITION_MAX_LEAD_BRAKE = 0.20
LOW_SPEED_FOLLOW_TRANSITION_MIN_GAP_MARGIN = 1.0
LOW_SPEED_FOLLOW_TRANSITION_MAX_CLOSING_SPEED = 0.6
LOW_SPEED_FOLLOW_TRANSITION_PREV_ACCEL_MIN = 0.18
LOW_SPEED_FOLLOW_TRANSITION_TARGET_BRAKE_MIN = -0.18
LOW_SPEED_FOLLOW_TRANSITION_MAX_BRAKE = 0.14
LOW_SPEED_FOLLOW_TRANSITION_MIN_BRAKE = 0.08
CRUISE_TRACKED_LEAD_ACCEL_CAP_MIN_SPEED = 10.0
CRUISE_TRACKED_LEAD_ACCEL_CAP_MAX_SPEED = 20.0
CRUISE_TRACKED_LEAD_ACCEL_CAP_MIN_MODEL_PROB = 0.85
CRUISE_TRACKED_LEAD_ACCEL_CAP_MAX_LEAD_BRAKE = 0.25
CRUISE_TRACKED_LEAD_ACCEL_CAP_MAX_PULLAWAY_SPEED = 1.0
CRUISE_TRACKED_LEAD_ACCEL_CAP_MAX_GAP_BUFFER_MIN = 12.0
CRUISE_TRACKED_LEAD_ACCEL_CAP_MAX_GAP_BUFFER_GAIN = 0.9
CRUISE_TRACKED_LEAD_ACCEL_CAP_MAX_LATERAL_OFFSET = 1.15
CRUISE_TRACKED_LEAD_ACCEL_CAP_UNRESOLVED_MIN_CLOSING_SPEED = 1.5
CRUISE_TRACKED_LEAD_ACCEL_CAP_UNRESOLVED_MAX_LEAD_DELTA = 0.25
CRUISE_TRACKED_LEAD_ACCEL_CAP_TRACKING_ONLY_MAX_HEADWAY_ABOVE_TARGET = 0.95
CRUISE_TRACKED_LEAD_ACCEL_CAP_TRACKING_ONLY_MAX_CLOSING_SPEED = 0.8
CRUISE_TRACKED_LEAD_ACCEL_CAP_TRACKING_ONLY_MAX_LEAD_BRAKE = 0.10
CRUISE_TRACKED_LEAD_ACCEL_CAP_MAX_ACCEL = 0.18
CRUISE_TRACKED_LEAD_ACCEL_CAP_ACCEL_AWAY_MIN = 0.25
CRUISE_TRACKED_LEAD_ACCEL_CAP_ACCEL_AWAY_MIN_LEAD_DELTA = 0.35
CRUISE_TRACKED_LEAD_ACCEL_CAP_ACCEL_AWAY_MIN_GAP_MARGIN = 1.0
CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MIN_SPEED = 12.0
CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MAX_SPEED = 22.0
CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MIN_MODEL_PROB = 0.9
CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MAX_LEAD_BRAKE = 0.35
CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MAX_LATERAL_OFFSET = 1.15
CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MAX_PULLAWAY_SPEED = 2.25
CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MAX_HEADWAY_ABOVE_TARGET = 0.95
CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MIN_DELTA_A = 0.18
CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MIN_STEP = 0.06
CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MAX_STEP = 0.18
# Uncertainty-based filter disable thresholds
UNCERT_SLOPE_TRIG = 0.12 # per second
UNCERT_MAG_TRIG = 0.50
UNCERT_PANIC_MIN_CLOSING_SPEED = 2.0
UNCERT_PANIC_MIN_CLOSING_SPEED_GAIN = 0.08
UNCERT_PANIC_MAX_GAP_BUFFER_MIN = 8.0
UNCERT_PANIC_MAX_GAP_BUFFER_GAIN = 0.35
STEADY_FOLLOW_SMOOTHING_MIN_SPEED = 22.0
STEADY_FOLLOW_SMOOTHING_MIN_CLOSING_SPEED = 0.15
STEADY_FOLLOW_SMOOTHING_MAX_CLOSING_SPEED = 1.8
STEADY_FOLLOW_SMOOTHING_MIN_HEADWAY = 0.95
STEADY_FOLLOW_SMOOTHING_HEADWAY_BELOW_TARGET = 0.35
STEADY_FOLLOW_SMOOTHING_HEADWAY_ABOVE_TARGET = 0.90
STEADY_FOLLOW_SMOOTHING_MAX_LEAD_BRAKE = 0.35
STEADY_FOLLOW_SMOOTHING_MIN_MODEL_PROB = 0.7
STEADY_FOLLOW_SMOOTHING_FILTER_FACTOR_FLOOR = 0.24
STEADY_FOLLOW_BRAKE_CAP_MIN_HEADWAY = 1.05
STEADY_FOLLOW_BRAKE_CAP_MAX_HEADWAY_ABOVE_TARGET = 0.90
STEADY_FOLLOW_BRAKE_CAP_MIN_REL_SPEED = -1.2
STEADY_FOLLOW_BRAKE_CAP_MAX_CLOSING_SPEED = 2.2
STEADY_FOLLOW_BRAKE_CAP_ZERO_REL_SPEED_DECEL = 0.12
STEADY_FOLLOW_BRAKE_CAP_OPENING_DECEL = 0.08
STEADY_FOLLOW_BRAKE_CAP_MIN_DECEL = 0.18
STEADY_FOLLOW_BRAKE_CAP_MAX_DECEL = 0.32
STEADY_FOLLOW_BRAKE_CAP_SPACIOUS_HEADWAY_MARGIN = 0.45
STEADY_FOLLOW_BRAKE_CAP_SPACIOUS_MIN_HEADWAY = 1.80
STEADY_FOLLOW_BRAKE_CAP_SPACIOUS_MAX_LEAD_BRAKE = 0.15
FAR_LEAD_COMFORT_BRAKE_CAP_MIN_DISTANCE = 80.0
FAR_LEAD_COMFORT_BRAKE_CAP_MIN_CLOSING_SPEED = 0.1
FAR_LEAD_COMFORT_BRAKE_CAP_MAX_CLOSING_SPEED = 2.0
FAR_LEAD_COMFORT_BRAKE_CAP_MIN_MODEL_PROB = 0.95
FAR_LEAD_COMFORT_BRAKE_CAP_MAX_LEAD_BRAKE = 0.12
FAR_LEAD_COMFORT_BRAKE_CAP_MIN_TTC = 7.5
FAR_LEAD_COMFORT_BRAKE_CAP_MIN_HEADWAY_MARGIN = 0.55
FAR_LEAD_COMFORT_BRAKE_CAP_FULL_HEADWAY_MARGIN = 1.00
FAR_LEAD_COMFORT_BRAKE_CAP_MIN_DECEL = 0.05
FAR_LEAD_COMFORT_BRAKE_CAP_MAX_DECEL = 0.18
FAR_LEAD_COMFORT_BRAKE_CAP_FULL_RELAX_DECEL = 0.05
MATCHED_FOLLOW_TRANSITION_MIN_SPEED = 20.0
MATCHED_FOLLOW_TRANSITION_MIN_HEADWAY_MARGIN = 0.25
MATCHED_FOLLOW_TRANSITION_FULL_HEADWAY_MARGIN = 0.75
MATCHED_FOLLOW_TRANSITION_MIN_MODEL_PROB = 0.9
MATCHED_FOLLOW_TRANSITION_MAX_LEAD_BRAKE = 0.18
MATCHED_FOLLOW_TRANSITION_MAX_CLOSING_SPEED = 1.75
MATCHED_FOLLOW_TRANSITION_MIN_TTC = 12.0
MATCHED_FOLLOW_TRANSITION_MIN_POSITIVE_STEP = 0.08
MATCHED_FOLLOW_TRANSITION_MAX_POSITIVE_STEP = 0.18
MATCHED_FOLLOW_TRANSITION_MIN_NEGATIVE_STEP = 0.08
MATCHED_FOLLOW_TRANSITION_MAX_NEGATIVE_STEP = 0.16
MATCHED_FOLLOW_TRANSITION_SIGN_CROSS_STEP = 0.10
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_SPEED = 10.0
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_SPEED = MATCHED_FOLLOW_TRANSITION_MIN_SPEED
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_HEADWAY_MARGIN = 0.45
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_FULL_HEADWAY_MARGIN = 1.00
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_MODEL_PROB = 0.98
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_LEAD_BRAKE = 0.08
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_CLOSING_SPEED = 1.25
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_TTC = 18.0
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_POSITIVE_STEP = 0.06
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_POSITIVE_STEP = 0.10
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_NEGATIVE_STEP = 0.05
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_NEGATIVE_STEP = 0.08
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_SIGN_CROSS_STEP = 0.06
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_TARGET = -0.12
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_DELTA_A = 0.12
MILD_FOLLOW_ZERO_CROSS_GUARD_MIN_SPEED = 3.0
MILD_FOLLOW_ZERO_CROSS_GUARD_MAX_SPEED = 35.0
MILD_FOLLOW_ZERO_CROSS_GUARD_MIN_MODEL_PROB = 0.95
MILD_FOLLOW_ZERO_CROSS_GUARD_MAX_LEAD_BRAKE = 0.80
MILD_FOLLOW_ZERO_CROSS_GUARD_MAX_CLOSING_SPEED = 3.25
MILD_FOLLOW_ZERO_CROSS_GUARD_MIN_TTC = 6.0
MILD_FOLLOW_ZERO_CROSS_GUARD_MIN_HEADWAY_MARGIN = 0.25
MILD_FOLLOW_ZERO_CROSS_GUARD_FULL_HEADWAY_MARGIN = 0.85
MILD_FOLLOW_ZERO_CROSS_GUARD_MIN_DELTA_A = 0.08
MILD_FOLLOW_ZERO_CROSS_GUARD_MIN_DEADBAND = 0.04
MILD_FOLLOW_ZERO_CROSS_GUARD_MAX_DEADBAND = 0.08
NEAR_DUPLICATE_LEAD_TRANSITION_MIN_SPEED = 20.0
NEAR_DUPLICATE_LEAD_TRANSITION_MIN_MODEL_PROB = 0.95
NEAR_DUPLICATE_LEAD_TRANSITION_MAX_LEAD_BRAKE = 0.35
NEAR_DUPLICATE_LEAD_TRANSITION_MAX_CLOSING_SPEED = 3.5
NEAR_DUPLICATE_LEAD_TRANSITION_MIN_TTC = 8.0
NEAR_DUPLICATE_LEAD_TRANSITION_MIN_HEADWAY_BELOW_TARGET = 0.45
NEAR_DUPLICATE_LEAD_TRANSITION_MAX_HEADWAY_ABOVE_TARGET = 0.85
LOW_SPEED_IDENTICAL_RADAR_DUPLICATE_TRANSITION_EXTRA_HEADWAY = 0.15
NEAR_DUPLICATE_LEAD_TRANSITION_MIN_DELTA_A = 0.35
NEAR_DUPLICATE_LEAD_TRANSITION_POSITIVE_STEP = 0.22
NEAR_DUPLICATE_LEAD_TRANSITION_NEGATIVE_STEP = 0.32
NEAR_DUPLICATE_LEAD_TRANSITION_SIGN_CROSS_STEP = 0.18
DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MIN_SPEED = 12.0
DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MIN_MODEL_PROB = 0.95
DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MIN_PREV_DECEL = 0.35
DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MIN_CLOSING_SPEED = 0.5
DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MAX_LEAD_BRAKE = 0.8
DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MAX_HEADWAY_ABOVE_TARGET = 0.85
DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MIN_DELTA_A = 0.35
DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MAX_DREL_DIFF = 1.5
DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MAX_VREL_DIFF = 0.35
DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MIN_POSITIVE_STEP = 0.12
DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MAX_POSITIVE_STEP = 0.28
DUPLICATE_SLOW_LEAD_BRAKE_HOLD_SIGN_CROSS_STEP = 0.22
TRACKED_VISION_MODEL_FLOOR_MIN_SPEED = 10.0
TRACKED_VISION_MODEL_FLOOR_MIN_MODEL_PROB = 0.95
TRACKED_VISION_MODEL_FLOOR_MIN_MODEL_DECEL = 0.80
TRACKED_VISION_MODEL_FLOOR_MIN_CLOSING_SPEED = 1.0
TRACKED_VISION_MODEL_FLOOR_MAX_TTC = 22.0
TRACKED_VISION_MODEL_FLOOR_MIN_GAP_MARGIN = -2.0
TRACKED_VISION_MODEL_FLOOR_MAX_GAP_BUFFER_MIN = 4.0
TRACKED_VISION_MODEL_FLOOR_MAX_GAP_BUFFER_GAIN = 0.25
TRACKED_VISION_MODEL_FLOOR_MIN_DECEL = 0.35
TRACKED_VISION_MODEL_FLOOR_MAX_DECEL = 1.10
TRACKED_VISION_MODEL_FLOOR_LEAD_BRAKE_MAX = 0.18
TRACKED_VISION_MODEL_CAP_MIN_SPEED = 14.0
TRACKED_VISION_MODEL_CAP_MAX_SPEED = 32.0
TRACKED_VISION_MODEL_CAP_MIN_MODEL_PROB = 0.95
TRACKED_VISION_MODEL_CAP_MAX_MODEL_DECEL = 0.75
TRACKED_VISION_MODEL_CAP_MIN_CLOSING_SPEED = 0.75
TRACKED_VISION_MODEL_CAP_MAX_CLOSING_SPEED = 4.0
TRACKED_VISION_MODEL_CAP_MAX_LEAD_BRAKE = 0.55
TRACKED_VISION_MODEL_CAP_MIN_TTC = 7.0
TRACKED_VISION_MODEL_CAP_MIN_GAP_MARGIN = -1.5
TRACKED_VISION_MODEL_CAP_MAX_GAP_BUFFER_MIN = 4.0
TRACKED_VISION_MODEL_CAP_MAX_GAP_BUFFER_GAIN = 0.25
TRACKED_VISION_MODEL_CAP_MIN_DECEL = 0.45
TRACKED_VISION_MODEL_CAP_MAX_DECEL = 1.10
# Lookup table for turns
_A_TOTAL_MAX_V = [3.5, 3.5, 3.2]
_A_TOTAL_MAX_BP = [0., 20., 40.]
_preap_follow_cache = None
def get_preap_follow_limit(v_ego):
global _preap_follow_cache
if _preap_follow_cache is None:
try:
from opendbc.car.tesla.preap.constants import ACCEL_PREAP_BP, ACCEL_PREAP_FOLLOW
_preap_follow_cache = (ACCEL_PREAP_BP, ACCEL_PREAP_FOLLOW)
except ImportError:
_preap_follow_cache = (None, None)
bp, values = _preap_follow_cache
if bp is None:
return None
return float(np.interp(v_ego, bp, values))
def get_longitudinal_personality(sm):
return sm['selfdriveState'].personality
def get_max_accel(v_ego):
return np.interp(v_ego, A_CRUISE_MAX_BP, A_CRUISE_MAX_VALS)
def get_coast_accel(pitch):
return np.sin(pitch) * -5.65 - 0.3 # fitted from data using xx/projects/allow_throttle/compute_coast_accel.py
def limit_accel_in_turns(v_ego, angle_steers, a_target, CP):
"""
This function returns a limited long acceleration allowed, depending on the existing lateral acceleration
this should avoid accelerating when losing the target in turns
"""
# FIXME: This function to calculate lateral accel is incorrect and should use the VehicleModel
# The lookup table for turns should also be updated if we do this
a_total_max = np.interp(v_ego, _A_TOTAL_MAX_BP, _A_TOTAL_MAX_V)
a_y = v_ego ** 2 * angle_steers * CV.DEG_TO_RAD / (CP.steerRatio * CP.wheelbase)
a_x_allowed = math.sqrt(max(a_total_max ** 2 - a_y ** 2, 0.))
return [a_target[0], min(a_target[1], a_x_allowed)]
def should_publish_planner_fcw(crash_cnt: int, car_state, radar_state) -> bool:
return (
crash_cnt > 2 and
not car_state.standstill and
should_trigger_planner_fcw(radar_state.leadOne, float(car_state.vEgo))
)
def get_vehicle_min_accel(CP, v_ego):
# Planner-side physical decel capability estimate for GM pedal-long paths.
if getattr(CP, "carName", "") == "gm" and getattr(CP, "enableGasInterceptorDEPRECATED", False):
try:
from opendbc.car.gm.values import GMFlags, CAR
if bool(CP.flags & GMFlags.PEDAL_LONG.value):
bolt_pedal_long_cars = {
CAR.CHEVROLET_BOLT_CC_2017,
CAR.CHEVROLET_BOLT_CC_2018_2021,
CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL,
CAR.CHEVROLET_BOLT_CC_2022_2023,
CAR.CHEVROLET_MALIBU_HYBRID_CC,
}
if CP.carFingerprint in bolt_pedal_long_cars:
return float(np.interp(v_ego, [0.0, 1.5, 4.0, 8.0, 15.0, 30.0],
[-0.93, -1.28, -1.98, -2.58, -2.86, -2.95]))
return float(np.interp(v_ego, [0.0, 1.5, 4.0, 8.0, 15.0, 30.0],
[-0.95, -1.3, -1.85, -2.3, -2.6, -2.8]))
except Exception:
pass
return float(ACCEL_MIN)
def get_planner_v_ego(CP, car_state):
v_ego = max(car_state.vEgo, car_state.vEgoCluster)
is_gm = getattr(CP, "carName", "") == "gm" or getattr(CP, "brand", "") == "gm"
if is_gm and getattr(CP, "enableGasInterceptorDEPRECATED", False):
try:
from opendbc.car.gm.values import GMFlags
is_gm_pedal_long = bool(CP.flags & GMFlags.PEDAL_LONG.value)
if is_gm_pedal_long:
return float(car_state.vEgo)
except Exception:
pass
return float(v_ego)
def get_accel_from_plan_classic(CP, speeds, accels, vEgoStopping):
if len(speeds) == CONTROL_N:
v_target_now = np.interp(DT_MDL, CONTROL_N_T_IDX, speeds)
a_target_now = np.interp(DT_MDL, CONTROL_N_T_IDX, accels)
v_target = np.interp(CP.longitudinalActuatorDelay + DT_MDL, CONTROL_N_T_IDX, speeds)
if v_target != v_target_now:
a_target = 2 * (v_target - v_target_now) / CP.longitudinalActuatorDelay - a_target_now
else:
a_target = a_target_now
v_target_1sec = np.interp(CP.longitudinalActuatorDelay + DT_MDL + 1.0, CONTROL_N_T_IDX, speeds)
else:
v_target = 0.0
v_target_1sec = 0.0
a_target = 0.0
should_stop = (v_target < vEgoStopping and
v_target_1sec < vEgoStopping)
return a_target, should_stop
def get_accel_from_plan(speeds, accels, action_t=DT_MDL, vEgoStopping=0.05):
if len(speeds) == CONTROL_N:
v_now = speeds[0]
a_now = accels[0]
v_target = np.interp(action_t, CONTROL_N_T_IDX, speeds)
a_target = 2 * (v_target - v_now) / (action_t) - a_now
v_target_1sec = np.interp(action_t + 1.0, CONTROL_N_T_IDX, speeds)
else:
v_target = 0.0
v_target_1sec = 0.0
a_target = 0.0
should_stop = (v_target < vEgoStopping and
v_target_1sec < vEgoStopping)
return a_target, should_stop
class LongitudinalPlanner:
def __init__(self, CP, init_v=0.0, init_a=0.0, dt=DT_MDL):
self.CP = CP
self.mpc = LongitudinalMpc(dt=dt)
self.fcw = False
self.dt = dt
self.model_allow_throttle = True
self.allow_throttle = True
self.mode = 'acc'
self.is_preap = (
CP.brand == "tesla" and CP.carFingerprint == "TESLA_MODEL_S_PREAP" and
CP.openpilotLongitudinalControl and not CP.pcmCruise
)
self.nap_adaptive_accel = False
self._preap_params = None
self._preap_param_frame = 0
self.generation = None
self.a_desired = init_a
self.v_desired_filter = FirstOrderFilter(init_v, 2.0, self.dt)
self.v_model_error = 0.0
self.output_a_target = 0.0
self.output_should_stop = False
self.confident_lead_depart_elapsed = 0.0
self.v_desired_trajectory = np.zeros(CONTROL_N)
self.a_desired_trajectory = np.zeros(CONTROL_N)
self.j_desired_trajectory = np.zeros(CONTROL_N)
self.solverExecutionTime = 0.0
# ---- Rubberband mitigation state ----
# Two uncertainty tracks (slow/fast) for asymmetric gating
self.uncert_slow = FirstOrderFilter(0.0, 1.6, self.dt) # ~lam=0.6
self.uncert_fast = FirstOrderFilter(0.0, 0.9, self.dt) # faster cool-down for accel decisions
# Lead stability tracking
self.prev_lead_dist = None
self.last_big_brake_t = 0.0
self.stable_lead = False
# Smoothed lead distance
self.lead_dist_f = None
# Uncertainty slope tracking
self._uncert_last = 0.0
self._uncert_last_t = None
self._panic_bypass_log_t = 0.0
self.effective_t_follow = None
self.vision_low_speed_stop_hold_until = 0.0
self.vision_lead_approach_confirm_t = 0.0
self.untracked_slow_lead_confirm_t = 0.0
self.manual_stop_resume_override_until = 0.0
self.lead_depart_accel_hold_until = 0.0
self.spacious_follow_cap_bypass_until = 0.0
self.post_departure_follow_settle_until = 0.0
if self.is_preap:
try:
from openpilot.common.params import Params
self._preap_params = Params()
self.nap_adaptive_accel = self._preap_params.get_bool("NAPAdaptiveAccel")
except Exception:
self._preap_params = None
self.nap_adaptive_accel = False
@property
def mlsim(self):
return is_tinygrad_model_version(self.generation)
def get_mpc_mode(self) -> str:
if not self.mlsim:
return self.mode
return getattr(self.mpc, 'mode', 'acc')
@staticmethod
def get_model_speed_error(model_msg, v_ego):
try:
if len(model_msg.temporalPose.trans):
return float(np.clip(model_msg.temporalPose.trans[0] - v_ego, -5.0, 5.0))
except AttributeError:
pass
return 0.0
@staticmethod
def parse_model(model_msg, model_error, v_ego, starpilot_toggles):
if (len(model_msg.position.x) == ModelConstants.IDX_N and
len(model_msg.velocity.x) == ModelConstants.IDX_N and
len(model_msg.acceleration.x) == ModelConstants.IDX_N):
x = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.position.x) - model_error * T_IDXS_MPC
v = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.velocity.x) - model_error
a = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.acceleration.x)
j = np.zeros(len(T_IDXS_MPC))
else:
x = np.zeros(len(T_IDXS_MPC))
v = np.zeros(len(T_IDXS_MPC))
a = np.zeros(len(T_IDXS_MPC))
j = np.zeros(len(T_IDXS_MPC))
if starpilot_toggles.taco_tune:
max_lat_accel = np.interp(v_ego, [5, 10, 20], [1.5, 2.0, 3.0])
curvatures = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.orientationRate.z) / np.clip(v, 0.3, 100.0)
max_v = np.sqrt(max_lat_accel / (np.abs(curvatures) + 1e-3)) - 2.0
v = np.minimum(max_v, v)
if len(model_msg.meta.disengagePredictions.gasPressProbs) > 1:
throttle_prob = model_msg.meta.disengagePredictions.gasPressProbs[1]
else:
throttle_prob = 1.0
return x, v, a, j, throttle_prob
def get_close_lead_brake_cap(self, lead, v_ego, accel_min):
if lead is None or not lead.status:
return None
lead_brake = max(0.0, -float(lead.aLeadK))
reaction_t = max(self.CP.longitudinalActuatorDelay, self.dt)
closing_speed = max(0.0, v_ego - lead.vLead)
projected_closing_speed = closing_speed + lead_brake * reaction_t
if projected_closing_speed < 0.1 and lead_brake < 0.5:
return None
target_gap = float(np.clip(2.0 + 0.2 * v_ego, 2.0, 6.0))
delay_buffer = projected_closing_speed * reaction_t
available_gap = max(float(lead.dRel) - target_gap - delay_buffer, 0.5)
projected_ttc = available_gap / max(projected_closing_speed, 0.1)
if projected_ttc > CLOSE_LEAD_BRAKE_CAP_MAX_TTC:
return None
required_decel = (projected_closing_speed ** 2) / (2.0 * available_gap) + 0.7 * lead_brake
if required_decel < 0.2:
return None
return max(accel_min, -required_decel)
def get_vision_lead_approach_cap(self, lead, v_ego, accel_min, t_follow):
if lead is None or not lead.status or bool(getattr(lead, "radar", False)):
return None
lead_prob = float(getattr(lead, "modelProb", 0.0))
if lead_prob < VISION_LEAD_APPROACH_MIN_MODEL_PROB:
return None
lead_brake = max(0.0, -float(lead.aLeadK))
reaction_t = max(self.CP.longitudinalActuatorDelay, self.dt)
closing_speed = max(0.0, v_ego - lead.vLead)
projected_closing_speed = closing_speed + lead_brake * reaction_t
if projected_closing_speed < VISION_LEAD_APPROACH_MIN_CLOSING_SPEED:
return None
tight_follow_gap = float(t_follow * v_ego + VISION_LEAD_APPROACH_TIGHT_BUFFER)
gap_to_tight_follow = float(lead.dRel) - tight_follow_gap
time_to_tight_follow = gap_to_tight_follow / max(projected_closing_speed, 0.1)
if time_to_tight_follow > VISION_LEAD_APPROACH_TRIGGER_TIME:
return None
desired_gap = float(desired_follow_distance(v_ego, lead.vLead, t_follow))
if float(lead.dRel) > desired_gap + VISION_LEAD_APPROACH_TIGHT_BUFFER:
return None
time_factor = float(np.clip((VISION_LEAD_APPROACH_TRIGGER_TIME - time_to_tight_follow) /
(VISION_LEAD_APPROACH_TRIGGER_TIME - VISION_LEAD_APPROACH_FULL_TIME), 0.0, 1.0))
prob_factor = float(np.clip((lead_prob - VISION_LEAD_APPROACH_MIN_MODEL_PROB) /
(VISION_LEAD_APPROACH_FULL_MODEL_PROB - VISION_LEAD_APPROACH_MIN_MODEL_PROB), 0.0, 1.0))
closing_factor = float(np.clip(projected_closing_speed / (VISION_LEAD_APPROACH_MIN_CLOSING_SPEED + 2.5), 0.0, 1.0))
tight_follow_deficit = max(tight_follow_gap - float(lead.dRel), 0.0)
tight_follow_buffer = max(VISION_LEAD_APPROACH_DEFICIT_BUFFER_MIN,
VISION_LEAD_APPROACH_DEFICIT_BUFFER_GAIN * float(v_ego) + 1.0)
deficit_factor = float(np.clip(tight_follow_deficit / tight_follow_buffer, 0.0, 1.0))
approach_decel = VISION_LEAD_APPROACH_MAX_DECEL * time_factor * (0.45 + 0.55 * prob_factor)
approach_decel *= 0.6 + 0.4 * closing_factor
deficit_decel = VISION_LEAD_APPROACH_DEFICIT_MAX_DECEL * deficit_factor * prob_factor
deficit_decel *= 0.5 + 0.5 * closing_factor
approach_decel = max(approach_decel, deficit_decel)
# If a tracked vision lead is already far inside the tight-follow window and
# it is actively braking, don't stay stuck at the softer comfort cap.
if deficit_factor >= VISION_LEAD_APPROACH_BRAKING_DEFICIT_MIN and lead_brake >= VISION_LEAD_APPROACH_BRAKING_MIN_LEAD_BRAKE:
braking_floor = float(np.interp(
lead_brake,
[VISION_LEAD_APPROACH_BRAKING_MIN_LEAD_BRAKE, VISION_LEAD_APPROACH_BRAKING_FULL_LEAD_BRAKE],
[VISION_LEAD_APPROACH_BRAKING_FLOOR_MIN_DECEL, VISION_LEAD_APPROACH_BRAKING_FLOOR_MAX_DECEL],
))
braking_floor *= 0.85 + 0.15 * max(closing_factor, prob_factor)
approach_decel = max(approach_decel, braking_floor)
if approach_decel < VISION_LEAD_APPROACH_MIN_DECEL:
return None
return max(accel_min, -approach_decel)
def get_vision_untracked_slow_lead_cap(self, lead, v_ego, accel_min):
if lead is None or not lead.status or bool(getattr(lead, "radar", False)):
return None
lead_prob = float(getattr(lead, "modelProb", 0.0))
lead_brake = max(0.0, -float(lead.aLeadK))
reaction_t = max(self.CP.longitudinalActuatorDelay, self.dt)
closing_speed = max(0.0, v_ego - lead.vLead)
projected_closing_speed = closing_speed + lead_brake * reaction_t
if projected_closing_speed < VISION_UNTRACKED_SLOW_LEAD_MIN_CLOSING_SPEED:
return None
closing_ratio = projected_closing_speed / max(float(v_ego), 0.1)
if closing_ratio < VISION_UNTRACKED_SLOW_LEAD_MIN_CLOSING_RATIO:
return None
projected_ttc = float(lead.dRel) / max(projected_closing_speed, 0.1)
if projected_ttc > VISION_UNTRACKED_SLOW_LEAD_TRIGGER_TTC:
return None
min_model_prob = VISION_UNTRACKED_SLOW_LEAD_MIN_MODEL_PROB
max_distance_time = VISION_UNTRACKED_SLOW_LEAD_MAX_DISTANCE_TIME
if float(lead.vLead) <= VISION_UNTRACKED_SLOW_LEAD_RELAXED_MAX_LEAD_SPEED and \
projected_ttc <= VISION_UNTRACKED_SLOW_LEAD_RELAXED_MAX_TTC:
closing_relax = float(np.clip((projected_closing_speed - VISION_UNTRACKED_SLOW_LEAD_RELAXED_MIN_CLOSING_SPEED) /
(VISION_UNTRACKED_SLOW_LEAD_RELAXED_FULL_CLOSING_SPEED -
VISION_UNTRACKED_SLOW_LEAD_RELAXED_MIN_CLOSING_SPEED), 0.0, 1.0))
ttc_relax = float(np.clip((VISION_UNTRACKED_SLOW_LEAD_RELAXED_MAX_TTC - projected_ttc) /
(VISION_UNTRACKED_SLOW_LEAD_RELAXED_MAX_TTC -
VISION_UNTRACKED_SLOW_LEAD_FULL_TTC), 0.0, 1.0))
relax_factor = closing_relax * ttc_relax
min_model_prob = float(np.interp(relax_factor, [0.0, 1.0],
[VISION_UNTRACKED_SLOW_LEAD_MIN_MODEL_PROB,
VISION_UNTRACKED_SLOW_LEAD_RELAXED_MODEL_PROB]))
max_distance_time = float(np.interp(relax_factor, [0.0, 1.0],
[VISION_UNTRACKED_SLOW_LEAD_MAX_DISTANCE_TIME,
VISION_UNTRACKED_SLOW_LEAD_RELAXED_MAX_DISTANCE_TIME]))
max_distance = float(np.clip(max_distance_time * v_ego,
VISION_UNTRACKED_SLOW_LEAD_MIN_DISTANCE,
VISION_UNTRACKED_SLOW_LEAD_MAX_DISTANCE))
if float(lead.dRel) > max_distance:
return None
if lead_prob < min_model_prob:
return None
time_factor = float(np.clip((VISION_UNTRACKED_SLOW_LEAD_TRIGGER_TTC - projected_ttc) /
(VISION_UNTRACKED_SLOW_LEAD_TRIGGER_TTC - VISION_UNTRACKED_SLOW_LEAD_FULL_TTC),
0.0, 1.0))
prob_factor = float(np.clip((lead_prob - min_model_prob) /
(VISION_UNTRACKED_SLOW_LEAD_FULL_MODEL_PROB - min_model_prob),
0.0, 1.0))
closing_factor = float(np.clip((closing_ratio - VISION_UNTRACKED_SLOW_LEAD_MIN_CLOSING_RATIO) /
(VISION_UNTRACKED_SLOW_LEAD_FULL_CLOSING_RATIO - VISION_UNTRACKED_SLOW_LEAD_MIN_CLOSING_RATIO),
0.0, 1.0))
approach_decel = VISION_UNTRACKED_SLOW_LEAD_MAX_DECEL * np.clip(
0.5 * time_factor + 0.3 * prob_factor + 0.2 * closing_factor, 0.0, 1.0)
if approach_decel < VISION_UNTRACKED_SLOW_LEAD_MIN_DECEL:
return None
return max(accel_min, -approach_decel)
def get_vision_slow_stopped_lead_cap(self, lead, v_ego, accel_min, t_follow):
if lead is None or not lead.status or bool(getattr(lead, "radar", False)):
return None
lead_prob = float(getattr(lead, "modelProb", 0.0))
if lead_prob < VISION_SLOW_LEAD_MIN_MODEL_PROB or float(lead.vLead) > VISION_SLOW_LEAD_MAX_SPEED:
return None
lead_brake = max(0.0, -float(lead.aLeadK))
reaction_t = max(self.CP.longitudinalActuatorDelay, self.dt)
closing_speed = max(0.0, v_ego - lead.vLead)
projected_closing_speed = closing_speed + lead_brake * reaction_t
if projected_closing_speed < VISION_SLOW_LEAD_MIN_CLOSING_SPEED:
return None
stop_gap = float(max(STOP_DISTANCE + 1.0, 2.5 + 0.15 * max(float(lead.vLead), 0.0)))
delay_buffer = projected_closing_speed * reaction_t
available_gap = max(float(lead.dRel) - stop_gap - delay_buffer, 0.5)
projected_ttc = available_gap / max(projected_closing_speed, 0.1)
if projected_ttc > VISION_SLOW_LEAD_TRIGGER_TTC:
return None
time_factor = float(np.clip((VISION_SLOW_LEAD_TRIGGER_TTC - projected_ttc) /
(VISION_SLOW_LEAD_TRIGGER_TTC - VISION_SLOW_LEAD_FULL_TTC), 0.0, 1.0))
prob_factor = float(np.clip((lead_prob - VISION_SLOW_LEAD_MIN_MODEL_PROB) /
(VISION_LEAD_APPROACH_FULL_MODEL_PROB - VISION_SLOW_LEAD_MIN_MODEL_PROB), 0.0, 1.0))
speed_factor = float(np.clip((VISION_SLOW_LEAD_MAX_SPEED - max(float(lead.vLead), 0.0)) /
VISION_SLOW_LEAD_MAX_SPEED, 0.0, 1.0))
required_decel = (projected_closing_speed ** 2) / (2.0 * available_gap)
decel_scale = 0.45 + 0.35 * time_factor + 0.20 * speed_factor
approach_decel = min(VISION_SLOW_LEAD_MAX_DECEL, required_decel * decel_scale)
approach_decel *= 0.65 + 0.35 * prob_factor
if approach_decel < VISION_SLOW_LEAD_MIN_DECEL:
return None
return max(accel_min, -approach_decel)
def tracked_vision_lead_approach_needs_immediate_brake(self, lead, v_ego, approach_cap):
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
reaction_t = max(self.CP.longitudinalActuatorDelay, self.dt)
projected_closing_speed = max(0.0, v_ego - float(lead.vLead)) + lead_brake * reaction_t
bypass_distance = max(VISION_LEAD_APPROACH_CONFIRM_BYPASS_DISTANCE_MIN,
VISION_LEAD_APPROACH_CONFIRM_BYPASS_DISTANCE_TIME * float(v_ego))
return (
approach_cap <= -VISION_LEAD_APPROACH_CONFIRM_BYPASS_DECEL or
projected_closing_speed >= VISION_LEAD_APPROACH_CONFIRM_BYPASS_CLOSING_SPEED or
lead_brake >= VISION_LEAD_APPROACH_CONFIRM_BYPASS_LEAD_BRAKE or
float(lead.dRel) <= bypass_distance
)
def get_dynamic_t_follow(self, base_t_follow, lead, v_ego):
base_t_follow = float(base_t_follow)
target_t_follow = base_t_follow
if lead is not None and lead.status:
lead_prob = float(getattr(lead, "modelProb", 1.0 if bool(getattr(lead, "radar", False)) else 0.0))
if bool(getattr(lead, "radar", False)) or lead_prob >= VISION_LEAD_APPROACH_MIN_MODEL_PROB:
lead_brake = max(0.0, -float(lead.aLeadK))
closing_speed = max(0.0, v_ego - lead.vLead)
if closing_speed >= LEAD_APPROACH_TFOLLOW_MIN_CLOSING_SPEED or lead_brake >= LEAD_APPROACH_TFOLLOW_MIN_LEAD_BRAKE:
desired_gap = float(desired_follow_distance(v_ego, lead.vLead, base_t_follow))
approach_window = max(LEAD_APPROACH_TFOLLOW_WINDOW_MIN, LEAD_APPROACH_TFOLLOW_WINDOW_GAIN * float(v_ego))
if float(lead.dRel) <= desired_gap + approach_window:
reaction_t = max(self.CP.longitudinalActuatorDelay, self.dt)
projected_closing_speed = closing_speed + 0.5 * lead_brake * reaction_t
gap_to_follow = max(float(lead.dRel) - desired_gap, 0.0)
time_to_follow = gap_to_follow / max(projected_closing_speed, 0.1)
time_factor = float(np.clip((LEAD_APPROACH_TFOLLOW_TRIGGER_TIME - time_to_follow) /
(LEAD_APPROACH_TFOLLOW_TRIGGER_TIME - LEAD_APPROACH_TFOLLOW_FULL_TIME), 0.0, 1.0))
closing_factor = float(np.clip(closing_speed / LEAD_APPROACH_TFOLLOW_MAX_CLOSING_SPEED, 0.0, 1.0))
brake_factor = float(np.clip(lead_brake / LEAD_APPROACH_TFOLLOW_MAX_LEAD_BRAKE, 0.0, 1.0))
target_delta = LEAD_APPROACH_TFOLLOW_MAX_DELTA * np.clip(
0.55 * time_factor + 0.25 * closing_factor + 0.20 * brake_factor, 0.0, 1.0)
if not bool(getattr(lead, "radar", False)):
gap_deficit = max(desired_gap - float(lead.dRel), 0.0)
gap_buffer = max(VISION_LEAD_TFOLLOW_GAP_BUFFER_MIN,
VISION_LEAD_TFOLLOW_GAP_BUFFER_GAIN * float(v_ego))
gap_factor = float(np.clip(gap_deficit / gap_buffer, 0.0, 1.0))
slow_lead_factor = float(np.clip((VISION_LEAD_TFOLLOW_SLOW_LEAD_SPEED - float(lead.vLead)) /
VISION_LEAD_TFOLLOW_SLOW_LEAD_SPEED, 0.0, 1.0))
vision_extra = VISION_LEAD_TFOLLOW_MAX_EXTRA_DELTA * np.clip(
0.40 * time_factor + 0.30 * gap_factor + 0.20 * slow_lead_factor + 0.10 * closing_factor,
0.0, 1.0)
target_delta += vision_extra
target_t_follow = base_t_follow + float(target_delta)
if self.effective_t_follow is None:
self.effective_t_follow = base_t_follow
rate = LEAD_APPROACH_TFOLLOW_RATE_UP if target_t_follow > self.effective_t_follow else LEAD_APPROACH_TFOLLOW_RATE_DOWN
step = rate * self.dt
self.effective_t_follow = float(np.clip(target_t_follow, self.effective_t_follow - step, self.effective_t_follow + step))
self.effective_t_follow = max(base_t_follow, self.effective_t_follow)
return self.effective_t_follow
def get_vision_low_speed_stop_buffer_cap(self, lead, v_ego, accel_min):
if lead is None or not lead.status or bool(getattr(lead, "radar", False)):
return None, False
lead_prob = float(getattr(lead, "modelProb", 0.0))
if lead_prob < VISION_LOW_SPEED_STOP_BUFFER_MIN_MODEL_PROB:
return None, False
lead_speed = max(float(lead.vLead), 0.0)
relative_speed = float(v_ego) - lead_speed
closing_speed = max(0.0, v_ego - lead_speed)
entry_context = (
v_ego <= VISION_LOW_SPEED_STOP_BUFFER_MAX_EGO_SPEED and
lead_speed <= VISION_LOW_SPEED_STOP_BUFFER_MAX_LEAD_SPEED and
closing_speed >= VISION_LOW_SPEED_STOP_BUFFER_MIN_CLOSING_SPEED
)
hold_context = (
v_ego <= VISION_LOW_SPEED_STOP_BUFFER_MAX_EGO_SPEED and
lead_speed <= VISION_LOW_SPEED_STOP_BUFFER_HOLD_MAX_LEAD_SPEED and
relative_speed >= VISION_LOW_SPEED_STOP_BUFFER_MIN_HOLD_REL_SPEED
)
now_t = time.monotonic()
entry_buffer = max(3.2, VISION_LOW_SPEED_STOP_BUFFER_BASE +
VISION_LOW_SPEED_STOP_BUFFER_EGO_GAIN * float(v_ego) +
VISION_LOW_SPEED_STOP_BUFFER_LEAD_GAIN * lead_speed)
release_buffer = entry_buffer + VISION_LOW_SPEED_STOP_BUFFER_RELEASE_MARGIN
if entry_context and float(lead.dRel) <= entry_buffer:
self.vision_low_speed_stop_hold_until = now_t + VISION_LOW_SPEED_STOP_BUFFER_HOLD_TIME
latched = now_t < self.vision_low_speed_stop_hold_until
active = bool(
(entry_context and float(lead.dRel) <= entry_buffer) or
(latched and hold_context and float(lead.dRel) <= release_buffer)
)
if not active:
return None, False
min_stop_brake = VISION_LOW_SPEED_STOP_BUFFER_MIN_BRAKE + VISION_LOW_SPEED_STOP_BUFFER_BRAKE_GAIN * float(v_ego)
return max(accel_min, -min_stop_brake), True
def get_vision_close_stop_hold_cap(self, lead, v_ego, accel_min, should_stop):
if not should_stop or lead is None or not lead.status or bool(getattr(lead, "radar", False)):
return None
lead_prob = float(getattr(lead, "modelProb", 0.0))
if lead_prob < VISION_CLOSE_STOP_HOLD_MIN_MODEL_PROB:
return None
lead_speed = max(float(lead.vLead), 0.0)
near_standstill_settle = bool(
float(v_ego) <= VISION_CLOSE_SETTLE_MAX_EGO_SPEED and
lead_speed <= VISION_CLOSE_SETTLE_MAX_LEAD_SPEED and
float(lead.dRel) <= VISION_CLOSE_SETTLE_MAX_DISTANCE
)
max_lead_speed = VISION_CLOSE_SETTLE_MAX_LEAD_SPEED if near_standstill_settle else VISION_CLOSE_STOP_HOLD_MAX_LEAD_SPEED
max_distance = VISION_CLOSE_SETTLE_MAX_DISTANCE if near_standstill_settle else VISION_CLOSE_STOP_HOLD_MAX_DISTANCE
if (
float(v_ego) > VISION_CLOSE_STOP_HOLD_MAX_EGO_SPEED or
lead_speed > max_lead_speed or
float(lead.dRel) > max_distance
):
return None
distance_factor = float(np.clip((max_distance - float(lead.dRel)) /
max(max_distance - 1.8, 0.1), 0.0, 1.0))
speed_factor = float(np.clip(float(v_ego) / max(VISION_CLOSE_STOP_HOLD_MAX_EGO_SPEED, 0.1), 0.0, 1.0))
hold_brake = VISION_CLOSE_STOP_HOLD_MIN_BRAKE + 0.08 * distance_factor + 0.08 * speed_factor
if near_standstill_settle:
settle_brake = VISION_CLOSE_SETTLE_MIN_BRAKE + 0.10 * distance_factor + 0.04 * speed_factor
hold_brake = max(hold_brake, settle_brake)
hold_brake = float(np.clip(hold_brake, VISION_CLOSE_STOP_HOLD_MIN_BRAKE, VISION_CLOSE_STOP_HOLD_MAX_BRAKE))
brake_floor = -hold_brake
return brake_floor if accel_min >= 0.0 else max(accel_min, brake_floor)
def get_vision_close_release_hold_cap(self, lead, v_ego, accel_min, should_stop):
if should_stop or lead is None or not lead.status or bool(getattr(lead, "radar", False)):
return None
lead_prob = float(getattr(lead, "modelProb", 0.0))
if lead_prob < VISION_CLOSE_RELEASE_HOLD_MIN_MODEL_PROB:
return None
lead_speed = max(float(lead.vLead), 0.0)
lead_delta = lead_speed - float(v_ego)
near_standstill_settle = bool(
float(v_ego) <= VISION_CLOSE_SETTLE_MAX_EGO_SPEED and
lead_speed <= VISION_CLOSE_SETTLE_MAX_LEAD_SPEED and
float(lead.dRel) <= VISION_CLOSE_SETTLE_MAX_DISTANCE and
lead_delta <= VISION_CLOSE_SETTLE_MAX_LEAD_DELTA
)
max_lead_speed = VISION_CLOSE_SETTLE_MAX_LEAD_SPEED if near_standstill_settle else VISION_CLOSE_RELEASE_HOLD_MAX_LEAD_SPEED
max_distance = VISION_CLOSE_SETTLE_MAX_DISTANCE if near_standstill_settle else VISION_CLOSE_RELEASE_HOLD_MAX_DISTANCE
max_lead_delta = VISION_CLOSE_SETTLE_MAX_LEAD_DELTA if near_standstill_settle else VISION_CLOSE_RELEASE_HOLD_MAX_LEAD_DELTA
if (
float(v_ego) > VISION_CLOSE_RELEASE_HOLD_MAX_EGO_SPEED or
lead_speed > max_lead_speed or
float(lead.dRel) > max_distance or
lead_delta < VISION_CLOSE_RELEASE_HOLD_MIN_LEAD_DELTA or
lead_delta > max_lead_delta
):
return None
distance_factor = float(np.clip((max_distance - float(lead.dRel)) /
max(max_distance - 2.8, 0.1), 0.0, 1.0))
speed_factor = float(np.clip(float(v_ego) / max(VISION_CLOSE_RELEASE_HOLD_MAX_EGO_SPEED, 0.1), 0.0, 1.0))
delta_factor = float(np.clip((max_lead_delta - lead_delta) /
max(max_lead_delta - VISION_CLOSE_RELEASE_HOLD_MIN_LEAD_DELTA, 0.1),
0.0, 1.0))
hold_brake = VISION_CLOSE_RELEASE_HOLD_MIN_BRAKE + 0.12 * distance_factor + 0.06 * speed_factor + 0.04 * delta_factor
if near_standstill_settle:
settle_brake = VISION_CLOSE_SETTLE_MIN_BRAKE + 0.08 * distance_factor + 0.02 * delta_factor
hold_brake = max(hold_brake, settle_brake)
hold_brake = float(np.clip(hold_brake, VISION_CLOSE_RELEASE_HOLD_MIN_BRAKE, VISION_CLOSE_RELEASE_HOLD_MAX_BRAKE))
brake_floor = -hold_brake
return brake_floor if accel_min >= 0.0 else max(accel_min, brake_floor)
def get_vision_close_settle_cap(self, lead, v_ego, accel_min, stop_guard_active):
if lead is None or not lead.status or bool(getattr(lead, "radar", False)):
return None
lead_prob = float(getattr(lead, "modelProb", 0.0))
if lead_prob < VISION_CLOSE_STOP_HOLD_MIN_MODEL_PROB:
return None
lead_speed = max(float(lead.vLead), 0.0)
lead_delta = lead_speed - float(v_ego)
if (
float(v_ego) > VISION_CLOSE_SETTLE_MAX_EGO_SPEED or
lead_speed > VISION_CLOSE_SETTLE_MAX_LEAD_SPEED or
float(lead.dRel) > VISION_CLOSE_SETTLE_MAX_DISTANCE or
lead_delta > VISION_CLOSE_SETTLE_MAX_LEAD_DELTA
):
return None
if not stop_guard_active and lead_delta < 0.0:
return None
distance_factor = float(np.clip((VISION_CLOSE_SETTLE_MAX_DISTANCE - float(lead.dRel)) /
max(VISION_CLOSE_SETTLE_MAX_DISTANCE - 2.8, 0.1), 0.0, 1.0))
delta_factor = float(np.clip((VISION_CLOSE_SETTLE_MAX_LEAD_DELTA - lead_delta) /
max(VISION_CLOSE_SETTLE_MAX_LEAD_DELTA, 0.1), 0.0, 1.0))
hold_brake = VISION_CLOSE_SETTLE_MIN_BRAKE + 0.10 * distance_factor + 0.04 * delta_factor
hold_brake = float(np.clip(hold_brake, VISION_CLOSE_SETTLE_MIN_BRAKE, VISION_CLOSE_SETTLE_MAX_BRAKE))
brake_floor = -hold_brake
return brake_floor if accel_min >= 0.0 else max(accel_min, brake_floor)
def get_vision_close_final_guard_cap(self, lead, v_ego, accel_min):
if lead is None or not lead.status or bool(getattr(lead, "radar", False)):
return None
lead_prob = float(getattr(lead, "modelProb", 0.0))
lead_speed = max(float(lead.vLead), 0.0)
if (
lead_prob < VISION_CLOSE_STOP_HOLD_MIN_MODEL_PROB or
float(v_ego) > VISION_CLOSE_FINAL_GUARD_MAX_EGO_SPEED or
lead_speed > VISION_CLOSE_FINAL_GUARD_MAX_LEAD_SPEED or
float(lead.dRel) > VISION_CLOSE_FINAL_GUARD_MAX_DISTANCE
):
return None
distance_factor = float(np.clip((VISION_CLOSE_FINAL_GUARD_MAX_DISTANCE - float(lead.dRel)) /
max(VISION_CLOSE_FINAL_GUARD_MAX_DISTANCE - 2.8, 0.1), 0.0, 1.0))
hold_brake = VISION_CLOSE_FINAL_GUARD_MIN_BRAKE + 0.10 * distance_factor
hold_brake = float(np.clip(hold_brake, VISION_CLOSE_FINAL_GUARD_MIN_BRAKE, VISION_CLOSE_FINAL_GUARD_MAX_BRAKE))
brake_floor = -hold_brake
return brake_floor if accel_min >= 0.0 else max(accel_min, brake_floor)
def _update_manual_stop_resume_override(self, sm):
now_t = time.monotonic()
lead = sm["radarState"].leadOne
no_lead = not bool(getattr(lead, "status", False))
try:
starpilot_car_state = sm["starpilotCarState"]
except KeyError:
starpilot_car_state = None
accel_pressed = bool(getattr(starpilot_car_state, "accelPressed", False))
model_should_stop = bool(getattr(sm["modelV2"].action, "shouldStop", False))
standstill = bool(getattr(sm["carState"], "standstill", False))
forcing_stop = bool(getattr(sm["starpilotPlan"], "forcingStop", False))
red_light = bool(getattr(sm["starpilotPlan"], "redLight", False))
if standstill and no_lead and accel_pressed and (forcing_stop or red_light or model_should_stop):
self.manual_stop_resume_override_until = now_t + MANUAL_STOP_RESUME_OVERRIDE_TIME
return bool(
no_lead and
float(getattr(sm["carState"], "vEgo", 0.0)) < MANUAL_STOP_RESUME_OVERRIDE_MAX_SPEED and
now_t < self.manual_stop_resume_override_until
)
def is_confident_lead_depart(self, lead, v_ego):
if lead is None or not lead.status:
return False
lead_radar = bool(getattr(lead, "radar", False))
lead_prob = float(getattr(lead, "modelProb", 1.0 if lead_radar else 0.0))
if not lead_radar and lead_prob < LEAD_DEPART_ACCEL_HOLD_MIN_MODEL_PROB:
return False
lead_speed = max(float(lead.vLead), 0.0)
lead_delta = lead_speed - float(v_ego)
lead_accel = float(getattr(lead, "aLeadK", 0.0))
return bool(
float(lead.dRel) >= LEAD_DEPART_CONFIDENT_MIN_GAP and
float(lead.dRel) <= LEAD_DEPART_CONFIDENT_MAX_GAP and
lead_speed >= LEAD_DEPART_CONFIDENT_MIN_LEAD_SPEED and
lead_delta >= LEAD_DEPART_CONFIDENT_MIN_LEAD_DELTA and
lead_accel >= LEAD_DEPART_CONFIDENT_MIN_LEAD_ACCEL
)
@staticmethod
def get_centered_model_lead(model_data):
try:
leads = model_data.leadsV3
except Exception:
return None
best_candidate = None
for i in range(3):
try:
lead = leads[i]
prob = float(lead.prob)
x = float(lead.x[0])
y = float(lead.y[0])
v = float(lead.v[0])
except Exception:
continue
if (
prob < RADAR_DEPART_CONFLICT_MIN_MODEL_PROB or
x <= 0.0 or
x > RADAR_DEPART_CONFLICT_MAX_MODEL_DISTANCE or
abs(y) > RADAR_DEPART_CONFLICT_MAX_MODEL_LATERAL or
max(v, 0.0) > RADAR_DEPART_CONFLICT_MAX_MODEL_LEAD_SPEED
):
continue
if best_candidate is None or x < best_candidate[0]:
best_candidate = (x, y, v, prob)
return best_candidate
def has_offcenter_radar_depart_conflict(self, sm):
if float(getattr(sm["carState"], "vEgo", 0.0)) > RADAR_DEPART_CONFLICT_MAX_EGO_SPEED:
return False
centered_model_lead = self.get_centered_model_lead(sm["modelV2"])
if centered_model_lead is None:
return False
centered_model_dist = float(centered_model_lead[0])
for lead in (self.lead_one, self.lead_two):
if not lead.status or not bool(getattr(lead, "radar", False)):
continue
lead_dist = float(getattr(lead, "dRel", 0.0))
if lead_dist <= 0.0 or lead_dist > RADAR_DEPART_CONFLICT_MAX_RADAR_DISTANCE:
continue
if abs(float(getattr(lead, "yRel", 0.0))) < RADAR_DEPART_CONFLICT_MIN_RADAR_LATERAL:
continue
if abs(lead_dist - centered_model_dist) > RADAR_DEPART_CONFLICT_MAX_DISTANCE_MISMATCH:
continue
return True
return False
def get_lead_depart_accel_floor(self, lead, v_ego, model_desired_accel):
if lead is None or not lead.status:
return None
lead_radar = bool(getattr(lead, "radar", False))
lead_prob = float(getattr(lead, "modelProb", 1.0 if lead_radar else 0.0))
if not lead_radar and lead_prob < LEAD_DEPART_ACCEL_HOLD_MIN_MODEL_PROB:
return None
lead_speed = max(float(lead.vLead), 0.0)
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
lead_delta = lead_speed - float(v_ego)
confident_depart = self.is_confident_lead_depart(lead, v_ego)
min_lead_speed = LEAD_DEPART_CONFIDENT_MIN_LEAD_SPEED if confident_depart else LEAD_DEPART_ACCEL_HOLD_MIN_LEAD_SPEED
min_lead_delta = LEAD_DEPART_CONFIDENT_MIN_LEAD_DELTA if confident_depart else LEAD_DEPART_ACCEL_HOLD_MIN_LEAD_DELTA
min_gap = LEAD_DEPART_CONFIDENT_MIN_GAP if confident_depart else LEAD_DEPART_ACCEL_HOLD_MIN_GAP
min_model_accel = 0.0 if confident_depart else LEAD_DEPART_ACCEL_HOLD_MIN_MODEL_ACCEL
if (
float(v_ego) > LEAD_DEPART_ACCEL_HOLD_MAX_EGO_SPEED or
lead_speed < min_lead_speed or
lead_delta < min_lead_delta or
float(lead.dRel) < min_gap or
lead_brake > LEAD_DEPART_ACCEL_HOLD_MAX_LEAD_BRAKE or
float(model_desired_accel) < min_model_accel
):
return None
gap_factor = float(np.clip((float(lead.dRel) - LEAD_DEPART_ACCEL_HOLD_MIN_GAP) /
max(LEAD_DEPART_ACCEL_HOLD_FULL_GAP - LEAD_DEPART_ACCEL_HOLD_MIN_GAP, 0.1), 0.0, 1.0))
lead_factor = float(np.clip((lead_speed - LEAD_DEPART_ACCEL_HOLD_MIN_LEAD_SPEED) /
max(LEAD_DEPART_ACCEL_HOLD_FULL_LEAD_SPEED - LEAD_DEPART_ACCEL_HOLD_MIN_LEAD_SPEED, 0.1), 0.0, 1.0))
accel_cap = LEAD_DEPART_ACCEL_HOLD_MIN_ACCEL + (LEAD_DEPART_ACCEL_HOLD_MAX_ACCEL - LEAD_DEPART_ACCEL_HOLD_MIN_ACCEL) * np.clip(
0.55 * lead_factor + 0.45 * gap_factor, 0.0, 1.0)
return min(accel_cap, max(float(model_desired_accel), LEAD_DEPART_ACCEL_HOLD_MIN_ACCEL))
def get_low_speed_weak_lead_accel_cap(self, lead, v_ego):
if lead is None or not lead.status:
return None
lead_radar = bool(getattr(lead, "radar", False))
lead_prob = float(getattr(lead, "modelProb", 1.0 if lead_radar else 0.0))
if not lead_radar and lead_prob < LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MIN_MODEL_PROB:
return None
d_rel = float(getattr(lead, "dRel", 0.0))
lead_speed = max(float(getattr(lead, "vLead", 0.0)), 0.0)
lead_delta = lead_speed - float(v_ego)
lead_accel = float(getattr(lead, "aLeadK", 0.0))
lead_lateral = abs(float(getattr(lead, "yRel", 0.0)))
if (
float(v_ego) > LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MAX_EGO_SPEED or
d_rel > LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MAX_DISTANCE or
lead_speed > LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MAX_LEAD_SPEED or
lead_lateral > LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MAX_LATERAL_OFFSET or
lead_delta > LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MAX_LEAD_DELTA or
lead_accel > LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MAX_LEAD_ACCEL
):
return None
if (
float(v_ego) <= LOW_SPEED_WEAK_LEAD_ACCEL_CAP_STRONG_DEPART_MAX_EGO_SPEED and
d_rel >= LOW_SPEED_WEAK_LEAD_ACCEL_CAP_STRONG_DEPART_MIN_GAP and
lead_speed >= LOW_SPEED_WEAK_LEAD_ACCEL_CAP_STRONG_DEPART_MIN_LEAD_SPEED and
lead_delta >= LOW_SPEED_WEAK_LEAD_ACCEL_CAP_STRONG_DEPART_MIN_LEAD_DELTA and
lead_accel >= LOW_SPEED_WEAK_LEAD_ACCEL_CAP_STRONG_DEPART_MIN_LEAD_ACCEL
):
return None
distance_factor = float(np.clip(
(d_rel - LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MIN_DISTANCE) /
max(LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MAX_DISTANCE - LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MIN_DISTANCE, 0.1),
0.0, 1.0,
))
delta_factor = float(np.clip(
(lead_delta - LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MIN_LEAD_DELTA) /
max(LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MAX_LEAD_DELTA - LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MIN_LEAD_DELTA, 0.1),
0.0, 1.0,
))
accel_factor = float(np.clip(
(lead_accel - LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MIN_LEAD_ACCEL) /
max(LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MAX_LEAD_ACCEL - LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MIN_LEAD_ACCEL, 0.1),
0.0, 1.0,
))
cap_strength = float(np.clip(0.5 * distance_factor + 0.3 * delta_factor + 0.2 * accel_factor, 0.0, 1.0))
return LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MIN_ACCEL + (
LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MAX_ACCEL - LOW_SPEED_WEAK_LEAD_ACCEL_CAP_MIN_ACCEL
) * cap_strength
def get_standstill_stopped_lead_guard_cap(self, lead, v_ego, accel_min, stop_distance,
release_ready, confident_depart_ready):
if lead is None or not lead.status or release_ready or confident_depart_ready:
return None
if float(v_ego) > STANDSTILL_STOPPED_LEAD_GUARD_MAX_EGO_SPEED:
return None
lead_radar = bool(getattr(lead, "radar", False))
lead_prob = float(getattr(lead, "modelProb", 1.0 if lead_radar else 0.0))
if not lead_radar and lead_prob < STANDSTILL_STOPPED_LEAD_GUARD_MIN_MODEL_PROB:
return None
if abs(float(getattr(lead, "yRel", 0.0))) > STANDSTILL_STOPPED_LEAD_GUARD_MAX_LATERAL_OFFSET:
return None
lead_speed = max(float(getattr(lead, "vLead", 0.0)), 0.0)
lead_delta = lead_speed - float(v_ego)
max_distance = max(
STANDSTILL_STOPPED_LEAD_GUARD_MIN_DISTANCE,
float(stop_distance) + STANDSTILL_STOPPED_LEAD_GUARD_DISTANCE_MARGIN,
)
if (
float(getattr(lead, "dRel", float("inf"))) > max_distance or
lead_speed > STANDSTILL_STOPPED_LEAD_GUARD_MAX_LEAD_SPEED or
lead_delta > STANDSTILL_STOPPED_LEAD_GUARD_MAX_LEAD_DELTA
):
return None
distance_factor = float(np.clip((max_distance - float(lead.dRel)) /
max(max_distance - STANDSTILL_STOPPED_LEAD_GUARD_MIN_DISTANCE, 0.1),
0.0, 1.0))
speed_factor = float(np.clip(lead_speed / max(STANDSTILL_STOPPED_LEAD_GUARD_MAX_LEAD_SPEED, 0.1), 0.0, 1.0))
delta_factor = float(np.clip((STANDSTILL_STOPPED_LEAD_GUARD_MAX_LEAD_DELTA - lead_delta) /
max(STANDSTILL_STOPPED_LEAD_GUARD_MAX_LEAD_DELTA, 0.1),
0.0, 1.0))
hold_brake = STANDSTILL_STOPPED_LEAD_GUARD_MIN_BRAKE + 0.06 * distance_factor + 0.02 * speed_factor + 0.02 * delta_factor
hold_brake = float(np.clip(
hold_brake,
STANDSTILL_STOPPED_LEAD_GUARD_MIN_BRAKE,
STANDSTILL_STOPPED_LEAD_GUARD_MAX_BRAKE,
))
brake_floor = -hold_brake
return brake_floor if accel_min >= 0.0 else max(accel_min, brake_floor)
def is_stable_post_departure_pullaway(self, lead, v_ego, t_follow):
if lead is None or not lead.status or float(v_ego) < POST_DEPARTURE_FOLLOW_BYPASS_MIN_SPEED:
return False
lead_radar = bool(getattr(lead, "radar", False))
lead_prob = float(getattr(lead, "modelProb", 1.0 if lead_radar else 0.0))
if not lead_radar and lead_prob < POST_DEPARTURE_FOLLOW_BYPASS_MIN_MODEL_PROB:
return False
if abs(float(getattr(lead, "yRel", 0.0))) > CRUISE_TRACKED_LEAD_ACCEL_CAP_MAX_LATERAL_OFFSET:
return False
lead_delta = float(lead.vLead) - float(v_ego)
lead_accel = float(getattr(lead, "aLeadK", 0.0))
if (lead_delta < POST_DEPARTURE_FOLLOW_BYPASS_MIN_LEAD_DELTA or
lead_accel < POST_DEPARTURE_FOLLOW_BYPASS_MIN_LEAD_ACCEL):
return False
actual_headway = float(lead.dRel) / max(float(v_ego), 1e-3)
headway_margin = actual_headway - float(t_follow)
return headway_margin >= POST_DEPARTURE_FOLLOW_BYPASS_MIN_HEADWAY_MARGIN
def is_comfortable_accelerating_away_follow(self, lead, v_ego, t_follow):
if lead is None or not lead.status or float(v_ego) < POST_DEPARTURE_FOLLOW_BYPASS_MIN_SPEED:
return False
lead_radar = bool(getattr(lead, "radar", False))
lead_prob = float(getattr(lead, "modelProb", 1.0 if lead_radar else 0.0))
if not lead_radar and lead_prob < COMFORTABLE_PULLAWAY_FOLLOW_MIN_MODEL_PROB:
return False
if abs(float(getattr(lead, "yRel", 0.0))) > CRUISE_TRACKED_LEAD_ACCEL_CAP_MAX_LATERAL_OFFSET:
return False
lead_delta = float(lead.vLead) - float(v_ego)
lead_accel = float(getattr(lead, "aLeadK", 0.0))
if (lead_delta < COMFORTABLE_PULLAWAY_FOLLOW_MIN_LEAD_DELTA or
lead_accel < COMFORTABLE_PULLAWAY_FOLLOW_MIN_LEAD_ACCEL):
return False
actual_headway = float(lead.dRel) / max(float(v_ego), 1e-3)
headway_margin = actual_headway - float(t_follow)
return headway_margin >= COMFORTABLE_PULLAWAY_FOLLOW_MIN_HEADWAY_MARGIN
def post_departure_follow_settle_active(self, lead, v_ego, t_follow):
if lead is None or not lead.status:
return False
if time.monotonic() > self.post_departure_follow_settle_until:
self.post_departure_follow_settle_until = 0.0
return False
if float(v_ego) < POST_DEPARTURE_FOLLOW_SETTLE_MIN_SPEED:
return False
lead_radar = bool(getattr(lead, "radar", False))
lead_prob = float(getattr(lead, "modelProb", 1.0 if lead_radar else 0.0))
if not lead_radar and lead_prob < POST_DEPARTURE_FOLLOW_SETTLE_MIN_MODEL_PROB:
return False
if abs(float(getattr(lead, "yRel", 0.0))) > POST_DEPARTURE_FOLLOW_SETTLE_MAX_LATERAL_OFFSET:
return False
actual_headway = float(lead.dRel) / max(float(v_ego), 1e-3)
headway_margin = actual_headway - float(t_follow)
if headway_margin <= POST_DEPARTURE_FOLLOW_SETTLE_COMPLETE_HEADWAY_MARGIN:
self.post_departure_follow_settle_until = 0.0
return False
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
if lead_brake > POST_DEPARTURE_FOLLOW_SETTLE_MAX_LEAD_BRAKE:
self.post_departure_follow_settle_until = 0.0
return False
closing_speed = max(float(v_ego) - float(lead.vLead), 0.0)
if closing_speed > POST_DEPARTURE_FOLLOW_SETTLE_MAX_CLOSING_SPEED:
self.post_departure_follow_settle_until = 0.0
return False
return headway_margin >= POST_DEPARTURE_FOLLOW_SETTLE_MIN_HEADWAY_MARGIN
def is_spacious_low_closure_follow(self, lead, v_ego, t_follow):
if lead is None or not lead.status or float(v_ego) < CRUISE_TRACKED_LEAD_ACCEL_CAP_MIN_SPEED:
return False
lead_radar = bool(getattr(lead, "radar", False))
lead_prob = float(getattr(lead, "modelProb", 1.0 if lead_radar else 0.0))
if not lead_radar and lead_prob < SPACIOUS_TRACKED_FOLLOW_MIN_MODEL_PROB:
return False
if abs(float(getattr(lead, "yRel", 0.0))) > CRUISE_TRACKED_LEAD_ACCEL_CAP_MAX_LATERAL_OFFSET:
return False
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
if lead_brake > SPACIOUS_TRACKED_FOLLOW_MAX_LEAD_BRAKE:
return False
closing_speed = max(float(v_ego) - float(lead.vLead), 0.0)
if closing_speed > SPACIOUS_TRACKED_FOLLOW_MAX_CLOSING_SPEED:
return False
if self.raw_close_lead_needs_control(lead, v_ego):
return False
actual_headway = float(lead.dRel) / max(float(v_ego), 1e-3)
headway_margin = actual_headway - float(t_follow)
return headway_margin >= SPACIOUS_TRACKED_FOLLOW_MIN_HEADWAY_MARGIN
def spacious_follow_cap_bypass_active(self, lead, v_ego, t_follow, tracking_lead_active):
if not tracking_lead_active or time.monotonic() > self.spacious_follow_cap_bypass_until:
return False
return self.is_spacious_low_closure_follow(lead, v_ego, t_follow)
def get_lead_catchup_accel_cap(self, lead, v_ego, t_follow, current_source=None, tracking_lead_active=False):
if lead is None or not lead.status:
return None
if self.post_departure_follow_settle_active(lead, v_ego, t_follow):
return None
lead_radar = bool(getattr(lead, "radar", False))
lead_prob = float(getattr(lead, "modelProb", 1.0 if lead_radar else 0.0))
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
low_speed_follow_window = (
not lead_radar and
v_ego <= LOW_SPEED_FOLLOW_ACCEL_CAP_MAX_SPEED and
lead_prob >= LOW_SPEED_FOLLOW_ACCEL_CAP_MIN_MODEL_PROB and
lead_brake <= LOW_SPEED_FOLLOW_ACCEL_CAP_MAX_LEAD_BRAKE
)
if v_ego < LEAD_CATCHUP_ACCEL_MIN_EGO and not low_speed_follow_window:
return None
lead_delta = float(lead.vLead) - float(v_ego)
min_lead_delta = LOW_SPEED_FOLLOW_ACCEL_CAP_MIN_LEAD_DELTA if low_speed_follow_window else LEAD_CATCHUP_ACCEL_MIN_LEAD_DELTA
desired_gap = float(desired_follow_distance(v_ego, lead.vLead, t_follow))
if low_speed_follow_window:
gap_buffer = max(LOW_SPEED_FOLLOW_ACCEL_CAP_MAX_GAP_BUFFER_MIN,
LOW_SPEED_FOLLOW_ACCEL_CAP_MAX_GAP_BUFFER_GAIN * float(v_ego))
else:
gap_buffer = max(LEAD_CATCHUP_ACCEL_MAX_GAP_BUFFER_MIN,
LEAD_CATCHUP_ACCEL_MAX_GAP_BUFFER_GAIN * float(v_ego))
gap_error = float(lead.dRel) - desired_gap
radar_matched_follow_active = (
lead_radar and
tracking_lead_active and
self.lead_is_matched_follow_window(lead, v_ego, t_follow)
)
if radar_matched_follow_active and gap_error > (gap_buffer - RADAR_MATCHED_FOLLOW_CATCHUP_CAP_BUFFER_MARGIN):
return None
if (radar_matched_follow_active and current_source == "cruise" and
gap_error <= RADAR_MATCHED_FOLLOW_CATCHUP_HOLD_MAX_GAP_ERROR and
lead_delta < min_lead_delta):
return RADAR_MATCHED_FOLLOW_CATCHUP_HOLD_CAP
if lead_delta < min_lead_delta:
return None
if gap_error > gap_buffer:
return None
if tracking_lead_active and self.is_comfortable_accelerating_away_follow(lead, v_ego, t_follow):
return None
if current_source == "cruise" and self.spacious_follow_cap_bypass_active(lead, v_ego, t_follow, tracking_lead_active):
return None
if not low_speed_follow_window and self.is_stable_post_departure_pullaway(lead, v_ego, t_follow):
return None
# If the lead is already pace-matched or pulling away, keep any catch-up
# accel very small while we're near the follow target so we don't surge into
# the lead and immediately ask for brake again.
if low_speed_follow_window:
edge_cap = float(np.interp(lead_delta, [min_lead_delta, 0.0, 1.0, 2.0], [0.20, 0.24, 0.38, 0.55]))
near_cap = min(edge_cap, 0.16)
else:
edge_cap = float(np.interp(lead_delta, [-0.5, 0.0, 1.0], [0.16, 0.08, 0.02]))
near_cap = min(edge_cap, 0.03)
gap_factor = float(np.clip(max(gap_error, 0.0) / max(gap_buffer, 0.1), 0.0, 1.0))
return float(np.interp(gap_factor, [0.0, 1.0], [near_cap, edge_cap]))
def get_low_speed_follow_transition_brake_cap(self, lead, v_ego, t_follow, prev_output_a_target, output_a_target):
if lead is None or not lead.status:
return None
if bool(getattr(lead, "radar", False)):
return None
if not (LOW_SPEED_FOLLOW_TRANSITION_MIN_SPEED <= float(v_ego) <= LOW_SPEED_FOLLOW_TRANSITION_MAX_SPEED):
return None
if prev_output_a_target <= LOW_SPEED_FOLLOW_TRANSITION_PREV_ACCEL_MIN:
return None
if output_a_target >= LOW_SPEED_FOLLOW_TRANSITION_TARGET_BRAKE_MIN:
return None
lead_prob = float(getattr(lead, "modelProb", 0.0))
if lead_prob < LOW_SPEED_FOLLOW_TRANSITION_MIN_MODEL_PROB:
return None
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
if lead_brake > LOW_SPEED_FOLLOW_TRANSITION_MAX_LEAD_BRAKE:
return None
closing_speed = max(0.0, float(v_ego) - float(lead.vLead))
if closing_speed > LOW_SPEED_FOLLOW_TRANSITION_MAX_CLOSING_SPEED:
return None
desired_gap = float(desired_follow_distance(v_ego, lead.vLead, t_follow))
if float(lead.dRel) < desired_gap + LOW_SPEED_FOLLOW_TRANSITION_MIN_GAP_MARGIN:
return None
cap_decel = float(np.interp(
closing_speed,
[0.0, LOW_SPEED_FOLLOW_TRANSITION_MAX_CLOSING_SPEED],
[LOW_SPEED_FOLLOW_TRANSITION_MIN_BRAKE, LOW_SPEED_FOLLOW_TRANSITION_MAX_BRAKE],
))
return -cap_decel
def get_cruise_tracking_lead_accel_cap(self, lead, v_ego, t_follow, current_source, tracking_lead_active):
if lead is None or not lead.status or current_source != "cruise":
return None
if self.post_departure_follow_settle_active(lead, v_ego, t_follow):
return None
if not (CRUISE_TRACKED_LEAD_ACCEL_CAP_MIN_SPEED <= float(v_ego) <= CRUISE_TRACKED_LEAD_ACCEL_CAP_MAX_SPEED):
return None
lead_prob = float(getattr(lead, "modelProb", 1.0 if bool(getattr(lead, "radar", False)) else 0.0))
if not bool(getattr(lead, "radar", False)) and lead_prob < CRUISE_TRACKED_LEAD_ACCEL_CAP_MIN_MODEL_PROB:
return None
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
if lead_brake > CRUISE_TRACKED_LEAD_ACCEL_CAP_MAX_LEAD_BRAKE and not tracking_lead_active:
return None
if abs(float(getattr(lead, "yRel", 0.0))) > CRUISE_TRACKED_LEAD_ACCEL_CAP_MAX_LATERAL_OFFSET:
return None
lead_delta = float(lead.vLead) - float(v_ego)
if lead_delta > CRUISE_TRACKED_LEAD_ACCEL_CAP_MAX_PULLAWAY_SPEED:
return None
if tracking_lead_active and self.is_comfortable_accelerating_away_follow(lead, v_ego, t_follow):
return None
if self.spacious_follow_cap_bypass_active(lead, v_ego, t_follow, tracking_lead_active):
return None
closing_speed = max(float(v_ego) - float(lead.vLead), 0.0)
raw_close_lead = self.raw_close_lead_needs_control(lead, v_ego)
unresolved_slow_lead = (
closing_speed >= CRUISE_TRACKED_LEAD_ACCEL_CAP_UNRESOLVED_MIN_CLOSING_SPEED and
lead_delta <= CRUISE_TRACKED_LEAD_ACCEL_CAP_UNRESOLVED_MAX_LEAD_DELTA
)
if not tracking_lead_active and not raw_close_lead and not unresolved_slow_lead:
return None
# Don't let a spacious, nearly pace-matched tracked lead toggle this cap on
# and off while cruise remains the source. That creates the square-wave
# accel "surge / give up / surge" behavior seen in real logs.
actual_headway = float(lead.dRel) / max(float(v_ego), 1e-3)
headway_margin = actual_headway - float(t_follow)
tracking_only_follow = tracking_lead_active and not raw_close_lead and not unresolved_slow_lead
if (tracking_only_follow and
headway_margin > CRUISE_TRACKED_LEAD_ACCEL_CAP_TRACKING_ONLY_MAX_HEADWAY_ABOVE_TARGET and
closing_speed < CRUISE_TRACKED_LEAD_ACCEL_CAP_TRACKING_ONLY_MAX_CLOSING_SPEED and
lead_brake <= CRUISE_TRACKED_LEAD_ACCEL_CAP_TRACKING_ONLY_MAX_LEAD_BRAKE):
return None
desired_gap = float(desired_follow_distance(v_ego, lead.vLead, t_follow))
gap_error = float(lead.dRel) - desired_gap
gap_buffer = max(CRUISE_TRACKED_LEAD_ACCEL_CAP_MAX_GAP_BUFFER_MIN,
CRUISE_TRACKED_LEAD_ACCEL_CAP_MAX_GAP_BUFFER_GAIN * float(v_ego))
if gap_error > gap_buffer:
return None
# If the same lead is already accelerating away and we're no longer tight to
# the follow target, don't slam the accel cap back on just because lead_delta
# momentarily falls near the pull-away threshold. That produces the repeated
# 0.18 m/s^2 "surge / give up / surge" behavior seen in real logs.
lead_accel = float(getattr(lead, "aLeadK", 0.0))
if self.is_stable_post_departure_pullaway(lead, v_ego, t_follow) or (
lead_delta >= CRUISE_TRACKED_LEAD_ACCEL_CAP_ACCEL_AWAY_MIN_LEAD_DELTA and
lead_accel >= CRUISE_TRACKED_LEAD_ACCEL_CAP_ACCEL_AWAY_MIN and
gap_error >= CRUISE_TRACKED_LEAD_ACCEL_CAP_ACCEL_AWAY_MIN_GAP_MARGIN
):
return None
base_cap = float(np.interp(
lead_delta,
[-1.5, -0.5, 0.0, 0.5, CRUISE_TRACKED_LEAD_ACCEL_CAP_MAX_PULLAWAY_SPEED],
[0.0, 0.04, 0.08, 0.12, 0.16],
))
if raw_close_lead:
base_cap = min(base_cap, float(np.interp(closing_speed, [0.5, 1.5, 3.5], [0.10, 0.05, 0.0])))
else:
base_cap = min(base_cap, float(np.interp(closing_speed, [0.0, 1.0, 2.0], [0.18, 0.12, 0.06])))
if gap_error <= 0.0:
return max(0.0, base_cap)
gap_factor = float(np.clip(gap_error / max(gap_buffer, 0.1), 0.0, 1.0))
cap = min(CRUISE_TRACKED_LEAD_ACCEL_CAP_MAX_ACCEL, base_cap + 0.06 * gap_factor)
return max(0.0, cap)
def get_cruise_tracking_lead_accel_transition_target(self, lead, v_ego, t_follow,
prev_output_a_target, output_a_target,
current_source):
if lead is None or not lead.status or current_source != "cruise":
return None
if not (CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MIN_SPEED <= float(v_ego) <= CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MAX_SPEED):
return None
target_delta = float(output_a_target) - float(prev_output_a_target)
if target_delta < CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MIN_DELTA_A:
return None
lead_prob = float(getattr(lead, "modelProb", 1.0 if bool(getattr(lead, "radar", False)) else 0.0))
if not bool(getattr(lead, "radar", False)) and lead_prob < CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MIN_MODEL_PROB:
return None
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
if lead_brake > CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MAX_LEAD_BRAKE:
return None
if abs(float(getattr(lead, "yRel", 0.0))) > CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MAX_LATERAL_OFFSET:
return None
lead_delta = float(lead.vLead) - float(v_ego)
if lead_delta > CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MAX_PULLAWAY_SPEED:
return None
actual_headway = float(lead.dRel) / max(float(v_ego), 1e-3)
headway_margin = actual_headway - float(t_follow)
if headway_margin > CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MAX_HEADWAY_ABOVE_TARGET:
return None
positive_step = float(np.interp(
lead_delta,
[-1.0, 0.0, 1.0, CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MAX_PULLAWAY_SPEED],
[CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MIN_STEP,
0.08,
0.12,
CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MAX_STEP],
))
if headway_margin > 0.0:
headway_factor = float(np.clip(
headway_margin / max(CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MAX_HEADWAY_ABOVE_TARGET, 1e-3),
0.0,
1.0,
))
positive_step = float(np.interp(
headway_factor,
[0.0, 1.0],
[positive_step, CRUISE_TRACKED_LEAD_ACCEL_TRANSITION_MAX_STEP],
))
upper = float(prev_output_a_target) + positive_step
smoothed_target = float(min(output_a_target, upper))
return smoothed_target if smoothed_target < float(output_a_target) - 1e-6 else None
def lead_is_matched_follow_window(self, lead, v_ego, base_t_follow):
if lead is None or not lead.status or v_ego < STEADY_FOLLOW_SMOOTHING_MIN_SPEED:
return False
relative_speed = float(v_ego) - float(lead.vLead)
if not (STEADY_FOLLOW_BRAKE_CAP_MIN_REL_SPEED <= relative_speed <= STEADY_FOLLOW_SMOOTHING_MAX_CLOSING_SPEED):
return False
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
if lead_brake > STEADY_FOLLOW_SMOOTHING_MAX_LEAD_BRAKE:
return False
lead_radar = bool(getattr(lead, "radar", False))
lead_prob = float(getattr(lead, "modelProb", 1.0 if lead_radar else 0.0))
if not lead_radar and not is_radarless_matched_follow_window(
v_ego,
lead.dRel,
lead.vLead,
base_t_follow,
radar=lead_radar,
lead_brake=lead_brake,
lead_prob=lead_prob,
):
return False
actual_headway = float(lead.dRel) / max(float(v_ego), 1e-3)
if actual_headway < max(STEADY_FOLLOW_BRAKE_CAP_MIN_HEADWAY, float(base_t_follow) - STEADY_FOLLOW_SMOOTHING_HEADWAY_BELOW_TARGET):
return False
if actual_headway > float(base_t_follow) + STEADY_FOLLOW_BRAKE_CAP_MAX_HEADWAY_ABOVE_TARGET:
return False
return True
def get_matched_follow_control_lead(self, v_ego, t_follow):
if self.mpc.source == 'lead1' and self.lead_is_matched_follow_window(self.lead_two, v_ego, t_follow):
return self.lead_two
if self.lead_is_matched_follow_window(self.lead_one, v_ego, t_follow):
return self.lead_one
if self.lead_is_matched_follow_window(self.lead_two, v_ego, t_follow):
return self.lead_two
return None
def get_follow_control_lead(self, lead_control_active, v_ego, t_follow, *, allow_optional_far_lead_logic=True):
if allow_optional_far_lead_logic:
matched_follow_lead = self.get_matched_follow_control_lead(v_ego, t_follow)
if matched_follow_lead is not None:
return matched_follow_lead
if not lead_control_active:
return None
if self.lead_one.status:
return self.lead_one
if self.lead_two.status:
return self.lead_two
return None
def lead_is_spacious_brake_cap_window(self, lead, v_ego, base_t_follow):
if lead is None or not lead.status or v_ego < STEADY_FOLLOW_SMOOTHING_MIN_SPEED:
return False
relative_speed = float(v_ego) - float(lead.vLead)
if not (STEADY_FOLLOW_BRAKE_CAP_MIN_REL_SPEED <= relative_speed <= STEADY_FOLLOW_BRAKE_CAP_MAX_CLOSING_SPEED):
return False
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
if lead_brake > STEADY_FOLLOW_BRAKE_CAP_SPACIOUS_MAX_LEAD_BRAKE:
return False
lead_radar = bool(getattr(lead, "radar", False))
lead_prob = float(getattr(lead, "modelProb", 1.0 if lead_radar else 0.0))
if not lead_radar and lead_prob < STEADY_FOLLOW_SMOOTHING_MIN_MODEL_PROB:
return False
actual_headway = float(lead.dRel) / max(float(v_ego), 1e-3)
if actual_headway < max(float(base_t_follow) + STEADY_FOLLOW_BRAKE_CAP_SPACIOUS_HEADWAY_MARGIN,
STEADY_FOLLOW_BRAKE_CAP_SPACIOUS_MIN_HEADWAY):
return False
if actual_headway > float(base_t_follow) + STEADY_FOLLOW_BRAKE_CAP_MAX_HEADWAY_ABOVE_TARGET:
return False
return True
def get_matched_follow_brake_cap(self, lead, v_ego, base_t_follow):
if not (
self.lead_is_matched_follow_window(lead, v_ego, base_t_follow) or
self.lead_is_spacious_brake_cap_window(lead, v_ego, base_t_follow)
):
return None
relative_speed = float(v_ego) - float(lead.vLead)
actual_headway = float(lead.dRel) / max(float(v_ego), 1e-3)
cap_decel = float(np.interp(
relative_speed,
[STEADY_FOLLOW_BRAKE_CAP_MIN_REL_SPEED, 0.0, STEADY_FOLLOW_BRAKE_CAP_MAX_CLOSING_SPEED],
[STEADY_FOLLOW_BRAKE_CAP_OPENING_DECEL,
STEADY_FOLLOW_BRAKE_CAP_ZERO_REL_SPEED_DECEL,
STEADY_FOLLOW_BRAKE_CAP_MAX_DECEL],
))
headway_deficit = float(np.clip((float(base_t_follow) - actual_headway) / STEADY_FOLLOW_SMOOTHING_HEADWAY_BELOW_TARGET, 0.0, 1.0))
cap_decel = min(STEADY_FOLLOW_BRAKE_CAP_MAX_DECEL, cap_decel + 0.05 * headway_deficit)
return -cap_decel
def get_far_lead_brake_cap(self, lead, v_ego, base_t_follow):
if lead is None or not lead.status or v_ego < STEADY_FOLLOW_SMOOTHING_MIN_SPEED:
return None
if bool(getattr(lead, "radar", False)):
return None
lead_prob = float(getattr(lead, "modelProb", 0.0))
if lead_prob < FAR_LEAD_COMFORT_BRAKE_CAP_MIN_MODEL_PROB:
return None
relative_speed = float(v_ego) - float(lead.vLead)
if not (FAR_LEAD_COMFORT_BRAKE_CAP_MIN_CLOSING_SPEED <= relative_speed <= FAR_LEAD_COMFORT_BRAKE_CAP_MAX_CLOSING_SPEED):
return None
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
if lead_brake > FAR_LEAD_COMFORT_BRAKE_CAP_MAX_LEAD_BRAKE:
return None
actual_headway = float(lead.dRel) / max(float(v_ego), 1e-3)
headway_margin = actual_headway - float(base_t_follow)
if float(lead.dRel) < FAR_LEAD_COMFORT_BRAKE_CAP_MIN_DISTANCE or headway_margin < FAR_LEAD_COMFORT_BRAKE_CAP_MIN_HEADWAY_MARGIN:
return None
ttc = float(lead.dRel) / max(relative_speed, 1e-3)
if ttc < FAR_LEAD_COMFORT_BRAKE_CAP_MIN_TTC:
return None
cap_decel = float(np.interp(
relative_speed,
[FAR_LEAD_COMFORT_BRAKE_CAP_MIN_CLOSING_SPEED, FAR_LEAD_COMFORT_BRAKE_CAP_MAX_CLOSING_SPEED],
[FAR_LEAD_COMFORT_BRAKE_CAP_MIN_DECEL, FAR_LEAD_COMFORT_BRAKE_CAP_MAX_DECEL],
))
relax_decel = float(np.interp(
headway_margin,
[FAR_LEAD_COMFORT_BRAKE_CAP_MIN_HEADWAY_MARGIN, FAR_LEAD_COMFORT_BRAKE_CAP_FULL_HEADWAY_MARGIN],
[0.0, FAR_LEAD_COMFORT_BRAKE_CAP_FULL_RELAX_DECEL],
))
return -max(0.0, cap_decel - relax_decel)
def get_matched_follow_transition_target(self, lead, v_ego, base_t_follow, prev_output_a_target, output_a_target,
current_source, tracking_lead_active):
if lead is None or not lead.status:
return None
low_speed_extension_active = (
bool(tracking_lead_active) and
current_source == "cruise" and
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_SPEED <= float(v_ego) < LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_SPEED
)
if float(v_ego) < MATCHED_FOLLOW_TRANSITION_MIN_SPEED and not low_speed_extension_active:
return None
lead_prob = float(getattr(lead, "modelProb", 0.0))
min_model_prob = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_MODEL_PROB if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_MIN_MODEL_PROB
if lead_prob < min_model_prob:
return None
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
max_lead_brake = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_LEAD_BRAKE if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_MAX_LEAD_BRAKE
if lead_brake > max_lead_brake:
return None
relative_speed = float(v_ego) - float(lead.vLead)
if not (STEADY_FOLLOW_BRAKE_CAP_MIN_REL_SPEED <= relative_speed <= STEADY_FOLLOW_SMOOTHING_MAX_CLOSING_SPEED):
return None
closing_speed = max(0.0, relative_speed)
max_closing_speed = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_CLOSING_SPEED if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_MAX_CLOSING_SPEED
if closing_speed > max_closing_speed:
return None
ttc = float(lead.dRel) / max(closing_speed, 0.1) if closing_speed > 0.1 else float("inf")
min_ttc = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_TTC if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_MIN_TTC
if ttc < min_ttc:
return None
actual_headway = float(lead.dRel) / max(float(v_ego), 1e-3)
headway_margin = actual_headway - float(base_t_follow)
min_headway_margin = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_HEADWAY_MARGIN if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_MIN_HEADWAY_MARGIN
full_headway_margin = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_FULL_HEADWAY_MARGIN if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_FULL_HEADWAY_MARGIN
if headway_margin < min_headway_margin:
return None
if actual_headway > float(base_t_follow) + STEADY_FOLLOW_BRAKE_CAP_MAX_HEADWAY_ABOVE_TARGET:
return None
target_delta = float(output_a_target) - float(prev_output_a_target)
if low_speed_extension_active:
if float(prev_output_a_target) < LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_TARGET:
return None
if float(output_a_target) < LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_TARGET:
return None
if abs(target_delta) < LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_DELTA_A:
return None
elif abs(target_delta) < 1e-3:
return None
headway_factor = float(np.clip(
(headway_margin - min_headway_margin) /
max(full_headway_margin - min_headway_margin, 1e-3),
0.0,
1.0,
))
min_positive_step = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_POSITIVE_STEP if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_MIN_POSITIVE_STEP
max_positive_step = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_POSITIVE_STEP if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_MAX_POSITIVE_STEP
positive_step = float(np.interp(
max(float(lead.vLead) - float(v_ego), 0.0),
[0.0, 1.0],
[min_positive_step, max_positive_step],
))
min_negative_step = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_NEGATIVE_STEP if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_MIN_NEGATIVE_STEP
max_negative_step = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_NEGATIVE_STEP if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_MAX_NEGATIVE_STEP
negative_step = float(np.interp(
closing_speed,
[0.0, max_closing_speed],
[min_negative_step, max_negative_step],
))
# The more space we still have, the less abrupt the comfort path should be.
positive_step = float(np.interp(headway_factor, [0.0, 1.0], [positive_step, min_positive_step]))
negative_step = float(np.interp(headway_factor, [0.0, 1.0], [negative_step, min_negative_step]))
if float(prev_output_a_target) * float(output_a_target) < 0.0:
sign_cross_step = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_SIGN_CROSS_STEP if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_SIGN_CROSS_STEP
positive_step = min(positive_step, sign_cross_step)
negative_step = min(negative_step, sign_cross_step)
lower = float(prev_output_a_target) - negative_step
upper = float(prev_output_a_target) + positive_step
smoothed_target = float(np.clip(output_a_target, lower, upper))
return smoothed_target if abs(smoothed_target - float(output_a_target)) > 1e-6 else None
def get_near_duplicate_lead_transition_target(self, lead, v_ego, base_t_follow,
prev_output_a_target, output_a_target,
current_source, tracking_lead_active):
if lead is None or not lead.status:
return None
if current_source not in ("cruise", "lead0", "lead1"):
return None
if current_source == "cruise" and not tracking_lead_active:
return None
if not (self.lead_one.status and self.lead_two.status):
return None
identical_radar_duplicates = self.mpc.leads_share_identical_radar_track(self.lead_one, self.lead_two)
if not self.mpc.leads_are_near_duplicates(self.lead_one, self.lead_two, v_ego):
return None
low_speed_extension_active = bool(
tracking_lead_active and
current_source in ("cruise", "lead0", "lead1") and
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_SPEED <= float(v_ego) < NEAR_DUPLICATE_LEAD_TRANSITION_MIN_SPEED
)
if float(v_ego) < LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_SPEED:
return None
if float(v_ego) < NEAR_DUPLICATE_LEAD_TRANSITION_MIN_SPEED and not low_speed_extension_active:
return None
lead_radar = bool(getattr(lead, "radar", False))
lead_prob = float(getattr(lead, "modelProb", 1.0 if lead_radar else 0.0))
if not lead_radar and lead_prob < NEAR_DUPLICATE_LEAD_TRANSITION_MIN_MODEL_PROB:
return None
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
if lead_brake > NEAR_DUPLICATE_LEAD_TRANSITION_MAX_LEAD_BRAKE:
return None
relative_speed = float(v_ego) - float(lead.vLead)
closing_speed = max(0.0, relative_speed)
if closing_speed > NEAR_DUPLICATE_LEAD_TRANSITION_MAX_CLOSING_SPEED:
return None
ttc = float(lead.dRel) / max(closing_speed, 0.1) if closing_speed > 0.1 else float("inf")
if ttc < NEAR_DUPLICATE_LEAD_TRANSITION_MIN_TTC:
return None
actual_headway = float(lead.dRel) / max(float(v_ego), 1e-3)
if actual_headway < max(0.0, float(base_t_follow) - NEAR_DUPLICATE_LEAD_TRANSITION_MIN_HEADWAY_BELOW_TARGET):
return None
max_headway_above_target = NEAR_DUPLICATE_LEAD_TRANSITION_MAX_HEADWAY_ABOVE_TARGET
if low_speed_extension_active and identical_radar_duplicates:
max_headway_above_target += LOW_SPEED_IDENTICAL_RADAR_DUPLICATE_TRANSITION_EXTRA_HEADWAY
if actual_headway > float(base_t_follow) + max_headway_above_target:
return None
target_delta = float(output_a_target) - float(prev_output_a_target)
if abs(target_delta) < NEAR_DUPLICATE_LEAD_TRANSITION_MIN_DELTA_A:
return None
if low_speed_extension_active and (float(prev_output_a_target) < LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_TARGET or
float(output_a_target) < LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_TARGET):
return None
positive_step = NEAR_DUPLICATE_LEAD_TRANSITION_POSITIVE_STEP
negative_step = NEAR_DUPLICATE_LEAD_TRANSITION_NEGATIVE_STEP
if float(prev_output_a_target) * float(output_a_target) < 0.0:
positive_step = min(positive_step, NEAR_DUPLICATE_LEAD_TRANSITION_SIGN_CROSS_STEP)
negative_step = min(negative_step, NEAR_DUPLICATE_LEAD_TRANSITION_SIGN_CROSS_STEP)
lower = float(prev_output_a_target) - negative_step
upper = float(prev_output_a_target) + positive_step
smoothed_target = float(np.clip(output_a_target, lower, upper))
return smoothed_target if abs(smoothed_target - float(output_a_target)) > 1e-6 else None
def get_mild_follow_zero_cross_guard_target(self, lead, v_ego, base_t_follow,
prev_output_a_target, output_a_target,
current_source, tracking_lead_active):
if lead is None or not lead.status:
return None
if current_source not in ("cruise", "lead0", "lead1") and not tracking_lead_active:
return None
if not (MILD_FOLLOW_ZERO_CROSS_GUARD_MIN_SPEED <= float(v_ego) <= MILD_FOLLOW_ZERO_CROSS_GUARD_MAX_SPEED):
return None
target_delta = float(output_a_target) - float(prev_output_a_target)
if abs(target_delta) < MILD_FOLLOW_ZERO_CROSS_GUARD_MIN_DELTA_A:
return None
lead_radar = bool(getattr(lead, "radar", False))
lead_prob = float(getattr(lead, "modelProb", 1.0 if lead_radar else 0.0))
if not lead_radar and lead_prob < MILD_FOLLOW_ZERO_CROSS_GUARD_MIN_MODEL_PROB:
return None
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
if lead_brake > MILD_FOLLOW_ZERO_CROSS_GUARD_MAX_LEAD_BRAKE:
return None
closing_speed = max(0.0, float(v_ego) - float(lead.vLead))
if closing_speed > MILD_FOLLOW_ZERO_CROSS_GUARD_MAX_CLOSING_SPEED:
return None
ttc = float(lead.dRel) / max(closing_speed, 0.1) if closing_speed > 0.1 else float("inf")
if ttc < MILD_FOLLOW_ZERO_CROSS_GUARD_MIN_TTC:
return None
actual_headway = float(lead.dRel) / max(float(v_ego), 1e-3)
headway_margin = actual_headway - float(base_t_follow)
if headway_margin < MILD_FOLLOW_ZERO_CROSS_GUARD_MIN_HEADWAY_MARGIN:
return None
deadband = float(np.interp(
headway_margin,
[MILD_FOLLOW_ZERO_CROSS_GUARD_MIN_HEADWAY_MARGIN, MILD_FOLLOW_ZERO_CROSS_GUARD_FULL_HEADWAY_MARGIN],
[MILD_FOLLOW_ZERO_CROSS_GUARD_MIN_DEADBAND, MILD_FOLLOW_ZERO_CROSS_GUARD_MAX_DEADBAND],
))
if float(prev_output_a_target) * float(output_a_target) < 0.0:
return 0.0
if abs(float(output_a_target)) < deadband and abs(float(prev_output_a_target)) < deadband + 0.06:
return 0.0
return None
def get_duplicate_slow_lead_brake_hold_target(self, lead, v_ego, base_t_follow,
prev_output_a_target, output_a_target,
current_source, tracking_lead_active):
if lead is None or not lead.status:
return None
if current_source not in ("cruise", "lead0", "lead1") and not tracking_lead_active:
return None
if not (self.lead_one.status and self.lead_two.status):
return None
if (
abs(float(self.lead_one.dRel) - float(self.lead_two.dRel)) > DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MAX_DREL_DIFF or
abs(float(self.lead_one.vRel) - float(self.lead_two.vRel)) > DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MAX_VREL_DIFF
):
return None
if float(v_ego) < DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MIN_SPEED:
return None
lead_prob = float(getattr(lead, "modelProb", 0.0))
if bool(getattr(lead, "radar", False)) or lead_prob < DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MIN_MODEL_PROB:
return None
prev_brake = max(0.0, -float(prev_output_a_target))
if prev_brake < DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MIN_PREV_DECEL:
return None
target_delta = float(output_a_target) - float(prev_output_a_target)
if target_delta < DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MIN_DELTA_A:
return None
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
if lead_brake > DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MAX_LEAD_BRAKE:
return None
closing_speed = max(0.0, float(v_ego) - float(lead.vLead))
if closing_speed < DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MIN_CLOSING_SPEED:
return None
actual_headway = float(lead.dRel) / max(float(v_ego), 1e-3)
if actual_headway > float(base_t_follow) + DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MAX_HEADWAY_ABOVE_TARGET:
return None
positive_step = float(np.interp(
closing_speed,
[DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MIN_CLOSING_SPEED, 1.5, 4.0, 8.0],
[DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MIN_POSITIVE_STEP,
0.16,
0.22,
DUPLICATE_SLOW_LEAD_BRAKE_HOLD_MAX_POSITIVE_STEP],
))
if float(prev_output_a_target) * float(output_a_target) < 0.0:
positive_step = min(positive_step, DUPLICATE_SLOW_LEAD_BRAKE_HOLD_SIGN_CROSS_STEP)
upper = float(prev_output_a_target) + positive_step
smoothed_target = float(min(float(output_a_target), upper))
return smoothed_target if abs(smoothed_target - float(output_a_target)) > 1e-6 else None
def get_tracked_vision_model_brake_floor(self, lead, v_ego, accel_min, t_follow, model_desired):
if lead is None or not lead.status or bool(getattr(lead, "radar", False)):
return None
if float(v_ego) < TRACKED_VISION_MODEL_FLOOR_MIN_SPEED:
return None
lead_prob = float(getattr(lead, "modelProb", 0.0))
if lead_prob < TRACKED_VISION_MODEL_FLOOR_MIN_MODEL_PROB:
return None
model_brake = max(0.0, -float(model_desired))
if model_brake < TRACKED_VISION_MODEL_FLOOR_MIN_MODEL_DECEL:
return None
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
reaction_t = max(self.CP.longitudinalActuatorDelay, self.dt)
projected_closing_speed = max(0.0, float(v_ego) - float(lead.vLead)) + lead_brake * reaction_t
if projected_closing_speed < TRACKED_VISION_MODEL_FLOOR_MIN_CLOSING_SPEED:
return None
desired_gap = float(desired_follow_distance(v_ego, lead.vLead, t_follow))
gap_margin = float(lead.dRel) - desired_gap
max_gap_margin = max(TRACKED_VISION_MODEL_FLOOR_MAX_GAP_BUFFER_MIN,
TRACKED_VISION_MODEL_FLOOR_MAX_GAP_BUFFER_GAIN * float(v_ego))
if gap_margin < TRACKED_VISION_MODEL_FLOOR_MIN_GAP_MARGIN or gap_margin > max_gap_margin:
return None
projected_ttc = float(lead.dRel) / max(projected_closing_speed, 0.1)
if projected_ttc > TRACKED_VISION_MODEL_FLOOR_MAX_TTC:
return None
floor_decel = float(np.interp(
model_brake,
[TRACKED_VISION_MODEL_FLOOR_MIN_MODEL_DECEL, 1.6],
[TRACKED_VISION_MODEL_FLOOR_MIN_DECEL, TRACKED_VISION_MODEL_FLOOR_MAX_DECEL],
))
floor_decel += float(np.interp(lead_brake, [0.0, 0.8], [0.0, TRACKED_VISION_MODEL_FLOOR_LEAD_BRAKE_MAX]))
return max(accel_min, -min(TRACKED_VISION_MODEL_FLOOR_MAX_DECEL, floor_decel))
def get_tracked_vision_model_brake_cap(self, lead, v_ego, t_follow, model_desired):
if lead is None or not lead.status or bool(getattr(lead, "radar", False)):
return None
if not (TRACKED_VISION_MODEL_CAP_MIN_SPEED <= float(v_ego) <= TRACKED_VISION_MODEL_CAP_MAX_SPEED):
return None
lead_prob = float(getattr(lead, "modelProb", 0.0))
if lead_prob < TRACKED_VISION_MODEL_CAP_MIN_MODEL_PROB:
return None
model_brake = max(0.0, -float(model_desired))
if model_brake > TRACKED_VISION_MODEL_CAP_MAX_MODEL_DECEL:
return None
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
if lead_brake > TRACKED_VISION_MODEL_CAP_MAX_LEAD_BRAKE:
return None
reaction_t = max(self.CP.longitudinalActuatorDelay, self.dt)
projected_closing_speed = max(0.0, float(v_ego) - float(lead.vLead)) + lead_brake * reaction_t
if not (TRACKED_VISION_MODEL_CAP_MIN_CLOSING_SPEED <= projected_closing_speed <= TRACKED_VISION_MODEL_CAP_MAX_CLOSING_SPEED):
return None
desired_gap = float(desired_follow_distance(v_ego, lead.vLead, t_follow))
gap_margin = float(lead.dRel) - desired_gap
max_gap_margin = max(TRACKED_VISION_MODEL_CAP_MAX_GAP_BUFFER_MIN,
TRACKED_VISION_MODEL_CAP_MAX_GAP_BUFFER_GAIN * float(v_ego))
if gap_margin < TRACKED_VISION_MODEL_CAP_MIN_GAP_MARGIN or gap_margin > max_gap_margin:
return None
projected_ttc = float(lead.dRel) / max(projected_closing_speed, 0.1)
if projected_ttc < TRACKED_VISION_MODEL_CAP_MIN_TTC:
return None
cap_decel = float(np.interp(
model_brake,
[0.0, TRACKED_VISION_MODEL_CAP_MAX_MODEL_DECEL],
[TRACKED_VISION_MODEL_CAP_MIN_DECEL, TRACKED_VISION_MODEL_CAP_MAX_DECEL],
))
cap_decel += float(np.interp(
projected_closing_speed,
[TRACKED_VISION_MODEL_CAP_MIN_CLOSING_SPEED, TRACKED_VISION_MODEL_CAP_MAX_CLOSING_SPEED],
[0.0, 0.08],
))
return -min(TRACKED_VISION_MODEL_CAP_MAX_DECEL, cap_decel)
@staticmethod
def raw_close_lead_needs_control(lead, v_ego):
if lead is None or not lead.status:
return False
d_rel = max(float(lead.dRel), 0.0)
lead_speed = max(float(getattr(lead, "vLead", 0.0)), 0.0)
closing_speed = float(v_ego - lead.vLead)
lead_braking = float(lead.aLeadK) < -0.5
centered_lead = abs(float(getattr(lead, "yRel", 0.0))) <= RAW_LEAD_LOW_SPEED_HOLD_MAX_LATERAL_OFFSET
if (
centered_lead and
float(v_ego) <= RAW_LEAD_LOW_SPEED_HOLD_MAX_EGO_SPEED and
lead_speed <= RAW_LEAD_LOW_SPEED_HOLD_MAX_LEAD_SPEED and
d_rel <= RAW_LEAD_LOW_SPEED_HOLD_MAX_DISTANCE and
closing_speed >= RAW_LEAD_LOW_SPEED_HOLD_MIN_CLOSING_SPEED
):
return True
if closing_speed <= RAW_LEAD_SAFETY_MIN_CLOSING_SPEED and not lead_braking:
return False
dynamic_distance = max(RAW_LEAD_SAFETY_DISTANCE, 3.0 * float(v_ego))
ttc = d_rel / max(closing_speed, 0.1) if closing_speed > 0.1 else float("inf")
return d_rel < dynamic_distance and (ttc < RAW_LEAD_SAFETY_TTC or lead_braking)
def update(self, sm, starpilot_toggles):
if self.is_preap:
self._preap_param_frame += 1
if self._preap_params is not None and (self._preap_param_frame % 20) == 0:
self.nap_adaptive_accel = self._preap_params.get_bool("NAPAdaptiveAccel")
self.generation = getattr(starpilot_toggles, "model_version", None)
self.mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc'
self.mpc.mode = 'acc'
if not self.mlsim:
self.mpc.mode = self.mode
if len(sm['carControl'].orientationNED) == 3:
accel_coast = get_coast_accel(sm['carControl'].orientationNED[1])
else:
accel_coast = ACCEL_MAX
v_ego = get_planner_v_ego(self.CP, sm['carState'])
scene_v_ego = float(sm['carState'].vEgo)
v_cruise = sm['starpilotPlan'].vCruise
if not np.isfinite(v_cruise):
cloudlog.error(f"Longitudinal planner received non-finite vCruise={v_cruise}, falling back to v_ego={v_ego:.2f}")
v_cruise = max(v_ego, 0.0)
v_cruise_initialized = sm['carState'].vCruise != V_CRUISE_UNSET
long_control_off = sm['controlsState'].longControlState == LongCtrlState.off
force_slow_decel = sm['controlsState'].forceDecel
# Reset current state when not engaged, or user is controlling the speed
reset_state = long_control_off if self.CP.openpilotLongitudinalControl else not sm['selfdriveState'].enabled
# PCM cruise speed may be updated a few cycles later, check if initialized
reset_state = reset_state or not v_cruise_initialized
# No change cost when user is controlling the speed, or when standstill
prev_accel_constraint = not (reset_state or sm['carState'].standstill)
if self.mpc.mode == 'acc':
accel_limits = [sm['starpilotPlan'].minAcceleration, sm['starpilotPlan'].maxAcceleration]
steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg
accel_limits_turns = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_limits, self.CP)
accel_limits_turns[0] = max(get_vehicle_min_accel(self.CP, v_ego), accel_limits_turns[0])
else:
accel_limits = [ACCEL_MIN, ACCEL_MAX]
accel_limits_turns = [ACCEL_MIN, ACCEL_MAX]
if reset_state:
self.v_desired_filter.x = v_ego
# Clip aEgo to cruise limits to prevent large accelerations when becoming active
self.a_desired = np.clip(sm['carState'].aEgo, accel_limits[0], accel_limits[1])
self.model_allow_throttle = True
# Prevent divergence, smooth in current v_ego
self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego))
# Compute model v_ego error
self.v_model_error = self.get_model_speed_error(sm['modelV2'], v_ego)
x, v, a, j, throttle_prob = self.parse_model(sm['modelV2'], self.v_model_error, v_ego, starpilot_toggles)
# Don't clip at low speeds since throttle_prob doesn't account for creep. Use
# hysteresis here because raw gasPressProb noise can chatter the throttle gate.
if v_ego <= MIN_ALLOW_THROTTLE_SPEED:
self.model_allow_throttle = True
elif self.model_allow_throttle:
self.model_allow_throttle = throttle_prob > ALLOW_THROTTLE_DISABLE_THRESHOLD
else:
self.model_allow_throttle = throttle_prob > ALLOW_THROTTLE_ENABLE_THRESHOLD
self.allow_throttle = self.model_allow_throttle and not sm['starpilotPlan'].disableThrottle
if not self.allow_throttle:
clipped_accel_coast = max(accel_coast, accel_limits_turns[0])
# Hold the output cap to the physical coasting limit until throttle is
# allowed again. Relaxing back toward positive accel while the gate is
# still closed can stall downhill coastdown well above the target speed.
accel_limits_turns[1] = min(accel_limits_turns[1], clipped_accel_coast)
no_throttle_output_max = accel_limits_turns[1]
if force_slow_decel:
v_cruise = 0.0
# clip limits, cannot init MPC outside of bounds
accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired + 0.05)
accel_limits_turns[1] = max(accel_limits_turns[1], self.a_desired - 0.05)
tracking_lead = bool(sm['starpilotPlan'].trackingLead)
self.lead_one = sm['radarState'].leadOne
self.lead_two = sm['radarState'].leadTwo
raw_close_lead_control = any(self.raw_close_lead_needs_control(lead, scene_v_ego) for lead in (self.lead_one, self.lead_two))
# StarPilot trackingLead is debounce/model-length based. Keep a raw close-lead
# safety path so ACC/chill does not ignore a visible lead during that debounce.
lead_control_active = tracking_lead or raw_close_lead_control
lead_one_active = bool(self.lead_one.status and lead_control_active)
effective_t_follow = self.get_dynamic_t_follow(sm['starpilotPlan'].tFollow, self.lead_one if lead_one_active else None, v_ego)
if self.is_preap and self.nap_adaptive_accel and lead_one_active:
follow_limit = get_preap_follow_limit(v_ego)
if follow_limit is not None:
safe_dist = get_safe_obstacle_distance(v_ego, effective_t_follow)
lead_dist_ratio = float(self.lead_one.dRel) / max(safe_dist, 1.0)
cap_strength = float(np.clip(1.0 - (lead_dist_ratio - 1.2) / 0.3, 0.0, 1.0))
if cap_strength > 0.0:
accel_limits_turns[1] = min(
accel_limits_turns[1],
accel_limits_turns[1] * (1.0 - cap_strength) + follow_limit * cap_strength,
)
lead_dist = self.lead_one.dRel if lead_one_active else 50.0
# Smooth lead distance (EMA) to avoid chatter in thresholds
alpha = max(0.02, min(0.15, 0.05 + 0.002 * v_ego))
if self.lead_dist_f is None:
self.lead_dist_f = float(lead_dist)
else:
self.lead_dist_f += alpha * (float(lead_dist) - self.lead_dist_f)
# Lead stability estimation and recent-brake timer
now_t = time.monotonic()
# relative speed (ego - lead) positive when closing
v_rel = (v_ego - self.lead_one.vLead) if lead_one_active else 0.0
if self.prev_lead_dist is None:
d_rel_dot = 0.0
else:
d_rel_dot = (lead_dist - self.prev_lead_dist) / max(self.dt, 1e-3)
self.prev_lead_dist = lead_dist
# Remember time of last non-trivial model brake risk
if 'raw_brake_max' in locals() and raw_brake_max is not None and raw_brake_max > 0.02:
self.last_big_brake_t = now_t
# Stable lead heuristic (short window, cheap to compute)
recently_braked = (now_t - self.last_big_brake_t) < 0.7
self.stable_lead = (
lead_one_active and
abs(v_rel) < 0.5 and
abs(d_rel_dot) < 0.5 and
not recently_braked
)
if lead_one_active and self.mpc.source == "cruise":
lead_delta = float(self.lead_one.vLead) - float(scene_v_ego)
lead_brake = max(0.0, -float(getattr(self.lead_one, "aLeadK", 0.0)))
if (
self.is_spacious_low_closure_follow(self.lead_one, scene_v_ego, effective_t_follow) and
lead_brake <= SPACIOUS_TRACKED_FOLLOW_MAX_LEAD_BRAKE and
(
self.is_stable_post_departure_pullaway(self.lead_one, scene_v_ego, effective_t_follow) or
lead_delta >= SPACIOUS_TRACKED_FOLLOW_LATCH_MIN_LEAD_DELTA
)
):
self.spacious_follow_cap_bypass_until = now_t + SPACIOUS_TRACKED_FOLLOW_LATCH_TIME
elif not lead_one_active:
self.spacious_follow_cap_bypass_until = 0.0
# Calculate scene uncertainty from model desire prediction entropy and disengage predictions
uncertainty = 0.0
if hasattr(sm['modelV2'], 'meta'):
# Desire prediction entropy (maneuver uncertainty), normalized to [0, 1]
desire_entropy = 0.0
if hasattr(sm['modelV2'].meta, 'desirePrediction'):
desire_probs = sm['modelV2'].meta.desirePrediction
if len(desire_probs) > 1:
probs = np.asarray(desire_probs, dtype=float)
total = float(np.sum(probs))
if total > 1e-6:
p = probs / total
entropy = -np.sum(p * np.log(p + 1e-10))
max_entropy = np.log(len(p))
desire_entropy = float(entropy / max(max_entropy, 1e-6)) # normalized entropy in [0,1]
else:
desire_entropy = 0.0 # guard against all-zero vector
# Disengage prediction risk (intervention likelihood)
disengage_risk = 0.0
raw_brake_max = -1.0
lam = -1.0
if hasattr(sm['modelV2'].meta, 'disengagePredictions'):
# Use brake press probabilities as primary risk indicator
brake_probs = sm['modelV2'].meta.disengagePredictions.brakePressProbs
if len(brake_probs) > 0:
# Exponentially decayed max over the full horizon
probs = np.asarray(brake_probs, dtype=float)
# Clip tiny brake blips so they don't inflate uncertainty
if float(np.max(probs)) < 0.015:
probs = probs * 0.5
raw_brake_max = float(np.max(probs))
# Time vector assuming model horizon step = DT_MDL
t = np.arange(len(probs), dtype=float) * DT_MDL
lam = 0.6 # decay rate per second (tunable: 0.50.9 typical)
weights = np.exp(-lam * t)
disengage_risk = float(np.max(probs * weights))
# Combined uncertainty metric (range roughly 0..2), with dual-track filtering
raw_uncertainty = desire_entropy + disengage_risk
# Update filters
self.uncert_slow.update(raw_uncertainty)
self.uncert_fast.update(raw_uncertainty)
# Use a more permissive track for accel decisions
uncertainty = self.uncert_slow.x
uncertainty_accel = min(self.uncert_slow.x, self.uncert_fast.x)
# --- Slope-based panic bypass ---
if self._uncert_last_t is None:
uncert_slope = 0.0
else:
dt_u = max(1e-3, now_t - self._uncert_last_t)
uncert_slope = (uncertainty - self._uncert_last) / dt_u
self._uncert_last = uncertainty
self._uncert_last_t = now_t
panic_close_window = False
closing_fast = False
desired_gap = None
closing_speed = 0.0
if lead_one_active:
desired_gap = float(desired_follow_distance(v_ego, self.lead_one.vLead, effective_t_follow))
scene_desired_gap = float(desired_follow_distance(scene_v_ego, self.lead_one.vLead, effective_t_follow))
close_gap_window = max(UNCERT_PANIC_MAX_GAP_BUFFER_MIN,
UNCERT_PANIC_MAX_GAP_BUFFER_GAIN * float(v_ego))
panic_close_window = float(self.lead_one.dRel) <= scene_desired_gap + close_gap_window
closing_speed = max(0.0, scene_v_ego - self.lead_one.vLead)
closing_fast = closing_speed >= max(
UNCERT_PANIC_MIN_CLOSING_SPEED,
UNCERT_PANIC_MIN_CLOSING_SPEED_GAIN * float(scene_v_ego),
)
# Only bypass lead smoothing when we're closing meaningfully and already
# near the follow window. Far or nearly pace-matched leads should stay on
# the smoothed path so the planner doesn't flip-flop between accel and brake.
panic_bypass = panic_close_window and closing_fast and (
uncert_slope > UNCERT_SLOPE_TRIG or uncertainty >= UNCERT_MAG_TRIG
)
prioritize_smooth_following = bool(getattr(starpilot_toggles, "prioritize_smooth_following", False))
allow_complex_follow_logic = not prioritize_smooth_following
steady_follow_filter_floor = 0.0
if allow_complex_follow_logic and lead_one_active and desired_gap is not None and not panic_bypass:
lead_brake = max(0.0, -float(getattr(self.lead_one, "aLeadK", 0.0)))
lead_radar = bool(getattr(self.lead_one, "radar", False))
lead_prob = float(getattr(self.lead_one, "modelProb", 1.0 if lead_radar else 0.0))
actual_headway = float(self.lead_one.dRel) / max(scene_v_ego, 1e-3)
matched_follow_window = (
is_radarless_matched_follow_window(
scene_v_ego,
self.lead_one.dRel,
self.lead_one.vLead,
effective_t_follow,
radar=lead_radar,
lead_brake=lead_brake,
lead_prob=lead_prob,
) or (
lead_radar and
scene_v_ego >= STEADY_FOLLOW_SMOOTHING_MIN_SPEED and
STEADY_FOLLOW_SMOOTHING_MIN_CLOSING_SPEED <= closing_speed <= STEADY_FOLLOW_SMOOTHING_MAX_CLOSING_SPEED and
actual_headway >= max(STEADY_FOLLOW_SMOOTHING_MIN_HEADWAY,
effective_t_follow - STEADY_FOLLOW_SMOOTHING_HEADWAY_BELOW_TARGET) and
actual_headway <= effective_t_follow + STEADY_FOLLOW_SMOOTHING_HEADWAY_ABOVE_TARGET and
lead_brake <= STEADY_FOLLOW_SMOOTHING_MAX_LEAD_BRAKE
)
)
if matched_follow_window:
steady_follow_filter_floor = STEADY_FOLLOW_SMOOTHING_FILTER_FACTOR_FLOOR
if panic_bypass:
if now_t - self._panic_bypass_log_t > 5.0:
self._panic_bypass_log_t = now_t
try:
cloudlog.warning(
"LON_SLOPE close bypass: "
f"slope={uncert_slope:.3f}/s uncertainty={uncertainty:.3f} "
f"v_ego={v_ego:.2f} v_rel={(v_ego - self.lead_one.vLead) if lead_one_active else 0.0:.2f} "
f"lead_dist={self.lead_dist_f if self.lead_dist_f is not None else -1:.2f}"
)
except Exception:
pass
personality = get_longitudinal_personality(sm)
self.mpc.set_weights(sm['starpilotPlan'].accelerationJerk,
sm['starpilotPlan'].dangerJerk,
sm['starpilotPlan'].speedJerk,
prev_accel_constraint,
personality=personality,
v_ego=v_ego,
lead_dist=self.lead_dist_f if lead_one_active and self.lead_dist_f is not None else 50.0,
uncertainty=uncertainty,
panic_bypass=panic_bypass,
filter_time_factor_floor=steady_follow_filter_floor)
self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1])
self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired)
# After deciding the MPC mode via get_mpc_mode(), ensure MPC uses that mode when not mlsim
dec_mpc_mode = self.get_mpc_mode()
if not self.mlsim:
self.mpc.mode = dec_mpc_mode
self.mpc.update(sm['radarState'], v_cruise, x, v, a, j,
sm['starpilotPlan'].dangerFactor, effective_t_follow,
personality=personality, tracking_lead=lead_control_active,
optional_far_lead_comfort=allow_complex_follow_logic)
self.a_desired_trajectory_full = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.a_solution)
self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution)
self.a_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.a_solution)
self.j_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC[:-1], self.mpc.j_solution)
# TODO counter is only needed because radar is glitchy, remove once radar is gone
self.fcw = should_publish_planner_fcw(self.mpc.crash_cnt, sm['carState'], sm['radarState'])
if self.fcw:
cloudlog.info("FCW triggered")
# Safety checks for rubber-banding mitigation
max_jerk = np.max(np.abs(self.mpc.j_solution))
max_accel_change = np.max(np.abs(np.diff(self.mpc.a_solution)))
if max_jerk > 5.0: # m/s^3
cloudlog.warning(f"High jerk detected: {max_jerk:.2f} m/s^3")
if max_accel_change > 2.0: # m/s^2
cloudlog.warning(f"High acceleration change: {max_accel_change:.2f} m/s^2")
# Interpolate 0.05 seconds and save as starting point for next iteration
a_prev = self.a_desired
self.a_desired = float(np.interp(self.dt, CONTROL_N_T_IDX, self.a_desired_trajectory))
self.v_desired_filter.x = self.v_desired_filter.x + self.dt * (self.a_desired + a_prev) / 2.0
# Anticipatory pre-brake to avoid "coming in hot" when closing on a lead
if allow_complex_follow_logic and lead_one_active:
rel_v = max(0.0, v_ego - self.lead_one.vLead)
# dynamic time headway adds a small buffer when uncertainty is elevated
base_th = max(1.6, effective_t_follow)
th = base_th + 0.6 * max(0.0, uncertainty - 0.42)
desired_gap = th * v_ego
if (self.lead_dist_f is not None and self.lead_dist_f < desired_gap and rel_v > 0.5):
k_rel, k_unc = 0.04, 0.20
pre_brake = k_rel * rel_v + k_unc * max(0.0, uncertainty - 0.42)
pre_brake = min(pre_brake, 0.06)
self.a_desired = float(self.a_desired - pre_brake)
# Small deadzone around zero accel to kill micro-dithers
if -0.05 < self.a_desired < 0.05:
self.a_desired = 0.0
classic_model = bool(getattr(starpilot_toggles, "classic_model", False))
tinygrad_model = bool(getattr(starpilot_toggles, "tinygrad_model", False))
experimental_mlsim = bool(tinygrad_model and self.mlsim and self.mode != 'acc')
action_t = self.CP.longitudinalActuatorDelay + DT_MDL
prev_output_a_target = float(self.output_a_target)
if classic_model:
output_a_target, output_should_stop = get_accel_from_plan_classic(
self.CP, self.v_desired_trajectory, self.a_desired_trajectory, starpilot_toggles.vEgoStopping)
elif tinygrad_model:
output_a_target_mpc, output_should_stop_mpc = get_accel_from_plan(
self.v_desired_trajectory, self.a_desired_trajectory,
action_t=action_t, vEgoStopping=starpilot_toggles.vEgoStopping)
output_a_target_e2e = sm['modelV2'].action.desiredAcceleration
output_should_stop_e2e = sm['modelV2'].action.shouldStop
if self.mode == 'acc' or self.generation == 'v9':
output_a_target = output_a_target_mpc
output_should_stop = output_should_stop_mpc
else:
output_a_target = min(output_a_target_mpc, output_a_target_e2e)
output_should_stop = output_should_stop_e2e or output_should_stop_mpc
else:
output_a_target, output_should_stop = get_accel_from_plan(
self.v_desired_trajectory, self.a_desired_trajectory,
action_t=action_t, vEgoStopping=starpilot_toggles.vEgoStopping)
comfort_output_accel_min = get_vehicle_min_accel(self.CP, v_ego) if experimental_mlsim else accel_limits_turns[0]
vision_cap_accel_min = min(comfort_output_accel_min, get_vehicle_min_accel(self.CP, v_ego))
output_accel_min = comfort_output_accel_min
model_desired_accel = float(sm['modelV2'].action.desiredAcceleration)
if not tracking_lead:
pretracking_vision_caps = []
for lead in (self.lead_one, self.lead_two):
if lead.status and not bool(getattr(lead, "radar", False)):
pretracking_cap = self.get_vision_untracked_slow_lead_cap(lead, v_ego, vision_cap_accel_min)
if pretracking_cap is not None:
pretracking_vision_caps.append((pretracking_cap, lead))
if pretracking_vision_caps:
pretracking_vision_cap, pretracking_vision_lead = min(pretracking_vision_caps, key=lambda cap_and_lead: cap_and_lead[0])
lead_brake = max(0.0, -float(getattr(pretracking_vision_lead, "aLeadK", 0.0)))
immediate_pretracking_cap = (
pretracking_vision_cap <= -VISION_UNTRACKED_SLOW_LEAD_IMMEDIATE_DECEL or
float(getattr(pretracking_vision_lead, "dRel", float("inf"))) <= VISION_UNTRACKED_SLOW_LEAD_IMMEDIATE_DISTANCE or
lead_brake >= VISION_UNTRACKED_SLOW_LEAD_IMMEDIATE_LEAD_BRAKE or
float(getattr(pretracking_vision_lead, "vLead", float("inf"))) <= VISION_UNTRACKED_SLOW_LEAD_RELAXED_MAX_LEAD_SPEED
)
if immediate_pretracking_cap:
self.untracked_slow_lead_confirm_t = VISION_UNTRACKED_SLOW_LEAD_CONFIRM_TIME
else:
self.untracked_slow_lead_confirm_t = min(
self.untracked_slow_lead_confirm_t + self.dt,
VISION_UNTRACKED_SLOW_LEAD_CONFIRM_TIME,
)
if self.untracked_slow_lead_confirm_t >= VISION_UNTRACKED_SLOW_LEAD_CONFIRM_TIME:
self.a_desired = min(self.a_desired, pretracking_vision_cap)
output_a_target = min(output_a_target, pretracking_vision_cap)
else:
self.untracked_slow_lead_confirm_t = 0.0
else:
self.untracked_slow_lead_confirm_t = 0.0
close_lead_caps = []
tracked_vision_approach_caps = []
vision_low_speed_stop_active = False
vision_brake_cap_active = False
if lead_control_active:
for lead in (self.lead_one, self.lead_two):
cap = self.get_close_lead_brake_cap(lead, v_ego, output_accel_min)
if cap is not None:
close_lead_caps.append(cap)
slow_stop_cap = self.get_vision_slow_stopped_lead_cap(lead, v_ego, vision_cap_accel_min, effective_t_follow)
if slow_stop_cap is not None:
close_lead_caps.append(slow_stop_cap)
vision_brake_cap_active = True
approach_cap = self.get_vision_lead_approach_cap(lead, v_ego, vision_cap_accel_min, effective_t_follow)
if approach_cap is not None:
tracked_vision_approach_caps.append((
approach_cap,
self.tracked_vision_lead_approach_needs_immediate_brake(lead, v_ego, approach_cap),
))
low_speed_stop_cap, low_speed_stop_active = self.get_vision_low_speed_stop_buffer_cap(lead, v_ego, vision_cap_accel_min)
if low_speed_stop_cap is not None:
close_lead_caps.append(low_speed_stop_cap)
vision_brake_cap_active = True
vision_low_speed_stop_active |= low_speed_stop_active
if tracked_vision_approach_caps:
if any(immediate for _, immediate in tracked_vision_approach_caps):
self.vision_lead_approach_confirm_t = VISION_LEAD_APPROACH_CONFIRM_TIME
else:
self.vision_lead_approach_confirm_t = min(
self.vision_lead_approach_confirm_t + self.dt,
VISION_LEAD_APPROACH_CONFIRM_TIME,
)
if self.vision_lead_approach_confirm_t >= VISION_LEAD_APPROACH_CONFIRM_TIME:
close_lead_caps.append(min(cap for cap, _ in tracked_vision_approach_caps))
vision_brake_cap_active = True
else:
self.vision_lead_approach_confirm_t = 0.0
if close_lead_caps:
close_lead_brake_cap = min(close_lead_caps)
self.a_desired = min(self.a_desired, close_lead_brake_cap)
output_a_target = min(output_a_target, close_lead_brake_cap)
standstill_nudge_gap = max(float(getattr(starpilot_toggles, "stop_distance", STOP_DISTANCE)), STOP_DISTANCE) - 0.5
moving_leads = [lead for lead in (self.lead_one, self.lead_two)
if lead.status and
lead.vLead > STANDSTILL_LEAD_NUDGE_MIN_SPEED and lead.dRel >= standstill_nudge_gap]
accelerating_nudge_lead = any(
lead.status and
float(getattr(lead, "vLead", 0.0)) > STANDSTILL_LEAD_NUDGE_MIN_SPEED and
float(getattr(lead, "aLeadK", 0.0)) >= STANDSTILL_LEAD_NUDGE_MIN_LEAD_ACCEL and
float(getattr(lead, "dRel", 0.0)) >= standstill_nudge_gap
for lead in (self.lead_one, self.lead_two)
)
confident_depart_detected = any(self.is_confident_lead_depart(lead, float(sm['carState'].vEgo))
for lead in (self.lead_one, self.lead_two))
lead_depart_ready = any(
lead.status and
lead.vLead >= STANDSTILL_LEAD_DEPART_MIN_LEAD_SPEED and
lead.dRel >= standstill_nudge_gap + STANDSTILL_LEAD_DEPART_MIN_GAP_MARGIN
for lead in (self.lead_one, self.lead_two)
)
depart_safety_veto = (not bool(getattr(starpilot_toggles, "radar_takeoffs", False))
and self.has_offcenter_radar_depart_conflict(sm))
if (
lead_control_active and
sm['carState'].standstill and
not depart_safety_veto and
not bool(getattr(sm['starpilotPlan'], 'forcingStop', False)) and
not bool(getattr(sm['starpilotPlan'], 'redLight', False)) and
confident_depart_detected
):
self.confident_lead_depart_elapsed = min(
LEAD_DEPART_CONFIDENT_CONFIRM_TIME,
self.confident_lead_depart_elapsed + self.dt,
)
else:
self.confident_lead_depart_elapsed = 0.0
confident_depart_ready = (
confident_depart_detected and
self.confident_lead_depart_elapsed >= LEAD_DEPART_CONFIDENT_CONFIRM_TIME
)
standstill_stopped_lead_guard_cap = None
standstill_guard_lead_present = any(bool(getattr(lead, "status", False)) for lead in (self.lead_one, self.lead_two))
if standstill_guard_lead_present and (bool(sm['carState'].standstill) or float(sm['carState'].vEgo) <= STANDSTILL_STOPPED_LEAD_GUARD_MAX_EGO_SPEED):
release_ready = bool(lead_depart_ready or confident_depart_ready)
standstill_stopped_lead_guard_caps = [
cap for cap in (
self.get_standstill_stopped_lead_guard_cap(
self.lead_one,
float(sm['carState'].vEgo),
output_accel_min,
standstill_nudge_gap,
release_ready,
confident_depart_ready,
),
self.get_standstill_stopped_lead_guard_cap(
self.lead_two,
float(sm['carState'].vEgo),
output_accel_min,
standstill_nudge_gap,
release_ready,
confident_depart_ready,
),
) if cap is not None
]
if standstill_stopped_lead_guard_caps:
standstill_stopped_lead_guard_cap = min(standstill_stopped_lead_guard_caps)
output_should_stop = True
if lead_control_active and sm['carState'].standstill and moving_leads and not depart_safety_veto:
output_a_target = max(output_a_target, STANDSTILL_LEAD_NUDGE_ACCEL)
if (
lead_control_active and
sm['carState'].standstill and
(confident_depart_ready or lead_depart_ready) and
not depart_safety_veto and
not bool(getattr(sm['starpilotPlan'], 'forcingStop', False)) and
not bool(getattr(sm['starpilotPlan'], 'redLight', False)) and
(confident_depart_ready or model_desired_accel >= STANDSTILL_LEAD_DEPART_MIN_MODEL_ACCEL)
):
vision_low_speed_stop_active = False
output_should_stop = False
output_a_target = max(output_a_target, STANDSTILL_LEAD_DEPART_MIN_ACCEL)
self.post_departure_follow_settle_until = now_t + POST_DEPARTURE_FOLLOW_SETTLE_LATCH_TIME
if lead_control_active and lead_depart_ready and not depart_safety_veto and not output_should_stop and float(sm['carState'].vEgo) <= STANDSTILL_LEAD_DEPART_MAX_EGO_SPEED:
output_a_target = max(output_a_target, STANDSTILL_LEAD_DEPART_MIN_ACCEL)
self.post_departure_follow_settle_until = now_t + POST_DEPARTURE_FOLLOW_SETTLE_LATCH_TIME
if depart_safety_veto or output_should_stop or bool(getattr(sm['starpilotPlan'], 'forcingStop', False)) or bool(getattr(sm['starpilotPlan'], 'redLight', False)):
self.lead_depart_accel_hold_until = 0.0
lead_depart_accel_floor = None
if lead_control_active and not output_should_stop and not depart_safety_veto:
lead_depart_accel_floors = [
floor for floor in (
self.get_lead_depart_accel_floor(self.lead_one, scene_v_ego, model_desired_accel),
self.get_lead_depart_accel_floor(self.lead_two, scene_v_ego, model_desired_accel),
) if floor is not None
]
if lead_depart_accel_floors:
lead_depart_accel_floor = max(lead_depart_accel_floors)
if sm['carState'].standstill:
self.lead_depart_accel_hold_until = now_t + LEAD_DEPART_ACCEL_HOLD_TIME
lead_depart_accel_hold_active = (
lead_depart_accel_floor is not None and
now_t < self.lead_depart_accel_hold_until and
float(sm['carState'].vEgo) <= LEAD_DEPART_ACCEL_HOLD_MAX_EGO_SPEED
)
low_speed_weak_lead_accel_cap = None
if not output_should_stop:
low_speed_weak_lead_accel_caps = [
cap for cap in (
self.get_low_speed_weak_lead_accel_cap(self.lead_one, scene_v_ego),
self.get_low_speed_weak_lead_accel_cap(self.lead_two, scene_v_ego),
) if cap is not None
]
if low_speed_weak_lead_accel_caps:
low_speed_weak_lead_accel_cap = min(low_speed_weak_lead_accel_caps)
close_stop_active = bool(output_should_stop or vision_low_speed_stop_active)
close_stop_hold_cap = None
if lead_control_active and close_stop_active:
close_stop_hold_caps = [
cap for cap in (
self.get_vision_close_stop_hold_cap(self.lead_one, v_ego, output_accel_min, close_stop_active),
self.get_vision_close_stop_hold_cap(self.lead_two, v_ego, output_accel_min, close_stop_active),
) if cap is not None
]
if close_stop_hold_caps:
close_stop_hold_cap = min(close_stop_hold_caps)
close_release_hold_cap = None
if lead_control_active and not close_stop_active:
close_release_hold_caps = [
cap for cap in (
self.get_vision_close_release_hold_cap(self.lead_one, v_ego, vision_cap_accel_min, close_stop_active),
self.get_vision_close_release_hold_cap(self.lead_two, v_ego, vision_cap_accel_min, close_stop_active),
) if cap is not None
]
if close_release_hold_caps:
close_release_hold_cap = min(close_release_hold_caps)
close_settle_guard_active = bool(
output_should_stop or
vision_low_speed_stop_active or
sm['controlsState'].longControlState == LongCtrlState.stopping
)
close_settle_cap = None
if lead_control_active:
close_settle_caps = [
cap for cap in (
self.get_vision_close_settle_cap(self.lead_one, v_ego, output_accel_min, close_settle_guard_active),
self.get_vision_close_settle_cap(self.lead_two, v_ego, output_accel_min, close_settle_guard_active),
) if cap is not None
]
if close_settle_caps:
close_settle_cap = min(close_settle_caps)
close_final_guard_cap = None
if lead_control_active:
close_final_guard_caps = [
cap for cap in (
self.get_vision_close_final_guard_cap(self.lead_one, v_ego, output_accel_min),
self.get_vision_close_final_guard_cap(self.lead_two, v_ego, output_accel_min),
) if cap is not None
]
if close_final_guard_caps:
close_final_guard_cap = min(close_final_guard_caps)
if allow_complex_follow_logic and lead_one_active:
lead_catchup_accel_cap = self.get_lead_catchup_accel_cap(
self.lead_one,
scene_v_ego,
effective_t_follow,
current_source=self.mpc.source,
tracking_lead_active=tracking_lead,
)
if lead_catchup_accel_cap is not None:
self.a_desired = min(self.a_desired, lead_catchup_accel_cap)
output_a_target = min(output_a_target, lead_catchup_accel_cap)
if lead_control_active and np.isfinite(v_cruise) and any(lead.status for lead in (self.lead_one, self.lead_two)):
# Keep follow/catchup behavior from pulling past the cruise target. Using the
# same action horizon as the planner preserves normal accel farther below set speed.
cruise_accel_cap = (v_cruise - v_ego + 0.01) / max(action_t, self.dt)
output_a_target = min(output_a_target, cruise_accel_cap)
if vision_brake_cap_active:
output_accel_min = min(output_accel_min, vision_cap_accel_min)
follow_control_lead = None
if allow_complex_follow_logic:
follow_control_lead = self.get_follow_control_lead(
lead_control_active,
scene_v_ego,
effective_t_follow,
allow_optional_far_lead_logic=True,
)
if allow_complex_follow_logic and follow_control_lead is not None and not panic_bypass:
if not output_should_stop and not vision_low_speed_stop_active:
tracked_vision_model_brake_floor = self.get_tracked_vision_model_brake_floor(
follow_control_lead,
scene_v_ego,
output_accel_min,
effective_t_follow,
model_desired_accel,
)
if tracked_vision_model_brake_floor is not None:
self.a_desired = min(self.a_desired, tracked_vision_model_brake_floor)
output_a_target = min(output_a_target, tracked_vision_model_brake_floor)
if allow_complex_follow_logic:
matched_follow_brake_cap = self.get_matched_follow_brake_cap(follow_control_lead, scene_v_ego, effective_t_follow)
if matched_follow_brake_cap is not None:
self.a_desired = max(self.a_desired, matched_follow_brake_cap)
output_a_target = max(output_a_target, matched_follow_brake_cap)
if not close_lead_caps and not output_should_stop and not vision_low_speed_stop_active:
low_speed_transition_brake_cap = self.get_low_speed_follow_transition_brake_cap(
follow_control_lead,
scene_v_ego,
effective_t_follow,
prev_output_a_target,
output_a_target,
)
if low_speed_transition_brake_cap is not None:
self.a_desired = max(self.a_desired, low_speed_transition_brake_cap)
output_a_target = max(output_a_target, low_speed_transition_brake_cap)
comfort_lead = self.lead_two if self.mpc.source == 'lead1' and self.lead_two.status else self.lead_one
if allow_complex_follow_logic and comfort_lead is not None and not panic_bypass:
far_lead_brake_cap = self.get_far_lead_brake_cap(comfort_lead, scene_v_ego, effective_t_follow)
if far_lead_brake_cap is not None:
self.a_desired = max(self.a_desired, far_lead_brake_cap)
output_a_target = max(output_a_target, far_lead_brake_cap)
if allow_complex_follow_logic and follow_control_lead is not None and not panic_bypass and not output_should_stop and not vision_low_speed_stop_active:
tracked_vision_model_brake_cap = self.get_tracked_vision_model_brake_cap(
follow_control_lead,
scene_v_ego,
effective_t_follow,
model_desired_accel,
)
if tracked_vision_model_brake_cap is not None:
self.a_desired = max(self.a_desired, tracked_vision_model_brake_cap)
output_a_target = max(output_a_target, tracked_vision_model_brake_cap)
if allow_complex_follow_logic and follow_control_lead is not None and not panic_bypass and not output_should_stop and not vision_low_speed_stop_active:
matched_follow_transition_target = self.get_matched_follow_transition_target(
follow_control_lead,
scene_v_ego,
effective_t_follow,
prev_output_a_target,
output_a_target,
self.mpc.source,
bool(getattr(sm["starpilotPlan"], "trackingLead", False)),
)
if matched_follow_transition_target is not None:
if matched_follow_transition_target < output_a_target:
self.a_desired = min(self.a_desired, matched_follow_transition_target)
else:
self.a_desired = max(self.a_desired, matched_follow_transition_target)
output_a_target = matched_follow_transition_target
if allow_complex_follow_logic and comfort_lead is not None and not panic_bypass and not output_should_stop and not vision_low_speed_stop_active:
near_duplicate_transition_target = self.get_near_duplicate_lead_transition_target(
comfort_lead,
scene_v_ego,
effective_t_follow,
prev_output_a_target,
output_a_target,
self.mpc.source,
bool(getattr(sm["starpilotPlan"], "trackingLead", False)),
)
if near_duplicate_transition_target is not None:
if near_duplicate_transition_target < output_a_target:
self.a_desired = min(self.a_desired, near_duplicate_transition_target)
else:
self.a_desired = max(self.a_desired, near_duplicate_transition_target)
output_a_target = near_duplicate_transition_target
duplicate_slow_lead_brake_hold_target = self.get_duplicate_slow_lead_brake_hold_target(
comfort_lead,
scene_v_ego,
effective_t_follow,
prev_output_a_target,
output_a_target,
self.mpc.source,
bool(getattr(sm["starpilotPlan"], "trackingLead", False)),
)
if duplicate_slow_lead_brake_hold_target is not None:
if duplicate_slow_lead_brake_hold_target < output_a_target:
self.a_desired = min(self.a_desired, duplicate_slow_lead_brake_hold_target)
else:
self.a_desired = max(self.a_desired, duplicate_slow_lead_brake_hold_target)
output_a_target = duplicate_slow_lead_brake_hold_target
if allow_complex_follow_logic and follow_control_lead is not None and not panic_bypass and not output_should_stop and not vision_low_speed_stop_active:
cruise_tracking_lead_accel_cap = self.get_cruise_tracking_lead_accel_cap(
follow_control_lead,
scene_v_ego,
effective_t_follow,
self.mpc.source,
tracking_lead,
)
if cruise_tracking_lead_accel_cap is not None:
self.a_desired = min(self.a_desired, cruise_tracking_lead_accel_cap)
output_a_target = min(output_a_target, cruise_tracking_lead_accel_cap)
cruise_tracking_lead_transition_target = self.get_cruise_tracking_lead_accel_transition_target(
follow_control_lead,
scene_v_ego,
effective_t_follow,
prev_output_a_target,
output_a_target,
self.mpc.source,
)
if cruise_tracking_lead_transition_target is not None:
self.a_desired = min(self.a_desired, cruise_tracking_lead_transition_target)
output_a_target = min(output_a_target, cruise_tracking_lead_transition_target)
mild_follow_zero_cross_guard_target = self.get_mild_follow_zero_cross_guard_target(
follow_control_lead,
scene_v_ego,
effective_t_follow,
prev_output_a_target,
output_a_target,
self.mpc.source,
tracking_lead,
)
if mild_follow_zero_cross_guard_target is not None:
output_a_target = mild_follow_zero_cross_guard_target
output_accel_max = no_throttle_output_max if not self.allow_throttle else accel_limits_turns[1]
output_a_target = float(np.clip(output_a_target, output_accel_min, output_accel_max))
if close_stop_hold_cap is not None:
self.a_desired = min(self.a_desired, close_stop_hold_cap)
output_a_target = min(output_a_target, close_stop_hold_cap)
if standstill_stopped_lead_guard_cap is not None:
self.a_desired = min(self.a_desired, standstill_stopped_lead_guard_cap)
output_a_target = min(output_a_target, standstill_stopped_lead_guard_cap)
if close_settle_cap is not None:
self.a_desired = min(self.a_desired, close_settle_cap)
output_a_target = min(output_a_target, close_settle_cap)
if close_final_guard_cap is not None:
self.a_desired = min(self.a_desired, close_final_guard_cap)
output_a_target = min(output_a_target, close_final_guard_cap)
if close_release_hold_cap is not None:
self.a_desired = min(self.a_desired, close_release_hold_cap)
output_a_target = min(output_a_target, close_release_hold_cap)
if depart_safety_veto:
self.a_desired = min(self.a_desired, 0.0)
output_a_target = min(output_a_target, 0.0)
if sm['carState'].standstill:
output_should_stop = True
if lead_depart_accel_hold_active:
output_a_target = max(output_a_target, lead_depart_accel_floor)
if low_speed_weak_lead_accel_cap is not None:
self.a_desired = min(self.a_desired, low_speed_weak_lead_accel_cap)
output_a_target = min(output_a_target, low_speed_weak_lead_accel_cap)
if (
lead_control_active and
(bool(sm['carState'].standstill) or float(sm['carState'].vEgo) <= STANDSTILL_STOPPED_LEAD_GUARD_MAX_EGO_SPEED) and
output_should_stop and
accelerating_nudge_lead and
not depart_safety_veto
):
output_a_target = max(output_a_target, STANDSTILL_LEAD_NUDGE_ACCEL)
force_stop_handoff = bool(
getattr(sm['starpilotPlan'], 'forcingStop', False) and
not lead_control_active and
(
float(getattr(sm['starpilotPlan'], 'forcingStopLength', float('inf'))) < 1.0 or
float(getattr(sm['starpilotPlan'], 'vCruise', float('inf'))) <= FORCE_STOP_HANDOFF_MAX_VCRUISE
)
)
if force_stop_handoff:
output_should_stop = True
manual_stop_resume_override = self._update_manual_stop_resume_override(sm)
if manual_stop_resume_override:
output_a_target = max(output_a_target, MANUAL_STOP_RESUME_OVERRIDE_MIN_ACCEL)
output_should_stop = False
self.output_a_target = output_a_target
self.output_should_stop = bool(output_should_stop or vision_low_speed_stop_active)
def publish(self, sm, pm):
plan_send = messaging.new_message('longitudinalPlan')
plan_send.valid = sm.all_checks(service_list=['carState', 'controlsState', 'selfdriveState', 'radarState'])
longitudinalPlan = plan_send.longitudinalPlan
longitudinalPlan.modelMonoTime = sm.logMonoTime['modelV2']
longitudinalPlan.processingDelay = (plan_send.logMonoTime / 1e9) - sm.logMonoTime['modelV2']
longitudinalPlan.solverExecutionTime = self.mpc.solve_time
longitudinalPlan.speeds = self.v_desired_trajectory.tolist()
longitudinalPlan.accels = self.a_desired_trajectory.tolist()
longitudinalPlan.jerks = self.j_desired_trajectory.tolist()
longitudinalPlan.hasLead = sm['radarState'].leadOne.status
longitudinalPlan.longitudinalPlanSource = self.mpc.source
longitudinalPlan.fcw = self.fcw
longitudinalPlan.aTarget = float(self.output_a_target)
force_stop_handoff = bool(
sm['starpilotPlan'].forcingStop and (
sm['starpilotPlan'].forcingStopLength < 1.0 or
sm['starpilotPlan'].vCruise <= FORCE_STOP_HANDOFF_MAX_VCRUISE
)
)
longitudinalPlan.shouldStop = bool(self.output_should_stop) or force_stop_handoff
longitudinalPlan.allowBrake = True
longitudinalPlan.allowThrottle = bool(self.allow_throttle)
pm.send('longitudinalPlan', plan_send)