mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-06 12:33:51 +08:00
128 lines
6.2 KiB
Python
128 lines
6.2 KiB
Python
"""One mapped boundary offered through the existing SLC decision owner."""
|
|
import math
|
|
from dataclasses import dataclass
|
|
|
|
from openpilot.starpilot.speed_limits import acceptance as acc, selection as sel
|
|
from openpilot.starpilot.speed_limits.map_observation import Upcoming
|
|
|
|
|
|
@dataclass
|
|
class Encounter:
|
|
boundary_id: str
|
|
from_mps: float
|
|
speed_mps: float
|
|
way_id: int
|
|
started_ns: int | None
|
|
crossed: bool = False
|
|
declined: bool = False
|
|
withdrawn: bool = False
|
|
disabled: bool = False
|
|
accepted_ns: int | None = None
|
|
|
|
|
|
class Advance:
|
|
def __init__(self):
|
|
self.encounter: Encounter | None = None
|
|
self.boundary_id = ''
|
|
self.started_ns: int | None = None
|
|
|
|
def disable(self):
|
|
if self.encounter is not None and not self.encounter.crossed:
|
|
self.encounter.disabled = True
|
|
|
|
def use_current(self, candidate):
|
|
self.boundary_id, self.started_ns = '', None
|
|
if self.encounter is not None:
|
|
self.encounter.disabled = True
|
|
self.encounter.withdrawn = False
|
|
if candidate.speed_mps == self.encounter.speed_mps:
|
|
self.encounter.declined = False
|
|
self.encounter.accepted_ns = None
|
|
|
|
def propose(self, observations, policy, upcoming: Upcoming | None, *, current_way_id: int,
|
|
seconds: int, speed_mps: float | None, standstill: bool, can_offer: bool,
|
|
confirmation_active: bool, now_ns: int):
|
|
self.boundary_id, self.started_ns = '', None
|
|
current = observations[sel.Source.MAP].candidate
|
|
encounter = self.encounter
|
|
if encounter is not None and not encounter.declined and encounter.accepted_ns is None:
|
|
if not confirmation_active:
|
|
encounter.started_ns = None
|
|
elif encounter.started_ns is None:
|
|
encounter.started_ns = now_ns
|
|
if current is None:
|
|
if encounter is not None and not encounter.crossed:
|
|
return {source: acc.Observation(acc.ObservationKind.UNKNOWN)
|
|
if observation.candidate is not None and observation.candidate.speed_mps == encounter.from_mps
|
|
else observation for source, observation in observations.items()}
|
|
return None
|
|
if encounter is not None:
|
|
if current.speed_mps == encounter.speed_mps and (encounter.crossed or current_way_id == encounter.way_id):
|
|
encounter.crossed = True
|
|
elif current.speed_mps not in (encounter.from_mps, encounter.speed_mps):
|
|
self.encounter = encounter = None
|
|
|
|
# shortcut: use fresh GPS distance without propagation; revisit only if replay shows material timing error.
|
|
eligible = (can_offer and type(seconds) is int and 0 < seconds <= 10 and not standstill and
|
|
speed_mps is not None and type(speed_mps) in (int, float) and math.isfinite(speed_mps) and speed_mps > 0 and
|
|
upcoming is not None and upcoming.distance_m <= speed_mps * seconds)
|
|
if eligible and (encounter is None or upcoming.boundary_id != encounter.boundary_id):
|
|
proposed = dict(observations)
|
|
proposed[sel.Source.MAP] = upcoming.observation
|
|
if sel.select_limit(proposed, policy).selected_source is sel.Source.MAP:
|
|
assert upcoming.observation.candidate is not None
|
|
encounter = self.encounter = Encounter(upcoming.boundary_id, current.speed_mps,
|
|
upcoming.observation.candidate.speed_mps, upcoming.way_id, now_ns)
|
|
if encounter is None:
|
|
return None
|
|
|
|
proposed = dict(observations)
|
|
blocked = encounter.declined or encounter.withdrawn
|
|
if encounter.crossed:
|
|
if blocked:
|
|
proposed = {source: acc.Observation(acc.ObservationKind.UNKNOWN)
|
|
if observation.candidate is not None and observation.candidate.speed_mps == encounter.speed_mps
|
|
else observation for source, observation in proposed.items()}
|
|
else:
|
|
if not encounter.disabled and sel.select_limit(observations, policy).selected_source is sel.Source.MAP:
|
|
self.started_ns = encounter.started_ns
|
|
return None
|
|
elif (seconds > 0 and not blocked and not encounter.disabled and upcoming is not None and
|
|
upcoming.boundary_id == encounter.boundary_id and
|
|
upcoming.observation.candidate.speed_mps == encounter.speed_mps):
|
|
proposed[sel.Source.MAP] = upcoming.observation
|
|
if sel.select_limit(proposed, policy).selected_source is sel.Source.MAP:
|
|
self.boundary_id, self.started_ns = encounter.boundary_id, encounter.started_ns
|
|
else:
|
|
# Losing a preview must not offer the still-current approach limit again.
|
|
proposed[sel.Source.MAP] = acc.Observation(acc.ObservationKind.UNKNOWN)
|
|
chosen = sel.select_limit(proposed, policy).observation.candidate
|
|
if not encounter.crossed and chosen is not None and chosen.speed_mps == encounter.from_mps:
|
|
encounter.withdrawn = True
|
|
proposed = {source: acc.Observation(acc.ObservationKind.UNKNOWN)
|
|
if source is sel.Source.MAP or observation.candidate is not None and observation.candidate.speed_mps == encounter.from_mps
|
|
else observation for source, observation in proposed.items()}
|
|
self.boundary_id, self.started_ns = '', None
|
|
return proposed
|
|
|
|
def observe(self, result):
|
|
encounter = self.encounter
|
|
if encounter is None or result.acceptance is None:
|
|
return
|
|
decision = result.acceptance
|
|
selected = result.selection.observation.candidate if result.selection is not None else None
|
|
if selected is not None and selected.speed_mps == encounter.speed_mps and selected.source == 'map':
|
|
if decision.basis in ('driver_reject', 'timeout_reject', 'rejected'):
|
|
encounter.declined = True
|
|
accepted = decision.state.accepted
|
|
if accepted is not None and accepted.candidate.speed_mps == encounter.speed_mps and decision.state.pending is None:
|
|
if encounter.accepted_ns is None:
|
|
encounter.accepted_ns = accepted.accepted_at_ns
|
|
elif selected is not None and not encounter.crossed and not encounter.disabled:
|
|
encounter.withdrawn = True
|
|
if encounter.accepted_ns is not None and result.state.override.persistent_selected_mps is not None:
|
|
encounter.withdrawn = True
|
|
accepted = decision.state.accepted
|
|
if not encounter.disabled and encounter.accepted_ns is not None and accepted is not None and accepted.accepted_at_ns != encounter.accepted_ns:
|
|
encounter.withdrawn = True
|