mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-10 06:23:43 +08:00
604 lines
35 KiB
Python
604 lines
35 KiB
Python
"""GM-owned AOL capabilities; exact CP admission lives with the vehicle."""
|
|
|
|
import math
|
|
from opendbc.car.gm.cancel_remap import aol_enabled, enabled as cancel_remap_enabled, supported as cancel_remap_supported
|
|
|
|
from opendbc.car.structs import CarParams
|
|
from opendbc.car.gm.values import CAR, GMFlags, control_flags, camera_acc_pedal_profile, is_volt_ascm_longitudinal
|
|
from opendbc.car.gm.aol import GM_AOL_ALTERNATIVE_EXPERIENCE, GM_AOL_WORDS, qualified_gm
|
|
from openpilot.starpilot.aol.policy import AolVehiclePolicy
|
|
from openpilot.starpilot.aol.intent import AOL_TOGGLE, AolCardIntent
|
|
|
|
|
|
def policy_for(cp) -> AolVehiclePolicy:
|
|
qualified = qualified_gm(cp)
|
|
return AolVehiclePolicy(intent_supported=qualified, settings_supported=qualified,
|
|
runtime_supported=qualified, normal_runtime_supported=qualified,
|
|
lkas_button_supported=False,
|
|
full_axis_runtime_required=qualified and aol_enabled(cp),
|
|
alternative_experience_addition=GM_AOL_ALTERNATIVE_EXPERIENCE if qualified else 0)
|
|
|
|
|
|
def native_profile_supported(model: int, param: int) -> bool:
|
|
return model == int(CarParams.SafetyModel.gm) and param in GM_AOL_WORDS
|
|
|
|
|
|
def native_accepts_cp(cp, model: int, param: int) -> bool:
|
|
return (qualified_gm(cp) and aol_enabled(cp) and
|
|
native_profile_supported(model, param))
|
|
|
|
|
|
def cc_independent_lateral_below_engage(cp, intent, *, enabled, active):
|
|
"""An armed independent Bolt LAT request uses its steering speed gate."""
|
|
from opendbc.car.gm.values import is_bolt_cc_profile
|
|
return bool(not enabled and not active and qualified_gm(cp) and aol_enabled(cp) and
|
|
is_bolt_cc_profile(cp) and intent is not None and intent.settingsQualified and
|
|
intent.allowedLatch and intent.lateralArmed and not intent.pauseLateral)
|
|
|
|
|
|
def f1_conventional_lateral_owner(cp) -> bool:
|
|
"""Exact development F1 conventional-cruise owner; no pedal or ACC profile."""
|
|
from opendbc.car.gm.values import VOLT_CC_F1_WORD, is_volt_cc_profile
|
|
return bool(is_volt_cc_profile(cp) and qualified_gm(cp) and aol_enabled(cp) and
|
|
int(cp.safetyConfigs[0].safetyParam) == VOLT_CC_F1_WORD)
|
|
|
|
|
|
def sdgm_alpha_lateral_owner(cp) -> bool:
|
|
from opendbc.car.gm.values import is_volt_sdgm_accepted_envelope
|
|
return bool(qualified_gm(cp) and aol_enabled(cp) and is_volt_sdgm_accepted_envelope(cp))
|
|
|
|
|
|
def steering_raw_projection_owner(cp) -> bool:
|
|
"""Qualified non-pedal Volt owners consume their exact raw steering receipt."""
|
|
from opendbc.car.gm.steering_authority import monitored_profile
|
|
return bool(qualified_gm(cp) and monitored_profile(cp))
|
|
|
|
|
|
|
|
def steering_source_gap_intent_only(owner, cs, companion, now_ns: int) -> bool:
|
|
"""Recognize lost Card publication; never supply a current steering receipt."""
|
|
from openpilot.starpilot.aol.runtime import INTENT_MAX_AGE_NS
|
|
service = 'starpilotCarState'
|
|
sdgm = sdgm_alpha_lateral_owner(owner.CP)
|
|
if (not (is_volt_ascm_longitudinal(owner.CP) or sdgm) or not steering_raw_projection_owner(owner.CP) or
|
|
getattr(owner, 'steering_authority_unavailable', False) or
|
|
getattr(owner, 'car_state_samples', ())):
|
|
return False
|
|
anchor = getattr(owner, 'aol_ordinary_source_anchor', None)
|
|
accepted = anchor[2] if anchor is not None else None
|
|
held = bool(accepted is not None and
|
|
getattr(owner, 'aol_publication_retained_intent', None) is accepted and
|
|
getattr(owner, 'aol_published_fault_reason', '') == 'availabilityPause')
|
|
if not (getattr(owner, 'aol_expired_healthy_source', False) or held):
|
|
return False
|
|
source = getattr(owner, 'car_state_retained_source_ns', 0)
|
|
if not source and held:
|
|
source = int(accepted.carStateLogMonoTime)
|
|
latest = companion.latest
|
|
sm = owner.sm
|
|
if (latest is None or not latest[1] or latest[2] != latest[0] or latest[3] or
|
|
not (latest[0] == source and not sdgm or sdgm and source < latest[0] <= now_ns and
|
|
now_ns - latest[0] <= INTENT_MAX_AGE_NS) or
|
|
not 0 < source < now_ns or now_ns - source <= INTENT_MAX_AGE_NS or
|
|
not sm.seen.get(service, False) or not sm.valid.get(service, False) or not sm.alive.get(service, False)):
|
|
return False
|
|
if sdgm and any(not valid or stamp != observed_source or unavailable
|
|
for stamp, valid, observed_source, unavailable in companion.samples.values() if stamp > source):
|
|
return False
|
|
cached = sm[service]
|
|
stamp = int(sm.logMonoTime[service])
|
|
if (not 0 < stamp <= (latest[0] if sdgm else source) or cached.sourceCarStateMonoTime != stamp or
|
|
cached.lateralAuthorityUnavailable):
|
|
return False
|
|
# The existing classifier owns every original 200ms intent/native, identity,
|
|
# physical, fault and host-health veto. The 30ms source/output cutoff remains
|
|
# independent. No old receipt becomes current and no fault latch is cleared.
|
|
return owner.ordinary_source_availability(cs, now_ns)
|
|
|
|
|
|
def held_sdgm_lateral_pedal(owner, cs) -> bool:
|
|
"""An already accepted LAT-only pedal state remains intent, never output."""
|
|
from openpilot.starpilot.nostalgia import physical_cancel
|
|
anchor = getattr(owner, 'aol_ordinary_source_anchor', None)
|
|
if not sdgm_alpha_lateral_owner(owner.CP) or anchor is None:
|
|
return False
|
|
proof, native, accepted, _, accepted_state = anchor
|
|
settings = getattr(owner, 'aol_settings', None)
|
|
return bool(cs is accepted_state and not getattr(owner, 'car_state_samples', ()) and
|
|
proof[8] and not proof[9] and not owner.enabled and not owner.active and
|
|
native is not None and native.requestedLateral and native.lateralAllowed and
|
|
not native.requestedLongitudinal and not native.longitudinalAllowed and
|
|
accepted is not None and accepted.allowedLatch and accepted.lateralArmed and
|
|
accepted.settingsQualified and not accepted.pauseLateral and not accepted.pauseLongitudinal and
|
|
settings is not None and settings.enabled and
|
|
not getattr(owner, 'aol_dm_lateral_inhibit', False) and
|
|
sdgm_alpha_pedal_withdrawal(owner.CP, cs) and not cs.gasPressed and
|
|
not cs.seatbeltUnlatched and not physical_cancel(cs, owner.CP) and
|
|
(not cs.brakePressed or cs.standstill or cs.vEgo >= settings.pause_brake_mps))
|
|
|
|
|
|
|
|
def sdgm_lateral_pedal_source_zero(feedback, cp, cs, axis, native, now_ns, *, producer):
|
|
"""Fresh physical braking cannot turn an accepted LAT-only source wait fatal."""
|
|
from openpilot.starpilot.aol.runtime import SAFETY_MAX_AGE_NS
|
|
from openpilot.starpilot.nostalgia import physical_cancel
|
|
prior = feedback.signature
|
|
return bool(sdgm_alpha_lateral_owner(cp) and feedback.lateral_only_baseline and
|
|
not feedback.failed and not feedback.pending_event and prior is not None and
|
|
prior[0] == feedback.session and prior[7] == feedback.session and
|
|
prior[14:16] == (producer, feedback.availability_epoch) and
|
|
prior[11] and prior[13] and not prior[10] and not prior[12] and
|
|
(prior[5] == 'none' and prior[9] or feedback.retention_verified and
|
|
prior[5] == 'availabilityPause' and prior[8] == 0 and not prior[9]) and
|
|
0 < feedback.lateral_source_ns <= now_ns <= feedback.lateral_source_ns + SAFETY_MAX_AGE_NS and
|
|
str(axis.faultReason) == 'availabilityPause' and axis.sourceCarStateMonoTime == 0 and
|
|
axis.desiredLateral and axis.nativeAcknowledged and not axis.desiredLongitudinal and
|
|
not axis.lateralActive and not axis.longitudinalActive and
|
|
sdgm_alpha_pedal_withdrawal(cp, cs) and not cs.gasPressed and not cs.seatbeltUnlatched and
|
|
not physical_cancel(cs, cp) and native is not None and
|
|
native.requestedLateral and native.lateralAllowed and
|
|
not native.requestedLongitudinal and not native.longitudinalAllowed)
|
|
|
|
|
|
def cold_gm_main_intent_current(feedback, sm, cp, cs, axis, native, now_ns, *, producer):
|
|
"""Cold MAIN may wait on a current SOFT occurrence without renewing output."""
|
|
from opendbc.car.structs import car
|
|
from openpilot.starpilot.nostalgia import physical_cancel
|
|
from openpilot.starpilot.aol.runtime import AXIS_MAX_AGE_NS
|
|
event_ns = int(sm.logMonoTime['onroadEvents'])
|
|
return bool(qualified_gm(cp) and aol_enabled(cp) and
|
|
not feedback.failed and not feedback.paused and not feedback.pending_event and
|
|
not feedback.ever_lateral_active and str(axis.faultReason) == 'availabilityPause' and
|
|
not axis.desiredLateral and not axis.desiredLongitudinal and
|
|
not axis.lateralActive and not axis.longitudinalActive and
|
|
producer and str(axis.intentProducerSessionId) == producer and
|
|
(feedback.signature is None or
|
|
(str(axis.intentProducerSessionId), int(axis.intentIntegrityEpoch)) == feedback.signature[14:16]) and
|
|
0 < axis.sourceCarStateMonoTime <= axis.observedMonoTime <= now_ns and
|
|
now_ns - axis.sourceCarStateMonoTime <= AXIS_MAX_AGE_NS and
|
|
axis.faultEventMonoTime == event_ns and sm.seen['onroadEvents'] and
|
|
sm.valid['onroadEvents'] and sm.alive['onroadEvents'] and
|
|
0 < event_ns <= axis.observedMonoTime and now_ns - event_ns <= 1_500_000_000 and
|
|
cs.gearShifter in (car.CarState.GearShifter.drive, car.CarState.GearShifter.low,
|
|
car.CarState.GearShifter.sport, car.CarState.GearShifter.brake,
|
|
car.CarState.GearShifter.manumatic) and
|
|
not physical_cancel(cs, cp) and
|
|
cold_main_readiness(cp, cs, native, sm['onroadEvents']))
|
|
|
|
|
|
def sdgm_alpha_pedal_withdrawal(cp, cs) -> bool:
|
|
"""Physical LONG withdrawal for the 2019 SASCM Alpha configuration."""
|
|
from opendbc.car.gm.values import is_volt_sdgm_accepted_envelope
|
|
from opendbc.car.structs import car
|
|
return bool(qualified_gm(cp) and aol_enabled(cp) and
|
|
is_volt_sdgm_accepted_envelope(cp) and
|
|
cs.canValid and not cs.canTimeout and cs.cruiseState.available and
|
|
not cs.steerFaultTemporary and not cs.steerFaultPermanent and not cs.accFaulted and
|
|
not cs.doorOpen and not cs.steeringDisengage and
|
|
cs.gearShifter in (car.CarState.GearShifter.drive, car.CarState.GearShifter.low,
|
|
car.CarState.GearShifter.sport, car.CarState.GearShifter.brake,
|
|
car.CarState.GearShifter.manumatic) and
|
|
(cs.brakePressed or cs.regenBraking))
|
|
|
|
|
|
def gm_physical_pedal_withdrawal(cp, cs) -> bool:
|
|
"""A qualified GM physical pedal withdraws LONG without canceling MAIN intent."""
|
|
from opendbc.car.structs import car
|
|
return bool(qualified_gm(cp) and aol_enabled(cp) and
|
|
cs.canValid and not cs.canTimeout and cs.cruiseState.available and
|
|
not cs.steerFaultTemporary and not cs.steerFaultPermanent and not cs.accFaulted and
|
|
not cs.doorOpen and not cs.steeringDisengage and
|
|
cs.gearShifter in (car.CarState.GearShifter.drive, car.CarState.GearShifter.low,
|
|
car.CarState.GearShifter.sport, car.CarState.GearShifter.brake,
|
|
car.CarState.GearShifter.manumatic) and
|
|
(cs.brakePressed or cs.regenBraking))
|
|
|
|
|
|
def stock_ascm_independent_lateral(cp) -> bool:
|
|
"""Stock cruise's engagement speed does not cancel independent lateral intent."""
|
|
return bool(qualified_gm(cp) and cp.carFingerprint == CAR.CHEVROLET_VOLT_ASCM and
|
|
cp.pcmCruise and not cp.openpilotLongitudinalControl and
|
|
not cp.flags & GMFlags.PEDAL_LONG)
|
|
|
|
|
|
class GmAolCardIntent(AolCardIntent):
|
|
"""Follow physical MAIN; a current fault inhibits intent until healthy again."""
|
|
|
|
def __init__(self, settings, cp=None):
|
|
super().__init__(settings, lkas_button_supported=False)
|
|
self.cp = cp
|
|
self.requires_fault_observation = True
|
|
self._main_cycle_required = False
|
|
self._main_available = False
|
|
self._main_arm_ns = 0
|
|
self.startup_calibration_pending = True
|
|
self._startup_incomplete = False
|
|
|
|
def update_auxiliary(self, CS, *, media=None, media_eligible=False, fault_active=None):
|
|
semantic = CS
|
|
if (cancel_remap_supported(self.cp) and not cancel_remap_enabled(self.cp) and
|
|
any(str(event.type) == 'cancel' for event in CS.buttonEvents)):
|
|
semantic = CS.as_builder() if hasattr(CS, 'as_builder') else CS.as_reader().as_builder()
|
|
semantic.buttonEvents = [event for event in CS.buttonEvents if str(event.type) != 'cancel']
|
|
super().update_auxiliary(semantic, media=media, media_eligible=media_eligible, fault_active=fault_active)
|
|
|
|
@property
|
|
def main_action(self):
|
|
# GM MAIN reports a persistent physical level, not an independent AOL toggle.
|
|
return 0 if self.settings.main_action == AOL_TOGGLE else self.settings.main_action
|
|
|
|
def update(self, CS, *, consumed_buttons=frozenset(), fault_active=None,
|
|
now_ns=0, native_rejection_ns=0, standard_enabled=False):
|
|
was_allowed = self.allowed_latch
|
|
main_rise = False
|
|
if CS.canValid and not CS.canTimeout:
|
|
main_rise = bool(CS.cruiseState.available and not self._main_available)
|
|
self._main_available = bool(CS.cruiseState.available)
|
|
fault = bool(fault_active or CS.steerFaultPermanent)
|
|
if fault:
|
|
self._main_cycle_required = True
|
|
elif fault_active is False or CS.canValid and not CS.canTimeout and not CS.cruiseState.available:
|
|
# MAIN is a level on GM, including when already ON at startup. A fresh
|
|
# healthy fault observation restores that request without a button cycle.
|
|
self._main_cycle_required = False
|
|
super().update(CS, consumed_buttons=consumed_buttons, fault_active=fault_active,
|
|
now_ns=now_ns, native_rejection_ns=native_rejection_ns,
|
|
standard_enabled=standard_enabled)
|
|
if self._main_cycle_required:
|
|
self.allowed_latch = False
|
|
self.startup_calibration_pending = False
|
|
if not self.allowed_latch:
|
|
self._main_arm_ns = 0
|
|
elif main_rise or not was_allowed:
|
|
self._main_arm_ns = now_ns
|
|
|
|
@property
|
|
def main_arm_ns(self):
|
|
return (self._main_arm_ns if self.settings.enabled and self.main_action != AOL_TOGGLE and
|
|
not self.pause_lateral and not self.pause_longitudinal else 0)
|
|
|
|
def _arm_from_full_engagement(self, CS, *, source_ns):
|
|
self._main_cycle_required = False
|
|
super()._arm_from_full_engagement(CS, source_ns=source_ns)
|
|
|
|
def observe_calibration(self, CS, *, events=None, standard_enabled=False):
|
|
from openpilot.cereal import log
|
|
# Cold calibration readiness is not a previously revoked driver gesture.
|
|
# The original calibration inhibit still withdraws all optional output.
|
|
prior_incomplete = self._startup_incomplete
|
|
cold_safe = True
|
|
if events is not None:
|
|
from openpilot.starpilot.aol.intent import disarming_fault
|
|
remaining = [event for event in _intent_fault_events(self.cp, events) if
|
|
event.name != log.OnroadEvent.EventName.calibrationIncomplete or event.immediateDisable]
|
|
cold_safe = not disarming_fault(remaining, CS,
|
|
temporary_selfdrive_lagging=qualified_gm(self.cp))
|
|
self._startup_incomplete = bool(cold_safe and
|
|
any(event.name == log.OnroadEvent.EventName.calibrationIncomplete for event in events))
|
|
cold_readiness = bool(self.startup_calibration_pending and cold_safe and
|
|
(prior_incomplete or self._startup_incomplete) and
|
|
self.settings.enabled and self.main_action != AOL_TOGGLE and
|
|
not CS.steerFaultPermanent and not self._main_cycle_required)
|
|
if cold_readiness:
|
|
self._calibration_rearm_required = False
|
|
if (events is not None and cold_safe and not self._main_cycle_required and not any(
|
|
event.name in (log.OnroadEvent.EventName.calibrationInvalid,
|
|
log.OnroadEvent.EventName.calibrationIncomplete,
|
|
log.OnroadEvent.EventName.calibrationRecalibrating) for event in events)):
|
|
self._calibration_rearm_required = False
|
|
super().observe_calibration(CS, events=events, standard_enabled=standard_enabled)
|
|
if cold_readiness:
|
|
self._calibration_rearm_required = False
|
|
if (events is not None and CS.canValid and not CS.canTimeout and not self._startup_incomplete and
|
|
not any(event.name == log.OnroadEvent.EventName.selfdriveInitializing for event in events)):
|
|
self.startup_calibration_pending = False
|
|
|
|
def output(self, CS):
|
|
allowed, pause_lateral, pause_longitudinal = super().output(CS)
|
|
return (bool(allowed and not CS.canTimeout and CS.cruiseState.available),
|
|
pause_lateral, pause_longitudinal)
|
|
|
|
|
|
def create_intent(cp, settings):
|
|
return GmAolCardIntent(settings, cp) if qualified_gm(cp) else None
|
|
|
|
|
|
def cold_park_readiness(cp, cs, native, events):
|
|
"""Retain cold MAIN intent while parked; acknowledge only idle native axes."""
|
|
from opendbc.car.structs import car
|
|
from openpilot.cereal import log
|
|
names = (log.OnroadEvent.EventName.wrongGear, log.OnroadEvent.EventName.seatbeltNotLatched,
|
|
log.OnroadEvent.EventName.commIssue, log.OnroadEvent.EventName.commIssueAvgFreq,
|
|
log.OnroadEvent.EventName.locationdTemporaryError)
|
|
return bool(qualified_gm(cp) and aol_enabled(cp) and
|
|
cs.gearShifter == car.CarState.GearShifter.park and cs.standstill and
|
|
math.isfinite(cs.vEgo) and abs(cs.vEgo) <= .3 and
|
|
cs.canValid and not cs.canTimeout and cs.cruiseState.available and not cs.cruiseState.enabled and
|
|
not cs.steerFaultTemporary and not cs.steerFaultPermanent and not cs.accFaulted and
|
|
not cs.doorOpen and not cs.gasPressed and not cs.steeringDisengage and
|
|
native is not None and native.pandaInventory and
|
|
not native.requestedLateral and not native.lateralAllowed and
|
|
not native.requestedLongitudinal and not native.longitudinalAllowed and
|
|
not any(event.immediateDisable or event.softDisable and event.name not in names or
|
|
event.userDisable and not (
|
|
event.name == log.OnroadEvent.EventName.wrongGear and event.noEntry and event.softDisable or
|
|
event.name == log.OnroadEvent.EventName.pedalPressed and (cs.brakePressed or cs.regenBraking) or
|
|
event.name == log.OnroadEvent.EventName.pcmDisable and not cs.cruiseState.enabled)
|
|
for event in events))
|
|
|
|
|
|
def cold_main_readiness(cp, cs, native, events, *, recovering=False):
|
|
"""A MAIN request may wait for host startup without acquiring either axis."""
|
|
from openpilot.cereal import log
|
|
names = (log.OnroadEvent.EventName.commIssue, log.OnroadEvent.EventName.commIssueAvgFreq,
|
|
log.OnroadEvent.EventName.locationdTemporaryError, log.OnroadEvent.EventName.processNotRunning)
|
|
faults = [event for event in events if event.softDisable or event.immediateDisable]
|
|
host_wait = not faults if recovering else bool(faults and
|
|
all(event.softDisable and not event.immediateDisable and event.name in names for event in faults))
|
|
return bool(qualified_gm(cp) and aol_enabled(cp) and host_wait and
|
|
not any(event.userDisable and not (gm_physical_pedal_withdrawal(cp, cs) and
|
|
event.name == log.OnroadEvent.EventName.pedalPressed and (cs.brakePressed or cs.regenBraking) and
|
|
not event.immediateDisable and not event.softDisable) for event in events) and
|
|
cs.canValid and not cs.canTimeout and cs.cruiseState.available and
|
|
not cs.steerFaultTemporary and not cs.steerFaultPermanent and not cs.accFaulted and
|
|
not cs.doorOpen and not cs.seatbeltUnlatched and not cs.steeringDisengage and
|
|
native is not None and native.pandaInventory and
|
|
not native.requestedLateral and not native.lateralAllowed and
|
|
not native.requestedLongitudinal and not native.longitudinalAllowed)
|
|
|
|
|
|
def main_off_idle_native_baseline(cp, cs, axis, native, events, *, producer):
|
|
"""Physical MAIN OFF can acknowledge zero native axes despite NO_ENTRY."""
|
|
from opendbc.car.structs import car
|
|
from openpilot.cereal import log
|
|
return bool(qualified_gm(cp) and aol_enabled(cp) and cs.canValid and not cs.canTimeout and
|
|
not cs.cruiseState.available and not cs.steerFaultTemporary and not cs.steerFaultPermanent and
|
|
not cs.accFaulted and not cs.doorOpen and not cs.seatbeltUnlatched and not cs.steeringDisengage and
|
|
cs.gearShifter in (car.CarState.GearShifter.drive, car.CarState.GearShifter.low,
|
|
car.CarState.GearShifter.sport, car.CarState.GearShifter.brake,
|
|
car.CarState.GearShifter.manumatic) and
|
|
producer and str(axis.intentProducerSessionId) == producer and str(axis.faultReason) == 'none' and
|
|
not axis.desiredLateral and not axis.desiredLongitudinal and
|
|
not axis.lateralActive and not axis.longitudinalActive and
|
|
native is not None and native.pandaInventory and
|
|
not native.requestedLateral and not native.lateralAllowed and
|
|
not native.requestedLongitudinal and not native.longitudinalAllowed and
|
|
not any(event.immediateDisable or event.softDisable or event.userDisable and not (
|
|
event.name in (log.OnroadEvent.EventName.wrongCarMode, log.OnroadEvent.EventName.pcmDisable) or
|
|
event.name == log.OnroadEvent.EventName.pedalPressed and (cs.brakePressed or cs.regenBraking))
|
|
for event in events))
|
|
|
|
|
|
def prior_main_off_intent_current(feedback, sm, cp, cs, axis, now_ns, *, latched, main_arm_ns, producer):
|
|
"""An older idle response cannot reject a newly armed cruise MAIN request."""
|
|
from opendbc.car.structs import car
|
|
from openpilot.cereal import log
|
|
from openpilot.starpilot.aol.runtime import AXIS_MAX_AGE_NS
|
|
from openpilot.starpilot.aol.transport_pause import _healthy_transport_native
|
|
|
|
if (not qualified_gm(cp) or not latched or not 0 < main_arm_ns <= now_ns or
|
|
now_ns - main_arm_ns > AXIS_MAX_AGE_NS or
|
|
feedback.failed or feedback.paused or feedback.pending_event or
|
|
feedback.retention_started_ns or feedback.source_retention_started_ns or
|
|
feedback.longitudinal_cancel_ns or feedback.honda_brake_ns or feedback.gm_withdrawal_ns or
|
|
not feedback.signature or not producer or
|
|
(str(axis.intentProducerSessionId), int(axis.intentIntegrityEpoch)) != feedback.signature[14:16] or
|
|
str(axis.intentProducerSessionId) != producer or
|
|
not cs.canValid or cs.canTimeout or not cs.cruiseState.available or cs.accFaulted or cs.doorOpen or
|
|
cs.steerFaultTemporary or cs.steerFaultPermanent or
|
|
cs.gearShifter not in (car.CarState.GearShifter.drive, car.CarState.GearShifter.low,
|
|
car.CarState.GearShifter.sport, car.CarState.GearShifter.brake) or
|
|
any(event.type == car.CarState.ButtonEvent.Type.cancel for event in cs.buttonEvents) or
|
|
str(axis.faultReason) != 'none' or
|
|
axis.desiredLateral or axis.desiredLongitudinal or axis.lateralActive or axis.longitudinalActive or
|
|
not 0 < axis.sourceCarStateMonoTime <= axis.observedMonoTime or axis.sourceCarStateMonoTime >= main_arm_ns or
|
|
now_ns - axis.sourceCarStateMonoTime > AXIS_MAX_AGE_NS or
|
|
not sm.seen['onroadEvents'] or not sm.valid['onroadEvents'] or not sm.alive['onroadEvents'] or
|
|
not 0 < sm.logMonoTime['onroadEvents'] <= now_ns or
|
|
now_ns - sm.logMonoTime['onroadEvents'] > 1_500_000_000):
|
|
return False
|
|
for event in sm['onroadEvents']:
|
|
if event.immediateDisable or event.softDisable:
|
|
return False
|
|
if event.userDisable and not (
|
|
event.name == log.OnroadEvent.EventName.pedalPressed and (cs.brakePressed or cs.regenBraking) or
|
|
event.name == log.OnroadEvent.EventName.pcmDisable and not cs.cruiseState.enabled or
|
|
event.name == log.OnroadEvent.EventName.wrongCarMode and sm.logMonoTime['onroadEvents'] < main_arm_ns):
|
|
return False
|
|
native = _healthy_transport_native(sm, cp, cs, str(axis.sessionId), now_ns)
|
|
return bool(native is not None and native.pandaInventory and
|
|
not native.requestedLateral and not native.requestedLongitudinal and
|
|
not native.lateralAllowed and not native.longitudinalAllowed)
|
|
|
|
|
|
def configuration_settings_policy(cp):
|
|
"""Saving stock Silverado lateral intent does not establish its physical source graph."""
|
|
try:
|
|
if (cp.brand == 'gm' and cp.carFingerprint == CAR.CHEVROLET_MALIBU_HYBRID_CC and
|
|
not cp.notCar and not cp.passive and cp.lateralTuning.which() == 'torque' and
|
|
cp.steerControlType == CarParams.SteerControlType.torque and
|
|
int(cp.alternativeExperience) in (0, GM_AOL_ALTERNATIVE_EXPERIENCE)):
|
|
return AolVehiclePolicy(settings_supported=True, lkas_button_supported=False)
|
|
if (cp.brand != 'gm' or cp.carFingerprint != CAR.CHEVROLET_SILVERADO_CC or
|
|
cp.notCar or cp.passive or cp.networkLocation != CarParams.NetworkLocation.gateway or
|
|
cp.transmissionType != CarParams.TransmissionType.automatic or not cp.radarUnavailable or
|
|
not cp.pcmCruise or cp.openpilotLongitudinalControl or cp.alphaLongitudinalAvailable or
|
|
control_flags(cp) != 0 or int(cp.alternativeExperience) not in (0, GM_AOL_ALTERNATIVE_EXPERIENCE) or
|
|
len(cp.safetyConfigs) != 1 or cp.safetyConfigs[0].safetyModel != CarParams.SafetyModel.gm or
|
|
int(cp.safetyConfigs[0].safetyParam) != 16 or cp.steerControlType != CarParams.SteerControlType.torque or
|
|
cp.lateralTuning.which() != 'torque'):
|
|
return None
|
|
tune = cp.lateralTuning.torque
|
|
if (not all(math.isfinite(value) for value in (tune.latAccelFactor, tune.latAccelOffset, tune.friction)) or
|
|
tune.latAccelFactor <= 0 or tune.friction < 0):
|
|
return None
|
|
return AolVehiclePolicy(settings_supported=True, lkas_button_supported=False)
|
|
except (AttributeError, IndexError, TypeError, ValueError, OverflowError):
|
|
return None
|
|
|
|
|
|
def _intent_fault_events(cp, events):
|
|
from openpilot.cereal import log
|
|
if qualified_gm(cp):
|
|
availability = (log.OnroadEvent.EventName.commIssue, log.OnroadEvent.EventName.commIssueAvgFreq)
|
|
events = [event for event in events if not
|
|
(event.name in availability and event.softDisable and not event.immediateDisable)]
|
|
if qualified_gm(cp) and camera_acc_pedal_profile(cp) is None and is_volt_ascm_longitudinal(cp):
|
|
events = [event for event in events if event.name != log.OnroadEvent.EventName.accFaulted]
|
|
return events
|
|
|
|
|
|
def intent_disarming_fault(cp, events, state, **context):
|
|
"""An ASCM Volt ACC fault withdraws axes without erasing main-armed intent."""
|
|
from openpilot.cereal import log
|
|
from openpilot.starpilot.aol.intent import disarming_fault
|
|
cold_calibration = context.pop('startup_calibration_pending', False)
|
|
if qualified_gm(cp) and cold_calibration and not state.steerFaultPermanent:
|
|
events = [event for event in events if not (
|
|
event.name == log.OnroadEvent.EventName.calibrationIncomplete and not event.immediateDisable)]
|
|
return disarming_fault(_intent_fault_events(cp, events), state, **context)
|
|
|
|
|
|
def physical_longitudinal_withdrawal_source(cp, cs, parser, observation, now_ns) -> int:
|
|
"""Bind a qualified GM physical withdrawal to parsed CAN and local receipt."""
|
|
from opendbc.car.gm.values import CruiseButtons, is_bolt_present_no_acc_pedal_profile
|
|
from opendbc.car.structs import car
|
|
try:
|
|
if (not qualified_gm(cp) or not (is_bolt_present_no_acc_pedal_profile(cp) or f1_conventional_lateral_owner(cp)) or
|
|
not aol_enabled(cp) or
|
|
observation is None or observation[0] is not cs or not cs.canValid or cs.canTimeout):
|
|
return 0
|
|
fields = []
|
|
if cs.brakePressed and parser.vl['ECMEngineStatus']['BrakePressed']:
|
|
fields.append(('ECMEngineStatus', 'BrakePressed'))
|
|
if (f1_conventional_lateral_owner(cp) and cs.brakePressed and
|
|
parser.vl['EBCMBrakePedalPosition']['BrakePedalPosition'] >= 6):
|
|
fields.append(('EBCMBrakePedalPosition', 'BrakePedalPosition'))
|
|
if cs.regenBraking and parser.vl['EBCMRegenPaddle']['RegenPaddle']:
|
|
fields.append(('EBCMRegenPaddle', 'RegenPaddle'))
|
|
if (parser.vl['ASCMSteeringButton']['ACCButtons'] == CruiseButtons.CANCEL and
|
|
any(e.type == car.CarState.ButtonEvent.Type.cancel and e.pressed for e in cs.buttonEvents)):
|
|
fields.append(('ASCMSteeringButton', 'ACCButtons'))
|
|
_, received_ns, can_source_ns, boot_ns = observation
|
|
if not fields or not 0 < received_ns <= now_ns or now_ns - received_ns > 30_000_000:
|
|
return 0
|
|
# Parser/CAN batch use BOOTTIME. The fixed deadline uses the original
|
|
# local MONOTONIC receipt; neither clock is converted or restamped.
|
|
stamps = [int(parser.ts_nanos[message][signal]) for message, signal in fields]
|
|
if not all(0 < stamp <= can_source_ns <= boot_ns and boot_ns - stamp <= 30_000_000 and
|
|
can_source_ns - stamp <= 30_000_000 for stamp in stamps):
|
|
return 0
|
|
return received_ns
|
|
except (AttributeError, KeyError, IndexError, TypeError, ValueError):
|
|
return 0
|
|
|
|
|
|
def retained_physical_longitudinal_intent(feedback, sm, cp, cs, axis, now_ns, *, latched, source_ns, producer):
|
|
"""Retain accepted LAT intent through a bounded physical LONG withdrawal."""
|
|
from opendbc.car.gm.values import is_bolt_present_no_acc_pedal_profile
|
|
from opendbc.car.structs import car
|
|
from openpilot.cereal import log
|
|
from openpilot.starpilot.aol.runtime import AXIS_MAX_AGE_NS, SAFETY_MAX_AGE_NS
|
|
from openpilot.starpilot.aol.transport_pause import independent_lateral_health
|
|
occurrence = feedback.gm_withdrawal_ns or source_ns
|
|
if (not qualified_gm(cp) or not (is_bolt_present_no_acc_pedal_profile(cp) or f1_conventional_lateral_owner(cp)) or
|
|
not aol_enabled(cp) or
|
|
not latched or feedback.failed or feedback.paused or feedback.pending_event or
|
|
feedback.retention_started_ns or not feedback.session or str(axis.sessionId) != feedback.session or
|
|
not feedback.healthy_feedback_ns or not 0 <= now_ns - feedback.healthy_feedback_ns <= AXIS_MAX_AGE_NS or
|
|
not 0 < occurrence <= now_ns or now_ns - occurrence > SAFETY_MAX_AGE_NS or
|
|
not feedback.gm_withdrawal_ns and now_ns - occurrence > AXIS_MAX_AGE_NS or
|
|
not producer or producer != feedback.availability_producer or
|
|
str(axis.intentProducerSessionId) != producer or int(axis.intentIntegrityEpoch) != feedback.availability_epoch or
|
|
cs.accFaulted or cs.doorOpen or not cs.cruiseState.available or
|
|
cs.gearShifter not in (car.CarState.GearShifter.drive, car.CarState.GearShifter.low,
|
|
car.CarState.GearShifter.sport, car.CarState.GearShifter.brake) or
|
|
str(axis.faultReason) != 'none' or not axis.desiredLateral or not axis.lateralActive or
|
|
not axis.nativeAcknowledged or
|
|
not 0 < axis.sourceCarStateMonoTime <= axis.observedMonoTime <= now_ns or
|
|
now_ns - axis.sourceCarStateMonoTime > AXIS_MAX_AGE_NS):
|
|
return None
|
|
# The saved LAT contract treats a parsed unlatched belt as a LONG lock.
|
|
# After rebuckling, accept only a prior producer's matching belt event and
|
|
# LAT-only proof within the already established physical occurrence. Events
|
|
# publish sparsely, so their stamp binds to faultEventMonoTime, not the newer
|
|
# car-state source. The original axis/native leases remain unchanged.
|
|
event_ns = int(sm.logMonoTime['onroadEvents'])
|
|
prior_belt_proof = bool(feedback.gm_withdrawal_ns and
|
|
feedback.gm_withdrawal_ns <= event_ns <= axis.observedMonoTime < now_ns and
|
|
event_ns == axis.faultEventMonoTime and str(axis.faultSessionId) == feedback.session and
|
|
int(sm.logMonoTime['aolAxisState']) == axis.observedMonoTime and
|
|
now_ns <= axis.validUntilMonoTime <= axis.observedMonoTime + AXIS_MAX_AGE_NS and
|
|
not axis.desiredLongitudinal and not axis.longitudinalActive)
|
|
if (not sm.seen['onroadEvents'] or not sm.valid['onroadEvents'] or not sm.alive['onroadEvents'] or
|
|
not 0 < event_ns <= now_ns or now_ns - event_ns > 1_500_000_000 or
|
|
any(e.immediateDisable or e.softDisable and not
|
|
((cs.seatbeltUnlatched or prior_belt_proof) and e.name == log.OnroadEvent.EventName.seatbeltNotLatched and
|
|
e.noEntry and not e.userDisable) for e in sm['onroadEvents'])):
|
|
return None
|
|
native = independent_lateral_health(sm, cp, cs, feedback.session, now_ns)
|
|
if (native is None or not native.pandaInventory or not native.requestedLongitudinal or
|
|
not any(not p.controlsAllowed for p in sm['pandaStates']) or
|
|
native.observedMonoTime < feedback.healthy_native_ns or
|
|
(native.pandaSerial, tuple(p.hardwareSerial for p in native.pandaInventory)) != feedback.healthy_inventory):
|
|
return None
|
|
# One occurrence spans the held native request. Held/released samples and
|
|
# duplicate Card calls cannot renew it; output/native leases remain original.
|
|
feedback.gm_withdrawal_ns = occurrence
|
|
return native
|
|
|
|
|
|
def physical_cancel_lateral_allowed(cp, cs, intent, native, *, source_ns, now_ns) -> bool:
|
|
"""Exact armed physical Cancel removes only the lateral NO_ENTRY restriction."""
|
|
from opendbc.car.gm.values import is_bolt_present_no_acc_pedal_profile
|
|
from opendbc.car.structs import car
|
|
from openpilot.starpilot.aol.runtime import AXIS_MAX_AGE_NS, SAFETY_MAX_AGE_NS, native_inventory_matches_cp
|
|
try:
|
|
return bool(qualified_gm(cp) and (is_bolt_present_no_acc_pedal_profile(cp) or f1_conventional_lateral_owner(cp)) and
|
|
aol_enabled(cp) and
|
|
intent is not None and intent.settingsQualified and intent.allowedLatch and intent.lateralArmed and
|
|
intent.producerSessionId and intent.carStateLogMonoTime == source_ns and
|
|
0 < source_ns <= intent.observedMonoTime <= now_ns <= intent.validUntilMonoTime and
|
|
now_ns - source_ns <= AXIS_MAX_AGE_NS and
|
|
# Card declares a 200ms wire lease; admission and this source remain 30ms fresh.
|
|
intent.validUntilMonoTime - intent.observedMonoTime <= SAFETY_MAX_AGE_NS and
|
|
cs.canValid and not cs.canTimeout and not cs.steerFaultTemporary and not cs.steerFaultPermanent and
|
|
any(e.type == car.CarState.ButtonEvent.Type.cancel for e in cs.buttonEvents) and
|
|
native is not None and native.requestedLateral and native.lateralAllowed and
|
|
native.pandaInventory and native_inventory_matches_cp(native, cp) and
|
|
0 < native.observedMonoTime <= now_ns <= native.validUntilMonoTime)
|
|
except (AttributeError, KeyError, IndexError, TypeError, ValueError):
|
|
return False
|
|
|
|
|
|
def seatbelt_lateral_soft_event_allowed(cp, cs, intent, native, event, *, source_ns, now_ns, axis_session_id) -> bool:
|
|
"""The qualified saved LAT owner treats only its ordinary belt event as a LONG lock."""
|
|
if not qualified_gm(cp):
|
|
return False
|
|
from opendbc.car.gm.values import is_bolt_present_no_acc_pedal_profile
|
|
from opendbc.car.structs import car
|
|
from openpilot.cereal import log
|
|
from openpilot.starpilot.aol.runtime import AXIS_MAX_AGE_NS, SAFETY_MAX_AGE_NS, native_inventory_matches_cp
|
|
try:
|
|
return bool(is_bolt_present_no_acc_pedal_profile(cp) and
|
|
aol_enabled(cp) and
|
|
event.name == log.OnroadEvent.EventName.seatbeltNotLatched and event.softDisable and
|
|
event.noEntry and not event.immediateDisable and not event.userDisable and
|
|
cs.seatbeltUnlatched and cs.canValid and not cs.canTimeout and not cs.accFaulted and not cs.doorOpen and
|
|
not cs.steerFaultTemporary and not cs.steerFaultPermanent and cs.cruiseState.available and
|
|
cs.gearShifter in (car.CarState.GearShifter.drive, car.CarState.GearShifter.low,
|
|
car.CarState.GearShifter.sport, car.CarState.GearShifter.brake) and
|
|
intent is not None and intent.settingsQualified and intent.allowedLatch and intent.lateralArmed and
|
|
intent.producerSessionId and intent.carStateLogMonoTime == source_ns and
|
|
0 < source_ns <= intent.observedMonoTime <= now_ns <= intent.validUntilMonoTime and
|
|
now_ns - source_ns <= AXIS_MAX_AGE_NS and
|
|
intent.validUntilMonoTime - intent.observedMonoTime <= SAFETY_MAX_AGE_NS and
|
|
native is not None and native.compatible and int(native.protocolVersion) == 1 and
|
|
axis_session_id and native.axisSessionId == axis_session_id and
|
|
native.requestedLateral and native.lateralAllowed and native.pandaInventory and
|
|
native_inventory_matches_cp(native, cp) and
|
|
0 < native.observedMonoTime <= now_ns <= native.validUntilMonoTime and
|
|
native.validUntilMonoTime - native.observedMonoTime <= SAFETY_MAX_AGE_NS)
|
|
except (AttributeError, KeyError, IndexError, TypeError, ValueError):
|
|
return False
|