Files
StarPilot/starpilot/controls/lib/conditional_chill_mode.py
T
firestar5683 afc8c6fb4b Mr. Freeze
2026-06-11 11:34:12 -05:00

337 lines
12 KiB
Python

#!/usr/bin/env python3
import time
from openpilot.common.constants import CV
from openpilot.starpilot.common.experimental_state import (
CCStatus,
is_manual_cc_status,
restore_persisted_cc_state,
)
class ConditionalChillMode:
CCM_STOP_MODEL_TIME = 7.0
CHILL_SPEED_ENTRY_CONFIRM_TIME = 0.35
CHILL_LEAD_ENTRY_CONFIRM_TIME = 1.0
CHILL_LAUNCH_ENTRY_CONFIRM_TIME = 0.0
CHILL_EXIT_BUFFER_TIME = 0.35
CHILL_MIN_DWELL_TIME = 1.2
CHILL_LAUNCH_EXIT_SPEED = 15 * CV.MPH_TO_MS
CHILL_LAUNCH_MAX_ENTRY_SPEED = 1.0
CHILL_LAUNCH_MAX_BRAKE = 0.2
CHILL_LAUNCH_MAX_CLOSING_SPEED = 0.75
CHILL_LAUNCH_MIN_LEAD_SPEED = 0.35
CHILL_LAUNCH_MIN_LEAD_DELTA = 0.2
CHILL_LAUNCH_MIN_LEAD_VREL = 0.2
CHILL_LAUNCH_MIN_LEAD_ACCEL = 0.08
STABLE_LEAD_MIN_MODEL_PROB = 0.9
STABLE_LEAD_MAX_BRAKE = 0.2
STABLE_LEAD_MIN_SPEED = 1.5
STABLE_LEAD_MAX_DISTANCE = 90.0
STABLE_LEAD_MAX_DISTANCE_TIME = 4.5
STABLE_LEAD_MAX_CLOSING_SPEED = 1.25
STABLE_LEAD_MAX_CLOSING_RATIO = 0.05
ADJACENT_LEAD_VETO_MIN_SPEED = 1.0
ADJACENT_LEAD_VETO_MAX_DISTANCE = 65.0
ADJACENT_LEAD_VETO_MAX_DISTANCE_TIME = 3.5
ADJACENT_LEAD_VETO_MAX_LATERAL_OFFSET = 5.5
LOW_SPEED_STOP_SCENE_MAX_SPEED = 18 * CV.MPH_TO_MS
def __init__(self, StarPilotPlanner, detector):
self.starpilot_planner = StarPilotPlanner
self.detector = detector
self.params = self.starpilot_planner.params
self.params_memory = self.starpilot_planner.params_memory
self.experimental_mode = True
self.status_value = CCStatus["OFF"]
self._active_auto_status = CCStatus["OFF"]
self._candidate_since = 0.0
self._soft_exit_since = 0.0
self._chill_hold_until = 0.0
self._prev_cc_status = None
self._launch_active = False
def update(self, v_ego, v_cruise, sm, starpilot_toggles):
now = time.monotonic()
safe_mode = self.params.get_bool("SafeMode")
self.status_value = CCStatus["OFF"] if safe_mode else restore_persisted_cc_state(self.params, self.params_memory)
if is_manual_cc_status(self.status_value):
self._reset_timers()
self.experimental_mode = self.status_value == CCStatus["USER_EXPERIMENTAL"]
self._write_status(self.status_value)
return
self._refresh_detector(v_ego, sm)
auto_status, launch_candidate = self._get_chill_status(v_ego, v_cruise, sm, starpilot_toggles)
if safe_mode or self._has_hard_veto(v_ego, sm, allow_launch=launch_candidate):
self._reset_timers()
self.experimental_mode = False if safe_mode else True
self.status_value = CCStatus["OFF"]
self._write_status(CCStatus["OFF"])
return
chill_candidate = auto_status != CCStatus["OFF"]
entry_confirm_time = self._get_entry_confirm_time(auto_status, launch_candidate)
if chill_candidate:
if self._candidate_since == 0.0:
self._candidate_since = now
self._soft_exit_since = 0.0
if not self.experimental_mode or (now - self._candidate_since) >= entry_confirm_time:
self.experimental_mode = False
self._active_auto_status = auto_status
self._chill_hold_until = max(self._chill_hold_until, now + self.CHILL_MIN_DWELL_TIME)
self.status_value = self._active_auto_status if not self.experimental_mode else CCStatus["OFF"]
else:
self._candidate_since = 0.0
if not self.experimental_mode:
if self._soft_exit_since == 0.0:
self._soft_exit_since = now
hold_active = now < self._chill_hold_until
exit_buffer_active = (now - self._soft_exit_since) < self.CHILL_EXIT_BUFFER_TIME
if hold_active or exit_buffer_active:
self.status_value = self._active_auto_status
else:
self.experimental_mode = True
self._active_auto_status = CCStatus["OFF"]
self.status_value = CCStatus["OFF"]
else:
self._soft_exit_since = 0.0
self.status_value = CCStatus["OFF"]
self._write_status(self.status_value if not self.experimental_mode else CCStatus["OFF"])
def _reset_timers(self):
self._active_auto_status = CCStatus["OFF"]
self._candidate_since = 0.0
self._soft_exit_since = 0.0
self._chill_hold_until = 0.0
self._launch_active = False
def _refresh_detector(self, v_ego, sm):
detector_toggles = type("DetectorToggles", (), {
"conditional_curves": True,
"conditional_curves_lead": True,
"conditional_lead": True,
"conditional_slower_lead": True,
"conditional_stopped_lead": True,
})()
self.detector.curve_detection(v_ego, detector_toggles)
self.detector.slow_lead(detector_toggles, v_ego)
self.detector.stop_sign_and_light(v_ego, sm, self.CCM_STOP_MODEL_TIME)
def _has_hard_veto(self, v_ego, sm, allow_launch=False):
if sm["carState"].standstill and not allow_launch:
return True
if sm["carState"].leftBlinker or sm["carState"].rightBlinker:
return True
if sm["starpilotCarState"].trafficModeEnabled:
return True
if self.starpilot_planner.starpilot_vcruise.slc.experimental_mode:
return True
if self.detector.curve_detected or self.detector.slow_lead_detected or self.detector.stop_light_detected:
return True
if self.starpilot_planner.starpilot_vcruise.stop_sign_confirmed or self.starpilot_planner.starpilot_vcruise.forcing_stop:
return True
if self._adjacent_lead_ambiguous(sm, v_ego):
return True
return self._low_speed_stop_scene(v_ego) and not allow_launch
def _low_speed_stop_scene(self, v_ego):
if v_ego >= self.LOW_SPEED_STOP_SCENE_MAX_SPEED:
return False
if self.starpilot_planner.raw_model_stopped or self.starpilot_planner.model_stopped or self.detector.stop_light_model_detected:
return True
lead = self.starpilot_planner.lead_one
if not getattr(lead, "status", False):
return False
lead_distance = float(getattr(lead, "dRel", float("inf")))
lead_speed = float(getattr(lead, "vLead", float("inf")))
lead_distance_limit = max(40.0, v_ego * self.STABLE_LEAD_MAX_DISTANCE_TIME)
return lead_distance < lead_distance_limit and lead_speed < max(6.0, v_ego + 0.5)
def _get_chill_status(self, v_ego, v_cruise, sm, starpilot_toggles):
if getattr(starpilot_toggles, "conditional_chill_launch_assist", False):
launch_status = self._get_launch_status(v_ego, sm)
if launch_status != CCStatus["OFF"]:
return launch_status, True
lead = self.starpilot_planner.lead_one
lead_status = bool(getattr(lead, "status", False))
tracking_lead = bool(getattr(self.starpilot_planner, "tracking_lead", False))
set_speed_error = max(0.0, v_cruise - v_ego)
if (not lead_status and not tracking_lead and
v_ego >= starpilot_toggles.conditional_chill_speed and
set_speed_error >= starpilot_toggles.conditional_chill_speed_margin):
return CCStatus["SPEED"], False
if not starpilot_toggles.conditional_chill_lead:
return CCStatus["OFF"], False
if v_ego < starpilot_toggles.conditional_chill_speed_lead or not lead_status or not tracking_lead:
return CCStatus["OFF"], False
lead_distance = float(getattr(lead, "dRel", float("inf")))
lead_speed = float(getattr(lead, "vLead", 0.0))
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
lead_prob = float(getattr(lead, "modelProb", 0.0))
closing_speed = max(0.0, v_ego - lead_speed)
max_closing_speed = max(self.STABLE_LEAD_MAX_CLOSING_SPEED, self.STABLE_LEAD_MAX_CLOSING_RATIO * v_ego)
max_distance = min(self.STABLE_LEAD_MAX_DISTANCE, max(35.0, v_ego * self.STABLE_LEAD_MAX_DISTANCE_TIME))
lead_confident = bool(getattr(lead, "radar", False)) or lead_prob >= self.STABLE_LEAD_MIN_MODEL_PROB
if not lead_confident:
return CCStatus["OFF"], False
if lead_distance >= max_distance or lead_speed <= self.STABLE_LEAD_MIN_SPEED:
return CCStatus["OFF"], False
if lead_brake > self.STABLE_LEAD_MAX_BRAKE or closing_speed > max_closing_speed:
return CCStatus["OFF"], False
return CCStatus["LEAD"], False
def _get_launch_status(self, v_ego, sm):
if self._launch_active and self._launch_exit_required(v_ego, sm):
self._launch_active = False
return CCStatus["OFF"]
if self._launch_active:
return self._get_launch_cc_status()
if not self._launch_scene_eligible(v_ego, sm):
return CCStatus["OFF"]
self._launch_active = True
return self._get_launch_cc_status()
def _launch_scene_eligible(self, v_ego, sm):
if v_ego > self.CHILL_LAUNCH_MAX_ENTRY_SPEED and not self._launch_active:
return False
selfdrive_state = self._get_sm_service(sm, "selfdriveState")
longitudinal_plan = self._get_sm_service(sm, "longitudinalPlan")
starpilot_plan = self._get_sm_service(sm, "starpilotPlan")
if selfdrive_state is None or longitudinal_plan is None:
return False
if not bool(getattr(selfdrive_state, "enabled", False)):
return False
if (
self.detector.stop_light_detected or
self.detector.stop_light_model_detected or
self.starpilot_planner.raw_model_stopped or
self.starpilot_planner.model_stopped or
self.starpilot_planner.starpilot_vcruise.stop_sign_confirmed or
self.starpilot_planner.starpilot_vcruise.forcing_stop or
bool(getattr(starpilot_plan, "redLight", False)) or
bool(getattr(starpilot_plan, "forcingStop", False))
):
return False
if bool(getattr(longitudinal_plan, "shouldStop", False)) or not bool(getattr(longitudinal_plan, "allowThrottle", False)):
return False
lead = getattr(self.starpilot_planner, "lead_one", None)
lead_status = bool(getattr(lead, "status", False))
tracking_lead = bool(getattr(self.starpilot_planner, "tracking_lead", False))
if not lead_status and not tracking_lead:
return True
lead_speed = float(getattr(lead, "vLead", 0.0))
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
lead_accel = float(getattr(lead, "aLeadK", 0.0))
lead_delta = lead_speed - float(v_ego)
lead_vrel = float(getattr(lead, "vRel", lead_delta))
closing_speed = max(0.0, v_ego - lead_speed)
return (
lead_speed >= self.CHILL_LAUNCH_MIN_LEAD_SPEED and
lead_delta >= self.CHILL_LAUNCH_MIN_LEAD_DELTA and
lead_vrel >= self.CHILL_LAUNCH_MIN_LEAD_VREL and
lead_accel >= self.CHILL_LAUNCH_MIN_LEAD_ACCEL and
lead_brake <= self.CHILL_LAUNCH_MAX_BRAKE and
closing_speed <= self.CHILL_LAUNCH_MAX_CLOSING_SPEED
)
def _launch_exit_required(self, v_ego, sm):
if v_ego >= self.CHILL_LAUNCH_EXIT_SPEED:
return True
if not self._launch_scene_eligible(v_ego, sm):
return True
return False
def _get_launch_cc_status(self):
lead = getattr(self.starpilot_planner, "lead_one", None)
lead_status = bool(getattr(lead, "status", False))
tracking_lead = bool(getattr(self.starpilot_planner, "tracking_lead", False))
return CCStatus["LEAD"] if lead_status or tracking_lead else CCStatus["SPEED"]
def _get_entry_confirm_time(self, auto_status, launch_candidate):
if launch_candidate:
return self.CHILL_LAUNCH_ENTRY_CONFIRM_TIME
if auto_status == CCStatus["LEAD"]:
return self.CHILL_LEAD_ENTRY_CONFIRM_TIME
return self.CHILL_SPEED_ENTRY_CONFIRM_TIME
def _adjacent_lead_ambiguous(self, sm, v_ego):
radar_state = self._get_sm_service(sm, "starpilotRadarState")
if radar_state is None:
return False
max_distance = min(self.ADJACENT_LEAD_VETO_MAX_DISTANCE, max(25.0, v_ego * self.ADJACENT_LEAD_VETO_MAX_DISTANCE_TIME))
for lead in (getattr(radar_state, "leadLeft", None), getattr(radar_state, "leadRight", None)):
if lead is None or not getattr(lead, "status", False):
continue
lateral_offset = abs(float(getattr(lead, "yRel", 0.0)))
if lateral_offset > self.ADJACENT_LEAD_VETO_MAX_LATERAL_OFFSET:
continue
if float(getattr(lead, "dRel", float("inf"))) < max_distance and float(getattr(lead, "vLead", 0.0)) > self.ADJACENT_LEAD_VETO_MIN_SPEED:
return True
return False
@staticmethod
def _get_sm_service(sm, key):
if isinstance(sm, dict):
return sm.get(key)
try:
return sm[key]
except (KeyError, IndexError, TypeError, AttributeError):
return None
def _write_status(self, status_value):
if status_value != self._prev_cc_status:
self.params_memory.put_int("CCStatus", status_value)
self._prev_cc_status = status_value