from openpilot.starpilot.gps.publisher import CarGpsPublisher #!/usr/bin/env python3 import os import time import threading import uuid from collections import deque from typing import cast import openpilot.cereal.messaging as messaging from openpilot.cereal import log from opendbc.car.structs import car from openpilot.common.params import Params from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper from openpilot.common.swaglog import cloudlog, ForwardingHandler from opendbc.car import DT_CTRL, structs from opendbc.car.can_definitions import CanData, CanRecvCallable, CanSendCallable from opendbc.car.carlog import carlog from opendbc.car.fw_versions import ObdCallback from opendbc.car.car_helpers import get_car, interfaces from opendbc.car.gm.distance_button import GMDistanceButtons from opendbc.car.gm.interface import CarInterface as GMInterface from opendbc.car.gm.profiles import profiles_supported as gm_profiles_supported from opendbc.car.hyundai.ioniq6_handoff import (IONIQ6_LONG_PREARM_ENABLED, HandoffOutcome, IONIQ6_ECAN_BUS, TimestampedCanPacket, build_ioniq6_hda2_long_candidate, prepare_ioniq6_long_candidate, confirm_ioniq6_prepared_takeover, restore_ioniq6_adas, inspect_ioniq6_long_sources) from opendbc.car.hyundai.ioniq6_media import Ioniq6MediaButtons from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR from opendbc.car.interfaces import CarInterfaceBase, RadarInterfaceBase from openpilot.selfdrive.pandad import can_capnp_to_list, can_list_to_can_capnp from openpilot.selfdrive.car.cruise import VCruiseHelper, SlcPendingConfirmation from openpilot.starpilot.speed_limits import physical_actions as slc_physical from openpilot.starpilot.aol.intent import AolProcessFaultContext, independent_axis_requested, read_settings from openpilot.starpilot.aol.transport_pause import TransportPauseFeedback, cp_identity from opendbc.car.honda.stock_aol import ( qualified as qualified_honda_stock_aol, native_observation as honda_native_observation, temporary_restriction as honda_temporary_restriction, ) from opendbc.car.volkswagen.stock_aol import (native_observation as volkswagen_native_observation, safety_config as volkswagen_aol_safety_config) from openpilot.starpilot.car.volkswagen.aol import (qualified as qualified_volkswagen_aol, temporary_restriction as volkswagen_temporary_restriction) from openpilot.starpilot.car.rivian.aol import retained_brake_lateral_intent as rivian_brake_intent_retained from opendbc.car.rivian.stock_aol import (qualified as qualified_rivian_aol, native_observation as rivian_native_observation, temporary_restriction as rivian_temporary_restriction) from opendbc.car.mazda.stock_aol import (qualified as qualified_mazda_aol, native_observation as mazda_native_observation, temporary_restriction as mazda_temporary_restriction) from opendbc.car.tesla.preap.aol import ( qualified as qualified_tesla_preap, native_observation as preap_native_observation, temporary_restriction as preap_temporary_restriction, ) from opendbc.car.ford.aol import ( qualified as qualified_ford_aol, native_observation as ford_native_observation, temporary_restriction as ford_temporary_restriction, ) from opendbc.car.hyundai.canfd_angle_aol import native_observation, temporary_restriction, qualified as qualified_angle_aol from opendbc.car.hyundai.canfd_owner import config_index as hyundai_canfd_config_index from opendbc.car.hyundai.captured_adrv_startup import CapturedADRVStartup from openpilot.starpilot.car.nissan.aol import ( qualified as qualified_nissan_aol, native_observation as nissan_native_observation, temporary_restriction as nissan_temporary_restriction, ) from openpilot.starpilot.car.hyundai.aol import aol_safety_config as hyundai_aol_safety_config, pending_stock_angle_arm from openpilot.starpilot.car.hyundai.aol import retained_shared_long_cancel_intent as hkg_cancel_intent_retained from openpilot.starpilot.aol.runtime import AXIS_MAX_AGE_NS, current_axis, current_native from openpilot.starpilot.aol.vehicle import create_intent as create_aol_intent, native_latch_rejected, policy_for as aol_policy_for from openpilot.starpilot.aol.wire import IntentState, encode_intent from openpilot.starpilot.conditional_mode.manual import Button, ButtonTracker, IoniqMediaMapCache, WheelMapCache, Press, ioniq6_media_eligible from openpilot.starpilot.longitudinal.ioniq6_start import eligible as ioniq6_long_eligible from opendbc.car.gm.aol import qualified_gm from openpilot.starpilot.aol.vehicle import native_bootstrap_supported from opendbc.car.gm.values import CAR as GM_CAR, GMFlags, is_volt_ascm_longitudinal, is_volt_sdgm_profile, is_bolt_present_no_acc_pedal_profile from opendbc.car.gm.lateral import lane_centering_supported as gm_lane_centering_supported from opendbc.car.gm.ordinary_cc import control_transport_required as gm_cc_transport_required from openpilot.starpilot.longitudinal.toyota_output_policy import CLOCK_PAIR_MAX_SKEW_NS, clock_pair_ns from openpilot.starpilot.longitudinal.cruise_intervals import read_cruise_intervals from openpilot.starpilot.conditional_mode.card_input import conditional_manual_candidate, conditional_traffic_candidate from openpilot.starpilot.conditional_mode.policy import ModeChoice from openpilot.starpilot.conditional_mode.runtime_settings import ConditionalSettingsOwner from openpilot.starpilot.conditional_mode.card_settings import CardSettingsWorker from openpilot.starpilot.conditional_mode.status import settings_fingerprint from openpilot.starpilot.feature_runtime import (requested as feature_requested, enabled as feature_enabled, slc_runtime_settings, slc_transport_capable) from openpilot.starpilot.saved_source import read_saved from openpilot.starpilot.vehicle_selection import startup_candidate from openpilot.starpilot.vehicle_preferences import VehicleStartupPreferences from openpilot.starpilot.controller_extensions import configure_controller from openpilot.starpilot.vehicle_startup import VehicleStartupOwner from openpilot.starpilot.controllers.wheel_actions import WheelPublisher, key_for from openpilot.starpilot.controllers.cruise_action import CruiseActionConsumer, eligible as cruise_action_eligible from openpilot.starpilot.schema_cache import get_cache, prewarm_cache_contracts, put_cache REPLAY = "REPLAY" in os.environ EventName = log.OnroadEvent.EventName DISTANCE_GESTURE_KEYS = {Press.SHORT: ('DistanceButtonControl', 0), Press.LONG: ('LongDistanceButtonControl', 1), Press.VERY_LONG: ('VeryLongDistanceButtonControl', 2)} # forward carlog.addHandler(ForwardingHandler(cloudlog)) def obd_callback(params: Params) -> ObdCallback: def set_obd_multiplexing(obd_multiplexing: bool): if params.get_bool("ObdMultiplexingEnabled") != obd_multiplexing: cloudlog.warning(f"Setting OBD multiplexing to {obd_multiplexing}") params.remove("ObdMultiplexingChanged") params.put_bool("ObdMultiplexingEnabled", obd_multiplexing, block=True) params.get_bool("ObdMultiplexingChanged", block=True) cloudlog.warning("OBD multiplexing set successfully") return set_obd_multiplexing def can_comm_callbacks(logcan: messaging.SubSocket, sendcan: messaging.PubSocket) -> tuple[CanRecvCallable, CanSendCallable]: def can_recv(wait_for_one: bool = False) -> list[list[CanData]]: """ wait_for_one: wait the normal logcan socket timeout for a CAN packet, may return empty list if nothing comes Returns: CAN packets comprised of CanData objects for easy access """ ret = [] for can in messaging.drain_sock(logcan, wait_for_one=wait_for_one): ret.append(TimestampedCanPacket([CanData(msg.address, msg.dat, msg.src) for msg in can.can], int(can.logMonoTime))) return ret def can_send(msgs: list[CanData]) -> None: sendcan.send(can_list_to_can_capnp(msgs, msgtype='sendcan')) return can_recv, can_send def curve_press_receipt(CS: car.CarState, helper: VCruiseHelper, sm, *, curve_replay: bool, slc_replay: bool, host_long_active: bool, observed_ns: int): """Reserve SLC presentation presses even when their command is stale or rejected.""" accelerate = frozenset((car.CarState.ButtonEvent.Type.accelCruise, car.CarState.ButtonEvent.Type.resumeCruise)) pressed = {b.type.raw for b in CS.buttonEvents if b.pressed and b.type.raw in accelerate} if (not curve_replay or not host_long_active or not CS.canValid or CS.canTimeout or not CS.cruiseState.available or not pressed): return None if slc_replay and (helper.slc_consumed_button is not None or bool(pressed & helper.slc_suppressed_buttons) or bool(sm['slcState'].hasPending)): return None return ('curveAccelPress', None, float(helper.v_cruise_kph_last / 3.6), float(helper.v_cruise_kph / 3.6), observed_ns) def alpha_long_requested(params, *, is_release: bool) -> bool: """Retain saved intent on release branches without granting Alpha authority.""" return not is_release and params.get_bool("AlphaLongitudinalEnabled") class Car: CI: CarInterfaceBase RI: RadarInterfaceBase CP: car.CarParams controller_cruise_sock = None aol_process_fault_context = None curve_replay = False car_params_published = False def __init__(self, CI=None, RI: RadarInterfaceBase | None = None, startup_owner=None) -> None: prewarm_cache_contracts(("CarParamsCache", "CarParamsPersistent", "CarParamsPrevRoute")) self.can_sock = messaging.sub_sock('can', timeout=20) self.params = Params() self.slc_replay = os.getenv('SLC_REPLAY_RUNTIME') == '1' or feature_requested(self.params, 'slc') self.curve_replay = os.getenv('CURVE_REPLAY_RUNTIME') == '1' or feature_requested(self.params, 'curve') self.aol_replay = os.getenv('AOL_REPLAY_RUNTIME') == '1' or feature_requested(self.params, 'aol') self.conditional_replay = os.getenv('CONDITIONAL_MODE_REPLAY_RUNTIME') == '1' or feature_requested(self.params, 'conditional') subscribed_services = ['pandaStates', 'carControl', 'onroadEvents', 'deviceState', 'autoSteeringLearning'] if self.params.get_bool('SubaruRedneckCruise') or self.params.get_bool('SubaruSNG'): subscribed_services.extend(('longitudinalPlan', 'selfdriveState')) if self.aol_replay: subscribed_services.extend(service for service in ('aolSafetyWire', 'managerState', 'aolAxisState', 'selfdriveState') if service not in subscribed_services) if self.slc_replay: subscribed_services.append('slcState') self.sm = messaging.SubMaster(subscribed_services, ignore_alive=['autoSteeringLearning'], ignore_valid=['autoSteeringLearning'], ignore_avg_freq=['autoSteeringLearning']) self.slc_cruise_event_id = 0 self.slc_producer_session = uuid.uuid4().hex self.slc_command_sock = messaging.sub_sock('slcCruiseCommand', conflate=False) if self.slc_replay else None self.slc_commands = deque(maxlen=8) self.slc_last_command_id = 0 self.slc_command_session = '' self.slc_receipts: list[tuple[str, object, float, float, int]] = [] self.manual_receipt = None self.manual_event_sequence = 0 self.traffic_receipt = None self.traffic_event_sequence = 0 self.switchback_receipt = None self.switchback_event_sequence = 0 publish_services = ['sendcan', 'carState', 'carParams', 'carOutput', 'radarTracks', 'starpilotCarState'] self.car_gps_publisher = CarGpsPublisher() # One existing event channel also carries source-qualified Switchback gestures. publish_services.append('slcCruiseEvent') self.wheel_publisher = WheelPublisher(self.params) self.wheel_commands = () if self.slc_replay: publish_services.append('slcDashboardObservation') if self.aol_replay: publish_services.append('aolIntentWire') self.pm = messaging.PubMaster(publish_services) self.can_rcv_cum_timeout_counter = 0 self.CC_prev = car.CarControl.new_message() self.CS_prev = car.CarState.new_message() self.initialized_prev = False self.ci_initialized = False self.last_actuators_output = structs.CarControl.Actuators() self.manual_button_tracker = ButtonTracker() if self.conditional_replay else None self.traffic_button_tracker = ButtonTracker() if self.conditional_replay else None self.ioniq_media_map = IoniqMediaMapCache() if self.conditional_replay else None self.manual_settings_owner = ConditionalSettingsOwner(self.params) if self.conditional_replay else None self.ioniq6_long_prearmed = False self.ioniq6_long_pending = False self.ioniq6_long_selected = False self.ioniq6_last_preflight_at = 0.0 self.ioniq6_panda_armed = False self.ioniq6_host_warmed = False self.ioniq6_handoff_source_floor_ns = 0 self.ioniq6_long_lost = False self.ioniq6_keepalive = None self.ioniq6_restore_callbacks = None self.ioniq6_restore_attempted = False self.ioniq6_init_complete = False self.vehicle_startup = VehicleStartupOwner(startup_owner) self.can_callbacks = can_comm_callbacks(self.can_sock, self.pm.sock['sendcan']) is_release = self.params.get_bool("IsReleaseBranch") openpilot_enabled_toggle = self.params.get_bool("OpenpilotEnabledToggle") startup_preferences = VehicleStartupPreferences.read(self.params, enabled=openpilot_enabled_toggle) def initialize_aol_preference(cp): from openpilot.starpilot.aol.defaults import enroll_missing_preference enroll_missing_preference(self.params, cp) if not self.aol_replay and feature_requested(self.params, 'aol'): self.aol_replay = True subscribed_services.extend(('aolSafetyWire', 'managerState', 'aolAxisState', 'selfdriveState')) self.sm = messaging.SubMaster(subscribed_services) self.pm.sock['aolIntentWire'] = messaging.pub_sock('aolIntentWire') if CI is None: # wait for one pandaState and one CAN packet print("Waiting for CAN messages...") while True: can = messaging.recv_one_retry(self.can_sock) if len(can.can) > 0: break alpha_long_allowed = alpha_long_requested(self.params, is_release=is_release) cached_params = None cached_params_raw = get_cache(self.params, "CarParamsCache") if cached_params_raw is not None: with car.CarParams.from_bytes(cached_params_raw) as _cached_params: cached_params = _cached_params def pre_create_hook(cp, candidate, fingerprints, car_fw): initialize_aol_preference(cp) if candidate == HYUNDAI_CAR.HYUNDAI_IONIQ_6: # This decision must precede CarInterface parser/controller construction # and the first CarParams publication below. The exact opt-in is # still subject to current source and restore verification. eligible = alpha_long_allowed and IONIQ6_LONG_PREARM_ENABLED and not is_release and openpilot_enabled_toggle and not self.params.get_bool("IsOffroad") if eligible: self.sm.update(0) eligible = bool(self.sm.valid['pandaStates'] and self.sm.alive['pandaStates'] and any(ps.ignitionLine or ps.ignitionCan for ps in self.sm['pandaStates'])) long_cp = build_ioniq6_hda2_long_candidate(cp, fingerprints) if eligible else None if long_cp is not None: preflight = inspect_ioniq6_long_sources(self.can_callbacks[0]) cloudlog.event('ioniq6_long_preflight', reason=preflight.reason) if preflight.source_period is None or preflight.vehicle_ready is not False: # Reboot in READY retains original stock CP before CI/parsers and # publication. Unknown evidence cannot be treated as Ready=False. long_cp = None if long_cp is not None: initialize_aol_preference(long_cp) if long_cp is not None and self.aol_replay and independent_axis_requested(read_settings(self.params)): # Bit 11 is AOL only inside the exact Ioniq 6 bit-15 namespace. # This immutable choice precedes the ECU transaction and CP publish. long_cp.safetyConfigs[-1].safetyParam |= 0x0800 cp, self.ioniq6_long_pending = prepare_ioniq6_long_candidate( cp, long_cp, enabled=eligible, is_release=is_release) self.ioniq6_long_selected = self.ioniq6_long_pending cp = startup_preferences.prepare(cp, fingerprints=fingerprints) from openpilot.starpilot.car.ford.longitudinal_preferences import prepare_release_long as prepare_ford_release_long cp = prepare_ford_release_long(cp, self.params, is_release=is_release, enabled=openpilot_enabled_toggle) return self.vehicle_startup.prepare(cp, interfaces[candidate], self.can_callbacks, requested=alpha_long_allowed and not is_release and openpilot_enabled_toggle, admission=self.startup_diagnostic_admission, defer_until_controls=True) self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, cached_params, pre_create_hook=pre_create_hook, forced_candidate=startup_candidate(self.params, developer_fingerprint=bool(os.getenv('FINGERPRINT')))) self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP) self.CP = self.CI.CP # continue onto next fingerprinting step in pandad self.params.put_bool("FirmwareQueryDone", True, block=True) else: self.CI, self.CP = CI, CI.CP if RI is None: raise ValueError('explicit car interface requires a radar interface') self.RI = RI self.volt_cc_selected = gm_cc_transport_required(self.CP) self.volt_cc_boot_offset_ns = None self.volt_cc_source_floor_ns = 0 if self.volt_cc_selected and 'deviceState' not in subscribed_services: subscribed_services.append('deviceState') self.sm = messaging.SubMaster(subscribed_services, ignore_alive=['deviceState'], ignore_valid=['deviceState'], ignore_avg_freq=['deviceState']) self.CP.alternativeExperience = 0 self.switchback_capable = ioniq6_media_eligible(self.CP) self.ioniq6_media = Ioniq6MediaButtons(IONIQ6_ECAN_BUS) if self.switchback_capable else None self.switchback_button_tracker = ButtonTracker() if self.switchback_capable else None self.switchback_settings_owner = ConditionalSettingsOwner(self.params) if self.switchback_capable else None self.gm_distance = GMDistanceButtons() if self.conditional_replay and gm_profiles_supported(self.CP) else None self.gm_distance_map = WheelMapCache(include_ioniq_media=False, cp=self.CP) if self.gm_distance is not None else None self.gm_distance_claim_tracker = ButtonTracker() if self.gm_distance is not None else None controller_available = self.CI.CC is not None and openpilot_enabled_toggle and not self.CP.dashcamOnly self.CP.passive = not controller_available or self.CP.dashcamOnly if self.CP.passive: safety_config = structs.CarParams.SafetyConfig() safety_config.safetyModel = structs.CarParams.SafetyModel.noOutput self.CP.safetyConfigs = [safety_config] startup_preferences.finalize(self.CP) from opendbc.car.gm.cancel_remap import migrate_assignments as migrate_gm_cancel_assignments migrate_gm_cancel_assignments(self.params, self.CP) startup_preferences.configure_controller(self.CI) configure_controller(self.CI, self.params) self.vehicle_startup.configure(self.CI) if self.CP.secOcRequired: # Copy user key if available try: with open("/cache/params/SecOCKey") as f: user_key = f.readline().strip() from openpilot.starpilot.car.toyota.security_key import decode_key if decode_key(user_key) is not None: self.params.put("SecOCKey", user_key, block=True) except Exception: pass from openpilot.starpilot.car.toyota.security_key import decode_key secoc_key = self.params.get("SecOCKey") if secoc_key is not None: saved_secoc_key = decode_key(secoc_key) if saved_secoc_key is not None: self.CP.secOcKeyAvailable = True self.CI.CS.secoc_key = saved_secoc_key if controller_available: self.CI.CC.secoc_key = saved_secoc_key else: cloudlog.warning("Saved SecOC key is invalid") from openpilot.starpilot.car.hyundai.button_migration import migrate_lkas_default migrate_lkas_default(self.CP, self.params) self.is_metric = self.params.get_bool("IsMetric") aol_policy = aol_policy_for(self.CP) if aol_policy.full_axis_runtime_required and not self.aol_replay: self.aol_replay = True subscribed_services.extend(('aolSafetyWire', 'managerState', 'aolAxisState', 'selfdriveState')) self.sm = messaging.SubMaster(subscribed_services) self.pm.sock['aolIntentWire'] = messaging.pub_sock('aolIntentWire') initialize_aol_preference(self.CP) self.aol_settings = read_settings(self.params) if self.aol_replay else None self.aol_qualified = bool(self.aol_settings is not None and (aol_policy.full_axis_runtime_required or aol_policy.physical_stalk_owner or independent_axis_requested(self.aol_settings, include_auxiliary=aol_policy.intent_supported and not aol_policy.explicit_latch)) and aol_policy.intent_supported and not self.CP.passive) self.aol_card_intent = (create_aol_intent(self.CP, self.aol_settings, aol_policy) if self.aol_qualified and self.aol_settings is not None else None) self.distance_personality_tracker = ButtonTracker() if aol_policy.distance_personality else None self.aol_sequence = 0 self.aol_process_fault_context = AolProcessFaultContext() if self.aol_replay else None if self.aol_qualified: if self.CP.brand == 'ford': from opendbc.car.ford.aol import aol_safety_config as ford_aol_safety_config config = ford_aol_safety_config(self.CP) else: config = (volkswagen_aol_safety_config(self.CP) if self.CP.brand == 'volkswagen' else (hyundai_aol_safety_config(self.CP) if self.CP.brand == 'hyundai' else self.CP.safetyConfigs[0])) if config is None: raise RuntimeError('AOL requires the exact vehicle safety configuration') config.safetyParam |= aol_policy.safety_param_addition self.CP.alternativeExperience |= aol_policy.alternative_experience_addition self.vehicle_startup.finalize_aol_configuration(self.CI) self.learning_stamp_observer = None self.learning_send_mono_ns = self.learning_send_committed_mono_ns = 0 self.learning_car_state_source_ns = 0 self.learning_applied_evidence = self.learning_previous_applied_evidence = None try: from opendbc.car.steering_projection import install_observer self.learning_stamp_observer = self.record_learning_send_stamp if install_observer(self.CI) else None except Exception: pass # Optional learning setup cannot stop native startup. self.aol_transport_feedback = TransportPauseFeedback(self.CP) if self.aol_replay else None if isinstance(self.CI, GMInterface) and self.CI.CC is not None and self.CI.CC.volt_sng_plan_input is not None: self.CI.CC.volt_sng_plan_input.boottime = self.volt_startup_keepalive() # Reserve optional transport from finalized capability, not saved admission. self.slc_transport_available = slc_transport_capable(self.CP, os.environ) if self.slc_transport_available: if 'slcState' not in subscribed_services: subscribed_services.append('slcState') self.sm = messaging.SubMaster(subscribed_services) for ignored in (self.sm.ignore_alive, self.sm.ignore_valid, self.sm.ignore_average_freq): if 'slcState' not in ignored: ignored.append('slcState') if self.slc_command_sock is None: self.slc_command_sock = messaging.sub_sock('slcCruiseCommand', conflate=False) if 'slcDashboardObservation' not in self.pm.sock: self.pm.sock['slcDashboardObservation'] = messaging.pub_sock('slcDashboardObservation') self.slc_requested_configuration = self.read_slc_configuration() self.slc_configuration = self.slc_requested_configuration self.slc_replay = self.slc_configuration[0] self.slc_control_enabled = self.slc_replay and self.slc_configuration[1].enabled self.slc_source_floor_ns = 0 # Write previous route's CarParams prev_cp = get_cache(self.params, "CarParamsPersistent") if prev_cp is not None: with car.CarParams.from_bytes(prev_cp) as previous: put_cache(self.params, "CarParamsPrevRoute", previous, block=True) # Write CarParams for controls and radard self.vehicle_startup.seal_publication() cp_bytes = self.CP.to_bytes() from openpilot.starpilot.car.tesla.pedal_startup import install as install_preap_pedal install_preap_pedal(self.CI, self.CP, cp_bytes, startup_preferences.tesla_preap_pedal, self.params) from openpilot.starpilot.car.tesla.radar_context import install_startup_context self.tesla_preap_radar_context = install_startup_context( self.RI, self.CP, cp_bytes, startup_preferences.tesla_preap_radar) from openpilot.starpilot.car.subaru.smoothing import install as install_subaru_smoothing install_subaru_smoothing(self.CP, cp_bytes, startup_preferences.subaru_smoothing, self.params) self.params.put("CarParams", cp_bytes, block=True) put_cache(self.params, "CarParamsCache", self.CP) put_cache(self.params, "CarParamsPersistent", self.CP) self.controller_cruise_consumer = CruiseActionConsumer() self.controller_cruise_sock = messaging.sub_sock('slcAction', conflate=False) if cruise_action_eligible(self.CP) else None self.v_cruise_helper = VCruiseHelper(self.CP) self.v_cruise_helper.intervals = read_cruise_intervals(self.params, self.CP) self.experimental_mode = self.params.get_bool("ExperimentalMode") self.initial_cruise_speed = self.params.get("InitialCruiseSpeedKph", return_default=True) # card is driven by can recv, expected at 100Hz self.rk = Ratekeeper(100, print_delay_threshold=None) self.ioniq6_init_complete = True def read_slc_configuration(self): settings = slc_runtime_settings(self.params, self.CP, os.environ) admitted = (self.slc_transport_available and not settings.errors and feature_enabled(self.params, self.CP, 'slc', os.environ)) return bool(admitted), settings def refresh_slc_configuration(self, now_ns: int) -> None: configuration = getattr(self, 'slc_requested_configuration', None) if configuration is None or configuration == getattr(self, 'slc_configuration', None): return self.slc_configuration = configuration self.slc_replay = configuration[0] self.slc_control_enabled = self.slc_replay and configuration[1].enabled self.slc_source_floor_ns = max(getattr(self, 'slc_source_floor_ns', 0), now_ns) self.slc_commands.clear() self.slc_command_session = '' self.slc_last_command_id = 0 # Keep the shared event producer and held suppression until physical release. # Only SLC's sourced/issued work predating this admission is retired. def observe_distance_personality(self, CS: car.CarState, now_ns: int) -> None: tracker = getattr(self, 'distance_personality_tracker', None) if tracker is None: return for gesture in tracker.observe(CS): if gesture.button is not Button.DISTANCE: continue key, slot = DISTANCE_GESTURE_KEYS[gesture.press] try: raw, readable = read_saved(self.params, key, 8) except (OSError, TypeError, ValueError): raw, readable = None, False sampled = self.aol_card_intent.settings.distance_actions[slot] if self.aol_card_intent is not None else 0 handled = sampled in (3, 4) or sampled == 9 and self.aol_card_intent is not None and \ self.aol_card_intent.settings.enabled and not self.aol_card_intent.explicit_latch configured = readable and (raw in (b'3', b'4') or ioniq6_long_eligible(self.CP) and raw == b'5') if handled or configured: tracker.claim(gesture, now_ns, key) def state_update(self, *, defer_radar=False) -> tuple[car.CarState, structs.RadarDataT | None]: """carState update loop, driven by can""" can_strs = messaging.drain_sock_raw(self.can_sock, wait_for_one=True) can_received_ns = time.monotonic_ns() can_list = can_capnp_to_list(can_strs) # Update carState from CAN CS = self.CI.update(can_list) from openpilot.starpilot.car.gm.aol import f1_conventional_lateral_owner cancel_boot_ns = (time.clock_gettime_ns(time.CLOCK_BOOTTIME) if (getattr(self.CI.CS, 'ev6_aol_source_healthy', False) or qualified_honda_stock_aol(self.CP, marked_only=True) or qualified_gm(self.CP) and (is_bolt_present_no_acc_pedal_profile(self.CP) or f1_conventional_lateral_owner(self.CP)) and int(self.CP.alternativeExperience) in (32, 96)) else 0) self.observe_ioniq6_long_authority(can_list, CS) media_owner = getattr(self, 'ioniq6_media', None) media_observation = media_owner.update(can_list) if media_owner is not None else None distance_owner = getattr(self, 'gm_distance', None) distance_observation = distance_owner.update(can_list) if distance_owner is not None else None # Direct state_update callers retain eager radar behavior. The production # loop consumes this exact CAN batch after carState and control publication. self._deferred_radar_can = can_list if defer_radar else None RD = None if defer_radar else self.radar_update(can_list) self.sm.update(0) self.update_vehicle_state_context(CS) can_rcv_valid = len(can_strs) > 0 # Check for CAN timeout if not can_rcv_valid: self.can_rcv_cum_timeout_counter += 1 if can_rcv_valid and REPLAY: self.can_log_mono_time = messaging.log_from_bytes(can_strs[0]).logMonoTime self.slc_receipts = [] now_ns = int(self.can_log_mono_time) if REPLAY and hasattr(self, 'can_log_mono_time') else time.monotonic_ns() settings_worker = getattr(self, 'conditional_settings_worker', None) if settings_worker is not None: settings_worker.adopt() self._aol_can_observation = (CS, can_received_ns, max((stamp for stamp, _ in can_list), default=0), cancel_boot_ns) self.refresh_slc_configuration(now_ns) evidence = getattr(self.CI.CS, 'dashboard_limit', None) if self.slc_replay else None observation = evidence.observation if evidence is not None else None floor_ns = getattr(self, 'slc_source_floor_ns', 0) if (observation is not None and floor_ns and (observation.observed_ns <= floor_ns or int(self.sm.logMonoTime['slcState']) <= floor_ns or int(self.sm['slcState'].frameMonoTime) <= floor_ns)): observation = None control_log_ns = int(self.sm.logMonoTime['carControl']) host_enabled = bool( CS.canValid and not CS.canTimeout and self.CC_prev.enabled and self.sm.valid['carControl'] and self.sm.alive['carControl'] and 0 < control_log_ns <= now_ns and now_ns - control_log_ns <= slc_physical.STATE_MAX_AGE_NS and self.sm['carControl'].enabled) host_control_enabled = bool(self.CP.openpilotLongitudinalControl and not self.CP.pcmCruise and host_enabled) host_long_active = host_control_enabled and self.sm['carControl'].longActive slc_long_active = bool(getattr(self, 'slc_control_enabled', self.slc_replay) and self.CP.openpilotLongitudinalControl and host_enabled and (self.sm['carControl'].longActive or CS.gasPressed)) pending = None if self.slc_replay: pending = slc_physical.pending_confirmation( self.sm, now_ns, long_active=slc_long_active, after_ns=floor_ns) approved = (slc_physical.state_current(self.sm, observation, self.slc_producer_session, now_ns) if self.v_cruise_helper.toyota is not None and self.slc_control_enabled else None) if approved is not None and int(approved.frameMonoTime) <= floor_ns: approved = None self.update_cruise_speed(CS, pending, approved=approved) if self.controller_cruise_sock is not None: for _ in range(8): request = messaging.recv_one_or_none(self.controller_cruise_sock) if request is None: break self.controller_cruise_consumer.apply(request, self.CP, CS, self.sm, self.v_cruise_helper, now_ns=now_ns, is_metric=self.is_metric, enabled=bool(host_control_enabled and can_rcv_valid and not self.params.get_bool("SafeMode"))) consumed = self.v_cruise_helper.slc_consumed_button if self.aol_card_intent is not None: if self.aol_process_fault_context is None: self.aol_process_fault_context = AolProcessFaultContext() self.aol_process_fault_context.observe(self.sm, now_ns) if getattr(self, 'aol_transport_feedback', None) is None: self.aol_transport_feedback = TransportPauseFeedback(self.CP) cancel_source_ns = self.aol_cancel_source(CS, now_ns) brake_source_ns = self.aol_honda_brake_source(CS, now_ns) gm_withdrawal_source_ns = self.aol_gm_withdrawal_source(CS, now_ns) hkg_cancel_retained = hkg_cancel_intent_retained(self.CP, CS, self.sm, self.aol_transport_feedback, self.aol_card_intent, cancel_source_ns=cancel_source_ns, now_ns=now_ns, producer=getattr(self, 'slc_producer_session', '')) cancel_feedback = self.aol_transport_feedback if (hkg_cancel_retained and self.CP.carFingerprint in (HYUNDAI_CAR.KIA_EV9, HYUNDAI_CAR.HYUNDAI_IONIQ_5_PE) and cancel_feedback is not None and not cancel_feedback.longitudinal_cancel_ns): # The pure guard proved this exact CCNC raw CANCEL/FULL join. Bind # its first local receipt for the existing bounded withdrawal handshake; # held samples never renew it or any source/native/output deadline. if cancel_source_ns > cancel_feedback.last_cancel_source_ns: cancel_feedback.longitudinal_cancel_ns = cancel_source_ns cancel_feedback.last_cancel_source_ns = cancel_source_ns rivian_brake_retained = rivian_brake_intent_retained(self.CP, CS, self.sm, self.aol_transport_feedback, self.aol_card_intent, now_ns=now_ns, producer=self.slc_producer_session) angle_arm_pending = pending_stock_angle_arm(self.CP, CS, self.sm, self.aol_card_intent, now_ns=now_ns, producer=self.slc_producer_session, acknowledged_arm_ns=getattr(self, '_angle_aol_acknowledged_arm_ns', 0)) transport_reset = not (hkg_cancel_retained or rivian_brake_retained or angle_arm_pending) and self.aol_transport_feedback.observe( self.sm, self.CP, CS, now_ns, latched=self.aol_card_intent.allowed_latch, cancel_source_ns=cancel_source_ns, brake_source_ns=brake_source_ns, gm_withdrawal_source_ns=gm_withdrawal_source_ns, intent_producer_session=getattr(self, 'slc_producer_session', ''), main_arm_ns=getattr(self.aol_card_intent, 'main_arm_ns', 0), startup_calibration_pending=getattr(self.aol_card_intent, 'startup_calibration_pending', False), intent_paused=getattr(self.aol_card_intent, 'pause_lateral', False)) fault_active = True if transport_reset else None event_ns = int(self.sm.logMonoTime['onroadEvents']) if (getattr(self.aol_card_intent, 'requires_fault_observation', self.aol_card_intent.explicit_latch) and self.sm.updated['onroadEvents'] and self.sm.valid['onroadEvents'] and 0 < event_ns <= now_ns and now_ns - event_ns <= 1_500_000_000): feedback = self.aol_transport_feedback # Reuse only this call's healthy observation; fault/physical paths reobserve. reuse_transport = rivian_brake_retained or bool(not transport_reset and not cancel_source_ns and not brake_source_ns and not gm_withdrawal_source_ns and CS.canValid and not CS.canTimeout and not CS.steerFaultTemporary and not CS.steerFaultPermanent and not CS.accFaulted and feedback.signature is not None and feedback.signature[5] == 'none' and not feedback.source_retention_started_ns and not feedback.longitudinal_cancel_ns and not feedback.honda_brake_ns and not feedback.gm_withdrawal_ns and not feedback.failed and not feedback.paused and not feedback.pending_event and not feedback.awaiting and not feedback.qualified and not feedback.retention_started_ns and not feedback.retention_qualified and not feedback.retention_pending and not any(e.immediateDisable or e.softDisable or e.userDisable for e in self.sm['onroadEvents'])) fault_active = transport_reset or self.aol_disarming_fault(CS, event_ns, now_ns, transport_observed_healthy=reuse_transport or angle_arm_pending) angle_aol, ford_aol, honda_aol, mazda_aol, preap_aol, volkswagen_aol = self.aol_permission_owners() nissan_aol = qualified_nissan_aol(self.CP, marked_only=True) rivian_aol = qualified_rivian_aol(self.CP, marked_only=True) permission_owner = angle_aol or ford_aol or honda_aol or mazda_aol or preap_aol or nissan_aol or volkswagen_aol or rivian_aol angle_panda_ready = not permission_owner or self.startup_panda_configured() native = ( current_native(self.sm, self.CP, now_ns=now_ns, axis_session_id=self.aol_axis_session() if permission_owner else None) if self.aol_card_intent.explicit_latch and (permission_owner or self.sm.updated['aolSafetyWire']) else None ) native_reset = False if permission_owner: observe = (rivian_native_observation if rivian_aol else volkswagen_native_observation if volkswagen_aol else nissan_native_observation if nissan_aol else preap_native_observation if preap_aol else mazda_native_observation if mazda_aol else self.aol_honda_native_observation if honda_aol else ford_native_observation if ford_aol else native_observation) restrict = (rivian_temporary_restriction if rivian_aol else volkswagen_temporary_restriction if volkswagen_aol else nissan_temporary_restriction if nissan_aol else preap_temporary_restriction if preap_aol else mazda_temporary_restriction if mazda_aol else honda_temporary_restriction if honda_aol else ford_temporary_restriction if ford_aol else temporary_restriction) preap_observation = {} if preap_aol: cancel_stamp = int(self.CI.CS.preap.cancel_echo_stamp_ns) if cancel_stamp > getattr(self, '_preap_cancel_echo_stamp_ns', 0): self._preap_cancel_echo_stamp_ns = cancel_stamp self._preap_cancel_echo_pending_ns = now_ns preap_observation['pending_cancel_ns'] = getattr(self, '_preap_cancel_echo_pending_ns', 0) pending_since, lost, native_reset = observe( native, latched=self.aol_card_intent.allowed_latch, panda_ready=angle_panda_ready, restricted=(getattr(self.aol_transport_feedback, 'ordinary_warning_qualified', False) or restrict(self.CP, CS) or ((angle_aol or mazda_aol or preap_aol or rivian_aol) and self.aol_card_intent.pause_lateral) or (angle_aol and CS.brakePressed and (not CS.standstill) and (CS.vEgo < self.aol_settings.pause_brake_mps)) or (volkswagen_aol and (self.aol_card_intent.pause_lateral or (CS.brakePressed and (not CS.standstill) and (CS.vEgo < self.aol_card_intent.settings.pause_brake_mps))))), now_ns=now_ns, pending_since_ns=getattr(self, '_angle_aol_pending_since_ns', 0), **preap_observation, ) if (preap_aol and native is not None and preap_observation['pending_cancel_ns'] and native.observedMonoTime >= preap_observation['pending_cancel_ns']): self._preap_cancel_echo_pending_ns = 0 self._angle_aol_pending_since_ns = pending_since if lost: fault_active = True if (angle_aol and native is not None and native.requestedLateral and native.lateralAllowed and 0 < self.aol_card_intent.physical_arm_ns <= native.observedMonoTime): self._angle_aol_acknowledged_arm_ns = self.aol_card_intent.physical_arm_ns rejected = native_reset or (not angle_arm_pending and native_latch_rejected(self.CP, native, state=CS if angle_panda_ready else None)) rejection_ns = now_ns if native_reset else int(native.observedMonoTime) if rejected else 0 observe_physical = getattr(self.aol_card_intent, 'observe_physical_samples', None) if observe_physical is not None: observe_physical(self.CI.CS, now_ns=now_ns) startup_readiness = getattr(self.aol_card_intent, 'observe_startup_readiness', None) if startup_readiness is not None: startup_readiness(self.sm, CS, native=native, panda_ready=angle_panda_ready, now_ns=now_ns, producer=self.slc_producer_session) self.aol_card_intent.update(CS, fault_active=fault_active, now_ns=now_ns, native_rejection_ns=rejection_ns, standard_enabled=(host_enabled if getattr(self.aol_card_intent, 'observe_stock_engagement', False) else host_control_enabled)) # Observe ordinary state directly: independent AOL actuator activity is # not evidence that ordinary preEnabled has become active engagement. engagement_ns = int(self.sm.logMonoTime.get('selfdriveState', 0)) engagement_seen = self.sm.seen.get('selfdriveState', False) engagement = self.sm['selfdriveState'] if engagement_seen else None self.aol_card_intent.observe_full_engagement( CS, enabled=bool(engagement is not None and engagement.enabled), active=bool(engagement is not None and engagement.active), source_ns=engagement_ns, now_ns=now_ns, fault_active=fault_active, native_rejection_ns=rejection_ns, observation_valid=bool(engagement_seen and self.sm.valid['selfdriveState'] and self.sm.alive['selfdriveState'] and 0 < engagement_ns <= now_ns and now_ns - engagement_ns <= AXIS_MAX_AGE_NS)) if self.aol_card_intent is not None and preap_aol: self.CI.CS.preap_lateral_authorized = bool( native is not None and native.lateralAllowed and self.aol_card_intent.allowed_latch and not self.aol_card_intent.pause_lateral and CS.canValid and not CS.canTimeout and CS.gearShifter == structs.CarState.GearShifter.drive and not CS.doorOpen and not CS.steerFaultPermanent and not CS.steeringDisengage) if self.aol_card_intent is not None: self.aol_card_intent.update_auxiliary(CS, media=media_observation, media_eligible=ioniq6_media_eligible(self.CP), fault_active=fault_active) self.observe_aol_calibration(CS, now_ns, host_enabled if getattr(self.aol_card_intent, 'observe_stock_engagement', False) else host_control_enabled) self.observe_distance_personality(CS, now_ns) gm_claim = getattr(self, 'gm_distance_claim_tracker', None) if gm_claim is not None: gm_claim.observe(CS) self.manual_receipt = None self.traffic_receipt = None if (self.conditional_replay and (not REPLAY or hasattr(self, 'can_log_mono_time')) and self.manual_button_tracker is not None and self.manual_settings_owner is not None): # Live Card uses its worker-verified, frame-local view; replay retains # the source-clock owner. Held gestures lose claims on unavailable settings. manual_snapshot = self.manual_settings_owner.refresh(now_ns) manual_drive_id = int(self.sm['deviceState'].startedMonoTime) manual_verdict = self.manual_settings_owner.verdict(manual_snapshot, now_mono_ns=now_ns, drive_id=manual_drive_id) manual_fingerprint = (settings_fingerprint(manual_snapshot, owner=self.manual_settings_owner) if manual_verdict.status == 'ready' and manual_verdict.safe_mode is False and manual_verdict.selection is not None and manual_verdict.selection.choice in (ModeChoice.CEM, ModeChoice.CCM) else None) self.manual_button_tracker.keep_claim(manual_drive_id, manual_fingerprint) shared_media_map = (self.ioniq_media_map.sample(self.params, media_observation, now_ns) if self.ioniq_media_map is not None and ioniq6_media_eligible(self.CP) else None) shared_media_captured = self.ioniq_media_map is not None and ioniq6_media_eligible(self.CP) self.manual_receipt = conditional_manual_candidate( CS, self.manual_button_tracker, self.params, self.manual_settings_owner, self.CP, self.sm, now_ns=now_ns, explicit_aol_latch=bool(self.aol_card_intent is not None and self.aol_card_intent.explicit_latch), media=media_observation, media_buttons=shared_media_map, media_map_captured=shared_media_captured) if self.manual_receipt is not None: gesture, drive_id, fingerprint, _, _ = self.manual_receipt self.manual_button_tracker.claim(gesture, drive_id, fingerprint) gm_buttons = (self.gm_distance_map.sample(self.params, distance_observation, now_ns) if distance_owner is not None else None) if self.traffic_button_tracker is not None: self.traffic_receipt = conditional_traffic_candidate( CS, self.traffic_button_tracker, self.params, self.manual_settings_owner, self.CP, self.sm, now_ns=now_ns, media=media_observation, media_buttons=gm_buttons if distance_owner is not None else shared_media_map, media_map_captured=distance_owner is not None or shared_media_captured, distance=distance_observation) if self.traffic_receipt is not None and self.traffic_receipt[0] and distance_owner is not None and gm_claim is not None: gm_claim.claim(self.traffic_receipt[1], self.traffic_receipt[2], self.traffic_receipt[3]) self.switchback_receipt = None if self.switchback_button_tracker is not None and self.switchback_settings_owner is not None: self.switchback_receipt = conditional_traffic_candidate( CS, self.switchback_button_tracker, self.params, self.switchback_settings_owner, self.CP, self.sm, now_ns=now_ns, media=media_observation, action=7) if consumed is not None: self.slc_receipts.append(('confirmationAccept' if consumed.button == 'accel' else 'confirmationReject', consumed, 0.0, 0.0, now_ns)) curve_receipt = curve_press_receipt(CS, self.v_cruise_helper, self.sm, curve_replay=self.curve_replay, slc_replay=self.slc_replay, host_long_active=host_long_active, observed_ns=now_ns) if curve_receipt is not None: self.slc_receipts.append(curve_receipt) if self.slc_command_sock is not None: for _ in range(8): command_message = messaging.recv_one_or_none(self.slc_command_sock) if command_message is None: break if self.slc_replay and command_message.valid: self.slc_commands.append(command_message) for _ in range(len(self.slc_commands)): command_message = self.slc_commands.popleft() command = command_message.slcCruiseCommand if int(command.issuedMonoTime) <= floor_ns: continue if now_ns < int(command.issuedMonoTime): if now_ns <= int(command.expiresMonoTime): self.slc_commands.append(command_message) continue state = slc_physical.state_current(self.sm, observation, self.slc_producer_session, now_ns) if (state is None or int(self.sm.logMonoTime['slcState']) < int(command.issuedMonoTime)) and now_ns < int(command.expiresMonoTime): self.slc_commands.append(command_message) continue if state is not None and str(state.sessionId) != self.slc_command_session: self.slc_command_session = str(state.sessionId) self.slc_last_command_id = 0 if not self.slc_command_session: self.slc_command_session = str(command.sessionId) if str(command.sessionId) != self.slc_command_session or int(command.commandId) <= self.slc_last_command_id: continue self.slc_last_command_id = int(command.commandId) previous_mps = float(self.v_cruise_helper.v_cruise_kph / 3.6) origin = SlcPendingConfirmation(str(command.sessionId), int(command.decisionId), int(command.presentationId)) owners = {**self.v_cruise_helper.slc_suppression_owner, **self.v_cruise_helper.slc_released_owner} allowed_buttons = {button for button, owner in owners.items() if owner == origin} if int(command.decisionId) > 0 else set() intervening_button = (bool(self.v_cruise_helper.slc_cruise_change) or any(b.type.raw not in allowed_buttons for b in CS.buttonEvents) or any(timer > 0 and button not in allowed_buttons for button, timer in self.v_cruise_helper.button_timers.items())) applicable = slc_physical.command_applicable( command, state, observation, self.slc_producer_session, now_ns, previous_mps, long_active=slc_long_active, button_event=intervening_button) if self.v_cruise_helper.toyota is not None: applicable = applicable and self.v_cruise_helper.toyota.can_apply(float(command.targetMps)) if applicable: self.v_cruise_helper.apply_slc_target(float(command.targetMps), self.is_metric) self.slc_receipts.append(('commandApplied' if applicable else 'commandRejected', command, previous_mps, float(self.v_cruise_helper.v_cruise_kph / 3.6), now_ns)) blocked_keys = set() if self.manual_receipt is not None: blocked_keys.add(key_for(self.manual_receipt[0])) for receipt in (self.traffic_receipt, self.switchback_receipt): if receipt is not None and receipt[0] and receipt[1] is not None: blocked_keys.add(key_for(receipt[1])) self.wheel_commands = self.wheel_publisher.observe( self.params, self.CP, CS, now_ns=now_ns, drive_id=int(self.sm['deviceState'].startedMonoTime), media=media_observation, blocked_keys=blocked_keys) # TODO: mirror the carState.cruiseState struct? CS.vCruise = float(self.v_cruise_helper.v_cruise_kph) CS.vCruiseCluster = float(self.v_cruise_helper.v_cruise_cluster_kph) return CS, RD def update_cruise_speed(self, CS, pending=None, *, approved=None): self.v_cruise_helper.update_v_cruise(CS, self.sm['carControl'].enabled, self.is_metric, pending, slc_enabled=getattr(self, 'slc_control_enabled', getattr(self, 'slc_replay', False)), slc_approved=approved) if self.sm['carControl'].enabled and not self.CC_prev.enabled: # Initialization owns this edge; evaluate commands against its final speed. self.v_cruise_helper.initialize_v_cruise(self.CS_prev, self.experimental_mode, resume=self.vehicle_startup.consume_cruise_resume(), initial_speed=self.initial_cruise_speed) def aol_cancel_source(self, CS, now_ns: int) -> int: """Bind Cancel to this parsed CAN batch and its local monotonic receipt.""" from opendbc.car.hyundai.values import Buttons state = getattr(getattr(self, 'CI', None), 'CS', None) observation = getattr(self, '_aol_can_observation', None) if (state is None or observation is None or observation[0] is not CS or not getattr(state, 'ev6_aol_source_healthy', False)): return 0 _, received_ns, can_source_ns, boot_ns = observation stamp = int(state.ev6_aol_sample_stamp_ns) # Parser source and CAN batch use the producer clock; compare only those. # The fixed intent-retention deadline uses the local MONOTONIC receipt. if (not 0 < received_ns <= now_ns or now_ns - received_ns > 30_000_000 or not 0 < stamp <= can_source_ns <= boot_ns or boot_ns - stamp > min(int(state.ev6_aol_timeout_ns), 30_000_000) or can_source_ns - stamp > min(int(state.ev6_aol_timeout_ns), 30_000_000) or not any(e.type == structs.CarState.ButtonEvent.Type.cancel and e.pressed for e in CS.buttonEvents) or not any(cruise == Buttons.CANCEL for _, _, cruise in state.ev6_aol_samples)): return 0 return received_ns def aol_honda_brake_source(self, CS, now_ns: int) -> int: observation = getattr(self, '_aol_can_observation', None) if (not CS.brakePressed or not qualified_honda_stock_aol(self.CP, marked_only=True) or observation is None or observation[0] is not CS): return 0 from opendbc.car import Bus from opendbc.car.honda.values import HondaFlags parser = self.CI.can_parsers[Bus.pt] message = 'BRAKE_MODULE' if self.CP.flags & HondaFlags.BOSCH_ALT_BRAKE else 'POWERTRAIN_DATA' signals = parser.vl.get(message, {}) # Match the real parser's applied-brake or two-sample switch decision. brake = bool(signals.get('BRAKE_PRESSED', 0) or message == 'POWERTRAIN_DATA' and signals.get('BRAKE_SWITCH', 0) and self.CI.CS.brake_switch_active) stamp = int(parser.ts_nanos.get(message, {}).get('BRAKE_PRESSED', 0)) _, received_ns, can_source_ns, boot_ns = observation # CANParser/CAN batch use BOOTTIME; retain the original MONOTONIC receipt. if (not brake or not 0 < received_ns <= now_ns or now_ns - received_ns > 30_000_000 or not 0 < stamp <= can_source_ns <= boot_ns or boot_ns - stamp > 30_000_000 or can_source_ns - stamp > 30_000_000): return 0 return received_ns def aol_gm_withdrawal_source(self, CS, now_ns: int) -> int: if self.CP.brand != 'gm': return 0 from opendbc.car import Bus from openpilot.starpilot.car.gm.aol import physical_longitudinal_withdrawal_source parser = getattr(getattr(self, 'CI', None), 'can_parsers', {}).get(Bus.pt) return physical_longitudinal_withdrawal_source(self.CP, CS, parser, getattr(self, '_aol_can_observation', None), now_ns) def aol_disarming_fault(self, CS, event_ns: int, now_ns: int, *, transport_observed_healthy=False) -> bool: if not aol_policy_for(self.CP).intent_supported: return True from openpilot.starpilot.aol.vehicle import intent_disarming_fault if getattr(self, 'aol_transport_feedback', None) is None: self.aol_transport_feedback = TransportPauseFeedback(self.CP) hkg_cancel_retained = hkg_cancel_intent_retained(self.CP, CS, self.sm, self.aol_transport_feedback, self.aol_card_intent, cancel_source_ns=self.aol_cancel_source(CS, now_ns), now_ns=now_ns, producer=getattr(self, 'slc_producer_session', '')) if not transport_observed_healthy and not hkg_cancel_retained and self.aol_transport_feedback.observe(self.sm, self.CP, CS, now_ns, latched=getattr(getattr(self, 'aol_card_intent', None), 'allowed_latch', True), cancel_source_ns=self.aol_cancel_source(CS, now_ns), brake_source_ns=self.aol_honda_brake_source(CS, now_ns), gm_withdrawal_source_ns=self.aol_gm_withdrawal_source(CS, now_ns), intent_producer_session=getattr(self, 'slc_producer_session', ''), main_arm_ns=getattr(self.aol_card_intent, 'main_arm_ns', 0), startup_calibration_pending=getattr(self.aol_card_intent, 'startup_calibration_pending', False), intent_paused=getattr(self.aol_card_intent, 'pause_lateral', False)): return True events = self.sm['onroadEvents'] if self.aol_transport_feedback.retention_qualified or self.aol_transport_feedback.retention_pending: from openpilot.starpilot.aol.transport_pause import AVAILABILITY_EVENTS events = [event for event in events if not (event.name in AVAILABILITY_EVENTS and event.softDisable and not event.immediateDisable)] if getattr(self.aol_transport_feedback, 'temporary_host_qualified', False): events = [event for event in events if not (event.name in (log.OnroadEvent.EventName.locationdTemporaryError, log.OnroadEvent.EventName.commIssue) and event.softDisable and not event.immediateDisable)] return intent_disarming_fault(self.CP, events, CS, startup_calibration_pending=bool(getattr(self.aol_card_intent, 'startup_calibration_pending', False) and self.aol_card_intent.settings.enabled and self.aol_card_intent.main_action != 9), temporary_ui_process_failure=self.aol_process_fault_context.temporary_ui_failure(event_ns, now_ns), temporary_selfdrive_lagging=(qualified_gm(self.CP) or aol_policy_for(self.CP).intent_supported), temporary_source_pause=(self.aol_transport_feedback.qualified or self.aol_transport_feedback.awaiting)) def aol_permission_owners(self) -> tuple[bool, bool, bool, bool, bool, bool]: # CP is fixed once the drive starts; these checks ran five times per 100 Hz frame. cached = getattr(self, '_aol_owner_cache', None) if cached is None or cached[0] is not self.CP: owners = (bool(qualified_angle_aol(self.CP, marked_only=True)), bool(qualified_ford_aol(self.CP, marked_only=True)), bool(qualified_honda_stock_aol(self.CP, marked_only=True)), bool(qualified_mazda_aol(self.CP, marked_only=True)), bool(qualified_tesla_preap(self.CP, marked_only=True)), bool(qualified_volkswagen_aol(self.CP, marked_only=True))) cached = self._aol_owner_cache = (self.CP, owners) return cached[1] def aol_honda_native_observation(self, native, *, latched, panda_ready, restricted, now_ns, pending_since_ns): acknowledged = getattr(self, '_honda_aol_acknowledgment', None) key = None host_requested = False if native is not None and qualified_honda_stock_aol(self.CP, marked_only=True): key = (cp_identity(self.CP), str(native.axisSessionId), str(native.pandaSerial), tuple((p.hardwareSerial, p.safetyModel, p.safetyParam) for p in native.pandaInventory)) try: axis = current_axis(self.sm, now_ns=now_ns) feedback = self.aol_transport_feedback host_requested = bool(axis is not None and axis.sessionId == native.axisSessionId and axis.observedMonoTime == self.sm.logMonoTime['aolAxisState'] and axis.validUntilMonoTime == axis.observedMonoTime + AXIS_MAX_AGE_NS and axis.faultSessionId == axis.sessionId and str(axis.faultReason) == 'none' and self.sm.seen['onroadEvents'] and self.sm.valid['onroadEvents'] and self.sm.alive['onroadEvents'] and 0 < axis.faultEventMonoTime == self.sm.logMonoTime['onroadEvents'] <= axis.observedMonoTime and now_ns - axis.faultEventMonoTime <= 1_500_000_000 and not any(event.immediateDisable or event.softDisable for event in self.sm['onroadEvents']) and axis.sequence > 0 and axis.nativeAcknowledged and axis.desiredLateral and axis.lateralActive and axis.intentProducerSessionId == self.slc_producer_session and 0 < axis.sourceCarStateMonoTime <= native.observedMonoTime <= now_ns and now_ns - axis.sourceCarStateMonoTime <= AXIS_MAX_AGE_NS and not feedback.qualified and not feedback.retention_qualified and not feedback.paused and not feedback.awaiting and not feedback.retention_pending) except (AttributeError, KeyError, TypeError, ValueError): host_requested = False previously_acknowledged = bool(acknowledged is not None and acknowledged[0] == key and 0 < acknowledged[1] <= native.observedMonoTime) if native is not None else False if not latched or not panda_ready or key is None: self._honda_aol_acknowledgment = None elif native.requestedLateral and native.lateralAllowed: self._honda_aol_acknowledgment = (key, int(native.observedMonoTime)) elif acknowledged is not None and acknowledged[0] != key: self._honda_aol_acknowledgment = None return honda_native_observation(native, latched=latched, panda_ready=panda_ready, restricted=restricted, now_ns=now_ns, pending_since_ns=pending_since_ns, previously_acknowledged=previously_acknowledged, host_requested_lateral=host_requested) def aol_axis_session(self) -> str: # pandad stamps aolSafetyWire with selfdrived's aolAxisState session, not card's producer session. seen = getattr(self.sm, 'seen', {}) if 'aolAxisState' in seen and seen['aolAxisState'] and self.sm['aolAxisState'].sessionId: return str(self.sm['aolAxisState'].sessionId) return self.slc_producer_session def observe_aol_calibration(self, CS, now_ns: int, standard_enabled: bool) -> None: calibration_events = None event_ns = int(self.sm.logMonoTime['onroadEvents']) if (self.sm.updated['onroadEvents'] and self.sm.valid['onroadEvents'] and 0 < event_ns <= now_ns and now_ns - event_ns <= 1_500_000_000): calibration_events = self.sm['onroadEvents'] self.aol_card_intent.observe_calibration(CS, events=calibration_events, standard_enabled=standard_enabled) def state_publish(self, CS: car.CarState, RD: structs.RadarDataT | None): """carState and carParams publish loop""" # Publish the finalized CP immediately, then retain the segment cadence. if not self.car_params_published or self.sm.frame % int(50. / DT_CTRL) == 0: cp_send = messaging.new_message('carParams') cp_send.valid = True cp_send.carParams = self.CP self.pm.send('carParams', cp_send) self.car_params_published = True # publish new carOutput co_send = messaging.new_message('carOutput') co_send.valid = self.sm.all_checks(['carControl']) co_send.carOutput.actuatorsOutput = self.last_actuators_output self.learning_feedback_publication = None capture = self._feedback_capture(int(co_send.logMonoTime)) if capture is not None: try: self.learning_feedback_publication = capture.publication(co_send) except Exception: capture.invalidate() # Optional observation cannot affect the original CO. self.pm.send('carOutput', co_send) # kick off controlsd step while we actuate the latest carControl packet cs_send = messaging.new_message('carState') if (self.slc_replay or self.curve_replay or getattr(self, 'conditional_replay', False)) and REPLAY and hasattr(self, 'can_log_mono_time'): cs_send.logMonoTime = int(self.can_log_mono_time) cs_send.valid = CS.canValid cs_send.carState = CS manual_tracker = getattr(self, 'manual_button_tracker', None) personality_tracker = getattr(self, 'distance_personality_tracker', None) gm_claim = getattr(self, 'gm_distance_claim_tracker', None) if ((getattr(self, 'conditional_replay', False) and manual_tracker is not None and manual_tracker.suppress_distance_release) or (personality_tracker is not None and personality_tracker.suppress_distance_release) or (gm_claim is not None and gm_claim.suppress_distance_release) or (getattr(self, 'wheel_publisher', None) is not None and self.wheel_publisher.suppress_distance_release)): # This is the serialized carState copy. Keep the original parsed state # and raw CAN intact for the controller and diagnostics. cs_send.carState.buttonEvents = [event for event in CS.buttonEvents if not (event.type == car.CarState.ButtonEvent.Type.gapAdjustCruise and not event.pressed)] cs_send.carState.canErrorCounter = self.can_rcv_cum_timeout_counter cs_send.carState.cumLagMs = -self.rk.remaining * 1000. if self.aol_replay: self.aol_sequence += 1 intent_msg = messaging.new_message('aolIntentWire', 0) intent_msg.logMonoTime = int(cs_send.logMonoTime) intent_msg.valid = bool(cs_send.valid and self.aol_qualified) allowed_latch = pause_lateral = pause_longitudinal = False lateral_armed = False if self.aol_card_intent is not None: allowed_latch, pause_lateral, pause_longitudinal = self.aol_card_intent.output(CS) lateral_armed = self.aol_card_intent.allowed_latch intent_msg.aolIntentWire = encode_intent(IntentState( self.slc_producer_session, self.aol_sequence, int(cs_send.logMonoTime), int(cs_send.logMonoTime), int(cs_send.logMonoTime) + 200_000_000, allowed_latch, pause_lateral, pause_longitudinal, self.aol_qualified, lateral_armed, bool(getattr(self.aol_card_intent, "optional_set_release_policy", lambda: False)()))) self.pm.send('aolIntentWire', intent_msg) # carState wakes selfdrived. Commit its companion intent first, so a # consumer scheduled at the wakeup cannot sample the preceding intent. if self.CP.brand == 'tesla': from openpilot.starpilot.car.tesla.stock_events import publish_stock_status internal = getattr(self.CI.CS, 'preap', None) if internal is not None: self.slc_cruise_event_id += 1 publish_stock_status(self.pm, self.CP, internal, session=self.slc_producer_session, sequence=self.slc_cruise_event_id, car_state_stamp=int(cs_send.logMonoTime), valid=bool(cs_send.valid and not CS.canTimeout)) companion = None if self.CP.brand == 'tesla' and self.CP.openpilotLongitudinalControl and qualified_tesla_preap(self.CP): internal = self.CI.CS.preap companion = messaging.new_message('starpilotCarState', valid=bool(cs_send.valid), logMonoTime=int(cs_send.logMonoTime)) companion.starpilotCarState.sourceCarStateMonoTime = int(cs_send.logMonoTime) companion.starpilotCarState.pedalLongActive = bool(internal.enableLongControl) companion.starpilotCarState.pedalMaxRegen = internal.pccEvent == 'pedalMaxRegen' steering_authority = getattr(self.CI.CS, 'steering_authority', None) if self.CP.brand == 'gm' and steering_authority is not None and steering_authority.enabled: companion = messaging.new_message('starpilotCarState', valid=bool(cs_send.valid), logMonoTime=int(cs_send.logMonoTime)) companion.starpilotCarState.lateralAuthorityUnavailable = bool(steering_authority.latched) companion.starpilotCarState.sourceCarStateMonoTime = int(cs_send.logMonoTime) if getattr(self, 'learning_applied_evidence', None) is not None: try: from openpilot.starpilot.flm.evidence_hooks import applied_message # Commit the optional child only after it is complete. A failed writer # must not publish partial trust or discard existing GM authority/GPS. learning_companion = (companion.as_reader().as_builder() if companion is not None else messaging.new_message('starpilotCarState', valid=bool(cs_send.valid), logMonoTime=int(cs_send.logMonoTime))) if applied_message(learning_companion, self.learning_applied_evidence) is not True: raise ValueError('optional applied evidence write failed') companion = learning_companion except Exception: self._clear_learning_evidence() publication = getattr(self, 'learning_feedback_publication', None) if publication is not None: try: from openpilot.starpilot.flm.feedback_wire import write_publication feedback_companion = (companion.as_reader().as_builder() if companion is not None else messaging.new_message('starpilotCarState', valid=bool(cs_send.valid), logMonoTime=int(cs_send.logMonoTime))) write_publication(feedback_companion.starpilotCarState, publication) companion = feedback_companion except Exception: self.learning_feedback_publication = None if self.CP.brand == 'mazda': from openpilot.starpilot.car.mazda.dashboard import compose companion = compose(self.CP, self.CI.CS, CS, companion, car_state_stamp=int(cs_send.logMonoTime)) if self.v_cruise_helper.toyota is not None: if companion is None: companion = messaging.new_message('starpilotCarState', valid=bool(cs_send.valid), logMonoTime=int(cs_send.logMonoTime)) companion.starpilotCarState.sourceCarStateMonoTime = int(cs_send.logMonoTime) self.v_cruise_helper.toyota.publish(companion.starpilotCarState.toyotaCruise) self.car_gps_publisher.update(self.CI.CS, self.pm, companion) self.pm.send('carState', cs_send) if getattr(self, 'learning_stamp_observer', None) is not None: self.learning_car_state_source_ns = int(cs_send.logMonoTime) wheel = getattr(self, 'wheel_publisher', None) if wheel is not None: wheel.publish(self.wheel_commands, self.CP, self.pm, now_ns=int(cs_send.logMonoTime), drive_id=int(self.sm['deviceState'].startedMonoTime), source_car_ns=int(cs_send.logMonoTime), source_control_ns=int(self.sm.logMonoTime['carControl'])) if self.slc_replay: source = messaging.new_message('slcDashboardObservation') source.logMonoTime = int(cs_send.logMonoTime) source.valid = bool(cs_send.valid) evidence = getattr(self.CI.CS, 'dashboard_limit', None) source.slcDashboardObservation.producerSessionId = self.slc_producer_session source.slcDashboardObservation.carStateLogMonoTime = int(cs_send.logMonoTime) if evidence is not None: observation = evidence.observation source.slcDashboardObservation.status = observation.status.value source.slcDashboardObservation.speedMps = observation.speed_mps source.slcDashboardObservation.observedMonoTime = observation.observed_ns source.slcDashboardObservation.validUntilMonoTime = observation.valid_until_ns source.slcDashboardObservation.episode = observation.episode self.pm.send('slcDashboardObservation', source) change = self.v_cruise_helper.slc_cruise_change if self.slc_replay or self.curve_replay: if change is not None: self.slc_receipts.append(('driverChange', change, change[0], change[1], int(cs_send.logMonoTime))) for kind, detail, previous_mps, selected_mps, observed_ns in self.slc_receipts: self.slc_cruise_event_id += 1 event = messaging.new_message('slcCruiseEvent') event.logMonoTime = int(cs_send.logMonoTime) event.valid = bool(cs_send.valid) record = event.slcCruiseEvent record.eventId = self.slc_cruise_event_id record.producerSessionId = self.slc_producer_session record.observedMonoTime = observed_ns record.kind = kind record.previousMps = previous_mps record.selectedMps = selected_mps if kind == 'driverChange': assert isinstance(detail, tuple) and len(detail) == 4 record.button, record.longPress = detail[2], detail[3] elif kind in ('confirmationAccept', 'confirmationReject'): record.button = detail.button record.sessionId = detail.pending.session_id record.decisionId = detail.pending.decision_id record.presentationId = detail.pending.presentation_id elif kind == 'curveAccelPress': record.button = 'accel' else: record.sessionId = str(detail.sessionId) record.commandId = int(detail.commandId) record.decisionId = int(detail.decisionId) record.presentationId = int(detail.presentationId) self.pm.send('slcCruiseEvent', event) if getattr(self, 'conditional_replay', False) and self.manual_receipt is not None: gesture, drive_id, fingerprint, choice, observed_ns = self.manual_receipt self.slc_cruise_event_id += 1 self.manual_event_sequence += 1 event = messaging.new_message('slcCruiseEvent') event.logMonoTime = int(cs_send.logMonoTime) event.valid = bool(cs_send.valid) record = event.slcCruiseEvent record.eventId = self.slc_cruise_event_id record.producerSessionId = self.slc_producer_session record.observedMonoTime = observed_ns record.kind = 'conditionalMode' record.manualMode = { 'version': 1, 'sessionId': self.slc_producer_session, 'sequence': self.manual_event_sequence, 'observedMonoTime': observed_ns, 'driveStartMonoTime': drive_id, 'settingsFingerprint': fingerprint, 'choice': 'conditionalExperimental' if choice is ModeChoice.CEM else 'conditionalChill', 'button': gesture.button.value, 'press': gesture.press.value, 'sourceCarStateMonoTime': int(cs_send.logMonoTime), 'validUntilMonoTime': int(cs_send.logMonoTime) + 100_000_000, } self.pm.send('slcCruiseEvent', event) self.publish_traffic_receipt(cs_send) self.radar_publish(RD) def radar_update(self, can_list): RD = self.RI.update(can_list) return RD def radar_publish(self, RD): if RD is not None: tracks_msg = messaging.new_message('radarTracks') tracks_msg.valid = not any(RD.errors.to_dict().values()) tracks_msg.radarTracks = RD self.pm.send('radarTracks', tracks_msg) def publish_traffic_receipt(self, cs_send): traffic_receipt = getattr(self, 'traffic_receipt', None) if getattr(self, 'conditional_replay', False) and traffic_receipt is not None: toggle, gesture, drive_id, fingerprint, map_fingerprint, source_epoch, source_boot_ns, observed_ns = traffic_receipt self.slc_cruise_event_id += 1 self.traffic_event_sequence += 1 event = messaging.new_message('slcCruiseEvent') event.logMonoTime = int(cs_send.logMonoTime) event.valid = bool(cs_send.valid) record = event.slcCruiseEvent record.eventId = self.slc_cruise_event_id record.producerSessionId = self.slc_producer_session record.observedMonoTime = observed_ns record.kind = 'trafficMode' record.trafficMode = { 'version': 1, 'sessionId': self.slc_producer_session, 'sequence': self.traffic_event_sequence, 'observedMonoTime': observed_ns, 'driveStartMonoTime': drive_id, 'settingsFingerprint': fingerprint, 'buttonMapFingerprint': map_fingerprint, 'sourceEpoch': source_epoch, 'sourceBootTime': source_boot_ns, 'sourceCarStateMonoTime': int(cs_send.logMonoTime), 'validUntilMonoTime': int(cs_send.logMonoTime) + 100_000_000, 'toggle': toggle, 'button': gesture.button.value if gesture is not None else 'unknown', 'press': gesture.press.value if gesture is not None else 'unknown', } self.pm.send('slcCruiseEvent', event) switchback_receipt = getattr(self, 'switchback_receipt', None) if getattr(self, 'switchback_capable', False) and switchback_receipt is not None: toggle, gesture, drive_id, fingerprint, map_fingerprint, source_epoch, source_boot_ns, observed_ns = switchback_receipt self.slc_cruise_event_id += 1 self.switchback_event_sequence += 1 event = messaging.new_message('slcCruiseEvent') event.logMonoTime = int(cs_send.logMonoTime) event.valid = bool(cs_send.valid) record = event.slcCruiseEvent record.eventId = self.slc_cruise_event_id record.producerSessionId = self.slc_producer_session record.observedMonoTime = observed_ns record.kind = 'switchbackMode' record.trafficMode = { 'version': 1, 'sessionId': self.slc_producer_session, 'sequence': self.switchback_event_sequence, 'observedMonoTime': observed_ns, 'driveStartMonoTime': drive_id, 'settingsFingerprint': fingerprint, 'buttonMapFingerprint': map_fingerprint, 'sourceEpoch': source_epoch, 'sourceBootTime': source_boot_ns, 'sourceCarStateMonoTime': int(cs_send.logMonoTime), 'validUntilMonoTime': int(cs_send.logMonoTime) + 100_000_000, 'toggle': toggle, 'button': gesture.button.value if gesture is not None else 'unknown', 'press': gesture.press.value if gesture is not None else 'unknown', } self.pm.send('slcCruiseEvent', event) def ioniq6_post_handoff_sources_current(self): """A parser's invalid grace is not proof of post-transaction warmup.""" from opendbc.can.parser import MAX_BAD_COUNTER floor = getattr(self, 'ioniq6_handoff_source_floor_ns', 0) if floor <= 0: return False now = time.clock_gettime_ns(time.CLOCK_BOOTTIME) sources = [source for parser in self.CI.can_parsers.values() for source in parser.message_states.values() if not source.ignore_alive] return bool(sources) and all( source.timestamps and floor < source.timestamps[-1] <= now and now - source.timestamps[-1] <= source.timeout_threshold and source.counter_fail < MAX_BAD_COUNTER for source in sources) def observe_ioniq6_long_authority(self, can_list, CS): if getattr(self, 'ioniq6_long_selected', False) and self.ioniq6_long_lost: CS.canValid = False return if not self.ioniq6_long_prearmed: return if any((src == 1 and address in (0x1A0, 0x1BA, 0x1E5)) or (src == 0 and address == 0x100) for _, packet in can_list for address, _, src in packet): self.ioniq6_long_lost = True if self.ioniq6_host_warmed: if not CS.canValid: self.ioniq6_long_lost = True elif CS.canValid and self.ioniq6_post_handoff_sources_current(): self.ioniq6_host_warmed = True if self.ioniq6_long_lost: CS.canValid = False if self.ioniq6_keepalive is not None: self.ioniq6_keepalive.stop() def ioniq6_panda_matches(self): panda_states = self.sm['pandaStates'] return bool( self.sm.valid['pandaStates'] and self.sm.alive['pandaStates'] and len(panda_states) >= len(self.CP.safetyConfigs) and all(ps.safetyModel == config.safetyModel and ps.safetyParam == config.safetyParam and ps.alternativeExperience == self.CP.alternativeExperience and not ps.safetyRxChecksInvalid for ps, config in zip(panda_states, self.CP.safetyConfigs, strict=False)) ) def ioniq6_panda_diagnostic_ready(self): if not self.sm.valid['pandaStates'] or not self.sm.alive['pandaStates']: return False panda_states = self.sm['pandaStates'] return bool( len(panda_states) == len(self.CP.safetyConfigs) and all(ps.safetyModel == structs.CarParams.SafetyModel.elm327 and ps.safetyParam in (0, 1) and not ps.controlsAllowed for ps in panda_states) ) def update_vehicle_state_context(self, state): holder = getattr(self, 'vehicle_startup', None) if holder is None or holder.owner is None: return # Python controls and receive times use MONOTONIC. Panda/CAN producers # use BOOTTIME, which can diverge after suspend. clocks = clock_pair_ns() mono_ns, boot_ns = clocks if clocks is not None else (0, 0) sm = self.sm control_stamp = int(sm.logMonoTime['carControl']) control_receipt = int(sm.recv_time['carControl'] * 1e9) panda_stamp = int(sm.logMonoTime['pandaStates']) panda_receipt = int(sm.recv_time['pandaStates'] * 1e9) control_current = bool(clocks is not None and sm.seen['carControl'] and sm.valid['carControl'] and sm.alive['carControl'] and 0 < control_stamp <= mono_ns and mono_ns - control_stamp <= 150_000_000 and 0 < control_receipt <= mono_ns and mono_ns - control_receipt <= 150_000_000) panda_current = bool(clocks is not None and sm.seen['pandaStates'] and sm.valid['pandaStates'] and sm.alive['pandaStates'] and 0 < panda_stamp <= boot_ns and boot_ns - panda_stamp <= 300_000_000 and 0 < panda_receipt <= mono_ns and mono_ns - panda_receipt <= 300_000_000) holder.after_state(ci=self.CI, state=state, now_ns=boot_ns, control_enabled=bool(sm['carControl'].enabled), control_current=control_current, pandas=sm['pandaStates'], panda_log_ns=panda_stamp, panda_recv_ns=panda_receipt + boot_ns - mono_ns if panda_current else 0, panda_current=panda_current) def startup_diagnostic_admission(self): from openpilot.starpilot.aol.panda_faults import blocking_fault_list self.sm.update(0) now, boot = time.monotonic_ns(), time.clock_gettime_ns(time.CLOCK_BOOTTIME) sm = self.sm stamp, receipt = int(sm.logMonoTime['pandaStates']), int(sm.recv_time['pandaStates'] * 1e9) pandas = sm['pandaStates'] owner = getattr(self.vehicle_startup, 'owner', None) count = 2 if isinstance(owner, CapturedADRVStartup) and hyundai_canfd_config_index(owner.cp) == 1 else 1 ignition_pandas = pandas if (count == 2 and len(pandas) == 2 and pandas[0].pandaType == log.PandaState.PandaType.dos and pandas[1].pandaType == log.PandaState.PandaType.redPanda): # pandad clears the internal DOS ignition line in this exact topology. # The external captured-ADRV owner must still observe vehicle ignition. ignition_pandas = (pandas[1],) return bool(not self.params.get_bool('IsOffroad') and not self.params.get_bool('ControlsReady') and sm.seen['pandaStates'] and sm.valid['pandaStates'] and sm.alive['pandaStates'] and 0 < stamp <= boot and boot - stamp <= 300_000_000 and 0 < receipt <= now and now - receipt <= 300_000_000 and len(pandas) == count and all(ps.safetyModel == structs.CarParams.SafetyModel.elm327 and ps.safetyParam == 1 and not ps.controlsAllowed and not ps.safetyRxChecksInvalid and not blocking_fault_list(getattr(ps, 'faults', None), getattr(owner, 'cp', None)) for ps in pandas) and all(ps.ignitionLine or ps.ignitionCan for ps in ignition_pandas)) def startup_panda_configured(self, *, inactive_keepalive=False): # Inactive keepalives carry no axis authority during the first native RX health tick. now, boot = time.monotonic_ns(), time.clock_gettime_ns(time.CLOCK_BOOTTIME) sm = self.sm stamp, receipt = int(sm.logMonoTime['pandaStates']), int(sm.recv_time['pandaStates'] * 1e9) pandas = sm['pandaStates'] return bool(sm.seen['pandaStates'] and sm.valid['pandaStates'] and sm.alive['pandaStates'] and 0 < stamp <= boot and boot - stamp <= 300_000_000 and 0 < receipt <= now and now - receipt <= 300_000_000 and len(pandas) == len(self.CP.safetyConfigs) and all(ps.safetyModel == cfg.safetyModel and ps.safetyParam == cfg.safetyParam and ps.alternativeExperience == self.CP.alternativeExperience and (not ps.safetyRxChecksInvalid or inactive_keepalive and self.volt_startup_keepalive()) for ps, cfg in zip(pandas, self.CP.safetyConfigs, strict=True))) def _feedback_capture(self, now_ns): # Default OFF performs no feedback-module import, digest or witness allocation. try: source = self.sm['autoSteeringLearning'].learning.sourceReceipt if not source.enabled or source.manual: self.learning_feedback_capture = None return None from openpilot.starpilot.flm.feedback_observation import card_capture_for return card_capture_for(self, now_ns) except Exception: self.learning_feedback_capture = None return None def _invalidate_feedback_output(self, *, assignment=False): capture = getattr(self, 'learning_feedback_capture', None) if capture is not None: try: if assignment: capture.begin_assignment() else: capture.invalidate() except Exception: self.learning_feedback_capture = None def _clear_learning_evidence(self): self._invalidate_feedback_output() self.learning_feedback_publication = None self.learning_stamp_observer = None self.learning_applied_evidence = self.learning_previous_applied_evidence = None self.learning_send_mono_ns = self.learning_send_committed_mono_ns = 0 self.learning_car_state_source_ns = 0 def record_learning_send_stamp(self, observed_ns): # Metadata only; no permission, coordinate conversion, or extra clock sample. self.learning_send_mono_ns = int(observed_ns) def _observe_learning_send_stamp(self, observed_ns): try: observer = getattr(self, 'learning_stamp_observer', None) if observer is not None: observer(observed_ns) except Exception: self._clear_learning_evidence() def publish_sendcan(self, frames, valid=True): steering_authority = getattr(getattr(getattr(self, 'CI', None), 'CS', None), 'steering_authority', None) monitored_steering = (getattr(getattr(self, 'CP', None), 'brand', None) == 'gm' and steering_authority is not None and steering_authority.enabled) observer=getattr(self,'learning_stamp_observer',None) self.learning_send_mono_ns=self.learning_send_committed_mono_ns=0 if observer is None: packet = can_list_to_can_capnp(frames, msgtype='sendcan', valid=valid) else: packet = can_list_to_can_capnp(frames, msgtype='sendcan', valid=valid,source_observer=self._observe_learning_send_stamp) if getattr(self, 'volt_cc_selected', False): # CAN producer/parser and pandad send age use BOOTTIME; Python controls/device envelopes use MONOTONIC. builder = messaging.log_from_bytes(packet).as_builder() builder.logMonoTime = int(getattr(self, 'volt_cc_now_boot_ns', 0)) packet = builder.to_bytes() elif self.volt_startup_keepalive() or monitored_steering: builder = messaging.log_from_bytes(packet).as_builder() builder.logMonoTime = time.clock_gettime_ns(time.CLOCK_BOOTTIME) packet = builder.to_bytes() if self.vehicle_startup.owner is not None: with self.vehicle_startup.send_lock: self.pm.send('sendcan', packet) # Successful send acknowledgment is outside the sender lock, before any join. self.vehicle_startup.sent(frames, valid=valid) elif self.ioniq6_long_prearmed and self.ioniq6_keepalive is not None: # The diagnostic sender and the normal controller share one PubSocket. with self.ioniq6_keepalive.lock: self.pm.send('sendcan', packet) else: self.pm.send('sendcan', packet) self.learning_send_committed_mono_ns=self.learning_send_mono_ns def volt_cc_control_current(self): pair = clock_pair_ns() if pair is None or REPLAY: self.volt_cc_boot_offset_ns = None return False now_ns, boot_ns = pair offset = boot_ns - now_ns if self.volt_cc_boot_offset_ns is None or abs(offset - self.volt_cc_boot_offset_ns) > CLOCK_PAIR_MAX_SKEW_NS: self.volt_cc_boot_offset_ns = offset self.volt_cc_source_floor_ns = now_ns return False sm = self.sm if not all(sm.seen[name] and sm.alive[name] and sm.valid[name] for name in ('carControl', 'deviceState')): return False drive_id = int(sm['deviceState'].startedMonoTime) if not sm['deviceState'].started or drive_id <= 0: return False for name, limit in (('carControl', 150_000_000), ('deviceState', 2_000_000_000)): source_ns, receipt_ns = int(sm.logMonoTime[name]), int(sm.recv_time[name] * 1e9) if (not max(drive_id, self.volt_cc_source_floor_ns) < source_ns <= receipt_ns <= now_ns or now_ns - source_ns > limit or now_ns - receipt_ns > limit): return False self.volt_cc_drive_id = drive_id self.volt_cc_now_boot_ns = boot_ns self.volt_cc_now_mono_ns = now_ns return True def volt_sdgm_startup_keepalive(self): if getattr(getattr(self, 'CP', None), 'brand', None) != 'gm': return False return (is_volt_sdgm_profile(self.CP, longitudinal=True) and self.CP.safetyConfigs[0].safetyParam == 0x5007) def volt_startup_keepalive(self): if getattr(getattr(self, 'CP', None), 'brand', None) != 'gm': return False return (self.volt_sdgm_startup_keepalive() or self.CP.alternativeExperience == 32 and not self.CP.flags & GMFlags.PEDAL_LONG and (is_volt_ascm_longitudinal(self.CP) or self.CP.carFingerprint == GM_CAR.CHEVROLET_VOLT_ASCM and self.CP.pcmCruise and not self.CP.openpilotLongitudinalControl and gm_lane_centering_supported(self.CP))) def ev6_startup_keepalive(self): from opendbc.car.hyundai.ev6_startup import EV6Startup, required from opendbc.car.hyundai.ecu_startup import Outcome owner = getattr(getattr(self, 'vehicle_startup', None), 'owner', None) return (isinstance(owner, EV6Startup) and owner.outcome in (Outcome.PENDING, Outcome.SENT_UNCONFIRMED) and owner.ready and owner.published and owner.ci is self.CI and not owner.closed and required(self.CP)) def classic_startup_keepalive(self): from opendbc.car.hyundai.g90_startup import G90Startup from opendbc.car.hyundai.classic_long_aol import qualified from opendbc.car.hyundai.legacy_long_aol import qualified as legacy_qualified from opendbc.car.hyundai.ecu_startup import Outcome owner = getattr(getattr(self, 'vehicle_startup', None), 'owner', None) return (isinstance(owner, G90Startup) and owner.outcome in (Outcome.PENDING, Outcome.SENT_UNCONFIRMED) and owner.ready and owner.published and owner.ci is self.CI and not owner.closed and (qualified(self.CP) or legacy_qualified(self.CP))) def torque_ev_startup_keepalive(self): from opendbc.car.hyundai.torque_ev_startup import TorqueEVStartup, required from opendbc.car.hyundai.ecu_startup import Outcome owner = getattr(getattr(self, 'vehicle_startup', None), 'owner', None) return (isinstance(owner, TorqueEVStartup) and owner.outcome in (Outcome.PENDING, Outcome.SENT_UNCONFIRMED) and owner.ready and owner.published and owner.ci is self.CI and not owner.closed and required(self.CP)) def mixed_startup_keepalive(self): from opendbc.car.hyundai.blended_longitudinal import BlendedStartup owner = getattr(getattr(self, 'vehicle_startup', None), 'owner', None) from opendbc.car.hyundai.ioniq5pe_startup import PEPendingStartup return (isinstance(owner, (BlendedStartup, PEPendingStartup)) and owner.owner.published and not owner.closed and (owner.pending_target or owner.owner.active())) def prepare_pe_restoration(self): from opendbc.car.hyundai.ioniq5pe_startup import PEPendingStartup owner = getattr(getattr(self, 'vehicle_startup', None), 'owner', None) if not isinstance(owner, PEPendingStartup) or not (owner.session_attempted or owner.takeover_attempted) or owner.restored: return True owner.quiesce() self.params.put_bool('ControlsReady', False) deadline = time.monotonic() + 1. while time.monotonic() < deadline: if self.startup_diagnostic_admission(): return True time.sleep(.01) return False def controls_update(self, CS: car.CarState, CC: car.CarControl, *, initialize_only=False): """control update loop, driven by carControl""" if initialize_only: ev6_startup = self.ev6_startup_keepalive() classic_startup = self.classic_startup_keepalive() or self.torque_ev_startup_keepalive() mixed_startup = self.mixed_startup_keepalive() now_ns = time.monotonic_ns() source_ns = int(self.sm.logMonoTime['carControl']) receipt_ns = int(self.sm.recv_time['carControl'] * 1e9) # Disabled torque/accel are commands; inactive curvature is only telemetry. if (not (mixed_startup or ev6_startup or classic_startup or (self.aol_replay and native_bootstrap_supported(self.CP) and self.vehicle_startup.owner is None)) or not CS.canValid or CS.canTimeout or not (self.sm.seen['carControl'] and self.sm.valid['carControl'] and self.sm.alive['carControl']) or not (0 < source_ns <= now_ns and now_ns - source_ns <= 150_000_000 and 0 < receipt_ns <= now_ns and now_ns - receipt_ns <= 150_000_000) or CC.enabled or CC.latActive or CC.longActive or CC.actuators.torque != 0.0 or CC.actuators.accel != 0.0): return if (ev6_startup or classic_startup) and not self.vehicle_startup.owner.prepared_for(self.CP): return if getattr(self, 'volt_cc_selected', False): if not self.volt_cc_control_current(): self.publish_sendcan([], valid=False) return physical = getattr(self.CI.CS, 'volt_cc_physical', None) if physical is None or any(source - self.volt_cc_boot_offset_ns <= max(self.volt_cc_drive_id, self.volt_cc_source_floor_ns) for source in (physical.observed_ns, *physical.source_ns)): self.publish_sendcan([], valid=False) return if physical.button_credit_ns - self.volt_cc_boot_offset_ns <= max(self.volt_cc_drive_id, self.volt_cc_source_floor_ns): self.CI.CC.volt_cc_consumed_source_ns = physical.button_credit_ns self.CI.CC.volt_cc_metric = self.is_metric if getattr(self.CI.CC, "bolt_cc_profile", False): self.CI.CC.bolt_cc_metric = self.is_metric if (initialize_only and self.mixed_startup_keepalive() and self.vehicle_startup.owner.pending_target): # Fresh disabled controls alone are insufficient: finish the host's # selfdriveInitializing phase before suppressing factory ADAS output. return if not self.ci_initialized: # Keep stock ECU output intact until CAN and controls are ready. if (not CS.canValid or CS.canTimeout or not self.sm.valid['carControl'] or not self.sm.all_alive(['carControl'])): self.publish_sendcan([], valid=False) return mixed = self.vehicle_startup.owner if self.mixed_startup_keepalive() else None from opendbc.car.hyundai.g90_startup import G90Startup from opendbc.car.hyundai.torque_ev_startup import TorqueEVStartup from opendbc.car.hyundai.ev6_startup import EV6Startup from opendbc.car.hyundai.ecu_startup import Outcome classic = self.vehicle_startup.owner if isinstance(classic, (G90Startup, TorqueEVStartup, EV6Startup)) and classic.outcome is Outcome.PENDING: now = time.monotonic_ns() source = int(self.sm.logMonoTime['carControl']) receipt = int(self.sm.recv_time['carControl'] * 1e9) current = bool(self.sm.seen['carControl'] and self.sm.alive['carControl'] and self.sm.valid['carControl'] and 0 < source <= now and now - source <= 150_000_000 and 0 < receipt <= now and now - receipt <= 150_000_000) inactive = not (CC.enabled or CC.latActive or CC.longActive or CC.actuators.torque != 0.0 or CC.actuators.accel != 0.0) sources = classic.sources_current(self.CI, time.clock_gettime_ns(time.CLOCK_BOOTTIME)) if not current or not inactive or not sources or CS.accFaulted or CS.cruiseState.enabled or not self.startup_diagnostic_admission(): self.publish_sendcan([], valid=False) return classic.commit_pending(self.CI, sources_current=sources, control_current=current) elif mixed is not None and mixed.pending_target: now = time.monotonic_ns() current = (self.sm.seen['carControl'] and self.sm.alive['carControl'] and self.sm.valid['carControl'] and 0 < int(self.sm.logMonoTime['carControl']) <= now and now - int(self.sm.logMonoTime['carControl']) <= 150_000_000 and 0 < int(self.sm.recv_time['carControl'] * 1e9) <= now and now - int(self.sm.recv_time['carControl'] * 1e9) <= 150_000_000) inactive = not (CC.enabled or CC.latActive or CC.longActive or CC.actuators.torque != 0.0 or CC.actuators.accel != 0.0) sources = mixed.sources_current(self.CI, time.clock_gettime_ns(time.CLOCK_BOOTTIME)) if not current or not inactive or not sources: self.publish_sendcan([], valid=False) return mixed.commit_pending(self.CI, sources_current=sources, control_current=current) elif getattr(self, 'ioniq6_long_pending', False): # Panda is still in its diagnostic safety mode here. Take over only # after the control producer is ready, then request Hyundai safety. if not self.ioniq6_panda_diagnostic_ready(): self.publish_sendcan([], valid=False) return handoff_started = time.monotonic() if handoff_started - self.ioniq6_last_preflight_at < 0.5: self.publish_sendcan([], valid=False) return self.ioniq6_last_preflight_at = handoff_started try: self.ioniq6_handoff_result = confirm_ioniq6_prepared_takeover(*self.can_callbacks) except Exception: cloudlog.exception("Ioniq 6 takeover transaction failed") self.ioniq6_handoff_result = None result = self.ioniq6_handoff_result cloudlog.event('ioniq6_long_handoff', outcome=result.outcome.name if result is not None else 'EXCEPTION', reason=result.reason if result is not None else 'transaction_exception', elapsed_s=round(time.monotonic() - handoff_started, 3)) if result is not None and result.outcome is HandoffOutcome.NOT_READY: self.publish_sendcan([], valid=False) return self.ioniq6_long_pending = False if result is None or result.outcome is not HandoffOutcome.CONFIRMED: self.ioniq6_long_lost = True self.ci_initialized = True CS.canValid = False self.publish_sendcan([], valid=False) return self.ioniq6_long_prearmed = True # The transaction consumed Card's CAN cursor. Rewarm the host parser # from post-handoff independent inputs before its first controller TX. self.ioniq6_host_warmed = False self.ioniq6_handoff_source_floor_ns = time.clock_gettime_ns(time.CLOCK_BOOTTIME) self.ioniq6_restore_callbacks = self.can_callbacks elif self.vehicle_startup.owner is None: self.CI.init(self.CP, *self.can_callbacks) # signal pandad to switch to car safety mode self.params.put_bool("ControlsReady", True) self.ci_initialized = True if initialize_only: if (ev6_startup or classic_startup) and any(ps.faults for ps in self.sm['pandaStates']): return if mixed_startup: owner = self.vehicle_startup.owner now_boot_ns = time.clock_gettime_ns(time.CLOCK_BOOTTIME) if (not owner.pending_target and self.startup_panda_configured() and owner.sources_current(self.CI, now_boot_ns)): inactive = car.CarControl.new_message().as_reader() self._invalidate_feedback_output(assignment=True) self.last_actuators_output, can_sends = self.CI.apply(inactive, now_boot_ns) self.publish_sendcan(can_sends, valid=CS.canValid) return if (((isinstance(self.CI, GMInterface) and self.volt_startup_keepalive()) or ev6_startup or classic_startup) and self.startup_panda_configured(inactive_keepalive=True)): inactive = car.CarControl.new_message().as_reader() now_boot_ns = time.clock_gettime_ns(time.CLOCK_BOOTTIME) if ev6_startup or classic_startup: now_mono_ns = time.monotonic_ns() source_ns = int(self.sm.logMonoTime['carControl']) receipt_ns = int(self.sm.recv_time['carControl'] * 1e9) current = bool(0 < source_ns <= now_mono_ns and now_mono_ns - source_ns <= 150_000_000 and 0 < receipt_ns <= now_mono_ns and now_mono_ns - receipt_ns <= 150_000_000) if not self.vehicle_startup.before_control(configured=True, ci=self.CI, now_ns=now_boot_ns, control_current=current): self.publish_sendcan([], valid=False) return self._invalidate_feedback_output(assignment=True) self.last_actuators_output, can_sends = self.CI.apply(inactive, now_boot_ns) else: self._invalidate_feedback_output(assignment=True) self.last_actuators_output, can_sends = cast(GMInterface, self.CI).apply(inactive, now_boot_ns, startup_keepalive=True) self.publish_sendcan(can_sends, valid=CS.canValid) return self.vehicle_startup.check() now_ns = time.monotonic_ns() control_current = bool(self.sm.seen['carControl'] and self.sm.alive['carControl'] and self.sm.valid['carControl'] and 0 < int(self.sm.logMonoTime['carControl']) <= now_ns and now_ns - int(self.sm.logMonoTime['carControl']) <= 150_000_000 and 0 < int(self.sm.recv_time['carControl'] * 1e9) <= now_ns and now_ns - int(self.sm.recv_time['carControl'] * 1e9) <= 150_000_000) if self.vehicle_startup.owner is not None and not self.vehicle_startup.before_control( configured=self.startup_panda_configured(), ci=self.CI, now_ns=time.clock_gettime_ns(time.CLOCK_BOOTTIME), control_current=control_current): self.publish_sendcan([], valid=False) return if getattr(self, 'ioniq6_long_selected', False) and not self.ioniq6_long_prearmed: CS.canValid = False self.publish_sendcan([], valid=False) return if self.ioniq6_long_prearmed: panda_matches = self.ioniq6_panda_matches() if not panda_matches and self.ioniq6_panda_armed: self.ioniq6_long_lost = True self.ioniq6_panda_armed |= panda_matches if not panda_matches or not self.ioniq6_host_warmed or self.ioniq6_long_lost or not CS.canValid: self.publish_sendcan([], valid=False) return if self.sm.valid['carControl'] and self.sm.all_alive(['carControl']): if self.mixed_startup_keepalive() and not self.vehicle_startup.owner.handed_off: # The first admitted replacement cycle is always neutral; the actual # send receipt, not reaching apply(), transfers diagnostic continuity. CC = car.CarControl.new_message().as_reader() if self.ioniq6_long_prearmed and self.ioniq6_keepalive is not None: self.ioniq6_keepalive.stop() if self.ioniq6_long_lost: self.publish_sendcan([], valid=False) return steering_authority = getattr(self.CI.CS, 'steering_authority', None) monitored_steering = self.CP.brand == 'gm' and steering_authority is not None and steering_authority.enabled # send car controls over can now_nanos = (self.volt_cc_now_boot_ns if getattr(self, 'volt_cc_selected', False) else self.can_log_mono_time if REPLAY else time.clock_gettime_ns(time.CLOCK_BOOTTIME) if self.volt_startup_keepalive() or monitored_steering else int(time.monotonic() * 1e9)) learning_snapshot = None try: from openpilot.starpilot.flm.evidence_hooks import begin_applied_frame, applied_snapshot begin_applied_frame(self) learning_snapshot = applied_snapshot except Exception: self._clear_learning_evidence() self._invalidate_feedback_output(assignment=True) if self.CP.brand == "subaru": from openpilot.starpilot.car.subaru.software_cruise_caller import update as update_subaru_software_cruise update_subaru_software_cruise(self, CS, CC, now_nanos) from openpilot.starpilot.car.subaru.sng_caller import update as update_subaru_sng update_subaru_sng(self, CS, CC, now_nanos) from openpilot.starpilot.car.subaru.stop_start_caller import update as update_subaru_startup update_subaru_startup(self, now_nanos) self.last_actuators_output, can_sends = self.CI.apply(CC, now_nanos) self.publish_sendcan(can_sends, valid=CS.canValid) if learning_snapshot is not None and self.learning_send_committed_mono_ns: try: self.learning_applied_evidence = learning_snapshot(self, CC, CS, self.last_actuators_output, apply_ns=self.learning_send_committed_mono_ns, state_ns=self.learning_car_state_source_ns) capture = getattr(self, 'learning_feedback_capture', None) if capture is not None: try: capture.committed(self.last_actuators_output, self.learning_applied_evidence) except Exception: self.learning_feedback_capture = None # Keep the original applied evidence. except Exception: self._clear_learning_evidence() self.CC_prev = CC def step(self): CS, RD = self.state_update(defer_radar=True) if self.vehicle_startup.owner is not None: self.vehicle_startup.maintain(configured=self.startup_panda_configured()) self.state_publish(CS, RD) initialized = (not any(e.name == EventName.selfdriveInitializing for e in self.sm['onroadEvents']) and self.sm.seen['onroadEvents']) if not self.CP.passive and initialized: self.controls_update(CS, self.sm['carControl']) elif ((not self.ci_initialized or self.volt_startup_keepalive() or self.ev6_startup_keepalive() or self.classic_startup_keepalive() or self.torque_ev_startup_keepalive() or self.mixed_startup_keepalive()) and self.sm.seen['onroadEvents'] and any(e.name == EventName.selfdriveInitializing for e in self.sm['onroadEvents']) and ((self.aol_replay and native_bootstrap_supported(self.CP)) or self.ev6_startup_keepalive() or self.classic_startup_keepalive() or self.torque_ev_startup_keepalive() or self.mixed_startup_keepalive())): self.controls_update(CS, self.sm['carControl'], initialize_only=True) radar_can = self._deferred_radar_can self._deferred_radar_can = None self.radar_publish(self.radar_update(radar_can)) self.initialized_prev = initialized self.CS_prev = CS def configure_conditional_settings_worker(self): self.conditional_settings_worker = None if not REPLAY and (getattr(self, 'manual_settings_owner', None) is not None or getattr(self, 'switchback_settings_owner', None) is not None): worker = CardSettingsWorker(self.params) self.conditional_settings_worker = worker if getattr(self, 'manual_settings_owner', None) is not None: self.manual_settings_owner = worker.view if getattr(self, 'switchback_settings_owner', None) is not None: self.switchback_settings_owner = worker.view def params_thread(self, evt): next_cruise_read = 0.0 while not evt.is_set(): settings_worker = getattr(self, 'conditional_settings_worker', None) if settings_worker is not None: settings_worker.refresh(time.monotonic_ns()) self.is_metric = self.params.get_bool("IsMetric") self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl self.initial_cruise_speed = self.params.get("InitialCruiseSpeedKph", return_default=True) if self.aol_card_intent is not None: self.aol_card_intent.settings = read_settings(self.params) now = time.monotonic() if now >= next_cruise_read: self.slc_requested_configuration = self.read_slc_configuration() self.v_cruise_helper.intervals = read_cruise_intervals(self.params, self.CP) pitch_input = getattr(self.CI.CC, 'long_pitch_input', None) if pitch_input is not None: self.CI.CC.long_pitch = pitch_input.update(time.monotonic_ns()) next_cruise_read = now + 1.0 time.sleep(0.1) def card_thread(self): self.configure_conditional_settings_worker() e = threading.Event() t = threading.Thread(target=self.params_thread, args=(e, )) try: t.start() while True: self.vehicle_startup.check() self.step() self.rk.monitor_time() finally: e.set() t.join() if self.ioniq6_keepalive is not None: self.ioniq6_long_lost = True self.ioniq6_keepalive.stop() def main(): config_realtime_process(4, Priority.CTRL_HIGH) # Retain the partially initialized object so its diagnostic worker can be # stopped even if construction fails after a confirmed ECU handoff. car = Car.__new__(Car) try: Car.__init__(car) car.card_thread() finally: if getattr(car, 'vehicle_startup', None) is not None: try: car.prepare_pe_restoration() finally: car.vehicle_startup.close() if getattr(car, 'ioniq6_keepalive', None) is not None: car.ioniq6_long_lost = True car.ioniq6_keepalive.stop() if getattr(car, 'ioniq6_long_prearmed', False) and not getattr(car, 'ioniq6_restore_attempted', False): # This Card is no longer an output owner, even if it had published a # qualified CP. Restore stock ECU traffic before a later Card starts. # Do not mutate the already published CP or claim stock on failure. callbacks = getattr(car, 'ioniq6_restore_callbacks', None) if callbacks is not None: car.ioniq6_restore_attempted = True try: restored = restore_ioniq6_adas(*callbacks) except Exception: restored = False if not restored: cloudlog.error("Ioniq 6 Card exited; stock SCC restoration unverified") if __name__ == "__main__": main()