Files
StarPilot/openpilot/starpilot/car/subaru/software_cruise.py
T

200 lines
5.6 KiB
Python
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.
"""Dom’s stock cruise button timing, with current physical readiness guards."""
import math
from opendbc.car.structs import car
from opendbc.car.subaru.software_cruise import selected
from opendbc.car import apply_hysteresis
from openpilot.common.constants import CV
from openpilot.common.realtime import DT_CTRL
ButtonType = car.CarState.ButtonEvent.Type
SEND_BUTTON_NONE = 0
SEND_BUTTON_INCREASE = 1
SEND_BUTTON_DECREASE = 2
HYST_GAP = 0.0
INCREASE_INACTIVE_TIMER = 0.12
DECREASE_INACTIVE_TIMER = 0.05
LEAD_INCREASE_INACTIVE_TIMER = 0.05
CRUISE_BUTTON_TIMERS = {
int(ButtonType.decelCruise): 0,
int(ButtonType.accelCruise): 0,
int(ButtonType.setCruise): 0,
int(ButtonType.resumeCruise): 0,
int(ButtonType.cancel): 0,
int(ButtonType.mainCruise): 0,
}
def get_minimum_set_speed(is_metric: bool) -> int:
return 30 if is_metric else 20
def update_manual_button_timers(CS: car.CarState, button_timers: dict[int, int]) -> None:
for button_type in button_timers:
if button_timers[button_type] > 0:
button_timers[button_type] += 1
for event in CS.buttonEvents:
button_type = event.type.raw if hasattr(event.type, "raw") else int(event.type)
if button_type in button_timers:
button_timers[button_type] = 1 if event.pressed else 0
class SoftwareCruise:
def __init__(self, CP):
self.CP = CP
self.v_target = 0
self.v_target_ms_last = 0.0
self.v_cruise_cluster = 0
self.v_cruise_min = 0
self.state = "inactive"
self.pre_active_timer = 0
self.is_ready = False
self.is_ready_prev = False
self.cruise_button_timers = dict(CRUISE_BUTTON_TIMERS)
@staticmethod
def _send_button_for_state(state: str) -> int:
if state == "increasing":
return SEND_BUTTON_INCREASE
if state == "decreasing":
return SEND_BUTTON_DECREASE
return SEND_BUTTON_NONE
def _reset(self) -> None:
self.state = "inactive"
self.pre_active_timer = 0
self.is_ready = False
self.is_ready_prev = False
def _update_calculations(self, CS: car.CarState, v_target_ms: float, is_metric: bool) -> None:
speed_conv = CV.MS_TO_KPH if is_metric else CV.MS_TO_MPH
ms_conv = CV.KPH_TO_MS if is_metric else CV.MPH_TO_MS
self.v_target_ms_last = apply_hysteresis(v_target_ms, self.v_target_ms_last, HYST_GAP * ms_conv)
self.v_target = round(self.v_target_ms_last * speed_conv)
self.v_cruise_min = get_minimum_set_speed(is_metric)
self.v_cruise_cluster = round(CS.cruiseState.speedCluster * speed_conv)
def _update_readiness(self, CS: car.CarState, CC: car.CarControl) -> None:
update_manual_button_timers(CS, self.cruise_button_timers)
button_pressed = any(timer > 0 for timer in self.cruise_button_timers.values())
stock_cruise_ready = (
CS.cruiseState.enabled
and CS.cruiseState.available
and CS.canValid
and not CS.canTimeout
and not CS.brakePressed
and not CS.gasPressed
and not CS.accFaulted
and not CS.steerFaultPermanent
and not CS.steerFaultTemporary
and CS.gearShifter == car.CarState.GearShifter.drive
and not CS.standstill
)
self.is_ready = (
CC.enabled and stock_cruise_ready and not CC.cruiseControl.override and not CC.cruiseControl.cancel and not CC.cruiseControl.resume and not button_pressed
)
def _desired_state(self) -> str:
if self.v_target > self.v_cruise_cluster:
return "increasing"
if self.v_target < self.v_cruise_cluster and self.v_cruise_cluster > self.v_cruise_min:
return "decreasing"
return "holding"
@staticmethod
def _get_pre_active_frames(state: str, lead_present: bool) -> int:
if state == "decreasing":
timer = DECREASE_INACTIVE_TIMER
elif lead_present:
timer = LEAD_INCREASE_INACTIVE_TIMER
else:
timer = INCREASE_INACTIVE_TIMER
return int(timer / DT_CTRL)
def _arm_pre_active(self, desired_state: str, lead_present: bool) -> None:
if desired_state == "holding":
self.state = "holding"
self.pre_active_timer = 0
return
self.state = "preActive"
self.pre_active_timer = self._get_pre_active_frames(desired_state, lead_present)
def _update_state_machine(self, lead_present: bool) -> int:
desired_state = self._desired_state()
if not self.is_ready:
self.state = "inactive"
self.pre_active_timer = 0
elif self.state == "inactive":
if not self.is_ready_prev:
self._arm_pre_active(desired_state, lead_present)
elif self.state == "preActive":
if desired_state == "holding":
self.state = "holding"
self.pre_active_timer = 0
else:
desired_frames = self._get_pre_active_frames(desired_state, lead_present)
self.pre_active_timer = max(0, min(self.pre_active_timer, desired_frames) - 1)
if self.pre_active_timer <= 0:
self.state = desired_state
elif self.state == "holding":
if desired_state != "holding":
self._arm_pre_active(desired_state, lead_present)
elif self.state != desired_state:
if desired_state == "holding":
self.state = "holding"
else:
self._arm_pre_active(desired_state, lead_present)
return self._send_button_for_state(self.state)
def run(
self,
CS: car.CarState,
CC: car.CarControl,
v_target_ms: float,
is_metric: bool,
lead_present: bool = False,
) -> tuple[int, int]:
if not selected(self.CP) or not math.isfinite(v_target_ms):
self._reset()
return SEND_BUTTON_NONE, 0
self._update_calculations(CS, v_target_ms, is_metric)
self._update_readiness(CS, CC)
send_button = self._update_state_machine(lead_present)
self.is_ready_prev = self.is_ready
return send_button, self.v_target