Skip to content

Commit ddfbf8e

Browse files
committed
Add angle steering support for Hyundai Ioniq 5 PE 2025+
This commit introduces angle-based steering control for the Hyundai Ioniq 5 PE (2025+ models) with Highway Driving Assist II (HDA2). It includes fingerprint updates, platform configuration, and control modifications to enable precise steering control and fault handling for CAN FD angle steering systems.
1 parent 353a2d3 commit ddfbf8e

File tree

9 files changed

+117
-23
lines changed

9 files changed

+117
-23
lines changed

opendbc/car/hyundai/carcontroller.py

+40-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import numpy as np
22
from opendbc.can.packer import CANPacker
3-
from opendbc.car import Bus, DT_CTRL, apply_driver_steer_torque_limits, common_fault_avoidance, make_tester_present_msg, structs
3+
from opendbc.car import Bus, DT_CTRL, apply_driver_steer_torque_limits, apply_std_steer_angle_limits, common_fault_avoidance, \
4+
make_tester_present_msg, structs
45
from opendbc.car.common.conversions import Conversions as CV
56
from opendbc.car.hyundai import hyundaicanfd, hyundaican
67
from opendbc.car.hyundai.carstate import CarState
@@ -16,9 +17,9 @@
1617

1718
# EPS faults if you apply torque while the steering angle is above 90 degrees for more than 1 second
1819
# All slightly below EPS thresholds to avoid fault
19-
MAX_ANGLE = 85
20-
MAX_ANGLE_FRAMES = 89
21-
MAX_ANGLE_CONSECUTIVE_FRAMES = 2
20+
MAX_FAULT_ANGLE = 85
21+
MAX_FAULT_ANGLE_FRAMES = 89
22+
MAX_FAULT_ANGLE_CONSECUTIVE_FRAMES = 2
2223

2324

2425
def process_hud_alert(enabled, fingerprint, hud_control):
@@ -53,33 +54,54 @@ def __init__(self, dbc_names, CP, CP_SP):
5354
self.CAN = CanBus(CP)
5455
self.params = CarControllerParams(CP)
5556
self.packer = CANPacker(dbc_names[Bus.pt])
56-
self.angle_limit_counter = 0
57+
self.car_fingerprint = CP.carFingerprint
5758

5859
self.accel_last = 0
5960
self.apply_torque_last = 0
60-
self.car_fingerprint = CP.carFingerprint
61+
self.apply_angle_last = 0
62+
self.lkas_max_torque = 0
6163
self.last_button_frame = 0
64+
self.angle_limit_counter = 0
6265

6366
def update(self, CC, CC_SP, CS, now_nanos):
6467
EsccCarController.update(self, CS)
6568
MadsCarController.update(self, self.CP, CC, CC_SP, self.frame)
6669
actuators = CC.actuators
6770
hud_control = CC.hudControl
6871

72+
# TODO: needed for angle control cars?
73+
# >90 degree steering fault prevention
74+
self.angle_limit_counter, apply_steer_req = common_fault_avoidance(abs(CS.out.steeringAngleDeg) >= MAX_FAULT_ANGLE, CC.latActive,
75+
self.angle_limit_counter, MAX_FAULT_ANGLE_FRAMES,
76+
MAX_FAULT_ANGLE_CONSECUTIVE_FRAMES)
77+
# Hold torque with induced temporary fault when cutting the actuation bit
78+
torque_fault = CC.latActive and not apply_steer_req
79+
80+
apply_torque = 0
81+
6982
# steering torque
70-
new_torque = int(round(actuators.torque * self.params.STEER_MAX))
71-
apply_torque = apply_driver_steer_torque_limits(new_torque, self.apply_torque_last, CS.out.steeringTorque, self.params)
83+
if not self.CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
84+
new_torque = int(round(actuators.torque * self.params.STEER_MAX))
85+
apply_torque = apply_driver_steer_torque_limits(new_torque, self.apply_torque_last, CS.out.steeringTorque, self.params)
7286

