Files
StarPilot/openpilot/starpilot/aol/vehicle.py
T

188 lines
7.7 KiB
Python

"""Fail-closed dispatch to vehicle-owned AOL capabilities."""
from functools import lru_cache
from capnp.lib.capnp import _DynamicStructBuilder, _DynamicStructReader
from opendbc.car.structs import CarParams
from openpilot.starpilot.aol.policy import AolVehiclePolicy
from openpilot.starpilot.aol.intent import AolCardIntent
from openpilot.starpilot.car.honda import aol as honda
from openpilot.starpilot.car.hyundai import aol as hyundai
from openpilot.starpilot.car.gm import aol as gm
from openpilot.starpilot.car.ford import aol as ford
from openpilot.starpilot.car.mazda import aol as mazda
from openpilot.starpilot.car.chrysler import aol as chrysler
from openpilot.starpilot.car.subaru import aol as subaru
from openpilot.starpilot.car.tesla import aol as tesla
from openpilot.starpilot.car.toyota import aol as toyota
from openpilot.starpilot.car.rivian import aol as rivian
from openpilot.starpilot.car.volkswagen import aol as volkswagen
from openpilot.starpilot.car.nissan import aol as nissan
_PORTS = {'honda': honda, 'hyundai': hyundai, 'gm': gm, 'ford': ford, 'mazda': mazda,
'tesla': tesla, 'toyota': toyota, 'chrysler': chrysler, 'nissan': nissan, 'volkswagen': volkswagen, 'subaru': subaru, 'rivian': rivian}
_POLICY_CACHE_SIZE = 8
_POLICY_CACHE_MAX_BYTES = 64 * 1024
def _policy_for_uncached(CP) -> AolVehiclePolicy:
try:
port = _PORTS.get(CP.brand)
return port.policy_for(CP) if port is not None else AolVehiclePolicy()
except (AttributeError, TypeError, ValueError, OverflowError, IndexError):
return AolVehiclePolicy()
def _car_params_bytes(CP) -> bytes | None:
# Unknown ducktypes keep the original dispatch behavior without serialization.
cp_type = type(CP)
if cp_type is not _DynamicStructBuilder and cp_type is not _DynamicStructReader:
return None
try:
if CP.schema != CarParams.schema:
return None
# Never serialize the original builder: to_bytes changes its write state.
snapshot = CP.copy() if cp_type is _DynamicStructBuilder else CP.as_builder()
if type(snapshot) is not _DynamicStructBuilder:
return None
key = snapshot.to_bytes()
return key if len(key) <= _POLICY_CACHE_MAX_BYTES else None
except Exception:
return None
@lru_cache(maxsize=_POLICY_CACHE_SIZE)
def _policy_for_bytes(key: bytes) -> AolVehiclePolicy:
# Evaluate the same immutable bytes as the key, never a mutable caller alias.
with CarParams.from_bytes(key) as CP:
if type(CP) is not _DynamicStructReader or CP.schema != CarParams.schema:
raise TypeError('Unsupported CarParams snapshot')
return _policy_for_uncached(CP)
def policy_for(CP) -> AolVehiclePolicy:
key = _car_params_bytes(CP)
if key is not None:
try:
# Only the frozen static policy is reused. Live authority stays uncached.
return _policy_for_bytes(key)
except Exception:
pass
return _policy_for_uncached(CP)
def native_profile_supported(model: int, param: int) -> bool:
return any(port.native_profile_supported(model, param) for port in _PORTS.values())
def native_matches_cp(CP, model: int, param: int) -> bool:
try:
if CP.brand == 'hyundai':
config = hyundai.aol_safety_config(CP)
return bool(config is not None and hyundai.native_accepts_cp(CP, model, param) and
model == int(config.safetyModel.raw) and param == int(config.safetyParam))
if CP.brand == 'ford':
config = ford.aol_safety_config(CP)
return bool(config is not None and ford.native_accepts_cp(CP, model, param) and
model == int(config.safetyModel.raw) and param == int(config.safetyParam))
if CP.brand == 'honda' and len(CP.safetyConfigs) == 2:
config = honda.aol_safety_config(CP)
return bool(config is not None and honda.native_accepts_cp(CP, model, param) and
model == int(config.safetyModel.raw) and param == int(config.safetyParam))
if CP.brand == 'volkswagen':
config = volkswagen.aol_safety_config(CP)
return bool(config is not None and volkswagen.native_accepts_cp(CP, model, param) and
model == int(config.safetyModel.raw) and param == int(config.safetyParam))
port = _PORTS.get(CP.brand)
return bool(port is not None and port.native_accepts_cp(CP, model, param) and
model == int(CP.safetyConfigs[0].safetyModel.raw) and
param == int(CP.safetyConfigs[-1].safetyParam))
except (AttributeError, TypeError, ValueError, OverflowError, IndexError):
return False
def native_admission_key(CP):
"""Bind structural reuse to the complete mutable vehicle configuration."""
try:
if CP.brand == 'hyundai':
existing = hyundai.native_admission_key(CP)
if existing is not None:
return existing
if CP.brand in _PORTS:
# Reuse only one exact packet's structural admission, for every owner.
# Full CP bytes invalidate every mutable field; live gates stay per call.
key = _car_params_bytes(CP)
if key is not None:
return ('vehicle-full-cp', key)
return None
except (AttributeError, TypeError, ValueError, OverflowError, IndexError):
return None
def native_latch_rejected(CP, native, *, state=None) -> bool:
if native is None:
return False
try:
port = _PORTS.get(CP.brand)
retain = getattr(port, 'retain_on_native_denial', None)
rejected = getattr(port, 'native_latch_rejected', None)
if retain is None and rejected is None:
return False
matched = native_matches_cp(CP, native.safetyModel, native.safetyParam)
if retain is not None and matched and retain(CP, native, state):
return False
return bool(rejected is not None and matched and rejected(CP, native))
except (AttributeError, TypeError, ValueError, OverflowError, IndexError):
return False
def create_intent(CP, settings, policy):
port = _PORTS.get(CP.brand)
factory = getattr(port, 'create_intent', None)
if factory is not None:
return factory(CP, settings)
return AolCardIntent(settings, explicit_latch=policy.explicit_latch)
def ordinary_axis_request_allowed(CP, CS) -> bool:
"""Required ordinary transport has no permission without its port owner."""
try:
port = _PORTS.get(CP.brand)
allowed = getattr(port, 'ordinary_axis_request_allowed', None)
return bool(allowed is not None and allowed(CP, CS))
except (AttributeError, TypeError, ValueError, OverflowError, IndexError):
return False
def allow_lateral_onset(CP, *, requested, normal_enabled, steering_pressed, previous_active):
"""Optional vehicle-owned onset restriction; never creates permission."""
port = _PORTS.get(CP.brand)
owner = getattr(port, 'allow_lateral_onset', None)
if owner is None:
return bool(requested)
return bool(requested and owner(CP, requested=requested, normal_enabled=normal_enabled,
steering_pressed=steering_pressed, previous_active=previous_active))
def configuration_settings_policy(CP):
from openpilot.starpilot.car.hyundai.settings import configuration_wheel_policy
return configuration_wheel_policy(CP) or toyota.configuration_settings_policy(CP) or gm.configuration_settings_policy(CP)
def native_bootstrap_supported(CP):
"""Dispatch only the newly owned Chrysler profile; preserve existing GM admission."""
try:
if CP.brand == 'chrysler':
return chrysler.native_bootstrap_supported(CP)
from opendbc.car.gm.aol import native_bootstrap_supported as gm_bootstrap
return gm_bootstrap(CP)
except (AttributeError, IndexError, TypeError, ValueError):
return False
def intent_disarming_fault(CP, events, state, **context):
if CP.brand == 'chrysler':
return chrysler.intent_disarming_fault(CP, events, state, **context)
return gm.intent_disarming_fault(CP, events, state, **context)