mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
147 lines
7.7 KiB
Python
147 lines
7.7 KiB
Python
"""Card input qualification for physical conditional-mode gestures."""
|
|
|
|
from opendbc.car.structs import car
|
|
from opendbc.car.gm.distance_button import DistanceObservation
|
|
from opendbc.car.gm.profiles import profiles_supported as gm_profiles_supported
|
|
from opendbc.car.hyundai.ioniq6_media import MediaObservation
|
|
from openpilot.common.params import Params
|
|
from openpilot.starpilot.speed_limits import physical_actions as slc_physical
|
|
from openpilot.starpilot.conditional_mode.manual import Button, ButtonMap, ButtonTracker, TRAFFIC_MODE_ACTION, ioniq6_media_eligible, read_button_map
|
|
from openpilot.starpilot.conditional_mode.policy import ModeChoice
|
|
from openpilot.starpilot.conditional_mode.runtime_settings import ConditionalSettingsOwner
|
|
from openpilot.starpilot.conditional_mode.status import settings_fingerprint
|
|
|
|
|
|
def conditional_manual_candidate(CS: car.CarState, tracker: ButtonTracker, params: Params,
|
|
settings_owner: ConditionalSettingsOwner, CP: car.CarParams, sm, *,
|
|
now_ns: int, explicit_aol_latch: bool = False,
|
|
media: MediaObservation | None = None,
|
|
media_buttons: ButtonMap | None = None, media_map_captured: bool = False):
|
|
"""Qualify one physical press after the Card's existing button arbitration."""
|
|
ioniq_media = ioniq6_media_eligible(CP)
|
|
gestures = tracker.observe(CS)
|
|
if not ioniq_media or media is None:
|
|
tracker.invalidate_media()
|
|
if type(now_ns) is not int or now_ns <= 0:
|
|
tracker.invalidate_media()
|
|
return None
|
|
if (not CP.openpilotLongitudinalControl or CP.passive or CP.dashcamOnly or CP.notCar or
|
|
not CS.canValid or CS.canTimeout):
|
|
tracker.invalidate_media()
|
|
return None
|
|
controls_ns = int(sm.logMonoTime.get('carControl', 0))
|
|
device_ns = int(sm.logMonoTime.get('deviceState', 0))
|
|
if (not sm.valid.get('carControl', False) or not sm.alive.get('carControl', False) or
|
|
not sm.valid.get('deviceState', False) or not sm.alive.get('deviceState', False) or
|
|
not 0 < controls_ns <= now_ns or now_ns - controls_ns > slc_physical.STATE_MAX_AGE_NS or
|
|
not 0 < device_ns <= now_ns or now_ns - device_ns > 1_000_000_000 or
|
|
not sm['carControl'].enabled or not sm['carControl'].longActive):
|
|
tracker.invalidate_media()
|
|
return None
|
|
drive_id = int(sm['deviceState'].startedMonoTime)
|
|
if not 0 < drive_id <= now_ns:
|
|
tracker.invalidate_media()
|
|
return None
|
|
snapshot = settings_owner.refresh(now_ns)
|
|
verdict = settings_owner.verdict(snapshot, now_mono_ns=now_ns, drive_id=drive_id)
|
|
if verdict.status != 'ready' or verdict.selection is None or verdict.safe_mode is not False:
|
|
tracker.invalidate_media()
|
|
return None
|
|
choice = verdict.selection.choice
|
|
if choice not in (ModeChoice.CEM, ModeChoice.CCM) or snapshot.preferences is None:
|
|
tracker.invalidate_media()
|
|
return None
|
|
fingerprint = settings_fingerprint(snapshot, owner=settings_owner)
|
|
if fingerprint is None:
|
|
tracker.invalidate_media()
|
|
return None
|
|
buttons = None
|
|
if ioniq_media and media is not None:
|
|
if not media.valid:
|
|
tracker.observe_media(media)
|
|
elif media.samples:
|
|
buttons = media_buttons if media_map_captured else read_button_map(params, include_ioniq_media=True, cp=CP)
|
|
if buttons is None:
|
|
tracker.invalidate_media()
|
|
return None
|
|
tracker.bind_media((drive_id, fingerprint, buttons))
|
|
gestures = (*gestures, *tracker.observe_media(media))
|
|
if len(gestures) != 1:
|
|
return None
|
|
gesture = gestures[0]
|
|
if explicit_aol_latch and gesture.button is Button.LKAS:
|
|
return None
|
|
if gesture.button in (Button.LKAS, Button.DISTANCE):
|
|
# Read the saved mapping on each physical gesture so live remaps apply without 0x448.
|
|
buttons = read_button_map(params, include_ioniq_media=ioniq_media, cp=CP)
|
|
elif buttons is None:
|
|
buttons = media_buttons if media_map_captured and ioniq_media else read_button_map(params, include_ioniq_media=ioniq_media, cp=CP)
|
|
if buttons is None or not buttons.assigned(gesture):
|
|
return None
|
|
return gesture, drive_id, fingerprint, choice, now_ns
|
|
|
|
|
|
def conditional_traffic_candidate(CS: car.CarState, tracker: ButtonTracker, params: Params,
|
|
settings_owner: ConditionalSettingsOwner, CP: car.CarParams, sm, *,
|
|
now_ns: int, media: MediaObservation | DistanceObservation | None = None,
|
|
media_buttons: ButtonMap | None = None, media_map_captured: bool = False,
|
|
distance: DistanceObservation | None = None, action: int = TRAFFIC_MODE_ACTION):
|
|
"""Source-backed wheel heartbeat and optional action-six proposal.
|
|
|
|
A heartbeat is emitted only for a new physical packet. An empty cached
|
|
observation cannot extend the planner's source lifetime.
|
|
"""
|
|
gm_distance = distance is not None and gm_profiles_supported(CP)
|
|
if gm_distance:
|
|
media = distance
|
|
if not (ioniq6_media_eligible(CP) or gm_distance) or media is None or not media.valid:
|
|
tracker.invalidate_media()
|
|
return None
|
|
if (type(now_ns) is not int or now_ns <= 0 or action not in (TRAFFIC_MODE_ACTION, 7) or
|
|
(action == TRAFFIC_MODE_ACTION and not CP.openpilotLongitudinalControl) or
|
|
CP.passive or CP.dashcamOnly or CP.notCar or not CS.canValid or CS.canTimeout):
|
|
tracker.invalidate_media()
|
|
return None
|
|
device_ns = int(sm.logMonoTime.get('deviceState', 0))
|
|
if (not sm.valid.get('deviceState', False) or not sm.alive.get('deviceState', False) or
|
|
not 0 < device_ns <= now_ns or now_ns - device_ns > 1_000_000_000):
|
|
tracker.invalidate_media()
|
|
return None
|
|
drive_id = int(sm['deviceState'].startedMonoTime)
|
|
if not getattr(sm['deviceState'], 'started', False) or not 0 < drive_id <= now_ns:
|
|
tracker.invalidate_media()
|
|
return None
|
|
snapshot = settings_owner.refresh(now_ns)
|
|
verdict = settings_owner.verdict(snapshot, now_mono_ns=now_ns, drive_id=drive_id)
|
|
fingerprint = settings_fingerprint(snapshot, owner=settings_owner) if verdict.status == 'ready' and verdict.safe_mode is False else None
|
|
if fingerprint is None:
|
|
tracker.invalidate_media()
|
|
return None
|
|
controls_ns = int(sm.logMonoTime.get('carControl', 0))
|
|
authority = (sm.valid.get('carControl', False) and sm.alive.get('carControl', False) and
|
|
0 < controls_ns <= now_ns and now_ns - controls_ns <= slc_physical.STATE_MAX_AGE_NS and
|
|
(sm['carControl'].latActive if action == 7 else
|
|
sm['carControl'].enabled and sm['carControl'].longActive))
|
|
if not authority:
|
|
tracker.invalidate_media() # No held press can finish after authority returns.
|
|
if not media.samples:
|
|
# Hold the 5 Hz gesture history across 100 Hz polls only within the same authority;
|
|
# cached samples must not become heartbeats.
|
|
if tracker.media_context is not None and tracker.media_context[:2] != (drive_id, fingerprint):
|
|
tracker.invalidate_media()
|
|
return None
|
|
buttons = media_buttons if media_map_captured else read_button_map(params, include_ioniq_media=not gm_distance, cp=CP)
|
|
if buttons is None:
|
|
tracker.invalidate_media()
|
|
return None
|
|
tracker.bind_media((drive_id, fingerprint, buttons))
|
|
gestures = (tracker.observe_distance_source(distance) if gm_distance else tracker.observe(CS, media)) if authority else ()
|
|
allowed = (Button.DISTANCE,) if gm_distance else (Button.MODE, Button.CUSTOM)
|
|
toggles = tuple(gesture for gesture in gestures if gesture.button in allowed and
|
|
buttons.action(gesture) == action)
|
|
if len(gestures) > 1 or len(toggles) > 1:
|
|
tracker.invalidate_media()
|
|
return None
|
|
return (bool(toggles), toggles[0] if toggles else None, drive_id, fingerprint,
|
|
buttons.fingerprint(), media.source_epoch, media.samples[-1].source_boot_ns, now_ns)
|