From 3e03917edcd35370faa221d0bc90ca3ae13d6403 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Mon, 11 May 2026 21:50:07 -0500 Subject: [PATCH] test --- .../opendbc/car/hyundai/carcontroller.py | 28 ++-- .../opendbc/car/hyundai/hyundaicanfd.py | 142 +++++++++++++++++- .../opendbc/car/hyundai/tests/test_hyundai.py | 34 ++++- 3 files changed, 177 insertions(+), 27 deletions(-) diff --git a/opendbc_repo/opendbc/car/hyundai/carcontroller.py b/opendbc_repo/opendbc/car/hyundai/carcontroller.py index f5bddf434..3ae2fb092 100644 --- a/opendbc_repo/opendbc/car/hyundai/carcontroller.py +++ b/opendbc_repo/opendbc/car/hyundai/carcontroller.py @@ -21,7 +21,6 @@ MAX_ANGLE = 85 MAX_ANGLE_FRAMES = 89 MAX_ANGLE_CONSECUTIVE_FRAMES = 2 CANFD_BLINDSPOT_STATUS_STALE_NS = 200_000_000 -CANFD_BLINKER_STALKS_STALE_NS = 200_000_000 CANFD_CAMERA_LEAD_STALE_NS = 300_000_000 CANFD_LEAD_MIN_DISTANCE = 0.1 CANFD_FALLBACK_LEAD_DISTANCE = 20.0 @@ -231,7 +230,7 @@ class CarController(CarControllerBase): self._params = Params() self.long_active_ecu = self.CP.openpilotLongitudinalControl self._ioniq_6_lane_change_ui_side = None - self._ioniq_6_lane_change_ui_trigger_frames = 0 + self._ioniq_6_lane_change_ui_frames = 0 self._ioniq_6_long_tuning = Ioniq6LongitudinalTuningState() self._genesis_g90_long_tuning = GenesisG90LongitudinalTuningState() self._dash_lat_disengage_blink_frame = 0 @@ -504,27 +503,26 @@ class CarController(CarControllerBase): if lka_steering and self.CP.flags & HyundaiFlags.ENABLE_BLINKERS: can_sends.extend(hyundaicanfd.create_spas_messages(self.packer, self.CAN, CC.leftBlinker, CC.rightBlinker)) + lane_change_ui_side = None if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6: - lane_change_ui_side = None if CC.leftBlinker and not CC.rightBlinker: lane_change_ui_side = "left" elif CC.rightBlinker and not CC.leftBlinker: lane_change_ui_side = "right" - stock_blinker_stalks_live = now_nanos - CS.stock_blinker_stalks_ts <= CANFD_BLINKER_STALKS_STALE_NS if lane_change_ui_side != self._ioniq_6_lane_change_ui_side: self._ioniq_6_lane_change_ui_side = lane_change_ui_side - self._ioniq_6_lane_change_ui_trigger_frames = 6 if lane_change_ui_side is not None else 0 + self._ioniq_6_lane_change_ui_frames = 0 - if stock_blinker_stalks_live: - self._ioniq_6_lane_change_ui_trigger_frames = 0 - - if lane_change_ui_side is not None and not stock_blinker_stalks_live: - trigger = self._ioniq_6_lane_change_ui_trigger_frames > 0 - can_sends.extend(hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(self.CAN, self.frame, - lane_change_ui_side, trigger)) - if self._ioniq_6_lane_change_ui_trigger_frames > 0: - self._ioniq_6_lane_change_ui_trigger_frames -= 1 + if lane_change_ui_side is None or not self.long_active_ecu: + self._ioniq_6_lane_change_ui_frames = 0 + else: + # The stock Ioniq 6 lane-change animation stops when the ADAS ECU is disabled, + # so replay the captured ECAN cluster frames ourselves while OP long is active. + can_sends.extend(hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(self.CAN, + self._ioniq_6_lane_change_ui_frames, + lane_change_ui_side)) + self._ioniq_6_lane_change_ui_frames += 1 if self.long_active_ecu: if lka_steering: @@ -542,7 +540,7 @@ class CarController(CarControllerBase): CS.right_blindspot_from_radar, CC.leftBlinker, CC.rightBlinker)) - if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6: + if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6 and lane_change_ui_side is None: can_sends.extend(hyundaicanfd.create_ioniq_6_cluster_blindspot_messages(self.CAN, self.frame, CS.left_blindspot_from_radar, CS.right_blindspot_from_radar, diff --git a/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py b/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py index a2b52b49d..7f15a3696 100644 --- a/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py +++ b/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py @@ -292,6 +292,125 @@ IONIQ_6_CLUSTER_LANE_CHANGE_3C1 = { }, } +# Captured from a stock Ioniq 6 route that shows the cluster lane-change animation +# on ECAN after the trigger/hold 0x3C1 states above. +IONIQ_6_CLUSTER_LANE_CHANGE_3B5 = { + "right": ( + bytes.fromhex("9f687600000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("d9317700000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("58457800000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("1e1c7900000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("d4f77a00000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("92ae7b00000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("61307c00000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("27697d00000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("ed827e00000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("abdb7f00000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("dd978000000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("9bce8100000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("51258200000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("177c8300000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("e4e28400000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("18ba8500000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("68508600000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("94088700000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("157c8800000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("53258900000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("99ce8a00000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("df978b00000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("2c098c00000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("6a508d00000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("a0bb8e00000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("e6e28f00000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("a2529000000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("e40b9100000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("2ee09200000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("d2b89300000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("9b279400000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("677f9500000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("17959600000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("ebcd9700000000464600000000000000d7020000000069070000000000000000"), + bytes.fromhex("d0b89800000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("96e19900000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("5c0a9a00000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("1a539b00000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("e9cd9c00000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("af949d00000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("657f9e00000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("23269f00000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("cc0fa000000000464600000000000000d8020000000069070000000000000000"), + bytes.fromhex("bba6a100000000464600000000000000d9020000000069070000000000000000"), + bytes.fromhex("714da200000000464600000000000000d9020000000069070000000000000000"), + bytes.fromhex("3714a300000000464600000000000000d9020000000069070000000000000000"), + ), + "left": ( + bytes.fromhex("e682c600000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("d2dbc700000000464600000000000000d9020000000069070000000000000000"), + bytes.fromhex("21afc800000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("67f6c900000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("ad1dca00000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("eb44cb00000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("18dacc00000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("5e83cd00000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("9468ce00000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("d231cf00000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("9681d000000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("d0d8d100000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("1a33d200000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("5c6ad300000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("ddf4d400000000464600000000000000d9020000000069070000000000000000"), + bytes.fromhex("e9add500000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("2346d600000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("651fd700000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("e46bd800000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("d032d900000000464600000000000000d9020000000069070000000000000000"), + bytes.fromhex("68d9da00000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("2e80db00000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("dd1edc00000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("9b47dd00000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("51acde00000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("17f5df00000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("f8dce000000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("be85e100000000464600000000000000da020000000069070000000000000000"), + bytes.fromhex("746ee200000000464600000000000000da020000000069070000000000000000"), + ), +} + +IONIQ_6_CLUSTER_LANE_CHANGE_31A = { + "right": ( + bytes.fromhex("eb4518f0f0ffff03898aff0a098678ff000000007e0055550000000000000000"), + bytes.fromhex("757119f0f0ffff03898aff0a088678ff000000007e0055550000000000000000"), + bytes.fromhex("bf9a1af0f0ffff03898aff0a088678ff000000007e0055550000000000000000"), + bytes.fromhex("f9c31bf0f0ffff03898aff0a088678ff000000007e0055550000000000000000"), + bytes.fromhex("0a5d1cf0f0ffff03898aff0a088678ff000000007e0055550000000000000000"), + bytes.fromhex("4c041df0f0ffff03898aff0a088678ff000000007e0055550000000000000000"), + bytes.fromhex("86ef1ef0f0ffff03898aff0a088678ff000000007e0055550000000000000000"), + bytes.fromhex("18db1ff0f0ffff03898aff0a098678ff000000007e0055550000000000000000"), + bytes.fromhex("f7f220f0f0ffff03898aff0a098678ff000000007e0055550000000000000000"), + ), + "left": ( + bytes.fromhex("851828f0f0ffff03898aff0a098678ff000000007e0055550000000000000000"), + bytes.fromhex("c34129f0f0ffff03898aff0a098678ff000000007e0055550000000000000000"), + bytes.fromhex("09aa2af0f0ffff03898aff0a098678ff000000007e0055550000000000000000"), + bytes.fromhex("4ff32bf0f0ffff03898aff0a098678ff000000007e0055550000000000000000"), + bytes.fromhex("bc6d2cf0f0ffff03898aff0a098678ff000000007e0055550000000000000000"), + bytes.fromhex("fa342df0f0ffff03898aff0a098678ff000000007e0055550000000000000000"), + ), +} + +IONIQ_6_CLUSTER_LANE_CHANGE_3C1_BURST = { + 0: "trigger", + 4: "trigger", + 7: "steady", + 10: "steady", + 13: "steady", + 16: "steady", +} + +IONIQ_6_CLUSTER_LANE_CHANGE_3C1_STEADY_START = 34 +IONIQ_6_CLUSTER_LANE_CHANGE_3B5_START = 4 +IONIQ_6_CLUSTER_LANE_CHANGE_31A_START = 30 + def create_ioniq_6_cluster_blindspot_messages(CAN, frame, left_blindspot=False, right_blindspot=False, left_blinker=False, right_blinker=False): @@ -320,15 +439,26 @@ def create_ioniq_6_cluster_blindspot_messages(CAN, frame, left_blindspot=False, return ret -def create_ioniq_6_cluster_lane_change_messages(CAN, frame, side=None, trigger=False): +def create_ioniq_6_cluster_lane_change_messages(CAN, frame, side=None): if side not in IONIQ_6_CLUSTER_LANE_CHANGE_3C1: return [] - if trigger: - return [(0x3C1, IONIQ_6_CLUSTER_LANE_CHANGE_3C1[side]["trigger"], CAN.ECAN)] - if frame % 20 == 0: - return [(0x3C1, IONIQ_6_CLUSTER_LANE_CHANGE_3C1[side]["steady"], CAN.ECAN)] - return [] + ret = [] + frame_phase = IONIQ_6_CLUSTER_LANE_CHANGE_3C1_BURST.get(frame) + if frame_phase is None and frame >= IONIQ_6_CLUSTER_LANE_CHANGE_3C1_STEADY_START and \ + (frame - IONIQ_6_CLUSTER_LANE_CHANGE_3C1_STEADY_START) % 20 == 0: + frame_phase = "steady" + if frame_phase is not None: + ret.append((0x3C1, IONIQ_6_CLUSTER_LANE_CHANGE_3C1[side][frame_phase], CAN.ECAN)) + + if frame >= IONIQ_6_CLUSTER_LANE_CHANGE_3B5_START and (frame - IONIQ_6_CLUSTER_LANE_CHANGE_3B5_START) % 20 == 0: + seq_3b5 = IONIQ_6_CLUSTER_LANE_CHANGE_3B5[side] + ret.append((0x3B5, seq_3b5[((frame - IONIQ_6_CLUSTER_LANE_CHANGE_3B5_START) // 20) % len(seq_3b5)], CAN.ECAN)) + if frame >= IONIQ_6_CLUSTER_LANE_CHANGE_31A_START and (frame - IONIQ_6_CLUSTER_LANE_CHANGE_31A_START) % 100 == 0: + seq_31a = IONIQ_6_CLUSTER_LANE_CHANGE_31A[side] + ret.append((0x31A, seq_31a[((frame - IONIQ_6_CLUSTER_LANE_CHANGE_31A_START) // 100) % len(seq_31a)], CAN.ECAN)) + + return ret def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_override, set_speed, hud_control, diff --git a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py index 86f7e4c14..448403631 100644 --- a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py +++ b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py @@ -907,26 +907,48 @@ class TestHyundaiFingerprint: both_msgs = hyundaicanfd.create_ioniq_6_cluster_blindspot_messages(can_bus, 0, True, True) assert both_msgs == [] - def test_ioniq_6_cluster_lane_change_helper_uses_stock_trigger_and_hold(self): + def test_ioniq_6_cluster_lane_change_helper_replays_stock_animation_family(self): CP = CarParams.new_message() CP.carFingerprint = CAR.HYUNDAI_IONIQ_6 CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.CANFD_LKA_STEERING) can_bus = CanBus(CP) - assert hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(can_bus, 7, "right") == [] - assert hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(can_bus, 0, "right", trigger=True) == [ + assert hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(can_bus, 1, "right") == [] + assert hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(can_bus, 0, "right") == [ (0x3C1, bytes.fromhex("e910300041000000"), can_bus.ECAN), ] - assert hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(can_bus, 20, "right") == [ + assert hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(can_bus, 4, "right") == [ + (0x3C1, bytes.fromhex("e910300041000000"), can_bus.ECAN), + (0x3B5, bytes.fromhex("9f687600000000464600000000000000d7020000000069070000000000000000"), can_bus.ECAN), + ] + assert hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(can_bus, 7, "right") == [ (0x3C1, bytes.fromhex("ab20300001000000"), can_bus.ECAN), ] - assert hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(can_bus, 0, "left", trigger=True) == [ + assert hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(can_bus, 24, "right") == [ + (0x3B5, bytes.fromhex("d9317700000000464600000000000000d7020000000069070000000000000000"), can_bus.ECAN), + ] + assert hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(can_bus, 30, "right") == [ + (0x31A, bytes.fromhex("eb4518f0f0ffff03898aff0a098678ff000000007e0055550000000000000000"), can_bus.ECAN), + ] + assert hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(can_bus, 34, "right") == [ + (0x3C1, bytes.fromhex("ab20300001000000"), can_bus.ECAN), + ] + + assert hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(can_bus, 0, "left") == [ (0x3C1, bytes.fromhex("3d40304010000000"), can_bus.ECAN), ] - assert hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(can_bus, 20, "left") == [ + assert hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(can_bus, 4, "left") == [ + (0x3C1, bytes.fromhex("3d40304010000000"), can_bus.ECAN), + (0x3B5, bytes.fromhex("e682c600000000464600000000000000da020000000069070000000000000000"), can_bus.ECAN), + ] + assert hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(can_bus, 7, "left") == [ (0x3C1, bytes.fromhex("3e50300000000000"), can_bus.ECAN), ] + assert hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(can_bus, 30, "left") == [ + (0x31A, bytes.fromhex("851828f0f0ffff03898aff0a098678ff000000007e0055550000000000000000"), can_bus.ECAN), + ] + assert hyundaicanfd.create_ioniq_6_cluster_lane_change_messages(can_bus, 5, "none") == [] def test_sportage_angle_jerk_override_is_scoped(self): sportage = CarParams.new_message()