From 4e0942a55317b50680b50596f5d401eba077211b Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Mon, 9 Nov 2026 23:59:52 -0600 Subject: [PATCH] Restore Ioniq 6 Alpha Long compatibility across camera and external Panda layouts --- .../opendbc/car/hyundai/canfd_stock_aol.py | 7 +- .../opendbc/car/hyundai/carcontroller.py | 3 +- opendbc_repo/opendbc/car/hyundai/carstate.py | 10 +- opendbc_repo/opendbc/car/hyundai/interface.py | 3 + .../hyundai/tests/test_ioniq6_camera_long.py | 187 ++++++++++++++++++ .../ui/layouts/settings/developer.py | 6 +- .../selfdrive/ui/layouts/settings/toggles.py | 3 +- .../ui/mici/layouts/settings/developer.py | 6 +- openpilot/starpilot/car/hyundai/aol.py | 18 +- .../starpilot/ui/feature_settings_owner.py | 4 +- prebuilt.json | 2 +- 11 files changed, 225 insertions(+), 24 deletions(-) create mode 100644 opendbc_repo/opendbc/car/hyundai/tests/test_ioniq6_camera_long.py diff --git a/opendbc_repo/opendbc/car/hyundai/canfd_stock_aol.py b/opendbc_repo/opendbc/car/hyundai/canfd_stock_aol.py index 7e94dbd309..7094a01cdc 100644 --- a/opendbc_repo/opendbc/car/hyundai/canfd_stock_aol.py +++ b/opendbc_repo/opendbc/car/hyundai/canfd_stock_aol.py @@ -52,7 +52,6 @@ def _base_word(cp, *, experiences=(0,)): return None if ( cp.brand != 'hyundai' - or factory == CAR.HYUNDAI_IONIQ_6 or not factory.config.flags & HyundaiFlags.CANFD or factory.config.flags & (HyundaiFlags.CANFD_ANGLE_STEERING | HyundaiFlags.NON_SCC) or cp.passive @@ -85,6 +84,8 @@ def _base_word(cp, *, experiences=(0,)): word |= 0x0400 if factory in CARNIVAL_CARS and alt_buttons: word |= 0x2000 + if factory == CAR.HYUNDAI_IONIQ_6 and word != 0x09: + return None return word if word | STOCK_AOL_MARKER in STOCK_AOL_WORDS else None @@ -100,7 +101,7 @@ CARNIVAL_LONG_AOL_WORD = 0x28B4 LONG_AOL_WORDS = frozenset((CARNIVAL_LONG_AOL_WORD, 0x080D, 0x0815, 0x0895, 0x2815, 0x2895)) | SPORTAGE_LONG_AOL_WORDS LONG_EV_CARS = frozenset((CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_KONA_EV_2ND_GEN)) INDEPENDENT_LONG_BUTTON_CARS = LONG_EV_CARS | { - CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.KIA_CARNIVAL_2025, + CAR.HYUNDAI_IONIQ_6, CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.KIA_CARNIVAL_2025, } @@ -115,7 +116,7 @@ def qualified_long(cp, *, marked_only=False): if not eligible(cp): return False word = _base_word(cp, experiences=(0, 32)) - if cp.carFingerprint == CAR.GENESIS_GV60_EV_1ST_GEN: + if cp.carFingerprint in (CAR.GENESIS_GV60_EV_1ST_GEN, CAR.HYUNDAI_IONIQ_6): if word != 0x09: return False elif cp.carFingerprint == CAR.KIA_SPORTAGE_5TH_GEN: diff --git a/opendbc_repo/opendbc/car/hyundai/carcontroller.py b/opendbc_repo/opendbc/car/hyundai/carcontroller.py index 64530be63d..3eb6242b6f 100644 --- a/opendbc_repo/opendbc/car/hyundai/carcontroller.py +++ b/opendbc_repo/opendbc/car/hyundai/carcontroller.py @@ -138,7 +138,8 @@ class CarController(CarControllerBase): self.last_ccnc_now_ns = 0 self.ccnc_baselined = False self.ioniq6_longitudinal = (Ioniq6LongitudinalPolicy() - if CP.carFingerprint == CAR.HYUNDAI_IONIQ_6 and CP.openpilotLongitudinalControl else None) + if CP.carFingerprint == CAR.HYUNDAI_IONIQ_6 and CP.openpilotLongitudinalControl and + int(CP.safetyConfigs[-1].safetyParam) in (0x8015, 0x8095, 0x8815, 0x8895) else None) self.pe_longitudinal = PELongitudinalPolicy() if pe_long_qualified(CP) else None self.pe_startup = None self.ev9_longitudinal = EV9LongitudinalPolicy() if ev9_long_qualified(CP) else None diff --git a/opendbc_repo/opendbc/car/hyundai/carstate.py b/opendbc_repo/opendbc/car/hyundai/carstate.py index afcef43400..9e9a9b4b30 100644 --- a/opendbc_repo/opendbc/car/hyundai/carstate.py +++ b/opendbc_repo/opendbc/car/hyundai/carstate.py @@ -685,17 +685,19 @@ class CarState(CarStateBase): if CP.flags & HyundaiFlags.CANFD_LKA_STEER_MSG: cam_msgs.append(("CAM_0x362" if CP.flags & HyundaiFlags.CANFD_LKA_STEER_MSG_ALT else "CAM_0x2a4", 20)) elif CP.carFingerprint == CAR.HYUNDAI_IONIQ_6 and CP.openpilotLongitudinalControl: - # The acknowledged ADAS takeover removes stock SCC from E-CAN. Require - # every independent native input and the matching camera source instead. + # Both topologies require genuine chassis inputs; only HDA-II uses + # the camera template after acknowledged ADAS takeover. msgs.extend((("ACCELERATOR", 100), ("TCS", 50), ("WHEEL_SPEEDS", 100), ("MDPS", 100))) msgs.extend((("CRUISE_BUTTONS_ALT", math.nan), ("DOORS_SEATBELTS", math.nan), ("STEERING_SENSORS", math.nan), ("BLINKERS", math.nan))) - cam_msgs.append(("CAM_0x362" if CP.flags & HyundaiFlags.CANFD_LKA_STEER_MSG_ALT else "CAM_0x2a4", 20)) + if CP.flags & HyundaiFlags.CANFD_LKA_STEER_MSG: + cam_msgs.append(("CAM_0x362" if CP.flags & HyundaiFlags.CANFD_LKA_STEER_MSG_ALT else "CAM_0x2a4", 20)) elif CP.carFingerprint == CAR.HYUNDAI_IONIQ_6 and not CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS: # Stock standard-button Ioniq 6 reads alternate DISTANCE_UNIT too, but # that frame may be absent. Subscribe before the lazy lookup makes it # required; existing native and CarState safety inputs remain required. - msgs.append(("CRUISE_BUTTONS_ALT", math.nan)) + if not any(name == "CRUISE_BUTTONS_ALT" for name, _ in msgs): + msgs.append(("CRUISE_BUTTONS_ALT", math.nan)) from opendbc.car.hyundai.ev6_startup import eligible as ev6_eligible from opendbc.car.hyundai.gv70_startup import eligible as gv70_eligible if ev6_eligible(CP) or gv70_eligible(CP): diff --git a/opendbc_repo/opendbc/car/hyundai/interface.py b/opendbc_repo/opendbc/car/hyundai/interface.py index 73532156f8..927e50195f 100644 --- a/opendbc_repo/opendbc/car/hyundai/interface.py +++ b/opendbc_repo/opendbc/car/hyundai/interface.py @@ -142,6 +142,9 @@ class CarInterface(CarInterfaceBase): CAN = CanBus(None, fingerprint, lka_steering) ret.alphaLongitudinalAvailable = not (ret.flags & HyundaiFlags.CANFD_NO_RADAR_DISABLE) + if (candidate == CAR.HYUNDAI_IONIQ_6 and not lka_steering and + fingerprint[CAN.CAM].get(0x1A0) == 32 and fingerprint[CAN.ECAN].get(0x1CF) == 8): + ret.alphaLongitudinalAvailable = True if ret.flags & HyundaiFlags.CANFD_ANGLE_STEERING: # These manual angle profiles retain stock SCC in both builds. ret.alphaLongitudinalAvailable = False diff --git a/opendbc_repo/opendbc/car/hyundai/tests/test_ioniq6_camera_long.py b/opendbc_repo/opendbc/car/hyundai/tests/test_ioniq6_camera_long.py new file mode 100644 index 0000000000..394c9181ac --- /dev/null +++ b/opendbc_repo/opendbc/car/hyundai/tests/test_ioniq6_camera_long.py @@ -0,0 +1,187 @@ +from tempfile import TemporaryDirectory +from types import SimpleNamespace +from unittest.mock import Mock, patch + +import pytest + +from opendbc.car import Bus, gen_empty_fingerprint, structs +from opendbc.car.hyundai.interface import CarInterface +from opendbc.car.hyundai.carcontroller import CarController +from opendbc.car.hyundai.canfd_stock_aol import qualified, qualified_long +from opendbc.car.hyundai.hyundaicanfd import CanBus +from opendbc.car.hyundai.tests.test_ioniq6_handoff import ioniq6_profiles +from opendbc.car.hyundai.values import CAR, DBC +from opendbc.can import CANPacker +from opendbc.safety.tests.libsafety import libsafety_py +from opendbc.safety.tests import test_hyundai_gv60_hda1_long_aol as native_fixture +from openpilot.common.params import Params +from openpilot.starpilot.car.hyundai.aol import policy_for, native_accepts_cp +from openpilot.selfdrive.ui.layouts.settings import toggles + + +def camera_params(offset=0, alpha=False): + fp = gen_empty_fingerprint() + fp[offset].update({0x1CF: 8, 0x35: 32, 0x175: 24, 0xA0: 24, 0xEA: 24, 0x1BA: 24, 0x1E5: 16}) + fp[offset + 2].update({0x12A: 16, 0x1A0: 32}) + fp[offset + 1].update({0x110: 32, 0x100: 24}) + return CarInterface.get_params(CAR.HYUNDAI_IONIQ_6, fp, [], alpha, False, False) + + +@pytest.mark.parametrize('offset', (0, 4)) +def test_camera_scc_factory_offers_long_without_hda2_takeover(offset): + stock = camera_params(offset) + cp = camera_params(offset, True) + assert stock.alphaLongitudinalAvailable, 'HDA1_ALPHA_CAPABILITY_MISSING' + assert not stock.openpilotLongitudinalControl and stock.pcmCruise + assert cp.alphaLongitudinalAvailable and cp.openpilotLongitudinalControl and not cp.pcmCruise + assert cp.steerControlType == structs.CarParams.SteerControlType.torque + assert cp.safetyConfigs[-1].safetyParam == 0x0D + assert CanBus(cp).ECAN == offset + assert CarController(DBC[cp.carFingerprint], cp).ioniq6_longitudinal is None + with patch('opendbc.car.hyundai.interface.disable_ecu') as disable: + CarInterface.init(cp, Mock(), Mock()) + disable.assert_not_called() + for value in (stock, cp): + policy = policy_for(value) + assert policy.runtime_supported and policy.explicit_latch + value.safetyConfigs[-1].safetyParam |= policy.safety_param_addition + value.alternativeExperience |= policy.alternative_experience_addition + word = 0x80D if value.openpilotLongitudinalControl else 0x809 + assert value.safetyConfigs[-1].safetyParam == word + assert native_accepts_cp(value, int(structs.CarParams.SafetyModel.hyundaiCanfd), word) + assert not native_accepts_cp(value, int(structs.CarParams.SafetyModel.hyundai), word) + assert not native_accepts_cp(value, int(structs.CarParams.SafetyModel.hyundaiCanfd), word ^ 16) + + +@pytest.mark.parametrize('offset', (0, 4)) +def test_external_hda2_settings_explain_opt_in_without_claiming_stock_acc_authority(offset): + _, stock, _ = ioniq6_profiles(bus_offset=offset, alternate=True) + with TemporaryDirectory() as root: + params = Params(root) + params.put_bool('ExperimentalMode', True, block=True) + class Toggle: + def __init__(self): + self.action_item = self + self.description = '' + def set_enabled(self, enabled): + self.enabled = enabled + def set_description(self, description): + self.description = description + def set_state(self, state): + self.state = state + panel = toggles.TogglesLayout.__new__(toggles.TogglesLayout) + panel._params = params + panel._is_release = False + panel._initial_cruise_speed = Mock() + panel._long_personality_setting = SimpleNamespace(action_item=Toggle()) + panel._toggles = {'ExperimentalMode': Toggle(), 'IsRhdDetected': Toggle()} + panel._toggle_defs = {} + panel._locked_toggles = set() + panel._update_experimental_mode_icon = Mock() + panel._slc_offsets = SimpleNamespace(_raw=lambda key: None) + panel._localization = SimpleNamespace(snapshot=lambda: SimpleNamespace(rows=(None, SimpleNamespace(source=None, available=True)))) + ui = SimpleNamespace(CP=stock, has_longitudinal_control=False, update_params=lambda: None) + with patch.object(toggles, 'ui_state', ui): + panel._update_toggles() + assert 'Enable the openpilot longitudinal control (alpha)' in panel._toggles['ExperimentalMode'].description + assert not panel._toggles['ExperimentalMode'].enabled + assert params.get_bool('ExperimentalMode') + + +@pytest.mark.parametrize('offset', (0, 4)) +def test_camera_long_actual_controller_packets_use_existing_native_contract(offset): + cp = camera_params(offset, True) + assert cp.openpilotLongitudinalControl + cp.safetyConfigs[-1].safetyParam |= 0x800 + cp.alternativeExperience = 32 + ci = CarInterface(cp) + packer = CANPacker(DBC[cp.carFingerprint][Bus.pt]) + bus = CanBus(cp) + frames = [packer.make_can_msg(name, bus.ECAN, values) for name, values in ( + ('ACCELERATOR', {'GEAR': 5}), ('TCS', {'ACCEnable': 0}), ('WHEEL_SPEEDS', {}), ('MDPS', {}), + ('CRUISE_BUTTONS', {'COUNTER': 1}), ('DOORS_SEATBELTS', {'DRIVER_SEATBELT': 1}), ('STEERING_SENSORS', {}))] + ci.update([(1_000_000_000, frames)]) + case = native_fixture.TestGv60Hda1LongAol(methodName='runTest') + case.setUp() + try: + case.mode(0x80D) + if case.release: + assert not case.safety.safety_config_valid() + return + case.healthy(0x80D, omit='CRUISE_BUTTONS') + case.safety.set_aol_test_heartbeat(True) + case.safety.aol_set_host_request(3) + for tick, counter, button in ((0, 13, 0), (1, 14, 2), (2, 0, 0)): + case.safety.set_timer(1_000_000 + tick * 20_000) + assert case.safety.safety_rx_hook(case.packet(packer.make_can_msg('CRUISE_BUTTONS', 0, {'COUNTER': counter, 'CRUISE_BUTTONS': button}))) + case.safety.aol_set_host_request(3) + assert case.safety.aol_get_permission_mask() == 3 + cc = structs.CarControl(enabled=True, latActive=True, longActive=True) + cc.actuators.torque = .01 + cc.actuators.accel = .1 + cc.actuators.longControlState = structs.CarControl.Actuators.LongControlState.pid + controller = CarController(DBC[cp.carFingerprint], cp) + assert controller.ioniq6_longitudinal is None + _, sent = controller.update(cc.as_reader(), ci.CS, 1_000_000_000) + assert any(frame[0] == 0x1A0 for frame in sent) + assert not any(frame[0] == 0x730 for frame in sent) + for frame in sent: + assert frame[2] in (offset, offset + 1, offset + 2) + packet = libsafety_py.make_CANPacket(frame[0], frame[2] - offset, frame[1]) + assert case.safety.safety_tx_hook(packet), (hex(frame[0]), frame[2]) + finally: + case.tearDown() + + +@pytest.mark.parametrize('mutation', ('aux_word', 'wrong_model', 'angle', 'lka', 'passive')) +def test_camera_admission_never_borrows_other_topologies(mutation): + cp = camera_params(4, True) + cp.safetyConfigs[-1].safetyParam |= 0x800 + cp.alternativeExperience = 32 + if mutation == 'aux_word': + cp.safetyConfigs[0].safetyParam = 1 + elif mutation == 'wrong_model': + cp.safetyConfigs[-1].safetyModel = structs.CarParams.SafetyModel.hyundai + elif mutation == 'angle': + cp.steerControlType = structs.CarParams.SteerControlType.angle + elif mutation == 'lka': + cp.flags |= 1 + else: + cp.passive = True + assert not qualified(cp) and not qualified_long(cp) + assert not policy_for(cp).runtime_supported + + +@pytest.mark.parametrize('offset', (0, 4)) +def test_galaxy_exposes_external_stock_alpha_request_and_preserves_choice(offset): + from openpilot.starpilot.ui.feature_settings_owner import FeatureSettingsOwner + _, stock, _ = ioniq6_profiles(bus_offset=offset, alternate=True) + with TemporaryDirectory() as root: + params = Params(root) + params.put_bool('IsReleaseBranch', False, block=True) + params.put_bool('AlphaLongitudinalEnabled', True, block=True) + stock.radarUnavailable = True + owner = FeatureSettingsOwner(params, lambda scope: True, + vehicle_params=lambda: stock, vehicle_fingerprint=lambda: str(stock.carFingerprint), galaxy=lambda: True) + assert owner._alpha_long_capability() is not None + rows = owner._alpha_long_rows(True) + assert len(rows) == 1 and rows[0].key == 'AlphaLongitudinalEnabled' + assert params.get_bool('AlphaLongitudinalEnabled') + stock.passive = True + assert owner._alpha_long_capability() is None + assert params.get_bool('AlphaLongitudinalEnabled') + + +@pytest.mark.parametrize('offset', (0, 4)) +def test_cached_radar_discovery_can_save_request_but_cannot_grant_long_or_aol(offset): + from opendbc.car.hyundai.ioniq6_handoff import build_ioniq6_hda2_long_candidate + from openpilot.starpilot.car.hyundai.aol import ioniq6_alpha_long_available, ioniq6_settings_capable + fingerprint, stock, _ = ioniq6_profiles(bus_offset=offset, alternate=True) + stock.radarUnavailable = True + assert ioniq6_alpha_long_available(stock), 'CACHED_EXTERNAL_RADAR_BLOCKS_ALPHA_REQUEST' + assert not ioniq6_settings_capable(stock) + assert not policy_for(stock).runtime_supported + assert build_ioniq6_hda2_long_candidate(stock, fingerprint) is None + assert not stock.openpilotLongitudinalControl and stock.pcmCruise + stock.safetyConfigs[-1].safetyParam ^= 16 + assert not ioniq6_alpha_long_available(stock) diff --git a/openpilot/selfdrive/ui/layouts/settings/developer.py b/openpilot/selfdrive/ui/layouts/settings/developer.py index 9b8e8a96c8..6eeba62ad1 100644 --- a/openpilot/selfdrive/ui/layouts/settings/developer.py +++ b/openpilot/selfdrive/ui/layouts/settings/developer.py @@ -1,7 +1,7 @@ from collections.abc import Callable from openpilot.common.params import Params -from openpilot.starpilot.car.hyundai.aol import ioniq6_settings_capable +from openpilot.starpilot.car.hyundai.aol import ioniq6_alpha_long_available from openpilot.starpilot.car.ford.longitudinal_preferences import release_long_available as ford_release_long_available from opendbc.car.volkswagen.meb_modes import MEB_CARS, settings_capable as meb_settings_capable from openpilot.starpilot.car.volkswagen.preferences import stock_long_requested @@ -144,7 +144,7 @@ class DeveloperLayout(Widget): # CP gating if ui_state.CP is not None: alpha_avail = (meb_settings_capable(ui_state.CP) if ui_state.CP.brand == 'volkswagen' and ui_state.CP.carFingerprint in MEB_CARS - else ui_state.CP.alphaLongitudinalAvailable or ioniq6_settings_capable(ui_state.CP)) + else ui_state.CP.alphaLongitudinalAvailable or ioniq6_alpha_long_available(ui_state.CP)) if not alpha_avail or (self._is_release and not (meb_settings_capable(ui_state.CP) or ford_release_long_available(ui_state.CP))): self._alpha_long_toggle.set_visible(False) else: @@ -216,7 +216,7 @@ class DeveloperLayout(Widget): if result == DialogResult.CONFIRM: if (not ui_state.is_offroad() or ui_state.CP is None or not (meb_settings_capable(ui_state.CP) if ui_state.CP.brand == 'volkswagen' and ui_state.CP.carFingerprint in MEB_CARS - else ui_state.CP.alphaLongitudinalAvailable or ioniq6_settings_capable(ui_state.CP)) or + else ui_state.CP.alphaLongitudinalAvailable or ioniq6_alpha_long_available(ui_state.CP)) or self._is_release and not (meb_settings_capable(ui_state.CP) or ford_release_long_available(ui_state.CP))): self._alpha_long_toggle.action_item.set_state(False) return diff --git a/openpilot/selfdrive/ui/layouts/settings/toggles.py b/openpilot/selfdrive/ui/layouts/settings/toggles.py index 638776a2d7..af98a0f476 100644 --- a/openpilot/selfdrive/ui/layouts/settings/toggles.py +++ b/openpilot/selfdrive/ui/layouts/settings/toggles.py @@ -1,3 +1,4 @@ +from openpilot.starpilot.car.hyundai.aol import ioniq6_alpha_long_available from openpilot.starpilot.car.ford.longitudinal_preferences import release_long_available as ford_release_long_available from openpilot.cereal import log from openpilot.common.params import Params, UnknownKeyName @@ -196,7 +197,7 @@ class TogglesLayout(Widget): unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.") long_desc = unavailable + " " + tr("openpilot longitudinal control may come in a future update.") - if ui_state.CP.alphaLongitudinalAvailable: + if ui_state.CP.alphaLongitudinalAvailable or ioniq6_alpha_long_available(ui_state.CP): if self._is_release and not ford_release_long_available(ui_state.CP): long_desc = unavailable + " " + tr("An alpha version of openpilot longitudinal control can be tested, along with " + "Experimental mode, on non-release branches.") diff --git a/openpilot/selfdrive/ui/mici/layouts/settings/developer.py b/openpilot/selfdrive/ui/mici/layouts/settings/developer.py index 61c1901317..e1a1b1a959 100644 --- a/openpilot/selfdrive/ui/mici/layouts/settings/developer.py +++ b/openpilot/selfdrive/ui/mici/layouts/settings/developer.py @@ -7,7 +7,7 @@ from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigInputDialog from openpilot.system.ui.lib.application import gui_app from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.starpilot.car.hyundai.aol import ioniq6_settings_capable +from openpilot.starpilot.car.hyundai.aol import ioniq6_alpha_long_available from openpilot.starpilot.car.ford.longitudinal_preferences import release_long_available as ford_release_long_available from opendbc.car.volkswagen.meb_modes import MEB_CARS, settings_capable as meb_settings_capable from openpilot.starpilot.car.volkswagen.preferences import stock_long_requested @@ -177,7 +177,7 @@ class DeveloperLayoutMici(NavScroller): # CP gating if ui_state.CP is not None: alpha_avail = (meb_settings_capable(ui_state.CP) if ui_state.CP.brand == 'volkswagen' and ui_state.CP.carFingerprint in MEB_CARS - else ui_state.CP.alphaLongitudinalAvailable or ioniq6_settings_capable(ui_state.CP)) + else ui_state.CP.alphaLongitudinalAvailable or ioniq6_alpha_long_available(ui_state.CP)) if not alpha_avail or (ui_state.is_release and not (meb_settings_capable(ui_state.CP) or ford_release_long_available(ui_state.CP))): self._alpha_long_toggle.set_visible(False) else: @@ -201,7 +201,7 @@ class DeveloperLayoutMici(NavScroller): def do_toggle(_state: bool): if (not ui_state.is_offroad() or (_state and (ui_state.CP is None or not (meb_settings_capable(ui_state.CP) if ui_state.CP.brand == 'volkswagen' and ui_state.CP.carFingerprint in MEB_CARS - else ui_state.CP.alphaLongitudinalAvailable or ioniq6_settings_capable(ui_state.CP)) or + else ui_state.CP.alphaLongitudinalAvailable or ioniq6_alpha_long_available(ui_state.CP)) or ui_state.is_release and not (meb_settings_capable(ui_state.CP) or ford_release_long_available(ui_state.CP))))): self._update_toggles() return diff --git a/openpilot/starpilot/car/hyundai/aol.py b/openpilot/starpilot/car/hyundai/aol.py index 9ffcf4c859..b6c095e17b 100644 --- a/openpilot/starpilot/car/hyundai/aol.py +++ b/openpilot/starpilot/car/hyundai/aol.py @@ -45,11 +45,11 @@ _REQUIRED_FLAGS = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_ _FORBIDDEN_FLAGS = int(HyundaiFlags.CANFD_ALT_BUTTONS | HyundaiFlags.CANFD_ANGLE_STEERING | HyundaiFlags.CANFD_CAMERA_SCC) -def _ioniq6_hda2_base(CP) -> bool: +def _ioniq6_hda2_base(CP, *, require_radar=True) -> bool: if ( str(CP.carFingerprint) != str(HyundaiCAR.HYUNDAI_IONIQ_6) or CP.brand != 'hyundai' - or CP.radarUnavailable + or (require_radar and CP.radarUnavailable) or CP.notCar or CP.passive or CP.dashcamOnly @@ -61,8 +61,8 @@ def _ioniq6_hda2_base(CP) -> bool: return flags & _REQUIRED_FLAGS == _REQUIRED_FLAGS and not flags & _FORBIDDEN_FLAGS -def _stock_ioniq6(CP, *, marked_only=False) -> bool: - if not _ioniq6_hda2_base(CP) or CP.openpilotLongitudinalControl or not CP.pcmCruise: +def _stock_ioniq6(CP, *, marked_only=False, require_radar=True) -> bool: + if not _ioniq6_hda2_base(CP, require_radar=require_radar) or CP.openpilotLongitudinalControl or not CP.pcmCruise: return False stock = 0x91 if int(CP.flags) & int(HyundaiFlags.CANFD_LKA_STEER_MSG_ALT) else 0x11 raw = int(CP.safetyConfigs[config_index(CP)].safetyParam) @@ -98,6 +98,12 @@ def ioniq6_settings_capable(CP) -> bool: return _stock_ioniq6(CP) or (IONIQ6_LONG_PREARM_ENABLED and ioniq6_long_eligible(CP)) +def ioniq6_alpha_long_available(CP) -> bool: + # Cached radar discovery may predate the external-bus repair. This saves only + # an opt-in; the takeover still requires fresh physical radar and ECU evidence. + return _stock_ioniq6(CP, require_radar=False) or (IONIQ6_LONG_PREARM_ENABLED and ioniq6_long_eligible(CP)) + + def qualified_ccnc_long_aol(CP, *, marked_only=False): return qualified_ev9_long_aol(CP, marked_only=marked_only) or qualified_pe_long_aol(CP, marked_only=marked_only) @@ -232,9 +238,9 @@ def native_profile_supported(model: int, param: int) -> bool: def native_accepts_cp(CP, model: int, param: int) -> bool: - # Ioniq 6 has its own exact contract; no generic Hyundai owner accepts it. + # HDA-II retains its dedicated namespace; camera SCC uses the shared exact word. # Revalidate the complete contract on every call, without caching live authority. - if CP.carFingerprint == HyundaiCAR.HYUNDAI_IONIQ_6: + if CP.carFingerprint == HyundaiCAR.HYUNDAI_IONIQ_6 and not CP.flags & HyundaiFlags.CANFD_CAMERA_SCC: return qualified_ioniq6(CP) if qualified_ccnc_long_aol(CP, marked_only=True): return model == int(car.CarParams.SafetyModel.hyundaiCanfd) and param == CP.safetyConfigs[0].safetyParam diff --git a/openpilot/starpilot/ui/feature_settings_owner.py b/openpilot/starpilot/ui/feature_settings_owner.py index dce7b1e369..f54dd3da3b 100644 --- a/openpilot/starpilot/ui/feature_settings_owner.py +++ b/openpilot/starpilot/ui/feature_settings_owner.py @@ -205,12 +205,12 @@ class FeatureSettingsOwner: release, readable = read_saved(self.params, 'IsReleaseBranch', 8) try: from opendbc.car.values import PLATFORMS - from openpilot.starpilot.car.hyundai.aol import ioniq6_settings_capable + from openpilot.starpilot.car.hyundai.aol import ioniq6_alpha_long_available from openpilot.starpilot.car.ford.longitudinal_preferences import release_long_available as ford_release_long_available platform = PLATFORMS.get(str(cp.carFingerprint)) if (not self._galaxy() or platform is None or platform.__class__.__module__.split('.')[-2] != str(cp.brand) or not readable or release not in (None, b'0', b'1') or cp.notCar or cp.passive or cp.dashcamOnly or - not (cp.alphaLongitudinalAvailable or ioniq6_settings_capable(cp)) or + not (cp.alphaLongitudinalAvailable or ioniq6_alpha_long_available(cp)) or (release == b'1' and not ford_release_long_available(cp))): return None return (str(cp.carFingerprint), str(cp.brand), int(cp.flags), int(cp.alternativeExperience), diff --git a/prebuilt.json b/prebuilt.json index 3e43f958e2..ceec9ededd 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "634cdb0998641b505e1f24b4fb715965c778b355de58ecfb7cf292b5616941e2", "schema_version": 1} +{"protected_inventory_sha256": "b93d12cdd85e78c16f978b68bd56777fab78fa2a3a94beb99a7ed0a2a635466f", "schema_version": 1}