Files
StarPilot/openpilot/starpilot/lateral/pause.py
T
firestar5683 79c8945a41 Lateral
Integrate torque feedback, lane-change smoothing and low-speed Turn Assist through dedicated lateral owners.

Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>

Co-authored-by: pharmacomaniac <blittle65@gmail.com>
2026-10-14 20:02:39 -05:00

91 lines
3.1 KiB
Python

"""Temporary steering output pauses, independent of driver authorization."""
from dataclasses import dataclass
import math
from openpilot.common.constants import CV
from openpilot.starpilot.saved_source import read_saved
KEYS = ('PauseLateralSpeed', 'PauseLateralOnSignal', 'LateralResumeDelay')
@dataclass(frozen=True)
class PauseSettings:
speed_mps: float = 0.0
signal_only: bool = False
delay_s: float = 0.0
def read_settings(params) -> PauseSettings | None:
values = {key: read_saved(params, key, 128) for key in (*KEYS, 'IsMetric')}
if not all(readable for _, readable in values.values()):
return None
try:
speed_raw = values[KEYS[0]][0]
speed = float(b'0' if speed_raw is None else speed_raw)
signal = values[KEYS[1]][0]
delay_raw = values[KEYS[2]][0]
delay = float(b'0' if delay_raw is None else delay_raw)
metric = values['IsMetric'][0]
if (signal not in (None, b'0', b'1') or metric not in (None, b'0', b'1') or
not math.isfinite(speed) or not 0 <= speed <= 100 or not math.isfinite(delay) or not 0 <= delay <= 5):
return None
return PauseSettings(speed * (CV.KPH_TO_MS if metric == b'1' else CV.MPH_TO_MS), signal == b'1', delay)
except (ValueError, OverflowError):
return None
class LateralPause:
def __init__(self, settings: PauseSettings | None, params=None):
self.settings = settings or PauseSettings()
self.params = params
self._refresh_ns = 0
self._drive_id = 0
self.reset()
def reset(self) -> None:
self._blinker = False
self._minimum = math.inf
self._release_ns = 0
self._last_ns = 0
def refresh(self, now_ns: int) -> None:
if self.params is not None and (now_ns < self._refresh_ns or now_ns - self._refresh_ns >= 1_000_000_000):
settings = read_settings(self.params)
if settings is not None:
if settings != self.settings:
self.reset()
self.settings = settings
self._refresh_ns = now_ns
def allowed(self, cs, *, now_ns: int, source_ns: int, drive_id: int | None = None) -> bool:
self.refresh(now_ns)
if self.settings.speed_mps == 0:
self.reset()
return True
if now_ns < self._last_ns:
self.reset()
return False
self._last_ns = now_ns
if drive_id is not None:
if drive_id <= 0:
return False
if drive_id != self._drive_id:
self.reset()
self._drive_id = drive_id
self._last_ns = now_ns
if (not cs.canValid or cs.canTimeout or not math.isfinite(cs.vEgo) or
not 0 < source_ns <= now_ns or now_ns - source_ns > 250_000_000):
return False
signal = bool(cs.leftBlinker or cs.rightBlinker)
if signal:
self._minimum = min(self._minimum, cs.vEgo) if self._blinker else cs.vEgo
self._release_ns = 0
elif self._blinker:
self._release_ns = now_ns
self._blinker = signal
delayed = (self.settings.signal_only and self._release_ns > 0 and
self._minimum < self.settings.speed_mps / 2 and
now_ns - self._release_ns < self.settings.delay_s * 1e9)
return bool((cs.vEgo >= self.settings.speed_mps or self.settings.signal_only and not signal) and not delayed)