Files
StarPilot/openpilot/selfdrive/selfdrived/selfdrived.py
T
Dom 65660a4d5c Restore selfdrived executable mode
Keep source bytes unchanged and refresh the canonical prebuilt inventory receipt.
2026-10-27 21:56:44 -05:00

2177 lines
132 KiB
Python
Executable File

#!/usr/bin/env python3
import os
import math
import time
import threading
import uuid
from copy import copy
from dataclasses import replace
import openpilot.cereal.messaging as messaging
from openpilot.cereal import log
from opendbc.car.structs import car
from opendbc.car.hyundai.ev6_aol import qualified as ev6_aol_qualified
from opendbc.car.honda.stock_aol import qualified as honda_aol_qualified, temporary_restriction as honda_temporary_restriction
from openpilot.cereal.visionipc import VisionStreamType
from msgq.visionipc import VisionIpcClient
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper, DT_CTRL
from openpilot.common.swaglog import cloudlog
from openpilot.common.gps import get_gps_location_service
from openpilot.starpilot.gps.source import observation as gps_observation
from openpilot.selfdrive.car.car_events import CarEvents
from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose
from openpilot.selfdrive.selfdrived.events import Events, ET, Alert, Priority as AlertPriority
from openpilot.starpilot.longitudinal.force_stop_alert import HoldAlertState, EVENT_TYPE as FORCE_STOP_HOLD
from openpilot.selfdrive.selfdrived.helpers import (ExcessiveActuationCheck, jetlink_in_control, jetlink_lost_alert, throttled_param,
update_big_model_status, update_jetlink_swaps)
from openpilot.starpilot.models import jetlink_adapter
from openpilot.system.manager.process_health import driving_process_failures
from openpilot.selfdrive.selfdrived.state import StateMachine
from opendbc.car.gm.aol import qualified_gm
from openpilot.starpilot.aol.panda_faults import blocking_fault_list
from openpilot.starpilot.aol.vehicle import native_bootstrap_supported
from opendbc.car.gm.steering_authority import monitored_profile
from openpilot.starpilot.car.gm.steering_companion import SteeringCompanion
from openpilot.starpilot.car.hyundai.aol import qualified_ioniq6
from opendbc.car.gm.values import GMFlags, is_volt_ascm_longitudinal
from openpilot.starpilot.aol.intent import read_settings
from openpilot.starpilot.aol.intent_companion import IntentCompanion
from openpilot.starpilot.aol.transport_pause import AvailabilityPause, TransportPause, availability_only, cp_identity, healthy_transport
from openpilot.starpilot.aol.source_availability import (
ordinary_source_availability, fresh_ordinary_lag_continuation, stock_lateral_role, stock_lateral_fault_proof,
)
from openpilot.starpilot.aol.runtime import (INTENT_MAX_AGE_NS, SAFETY_MAX_AGE_NS, AxisDecision, current_intent, current_native, decide_axes,
ordinary_lateral_requested, decide_ordinary_axis, known_reverse_pause)
from openpilot.starpilot.aol.vehicle import policy_for as axis_policy_for, ordinary_axis_request_allowed
from openpilot.starpilot.conditional_mode.consumer import ConsumerResult, ModeConsumer
from openpilot.starpilot.conditional_mode.effective_status import publish_ack
from openpilot.starpilot.conditional_mode.policy import ModeChoice
from openpilot.starpilot.feature_capabilities import lateral_supported
from openpilot.starpilot.controllers.mode_actions import SwitchbackStatusOwner, SwitchbackCooldown, ModeActionOwner
from openpilot.starpilot.conditional_mode.projection import paired_clocks_ns
from openpilot.starpilot.conditional_mode.runtime_settings import (ConditionalSettingsOwner, experimental_requested, read_mode_sources)
from openpilot.starpilot.conditional_mode.status import settings_fingerprint
from openpilot.starpilot.feature_runtime import enabled as feature_enabled
from openpilot.starpilot.nostalgia import aol_no_entry, paddle_cancel, physical_cancel, saved_enabled as nostalgia_saved_enabled
from openpilot.starpilot.lateral.low_speed_advisory import LowSpeedAdvisory
from openpilot.starpilot.lateral.lane_change_status_wire import alert_wording, decode as decode_lane_status, fresh_for_model
from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroad_alert
from openpilot.common.version import get_build_metadata
from openpilot.common.hardware import HARDWARE
REPLAY = "REPLAY" in os.environ
SIMULATION = "SIMULATION" in os.environ
TESTING_CLOSET = "TESTING_CLOSET" in os.environ
LONGITUDINAL_PERSONALITY_MAP = {v: k for k, v in log.LongitudinalPersonality.schema.enumerants.items()}
ThermalStatus = log.DeviceState.ThermalStatus
State = log.SelfdriveState.OpenpilotState
PandaType = log.PandaState.PandaType
LaneChangeState = log.LaneChangeState
LaneChangeDirection = log.LaneChangeDirection
EventName = log.OnroadEvent.EventName
ButtonType = car.CarState.ButtonEvent.Type
SafetyModel = car.CarParams.SafetyModel
AlertLevel = log.DriverMonitoringState.AlertLevel
MonitoringPolicy = log.DriverMonitoringState.MonitoringPolicy
IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
class SelfdriveD:
def __init__(self, CP=None):
self.params = Params()
# Ensure the current branch is cached, otherwise the first cycle lags
build_metadata = get_build_metadata()
if CP is None:
cloudlog.info("selfdrived is waiting for CarParams")
self.CP = messaging.log_from_bytes(self.params.get("CarParams", block=True), car.CarParams)
cloudlog.info("selfdrived got CarParams")
else:
self.CP = CP
self.aol_replay = feature_enabled(self.params, self.CP, 'aol', os.environ)
self.ordinary_axis_ack_required = axis_policy_for(self.CP).ordinary_axis_ack_required
self.aol_cruise_main_required = axis_policy_for(self.CP).cruise_main_required
self.axis_transport_required = self.aol_replay or self.ordinary_axis_ack_required
self.nostalgia_enabled = nostalgia_saved_enabled(self.params)
self.nostalgia_paddle_cancel = False
self.aol_session_id = uuid.uuid4().hex
self.aol_sequence = 0
self.aol_axis_decision = AxisDecision()
self.aol_dm_lateral_inhibit = False
self.aol_authority_lost = False
self.aol_transport_pause = TransportPause(self.CP, self.aol_session_id)
self.aol_fault_reason = 'none'
self.steering_authority_unavailable = False
self.steering_companion = (SteeringCompanion(messaging.sub_sock('starpilotCarState', conflate=False))
if monitored_profile(self.CP) else None)
self.force_stop_hold_alert = HoldAlertState()
self.aol_car_state_log_ns = 0
self.aol_last_intent = None
self.aol_intent_companion = (IntentCompanion(messaging.sub_sock('aolIntentWire', conflate=False))
if self.aol_replay else None)
self.lane_status_session = ""
self.lane_status_sequence = -1
self.conditional_replay = feature_enabled(self.params, self.CP, 'conditional', os.environ)
self.conditional_settings = ConditionalSettingsOwner(self.params) if self.conditional_replay else None
self.conditional_consumer = ModeConsumer() if self.conditional_replay else None
self.conditional_status = 'disabled'
self.controller_action_sock = messaging.sub_sock('slcAction', conflate=False)
self.controller_action_owner = ModeActionOwner()
self.switchback_capable = lateral_supported(self.CP)
self.switchback_status = SwitchbackStatusOwner()
self.switchback_cooldown = SwitchbackCooldown()
self.low_speed_advisory = LowSpeedAdvisory()
self.switchback_setting_ns = 0
self.switchback_cooldown_ns = 300_000_000_000
self.conditional_car_state_valid = False
self.conditional_ack_session = uuid.uuid4().hex
self.conditional_ack_sequence = 0
self.conditional_result = ConsumerResult(False, False, 'disabled')
self.car_events = CarEvents(self.CP)
from openpilot.starpilot.car.tesla.stock_events import StockCruiseConsumer
self.tesla_stock_consumer = StockCruiseConsumer(self.CP)
self.tesla_stock_alerts = []
self.pose_calibrator = PoseCalibrator()
self.calibrated_pose: Pose | None = None
self.excessive_actuation_check = ExcessiveActuationCheck()
self.excessive_actuation = self.params.get("Offroad_ExcessiveActuation") is not None
self.big_model_loading = False
self.big_model_active = False
self.big_model_chestnut = False
self.big_model_failed = False
self.big_model_ready_t = 0.
# Jetlink's swap events: bookkeeping alone, so built link on or off; None without a usable jetlink
self.jetlink_hooks = jetlink_adapter.hooks()
self.jetlink_swaps = self.jetlink_hooks.swap_events(DT_CTRL)
self.jetlink_settling = self.jetlink_link_lost = False
# Setup sockets
self.pm = messaging.PubMaster(['selfdriveState', 'onroadEvents'] +
(['aolAxisState'] if self.axis_transport_required else []) +
(['starpilotSelfdriveState'] if self.conditional_replay else []))
self.gps_location_service = get_gps_location_service(self.params)
self.gps_packets = [self.gps_location_service, 'starpilotCarState']
self.sensor_packets = ["accelerometer", "gyroscope"]
self.camera_packets = ["narrowRoadCameraState", "cabinCameraState", "wideRoadCameraState"]
# TODO: de-couple selfdrived with card/conflate on carState without introducing controls mismatches
self.car_state_poller = messaging.Poller() if self.aol_replay and self.axis_transport_required else None
self.car_state_sock = messaging.sub_sock('carState', poller=self.car_state_poller, timeout=20)
ignore = self.sensor_packets + self.gps_packets + ['alertDebug', 'lateralManeuverPlan', 'laneChangeAssistWire', 'starpilotLongitudinalPlan']
if self.aol_replay:
ignore += ['aolIntentWire']
if self.axis_transport_required:
ignore += ['aolSafetyWire']
if self.conditional_replay or self.switchback_capable:
# This optional proposal never controls the native process-health gate.
ignore += ['slcState']
if SIMULATION:
ignore += ['cabinCameraState', 'managerState']
if REPLAY:
# no vipc in replay will make them ignored anyways
ignore += ['narrowRoadCameraState', 'wideRoadCameraState']
from openpilot.starpilot.car.gm.radar_recovery import capability as radar_recovery_capability
radar_notifications = ['radarTracks'] if radar_recovery_capability(self.CP) is not None else []
ignore += radar_notifications
ignore = set(ignore)
self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'extrinsicsCalibration',
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'deviceMotion', 'lateralDelay',
'managerState', 'vehicleParameters', 'radarState', 'lateralTorqueParameters',
'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark',
'lateralManeuverPlan', 'laneChangeAssistWire', 'starpilotLongitudinalPlan'] +
(['aolIntentWire'] if self.aol_replay else []) +
(['aolSafetyWire'] if self.axis_transport_required else []) +
(['slcState'] if self.conditional_replay or self.switchback_capable else []) + \
self.camera_packets + self.sensor_packets + self.gps_packets + radar_notifications,
ignore_alive=ignore, ignore_avg_freq=ignore,
ignore_valid=ignore, frequency=int(1/DT_CTRL))
# read params
self.is_metric = self.params.get_bool("IsMetric")
self.aol_settings = read_settings(self.params) if self.aol_replay else None
self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled")
self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
self.radar_recovery_enabled = self.params.get_bool("RadarRecoveryAlert")
car_recognized = self.CP.brand != 'mock'
# Capability changes affect runtime authority, never the saved Alpha Long choice.
if not self.CP.openpilotLongitudinalControl:
self.params.remove("ExperimentalMode")
self.CS_prev = car.CarState.new_message()
self.AM = AlertManager()
from openpilot.starpilot.car.events import event_rules
self.events = Events(event_rules(self.CP))
self.initialized = False
self.enabled = False
self.active = False
self.mismatch_counter = 0
self.cruise_mismatch_counter = 0
self.last_steering_pressed_frame = 0
self.distance_traveled = 0
self.last_functional_fan_frame = 0
self.events_prev = []
self.logged_comm_issue = None
self.not_running_prev = None
self.experimental_mode = False
self.requested_experimental_mode = False
self.personality = self.params.get("LongitudinalPersonality", return_default=True)
self.recalibrating_seen = False
self.dm_lockout_set = False
self.dm_uncertain_alerted = False
self.state_machine = StateMachine()
self.rk = Ratekeeper(100, print_delay_threshold=None)
# Observational only. Do not cache this qualification for authority decisions.
# Determine startup event
self.startup_event = EventName.startup if build_metadata.openpilot.comma_remote and build_metadata.tested_channel else EventName.startupMaster
if HARDWARE.get_device_type() == 'mici':
self.startup_event = None
if not car_recognized:
self.startup_event = EventName.startupNoCar
elif car_recognized and self.CP.passive:
self.startup_event = EventName.startupNoControl
elif self.CP.secOcRequired and not self.CP.secOcKeyAvailable:
self.startup_event = EventName.startupNoSecOcKey
if not car_recognized:
self.events.add(EventName.carUnrecognized, static=True)
set_offroad_alert("Offroad_CarUnrecognized", True)
elif self.CP.passive:
self.events.add(EventName.dashcamMode, static=True)
def update_process_health(self) -> bool:
not_running = driving_process_failures(self.sm['managerState'].processes)
if self.sm.recv_frame['managerState'] and not_running:
if not_running != self.not_running_prev:
cloudlog.event("process_not_running", not_running=not_running, error=True)
self.not_running_prev = not_running
self.events.add(EventName.processNotRunning)
return True
return False
def update_big_model_status(self) -> bool:
return update_big_model_status(self)
def _update_steering_authority(self, CS):
if not monitored_profile(self.CP):
self.steering_authority_unavailable = False
return
companion = getattr(self, 'steering_companion', None)
if companion is None:
companion = self.steering_companion = SteeringCompanion()
# Receiving may cross a publication boundary. Validate every original source
# against the clock after the bounded drain, without renewing any receipt.
drained = companion.drain()
now_ns = self.aol_car_state_log_ns if REPLAY and self.aol_car_state_log_ns else time.monotonic_ns()
source_ns = self.aol_car_state_log_ns
source_current = bool(getattr(self, 'conditional_car_state_valid', False) and CS.canValid and not CS.canTimeout and
0 < source_ns <= now_ns and now_ns - source_ns <= INTENT_MAX_AGE_NS)
from openpilot.starpilot.car.gm.aol import steering_raw_projection_owner, steering_source_gap_intent_only
pair = companion.current(self.sm, source_ns=source_ns, now_ns=now_ns, drain=False,
allow_raw_projection=steering_raw_projection_owner(self.CP)) if drained else None
if not source_current:
pair = None
previous_axes = self.aol_axis_decision
expected_active = self.active or previous_axes.lateral_active or previous_axes.longitudinal_active
if pair is not None:
self.steering_authority_unavailable = pair
elif expected_active and not (drained and steering_source_gap_intent_only(self, CS, companion, now_ns)):
self.steering_authority_unavailable = True
if getattr(self, 'steering_authority_unavailable', False):
self.events.add(EventName.controlsMismatch)
def _update_car_event_sample(self, CS, previous):
# Block resume if cruise never previously enabled
resume_pressed = any(be.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for be in CS.buttonEvents)
if not self.CP.pcmCruise and CS.vCruise > 250 and resume_pressed:
self.events.add(EventName.resumeBlocked)
# Add car events, ignore if CAN isn't valid
if CS.canValid:
car_events = self.car_events.update(CS, previous, self.sm['carControl']).to_msg()
device = self.sm['deviceState']
drive_id = (int(device.startedMonoTime) if self.sm.seen['deviceState'] and self.sm.alive['deviceState'] and
self.sm.valid['deviceState'] and device.started else 0)
show_low_speed = self.low_speed_advisory.update(float(CS.vEgo), float(self.CP.minSteerSpeed), drive_id=drive_id)
self.events.add_from_msg([event for event in car_events if event.name != EventName.belowSteerSpeed])
if show_low_speed:
self.events.add(EventName.belowSteerSpeed)
paddle_pressed = paddle_cancel(self.CP, CS, enabled=self.enabled, saved=self.nostalgia_enabled)
self.nostalgia_paddle_cancel |= bool(paddle_pressed and EventName.buttonCancel not in self.events.names and
not physical_cancel(CS, self.CP))
if paddle_pressed:
self.events.add(EventName.buttonCancel)
if self.CP.notCar:
# wait for everything to init first
if self.sm.frame > int(2. / DT_CTRL) and self.initialized:
# body always wants to enable
self.events.add(EventName.pcmEnable)
# Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0
if (CS.gasPressed and not previous.gasPressed and self.disengage_on_accelerator) or \
(CS.brakePressed and (not previous.brakePressed or not CS.standstill)) or \
(CS.regenBraking and (not previous.regenBraking or not CS.standstill)):
self.events.add(EventName.pedalPressed)
def update_events(self, CS):
"""Compute onroadEvents from carState"""
self.events.clear()
self.car_event_sample_count = 0
self.aol_reconciled_sources = ()
self.tesla_stock_alerts = []
self.nostalgia_paddle_cancel = False
action_owner = getattr(self, 'controller_action_owner', None)
action_sock = getattr(self, 'controller_action_sock', None)
if action_owner is not None and action_sock is not None:
for _ in range(8):
request = messaging.recv_one_or_none(action_sock)
if request is None:
break
if (str(request.slcAction.kind) == 'disengageRequest' and self.enabled and
action_owner.update(request, self.sm, self.CP, now_ns=time.monotonic_ns())):
self.events.add(EventName.buttonCancel)
if self.sm['controlsState'].lateralControlState.which() == 'debugState':
self.events.add(EventName.joystickDebug)
self.startup_event = None
loading = throttled_param(self, "ChestnutLoading", self.params.get_bool)
if self.big_model_loading and not loading:
self.big_model_ready_t = time.monotonic()
self.big_model_loading = loading
if self.big_model_loading:
self.events.add(EventName.bigModelLoading)
if self.jetlink_swaps is not None:
update_jetlink_swaps(self, jetlink_in_control(self))
big_failed = self.update_big_model_status()
if self.sm.recv_frame['lateralManeuverPlan'] > 0:
self.events.add(EventName.lateralManeuver)
self.startup_event = None
elif self.sm.recv_frame['alertDebug'] > 0:
self.events.add(EventName.longitudinalManeuver)
self.startup_event = None
# Add startup event
if self.startup_event is not None:
self.events.add(self.startup_event)
self.startup_event = None
# Don't add any more events if not initialized
if not self.initialized:
self._update_steering_authority(CS)
self.events.add(EventName.selfdriveInitializing)
return
# Check for user bookmark press
if self.sm.updated['userBookmark']:
prime_type = self.params.get("PrimeType")
paired = prime_type is not None and int(prime_type) >= 0
self.events.add(EventName.userBookmark if paired else EventName.userBookmarkNotPaired)
# Don't add any more events while in dashcam mode
if self.CP.passive:
self._update_steering_authority(CS)
return
# Handle DM
if not self.CP.notCar:
# Block engaging until lockout times out or ignition reset
if self.sm['driverMonitoringState'].lockout and not self.dm_lockout_set:
self.params.put_bool("DriverTooDistracted", True)
self.dm_lockout_set = True
elif not self.sm['driverMonitoringState'].lockout and self.dm_lockout_set:
self.params.remove("DriverTooDistracted")
self.dm_lockout_set = False
# No entry conditions
if self.sm['driverMonitoringState'].lockout or self.sm['driverMonitoringState'].alwaysOnLockout:
self.events.add(EventName.tooDistracted)
# Alerts
vision_dm = self.sm['driverMonitoringState'].activePolicy == MonitoringPolicy.vision
if self.sm['driverMonitoringState'].alertLevel == AlertLevel.one:
self.events.add(EventName.driverDistracted1 if vision_dm else EventName.driverUnresponsive1)
elif self.sm['driverMonitoringState'].alertLevel == AlertLevel.two:
self.events.add(EventName.driverDistracted2 if vision_dm else EventName.driverUnresponsive2)
elif self.sm['driverMonitoringState'].alertLevel == AlertLevel.three:
self.events.add(EventName.driverDistracted3 if vision_dm else EventName.driverUnresponsive3)
# Warn consistent DM uncertainty
if self.sm['driverMonitoringState'].visionPolicyState.uncertainOffroadAlertPercent >= 100 and not self.dm_uncertain_alerted:
set_offroad_alert("Offroad_DriverMonitoringUncertain", True)
self.dm_uncertain_alerted = True
# Create events for temperature, disk space, and memory
if self.sm['deviceState'].thermalStatus >= ThermalStatus.overheated:
self.events.add(EventName.overheat)
if self.sm['deviceState'].freeSpacePercent < 7 and not SIMULATION:
self.events.add(EventName.outOfSpace)
if self.sm['deviceState'].memoryUsagePercent > 90 and not SIMULATION:
self.events.add(EventName.lowMemory)
# Alert if fan isn't spinning for 5 seconds
if self.sm['peripheralState'].pandaType != log.PandaState.PandaType.unknown:
if self.sm['peripheralState'].fanSpeedRpm < 500 and self.sm['deviceState'].fanSpeedPercentDesired > 50:
# allow enough time for the fan controller in the panda to recover from stalls
if (self.sm.frame - self.last_functional_fan_frame) * DT_CTRL > 15.0:
self.events.add(EventName.fanMalfunction)
else:
self.last_functional_fan_frame = self.sm.frame
# Handle calibration status
cal_status = self.sm['extrinsicsCalibration'].calStatus
if cal_status != log.ExtrinsicsCalibration.Status.calibrated:
if cal_status == log.ExtrinsicsCalibration.Status.uncalibrated:
self.events.add(EventName.calibrationIncomplete)
elif cal_status == log.ExtrinsicsCalibration.Status.recalibrating:
if not self.recalibrating_seen:
set_offroad_alert("Offroad_Recalibration", True)
self.recalibrating_seen = True
self.events.add(EventName.calibrationRecalibrating)
else:
self.events.add(EventName.calibrationInvalid)
# Lane departure warning
if self.is_ldw_enabled and self.sm.valid['driverAssistance']:
if self.sm['driverAssistance'].leftLaneDeparture or self.sm['driverAssistance'].rightLaneDeparture:
self.events.add(EventName.ldw)
# ******************************************************************************************
# NOTE: To fork maintainers.
# Disabling or nerfing safety features will get you and your users banned from our servers.
# We recommend that you do not change these numbers from the defaults.
if self.sm.updated['extrinsicsCalibration']:
self.pose_calibrator.feed_extrinsics_calibration(self.sm['extrinsicsCalibration'])
if self.sm.updated['deviceMotion']:
device_motion = Pose.from_device_motion(self.sm['deviceMotion'])
self.calibrated_pose = self.pose_calibrator.build_calibrated_pose(device_motion)
for i, pandaState in enumerate(self.sm['pandaStates']):
# All pandas must match the list of safetyConfigs, and if outside this list, must be silent or noOutput
if i < len(self.CP.safetyConfigs):
safety_mismatch = pandaState.safetyModel != self.CP.safetyConfigs[i].safetyModel or \
pandaState.safetyParam != self.CP.safetyConfigs[i].safetyParam or \
pandaState.alternativeExperience != self.CP.alternativeExperience
else:
safety_mismatch = pandaState.safetyModel not in IGNORED_SAFETY_MODES
# safety mismatch allows some time for pandad to set the safety mode and publish it back from panda
if ((safety_mismatch and self.sm.frame*DT_CTRL > 10.) or pandaState.safetyRxChecksInvalid or self.mismatch_counter >= 200 or
(pandaState.faults and blocking_fault_list(pandaState.faults, self.CP))):
self.events.add(EventName.controlsMismatch)
if log.PandaState.FaultType.relayMalfunction in pandaState.faults:
self.events.add(EventName.relayMalfunction)
# Handle HW and system malfunctions
# Order is very intentional here. Be careful when modifying this.
# All events here should at least have NO_ENTRY and SOFT_DISABLE.
num_events = len(self.events)
if self.big_model_active and big_failed:
self.events.add(EventName.bigModelFailed)
if not self.update_process_health():
if not SIMULATION and not self.rk.lagging:
if not self.sm.all_alive(self.camera_packets):
self.events.add(EventName.cameraMalfunction)
elif not self.sm.all_freq_ok(self.camera_packets):
self.events.add(EventName.cameraFrameRate)
if not REPLAY and self.rk.lagging:
self.events.add(EventName.selfdrivedLagging)
if self.CP.openpilotLongitudinalControl:
if self.sm['radarState'].radarErrors.canError:
self.events.add(EventName.canError)
elif self.sm['radarState'].radarErrors.radarUnavailableTemporary:
self.events.add(EventName.radarTempUnavailable)
elif any(self.sm['radarState'].radarErrors.to_dict().values()):
self.events.add(EventName.radarFault)
hardware_events_added = len(self.events) - num_events
from openpilot.starpilot.car.hyundai.aol import source_deadline_supported
input_proof = getattr(self, 'aol_step_input_proof', None)
reconcile_input = bool(input_proof is not None and not REPLAY and self.aol_replay and self.axis_transport_required and
source_deadline_supported(self.CP) and
input_proof[0] == self.aol_session_id and
(input_proof[8] or input_proof[9]))
self.aol_event_input_reconcile = reconcile_input
CS = self.refresh_event_car_state(CS)
if not reconcile_input:
# Other vehicle owners retain their original ordering. In particular,
# GM's exact steering companion must not lose an earlier source's fault
# when a newer carState arrives during car-event processing.
self._update_steering_authority(CS)
previous = self.CS_prev
retained_source = (getattr(self, 'car_state_retained_source_ns', 0)
if not getattr(self, 'car_state_samples', ()) else 0)
consumed = 0
envelope_errors = []
while True:
received = getattr(self, 'car_state_samples', ())
samples = received[consumed:] if received else ((CS, getattr(self, 'conditional_car_state_valid', False)),)
for sample, envelope_valid in samples:
self._update_car_event_sample(sample, previous)
self.car_event_sample_count += 1
if sample.canTimeout:
envelope_errors.append(EventName.canBusMissing)
elif (received and not envelope_valid) or not sample.canValid:
envelope_errors.append(EventName.canError)
previous = sample
consumed = len(received)
if not reconcile_input:
break
# Input can arrive while car-event owners run. Reconcile it before the
# selected-source owners and authority/state-machine decisions. The drain
# retains one total 32-envelope bound; only a blocking wait depends on
# the original output deadline. A fresh pair may renew accepted engagement
# after that deadline. The cursor processes each physical sample once.
# Interleave the two raw inputs before selected-source owners. Intent
# can arrive just before its carState; never select that state without
# processing every newly consumed physical sample through this cursor.
# The cursor only repeats for new carStates (32 total); all of these
# intent drains share one 32-message step budget, including earlier waits.
companion = getattr(self, 'aol_intent_companion', None)
remaining = max(0, 32 - self.aol_step_intent_drain_count)
drained = companion is not None and self.drain_axis_intent(companion, max_messages=remaining)
self.aol_event_intent_drained = bool(drained and self.aol_step_intent_drain_ok and self.aol_event_intent_drained is not False)
CS = self.refresh_event_car_state(CS, allow_wait=not self.aol_event_input_wait_attempted)
if len(getattr(self, 'car_state_samples', ())) <= consumed:
break
self.aol_reconciled_sources = ((retained_source,) if retained_source else ()) + tuple(
int(sample.logMonoTime) for sample in self.car_state_envelopes)
samples = getattr(self, 'car_state_samples', ()) or ((CS, getattr(self, 'conditional_car_state_valid', False)),)
if self.car_event_sample_count > 1:
for event in envelope_errors:
self.events.add(event)
self.event_car_state = CS
if reconcile_input:
self._update_steering_authority(CS)
if any(physical_cancel(sample, self.CP) for sample, _ in samples):
self.nostalgia_paddle_cancel = False
if CS.canValid:
if self.tesla_stock_consumer.binding is not None:
intent_companion = getattr(self, 'aol_intent_companion', None)
drained = intent_companion is None or self.drain_axis_intent(intent_companion)
now_ns = self.aol_car_state_log_ns if REPLAY and self.aol_car_state_log_ns else time.monotonic_ns()
companion = current_intent(self.sm, car_state_ns=self.aol_car_state_log_ns, now_ns=now_ns,
previous=self.aol_last_intent, companion=intent_companion, drain=False) if drained else None
session = companion.producerSessionId if companion is not None else None
self.tesla_stock_alerts = self.tesla_stock_consumer.poll(
self.sm, now_ns=now_ns, session=session, car_state=CS,
source_ns=self.aol_car_state_log_ns, source_valid=self.conditional_car_state_valid)
if self.calibrated_pose is not None and not self.CP.notCar:
excessive_actuation = self.excessive_actuation_check.update(self.sm, CS, self.calibrated_pose)
if not self.excessive_actuation and excessive_actuation is not None:
set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text=str(excessive_actuation))
self.excessive_actuation = True
if self.excessive_actuation:
self.events.add(EventName.excessiveActuation)
# ******************************************************************************************
# Handle lane change
if self.sm['modelV2'].meta.laneChangeState == LaneChangeState.preLaneChange:
direction = self.sm['modelV2'].meta.laneChangeDirection
if (CS.leftBlindspot and direction == LaneChangeDirection.left) or \
(CS.rightBlindspot and direction == LaneChangeDirection.right):
self.events.add(EventName.laneChangeBlocked)
else:
if direction == LaneChangeDirection.left:
self.events.add(EventName.preLaneChangeLeft)
else:
self.events.add(EventName.preLaneChangeRight)
elif self.sm['modelV2'].meta.laneChangeState in (LaneChangeState.laneChangeStarting,
LaneChangeState.laneChangeFinishing):
self.events.add(EventName.laneChange)
num_can_events = len(self.events)
if CS.canTimeout:
self.events.add(EventName.canBusMissing)
elif not CS.canValid:
self.events.add(EventName.canError)
# generic catch-all. ideally, a more specific event should be added above instead
has_disable_events = self.events.contains(ET.NO_ENTRY) and (self.events.contains(ET.SOFT_DISABLE) or self.events.contains(ET.IMMEDIATE_DISABLE))
no_system_errors = (not has_disable_events) or (hardware_events_added == 0 and len(self.events) == num_can_events)
warmup_sec = 5.
big_model_settling = self.big_model_loading or time.monotonic() < self.big_model_ready_t + warmup_sec
if not self.sm.all_checks() and no_system_errors and not big_model_settling: # the load holds modelV2 and friends back on purpose
if not self.sm.all_alive():
self.events.add(EventName.commIssue)
elif not self.sm.all_freq_ok():
self.events.add(EventName.commIssueAvgFreq)
else:
self.events.add(EventName.commIssue)
logs = {
'invalid': [s for s, valid in self.sm.valid.items() if not valid],
'not_alive': [s for s, alive in self.sm.alive.items() if not alive],
'not_freq_ok': [s for s, freq_ok in self.sm.freq_ok.items() if not freq_ok],
}
if logs != self.logged_comm_issue:
cloudlog.event("commIssue", error=True, **logs)
self.logged_comm_issue = logs
else:
self.logged_comm_issue = None
if not self.CP.notCar and not big_model_settling: # localization has nothing to work with during the load
# the defaults of a message that was never received are not a localizer failure
if self.sm.seen['deviceMotion'] and not self.sm['deviceMotion'].posenetOK:
self.events.add(EventName.posenetInvalid)
if self.sm.seen['deviceMotion'] and not self.sm['deviceMotion'].inputsOK:
self.events.add(EventName.locationdTemporaryError)
if (self.sm.seen['vehicleParameters'] and not self.sm['vehicleParameters'].valid and cal_status == log.ExtrinsicsCalibration.Status.calibrated and
not TESTING_CLOSET and (not SIMULATION or REPLAY)):
self.events.add(EventName.paramsdTemporaryError)
# conservative HW alert. if the data or frequency are off, locationd will throw an error
if any((self.sm.frame - self.sm.recv_frame[s])*DT_CTRL > 10. for s in self.sensor_packets):
self.events.add(EventName.sensorDataInvalid)
if not REPLAY:
# Check for mismatch between openpilot and car's PCM
cruise_mismatch = CS.cruiseState.enabled and (not self.enabled or not self.CP.pcmCruise)
self.cruise_mismatch_counter = self.cruise_mismatch_counter + 1 if cruise_mismatch else 0
if self.cruise_mismatch_counter > int(6. / DT_CTRL):
self.events.add(EventName.cruiseMismatch)
# Send a "steering required alert" if saturation count has reached the limit
if CS.steeringPressed:
self.last_steering_pressed_frame = self.sm.frame
recent_steer_pressed = (self.sm.frame - self.last_steering_pressed_frame)*DT_CTRL < 2.0
controlstate = self.sm['controlsState']
lac = getattr(controlstate.lateralControlState, controlstate.lateralControlState.which())
if lac.active and not recent_steer_pressed and not self.CP.notCar:
clipped_speed = max(CS.vEgo, 0.3)
actual_lateral_accel = controlstate.curvature * (clipped_speed**2)
desired_lateral_accel = self.sm['modelV2'].action.desiredCurvature * (clipped_speed**2)
undershooting = abs(desired_lateral_accel) / abs(1e-3 + actual_lateral_accel) > 1.2
turning = abs(desired_lateral_accel) > 1.0
# TODO: lac.saturated includes speed and other checks, should be pulled out
if undershooting and turning and lac.saturated:
self.events.add(EventName.steerSaturated)
# Check for FCW
stock_long_is_braking = self.enabled and not self.CP.openpilotLongitudinalControl and CS.aEgo < -1.25
model_fcw = self.sm['modelV2'].meta.hardBrakePredicted and not CS.brakePressed and not stock_long_is_braking
planner_fcw = self.sm['longitudinalPlan'].fcw and self.enabled
if (planner_fcw or model_fcw) and not self.CP.notCar:
self.events.add(EventName.fcw)
# GPS checks
gps_ok = self.sm.recv_frame[self.gps_location_service] > 0 and (self.sm.frame - self.sm.recv_frame[self.gps_location_service]) * DT_CTRL < 2.0
gps_ok = gps_ok or gps_observation(self.sm, 'starpilotCarState', time.monotonic_ns()) is not None
if not gps_ok and self.sm['deviceMotion'].inputsOK and (self.distance_traveled > 1500):
self.events.add(EventName.noGps)
if gps_ok:
self.distance_traveled = 0
self.distance_traveled += abs(CS.vEgo) * DT_CTRL
# TODO: fix simulator
if not SIMULATION or REPLAY:
if self.sm['modelV2'].frameDropPerc > 1 and not big_model_settling:
self.events.add(EventName.modeldLagging)
# Decrement personality on distance button press
if self.CP.openpilotLongitudinalControl:
for sample, _ in samples:
if any(not be.pressed and be.type == ButtonType.gapAdjustCruise for be in sample.buttonEvents):
self.personality = (self.personality - 1) % 3
self.params.put('LongitudinalPersonality', self.personality)
self.events.add(EventName.personalityChanged)
def data_sample(self):
self.car_state_retained_source_ns = self.aol_car_state_log_ns
self.car_state_retained_valid = getattr(self, 'conditional_car_state_valid', False)
_car_state = messaging.recv_one(self.car_state_sock)
samples = []
queue_invalid = False
if _car_state is not None:
samples.append(_car_state)
if (getattr(self, 'aol_replay', False) and getattr(self, 'axis_transport_required', False) and
not REPLAY):
# Preserve every consumed transition; only the authority source advances.
for _ in range(31):
following = messaging.recv_one_or_none(self.car_state_sock)
if following is None:
break
queue_invalid |= int(following.logMonoTime) <= int(samples[-1].logMonoTime)
samples.append(following)
else:
# A full bounded batch cannot establish that the receiver caught up.
queue_invalid = True
_car_state = samples[-1]
self.sm.update(0)
if (getattr(self, 'aol_replay', False) and getattr(self, 'axis_transport_required', False) and
not REPLAY):
# A carState may arrive during SubMaster work after the blocking wait/drain.
# Catch up without a second wait, preserving every transition and the total bound.
for _ in range(32 - len(samples)):
following = messaging.recv_one_or_none(self.car_state_sock)
if following is None:
break
previous_stamp = int(samples[-1].logMonoTime) if samples else self.aol_car_state_log_ns
queue_invalid |= int(following.logMonoTime) <= previous_stamp or not following.valid
samples.append(following)
else:
queue_invalid = True
if samples:
_car_state = samples[-1]
queue_invalid |= any(not sample.valid for sample in samples)
self.car_state_envelopes = tuple(samples)
self.car_state_queue_invalid = queue_invalid
if samples:
self.car_state_last_received_ns = max(int(sample.logMonoTime) for sample in samples)
self.car_state_samples = tuple((sample.carState, bool(sample.valid)) for sample in samples)
CS = _car_state.carState if _car_state else self.CS_prev
self.aol_expired_healthy_source = False
if _car_state is not None:
self.aol_car_state_log_ns = int(_car_state.logMonoTime)
self.conditional_car_state_valid = bool(_car_state.valid) and not queue_invalid
if queue_invalid:
self.aol_car_state_log_ns = 0
else:
# The 20 ms socket wait can expire between healthy CAN frames. Reusing
# CS_prev must also keep its original timestamp, never create a new one.
age_ns = time.monotonic_ns() - self.aol_car_state_log_ns
retained = (self.conditional_car_state_valid and self.aol_car_state_log_ns > 0 and
0 <= age_ns <= INTENT_MAX_AGE_NS and CS.canValid and not CS.canTimeout)
self.aol_expired_healthy_source = bool(self.conditional_car_state_valid and
self.aol_car_state_log_ns > 0 and age_ns > INTENT_MAX_AGE_NS and CS.canValid and not CS.canTimeout)
if not retained:
self.aol_car_state_log_ns = 0
self.conditional_car_state_valid = False
if not self.initialized:
all_valid = CS.canValid and self.sm.all_checks()
if self.axis_transport_required and native_bootstrap_supported(self.CP):
now_ns = self.aol_car_state_log_ns if REPLAY and self.aol_car_state_log_ns else time.monotonic_ns()
all_valid = all_valid and current_native(self.sm, self.CP, now_ns=now_ns,
axis_session_id=self.aol_session_id) is not None
timed_out = self.sm.frame * DT_CTRL > 6.
if all_valid or timed_out or (SIMULATION and not REPLAY):
available_streams = VisionIpcClient.available_streams("camerad", block=False)
if VisionStreamType.VISION_STREAM_NARROW_ROAD not in available_streams:
self.sm.ignore_alive.add('narrowRoadCameraState')
self.sm.ignore_valid.add('narrowRoadCameraState')
if VisionStreamType.VISION_STREAM_WIDE_ROAD not in available_streams:
self.sm.ignore_alive.add('wideRoadCameraState')
self.sm.ignore_valid.add('wideRoadCameraState')
if REPLAY and any(ps.controlsAllowed for ps in self.sm['pandaStates']):
self.state_machine.state = State.enabled
self.initialized = True
cloudlog.event(
"selfdrived.initialized",
dt=self.sm.frame*DT_CTRL,
timeout=timed_out,
canValid=CS.canValid,
invalid=[s for s, valid in self.sm.valid.items() if not valid],
not_alive=[s for s, alive in self.sm.alive.items() if not alive],
not_freq_ok=[s for s, freq_ok in self.sm.freq_ok.items() if not freq_ok],
error=True,
)
# When the panda and selfdrived do not agree on controls_allowed
# we want to disengage openpilot. However the status from the panda goes through
# another socket other than the CAN messages and one can arrive earlier than the other.
# Therefore we allow a mismatch for two samples, then we trigger the disengagement.
if not self.enabled:
self.mismatch_counter = 0
# All pandas not in silent mode must have controlsAllowed when openpilot is enabled
if self.enabled and any(not ps.controlsAllowed for ps in self.sm['pandaStates']
if ps.safetyModel not in IGNORED_SAFETY_MODES):
self.mismatch_counter += 1
return CS
def ford_source_deadline_supported(self):
from opendbc.car.ford.aol import qualified
from opendbc.car.ford.values import CAR
return (qualified(self.CP, marked_only=True) and
self.CP.carFingerprint == CAR.FORD_MUSTANG_MACH_E_MK1 and
len(self.CP.safetyConfigs) == 1 and self.CP.safetyConfigs[0].safetyParam == 19)
def source_deadline_deferral_supported(self):
from openpilot.starpilot.car.hyundai.aol import source_deadline_supported
from openpilot.starpilot.car.toyota.aol import qualified as qualified_toyota
return (qualified_ioniq6(self.CP) or qualified_gm(self.CP) or source_deadline_supported(self.CP) or
qualified_toyota(self.CP, marked_only=True) or self.ford_source_deadline_supported())
def held_axis_override_wait(self, CS, proof, now_ns, *, fresh_successor=False):
"""Recognize an already applied override; never admit a new physical edge."""
if (proof is None or not (qualified_ioniq6(self.CP) or qualified_gm(self.CP) or
(self.CP.brand == 'hyundai' and self.source_deadline_deferral_supported()) or
self.ford_source_deadline_supported()) or not self.sm.seen['carControl'] or
not self.sm.valid['carControl'] or self.sm['carControl'].longActive or
not 0 < int(self.sm.logMonoTime['carControl']) <= now_ns or
not fresh_successor and now_ns - int(self.sm.logMonoTime['carControl']) > INTENT_MAX_AGE_NS):
return False, False
previous = self.CS_prev
# A missed socket read may retain only the original healthy receipt. This
# recognizes an already-held override; the caller still checks its expiry.
retained_valid = bool((self.CP.brand == 'hyundai' or self.ford_source_deadline_supported()) and
getattr(self, 'aol_expired_healthy_source', False) and not getattr(self, 'car_state_queue_invalid', False) and
getattr(self, 'car_state_retained_valid', False) and getattr(self, 'car_state_retained_source_ns', 0) == proof[1])
samples = getattr(self, 'car_state_samples', ()) or ((CS, self.conditional_car_state_valid or retained_valid),)
held_brake = bool(proof[8] and not proof[9] and not self.enabled and not self.active and
(previous.brakePressed or previous.regenBraking) and
all(valid and sample.brakePressed == previous.brakePressed and sample.regenBraking == previous.regenBraking
and not sample.gasPressed and not sample.buttonEvents for sample, valid in samples))
held_gas = bool(proof[9] and self.enabled and self.active and previous.gasPressed and
self.events.contains(ET.OVERRIDE_LONGITUDINAL) and
all(valid and sample.gasPressed and not sample.brakePressed and not sample.regenBraking
and not sample.buttonEvents for sample, valid in samples))
return held_brake, held_gas
def drain_axis_intent(self, companion, *, max_messages=None):
if getattr(self, 'aol_event_input_reconcile', False):
remaining = max(0, 32 - getattr(self, 'aol_step_intent_drain_count', 0))
max_messages = remaining if max_messages is None else min(max_messages, remaining)
drained = companion.drain() if max_messages is None else companion.drain(max_messages=max_messages)
# Empty later drains must not erase earlier restrictive burst evidence.
self.aol_step_intent_drain_count = (getattr(self, 'aol_step_intent_drain_count', 0) +
getattr(companion, 'last_drain_count', 0))
# A later empty/fresh drain cannot erase a failed earlier burst. Only the
# qualified interleaved reconciliation consumes this cumulative verdict;
# legacy callers still receive this call's original result.
self.aol_step_intent_drain_ok = bool(getattr(self, 'aol_step_intent_drain_ok', True) and drained)
return drained
def event_input_wait_ns(self, CS, now_ns, *, final_held_override=False):
"""Bound an input wait by an original published lease; grant no new output."""
proof = getattr(self, 'aol_published_input_proof', None)
companion = getattr(self, 'aol_intent_companion', None)
intent = getattr(self, 'aol_last_intent', None)
previous_native = getattr(self, 'aol_published_input_native', None)
held_brake, held_gas = self.held_axis_override_wait(CS, proof, now_ns) if final_held_override else (False, False)
from opendbc.car.hyundai.canfd_stock_aol import qualified as qualified_stock_hkg
from opendbc.car.hyundai.canfd_angle_aol import qualified as qualified_angle_aol
stock_cruise_off = bool(proof is not None and proof[8] and not proof[9] and
not self.enabled and not self.active and not CS.cruiseState.enabled and
(qualified_stock_hkg(self.CP, marked_only=True) or
(self.ford_source_deadline_supported() and held_brake) or
qualified_angle_aol(self.CP, marked_only=True) and self.CP.pcmCruise and not self.CP.openpilotLongitudinalControl) and
EventName.pcmDisable in self.events_prev and self.events.names == self.events_prev)
user_disable = any(event.userDisable and
not (held_brake and event.name == EventName.pedalPressed) and
not (stock_cruise_off and event.name == EventName.pcmDisable) for event in self.events.to_msg())
if (proof is None or companion is None or intent is None or previous_native is None or
self.aol_authority_lost or
user_disable or any(self.events.contains(kind) for kind in (ET.SOFT_DISABLE, ET.IMMEDIATE_DISABLE)) or
not self.sm.all_checks() or self.rk.lagging or
self.sm['extrinsicsCalibration'].calStatus != log.ExtrinsicsCalibration.Status.calibrated or
self.sm['deviceState'].thermalStatus >= ThermalStatus.overheated or
self.sm['deviceState'].freeSpacePercent < 7 or self.sm['deviceState'].memoryUsagePercent > 90 or
self.sm['deviceMotion'].inputsOK is False or self.sm['deviceMotion'].posenetOK is False or
self.sm.seen['vehicleParameters'] and not self.sm['vehicleParameters'].valid or
any(self.sm['radarState'].radarErrors.to_dict().values()) or
driving_process_failures(self.sm['managerState'].processes)):
return 0
session, source, published, deadline, producer, epoch, lateral, longitudinal, active_lateral, active_longitudinal = proof
ordinary = bool(active_longitudinal and self.enabled and self.active)
lateral_only = bool(active_lateral and not active_longitudinal and intent.allowedLatch and intent.lateralArmed)
if not (ordinary or lateral_only):
return 0
if (session != self.aol_session_id or not 0 < source <= published <= now_ns < deadline or
published - source > INTENT_MAX_AGE_NS or deadline != published + INTENT_MAX_AGE_NS or
intent.producerSessionId != producer or intent.carStateLogMonoTime != source or companion.integrity_epoch != epoch):
return 0
wait_origin_ns = now_ns
wait_deadline_ns = min(deadline, wait_origin_ns + int(DT_CTRL * 1e9))
if not self.drain_axis_intent(companion) or companion.integrity_epoch != epoch:
return 0
# Transport may deliver intent published after the caller's clock sample.
# Validate it against a post-drain clock without renewing the original wait.
now_ns = time.monotonic_ns()
if not wait_origin_ns <= now_ns < wait_deadline_ns:
return 0
latest = companion.latest
if final_held_override and getattr(self, 'aol_step_intent_drain_count', 0) > 1:
# A burst could contain an intermediate restriction. Do not infer an
# unchanged permission from just its final sample during this hold.
return 0
# An unchanged pause on an unrequested, inactive axis does not revoke the
# other axis's existing lease. New pauses and requested-axis pauses still veto.
if (latest is None or not latest[1] or latest[2] is None or
latest[2].producerSessionId != producer or not latest[2].settingsQualified or
latest[2].pauseLateral and (lateral or active_lateral or not intent.pauseLateral) or
latest[2].pauseLongitudinal and (longitudinal or active_longitudinal or not intent.pauseLongitudinal) or
latest[2].allowedLatch != intent.allowedLatch or latest[2].lateralArmed != intent.lateralArmed or
latest[0] != latest[2].carStateLogMonoTime or not 0 < latest[0] <= now_ns or
not 0 < latest[2].observedMonoTime <= now_ns <= latest[2].validUntilMonoTime or
not 0 < latest[2].validUntilMonoTime - latest[2].observedMonoTime <= SAFETY_MAX_AGE_NS):
return 0
samples = getattr(self, 'car_state_samples', ()) or ((CS, getattr(self, 'car_state_retained_valid', False)),)
if any(not valid or not sample.canValid or sample.canTimeout or sample.steerFaultTemporary or
sample.steerFaultPermanent or (sample.brakePressed or sample.regenBraking) and not held_brake or
active_longitudinal and sample.gasPressed and not held_gas or
sample.seatbeltUnlatched or sample.accFaulted or sample.doorOpen or physical_cancel(sample, self.CP) or
qualified_gm(self.CP) and not sample.cruiseState.available or
sample.gearShifter not in (car.CarState.GearShifter.drive, car.CarState.GearShifter.sport,
car.CarState.GearShifter.low, car.CarState.GearShifter.brake)
for sample, valid in samples):
return 0
native = current_native(self.sm, self.CP, now_ns=now_ns, axis_session_id=session)
if (native is None or not healthy_transport(self.sm, self.CP, CS, session, now_ns, native) or
native.pandaSerial != previous_native.pandaSerial or native.pandaInventory != previous_native.pandaInventory or
native.observedMonoTime < previous_native.observedMonoTime or
bool(native.requestedLateral) != lateral or bool(native.requestedLongitudinal) != longitudinal):
return 0
return wait_deadline_ns - wait_origin_ns
def refresh_event_car_state(self, CS, *, allow_wait=True):
if not getattr(self, 'event_input_refresh_pending', False) or REPLAY or not self.aol_replay or not self.axis_transport_required:
return CS
samples = list(getattr(self, 'car_state_envelopes', ()))
invalid = getattr(self, 'car_state_queue_invalid', False)
previous_stamp = int(samples[-1].logMonoTime) if samples else getattr(self, 'car_state_last_received_ns', 0)
initial_count = len(samples)
for _ in range(32 - len(samples)):
following = messaging.recv_one_or_none(self.car_state_sock)
if following is None:
break
invalid |= int(following.logMonoTime) <= previous_stamp or not following.valid
previous_stamp = max(previous_stamp, int(following.logMonoTime))
samples.append(following)
else:
invalid = True
# If a real producer gap straddles the source cutoff, only wait while the
# original published proof can independently remain valid at consumers.
now_ns = time.monotonic_ns()
selected_stamp = int(samples[-1].logMonoTime) if samples else getattr(self, 'car_state_retained_source_ns', 0)
if (allow_wait and not invalid and len(samples) == initial_count and selected_stamp > 0 and
now_ns - selected_stamp > INTENT_MAX_AGE_NS and
not (getattr(self, 'aol_event_input_reconcile', False) and getattr(self, 'aol_event_input_wait_attempted', False))):
self.aol_event_input_wait_attempted = True
wait_until_ns = now_ns + self.event_input_wait_ns(CS, now_ns)
poller = getattr(self, 'car_state_poller', None)
# Validation itself can consume the remaining lease. Never poll from its
# earlier clock; the original absolute deadline still bounds this wait.
wait_ns = max(0, wait_until_ns - time.monotonic_ns())
polled = False
if poller is not None and wait_ns >= 1_000_000:
polled = poller.poll(wait_ns // 1_000_000)
if polled:
for _ in range(32 - len(samples)):
following = messaging.recv_one_or_none(self.car_state_sock)
if following is None:
break
invalid |= int(following.logMonoTime) <= previous_stamp or not following.valid
previous_stamp = max(previous_stamp, int(following.logMonoTime))
samples.append(following)
else:
invalid = True
self.car_state_envelopes = tuple(samples)
self.car_state_queue_invalid = invalid
self.car_state_last_received_ns = previous_stamp
self.car_state_samples = tuple((sample.carState, bool(sample.valid)) for sample in samples)
if samples:
latest = samples[-1]
self.aol_car_state_log_ns = 0 if invalid else int(latest.logMonoTime)
self.conditional_car_state_valid = bool(latest.valid) and not invalid
self.aol_expired_healthy_source = False
CS = latest.carState
return CS
def defer_expired_axis_publication(self, CS, initial_state, *, now_ns=None):
"""Keep an existing unexpired proof for one normal tick; issue no new lease."""
if REPLAY or not self.aol_replay or not self.axis_transport_required or not self.source_deadline_deferral_supported():
return False
proof = getattr(self, 'aol_step_input_proof', None)
if now_ns is None:
now_ns = time.monotonic_ns()
source = self.aol_car_state_log_ns
source_valid = self.conditional_car_state_valid
from openpilot.starpilot.car.hyundai.aol import source_deadline_supported
from openpilot.starpilot.car.toyota.aol import qualified as qualified_toyota
if ((source_deadline_supported(self.CP) or qualified_toyota(self.CP, marked_only=True) or
self.ford_source_deadline_supported()) and
source == 0 and not source_valid and
getattr(self, 'aol_expired_healthy_source', False) and
not getattr(self, 'car_state_samples', ()) and not getattr(self, 'car_state_queue_invalid', False) and
getattr(self, 'car_state_retained_valid', False)):
# A socket timeout cleared the selected source. Validate only the old
# publication's receipt; keep the source invalid and its clocks unchanged.
source = getattr(self, 'car_state_retained_source_ns', 0)
source_valid = True
if (getattr(self, 'aol_step_deferral_checked', False) or proof is None or proof != getattr(self, 'aol_published_input_proof', None) or
not source_valid or not 0 < source <= now_ns or
now_ns - source <= INTENT_MAX_AGE_NS or source != proof[1] or
now_ns >= proof[3] or self.aol_authority_lost or
getattr(self, 'aol_fault_reason', 'none') != 'none' or
getattr(self, 'aol_published_fault_reason', 'none') != 'none' or
initial_state != (self.state_machine.state, self.state_machine.soft_disable_timer, self.enabled, self.active) or
(self.state_machine.soft_disable_timer != 0 and not (
self.state_machine.state == log.SelfdriveState.OpenpilotState.disabled and
not self.enabled and not self.active and getattr(self, 'aol_availability_long_inhibit', False) and
proof[6] and not proof[7] and proof[8] and not proof[9])) or self.events.names != self.events_prev or
CS.buttonEvents or any(sample.buttonEvents for sample, _ in getattr(self, 'car_state_samples', ())) or
any(self.events.contains(kind) for kind in (ET.ENABLE, ET.SOFT_DISABLE, ET.IMMEDIATE_DISABLE)) or
(self.aol_axis_decision.lateral_active, self.aol_axis_decision.longitudinal_active) != (proof[8], proof[9])):
return False
# This eligibility check uses the same native/intent/health checks as an
# existing bounded input wait. It never changes the source or publishes an
# active decision. The next ordinary loop iteration consumes the next input.
self.aol_step_deferral_checked = True
allowed = self.event_input_wait_ns(CS, now_ns, final_held_override=True) > 0
return bool(allowed and now_ns <= time.monotonic_ns() < proof[3])
def reconciled_intent(self, intent, previous, pairs, epoch, now_ns):
sources = getattr(self, 'aol_reconciled_sources', ())
if not sources or intent is None:
return intent
companion = self.aol_intent_companion
proof = getattr(self, 'aol_step_input_proof', None)
if (proof is None or proof[0] != self.aol_session_id or companion is None or
epoch != proof[5] or companion.integrity_epoch != epoch or
intent.producerSessionId != proof[4]):
return None
for source_ns in sources:
pair = pairs.get(source_ns)
if (pair is None and previous is not None and source_ns == proof[1] and
previous.carStateLogMonoTime == source_ns and previous.producerSessionId == proof[4]):
pair = (source_ns, True, previous)
if pair is None or not pair[1] or pair[2] is None:
return None
restriction = pair[2]
if (pair[0] != source_ns or restriction.carStateLogMonoTime != source_ns or
not restriction.settingsQualified or restriction.producerSessionId != intent.producerSessionId or
not 0 < source_ns <= now_ns or not 0 < restriction.observedMonoTime <= now_ns <= restriction.validUntilMonoTime or
not 0 < restriction.validUntilMonoTime - restriction.observedMonoTime <= SAFETY_MAX_AGE_NS):
return None
# Earlier exact companions only veto the normally validated fresh pair.
# Their clocks never renew a source or supply an output lease.
intent = replace(intent, allowedLatch=intent.allowedLatch and restriction.allowedLatch,
lateralArmed=intent.lateralArmed and restriction.lateralArmed,
pauseLateral=intent.pauseLateral or restriction.pauseLateral,
pauseLongitudinal=intent.pauseLongitudinal or restriction.pauseLongitudinal,
optionalSetRelease=intent.optionalSetRelease and restriction.optionalSetRelease)
return intent
def update_alerts(self, CS):
clear_event_types = set()
if ET.WARNING not in self.state_machine.current_alert_types:
clear_event_types.add(ET.WARNING)
if self.enabled:
clear_event_types.add(ET.NO_ENTRY)
pers = LONGITUDINAL_PERSONALITY_MAP[self.personality]
alerts = self.events.create_alerts(self.state_machine.current_alert_types, [self.CP, CS, self.sm, self.is_metric,
self.state_machine.soft_disable_timer, pers])
if getattr(self, 'aol_lag_takeover_alert', False) and not self.events.contains(ET.IMMEDIATE_DISABLE):
lag = Events(self.events.event_rules)
lag.add(EventName.selfdrivedLagging)
# Steering is already withdrawn, irrespective of any old ordinary timer.
alerts.extend(lag.create_alerts([ET.SOFT_DISABLE], [self.CP, CS, self.sm, self.is_metric, 0, pers]))
if (getattr(self, 'aol_soft_takeover_alert', False) and
not getattr(self, 'aol_lag_takeover_alert', False) and not self.events.contains(ET.IMMEDIATE_DISABLE)):
# AOL already withdrew steering while ordinary cruise was disabled.
# Select the existing fault's takeover warning without starting LONG or
# changing its event classification, threshold, or output decision.
alerts.extend(self.events.create_alerts([ET.SOFT_DISABLE], [self.CP, CS, self.sm, self.is_metric, 0, pers]))
alerts.extend(alert for alert in getattr(self, 'tesla_stock_alerts', [])
if alert.event_type in self.state_machine.current_alert_types)
now_ns = time.monotonic_ns()
drive = int(self.sm['deviceState'].startedMonoTime) if self.sm['deviceState'].started else 0
switchback = bool(self.switchback_capable and self.sm.seen['slcState'] and self.sm.alive['slcState'] and
self.sm.valid['slcState'] and self.sm['carControl'].latActive and
self.switchback_status.sample_state(self.sm['slcState'], drive_id=drive, now_ns=now_ns,
event_ns=int(self.sm.logMonoTime['slcState'])))
if now_ns - self.switchback_setting_ns >= 1_000_000_000:
self.switchback_setting_ns = now_ns
try:
cooldown = float(self.params.get('SwitchbackModeCooldown') or 5)
self.switchback_cooldown_ns = int(cooldown * 60e9) if math.isfinite(cooldown) and 0 <= cooldown <= 30 else 0
except (TypeError, ValueError, OverflowError):
self.switchback_cooldown_ns = 0
# Reset the advisory clock even on frames without either advisory.
self.switchback_cooldown.allow('', active=switchback, drive_id=drive, now_ns=now_ns,
cooldown_ns=self.switchback_cooldown_ns)
alerts = [alert for alert in alerts if alert.alert_type not in ('belowSteerSpeed/warning', 'steerSaturated/warning') or
self.switchback_cooldown.allow(alert.alert_type.split('/')[0], active=switchback,
drive_id=drive, now_ns=now_ns, cooldown_ns=self.switchback_cooldown_ns)]
for index, alert in enumerate(alerts):
if alert.alert_type not in ("preLaneChangeLeft/warning", "preLaneChangeRight/warning"):
continue
source_ok = (self.sm.seen['laneChangeAssistWire'] and self.sm.alive['laneChangeAssistWire'] and
self.sm.valid['laneChangeAssistWire'] and self.sm.valid['modelV2'])
status = decode_lane_status(self.sm['laneChangeAssistWire']) if source_ok else None
now_mono_ns = time.monotonic_ns()
recv_ns = int(self.sm.recv_time['laneChangeAssistWire'] * 1e9)
message_ns = int(self.sm.logMonoTime['laneChangeAssistWire'])
valid_status = fresh_for_model(status, self.sm['modelV2'], now_mono_ns, recv_ns,
self.lane_status_sequence, self.lane_status_session, message_ns)
if valid_status:
assert status is not None
self.lane_status_session, self.lane_status_sequence = status.producer_session_id, status.sequence
wording = alert_wording(status if valid_status else None, self.sm['modelV2'], now_mono_ns, recv_ns)
if wording is None:
continue
replacement = copy(alert)
replacement.alert_text_1, replacement.alert_text_2 = wording
alerts[index] = replacement
if not hasattr(self, 'force_stop_hold_alert'):
self.force_stop_hold_alert = HoldAlertState()
holding = self.force_stop_hold_alert.active(
self.sm, self.CP, CS, enabled=self.enabled, car_ns=getattr(self, 'aol_car_state_log_ns', 0),
car_valid=getattr(self, 'conditional_car_state_valid', False), now_ns=time.monotonic_ns()) and ET.WARNING in self.state_machine.current_alert_types
if holding:
alert = Alert("Force Stop Holding", "Press RES or accelerator to proceed", log.SelfdriveState.AlertStatus.normal,
log.SelfdriveState.AlertSize.mid, AlertPriority.LOW, car.CarControl.HUDControl.VisualAlert.none,
log.SelfdriveState.AudibleAlert.none, 0.)
alert.alert_type, alert.event_type = "forceStopHold/warning", FORCE_STOP_HOLD
alerts.append(alert)
else:
clear_event_types.add(FORCE_STOP_HOLD)
from openpilot.starpilot.car.gm.radar_recovery import EVENT_TYPE as RADAR_RECOVERY_EVENT, RecoveryNotification
if not hasattr(self, 'radar_recovery_notification'):
self.radar_recovery_notification = RecoveryNotification()
recovery = self.radar_recovery_notification
notification = recovery.update(self.CP, self.sm, enabled=getattr(self, 'radar_recovery_enabled', False), now_ns=now_ns)
if notification is not None:
alerts.append(notification)
if not recovery.monitor.visible(now_ns):
clear_event_types.add(RADAR_RECOVERY_EVENT)
# A cold/unarmed fault classification is not a pause of expected steering.
# Qualified availability retention already carries both fields even while
# ordinary cruise is disabled; this only selects its presentation.
availability_warning = bool(getattr(self, 'aol_fault_reason', 'none') == 'availabilityPause' and
self.aol_axis_decision.desired_lateral and self.aol_axis_decision.native_acknowledged)
if availability_warning:
warning = Alert("Steering Paused", "Communication Issue", log.SelfdriveState.AlertStatus.normal,
log.SelfdriveState.AlertSize.small, AlertPriority.LOW, car.CarControl.HUDControl.VisualAlert.none,
(log.SelfdriveState.AudibleAlert.prompt if
now_ns <= getattr(self, 'aol_availability_chime_until_ns', 0) else log.SelfdriveState.AudibleAlert.none), 0.)
warning.alert_type, warning.event_type = 'aolAvailability/warning', ET.WARNING
alerts.append(warning)
else:
# Retire only this presentation; retain every original safety alert.
self.AM.alerts.pop('aolAvailability/warning', None)
if getattr(self, 'jetlink_link_lost', False):
alerts.append(jetlink_lost_alert())
self.AM.add_many(self.sm.frame, alerts)
self.AM.process_alerts(self.sm.frame, clear_event_types)
from openpilot.starpilot.audio.availability_warning import AvailabilityWarningDelivery
if not hasattr(self, 'availability_warning_delivery'):
self.availability_warning_delivery = AvailabilityWarningDelivery()
selected = self.AM.current_alert
self.availability_warning_delivery.observe(session=getattr(self, 'aol_session_id', None), drive=drive,
now_ns=now_ns, selected=bool(selected.alert_type == 'aolAvailability/warning' and
selected.audible_alert == log.SelfdriveState.AudibleAlert.prompt),
requested_until_ns=getattr(self, 'aol_availability_chime_until_ns', 0))
recovery.selected(self.AM.current_alert)
def ordinary_source_availability(self, CS, now_ns, native=None):
"""Classify lost host source; never renew a command or change engagement."""
return ordinary_source_availability(self, CS, now_ns, native, replay=REPLAY, native_provider=current_native)
def observe_ordinary_source_metadata(self, CS, intent, healthy_native, now_ns):
"""Reuse this call's exact transport validation; retain the original proof."""
native = healthy_native
anchor = getattr(self, 'aol_ordinary_source_anchor', None)
companion = getattr(self, 'aol_intent_companion', None)
if (anchor is None or not (anchor[0][8] or anchor[0][9]) or
anchor[0][9] and (not self.enabled or not self.active) or intent is None or
not 0 < intent.carStateLogMonoTime <= now_ns or now_ns - intent.carStateLogMonoTime > INTENT_MAX_AGE_NS or
intent.carStateLogMonoTime <= anchor[2].carStateLogMonoTime or
intent.producerSessionId != anchor[0][4] or companion is None or companion.integrity_epoch != anchor[0][5] or
(self.events.contains(ET.USER_DISABLE) and not stock_lateral_role(self, CS, native, intent)) or
self.events.contains(ET.IMMEDIATE_DISABLE) or
native is None or native is not getattr(self.aol_transport_pause, 'observed_native', None) or
native.pandaSerial != anchor[1].pandaSerial or native.observedMonoTime < anchor[1].observedMonoTime or
tuple(p.hardwareSerial for p in native.pandaInventory) != tuple(p.hardwareSerial for p in anchor[1].pandaInventory)):
return
# The originally published command proof, deadline and CP identity remain
# unchanged. Only an input already admitted by normal runtime is remembered.
self.aol_ordinary_source_anchor = (anchor[0], native, intent, anchor[3], CS)
def ordinary_source_withdrawal(self):
accepted = self.aol_ordinary_source_anchor[2]
native = self.aol_ordinary_source_native
lateral_grant = bool(accepted.allowedLatch and accepted.lateralArmed and not accepted.pauseLateral and
not getattr(self, 'aol_dm_lateral_inhibit', False) and
native.requestedLateral and native.lateralAllowed)
# Both commands remain inactive. This ACK describes only an actual native
# lateral grant supporting separately retained armed intent; none is invented.
return AxisDecision(desired_lateral=lateral_grant, native_acknowledged=lateral_grant)
def retain_ordinary_source_state(self, CS, now_ns):
"""Publication-time expiry withdraws commands; StateMachine owns countdown."""
if not self.ordinary_source_availability(CS, now_ns):
return False
self.aol_ordinary_source_pause = True
self.aol_publication_retained_intent = self.aol_ordinary_source_anchor[2]
self.events.add(EventName.commIssue)
self.aol_axis_decision = self.ordinary_source_withdrawal()
self.aol_fault_reason = 'availabilityPause'
self.aol_car_state_log_ns = 0
self.conditional_car_state_valid = False
# If publication crosses the cutoff after StateMachine ran, emit the fault
# now; its existing next-cycle transition owns the timer and driver state.
if self.enabled and ET.SOFT_DISABLE not in self.state_machine.current_alert_types:
self.state_machine.current_alert_types.append(ET.SOFT_DISABLE)
return True
def _expire_axis_source(self, CS, now_ns):
if REPLAY or not self.aol_replay or not self.axis_transport_required:
return
source_ns = self.aol_car_state_log_ns
if 0 < source_ns <= now_ns and now_ns - source_ns <= INTENT_MAX_AGE_NS:
self.aol_publication_retained_intent = None
return
if self.retain_ordinary_source_state(CS, now_ns):
return
pause = getattr(self, 'aol_transport_pause', None)
if (getattr(self, 'aol_fault_reason', 'none') == 'transportPause' and pause is not None and
pause.active and pause.observed_native is not None and not self.aol_authority_lost and
not getattr(self, 'aol_selected_pair_future', False) and
not self.aol_axis_decision.lateral_active and not self.aol_axis_decision.longitudinal_active):
# This step already validated the typed pause; its expired source grants no output.
self.aol_car_state_log_ns = 0
self.conditional_car_state_valid = False
self.aol_publication_retained_intent = None
self.aol_axis_decision = AxisDecision()
self.state_machine.state = log.SelfdriveState.OpenpilotState.disabled
self.enabled = self.active = False
if ET.ENABLE in self.state_machine.current_alert_types:
self.state_machine.current_alert_types.remove(ET.ENABLE)
return
intent = getattr(self, 'aol_last_intent', None) or getattr(self, 'aol_publication_retained_intent', None)
if not source_ns and intent is not None:
source_ns = int(intent.carStateLogMonoTime)
healthy_expiry = bool(not getattr(self, 'aol_selected_pair_future', False) and source_ns > 0 and source_ns <= now_ns and
now_ns - source_ns <= SAFETY_MAX_AGE_NS and CS.canValid and not CS.canTimeout and
(getattr(self, 'conditional_car_state_valid', False) or
getattr(self, 'aol_publication_retained_intent', None) is intent))
self.aol_car_state_log_ns = 0
self.conditional_car_state_valid = False
self.aol_expired_healthy_source = healthy_expiry
previous = self.aol_axis_decision
was_enabled = self.enabled
self.aol_axis_decision = AxisDecision()
if healthy_expiry and not was_enabled and self.cold_gm_main_readiness(CS, intent, now_ns):
self.aol_fault_reason = 'availabilityPause'
self.aol_publication_retained_intent = intent
return
pause = getattr(self, 'aol_availability_pause', None)
if (healthy_expiry and pause is not None and intent is not None and
getattr(self, 'aol_fault_reason', 'none') in ('none', 'availabilityPause') and
not self.aol_authority_lost and not self.events.contains(ET.IMMEDIATE_DISABLE) and
(not self.events.contains(ET.SOFT_DISABLE) or availability_only(self.events.to_msg())) and
not self.events.contains(ET.USER_DISABLE)):
self.events.add(EventName.commIssue)
native = current_native(self.sm, self.CP, now_ns=now_ns, axis_session_id=self.aol_session_id)
from openpilot.starpilot.aol.transport_pause import _healthy_transport_native
healthy = _healthy_transport_native(self.sm, self.CP, CS, self.aol_session_id, now_ns, native)
retained = pause.observe(cp=self.CP, session=self.aol_session_id, sm=self.sm, cs=CS,
native=native, now_ns=now_ns, companion=getattr(self, 'aol_intent_companion', None),
intent=intent, events=self.events.to_msg(),
model_ready=bool(self.sm.all_checks(['modelV2', 'extrinsicsCalibration']) and
self.sm['extrinsicsCalibration'].calStatus == log.ExtrinsicsCalibration.Status.calibrated),
source_current=False, source_expired=True, prior_lateral_active=previous.lateral_active,
hard_fault=False, healthy_native=healthy)
if retained:
self.aol_publication_retained_intent = intent
self.aol_fault_reason = 'availabilityPause'
self.aol_availability_long_inhibit = True
self.aol_availability_ordinary_warning = bool(was_enabled or (
getattr(self, 'aol_published_fault_reason', 'none') == 'availabilityPause' and
getattr(self, 'aol_availability_ordinary_warning', False)))
if getattr(self, 'aol_published_fault_reason', 'none') != 'availabilityPause':
self.aol_availability_chime_until_ns = now_ns + 400_000_000
self.state_machine.state = log.SelfdriveState.OpenpilotState.disabled
self.enabled = self.active = False
if ET.ENABLE in self.state_machine.current_alert_types:
self.state_machine.current_alert_types.remove(ET.ENABLE)
if self.aol_availability_ordinary_warning and ET.SOFT_DISABLE not in self.state_machine.current_alert_types:
self.state_machine.current_alert_types.append(ET.SOFT_DISABLE)
self.aol_axis_decision = AxisDecision(desired_lateral=True, native_acknowledged=True)
if ET.WARNING not in self.state_machine.current_alert_types:
self.state_machine.current_alert_types.append(ET.WARNING)
return
self.aol_publication_retained_intent = None
self.state_machine.state = log.SelfdriveState.OpenpilotState.disabled
self.enabled = self.active = False
if ET.ENABLE in self.state_machine.current_alert_types:
self.state_machine.current_alert_types.remove(ET.ENABLE)
cold_gm_startup = bool(not self.initialized and qualified_gm(self.CP) and
not getattr(self, 'aol_ever_active', False) and not self.aol_authority_lost and
not self.events.contains(ET.IMMEDIATE_DISABLE))
# Armed MAIN while initializing is intent, not previously granted actuation.
# Expiry still removes the source and all output; actual active/fault paths
# retain their existing immediate withdrawal and warning ownership.
if (was_enabled or previous.lateral_active or previous.longitudinal_active or
intent is not None and intent.allowedLatch and not cold_gm_startup):
self.aol_fault_reason = 'critical'
self.aol_authority_lost = True
self.events.add(EventName.controlsMismatch)
if ET.IMMEDIATE_DISABLE not in self.state_machine.current_alert_types:
self.state_machine.current_alert_types.append(ET.IMMEDIATE_DISABLE)
def _expire_axis_continuity(self, CS, now_ns):
proof = getattr(self, 'aol_step_input_proof', getattr(self, 'aol_published_input_proof', None))
decision = self.aol_axis_decision
if (proof is None or proof[0] != self.aol_session_id or now_ns <= proof[3] or
not (decision.lateral_active or decision.longitudinal_active)):
return False
source_ns = self.aol_car_state_log_ns
intent = getattr(self, 'aol_last_intent', None)
companion = getattr(self, 'aol_intent_companion', None)
fresh_successor = bool(proof[1] < source_ns <= now_ns and now_ns - source_ns <= INTENT_MAX_AGE_NS and
self.conditional_car_state_valid and CS.canValid and not CS.canTimeout and
intent is not None and intent.carStateLogMonoTime == source_ns and
intent.producerSessionId == proof[4] and intent.settingsQualified and
0 < intent.observedMonoTime <= now_ns <= intent.validUntilMonoTime and
now_ns - intent.observedMonoTime <= INTENT_MAX_AGE_NS and
companion is not None and companion.integrity_epoch == proof[5] and
decision.native_acknowledged and not self.aol_authority_lost and
getattr(self, 'aol_fault_reason', 'none') == 'none' and
not self.events.contains(ET.IMMEDIATE_DISABLE) and
(not decision.longitudinal_active or self.enabled and self.active))
user_disable = self.events.contains(ET.USER_DISABLE)
if user_disable:
from opendbc.car.hyundai.canfd_angle_aol import qualified as angle_qualified
from opendbc.car.hyundai.canfd_stock_aol import qualified as stock_canfd_qualified
from openpilot.starpilot.car.toyota.aol import qualified as toyota_qualified
if (toyota_qualified(self.CP, marked_only=True) and CS.standstill or
angle_qualified(self.CP, marked_only=True) or
self.CP.brand == 'hyundai' and CS.brakePressed and len(self.CP.safetyConfigs) == 1 and
stock_canfd_qualified(self.CP, marked_only=True)):
native = current_native(self.sm, self.CP, now_ns=now_ns, axis_session_id=self.aol_session_id)
if stock_lateral_role(self, CS, native, intent, allow_independent_cancel=True,
allow_independent_brake=True, now_ns=now_ns):
user_disable = False
if user_disable and self.held_axis_override_wait(CS, proof, now_ns, fresh_successor=fresh_successor and qualified_gm(self.CP))[0]:
# A fresh same-owner source can establish its own LAT permission after
# an old output expires. Held-brake continuity does not renew that output;
# the current native proof below still has to grant the new decision.
stock_already_off = bool(fresh_successor and
((qualified_gm(self.CP) and self.CP.pcmCruise and not self.CP.openpilotLongitudinalControl) or
self.ford_source_deadline_supported()) and not CS.cruiseState.enabled and
not self.CS_prev.cruiseState.enabled and all(valid and not sample.cruiseState.enabled
for sample, valid in getattr(self, 'car_state_samples', ())))
user_disable = any(event.userDisable and event.name != EventName.pedalPressed and not
(stock_already_off and event.name == EventName.pcmDisable) for event in self.events.to_msg())
if fresh_successor and not user_disable:
native = current_native(self.sm, self.CP, now_ns=now_ns, axis_session_id=self.aol_session_id)
prior_native = getattr(self, 'aol_published_input_native', None)
if (native is not None and prior_native is not None and native.pandaSerial == prior_native.pandaSerial and
native.observedMonoTime >= prior_native.observedMonoTime and
tuple(slot.hardwareSerial for slot in native.pandaInventory) ==
tuple(slot.hardwareSerial for slot in prior_native.pandaInventory) and
(not decision.lateral_active or native.requestedLateral and native.lateralAllowed) and
(not decision.longitudinal_active or native.requestedLongitudinal and native.longitudinalAllowed) and
(not self.events.contains(ET.SOFT_DISABLE) or
fresh_ordinary_lag_continuation(self, CS, intent, native, now_ns))):
# The old lease still expires at consumers. A newly validated source
# supplies its own permission without restoring a disengaged state.
return False
# The expired command still withdraws; host loss need not revoke a healthy
# driver's ordinary engagement before the existing soft-disable countdown.
if self.retain_ordinary_source_state(CS, now_ns):
return True
# A retained source cannot renew an expired output lease.
self.aol_axis_decision = AxisDecision()
was_enabled = self.enabled
self.state_machine.state = log.SelfdriveState.OpenpilotState.disabled
self.enabled = self.active = False
self.aol_availability_long_inhibit = True
if ET.ENABLE in self.state_machine.current_alert_types:
self.state_machine.current_alert_types.remove(ET.ENABLE)
intent = getattr(self, 'aol_last_intent', None)
pause = getattr(self, 'aol_availability_pause', None)
retained = False
if (intent is not None and pause is not None and not self.aol_authority_lost and
getattr(self, 'aol_fault_reason', 'none') == 'none' and
not any(self.events.contains(kind) for kind in (ET.USER_DISABLE, ET.SOFT_DISABLE, ET.IMMEDIATE_DISABLE))):
self.events.add(EventName.commIssue)
native = current_native(self.sm, self.CP, now_ns=now_ns, axis_session_id=self.aol_session_id)
from openpilot.starpilot.aol.transport_pause import _healthy_transport_native
healthy = _healthy_transport_native(self.sm, self.CP, CS, self.aol_session_id, now_ns, native)
retained = pause.observe(cp=self.CP, session=self.aol_session_id, sm=self.sm, cs=CS,
native=native, now_ns=now_ns, companion=getattr(self, 'aol_intent_companion', None),
intent=intent, events=self.events.to_msg(),
model_ready=bool(self.sm.all_checks(['modelV2', 'extrinsicsCalibration']) and
self.sm['extrinsicsCalibration'].calStatus == log.ExtrinsicsCalibration.Status.calibrated),
source_current=bool(self.conditional_car_state_valid and 0 < self.aol_car_state_log_ns <= now_ns and
now_ns - self.aol_car_state_log_ns <= INTENT_MAX_AGE_NS and CS.canValid and not CS.canTimeout),
source_expired=False, prior_lateral_active=bool(proof[8]), hard_fault=False, healthy_native=healthy)
if retained:
self.aol_fault_reason = 'availabilityPause'
self.aol_axis_decision = AxisDecision(desired_lateral=True, native_acknowledged=True)
self.aol_availability_ordinary_warning = was_enabled
if getattr(self, 'aol_published_fault_reason', 'none') != 'availabilityPause':
self.aol_availability_chime_until_ns = now_ns + 400_000_000
if ET.WARNING not in self.state_machine.current_alert_types:
self.state_machine.current_alert_types.append(ET.WARNING)
if was_enabled and ET.SOFT_DISABLE not in self.state_machine.current_alert_types:
self.state_machine.current_alert_types.append(ET.SOFT_DISABLE)
else:
self.aol_fault_reason = 'critical'
self.aol_authority_lost = True
self.events.add(EventName.controlsMismatch)
if ET.IMMEDIATE_DISABLE not in self.state_machine.current_alert_types:
self.state_machine.current_alert_types.append(ET.IMMEDIATE_DISABLE)
return True
def publish_axis_proof(self, CS, *, defer_events=False, source_context=None):
now_ns = self.aol_car_state_log_ns if REPLAY and self.aol_car_state_log_ns else time.monotonic_ns()
source_clock_scoped = bool(not REPLAY and self.aol_replay and self.axis_transport_required)
if source_clock_scoped:
self._expire_axis_source(CS, now_ns)
self._expire_axis_continuity(CS, now_ns)
if self.aol_axis_decision.lateral_active or self.aol_axis_decision.longitudinal_active:
self.aol_ever_active = True
reason = getattr(self, 'aol_fault_reason', 'none')
event_now_ns = now_ns
ce_send = None
if ((self.sm.frame % int(1. / DT_CTRL) == 0) or self.events.names != self.events_prev or
reason != getattr(self, 'aol_published_fault_reason', 'unknown')):
ce_send = messaging.new_message('onroadEvents', len(self.events))
ce_send.logMonoTime = event_now_ns
ce_send.valid = True
ce_send.onroadEvents = self.events.to_msg()
fault_event_ns = int(ce_send.logMonoTime) if ce_send is not None else getattr(self, 'aol_fault_event_ns', 0)
if self.aol_replay or getattr(self, 'ordinary_axis_ack_required', False):
if not source_clock_scoped:
now_ns = self.aol_car_state_log_ns if REPLAY and self.aol_car_state_log_ns else time.monotonic_ns()
next_sequence = self.aol_sequence + 1
axis_msg = messaging.new_message('aolAxisState')
axis_msg.logMonoTime = now_ns
axis_msg.valid = bool(self.aol_car_state_log_ns > 0 or reason == 'transportPause' or
source_clock_scoped and reason == 'availabilityPause')
axis = axis_msg.aolAxisState
axis.sessionId = self.aol_session_id
axis.faultReason = reason
axis.faultSessionId = self.aol_session_id
accepted_intent = getattr(self, 'aol_last_intent', None)
if accepted_intent is None and reason == 'availabilityPause' and not self.aol_car_state_log_ns:
accepted_intent = getattr(self, 'aol_publication_retained_intent', None)
accepted_companion = getattr(self, 'aol_intent_companion', None)
axis.intentProducerSessionId = accepted_intent.producerSessionId if accepted_intent is not None else ''
axis.intentIntegrityEpoch = accepted_companion.integrity_epoch if accepted_companion is not None else 0
axis.faultEventMonoTime = fault_event_ns
axis.sequence = next_sequence
axis.sourceCarStateMonoTime = self.aol_car_state_log_ns
axis.observedMonoTime = now_ns
axis.validUntilMonoTime = now_ns + 30_000_000
axis.mode = self.aol_axis_decision.mode
axis.lateralActive = self.aol_axis_decision.lateral_active
axis.longitudinalActive = self.aol_axis_decision.longitudinal_active
axis.desiredLateral = self.aol_axis_decision.desired_lateral
axis.desiredLongitudinal = self.aol_axis_decision.desired_longitudinal
axis.optionalSetRelease = bool(self.aol_replay and self.aol_last_intent is not None
and self.aol_last_intent.optionalSetRelease)
axis.nativeAcknowledged = self.aol_axis_decision.native_acknowledged
axis.qualified = bool((self.aol_replay or getattr(self, 'ordinary_axis_ack_required', False)) and
(not source_clock_scoped or self.aol_car_state_log_ns > 0 or
reason in ('transportPause', 'availabilityPause', 'critical')))
axis_deferred = False
if source_clock_scoped:
final_ns = time.monotonic_ns()
if ((axis.lateralActive or axis.longitudinalActive) and
(not 0 < self.aol_car_state_log_ns <= final_ns or final_ns - self.aol_car_state_log_ns > INTENT_MAX_AGE_NS) and
self.source_deadline_deferral_supported()):
if (source_context is not None and source_context[1] and
self.defer_expired_axis_publication(CS, source_context[0])):
# Serialization crossed the same source boundary. Retain only the
# original proof; staged message metadata grants no new authority.
self.aol_axis_publication_deferred = axis_deferred = True
else:
self._expire_axis_source(CS, time.monotonic_ns())
return self.publish_axis_proof(CS, defer_events=defer_events, source_context=source_context)
if not axis_deferred and self._expire_axis_continuity(CS, final_ns):
return self.publish_axis_proof(CS, defer_events=defer_events, source_context=source_context)
if not axis_deferred:
self.aol_sequence = next_sequence
self.pm.send('aolAxisState', axis_msg)
# Bind critical history to the actual completed axis publication. The
# working decision can change again during later source-expiry checks.
self.aol_published_fault_proof = ((str(axis.sessionId), str(axis.faultReason),
int(axis.faultEventMonoTime), tuple(self.events.names), bool(axis.desiredLateral),
bool(axis.desiredLongitudinal), bool(axis.lateralActive), bool(axis.longitudinalActive),
bool(axis.nativeAcknowledged)) if reason == 'critical' else None)
self.aol_published_stock_lateral_proof = (
stock_lateral_fault_proof(self, CS, axis_msg, time.monotonic_ns()) if source_clock_scoped and reason == 'critical' else None)
self.aol_published_input_proof = None
self.aol_published_input_native = None
if (source_clock_scoped and reason == 'none' and axis_msg.valid and axis.qualified and
axis.nativeAcknowledged and (axis.lateralActive or axis.longitudinalActive) and
0 < axis.sourceCarStateMonoTime <= now_ns and
now_ns - axis.sourceCarStateMonoTime <= INTENT_MAX_AGE_NS):
self.aol_published_input_proof = (str(axis.sessionId), int(axis.sourceCarStateMonoTime),
now_ns, int(axis.validUntilMonoTime), str(axis.intentProducerSessionId),
int(axis.intentIntegrityEpoch), bool(axis.desiredLateral), bool(axis.desiredLongitudinal),
bool(axis.lateralActive), bool(axis.longitudinalActive))
self.aol_published_input_native = getattr(self, 'aol_decision_native', None)
if axis.lateralActive or axis.longitudinalActive and self.enabled and self.active:
previous_anchor = getattr(self, 'aol_ordinary_source_anchor', None)
identity = previous_anchor[3] if previous_anchor is not None else cp_identity(self.CP)
self.aol_ordinary_source_anchor = (self.aol_published_input_proof,
self.aol_published_input_native, accepted_intent, identity, CS)
if not defer_events:
if ce_send is not None:
self.aol_fault_event_ns = int(ce_send.logMonoTime)
self.pm.send('onroadEvents', ce_send)
self.aol_published_fault_reason = reason
self.events_prev = self.events.names.copy()
return ce_send, reason
def publish_selfdriveState(self, CS, *, axis_published=False):
# selfdriveState
ss_msg = messaging.new_message('selfdriveState')
ss_msg.valid = True
ss = ss_msg.selfdriveState
ss.enabled = self.enabled
ss.active = self.active
ss.state = self.state_machine.state
ss.engageable = not self.events.contains(ET.NO_ENTRY)
ss.experimentalMode = self.experimental_mode
ss.personality = self.personality
ss.alertText1 = self.AM.current_alert.alert_text_1
ss.alertText2 = self.AM.current_alert.alert_text_2
ss.alertSize = self.AM.current_alert.alert_size
ss.alertStatus = self.AM.current_alert.alert_status
ss.alertType = self.AM.current_alert.alert_type
ss.alertSound = self.AM.current_alert.audible_alert
delivery = getattr(self, 'availability_warning_delivery', None)
if delivery is not None and delivery.pending(session=getattr(self, 'aol_session_id', None),
drive=int(self.sm['deviceState'].startedMonoTime) if self.sm['deviceState'].started else 0,
now_ns=time.monotonic_ns(),
audible_none=ss.alertSound == log.SelfdriveState.AudibleAlert.none,
priority=self.AM.current_alert.priority, low_priority=AlertPriority.LOW):
ss.alertSound = log.SelfdriveState.AudibleAlert.prompt
ss.alertHudVisual = self.AM.current_alert.visual_alert
ce_send, reason = (None, None) if axis_published else self.publish_axis_proof(CS, defer_events=True)
self.pm.send('selfdriveState', ss_msg)
if self.conditional_replay:
self.conditional_ack_sequence += 1
result = self.conditional_result
proposal = self.conditional_consumer.last if result.accepted and self.conditional_consumer is not None else None
ack = publish_ack(session=self.conditional_ack_session, sequence=self.conditional_ack_sequence,
observed_ns=time.monotonic_ns(), selfdrive_state_ns=int(ss_msg.logMonoTime),
drive_id=int(self.sm['deviceState'].startedMonoTime), effective_experimental=bool(ss.experimentalMode),
result=result, accepted_proposal=proposal)
historic = ack.starpilotSelfdriveState
historic.alertText1 = ss.alertText1
historic.alertText2 = ss.alertText2
historic.alertStatus = str(ss.alertStatus)
historic.alertSize = str(ss.alertSize)
historic.alertType = ss.alertType
sound = str(ss.alertSound)
historic.alertSound = sound if sound in ('none', 'engage', 'disengage', 'refuse', 'warningSoft',
'warningImmediate', 'prompt', 'promptRepeat', 'promptDistracted') else 'none'
historic.vEgo = CS.vEgo
self.pm.send('starpilotSelfdriveState', ack)
if not axis_published:
if ce_send is not None:
self.aol_fault_event_ns = int(ce_send.logMonoTime)
self.pm.send('onroadEvents', ce_send)
self.aol_published_fault_reason = reason
self.events_prev = self.events.names.copy()
def update_conditional_mode(self, CS):
# Conditional document and baseline come from one accepted owner snapshot;
# only this thread publishes their effective choice.
self.experimental_mode = self.requested_experimental_mode if not self.conditional_replay else False
self.conditional_result = ConsumerResult(self.experimental_mode, False, 'unavailable')
if not self.conditional_replay:
return
assert self.conditional_settings is not None and self.conditional_consumer is not None
snapshot = self.conditional_settings.current
clocks = paired_clocks_ns() if not REPLAY else None
if clocks is None:
self.conditional_consumer.invalidate(time.monotonic_ns())
self.conditional_status = 'clock_unavailable'
return
now_ns, boot_ns, skew_ns = clocks
drive_id = int(self.sm['deviceState'].startedMonoTime)
verdict = self.conditional_settings.verdict(snapshot, now_mono_ns=now_ns, drive_id=drive_id)
configured = (verdict.status == 'ready' and verdict.selection is not None and
snapshot is not None and verdict.revision == snapshot.revision)
baseline = bool(configured and experimental_requested(snapshot.experimental_raw, snapshot.experimental_readable,
snapshot.safe_mode_raw, True, self.CP))
self.requested_experimental_mode = baseline
self.experimental_mode = baseline
required = ('deviceState', 'modelV2', 'carControl')
def service_fresh(service):
return bool(self.sm.seen[service] and self.sm.valid[service] and self.sm.alive[service] and
0 < self.sm.logMonoTime[service] <= now_ns and
now_ns - self.sm.logMonoTime[service] <= (2_000_000_000 if service == 'deviceState' else 150_000_000))
fresh = all(service_fresh(service) for service in required)
axis_active = self.aol_axis_decision.longitudinal_active if self.aol_replay else self.enabled
authority = bool(configured and fresh and self.initialized and axis_active and
not getattr(self, 'aol_axis_publication_deferred', False) and
self.CP.openpilotLongitudinalControl and not self.CP.passive and
self.conditional_car_state_valid and CS.canValid and not CS.canTimeout and self.sm['deviceState'].started and
self.sm['carControl'].longActive and self.sm.seen['slcState'] and self.sm.alive['slcState'])
message_ns = int(self.sm.logMonoTime['slcState'])
receipt_ns = int(self.sm.recv_time['slcState'] * 1e9)
model_ns = int(self.sm.logMonoTime['modelV2'])
car_state_ns = self.aol_car_state_log_ns
result = self.conditional_consumer.sample(
self.sm['slcState'], now_ns=now_ns, now_boot_ns=boot_ns, sample_skew_ns=skew_ns,
message_ns=message_ns, receipt_ns=receipt_ns,
drive_id=drive_id, model_ns=model_ns, car_state_ns=car_state_ns,
authority=authority, stock_experimental=baseline,
choice=verdict.selection.choice if configured else ModeChoice.STOCK,
settings_fingerprint=settings_fingerprint(snapshot) if configured else None)
self.experimental_mode = result.experimental
self.conditional_status = result.status
self.conditional_result = result
def _decide_aol_axes(self, CS, intent, native, *, now_ns=0, ordinary_lag=False):
from openpilot.starpilot.car.gm.aol import physical_cancel_lateral_allowed, stock_ascm_independent_lateral, cc_independent_lateral_below_engage
from openpilot.starpilot.car.hyundai.aol import physical_cancel_lateral_allowed as hkg_cancel_lateral_allowed
from openpilot.starpilot.car.mazda.aol import physical_cancel_lateral_allowed as mazda_cancel_lateral_allowed
from openpilot.starpilot.car.ford.aol import physical_cancel_lateral_allowed as ford_cancel_lateral_allowed
from openpilot.starpilot.car.subaru.aol import sng_standstill_lateral_inhibit
from openpilot.starpilot.aol.dm import lateral_event_context
from openpilot.starpilot.car.toyota.legacy_lateral import below_engage_lateral_allowed as toyota_below_engage_lateral_allowed
retained_dm_lateral, lateral_events = lateral_event_context(self, CS, intent, native, now_ns, ordinary_lag)
reverse_pause = known_reverse_pause(CS, getattr(self, 'car_state_samples', ()))
if reverse_pause:
lateral_events = [event for event in lateral_events if event not in (EventName.wrongGear, EventName.reverseGear)]
return decide_axes(
standard_lateral=self.active, standard_longitudinal=bool(self.enabled and self.CP.openpilotLongitudinalControl and
not any(sample.seatbeltUnlatched for sample, _ in getattr(self, 'car_state_samples', ()))),
intent=intent, native=native, car_state=CS, initialized=self.initialized,
model_ready=bool(self.sm.all_checks(['modelV2', 'extrinsicsCalibration']) and
self.sm['extrinsicsCalibration'].calStatus == log.ExtrinsicsCalibration.Status.calibrated),
no_entry=aol_no_entry(lateral_events, CS, paddle_only_cancel=self.nostalgia_paddle_cancel,
allow_lateral_cancel=bool(intent is not None and intent.allowedLatch and intent.optionalSetRelease
and ev6_aol_qualified(self.CP, marked_only=True)) or
physical_cancel_lateral_allowed(self.CP, CS, intent, native,
source_ns=getattr(self, 'aol_car_state_log_ns', 0), now_ns=now_ns) or
hkg_cancel_lateral_allowed(self.CP, CS, intent, native,
settings=self.aol_settings, previous=getattr(self, 'aol_axis_decision', None),
source_ns=getattr(self, 'aol_car_state_log_ns', 0), now_ns=now_ns,
axis_session_id=getattr(self, 'aol_session_id', '')) or
ford_cancel_lateral_allowed(self.CP, CS, intent, native,
settings=self.aol_settings, previous=getattr(self, 'aol_axis_decision', None),
source_ns=getattr(self, 'aol_car_state_log_ns', 0), now_ns=now_ns,
axis_session_id=getattr(self, 'aol_session_id', '')) or
mazda_cancel_lateral_allowed(self.CP, CS, intent, native,
settings=self.aol_settings, previous=getattr(self, 'aol_axis_decision', None),
source_ns=getattr(self, 'aol_car_state_log_ns', 0), now_ns=now_ns,
axis_session_id=getattr(self, 'aol_session_id', '')),
cruise_main_required=getattr(self, 'aol_cruise_main_required', True),
allow_below_engage_speed=bool((is_volt_ascm_longitudinal(self.CP) or
stock_ascm_independent_lateral(self.CP) or
cc_independent_lateral_below_engage(self.CP, intent,
enabled=self.enabled, active=self.active)) and
not self.CP.flags & GMFlags.PEDAL_LONG) or
toyota_below_engage_lateral_allowed(self.CP, CS, intent, native,
source_ns=getattr(self, 'aol_car_state_log_ns', 0), now_ns=now_ns,
axis_session_id=getattr(self, 'aol_session_id', ''))),
immediate_disable=self.events.contains(ET.IMMEDIATE_DISABLE),
dm_lockout=bool(not self.sm.all_checks(['driverMonitoringState']) or
not retained_dm_lateral and (self.sm['driverMonitoringState'].lockout or
self.sm['driverMonitoringState'].alwaysOnLockout or
self.sm['driverMonitoringState'].alertLevel == AlertLevel.three)),
pause_brake_mps=self.aol_settings.pause_brake_mps if self.aol_settings is not None else 0.0,
gear_pause=reverse_pause,
lateral_inhibit=self.aol_dm_lateral_inhibit or sng_standstill_lateral_inhibit(self.CP, CS) or any(
sample.steerFaultTemporary or sample.brakePressed and not sample.standstill and
sample.vEgo < (self.aol_settings.pause_brake_mps if self.aol_settings is not None else 0.0)
for sample, _ in getattr(self, 'car_state_samples', ())))
def cold_gm_park_readiness(self, CS, intent, now_ns):
from openpilot.starpilot.car.gm.aol import cold_park_readiness
from openpilot.starpilot.aol.transport_pause import _healthy_transport_native
from openpilot.starpilot.aol.runtime import INTENT_MAX_AGE_NS
if (self.CP.brand != 'gm' or not self.initialized or self.enabled or self.active or getattr(self, 'aol_ever_active', False) or
self.aol_authority_lost or not getattr(self, 'conditional_car_state_valid', False) or
intent is None or not intent.settingsQualified or not intent.lateralArmed or intent.allowedLatch or
intent.pauseLateral or intent.pauseLongitudinal or
intent.carStateLogMonoTime != self.aol_car_state_log_ns or
not 0 < intent.carStateLogMonoTime <= intent.observedMonoTime <= now_ns <= intent.validUntilMonoTime or
now_ns - intent.carStateLogMonoTime > INTENT_MAX_AGE_NS):
return False
native = _healthy_transport_native(self.sm, self.CP, CS, self.aol_session_id, now_ns)
return cold_park_readiness(self.CP, CS, native, self.events.to_msg())
def cold_gm_main_readiness(self, CS, intent, now_ns):
from openpilot.starpilot.car.gm.aol import cold_main_readiness
from openpilot.starpilot.aol.transport_pause import _healthy_transport_native
if (self.CP.brand != 'gm' or self.enabled or self.active or getattr(self, 'aol_ever_active', False) or self.aol_authority_lost or
not self.events.contains(ET.SOFT_DISABLE) or self.events.contains(ET.IMMEDIATE_DISABLE) or
intent is None or not intent.settingsQualified or not intent.allowedLatch or not intent.lateralArmed or
not 0 < intent.carStateLogMonoTime <= intent.observedMonoTime <= now_ns <= intent.validUntilMonoTime or
now_ns - intent.carStateLogMonoTime > SAFETY_MAX_AGE_NS):
return False
native = _healthy_transport_native(self.sm, self.CP, CS, self.aol_session_id, now_ns)
return cold_main_readiness(self.CP, CS, native, self.events.to_msg())
def ordinary_native_long_withdrawal(self, CS, native, previous, requested, intent, *, source_current, now_ns):
"""Keep the existing ordinary Panda mismatch owner for a LONG-only drop.
This classifies no new native permission. The unchanged per-axis decision
withdraws LONG immediately; genuine hardware faults and the ordinary
mismatch counter retain their original fault ownership.
"""
from openpilot.starpilot.aol.transport_pause import independent_lateral_health
from opendbc.car.ford.values import CAR as FORD_CAR
from openpilot.starpilot.car.ford.aol import native_accepts_cp as ford_native_accepts_cp
# Established ordinary Mach-E cruise does not require an independent AOL latch.
# Its native LONG pedal withdrawal may precede Card; the original combined
# publication, live LAT grant and every health/deadline check below still apply.
ordinary_ford = bool(self.CP.carFingerprint == FORD_CAR.FORD_MUSTANG_MACH_E_MK1 and
ford_native_accepts_cp(self.CP, int(car.CarParams.SafetyModel.ford), 19))
proof = getattr(self, 'aol_published_input_proof', None)
prior = getattr(self, 'aol_published_input_native', None)
anchor = getattr(self, 'aol_ordinary_source_anchor', None)
companion = getattr(self, 'aol_intent_companion', None)
if (not self.enabled or not self.active or not self.CP.openpilotLongitudinalControl or
not previous.lateral_active or not previous.longitudinal_active or
not requested.desired_lateral or not requested.desired_longitudinal or not source_current or
not self.conditional_car_state_valid or not 0 < self.aol_car_state_log_ns <= now_ns or
now_ns - self.aol_car_state_log_ns > INTENT_MAX_AGE_NS or
native is None or prior is None or proof is None or anchor is None or
anchor[3] != cp_identity(self.CP) or proof[0] != self.aol_session_id or
not proof[8] or not proof[9] or not 0 < proof[2] <= now_ns <= proof[3] or
intent is None or companion is None or proof[4] != intent.producerSessionId or
proof[5] != companion.integrity_epoch or not ((intent.allowedLatch and intent.lateralArmed) or ordinary_ford) or
not native.requestedLateral or not native.lateralAllowed or
not native.requestedLongitudinal or native.longitudinalAllowed or not native.pandaInventory or
CS.brakePressed or CS.regenBraking or CS.gasPressed or CS.steeringPressed or
any(self.events.contains(kind) for kind in (ET.IMMEDIATE_DISABLE, ET.SOFT_DISABLE, ET.USER_DISABLE,
ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL))):
return False
def identity(state):
return (state.axisSessionId, state.pandaSerial, state.safetyModel, state.safetyParam,
tuple((slot.slotIndex, slot.hardwareSerial, slot.safetyModel, slot.safetyParam,
slot.alternativeExperience) for slot in state.pandaInventory))
if (identity(prior) != identity(native) or
not 0 < prior.observedMonoTime <= native.observedMonoTime <= now_ns <= prior.validUntilMonoTime or
independent_lateral_health(self.sm, self.CP, CS, self.aol_session_id, now_ns) != native):
return False
owners = [index for index, config in enumerate(self.CP.safetyConfigs)
if str(config.safetyModel) not in ('silent', 'noOutput')]
if len(owners) != 1:
return False
from openpilot.starpilot.car.gm.aol import native_accepts_cp as gm_native_accepts_cp
from openpilot.starpilot.car.toyota.aol import qualified as toyota_aol_qualified
from openpilot.starpilot.car.hyundai.aol import early_native_long_withdrawal_supported
# Pandad reads health before its final native permission read. A GM driver
# withdrawal between those reads legitimately leaves the earlier health
# controlsAllowed true. For an admitted OP-long GM profile, the current
# LAT-only native mask itself proves controls_allowed has gone false.
# Keep LONG withdrawn and leave disagreement to the ordinary mismatch owner.
# Toyota's native LONG mask also checks gas, brake and belt independently
# of controls_allowed. Health and that mask may precede the parsed pedal
# edge: honor LONG withdrawal while the existing native LAT grant stands.
# Ioniq6 also checks the physical brake before its separately published host sample.
# Retain only its already granted LAT; all proof, health and LONG withdrawal gates remain.
# The exact vehicle policies and all proof/health checks above remain required.
from openpilot.starpilot.car.ford.aol import early_native_long_withdrawal_supported as ford_early_long
return bool(ordinary_ford or gm_native_accepts_cp(self.CP, native.safetyModel, native.safetyParam) or
early_native_long_withdrawal_supported(self.CP) or ford_early_long(self.CP) or
toyota_aol_qualified(self.CP, marked_only=True) or
not native.pandaInventory[owners[0]].controlsAllowed and
not self.sm['pandaStates'][owners[0]].controlsAllowed)
def honda_native_lateral_grant_lost(self, CS, native, previous, requested, *, source_current, previous_intent, intent, now_ns):
"""An established host LAT request lost its native grant, not a cold wait."""
pause = getattr(self, 'aol_transport_pause', None)
prior = getattr(pause, 'observed_native', None)
if (not honda_aol_qualified(self.CP, marked_only=True) or not source_current or
not self.initialized or not previous.lateral_active or not requested.desired_lateral or
native is None or previous_intent is None or intent is None or not previous_intent.producerSessionId or
previous_intent.producerSessionId != intent.producerSessionId or
not 0 < previous_intent.carStateLogMonoTime <= native.observedMonoTime <= now_ns or
now_ns - previous_intent.carStateLogMonoTime > INTENT_MAX_AGE_NS or
native.requestedLateral or native.lateralAllowed or
prior is None or not prior.requestedLateral or not prior.lateralAllowed or
getattr(self, 'aol_fault_reason', 'none') != 'none' or
pause.active or getattr(getattr(self, 'aol_availability_pause', None), 'active', False) or
CS.brakePressed or CS.regenBraking or CS.gasPressed or CS.steeringPressed or
honda_temporary_restriction(self.CP, CS) or
any(self.events.contains(event) for event in
(ET.USER_DISABLE, ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL, ET.IMMEDIATE_DISABLE, ET.SOFT_DISABLE)) or
not self.sm.all_checks(['modelV2', 'extrinsicsCalibration']) or
self.sm['extrinsicsCalibration'].calStatus != log.ExtrinsicsCalibration.Status.calibrated):
return False
def identity(state):
return (state.axisSessionId, state.pandaSerial, state.safetyModel, state.safetyParam,
tuple((p.slotIndex, p.hardwareSerial, p.safetyModel, p.safetyParam,
p.alternativeExperience) for p in state.pandaInventory))
return bool(pause.cp == cp_identity(self.CP) and pause.session == self.aol_session_id and
identity(prior) == identity(native) and
0 < prior.observedMonoTime <= native.observedMonoTime <= now_ns <= prior.validUntilMonoTime and
now_ns - prior.observedMonoTime <= SAFETY_MAX_AGE_NS and
current_native(self.sm, self.CP, now_ns=now_ns, axis_session_id=self.aol_session_id) == native and
healthy_transport(self.sm, self.CP, CS, self.aol_session_id, now_ns, native))
def step(self):
self.aol_lag_takeover_alert = False
self.aol_soft_takeover_alert = False
initial_state = (self.state_machine.state, self.state_machine.soft_disable_timer, self.enabled, self.active)
previous_deferred = getattr(self, 'aol_axis_publication_deferred', False)
self.aol_axis_publication_deferred = False
self.aol_step_deferral_checked = False
self.aol_step_intent_drain_count = 0
self.aol_step_intent_drain_ok = True
self.aol_event_intent_drained = None
self.aol_event_input_reconcile = False
self.aol_event_input_wait_attempted = False
previous_fault_reason = getattr(self, 'aol_fault_reason', 'none')
self.aol_ordinary_source_pause = False
self.aol_step_input_proof = getattr(self, 'aol_published_input_proof', None)
CS = self.data_sample()
self.car_event_sample_count = 0
self.aol_reconciled_sources = ()
self.event_car_state = CS
self.event_input_refresh_pending = True
try:
self.update_events(CS)
finally:
self.event_input_refresh_pending = False
CS = self.event_car_state
if max(self.car_event_sample_count, len(getattr(self, 'car_state_samples', ()))) > 1 and any(self.events.contains(kind) for kind in (
ET.USER_DISABLE, ET.SOFT_DISABLE, ET.IMMEDIATE_DISABLE)):
# A queued enable cannot override a cancellation or pedal edge in this batch.
self.events.events = [event for event in self.events.names if ET.ENABLE not in self.events.event_rules.get(event, {})]
native = None
lost_active_aol = bool(self.aol_replay and getattr(self, 'steering_authority_unavailable', False) and
(self.aol_axis_decision.lateral_active or self.aol_axis_decision.longitudinal_active))
intent_companion = getattr(self, 'aol_intent_companion', None)
intent_drained = (self.aol_event_intent_drained and self.aol_step_intent_drain_ok if self.aol_event_intent_drained is not None else
not self.aol_replay or intent_companion is None or self.drain_axis_intent(intent_companion))
if self.aol_replay or getattr(self, 'ordinary_axis_ack_required', False):
now_ns = self.aol_car_state_log_ns if REPLAY and self.aol_car_state_log_ns else time.monotonic_ns()
# Intent draining can cross the source cutoff. Check the original proof
# against the same post-drain clock used by admission, before authority
# state changes. This retains no new input, proof, deadline, or enable.
if not previous_deferred and self.defer_expired_axis_publication(CS, initial_state, now_ns=now_ns):
self.aol_axis_publication_deferred = True
self.update_alerts(CS)
self.update_conditional_mode(CS)
self.publish_selfdriveState(CS, axis_published=True)
self.CS_prev = CS
del self.aol_step_input_proof
return
native = current_native(self.sm, self.CP, now_ns=now_ns, axis_session_id=self.aol_session_id)
if native is None and self.initialized:
lost_active_aol = self.aol_axis_decision.lateral_active or self.aol_axis_decision.longitudinal_active
self.events.add(EventName.controlsMismatch)
self.aol_decision_native = native
if self.aol_replay:
previous_intent = getattr(self, 'aol_last_intent', None)
reconciled_pairs = dict(intent_companion.samples) if intent_companion is not None and self.aol_reconciled_sources else {}
reconciled_epoch = intent_companion.integrity_epoch if intent_companion is not None else -1
source_pair = intent_companion.samples.get(self.aol_car_state_log_ns) if intent_companion is not None else None
verified = intent_companion.verified_sm_projection if intent_companion is not None else None
if (intent_companion is not None and source_pair is None and verified is not None and
verified[0] == self.aol_car_state_log_ns and
verified[2] == intent_companion.integrity_epoch and verified[3] == intent_companion.session):
source_pair = (verified[0], True, verified[1])
self.aol_selected_pair_future = bool(source_pair is not None and source_pair[2] is not None and
source_pair[2].observedMonoTime > now_ns)
known_intent = previous_intent or getattr(self, 'aol_publication_retained_intent', None)
if self.aol_car_state_log_ns > 0:
self.aol_expired_healthy_source = bool(not REPLAY and self.conditional_car_state_valid and
CS.canValid and not CS.canTimeout and now_ns - self.aol_car_state_log_ns > INTENT_MAX_AGE_NS and
source_pair is not None and source_pair[1] and source_pair[2] is not None and
source_pair[2].carStateLogMonoTime == self.aol_car_state_log_ns and source_pair[2].observedMonoTime <= now_ns and
source_pair[2].settingsQualified and source_pair[2].allowedLatch and source_pair[2].lateralArmed and
known_intent is not None and source_pair[2].producerSessionId == known_intent.producerSessionId)
intent = current_intent(self.sm, car_state_ns=self.aol_car_state_log_ns, now_ns=now_ns,
previous=getattr(self, 'aol_last_intent', None),
companion=intent_companion, drain=False) if intent_drained else None
intent = self.reconciled_intent(intent, previous_intent, reconciled_pairs, reconciled_epoch, now_ns)
self.aol_last_intent = intent
from openpilot.starpilot.aol.dm import dm_clear, observe_owner
observe_owner(self, intent, previous_intent)
previous = self.aol_axis_decision
requested = self._decide_aol_axes(CS, intent, native, now_ns=now_ns)
source_current = bool(intent is not None and getattr(self, 'conditional_car_state_valid', False) and
CS.canValid and not CS.canTimeout)
driver_override = bool(CS.brakePressed or CS.regenBraking or CS.gasPressed or
self.events.contains(ET.USER_DISABLE))
lost_companion = bool((previous.lateral_active or previous.longitudinal_active) and
not source_current and not self.events.contains(ET.USER_DISABLE))
lost_honda_lateral_grant = self.honda_native_lateral_grant_lost(
CS, native, previous, requested, source_current=source_current,
previous_intent=previous_intent, intent=intent, now_ns=now_ns)
ordinary_long_withdrawal = bool(native is not None and not native.longitudinalAllowed and
self.ordinary_native_long_withdrawal(
CS, native, previous, requested, intent, source_current=source_current, now_ns=now_ns))
from openpilot.starpilot.car.hyundai.aol import expected_stopped_lateral_denial
stopped_lateral_withdrawal = bool(source_current and requested.desired_lateral and
expected_stopped_lateral_denial(self.CP, CS, native, sm=self.sm,
axis_session_id=self.aol_session_id, now_ns=now_ns))
lost_permission = bool(lost_honda_lateral_grant or native is not None and not driver_override and (
previous.lateral_active and requested.desired_lateral and native.requestedLateral and
not native.lateralAllowed and not stopped_lateral_withdrawal and
not CS.steeringPressed and not self.events.contains(ET.OVERRIDE_LATERAL) or
previous.longitudinal_active and requested.desired_longitudinal and native.requestedLongitudinal and
not native.longitudinalAllowed and not ordinary_long_withdrawal and
not self.events.contains(ET.OVERRIDE_LONGITUDINAL)))
if not hasattr(self, 'aol_transport_pause'):
self.aol_transport_pause = TransportPause(self.CP, self.aol_session_id)
pause, upgraded = self.aol_transport_pause.observe(
cp=self.CP, session=self.aol_session_id, sm=self.sm, cs=CS, native=native, now_ns=now_ns,
companion=getattr(self, 'aol_intent_companion', None), previous_intent=previous_intent, intent=intent,
expired_healthy_source=getattr(self, 'aol_expired_healthy_source', False),
previously_active=previous.lateral_active, hard_fault=bool(
self.aol_selected_pair_future or self.aol_authority_lost or lost_active_aol or lost_permission or self.events.contains(ET.IMMEDIATE_DISABLE) or
self.events.contains(ET.SOFT_DISABLE)),
model_ready=bool(self.sm.all_checks(['modelV2', 'extrinsicsCalibration']) and
self.sm['extrinsicsCalibration'].calStatus == log.ExtrinsicsCalibration.Status.calibrated),
user_disable=self.events.contains(ET.USER_DISABLE))
if source_current:
self.observe_ordinary_source_metadata(CS, intent, self.aol_transport_pause.observed_native, now_ns)
if not hasattr(self, 'aol_availability_pause'):
self.aol_availability_pause = AvailabilityPause(self.CP, self.aol_session_id)
retained_intent = getattr(self, 'aol_publication_retained_intent', None)
if retained_intent is None and getattr(self, 'aol_expired_healthy_source', False):
retained_intent = previous_intent
healthy_native = self.aol_transport_pause.observed_native
publication_expiry = bool(not self.aol_selected_pair_future and healthy_native is not None and
healthy_native.pandaInventory and retained_intent is not None and not source_current and
0 < retained_intent.carStateLogMonoTime <= now_ns and
now_ns - retained_intent.carStateLogMonoTime <= SAFETY_MAX_AGE_NS)
availability_events = self.events.to_msg()
if publication_expiry and EventName.commIssue not in self.events.names:
source_expiry_events = Events()
source_expiry_events.add(EventName.commIssue)
availability_events += source_expiry_events.to_msg()
availability = self.aol_availability_pause.observe(
cp=self.CP, session=self.aol_session_id, sm=self.sm, cs=CS, native=native, now_ns=now_ns,
companion=intent_companion, intent=intent if intent is not None else retained_intent, events=availability_events,
model_ready=bool(self.sm.all_checks(['modelV2', 'extrinsicsCalibration']) and
self.sm['extrinsicsCalibration'].calStatus == log.ExtrinsicsCalibration.Status.calibrated),
source_current=source_current, source_expired=publication_expiry, prior_lateral_active=previous.lateral_active,
hard_fault=bool(self.aol_selected_pair_future or self.aol_authority_lost or lost_active_aol or
lost_companion and not publication_expiry or lost_permission or upgraded),
healthy_native=self.aol_transport_pause.observed_native)
ordinary_availability = bool(not source_current and not lost_active_aol and not lost_permission and not upgraded and
self.ordinary_source_availability(CS, now_ns, native))
if ordinary_availability:
self.aol_ordinary_source_pause = True
self.aol_publication_retained_intent = self.aol_ordinary_source_anchor[2]
self.events.add(EventName.commIssue)
lost_companion = False
self.aol_transport_pause.active = pause = False
# Preserve the normal state machine and SOFT_DISABLE countdown. The
# independent armed-LAT retention must not disable ordinary engagement.
availability = False
if publication_expiry and (availability or not pause) and EventName.commIssue not in self.events.names:
self.events.add(EventName.commIssue)
if availability:
if publication_expiry:
self.aol_publication_retained_intent = retained_intent
lost_companion = False
self.aol_transport_pause.active = pause = False
if previous_fault_reason != 'availabilityPause':
self.aol_availability_ordinary_warning = self.enabled
self.aol_availability_chime_until_ns = time.monotonic_ns() + 400_000_000
self.aol_availability_long_inhibit = True
if pause and source_current and requested.lateral_active:
# Ordinary controls were disabled at the first pause. Fresh normal AOL
# gates and exact native acknowledgment recover only its lateral axis.
self.aol_transport_pause.active = pause = False
if pause:
lost_companion = False
if self.initialized and (upgraded or lost_active_aol or lost_companion or lost_permission):
lost_active_aol = True
self.aol_authority_lost = True
elif source_current and not intent.allowedLatch:
self.aol_authority_lost = False
# Temporary EPS withdraws steering commands without revoking the armed
# latch. Keep the event and ordinary soft-disable behavior unchanged.
from openpilot.starpilot.aol.seatbelt import seatbelt_lateral_soft_event_allowed
reverse_pause = known_reverse_pause(CS, getattr(self, 'car_state_samples', ()))
aol_hard_soft_fault = any(event.softDisable and not (
event.name == EventName.wrongGear and reverse_pause or
event.name == EventName.steerTempUnavailable and CS.steerFaultTemporary and not CS.steerFaultPermanent or
seatbelt_lateral_soft_event_allowed(self.CP, CS, intent, native, event,
source_ns=self.aol_car_state_log_ns, now_ns=now_ns, axis_session_id=self.aol_session_id,
pending_lateral_request=not previous.lateral_active and not self.aol_authority_lost,
gear_pause=reverse_pause))
for event in self.events.to_msg())
cold_main_wait = self.cold_gm_main_readiness(CS, intent, now_ns)
cold_park_wait = self.cold_gm_park_readiness(CS, intent, now_ns)
self.aol_fault_reason = ('none' if cold_park_wait else
'availabilityPause' if ordinary_availability or cold_main_wait else
'critical' if self.aol_authority_lost or self.events.contains(ET.IMMEDIATE_DISABLE) else
'availabilityPause' if availability or aol_hard_soft_fault else 'transportPause' if pause else 'none')
if self.aol_authority_lost or pause:
self.events.add(EventName.controlsMismatch)
lost_active_aol |= pause
was_enabled = self.enabled
if not self.CP.passive and self.initialized:
self.enabled, self.active = self.state_machine.update(self.events)
if not was_enabled and self.enabled and self.events.contains(ET.ENABLE):
self.aol_step_input_proof = None
if lost_active_aol and ET.IMMEDIATE_DISABLE not in self.state_machine.current_alert_types:
self.state_machine.current_alert_types.append(ET.IMMEDIATE_DISABLE)
if self.aol_replay:
if (not availability and source_current and self.enabled and self.events.contains(ET.ENABLE) and
dm_clear(self) and
not self.events.contains(ET.NO_ENTRY) and not self.events.contains(ET.IMMEDIATE_DISABLE) and
not self.events.contains(ET.SOFT_DISABLE)):
self.aol_availability_long_inhibit = False
if getattr(self, 'aol_availability_long_inhibit', False):
# Keep ordinary state truthful until a fresh accepted ENABLE. AOL's
# separately acknowledged lateral recovery does not reengage LONG.
self.state_machine.state = log.SelfdriveState.OpenpilotState.disabled
self.enabled = self.active = False
if ET.ENABLE in self.state_machine.current_alert_types:
self.state_machine.current_alert_types.remove(ET.ENABLE)
if (availability and getattr(self, 'aol_availability_ordinary_warning', False) and
self.state_machine.soft_disable_timer > 0 and
ET.SOFT_DISABLE not in self.state_machine.current_alert_types):
self.state_machine.current_alert_types.append(ET.SOFT_DISABLE)
ordinary_lag = bool(not availability and not pause and not self.aol_ordinary_source_pause and
fresh_ordinary_lag_continuation(self, CS, intent, native, now_ns))
self.aol_axis_decision = (self._decide_aol_axes(CS, intent, native, now_ns=now_ns, ordinary_lag=True)
if ordinary_lag else self._decide_aol_axes(CS, intent, native, now_ns=now_ns))
if ordinary_lag:
# Preserve the exact current SOFT event and normal countdown. A live
# standard-axis proof describes healthy current authority, not critical.
self.aol_fault_reason = 'none'
if cold_main_wait:
self.aol_axis_decision = AxisDecision()
if cold_park_wait:
self.aol_axis_decision = AxisDecision(native_acknowledged=True)
if self.aol_ordinary_source_pause:
self.aol_axis_decision = self.ordinary_source_withdrawal()
elif availability:
self.aol_axis_decision = AxisDecision(desired_lateral=True, native_acknowledged=True)
elif getattr(self, 'aol_availability_long_inhibit', False):
decision = self.aol_axis_decision
self.aol_axis_decision = AxisDecision(desired_lateral=decision.desired_lateral,
lateral_active=decision.lateral_active, native_acknowledged=decision.native_acknowledged)
if pause:
# Negotiate the fresh lateral request while output and ordinary LONG
# remain off; requiring an active ack before requesting would deadlock.
self.aol_axis_decision = AxisDecision(desired_lateral=bool(source_current and requested.desired_lateral))
elif getattr(self, 'ordinary_axis_ack_required', False):
self.aol_axis_decision = decide_ordinary_axis(
requested=bool(self.initialized and self.conditional_car_state_valid and
CS.canValid and not CS.canTimeout and
ordinary_lateral_requested(self.active, CS, self.CP) and
ordinary_axis_request_allowed(self.CP, CS)), native=native)
if (self.aol_replay and source_current and not was_enabled and not self.enabled and
previous.lateral_active and previous.native_acknowledged and not self.aol_axis_decision.lateral_active and
self.aol_fault_reason == 'critical' and not self.aol_authority_lost and
intent is not None and intent.allowedLatch and EventName.selfdrivedLagging in self.events.names and
not self.events.contains(ET.IMMEDIATE_DISABLE) and
not any(event.softDisable and event.name != EventName.selfdrivedLagging for event in self.events.to_msg()) and
self._decide_aol_axes(CS, intent, native, now_ns=now_ns, ordinary_lag=True).lateral_active):
# Independent AOL has no ordinary enabled state to select a SOFT alert.
# Removing only lag proves the cause of this acknowledged steering loss;
# this notification-only result never replaces the withdrawn axis decision.
self.aol_lag_takeover_alert = True
if (self.aol_replay and source_current and not was_enabled and not self.enabled and
previous.lateral_active and previous.native_acknowledged and not self.aol_axis_decision.lateral_active and
self.aol_fault_reason in ('critical', 'availabilityPause') and not self.aol_authority_lost and
intent is not None and intent.allowedLatch and self.events.contains(ET.SOFT_DISABLE) and
not self.events.contains(ET.IMMEDIATE_DISABLE)):
self.aol_soft_takeover_alert = True
if (self.aol_replay and intent is not None and intent.lateralArmed and
CS.steerFaultTemporary and not CS.steerFaultPermanent and not CS.steeringPressed):
# An armed AOL session still needs the ordinary temporary-steering warning
# while actual steering is suspended and the stock state machine is off.
# Like CarSpecificEvents, skip it while the driver is steering: a hard
# override alone puts Honda EPS in NO_TORQUE_ALERT_1.
if EventName.steerTempUnavailableSilent not in self.events.names:
self.events.add(EventName.steerTempUnavailableSilent)
if ET.WARNING not in self.state_machine.current_alert_types:
self.state_machine.current_alert_types.append(ET.WARNING)
if (getattr(self, 'aol_fault_reason', 'none') == 'availabilityPause' and
(self.CP.brand != 'ford' or self.enabled or intent is not None and intent.allowedLatch) and
ET.WARNING not in self.state_machine.current_alert_types):
self.state_machine.current_alert_types.append(ET.WARNING)
axis_published = bool(not REPLAY and self.aol_replay and self.axis_transport_required)
if axis_published:
self.aol_axis_publication_deferred = bool(not previous_deferred and self.defer_expired_axis_publication(CS, initial_state))
if not self.aol_axis_publication_deferred:
self.publish_axis_proof(CS, source_context=(initial_state, not previous_deferred))
self.update_alerts(CS)
self.update_conditional_mode(CS)
self.publish_selfdriveState(CS, axis_published=axis_published)
self.CS_prev = CS
# Preserve an explicit None during a new engagement; retire it after publication.
del self.aol_step_input_proof
def refresh_saved_driving_mode(self, now_ns: int):
if self.conditional_replay:
assert self.conditional_settings is not None
self.conditional_settings.refresh(now_ns, force=True, wait=True)
else:
_, (baseline, readable), (safe, safe_readable) = read_mode_sources(self.params, wait=True)
self.requested_experimental_mode = experimental_requested(baseline, readable, safe, safe_readable, self.CP)
def params_thread(self, evt):
while not evt.is_set():
self.is_metric = self.params.get_bool("IsMetric")
if self.aol_replay:
self.aol_settings = read_settings(self.params)
self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled")
self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
self.radar_recovery_enabled = self.params.get_bool("RadarRecoveryAlert")
self.nostalgia_enabled = nostalgia_saved_enabled(self.params)
self.refresh_saved_driving_mode(time.monotonic_ns())
self.personality = self.params.get("LongitudinalPersonality", return_default=True)
time.sleep(0.1)
def run(self):
e = threading.Event()
t = threading.Thread(target=self.params_thread, args=(e, ))
try:
t.start()
while True:
self.step()
self.rk.monitor_time()
finally:
e.set()
t.join()
def main():
config_realtime_process(5, Priority.CTRL_HIGH)
s = SelfdriveD()
s.run()
if __name__ == "__main__":
main()