From ac3fb6a4d889bb0e43373d602ad80ac349ecb81c Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Thu, 10 Sep 2026 20:45:47 -0500 Subject: [PATCH] Hyundai: hotfix platform-specific control status --- .../opendbc/car/hyundai/carcontroller.py | 19 ++-- .../opendbc/car/hyundai/hyundaicanfd.py | 16 ++- opendbc_repo/opendbc/car/hyundai/interface.py | 4 +- .../opendbc/car/hyundai/tests/test_hyundai.py | 100 ++++++++++++++++-- opendbc_repo/opendbc/car/hyundai/values.py | 7 ++ selfdrive/controls/controlsd.py | 7 -- selfdrive/controls/lib/drive_helpers.py | 5 +- .../controls/tests/test_drive_helpers.py | 10 +- 8 files changed, 137 insertions(+), 31 deletions(-) diff --git a/opendbc_repo/opendbc/car/hyundai/carcontroller.py b/opendbc_repo/opendbc/car/hyundai/carcontroller.py index e9aa5224ca..2ba1fe1e09 100644 --- a/opendbc_repo/opendbc/car/hyundai/carcontroller.py +++ b/opendbc_repo/opendbc/car/hyundai/carcontroller.py @@ -10,8 +10,9 @@ from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_steer_an from opendbc.car.common.conversions import Conversions as CV from opendbc.car.hyundai import hyundaicanfd, hyundaican from opendbc.car.hyundai.hyundaicanfd import CanBus -from opendbc.car.hyundai.values import HyundaiFlags, HyundaiSafetyFlags, HyundaiStarPilotFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \ - CANFD_RADAR_LIVE_LONGITUDINAL_CAR, CANFD_ALT_BUTTONS_RESUME_CAR, kia_ev6_gt_line_longitudinal_tuning, \ +from opendbc.car.hyundai.values import HyundaiFlags, HyundaiSafetyFlags, HyundaiStarPilotFlags, Buttons, CarControllerParams, CAR, \ + CANFD_ANGLE_LONGITUDINAL_CAR, CANFD_RADAR_ECU_KEEPALIVE_CAR, CANFD_ALT_BUTTONS_RESUME_CAR, \ + kia_ev6_gt_line_longitudinal_tuning, \ KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID from opendbc.car.interfaces import CarControllerBase from opendbc.car.vehicle_model import VehicleModel @@ -859,10 +860,9 @@ class CarController(CarControllerBase): can_sends = [] lka_steering = self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING - longitudinal_active = bool(self.long_active_ecu and getattr(CC, "longActive", False)) - lfa_status_cars = (CAR.HYUNDAI_IONIQ_6, CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, CAR.KIA_EV6) + persistent_lfa_status_cars = (CAR.HYUNDAI_IONIQ_6, CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, CAR.KIA_EV6) lfa_longitudinal_active = self.CP.openpilotLongitudinalControl \ - if self.CP.carFingerprint in lfa_status_cars else longitudinal_active + if self.CP.carFingerprint in persistent_lfa_status_cars else self.long_active_ecu lka_steering_long = lka_steering and lfa_longitudinal_active ccnc_non_hda2 = self.CP.flags & HyundaiFlags.CCNC and not lka_steering use_egmp_dynamic_long_tuning = egmp_dynamic_longitudinal_tuning(self.CP) and self.long_active_ecu and \ @@ -889,11 +889,10 @@ class CarController(CarControllerBase): gear = getattr(getattr(CS, "out", None), "gearShifter", None) drive_gear = gear == structs.CarState.GearShifter.drive - if angle_lkas_alt: + if angle_lkas_alt and self.CP.carFingerprint != CAR.KIA_SPORTAGE_HEV_2026: steering_msg_active = bool(steering_msg_active and drive_gear) angle_lkas_alt_standstill_handoff = bool(getattr(CS.out, "standstill", False) and not CC.latActive) - forward_stock_lkas = (self.CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR or - self.CP.carFingerprint == CAR.KIA_SPORTAGE_HEV_2026) and angle_lkas_alt and ( + forward_stock_lkas = self.CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR and angle_lkas_alt and ( angle_lkas_alt_standstill_handoff or not (drive_gear and (CC.latActive or CC.enabled)) ) preserve_stock_lfa_status = preserve_stock_canfd_lfa_status(self.CP.carFingerprint) @@ -920,7 +919,7 @@ class CarController(CarControllerBase): # prevent LFA from activating on LKA steering cars by sending "no lane lines detected" to ADAS ECU suppress_lfa = bool(lka_steering) - if angle_lkas_alt: + if angle_lkas_alt and self.CP.carFingerprint != CAR.KIA_SPORTAGE_HEV_2026: suppress_lfa = bool(lka_steering and drive_gear and (CC.latActive or (ccnc_angle_long and CC.enabled))) if self.frame % 5 == 0 and suppress_lfa: can_sends.append(hyundaicanfd.create_suppress_lfa(self.packer, self.CAN, CS.lfa_block_msg, @@ -995,7 +994,7 @@ class CarController(CarControllerBase): # The front radar treats ADAS_DRV's 0x100 broadcast as its host heartbeat # and stops publishing object tracks when it disappears. radar_heartbeat_step = 1 if ccnc_angle_long else 4 - if self.CP.carFingerprint in CANFD_RADAR_LIVE_LONGITUDINAL_CAR and self.frame % radar_heartbeat_step == 0: + if self.CP.carFingerprint in CANFD_RADAR_ECU_KEEPALIVE_CAR and self.frame % radar_heartbeat_step == 0: can_sends.append(hyundaicanfd.create_accelerator_brake_alt_spoof(0, self.frame // radar_heartbeat_step, CS.out.brakePressed, CS.out.gasPressed, self.CP.carFingerprint)) diff --git a/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py b/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py index 95de0b03b3..825e70006f 100644 --- a/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py +++ b/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py @@ -142,7 +142,21 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque, lkas_values["LKAS_ANGLE_ACTIVE"] = 2 if lat_active else 1 lkas_values["ADAS_ACIAnglTqRedcGainVal"] = apply_torque if lat_active else 0.0 if angle_lkas_alt: - if lat_active: + if CP.carFingerprint == CAR.KIA_SPORTAGE_HEV_2026: + lkas_values = { + "LKA_OptUsmSta": 0, + "LKA_SysIndReq": 2 if enabled else 1, + "StrTqReqVal": 0, + "LKA_SysWrn": 0, + "ActToiSta": 0, + "LKA_UsmMod": 0, + "LKA_RcgSta": 3 if lat_active else 0, + "Damping_Gain": 100, + "ADAS_StrAnglReqVal": apply_angle, + "LKAS_ANGLE_ACTIVE": 2 if lat_active else 1, + "ADAS_ACIAnglTqRedcGainVal": apply_torque if lat_active else 0.0, + } + elif lat_active: lkas_values = { "LKA_OptUsmSta": 0, "LKA_RcgSta": 3, diff --git a/opendbc_repo/opendbc/car/hyundai/interface.py b/opendbc_repo/opendbc/car/hyundai/interface.py index 4d95a15cee..a17b841f6b 100644 --- a/opendbc_repo/opendbc/car/hyundai/interface.py +++ b/opendbc_repo/opendbc/car/hyundai/interface.py @@ -7,7 +7,7 @@ from opendbc.car.hyundai.values import HyundaiFlags, CAR, CarControllerParams, \ CANFD_UNSUPPORTED_LONGITUDINAL_CAR, \ CANFD_SECURITYACCESS_CAR, \ CANFD_ANGLE_LONGITUDINAL_CAR, \ - CANFD_RADAR_LIVE_LONGITUDINAL_CAR, \ + CANFD_RADAR_ECU_KEEPALIVE_CAR, \ RADAR_LIVE_LONGITUDINAL_CAR, \ UNSUPPORTED_LONGITUDINAL_CAR, HyundaiSafetyFlags, \ LEGACY_LONGITUDINAL_CAR, \ @@ -29,7 +29,7 @@ Ecu = structs.CarParams.Ecu def get_communication_control_request(car_fingerprint): - if car_fingerprint in CANFD_RADAR_LIVE_LONGITUDINAL_CAR and car_fingerprint != CAR.KIA_EV6: + if car_fingerprint in CANFD_RADAR_ECU_KEEPALIVE_CAR: return bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, uds.CONTROL_TYPE.ENABLE_RX_DISABLE_TX, uds.MESSAGE_TYPE.NORMAL]) diff --git a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py index 666bba367f..c068e58cdf 100644 --- a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py +++ b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py @@ -129,12 +129,17 @@ def get_test_toggles() -> SimpleNamespace: class TestHyundaiFingerprint: - def test_ev6_uses_stock_hda2_communication_control_path(self): + @pytest.mark.parametrize("car", [ + CAR.HYUNDAI_IONIQ_5, + CAR.KIA_EV6, + CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, + ]) + def test_hda2_cars_without_radar_keepalive_use_stock_communication_control(self, car): stock_request = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, 0x83, uds.MESSAGE_TYPE.NORMAL]) radar_keepalive_request = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, uds.CONTROL_TYPE.ENABLE_RX_DISABLE_TX, uds.MESSAGE_TYPE.NORMAL]) - assert get_communication_control_request(CAR.KIA_EV6) == stock_request + assert get_communication_control_request(car) == stock_request assert get_communication_control_request(CAR.HYUNDAI_IONIQ_6) == radar_keepalive_request def test_carnival_hev_low_speed_torque_rate_limits(self): @@ -2560,7 +2565,7 @@ class TestHyundaiFingerprint: if controller.packer.dbc.addr_to_msg[addr].name in ("LFA", "LKAS")] assert steering_names == [("LFA", can_bus.ECAN), ("LKAS", can_bus.ACAN)] - @pytest.mark.parametrize("car", [CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6]) + @pytest.mark.parametrize("car", [CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6, CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN]) def test_egmp_keeps_lfa_status_when_longitudinal_is_inactive(self, car): CP = CarParams.new_message() CP.carFingerprint = car @@ -2584,6 +2589,38 @@ class TestHyundaiFingerprint: cc.hudControl, cs, cc, get_test_toggles(), lka_icon=1, lfa_icon=1) assert any(addr == 0x12A for addr, _, _ in msgs) + @pytest.mark.parametrize(("car", "powertrain_flag"), [ + (CAR.HYUNDAI_IONIQ_5, HyundaiFlags.EV), + (CAR.KIA_CARNIVAL_2025, 0), + (CAR.KIA_CARNIVAL_HEV_4TH_GEN, HyundaiFlags.HYBRID), + ]) + def test_hda2_keeps_lfa_status_while_longitudinal_ecu_is_disabled(self, car, powertrain_flag): + CP = CarParams.new_message() + CP.carFingerprint = car + CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.CANFD_LKA_STEERING | powertrain_flag) + CP.openpilotLongitudinalControl = True + + controller = CarController(DBC[CP.carFingerprint], CP) + controller.frame = 1 + controller.long_active_ecu = True + cc = SimpleNamespace( + enabled=False, latActive=False, longActive=False, + actuators=SimpleNamespace(longControlState=LongCtrlState.off), + leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace(), + ) + cs = SimpleNamespace( + stock_lfa_msg=None, stock_lkas_msg=None, + left_blindspot_from_radar=False, right_blindspot_from_radar=False, + out=SimpleNamespace( + brakePressed=False, gasPressed=False, + gearShifter=structs.CarState.GearShifter.park, + ), + ) + + msgs = controller.create_canfd_msgs(0, False, 0.0, 0.0, 0.0, 0.0, False, + cc.hudControl, cs, cc, get_test_toggles(), lka_icon=1, lfa_icon=1) + assert any(addr == 0x12A for addr, _, _ in msgs) + def test_gv70_electrified_longitudinal_uses_hda2_scc_contract(self): CP = CarParams.new_message() CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN @@ -2716,7 +2753,7 @@ class TestHyundaiFingerprint: assert len([msg for msg in msgs if msg[0] == 0x110]) == expected_lkas_msgs @pytest.mark.parametrize("standstill", [False, True]) - def test_sportage_angle_lkas_alt_forwards_stock_status_when_inactive(self, standstill): + def test_sportage_angle_lkas_alt_keeps_status_and_suppression_alive(self, standstill): CP = CarParams.new_message() CP.carFingerprint = CAR.KIA_SPORTAGE_HEV_2026 CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.HYBRID | HyundaiFlags.CANFD_ANGLE_STEERING | @@ -2724,16 +2761,67 @@ class TestHyundaiFingerprint: CP.openpilotLongitudinalControl = False controller = CarController(DBC[CP.carFingerprint], CP) + controller.frame = 5 + can_bus = CanBus(CP) cc = SimpleNamespace(enabled=False, latActive=False, actuators=SimpleNamespace(longControlState=LongCtrlState.off), leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace()) - cs = SimpleNamespace(stock_lfa_msg=None, stock_lkas_msg={}, + lfa_block_msg = {f"BYTE{i}": 0 for i in range(3, 32) if i != 7} + lfa_block_msg["COUNTER"] = 0 + cs = SimpleNamespace(stock_lfa_msg=None, stock_lkas_msg={}, lfa_block_msg=lfa_block_msg, out=SimpleNamespace(standstill=standstill, steeringAngleDeg=0.0, gearShifter=structs.CarState.GearShifter.drive)) msgs = controller.create_canfd_msgs(0, False, 0.0, 0.0, 0.0, 0.0, False, cc.hudControl, cs, cc, get_test_toggles(), lka_icon=1, lfa_icon=1) - assert not [msg for msg in msgs if msg[0] in (0x110, 0x12A)] + lkas_msgs = [msg for msg in msgs if msg[0] == 0x110] + assert len(lkas_msgs) == 1 + assert len([msg for msg in msgs if msg[0] == 0x362]) == 1 + + parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS_ALT", 0)], can_bus.ACAN) + parser.update([(1, lkas_msgs)]) + assert parser.can_valid + assert parser.vl["LKAS_ALT"]["LKA_OptUsmSta"] == 0 + assert parser.vl["LKAS_ALT"]["LKA_SysIndReq"] == 1 + assert parser.vl["LKAS_ALT"]["LKA_RcgSta"] == 0 + assert parser.vl["LKAS_ALT"]["Damping_Gain"] == 100 + assert parser.vl["LKAS_ALT"]["LKAS_ANGLE_ACTIVE"] == 1 + assert parser.vl["LKAS_ALT"]["ADAS_ACIAnglTqRedcGainVal"] == 0.0 + + def test_sportage_angle_lkas_alt_active_status_matches_vehicle_contract(self): + CP = CarParams.new_message() + CP.carFingerprint = CAR.KIA_SPORTAGE_HEV_2026 + CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.HYBRID | HyundaiFlags.CANFD_ANGLE_STEERING | + HyundaiFlags.CANFD_LKA_STEERING | HyundaiFlags.CANFD_LKA_STEERING_ALT) + CP.openpilotLongitudinalControl = False + + controller = CarController(DBC[CP.carFingerprint], CP) + controller.frame = 5 + can_bus = CanBus(CP) + cc = SimpleNamespace(enabled=True, latActive=True, + actuators=SimpleNamespace(longControlState=LongCtrlState.off), + leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace()) + lfa_block_msg = {f"BYTE{i}": 0 for i in range(3, 32) if i != 7} + lfa_block_msg["COUNTER"] = 0 + cs = SimpleNamespace(stock_lfa_msg=None, stock_lkas_msg={}, lfa_block_msg=lfa_block_msg, + out=SimpleNamespace(standstill=False, steeringAngleDeg=10.0, + gearShifter=structs.CarState.GearShifter.drive)) + + msgs = controller.create_canfd_msgs(0, True, 0.4, 12.0, 0.0, 0.0, False, cc.hudControl, cs, cc, + get_test_toggles(), lka_icon=2, lfa_icon=2) + lkas_msgs = [msg for msg in msgs if msg[0] == 0x110] + assert len(lkas_msgs) == 1 + assert len([msg for msg in msgs if msg[0] == 0x362]) == 1 + + parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS_ALT", 0)], can_bus.ACAN) + parser.update([(1, lkas_msgs)]) + assert parser.can_valid + assert parser.vl["LKAS_ALT"]["LKA_OptUsmSta"] == 0 + assert parser.vl["LKAS_ALT"]["LKA_SysIndReq"] == 2 + assert parser.vl["LKAS_ALT"]["LKA_RcgSta"] == 3 + assert parser.vl["LKAS_ALT"]["Damping_Gain"] == 100 + assert parser.vl["LKAS_ALT"]["LKAS_ANGLE_ACTIVE"] == 2 + assert parser.vl["LKAS_ALT"]["ADAS_ACIAnglTqRedcGainVal"] == pytest.approx(0.4) def test_ev9_inactive_angle_steering_does_not_suppress_stock_lfa(self): CP = CarParams.new_message() diff --git a/opendbc_repo/opendbc/car/hyundai/values.py b/opendbc_repo/opendbc/car/hyundai/values.py index 0c74a6c041..d7cfa094c7 100644 --- a/opendbc_repo/opendbc/car/hyundai/values.py +++ b/opendbc_repo/opendbc/car/hyundai/values.py @@ -1218,6 +1218,13 @@ CANFD_ALT_BUTTONS_RESUME_CAR = {CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_ CANFD_CORNER_RADAR_BSM_CAR = {CAR.HYUNDAI_IONIQ_6, CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9} CANFD_RADAR_LIVE_LONGITUDINAL_CAR = { CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_IONIQ_5_PE, CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6, CAR.KIA_EV9, CAR.GENESIS_GV60_EV_1ST_GEN, + CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, +} +CANFD_RADAR_ECU_KEEPALIVE_CAR = { + CAR.HYUNDAI_IONIQ_5_PE, + CAR.HYUNDAI_IONIQ_6, + CAR.KIA_EV9, + CAR.GENESIS_GV60_EV_1ST_GEN, } RADAR_LIVE_LONGITUDINAL_CAR = CANFD_RADAR_LIVE_LONGITUDINAL_CAR | { CAR.HYUNDAI_IONIQ, diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index ecab7094ee..6b63b9c88f 100644 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -404,7 +404,6 @@ class Controls: self.turn_blinker_swept = 0.0 self.twitch_guard_remaining = 0.0 self.kona_non_scc_lateral_active = False - self.kona_non_scc_lateral_faulted = False self.elantra_hev_2024_lateral_faulted = False self.elantra_hev_2024_previous_cruise_enabled = False @@ -504,11 +503,6 @@ class Controls: standstill = abs(CS.vEgo) <= max(self.CP.minSteerSpeed, 0.3) or CS.standstill if self.CP.carFingerprint == HYUNDAI_CAR.HYUNDAI_KONA_NON_SCC: always_on_lateral_enabled = self.sm['starpilotCarState'].alwaysOnLateralEnabled - lateral_requested = (CC.enabled and self.sm['selfdriveState'].active) or always_on_lateral_enabled - if not lateral_requested: - self.kona_non_scc_lateral_faulted = False - elif CS.steerFaultTemporary: - self.kona_non_scc_lateral_faulted = True CC.latActive = get_kona_non_scc_lateral_active( CC.enabled, self.sm['selfdriveState'].active, always_on_lateral_enabled, @@ -516,7 +510,6 @@ class Controls: standstill, self.CP.steerAtStandstill, self.sm['starpilotPlan'].lateralCheck, CS.steeringPressed, self.kona_non_scc_lateral_active, - self.kona_non_scc_lateral_faulted, ) self.kona_non_scc_lateral_active = CC.latActive elif self.CP.carFingerprint == HYUNDAI_CAR.HYUNDAI_ELANTRA_HEV_2024: diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 7a6d5b8550..8a2a1881f8 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -89,13 +89,12 @@ def update_lateral_fault_latch(previous_latched: bool, lateral_requested: bool, def get_kona_non_scc_lateral_active(enabled: bool, active: bool, always_on_lateral_enabled: bool, steer_fault_temporary: bool, steer_fault_permanent: bool, standstill: bool, steer_at_standstill: bool, lateral_check: bool, - steering_pressed: bool, previous_lateral_active: bool, - steer_fault_latched: bool = False) -> bool: + steering_pressed: bool, previous_lateral_active: bool) -> bool: """Avoid the Kona EPS torque fault when AOL is enabled over driver steering input.""" lateral_active = get_lateral_active(enabled, active, always_on_lateral_enabled, steer_fault_temporary, steer_fault_permanent, standstill, steer_at_standstill, lateral_check) - if not lateral_active or steer_fault_latched: + if not lateral_active: return False aol_rising_edge = always_on_lateral_enabled and not enabled and not previous_lateral_active diff --git a/selfdrive/controls/tests/test_drive_helpers.py b/selfdrive/controls/tests/test_drive_helpers.py index 02eaf96239..8ec54a9bdb 100644 --- a/selfdrive/controls/tests/test_drive_helpers.py +++ b/selfdrive/controls/tests/test_drive_helpers.py @@ -35,9 +35,15 @@ def test_kona_non_scc_aol_gate_does_not_change_fault_or_normal_lateral_gates(): ) -def test_kona_non_scc_does_not_retry_after_a_latched_temporary_fault(): +def test_kona_non_scc_recovers_after_temporary_fault_clears(): assert not get_kona_non_scc_lateral_active( - False, False, True, False, False, False, False, True, False, False, True, + False, False, True, True, False, False, False, True, False, True, + ) + assert not get_kona_non_scc_lateral_active( + False, False, True, False, False, False, False, True, True, False, + ) + assert get_kona_non_scc_lateral_active( + False, False, True, False, False, False, False, True, False, False, )