73-
# >90 degree steering fault prevention
74-
self.angle_limit_counter, apply_steer_req = common_fault_avoidance(abs(CS.out.steeringAngleDeg) >= MAX_ANGLE, CC.latActive,
75-
self.angle_limit_counter, MAX_ANGLE_FRAMES,
76-
MAX_ANGLE_CONSECUTIVE_FRAMES)
87+
# angle control
88+
else:
89+
if CS.out.steeringPressed:
90+
self.apply_angle_last = actuators.steeringAngleDeg
91+
self.apply_angle_last = apply_std_steer_angle_limits(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgoRaw,
92+
CS.out.steeringAngleDeg, CC.latActive, self.params.ANGLE_LIMITS)
93+
94+
# Similar to torque control driver torque override, we ramp up and down the max allowed torque,
95+
# but this is a single threshold for simplicity. It also matches the stock system behavior.
96+
if abs(CS.out.steeringTorque) > self.params.STEER_THRESHOLD:
97+
self.lkas_max_torque = max(self.lkas_max_torque - self.params.ANGLE_TORQUE_DOWN_RATE, self.params.ANGLE_MIN_TORQUE)
98+
else:
99+
# ramp back up on engage as well
100+
self.lkas_max_torque = min(self.lkas_max_torque + self.params.ANGLE_TORQUE_UP_RATE, self.params.ANGLE_MAX_TORQUE)
77101

78102
if not CC.latActive:
79103
apply_torque = 0
80-
81-
# Hold torque with induced temporary fault when cutting the actuation bit
82-
torque_fault = CC.latActive and not apply_steer_req
104+
self.lkas_max_torque = 0
83105

84106
self.apply_torque_last = apply_torque
85107

@@ -115,7 +137,8 @@ def update(self, CC, CC_SP, CS, now_nanos):
115137
lka_steering_long = lka_steering and self.CP.openpilotLongitudinalControl
116138

117139
# steering control
118-
can_sends.extend(hyundaicanfd.create_steering_messages(self.packer, self.CP, self.CAN, CC.enabled, apply_steer_req, apply_torque, self.lkas_icon))
140+
can_sends.extend(hyundaicanfd.create_steering_messages(self.packer, self.CP, self.CAN, CC.enabled, apply_steer_req, apply_torque,
141+
self.apply_angle_last, self.lkas_max_torque, self.lkas_icon))
119142

120143
# prevent LFA from activating on LKA steering cars by sending "no lane lines detected" to ADAS ECU
121144
if self.frame % 5 == 0 and lka_steering:
@@ -177,6 +200,7 @@ def update(self, CC, CC_SP, CS, now_nanos):
177200
new_actuators = actuators.as_builder()
178201
new_actuators.torque = apply_torque / self.params.STEER_MAX
179202
new_actuators.torqueOutputCan = apply_torque
203+
new_actuators.steeringAngleDeg = self.apply_angle_last
180204
new_actuators.accel = accel
181205

182206
self.frame += 1

opendbc/car/hyundai/carstate.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -243,16 +243,20 @@ def update_canfd(self, can_parsers) -> structs.CarState:
243243
ret.steeringTorqueEps = cp.vl["MDPS"]["STEERING_OUT_TORQUE"]
244244
ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > self.params.STEER_THRESHOLD, 5)
245245
ret.steerFaultTemporary = cp.vl["MDPS"]["LKA_FAULT"] != 0
246+
if self.CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
247+
ret.steerFaultTemporary = ret.steerFaultTemporary or cp.vl["MDPS"]["LKA_ANGLE_FAULT"] != 0
246248

