This commit is contained in:
firestar5683
2026-05-11 21:50:07 -05:00
parent 4d0f65a6d1
commit 3e03917edc
3 changed files with 177 additions and 27 deletions
@@ -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,
@@ -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,
@@ -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()