mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
d8708b65c7
Implement conditional driving modes with explicit scene, button and traffic state ownership. Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com> Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com> Co-authored-by: whoisdomi <dcantu83@gmail.com>
182 lines
7.1 KiB
Python
182 lines
7.1 KiB
Python
"""Source-bound low-speed stop and launch-assist evidence for Conditional Chill.
|
|
|
|
This module only reports scene evidence. It never selects a driving mode or
|
|
reads a service, Params, or vehicle signal on its own.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
import math
|
|
|
|
from openpilot.common.realtime import DT_MDL
|
|
|
|
|
|
MPH_TO_MPS = 0.44704
|
|
LOW_SPEED_STOP_MAX_MPS = 18.0 * MPH_TO_MPS
|
|
LAUNCH_EXIT_MPS = 15.0 * MPH_TO_MPS
|
|
LAUNCH_ENTRY_MAX_MPS = 1.0
|
|
MAX_SOURCE_AGE_S = 0.25
|
|
MAX_FRAME_GAP_S = 2.0 * DT_MDL
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ChillLead:
|
|
present: bool | None
|
|
distance_m: float | None = None
|
|
speed_mps: float | None = None
|
|
relative_speed_mps: float | None = None # Raw lead.vRel; never inferred from vLead - vEgo.
|
|
accel_mps2: float | None = None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ChillSceneFrame:
|
|
observed_mono_s: float
|
|
model_tick_mono_s: float
|
|
now_mono_s: float
|
|
speed_mps: float | None
|
|
lead: ChillLead | None
|
|
tracking_lead: bool | None
|
|
raw_model_stopped: bool | None
|
|
model_stopped: bool | None
|
|
stop_light_model_detected: bool | None
|
|
stop_light_detected: bool | None
|
|
stop_sign_confirmed: bool | None
|
|
forcing_stop: bool | None
|
|
red_light: bool | None
|
|
plan_forcing_stop: bool | None
|
|
should_stop: bool | None
|
|
allow_throttle: bool | None
|
|
selfdrive_enabled: bool | None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ChillSceneObservation:
|
|
low_speed_stop_scene: bool | None
|
|
launch_candidate: bool | None
|
|
forced_exit: bool
|
|
launch_status: str | None # "speed" or "lead" when candidate is true.
|
|
speed_reason: str | None
|
|
lead_reason: str | None
|
|
|
|
|
|
def _finite(value: object, low: float, high: float) -> float | None:
|
|
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
return None
|
|
try:
|
|
number = float(value)
|
|
except (OverflowError, ValueError):
|
|
return None
|
|
return number if math.isfinite(number) and low <= number <= high else None
|
|
|
|
|
|
def _bool(value: object) -> bool | None:
|
|
return value if type(value) is bool else None
|
|
|
|
|
|
class ChillSceneDetector:
|
|
"""One model-tick launch latch with immediate frozen exit conditions."""
|
|
|
|
def __init__(self):
|
|
self.reset()
|
|
|
|
def reset(self) -> None:
|
|
self.launch_active = False
|
|
self.last_model_tick_mono_s: float | None = None
|
|
|
|
@staticmethod
|
|
def _low_speed_stop(frame: ChillSceneFrame, speed: float, lead: ChillLead | None) -> bool | None:
|
|
if speed >= LOW_SPEED_STOP_MAX_MPS:
|
|
return False
|
|
if frame.raw_model_stopped or frame.model_stopped or frame.stop_light_model_detected:
|
|
return True
|
|
lead_stop = None
|
|
if lead is not None and _bool(lead.present) is not None:
|
|
if not lead.present:
|
|
lead_stop = False
|
|
else:
|
|
distance = _finite(lead.distance_m, 0.0, 500.0)
|
|
lead_speed = _finite(lead.speed_mps, -30.0, 100.0)
|
|
if distance is not None and lead_speed is not None:
|
|
lead_stop = distance < max(40.0, speed * 4.5) and lead_speed < max(6.0, speed + 0.5)
|
|
if lead_stop:
|
|
return True
|
|
if (any(_bool(value) is None for value in (
|
|
frame.raw_model_stopped, frame.model_stopped, frame.stop_light_model_detected,
|
|
)) or lead_stop is None):
|
|
return None
|
|
return False
|
|
|
|
@staticmethod
|
|
def _eligible(frame: ChillSceneFrame, speed: float, lead: ChillLead, active: bool) -> tuple[bool, str, str]:
|
|
if speed > LAUNCH_ENTRY_MAX_MPS and not active:
|
|
return False, "above_entry_speed", "not_evaluated"
|
|
if not frame.selfdrive_enabled:
|
|
return False, "selfdrive_disabled", "not_evaluated"
|
|
if (frame.stop_light_detected or frame.stop_light_model_detected or frame.raw_model_stopped or
|
|
frame.model_stopped or frame.stop_sign_confirmed or frame.forcing_stop or frame.red_light or
|
|
frame.plan_forcing_stop):
|
|
return False, "stop_scene", "not_evaluated"
|
|
if frame.should_stop or not frame.allow_throttle:
|
|
return False, "longitudinal_stop", "not_evaluated"
|
|
if not lead.present and not frame.tracking_lead:
|
|
return True, "go", "no_lead"
|
|
assert lead.speed_mps is not None and lead.relative_speed_mps is not None and lead.accel_mps2 is not None
|
|
lead_delta = lead.speed_mps - speed
|
|
closing_speed = max(0.0, speed - lead.speed_mps)
|
|
departing = (
|
|
lead.speed_mps >= 0.35 and lead_delta >= 0.2 and lead.relative_speed_mps >= 0.2 and
|
|
lead.accel_mps2 >= 0.08 and max(0.0, -lead.accel_mps2) <= 0.2 and closing_speed <= 0.75
|
|
)
|
|
return departing, "go" if departing else "lead_not_departing", "departing" if departing else "not_departing"
|
|
|
|
def step(self, frame: ChillSceneFrame) -> ChillSceneObservation:
|
|
unknown = ChillSceneObservation(None, None, self.launch_active, None, "unavailable", "unavailable")
|
|
observed = _finite(frame.observed_mono_s, 0.0, 1e12)
|
|
tick = _finite(frame.model_tick_mono_s, 0.0, 1e12)
|
|
now = _finite(frame.now_mono_s, 0.0, 1e12)
|
|
speed = _finite(frame.speed_mps, 0.0, 80.0)
|
|
lead = frame.lead
|
|
if (observed is None or tick is None or now is None or
|
|
not 0.0 <= now - observed <= MAX_SOURCE_AGE_S or not observed <= tick <= now or speed is None):
|
|
self.reset()
|
|
return unknown
|
|
if self.last_model_tick_mono_s is not None:
|
|
elapsed = tick - self.last_model_tick_mono_s
|
|
if elapsed <= 0.0 or elapsed > MAX_FRAME_GAP_S:
|
|
self.reset()
|
|
return unknown
|
|
self.last_model_tick_mono_s = tick
|
|
low_speed_stop = self._low_speed_stop(frame, speed, lead)
|
|
launch_inputs = (
|
|
frame.raw_model_stopped, frame.model_stopped,
|
|
frame.stop_light_model_detected, frame.stop_light_detected,
|
|
frame.stop_sign_confirmed, frame.forcing_stop, frame.red_light,
|
|
frame.plan_forcing_stop, frame.should_stop, frame.allow_throttle,
|
|
frame.selfdrive_enabled,
|
|
)
|
|
if (lead is None or _bool(lead.present) is None or
|
|
(not lead.present and _bool(frame.tracking_lead) is None) or
|
|
any(_bool(value) is None for value in launch_inputs)):
|
|
forced_exit = self.launch_active
|
|
self.launch_active = False
|
|
return ChillSceneObservation(low_speed_stop, None, forced_exit, None, "unavailable", "unavailable")
|
|
if lead.present or frame.tracking_lead:
|
|
if (_finite(lead.distance_m, 0.0, 500.0) is None or
|
|
_finite(lead.speed_mps, -30.0, 100.0) is None or
|
|
_finite(lead.relative_speed_mps, -100.0, 100.0) is None or
|
|
_finite(lead.accel_mps2, -20.0, 20.0) is None):
|
|
forced_exit = self.launch_active
|
|
self.launch_active = False
|
|
return ChillSceneObservation(low_speed_stop, None, forced_exit, None, "unavailable", "unavailable")
|
|
if self.launch_active and speed >= LAUNCH_EXIT_MPS:
|
|
self.launch_active = False
|
|
return ChillSceneObservation(low_speed_stop, False, True, None, "exit_speed", "not_evaluated")
|
|
eligible, speed_reason, lead_reason = self._eligible(frame, speed, lead, self.launch_active)
|
|
if self.launch_active and not eligible:
|
|
self.launch_active = False
|
|
return ChillSceneObservation(low_speed_stop, False, True, None, speed_reason, lead_reason)
|
|
self.launch_active = eligible
|
|
status = ("lead" if lead.present or frame.tracking_lead else "speed") if eligible else None
|
|
return ChillSceneObservation(low_speed_stop, eligible, False, status, speed_reason, lead_reason)
|