247249
# TODO: alt signal usage may be described by cp.vl['BLINKERS']['USE_ALT_LAMP']
248250
left_blinker_sig, right_blinker_sig = "LEFT_LAMP", "RIGHT_LAMP"
249-
if self.CP.carFingerprint == CAR.HYUNDAI_KONA_EV_2ND_GEN:
251+
if self.CP.carFingerprint in (CAR.HYUNDAI_KONA_EV_2ND_GEN, CAR.HYUNDAI_IONIQ_5_PE):
250252
left_blinker_sig, right_blinker_sig = "LEFT_LAMP_ALT", "RIGHT_LAMP_ALT"
251253
ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["BLINKERS"][left_blinker_sig],
252254
cp.vl["BLINKERS"][right_blinker_sig])
253255
if self.CP.enableBsm:
254-
ret.leftBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["FL_INDICATOR"] != 0
255-
ret.rightBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["FR_INDICATOR"] != 0
256+
left_bsm_sig = "FL_INDICATOR_ALT" if self.CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING else "FL_INDICATOR"
257+
right_bsm_sig = "FR_INDICATOR_ALT" if self.CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING else "FR_INDICATOR"
258+
ret.leftBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"][left_bsm_sig] != 0
259+
ret.rightBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"][right_bsm_sig] != 0
256260

257261
# cruise state
258262
# CAN FD cars enable on main button press, set available if no TCS faults preventing engagement

opendbc/car/hyundai/fingerprints.py

