Files
StarPilot/selfdrive/controls/tests/test_conditional_chill_mode.py
T
firestar5683 afc8c6fb4b Mr. Freeze
2026-06-11 11:34:12 -05:00

463 lines
15 KiB
Python

from types import SimpleNamespace
from openpilot.common.constants import CV
from openpilot.starpilot.common.experimental_state import CCStatus
from openpilot.starpilot.controls.lib.conditional_chill_mode import ConditionalChillMode
class FakeParams:
def __init__(self, bools=None, ints=None):
self.bools = dict(bools or {})
self.ints = dict(ints or {})
def get_bool(self, key):
return bool(self.bools.get(key, False))
def put_bool(self, key, value):
self.bools[key] = bool(value)
def get_int(self, key, default=0):
return int(self.ints.get(key, default))
def put_int(self, key, value):
self.ints[key] = int(value)
class FakeDetector:
def __init__(self):
self.curve_detected = False
self.slow_lead_detected = False
self.stop_light_detected = False
self.stop_light_model_detected = False
def curve_detection(self, *_args, **_kwargs):
return None
def slow_lead(self, *_args, **_kwargs):
return None
def stop_sign_and_light(self, *_args, **_kwargs):
return None
class FakeSubMaster:
def __init__(self, services):
self.services = dict(services)
def __getitem__(self, key):
return self.services[key]
def make_sm():
return {
"carState": SimpleNamespace(standstill=False, leftBlinker=False, rightBlinker=False),
"selfdriveState": SimpleNamespace(enabled=True),
"longitudinalPlan": SimpleNamespace(allowThrottle=True, shouldStop=False),
"starpilotCarState": SimpleNamespace(trafficModeEnabled=False),
"starpilotPlan": SimpleNamespace(redLight=False, forcingStop=False),
"starpilotRadarState": SimpleNamespace(
leadLeft=SimpleNamespace(status=False, dRel=float("inf"), vLead=0.0),
leadRight=SimpleNamespace(status=False, dRel=float("inf"), vLead=0.0),
),
}
def make_submaster_sm():
return FakeSubMaster(make_sm())
def make_toggles():
return SimpleNamespace(
conditional_chill_speed=45 * CV.MPH_TO_MS,
conditional_chill_speed_lead=35 * CV.MPH_TO_MS,
conditional_chill_speed_margin=3 * CV.MPH_TO_MS,
conditional_chill_lead=True,
conditional_chill_launch_assist=False,
)
def make_ccm():
planner = SimpleNamespace(
params=FakeParams(),
params_memory=FakeParams(),
starpilot_vcruise=SimpleNamespace(
stop_sign_confirmed=False,
forcing_stop=False,
slc=SimpleNamespace(experimental_mode=False),
),
raw_model_stopped=False,
model_stopped=False,
tracking_lead=False,
lead_one=SimpleNamespace(
status=False,
dRel=float("inf"),
vLead=0.0,
vRel=0.0,
aLeadK=0.0,
modelProb=0.0,
radar=False,
),
)
detector = FakeDetector()
return planner, detector, ConditionalChillMode(planner, detector)
def test_ccm_stays_experimental_when_no_chill_condition_matches(monkeypatch):
planner, _detector, ccm = make_ccm()
sm = make_sm()
toggles = make_toggles()
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: 1.0)
ccm.update(20 * CV.MPH_TO_MS, 21 * CV.MPH_TO_MS, sm, toggles)
assert ccm.experimental_mode
assert ccm.status_value == CCStatus["OFF"]
def test_ccm_enters_chill_for_open_road_speed_recovery(monkeypatch):
planner, _detector, ccm = make_ccm()
sm = make_sm()
toggles = make_toggles()
monotonic_values = iter([10.0, 10.5])
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: next(monotonic_values))
v_ego = 55 * CV.MPH_TO_MS
v_cruise = v_ego + 5 * CV.MPH_TO_MS
ccm.update(v_ego, v_cruise, sm, toggles)
ccm.update(v_ego, v_cruise, sm, toggles)
assert not ccm.experimental_mode
assert ccm.status_value == CCStatus["SPEED"]
assert planner.params_memory.get_int("CCStatus") == CCStatus["SPEED"]
def test_ccm_enters_chill_for_stable_lead_cruising(monkeypatch):
planner, _detector, ccm = make_ccm()
sm = make_sm()
toggles = make_toggles()
monotonic_values = iter([10.0, 11.1])
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: next(monotonic_values))
planner.tracking_lead = True
planner.lead_one.status = True
planner.lead_one.dRel = 45.0
planner.lead_one.vLead = 24.8
planner.lead_one.radar = True
v_ego = 58 * CV.MPH_TO_MS
ccm.update(v_ego, v_ego, sm, toggles)
ccm.update(v_ego, v_ego, sm, toggles)
assert not ccm.experimental_mode
assert ccm.status_value == CCStatus["LEAD"]
def test_ccm_stable_lead_requires_longer_entry_debounce(monkeypatch):
planner, _detector, ccm = make_ccm()
sm = make_sm()
toggles = make_toggles()
monotonic_values = iter([10.0, 10.6])
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: next(monotonic_values))
planner.tracking_lead = True
planner.lead_one.status = True
planner.lead_one.dRel = 45.0
planner.lead_one.vLead = 24.8
planner.lead_one.radar = True
v_ego = 58 * CV.MPH_TO_MS
ccm.update(v_ego, v_ego, sm, toggles)
ccm.update(v_ego, v_ego, sm, toggles)
assert ccm.experimental_mode
assert ccm.status_value == CCStatus["OFF"]
def test_ccm_hard_vetoes_force_experimental(monkeypatch):
planner, detector, ccm = make_ccm()
sm = make_sm()
toggles = make_toggles()
v_ego = 60 * CV.MPH_TO_MS
v_cruise = v_ego + 5 * CV.MPH_TO_MS
veto_scenes = []
detector.slow_lead_detected = True
veto_scenes.append(("slow_lead", make_sm()))
detector.slow_lead_detected = False
traffic_sm = make_sm()
traffic_sm["starpilotCarState"].trafficModeEnabled = True
veto_scenes.append(("traffic_mode", traffic_sm))
adjacent_sm = make_sm()
adjacent_sm["starpilotRadarState"].leadLeft = SimpleNamespace(status=True, dRel=25.0, vLead=12.0)
veto_scenes.append(("adjacent_lead", adjacent_sm))
for index, (_name, scene_sm) in enumerate(veto_scenes, start=1):
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda idx=index: float(idx))
if _name == "slow_lead":
detector.slow_lead_detected = True
else:
detector.slow_lead_detected = False
ccm.update(v_ego, v_cruise, scene_sm, toggles)
assert ccm.experimental_mode
assert ccm.status_value == CCStatus["OFF"]
planner.starpilot_vcruise.slc.experimental_mode = True
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: 10.0)
ccm.update(v_ego, v_cruise, sm, toggles)
assert ccm.experimental_mode
assert ccm.status_value == CCStatus["OFF"]
def test_ccm_far_lateral_adjacent_lead_does_not_block_open_road_chill(monkeypatch):
planner, _detector, ccm = make_ccm()
sm = make_sm()
toggles = make_toggles()
monotonic_values = iter([10.0, 10.5])
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: next(monotonic_values))
sm["starpilotRadarState"].leadLeft = SimpleNamespace(status=True, dRel=18.0, vLead=12.0, yRel=6.5)
v_ego = 55 * CV.MPH_TO_MS
v_cruise = v_ego + 5 * CV.MPH_TO_MS
ccm.update(v_ego, v_cruise, sm, toggles)
ccm.update(v_ego, v_cruise, sm, toggles)
assert not ccm.experimental_mode
assert ccm.status_value == CCStatus["SPEED"]
def test_ccm_immediate_adjacent_lead_still_blocks_open_road_chill(monkeypatch):
planner, _detector, ccm = make_ccm()
sm = make_sm()
toggles = make_toggles()
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: 10.0)
sm["starpilotRadarState"].leadLeft = SimpleNamespace(status=True, dRel=18.0, vLead=12.0, yRel=4.5)
v_ego = 55 * CV.MPH_TO_MS
v_cruise = v_ego + 5 * CV.MPH_TO_MS
ccm.update(v_ego, v_cruise, sm, toggles)
assert ccm.experimental_mode
assert ccm.status_value == CCStatus["OFF"]
def test_ccm_immediately_exits_chill_when_scene_turns_into_slow_lead(monkeypatch):
planner, detector, ccm = make_ccm()
sm = make_sm()
toggles = make_toggles()
monotonic_values = iter([10.0, 10.5, 10.6])
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: next(monotonic_values))
planner.tracking_lead = True
planner.lead_one.status = True
planner.lead_one.dRel = 40.0
planner.lead_one.vLead = 24.9
planner.lead_one.radar = True
v_ego = 58 * CV.MPH_TO_MS
ccm.update(v_ego, v_ego, sm, toggles)
ccm.update(v_ego, v_ego, sm, toggles)
assert not ccm.experimental_mode
detector.slow_lead_detected = True
ccm.update(v_ego, v_ego, sm, toggles)
assert ccm.experimental_mode
assert ccm.status_value == CCStatus["OFF"]
def test_ccm_launch_assist_enters_chill_from_standstill_when_planner_wants_to_go(monkeypatch):
planner, _detector, ccm = make_ccm()
sm = make_sm()
sm["carState"].standstill = True
toggles = make_toggles()
toggles.conditional_chill_launch_assist = True
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: 20.0)
ccm.update(0.0, 25 * CV.MPH_TO_MS, sm, toggles)
assert not ccm.experimental_mode
assert ccm.status_value == CCStatus["SPEED"]
def test_ccm_launch_assist_does_not_bypass_real_stop_scene(monkeypatch):
planner, detector, ccm = make_ccm()
sm = make_sm()
sm["carState"].standstill = True
sm["longitudinalPlan"].shouldStop = True
toggles = make_toggles()
toggles.conditional_chill_launch_assist = True
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: 21.0)
ccm.update(0.0, 25 * CV.MPH_TO_MS, sm, toggles)
assert ccm.experimental_mode
assert ccm.status_value == CCStatus["OFF"]
detector.stop_light_detected = True
sm["longitudinalPlan"].shouldStop = False
ccm.update(0.0, 25 * CV.MPH_TO_MS, sm, toggles)
assert ccm.experimental_mode
assert ccm.status_value == CCStatus["OFF"]
def test_ccm_launch_assist_rejects_red_light_stationary_lead_even_if_planner_says_go(monkeypatch):
planner, _detector, ccm = make_ccm()
sm = make_sm()
sm["carState"].standstill = True
sm["starpilotPlan"].redLight = True
toggles = make_toggles()
toggles.conditional_chill_launch_assist = True
planner.tracking_lead = True
planner.lead_one.status = True
planner.lead_one.dRel = 6.1
planner.lead_one.vLead = 0.01
planner.lead_one.vRel = -0.08
planner.lead_one.aLeadK = 0.0
planner.lead_one.radar = True
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: 22.0)
ccm.update(0.0, 25 * CV.MPH_TO_MS, sm, toggles)
assert ccm.experimental_mode
assert ccm.status_value == CCStatus["OFF"]
def test_ccm_launch_assist_requires_real_lead_departure_signal(monkeypatch):
planner, _detector, ccm = make_ccm()
sm = make_sm()
sm["carState"].standstill = True
toggles = make_toggles()
toggles.conditional_chill_launch_assist = True
planner.tracking_lead = True
planner.lead_one.status = True
planner.lead_one.dRel = 18.0
planner.lead_one.vLead = 0.12
planner.lead_one.vRel = 0.05
planner.lead_one.aLeadK = 0.02
planner.lead_one.radar = True
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: 23.0)
ccm.update(0.0, 25 * CV.MPH_TO_MS, sm, toggles)
assert ccm.experimental_mode
assert ccm.status_value == CCStatus["OFF"]
planner.lead_one.vLead = 0.8
planner.lead_one.vRel = 0.5
planner.lead_one.aLeadK = 0.2
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: 23.2)
ccm.update(0.0, 25 * CV.MPH_TO_MS, sm, toggles)
assert not ccm.experimental_mode
assert ccm.status_value == CCStatus["LEAD"]
def test_ccm_launch_assist_exits_once_launch_speed_is_reached(monkeypatch):
planner, _detector, ccm = make_ccm()
sm = make_sm()
sm["carState"].standstill = True
toggles = make_toggles()
toggles.conditional_chill_launch_assist = True
monotonic_values = iter([30.0, 30.2])
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: next(monotonic_values))
ccm.update(0.0, 25 * CV.MPH_TO_MS, sm, toggles)
assert not ccm.experimental_mode
sm["carState"].standstill = False
ccm.update(16 * CV.MPH_TO_MS, 25 * CV.MPH_TO_MS, sm, toggles)
assert ccm.experimental_mode
assert ccm.status_value == CCStatus["OFF"]
def test_ccm_launch_assist_exits_immediately_if_lead_slows_again(monkeypatch):
planner, _detector, ccm = make_ccm()
sm = make_sm()
sm["carState"].standstill = True
toggles = make_toggles()
toggles.conditional_chill_launch_assist = True
monotonic_values = iter([40.0, 40.1])
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: next(monotonic_values))
planner.tracking_lead = True
planner.lead_one.status = True
planner.lead_one.dRel = 18.0
planner.lead_one.vLead = 2.0
planner.lead_one.vRel = 2.0
planner.lead_one.radar = True
ccm.update(0.0, 25 * CV.MPH_TO_MS, sm, toggles)
assert not ccm.experimental_mode
assert ccm.status_value == CCStatus["LEAD"]
sm["carState"].standstill = False
planner.lead_one.vLead = 0.2
planner.lead_one.vRel = 0.0
planner.lead_one.aLeadK = -0.4
ccm.update(3 * CV.MPH_TO_MS, 25 * CV.MPH_TO_MS, sm, toggles)
assert ccm.experimental_mode
assert ccm.status_value == CCStatus["OFF"]
def test_ccm_respects_manual_chill_override(monkeypatch):
planner, _detector, ccm = make_ccm()
sm = make_sm()
toggles = make_toggles()
planner.params_memory.put_int("CCStatus", CCStatus["USER_CHILL"])
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: 1.0)
ccm.update(55 * CV.MPH_TO_MS, 60 * CV.MPH_TO_MS, sm, toggles)
assert not ccm.experimental_mode
assert ccm.status_value == CCStatus["USER_CHILL"]
def test_ccm_launch_assist_is_disabled_by_default(monkeypatch):
planner, _detector, ccm = make_ccm()
sm = make_sm()
sm["carState"].standstill = True
toggles = make_toggles()
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: 50.0)
ccm.update(0.0, 25 * CV.MPH_TO_MS, sm, toggles)
assert ccm.experimental_mode
assert ccm.status_value == CCStatus["OFF"]
def test_ccm_restores_persisted_manual_experimental_override(monkeypatch):
planner, _detector, ccm = make_ccm()
sm = make_sm()
toggles = make_toggles()
planner.params.put_bool("PersistChillState", True)
planner.params.put_int("PersistedCCStatus", CCStatus["USER_EXPERIMENTAL"])
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: 1.0)
ccm.update(55 * CV.MPH_TO_MS, 60 * CV.MPH_TO_MS, sm, toggles)
assert ccm.experimental_mode
assert ccm.status_value == CCStatus["USER_EXPERIMENTAL"]
assert planner.params_memory.get_int("CCStatus") == CCStatus["USER_EXPERIMENTAL"]
def test_ccm_adjacent_lead_veto_works_with_submaster_like_input(monkeypatch):
planner, _detector, ccm = make_ccm()
sm = make_submaster_sm()
toggles = make_toggles()
sm["starpilotRadarState"].leadLeft = SimpleNamespace(status=True, dRel=20.0, vLead=12.0)
monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: 10.0)
ccm.update(60 * CV.MPH_TO_MS, 65 * CV.MPH_TO_MS, sm, toggles)
assert ccm.experimental_mode
assert ccm.status_value == CCStatus["OFF"]