+10
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,16 @@
10261026
b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.06 99211-GI010 230110',
10271027
],
10281028
},
1029+
CAR.HYUNDAI_IONIQ_5_PE: {
1030+
(Ecu.fwdRadar, 0x7d0, None): [
1031+
b'\xf1\x00NE__ RDR ----- 1.00 1.00 99110-PI000 ',
1032+
b'\xf1\x00NE__ RDR ----- 1.00 1.01 99110-GI500 '
1033+
],
1034+
(Ecu.fwdCamera, 0x7C4, None): [
1035+
b'\xf1\x00NE MFC AT USA LHD 1.00 1.01 99211-PI000 240905',
1036+
b'\xf1\x00NE MFC AT EUR LHD 1.00 1.03 99211-GI500 240809',
1037+
],
1038+
},
10291039
CAR.HYUNDAI_IONIQ_6: {
10301040
(Ecu.fwdRadar, 0x7d0, None): [
10311041
b'\xf1\x00CE__ RDR ----- 1.00 1.01 99110-KL000 ',

opendbc/car/hyundai/hyundaicanfd.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def CAM(self):
3535
return self._cam
3636

3737

38-
def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque, lkas_icon):
38+
def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque, apply_angle, angle_max_torque, lkas_icon):
3939
common_values = {
4040
"LKA_MODE": 2,
4141
"LKA_ICON": lkas_icon,
@@ -50,9 +50,25 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque,
5050
lkas_values = copy.copy(common_values)
5151
lkas_values["LKA_AVAILABLE"] = 0
5252

53+
# Angle control doesn't support using LFA yet
54+
if CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
55+
# TODO: HAS_LANE_SAFETY isn't used by the stock system
56+
lkas_values |= {
57+
"LKA_MODE": 0, # TODO: not used by the stock system
58+
"TORQUE_REQUEST": 0, # we don't use torque
59+
"STEER_REQ": 0, # we don't use torque
60+
# this goes 0 when LFA lane changes, 3 when LKA_ICON is >=green
61+
"LKA_AVAILABLE": 3 if lat_active else 0,
62+
"LKAS_ANGLE_CMD": apply_angle,
63+
"LKAS_ANGLE_ACTIVE": 2 if lat_active else 1,
64+
"LKAS_ANGLE_MAX_TORQUE": angle_max_torque if lat_active else 0,
65+
}
66+
5367
lfa_values = copy.copy(common_values)
5468
lfa_values["NEW_SIGNAL_1"] = 0
5569

70+
# For cars with an ADAS ECU (commonly HDA2), by sending LKAS actuation messages we're
71+
# telling the ADAS ECU to forward our steering and disable stock LFA lane centering.
5672
ret = []
5773
if CP.flags & HyundaiFlags.CANFD_LKA_STEERING:
5874
lkas_msg = "LKAS_ALT" if CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT else "LKAS"

opendbc/car/hyundai/interface.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, experime
4242
if lka_steering:
4343
# detect LKA steering
4444
ret.flags |= HyundaiFlags.CANFD_LKA_STEERING.value
45-
if 0x110 in fingerprint[CAN.CAM]:
45+
# we only have validated ALT messages for angle steering cars
46+
if 0x110 in fingerprint[CAN.CAM] or ret.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
4647
ret.flags |= HyundaiFlags.CANFD_LKA_STEERING_ALT.value
4748
else:
4849
# no LKA steering
@@ -72,6 +73,9 @@ def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, experime
7273
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.CANFD_ALT_BUTTONS.value
7374
if ret.flags & HyundaiFlags.CANFD_CAMERA_SCC:
7475
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.CAMERA_SCC.value
76+
if ret.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
77+
ret.steerControlType = structs.CarParams.SteerControlType.angle
78+
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.CANFD_ANGLE_STEERING.value
7579

7680
else:
7781
# Shared configuration for non CAN-FD cars
@@ -104,7 +108,9 @@ def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, experime
104108
ret.centerToFront = ret.wheelbase * 0.4
105109
ret.steerActuatorDelay = 0.1
106110
ret.steerLimitTimer = 0.4
107-
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
111+
112+
if not ret.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
113+
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
108114

109115
if ret.flags & HyundaiFlags.ALT_LIMITS:
110116
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.ALT_LIMITS.value

opendbc/car/hyundai/values.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from dataclasses import dataclass, field
33
from enum import Enum, IntFlag
44

5-
from opendbc.car import Bus, CarSpecs, DbcDict, PlatformConfig, Platforms, uds
5+
from opendbc.car import AngleSteeringLimits, Bus, CarSpecs, DbcDict, PlatformConfig, Platforms, uds
66
from opendbc.car.common.conversions import Conversions as CV
77
from opendbc.car.structs import CarParams
88
from opendbc.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Column
@@ -15,6 +15,21 @@ class CarControllerParams:
1515
ACCEL_MIN = -3.5 # m/s
1616
ACCEL_MAX = 2.0 # m/s
1717

18+
ANGLE_LIMITS: AngleSteeringLimits = AngleSteeringLimits(
19+
# LKAS angle command is unlimited, but LFA is limited to 176.7 deg (but does not fault if requesting above)
20+
180, # deg
21+
# seen changing at 0.2 deg/frame down, 0.1 deg/frame up at 100Hz
22+
([5, 25], [0.3, 0.15]),
23+
([5, 25], [0.36, 0.26]),
24+
)
25+
26+
# Stock LFA system is seen sending 250 max, but for LKAS events it's 175 max.
27+
# 250 can at least achieve 4 m/s^2, 80 corresponds to ~2.5 m/s^2
28+
ANGLE_MAX_TORQUE = 150 # The maximum amount of torque that will be allowed
29+
ANGLE_MIN_TORQUE = 25 # equivalent to ~0.8 m/s^2 of torque (based on ANGLE_MAX_TORQUE) when overriding
30+
ANGLE_TORQUE_UP_RATE = 2 # Indicates how fast the torque ramps up after user intervention.
31+
ANGLE_TORQUE_DOWN_RATE = 4 # Indicates how fast the torque ramps down during user intervention (handing off).
32+
1833
def __init__(self, CP):
1934
self.STEER_DELTA_UP = 3
2035
self.STEER_DELTA_DOWN = 7
@@ -66,6 +81,7 @@ class HyundaiSafetyFlags(IntFlag):
6681
CANFD_LKA_STEERING_ALT = 128
6782
FCEV_GAS = 256
6883
ALT_LIMITS_2 = 512
84+
CANFD_ANGLE_STEERING = 1024
6985
FLAG_HYUNDAI_ESCC = 8192
7086
FLAG_HYUNDAI_LONG_MAIN_CRUISE_TOGGLEABLE = 16384
7187

@@ -128,6 +144,7 @@ class HyundaiFlags(IntFlag):
128144

129145
ALT_LIMITS_2 = 2 ** 26
130146

147+
CANFD_ANGLE_STEERING = 2 ** 27
131148

132149
class Footnote(Enum):
133150
CANFD = CarFootnote(
@@ -356,6 +373,11 @@ class CAR(Platforms):
356373
CarSpecs(mass=1948, wheelbase=2.97, steerRatio=14.26, tireStiffnessFactor=0.65),
357374
flags=HyundaiFlags.EV,
358375
)
376+
HYUNDAI_IONIQ_5_PE = HyundaiCanFDPlatformConfig(
377+
[HyundaiCarDocs("Hyundai Ioniq 5 PE (with HDA II) 2025+", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_q]))],
378+
HYUNDAI_IONIQ_5.specs,
379+
flags=HyundaiFlags.EV | HyundaiFlags.CANFD_ANGLE_STEERING,
380+
)
359381
HYUNDAI_IONIQ_6 = HyundaiCanFDPlatformConfig(
360382
[HyundaiCarDocs("Hyundai Ioniq 6 (with HDA II) 2023-24", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_p]))],
361383
HYUNDAI_IONIQ_5.specs,

opendbc/car/tests/routes.py

+1
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ class CarTestRoute(NamedTuple):
144144
CarTestRoute("628935d7d3e5f4f7|2022-11-30--01-12-46", HYUNDAI.KIA_SORENTO_HEV_4TH_GEN), # plug-in hybrid
145145
CarTestRoute("9c917ba0d42ffe78|2020-04-17--12-43-19", HYUNDAI.HYUNDAI_PALISADE),
146146
CarTestRoute("05a8f0197fdac372|2022-10-19--14-14-09", HYUNDAI.HYUNDAI_IONIQ_5), # LKA steering
147+
CarTestRoute("e1107f9d04dfb1e2/00000455--9b2328ec73", HYUNDAI.HYUNDAI_IONIQ_5_PE), # LKA steering HDA2 LFA2
147148
CarTestRoute("eb4eae1476647463|2023-08-26--18-07-04", HYUNDAI.HYUNDAI_IONIQ_6, segment=6), # LKA steering
148149
CarTestRoute("3f29334d6134fcd4|2022-03-30--22-00-50", HYUNDAI.HYUNDAI_IONIQ_PHEV_2019),
149150
CarTestRoute("fa8db5869167f821|2021-06-10--22-50-10", HYUNDAI.HYUNDAI_IONIQ_PHEV),

opendbc/car/torque_data/override.toml

+3
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,6 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
8282
# Manually checked
8383
"HONDA_CIVIC_2022" = [2.5, 1.2, 0.15]
8484
"HONDA_HRV_3G" = [2.5, 1.2, 0.2]
85+
86+
# Hyundai/Kia/Genesis angle control
87+
"HYUNDAI_IONIQ_5_PE" = [nan, 3.0, nan]

opendbc/sunnypilot/car/car_list.json

+8
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,14 @@
11731173
"2024"
11741174
],
11751175
"package": "Highway Driving Assist"
1176+
},
1177+
"Hyundai Ioniq 5 PE (with HDA II) 2025+": {
1178+
"platform": "HYUNDAI_IONIQ_5_PE",
1179+
"make": "Hyundai",
1180+
"brand": "hyundai",
1181+
"model": "Ioniq 5 PE (with HDA II) 2025+",
1182+
"year": [],
1183+
"package": "Highway Driving Assist II"
11761184
},
11771185
"Hyundai Ioniq 6 (with HDA II) 2023-24": {
11781186
"platform": "HYUNDAI_IONIQ_6",

0 commit comments

Comments
 (0)