feat(hyundai): add EV9 alpha longitudinal support

This commit is contained in:
LowkeyNEXT
2026-07-19 17:54:54 -05:00
committed by firestar5683
parent ce86d9d05a
commit 42eef3f21c
6 changed files with 681 additions and 33 deletions
+197 -19
View File
@@ -8,8 +8,8 @@ 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, Buttons, CarControllerParams, CAR, CANFD_RADAR_LIVE_LONGITUDINAL_CAR, \
kia_ev6_gt_line_longitudinal_tuning
from opendbc.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, kia_ev6_gt_line_longitudinal_tuning
from opendbc.car.interfaces import CarControllerBase
from opendbc.car.vehicle_model import VehicleModel
from openpilot.common.params import Params
@@ -80,10 +80,16 @@ EV9_DRIVER_OVERRIDE_RECOVERY_ANGLE_BP = [0, EV9_DRIVER_OVERRIDE_RECOVERY_FRAMES
EV9_DRIVER_OVERRIDE_RECOVERY_ANGLE_V = [0.75, 2.0, 5.0]
EV9_DRIVER_OVERRIDE_RECOVERY_GAIN_V = [0.08, 0.20, 0.45]
EV9_DRIVER_OVERRIDE_RECOVERY_ALPHA = 0.02
EV9_STOP_REQUEST_SPEED = 0.47
EV9_STANDSTILL_DELAY_FRAMES = 178
EV9_STOP_RELEASE_DELAY_FRAMES = 6
BLINDSPOT_WARNING_FLASH_SAMPLES = 20
BLINDSPOT_WARNING_FLASH_ON_SAMPLES = 16
BLINDSPOT_WARNING_SOUND_SAMPLES = 36
def egmp_dynamic_longitudinal_tuning(CP) -> bool:
return CP.carFingerprint == CAR.HYUNDAI_IONIQ_6 or \
return CP.carFingerprint in (CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV9) or \
kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, getattr(CP, "carVin", ""))
@@ -119,6 +125,29 @@ class GenesisG90LongitudinalTuningState:
long_control_state_last: LongCtrlState = LongCtrlState.off
@dataclass(frozen=True)
class EV9LongitudinalTuningState:
stop_request: bool = False
cruise_standstill: bool = False
stop_request_frames: int = 0
release_frames: int = 0
@dataclass(frozen=True)
class BlindspotWarningOutput:
mirror_lamp_active: bool = False
sound_active: bool = False
@dataclass
class BlindspotWarningState:
flash_phase: int = 0
mirror_warning_active: bool = False
escalated_prev: bool = False
sound_remaining: int = 0
sound_armed: bool = True
def _jerk_limited_integrator(desired_accel: float, last_accel: float, jerk_upper: float, jerk_lower: float) -> float:
step = (jerk_upper if desired_accel >= last_accel else jerk_lower) * DT_CTRL * 5.0
return float(np.clip(desired_accel, last_accel - step, last_accel + step))
@@ -131,14 +160,93 @@ def _calculate_ioniq_6_dynamic_lower_jerk(accel_error: float) -> float:
return IONIQ_6_LONG_MIN_JERK
def should_track_stop_accel_directly(stopping: bool, v_ego: float,
accel_cmd: float, actual_accel: float) -> bool:
return bool(stopping and v_ego > EV6_GT_LINE_STOP_BRAKE_CAP_MAX_SPEED and accel_cmd < actual_accel)
def should_use_ev6_gt_line_stop_direct_tracking(ev6_gt_line: bool, stopping: bool, v_ego: float,
accel_cmd: float, actual_accel: float) -> bool:
return bool(ev6_gt_line and stopping and v_ego > EV6_GT_LINE_STOP_BRAKE_CAP_MAX_SPEED and accel_cmd < actual_accel)
def update_ev9_longitudinal_tuning(state: EV9LongitudinalTuningState, enabled: bool,
stopping: bool, v_ego: float) -> EV9LongitudinalTuningState:
if not enabled:
return EV9LongitudinalTuningState()
if stopping:
if not state.stop_request and v_ego > EV9_STOP_REQUEST_SPEED:
return EV9LongitudinalTuningState()
frames = state.stop_request_frames + 1 if state.stop_request else 0
return EV9LongitudinalTuningState(
stop_request=True,
cruise_standstill=frames >= EV9_STANDSTILL_DELAY_FRAMES,
stop_request_frames=frames,
)
if state.stop_request:
release_frames = state.release_frames + 1
if release_frames <= EV9_STOP_RELEASE_DELAY_FRAMES:
return EV9LongitudinalTuningState(
stop_request=True,
cruise_standstill=False,
stop_request_frames=state.stop_request_frames,
release_frames=release_frames,
)
return EV9LongitudinalTuningState()
def update_blindspot_warning(state: BlindspotWarningState, escalated: bool,
blinker: bool) -> BlindspotWarningOutput:
if not blinker:
state.flash_phase = 0
state.mirror_warning_active = False
state.escalated_prev = False
state.sound_remaining = 0
state.sound_armed = True
return BlindspotWarningOutput()
rising = escalated and not state.escalated_prev
if rising:
state.flash_phase = 0
state.mirror_warning_active = True
if state.sound_armed:
state.sound_remaining = BLINDSPOT_WARNING_SOUND_SAMPLES
state.sound_armed = False
elif escalated:
state.flash_phase = (state.flash_phase + 1) % BLINDSPOT_WARNING_FLASH_SAMPLES
state.mirror_warning_active = True
elif state.mirror_warning_active and state.flash_phase < BLINDSPOT_WARNING_FLASH_ON_SAMPLES - 1:
state.flash_phase += 1
else:
state.flash_phase = 0
state.mirror_warning_active = False
state.escalated_prev = escalated
sound_active = state.sound_remaining > 0
if state.sound_remaining > 0:
state.sound_remaining -= 1
return BlindspotWarningOutput(
mirror_lamp_active=state.mirror_warning_active and state.flash_phase < BLINDSPOT_WARNING_FLASH_ON_SAMPLES,
sound_active=sound_active,
)
def reset_egmp_longitudinal_tuning(state: Ioniq6LongitudinalTuningState) -> Ioniq6LongitudinalTuningState:
state.desired_accel = 0.0
state.actual_accel = 0.0
state.accel_last = 0.0
state.jerk_upper = 0.0
state.jerk_lower = 0.0
state.launch_active = False
return state
def update_ioniq_6_longitudinal_tuning(state: Ioniq6LongitudinalTuningState, accel_cmd: float, v_ego: float, a_ego: float,
long_control_state: LongCtrlState, long_active: bool,
ev6_gt_line: bool = False) -> Ioniq6LongitudinalTuningState:
ev6_gt_line: bool = False, low_speed_stop_brake_cap: bool = False) -> Ioniq6LongitudinalTuningState:
starting = long_control_state == LongCtrlState.starting
stopping = long_control_state == LongCtrlState.stopping
restart_from_stop = state.long_control_state_last in (LongCtrlState.stopping, LongCtrlState.starting) and \
@@ -180,7 +288,8 @@ def update_ioniq_6_longitudinal_tuning(state: Ioniq6LongitudinalTuningState, acc
state.jerk_lower = min(dynamic_lower_jerk, lower_speed_limit)
if state.stopping:
stop_brake_cap_max_speed = EV6_GT_LINE_STOP_BRAKE_CAP_MAX_SPEED if ev6_gt_line else IONIQ_6_STOP_BRAKE_CAP_MAX_SPEED
stop_brake_cap_max_speed = EV6_GT_LINE_STOP_BRAKE_CAP_MAX_SPEED if ev6_gt_line or low_speed_stop_brake_cap else \
IONIQ_6_STOP_BRAKE_CAP_MAX_SPEED
if v_ego <= stop_brake_cap_max_speed:
stop_brake_cap = float(np.interp(v_ego, IONIQ_6_STOP_BRAKE_CAP_SPEED_BP, IONIQ_6_STOP_BRAKE_CAP_ACCEL_V))
state.desired_accel = min(0.0, max(accel_cmd, stop_brake_cap))
@@ -355,6 +464,10 @@ class CarController(CarControllerBase):
self.ecu_disable_failed = False
self._ecu_disable_checked = False
self._params = Params()
if CP.carFingerprint == CAR.KIA_EV9:
self._ev9_long_tuning = EV9LongitudinalTuningState()
self._left_blindspot_warning = BlindspotWarningState()
self._right_blindspot_warning = BlindspotWarningState()
self.long_active_ecu = self.CP.openpilotLongitudinalControl
self._ioniq_6_lane_change_ui_side = None
self._ioniq_6_lane_change_ui_frames = 0
@@ -549,6 +662,9 @@ class CarController(CarControllerBase):
use_egmp_dynamic_long_tuning = egmp_dynamic_longitudinal_tuning(self.CP) and self.long_active_ecu and \
actuators.longControlState in (LongCtrlState.starting, LongCtrlState.pid, LongCtrlState.stopping)
is_ev6_gt_line = kia_ev6_gt_line_longitudinal_tuning(self.CP.carFingerprint, getattr(self.CP, "carVin", ""))
is_ev9 = self.CP.carFingerprint == CAR.KIA_EV9
if is_ev9 and (self._ev9_long_tuning.stop_request or not CC.enabled or CC.cruiseControl.override):
self._ioniq_6_long_tuning = reset_egmp_longitudinal_tuning(self._ioniq_6_long_tuning)
if should_reset_ev6_gt_line_longitudinal_tuning(self.CP, actuators.longControlState):
self._ioniq_6_long_tuning = reset_ev6_gt_line_longitudinal_tuning(self._ioniq_6_long_tuning, self.CP,
actuators.longControlState)
@@ -556,7 +672,8 @@ class CarController(CarControllerBase):
self._ioniq_6_long_tuning = update_ioniq_6_longitudinal_tuning(self._ioniq_6_long_tuning, accel_cmd,
CS.out.vEgo, CS.out.aEgo,
actuators.longControlState, self.long_active_ecu,
ev6_gt_line=is_ev6_gt_line)
ev6_gt_line=is_ev6_gt_line,
low_speed_stop_brake_cap=is_ev9)
use_egmp_smoothed_accel = use_egmp_dynamic_long_tuning and (
accel_cmd >= self._ioniq_6_long_tuning.actual_accel or
self._ioniq_6_long_tuning.launch_active or
@@ -565,6 +682,9 @@ class CarController(CarControllerBase):
if should_use_ev6_gt_line_stop_direct_tracking(is_ev6_gt_line, self._ioniq_6_long_tuning.stopping,
CS.out.vEgo, accel_cmd, self._ioniq_6_long_tuning.actual_accel):
use_egmp_smoothed_accel = False
if is_ev9 and should_track_stop_accel_directly(self._ioniq_6_long_tuning.stopping, CS.out.vEgo,
accel_cmd, self._ioniq_6_long_tuning.actual_accel):
use_egmp_smoothed_accel = False
if use_egmp_dynamic_long_tuning:
if use_egmp_smoothed_accel:
accel = self._ioniq_6_long_tuning.actual_accel
@@ -706,6 +826,8 @@ class CarController(CarControllerBase):
preserve_stock_lkas = bool(self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING) and not self.long_active_ecu
angle_lkas_alt = bool(self.CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING and
self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT)
ccnc_angle_long = self.CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR and \
self.CP.flags & HyundaiFlags.CCNC and angle_lkas_alt and self.long_active_ecu
steering_msg_active = apply_steer_req
if angle_lkas_alt:
# Angle LKAS_ALT cars fault if the angle-steering status drops inactive during torque limiting.
@@ -717,23 +839,33 @@ class CarController(CarControllerBase):
if angle_lkas_alt:
steering_msg_active = bool(steering_msg_active and drive_gear)
forward_stock_lkas = angle_lkas_alt and not (drive_gear and (CC.latActive or CC.enabled))
if not forward_stock_lkas:
if not forward_stock_lkas and not ccnc_angle_long:
can_sends.extend(hyundaicanfd.create_steering_messages(self.packer, self.CP, self.CAN, CC.enabled,
steering_msg_active, apply_torque, apply_angle,
CS.stock_lfa_msg,
CS.stock_lkas_msg if preserve_stock_lkas else None,
lka_icon=lka_icon))
direct_steering_active = ccnc_angle_long and drive_gear and CC.latActive and not CS.angle_steering_fault
if ccnc_angle_long and drive_gear:
can_sends.append(hyundaicanfd.create_angle_adas_cmd(
self.packer, self.CAN,
apply_angle if direct_steering_active else CS.angle_steering_angle,
direct_steering_active, apply_torque if direct_steering_active else 0.0,
))
if ccnc_angle_long and not drive_gear:
can_sends.extend(hyundaicanfd.create_inactive_angle_steering_messages(self.packer, self.CAN,
CS.angle_steering_angle))
# 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:
suppress_lfa = bool(lka_steering and CC.latActive and drive_gear)
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,
self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT))
# LFA and HDA icons
if self.frame % 5 == 0 and (not lka_steering or lka_steering_long):
if self.frame % 5 == 0 and (not lka_steering or lka_steering_long) and not ccnc_angle_long:
if ccnc_non_hda2:
can_sends.extend(hyundaicanfd.create_ccnc(self.packer, self.CAN, self.long_active_ecu, CC.enabled, CC.hudControl,
CC.leftBlinker, CC.rightBlinker, CS.msg_161, CS.msg_162, CS.msg_1b5,
@@ -769,13 +901,41 @@ class CarController(CarControllerBase):
if self.long_active_ecu:
if lka_steering:
can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame))
# Ioniq 5/6: front radar treats ADAS_DRV's 0x100 broadcast as its host heartbeat
# and stops publishing object tracks when it disappears. Spoof it periodically on
# PT bus so the radar keeps tracking.
if self.CP.carFingerprint in CANFD_RADAR_LIVE_LONGITUDINAL_CAR and self.frame % 4 == 0:
can_sends.append(hyundaicanfd.create_accelerator_brake_alt_spoof(0, self.frame // 4, CS.out.brakePressed,
CS.out.gasPressed, self.CP.carFingerprint))
if ccnc_angle_long:
left_escalated = CS.left_blindspot_from_radar and CC.leftBlinker and not CC.rightBlinker
right_escalated = CS.right_blindspot_from_radar and CC.rightBlinker and not CC.leftBlinker
left_warning = BlindspotWarningOutput()
right_warning = BlindspotWarningOutput()
if self.frame % 5 == 0:
left_warning = update_blindspot_warning(
self._left_blindspot_warning, left_escalated, CC.leftBlinker,
)
right_warning = update_blindspot_warning(
self._right_blindspot_warning, right_escalated, CC.rightBlinker,
)
steering_available = CC.latActive or CC.enabled
steering_active = direct_steering_active and apply_steer_req and not CS.out.steeringPressed
adrv_messages = hyundaicanfd.create_ccnc_adrv_messages(
self.packer, self.CP, self.CAN, self.frame, CC.enabled, CS.out.cruiseState.available, CC.hudControl,
CS.out, CS.is_metric, steering_available, steering_active,
CS.left_blindspot_from_radar, CS.right_blindspot_from_radar,
drive_gear=drive_gear,
hba_icon=CS.hba_icon,
left_escalated=left_escalated, right_escalated=right_escalated,
left_warning_lamp=left_warning.mirror_lamp_active,
right_warning_lamp=right_warning.mirror_lamp_active,
left_sound_active=left_warning.sound_active, right_sound_active=right_warning.sound_active,
)
else:
adrv_messages = hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame)
can_sends.extend(adrv_messages)
# 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:
can_sends.append(hyundaicanfd.create_accelerator_brake_alt_spoof(0, self.frame // radar_heartbeat_step,
CS.out.brakePressed, CS.out.gasPressed,
self.CP.carFingerprint))
elif not ccnc_non_hda2:
can_sends.extend(hyundaicanfd.create_fca_warning_light(self.packer, self.CAN, self.frame))
if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6 and self.frame % 5 == 0:
@@ -810,9 +970,27 @@ class CarController(CarControllerBase):
if use_egmp_smoothed_accel:
acc_kwargs["jerk_lower"] = self._ioniq_6_long_tuning.jerk_lower
acc_kwargs["jerk_upper"] = self._ioniq_6_long_tuning.jerk_upper
can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CAN, CC.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override,
set_speed_in_units, hud_control, cruise_info=CS.cruise_info if ccnc_non_hda2 else None,
**acc_kwargs))
if ccnc_angle_long:
self._ev9_long_tuning = update_ev9_longitudinal_tuning(
self._ev9_long_tuning, CC.enabled and not CC.cruiseControl.override,
CC.actuators.longControlState == LongCtrlState.stopping, float(CS.out.vEgo),
)
if self._ev9_long_tuning.stop_request or not CC.enabled or CC.cruiseControl.override:
self._ioniq_6_long_tuning = reset_egmp_longitudinal_tuning(self._ioniq_6_long_tuning)
accel = 0.0
can_sends.append(hyundaicanfd.create_ccnc_acc_control(
self.packer, self.CAN, CC.enabled, accel,
self._ev9_long_tuning.stop_request, self._ev9_long_tuning.cruise_standstill, CC.cruiseControl.override,
set_speed_in_units, int(CS.out.cruiseState.available), lead_distance, lead_rel_speed, lead_visible,
float(CS.out.vEgo),
jerk_lower=acc_kwargs["jerk_lower"],
jerk_upper=acc_kwargs["jerk_upper"],
))
else:
can_sends.append(hyundaicanfd.create_acc_control(
self.packer, self.CAN, CC.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override,
set_speed_in_units, hud_control, cruise_info=CS.cruise_info if ccnc_non_hda2 else None, **acc_kwargs,
))
self.accel_last = accel
else:
# button presses
+30 -2
View File
@@ -8,6 +8,7 @@ from opendbc.car import Bus, create_button_events, structs
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.values import HyundaiFlags, HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags, CAR, DBC, Buttons, CarControllerParams, \
CANFD_ANGLE_LONGITUDINAL_CAR, CANFD_CORNER_RADAR_BSM_CAR, \
hyundai_cancel_button_enables_cruise, ALT_BUS_LDA_BUTTON_CARS, ALT_BUS_LDA_BUTTON_SWL_STAT_CARS
from opendbc.car.interfaces import CarStateBase
@@ -136,6 +137,11 @@ class CarState(CarStateBase):
self.blindspots_front_corner_1_ts = 0
self.left_blindspot_from_radar = False
self.right_blindspot_from_radar = False
if CP.carFingerprint == CAR.KIA_EV9:
self.hba_icon = 0
self.main_cruise_on = False
self.angle_steering_angle = 0.0
self.angle_steering_fault = False
# On some cars, CLU15->CF_Clu_VehicleSpeed can oscillate faster than the dash updates. Sample at 5 Hz
self.cluster_speed = 0
@@ -149,6 +155,12 @@ class CarState(CarStateBase):
# Main button also can trigger an engagement on these cars
return any(btn in ENABLE_BUTTONS for btn in self.cruise_buttons) or any(self.main_buttons)
def update_main_cruise(self, ret: structs.CarState) -> bool:
if any(be.type == ButtonType.mainCruise and be.pressed for be in ret.buttonEvents):
self.main_cruise_on = not self.main_cruise_on
return bool(ret.cruiseState.available and self.main_cruise_on)
def create_cruise_button_events(self, cur_button: int, prev_button: int) -> list[structs.CarState.ButtonEvent]:
if cur_button != prev_button and prev_button != Buttons.CANCEL and cur_button == Buttons.CANCEL:
self.cancel_button_enable_in_progress = (
@@ -448,6 +460,10 @@ class CarState(CarStateBase):
ret.steeringTorqueEps = cp.vl["MDPS"]["STEERING_OUT_TORQUE"]
ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > self.params.STEER_THRESHOLD, 5)
ret.steerFaultTemporary = cp.vl["MDPS"]["LKA_FAULT"] != 0
if self.CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR:
self.angle_steering_angle = cp.vl["MDPS"]["STEERING_ANGLE"]
self.angle_steering_fault = cp.vl["MDPS"]["LKA_ANGLE_FAULT"] != 0
ret.steerFaultTemporary = ret.steerFaultTemporary or self.angle_steering_fault
ccnc_non_hda2 = self.CP.flags & HyundaiFlags.CCNC and not self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING
if ccnc_non_hda2:
@@ -463,11 +479,12 @@ class CarState(CarStateBase):
cp.vl["BLINKERS"][right_blinker_sig])
self.left_blindspot_from_radar = False
self.right_blindspot_from_radar = False
if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6:
corner_radar_bsm = self.CP.carFingerprint in CANFD_CORNER_RADAR_BSM_CAR
if corner_radar_bsm:
self.left_blindspot_from_radar, self.right_blindspot_from_radar = decode_ioniq_6_blindspot_radar_state(
cp.vl["BLINDSPOTS_FRONT_CORNER_2"]["SIDE_DETECT_STATE"])
if self.CP.enableBsm:
if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6:
if corner_radar_bsm:
ret.leftBlindspot = (bool(cp.vl["BLINDSPOTS_REAR_CORNERS"]["BCW_LtIndSta"]) or
self.left_blindspot_from_radar)
ret.rightBlindspot = (bool(cp.vl["BLINDSPOTS_REAR_CORNERS"]["BCW_RtIndSta"]) or
@@ -537,6 +554,9 @@ class CarState(CarStateBase):
self.stock_lfa_msg = copy.copy(cp.vl["LFA"])
if cp.ts_nanos["LFAHDA_CLUSTER"]["CHECKSUM"] > 0:
self.stock_lfahda_cluster_msg = copy.copy(cp.vl["LFAHDA_CLUSTER"])
if self.CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR and cp.ts_nanos["FR_CMR_01_10ms"]["FR_CMR_Crc1Val"] > 0:
hba_icon = int(cp.vl["FR_CMR_01_10ms"]["HBA_IndLmpReq"])
self.hba_icon = hba_icon if hba_icon in (1, 2) else 0
if cp.ts_nanos["BLINKER_STALKS"]["CHECKSUM_MAYBE"] > 0:
self.stock_blinker_stalks_ts = cp.ts_nanos["BLINKER_STALKS"]["CHECKSUM_MAYBE"]
@@ -544,6 +564,8 @@ class CarState(CarStateBase):
*create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise}),
*create_button_events(self.lda_button, prev_lda_button, {1: ButtonType.lkas}),
*create_button_events(self.left_paddle, prev_left_paddle, {1: ButtonType.altButton2})]
if self.CP.openpilotLongitudinalControl and self.CP.carFingerprint == CAR.KIA_EV9:
ret.cruiseState.available = self.update_main_cruise(ret)
ret.blockPcmEnable = not self.recent_button_interaction()
@@ -592,6 +614,12 @@ class CarState(CarStateBase):
("LFAHDA_CLUSTER", 0), # optional: carries cluster icon state on some variants
("BLINKER_STALKS", 0), # optional: some trims publish live stalk/light state on ECAN during turn camera events
]
if CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR and CP.enableBsm:
# Keep the suppressed ADAS BSM output optional.
msgs.append(("BLINDSPOTS_REAR_CORNERS", 0))
if CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR:
msgs.append(("BLINDSPOTS_FRONT_CORNER_2", 0))
msgs.append(("FR_CMR_01_10ms", 0))
if CP.flags & HyundaiFlags.EV:
msgs.append(("DRIVE_MODE_EV", 0)) # optional: not all CAN-FD EV variants publish drive mode
msgs.append(("MANUAL_SPEED_LIMIT_ASSIST", 0)) # optional: used for non-adaptive cruise state and Ioniq 6 i-Pedal latch detection
@@ -3,7 +3,7 @@ import numpy as np
from opendbc.car import CanBusBase, CanData
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.crc import CRC16_XMODEM
from opendbc.car.hyundai.values import HyundaiFlags
from opendbc.car.hyundai.values import HyundaiFlags, CAR
def _set_value(msg: bytearray, sig, ival: int) -> None:
@@ -92,6 +92,10 @@ def _create_angle_adas_cmd_msg(packer, CAN, apply_angle: float, lat_active: bool
return packer.make_can_msg("ADAS_CMD_35_10ms", CAN.ECAN, values)
def create_angle_adas_cmd(packer, CAN, apply_angle: float, lat_active: bool, torque_reduction_gain: float):
return _create_angle_adas_cmd_msg(packer, CAN, apply_angle, lat_active, torque_reduction_gain)
def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque, apply_angle,
lfa_base_values=None, lkas_base_values=None, lka_icon=None):
if lka_icon is None:
@@ -204,6 +208,25 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque,
return ret
def create_inactive_angle_steering_messages(packer, CAN, steering_angle: float):
lfa_values = {
"LKA_MODE": 2,
"LKA_ICON": 1,
"TORQUE_REQUEST": 0,
"LKA_ASSIST": 0,
"STEER_REQ": 0,
"STEER_MODE": 0,
"HAS_LANE_SAFETY": 0,
"NEW_SIGNAL_1": 0,
"NEW_SIGNAL_2": 0,
"DAMP_FACTOR": 100,
}
return [
packer.make_can_msg("LFA", CAN.ECAN, lfa_values),
create_angle_adas_cmd(packer, CAN, steering_angle, False, 0.0),
]
def create_suppress_lfa(packer, CAN, lfa_block_msg, lka_steering_alt):
suppress_msg = "CAM_0x362" if lka_steering_alt else "CAM_0x2a4"
msg_bytes = 32 if lka_steering_alt else 24
@@ -397,6 +420,35 @@ def create_blindspot_status_messages(packer, CAN, rear_values, front_corner_valu
]
def create_ccnc_blindspot_status_messages(packer, CP, CAN, counter, left_blindspot=False, right_blindspot=False,
left_escalated=False, right_escalated=False, drive_gear=False,
left_warning_lamp=False, right_warning_lamp=False,
left_sound_active=False, right_sound_active=False):
left_state = 2 if left_blindspot and left_escalated else (1 if left_blindspot else 0)
right_state = 2 if right_blindspot and right_escalated else (1 if right_blindspot else 0)
left_osm_state = 2 if left_warning_lamp else 1 if left_state == 1 else 0
right_osm_state = 2 if right_warning_lamp else 1 if right_state == 1 else 0
desired_fields = {
"BCW_IndSta": 1,
"BCA_OnOffEquip2Sta": 2,
"BCA_Sta": int(drive_gear),
"BCW_LtIndSta": left_state,
"BCW_RtIndSta": right_state,
"BCW_LtSndWrngSta": int(left_sound_active),
"BCW_RtSndWrngSta": int(right_sound_active),
"OSMrrLamp_LtIndSta": left_osm_state,
"OSMrrLamp_RtIndSta": right_osm_state,
}
return [
_create_ccnc_adrv_message_with_signals(
packer, CP, CAN, 0x1BA, counter, "BLINDSPOTS_REAR_CORNERS", desired_fields,
),
# No retained radar input reproduces the stock RCTA target decision across routes.
_create_ccnc_adrv_message(CP.carFingerprint, 0x1E5, CAN.ECAN, counter),
]
IONIQ_6_CLUSTER_BLINDSPOT_31A = {
"right": (
bytes.fromhex("fa7c10f0f0ffff03898aff0b0a8678ff000000007e0055550000000000000000"),
@@ -741,6 +793,30 @@ def create_adrv_messages(packer, CAN, frame):
return ret
def create_ccnc_adrv_messages(packer, CP, CAN, frame, enabled, main_cruise_enabled, hud, out, is_metric,
steering_available, steering_active, left_blindspot, right_blindspot,
drive_gear=False,
hba_icon=0,
left_escalated=False, right_escalated=False,
left_warning_lamp=False, right_warning_lamp=False,
left_sound_active=False, right_sound_active=False):
ret = [
_create_ccnc_adrv_message(CP.carFingerprint, address, CAN.ECAN, frame // period)
for address, period in _CCNC_ADRV_PERIODS[CP.carFingerprint].items() if frame % period == 0
]
if frame % 5 == 0:
ret.extend(create_ccnc_angle_long_status_messages(
packer, CP, CAN, frame // 5, enabled, main_cruise_enabled, hud, out, is_metric,
steering_available, steering_active, hba_icon,
))
ret.extend(create_ccnc_blindspot_status_messages(
packer, CP, CAN, frame // 5, left_blindspot, right_blindspot, left_escalated, right_escalated,
drive_gear,
left_warning_lamp, right_warning_lamp, left_sound_active, right_sound_active,
))
return ret
def hkg_can_fd_checksum(address: int, sig, d: bytearray) -> int:
crc = 0
for i in range(2, len(d)):
@@ -767,10 +843,39 @@ def hkg_can_fd_checksum(address: int, sig, d: bytearray) -> int:
# brake, and accelerator bits are updated for the radar heartbeat.
_ACCEL_BRAKE_ALT_TEMPLATE = bytes.fromhex("000000020000fcff000000000020000055ff000068000000")
_KIA_EV9_ACCEL_BRAKE_ALT_TEMPLATE = bytes.fromhex("00000000ff006f00e80400001201030055ffff0000000000")
# Neutral bodies verified across stock and successful suppression routes. Only
# rolling integrity fields and the decoded state above are changed at runtime.
_CCNC_ADRV_TEMPLATES = {
CAR.KIA_EV9: {
0x160: bytes.fromhex("0000000100000000fffc0100a8001000"),
0x1DA: bytes.fromhex("0000002200110000000000000000000000000000000000000000000000000000"),
0x1EA: bytes.fromhex("000000080000000000000000000000ff000000000000000000000000000f0f00"),
0x200: bytes.fromhex("00000014801a0000"),
0x345: bytes.fromhex("0000001500560000"),
0x161: bytes.fromhex("0000000000000000c0fff0c003000040000000000000000000ff000000000000"),
0x162: bytes.fromhex("0000002700000000000000000000000000000000000000000000000000000000"),
0x1BA: bytes.fromhex("00000000000000880200000000000000000100000000000f"),
0x1E5: bytes.fromhex("00000000000000000000220300000080"),
0x1E0: bytes.fromhex("00000002000000000000000000000000"),
0x38C: bytes.fromhex("000000f71f000000000000000000000000000000000000000000000000000000"),
},
}
_CCNC_ADRV_PERIODS = {
CAR.KIA_EV9: {
0x160: 2,
0x1DA: 100,
0x1EA: 5,
0x200: 5,
0x345: 20,
0x1E0: 5,
0x38C: 20,
},
}
def create_accelerator_brake_alt_spoof(bus: int, counter: int, brake_pressed: bool, accelerator_pressed: bool,
car_fingerprint=None) -> CanData:
template = _KIA_EV9_ACCEL_BRAKE_ALT_TEMPLATE if str(car_fingerprint) == "KIA_EV9" else _ACCEL_BRAKE_ALT_TEMPLATE
template = _KIA_EV9_ACCEL_BRAKE_ALT_TEMPLATE if car_fingerprint == CAR.KIA_EV9 else _ACCEL_BRAKE_ALT_TEMPLATE
d = bytearray(template)
d[2] = counter & 0xFF # COUNTER (bit 16, 8-bit)
d[4] = (d[4] & ~0x01) | (0x01 if brake_pressed else 0x00) # BRAKE_PRESSED (bit 32)
@@ -779,3 +884,111 @@ def create_accelerator_brake_alt_spoof(bus: int, counter: int, brake_pressed: bo
d[0] = crc & 0xFF
d[1] = (crc >> 8) & 0xFF
return CanData(0x100, bytes(d), bus)
def _create_ccnc_adrv_message(car_fingerprint, address: int, bus: int, counter: int) -> CanData:
d = bytearray(_CCNC_ADRV_TEMPLATES[car_fingerprint][address])
d[2] = counter & 0xFF
crc = hkg_can_fd_checksum(address, None, d)
d[0] = crc & 0xFF
d[1] = (crc >> 8) & 0xFF
return CanData(address, bytes(d), bus)
def _set_ccnc_message_signals(packer, message_name: str, dat: bytearray, values: dict) -> None:
dbc_msg = packer.dbc.name_to_msg[message_name]
for name, value in values.items():
sig = dbc_msg.sigs[name]
ival = int(np.floor((value - sig.offset) / sig.factor + 0.5))
if ival < 0:
ival = (1 << sig.size) + ival
_set_value(dat, sig, ival)
def _create_ccnc_adrv_message_with_signals(packer, CP, CAN, address: int, counter: int,
message_name: str, values: dict) -> CanData:
msg = _create_ccnc_adrv_message(CP.carFingerprint, address, CAN.ECAN, counter)
dat = bytearray(msg.dat)
# Update decoded fields in the verified neutral payload.
_set_ccnc_message_signals(packer, message_name, dat, values)
crc = hkg_can_fd_checksum(address, None, dat)
dat[0] = crc & 0xFF
dat[1] = (crc >> 8) & 0xFF
return CanData(address, bytes(dat), CAN.ECAN)
def create_ccnc_acc_control(packer, CAN, enabled: bool, accel: float,
stop_request: bool, cruise_standstill: bool, gas_override: bool, set_speed: float,
main_mode_acc: int, lead_distance: float, lead_rel_speed: float, lead_visible: bool,
v_ego: float, jerk_lower: float = 0.7, jerk_upper: float = 0.7):
if not enabled or gas_override or stop_request:
accel = 0.0
lead_visible = bool(enabled and lead_visible)
desired_headway = min(max(round(1.625 * max(v_ego, 0.0), 1), 3.5), 204.6) if enabled else 204.6
values = {
"ACCMode": 0 if not enabled else (2 if gas_override else 1),
"MainMode_ACC": int(bool(main_mode_acc)),
"StopReq": 1 if stop_request and enabled else 0,
"CRUISE_STANDSTILL": 1 if cruise_standstill and stop_request and enabled else 0,
"aReqValue": accel,
"aReqRaw": accel,
"VSetDis": set_speed,
"JerkLowerLimit": jerk_lower if enabled else 1.0,
"JerkUpperLimit": jerk_upper if enabled else 3.0,
"ACC_ObjDist": float(np.clip(lead_distance, 0.0, 204.7)) if lead_visible else 204.6,
"ACC_ObjRelSpd": float(np.clip(lead_rel_speed, -16.4, 34.7)) if lead_visible else 34.6,
"ObjValid": 0 if lead_visible else 1,
"OBJ_STATUS": 2 if enabled and lead_visible else 0,
"NEW_SIGNAL_3": 2 if lead_visible else 0,
"NEW_SIGNAL_15": desired_headway,
"SET_ME_2": 4,
"SET_ME_3": 3,
"SET_ME_TMP_64": 0x64,
# Stock CCNC LKA-long routes use raw 7. The DBC's physical range is stale.
"DISTANCE_SETTING": 7 if enabled else 0,
}
return packer.make_can_msg("SCC_CONTROL", CAN.ECAN, values)
def create_ccnc_angle_long_status_messages(packer, CP, CAN, counter: int, enabled: bool = False,
main_cruise_enabled: bool = False, hud=None, out=None,
is_metric: bool = True, steering_available: bool = False,
steering_active: bool = False, hba_icon: int = 0) -> list[CanData]:
cruise_speed = round(out.vCruiseCluster * (1 if is_metric else CV.KPH_TO_MPH)) if out is not None else 0
display_speed = (40 if is_metric else 25) if cruise_speed > (145 if is_metric else 90) else max(cruise_speed, 0)
main_standby = bool(main_cruise_enabled and not enabled)
values_161 = {
"FCA_ICON": 1, # orange: FCA unavailable
"FCA_ALT_ICON": 0,
"FCA_IMAGE": 0,
"ALERTS_1": 0,
"ALERTS_2": 0,
"ALERTS_3": 0,
"ALERTS_4": 0,
"ALERTS_5": 0,
"SOUNDS_1": 0,
"SOUNDS_2": 0,
"SOUNDS_3": 0,
"SOUNDS_4": 0,
"LFA_ICON": (2 if steering_active else 1) if steering_available else 0,
"HBA_ICON": hba_icon if hba_icon in (1, 2) else 0,
"HDA_ICON": 2 if enabled else 1 if main_standby else 0,
"TARGET": 3 if enabled else 0,
"SETSPEED": 3 if enabled else 1 if main_standby else 0,
"SETSPEED_HUD": 2 if enabled else 1 if main_standby else 0,
"SETSPEED_SPEED": display_speed if enabled or main_standby else 255,
"DISTANCE": hud.leadDistanceBars if enabled and hud is not None else 0,
"DISTANCE_SPACING": 3 if enabled or main_standby else 0,
"DISTANCE_CAR": 2 if enabled else 1 if main_standby else 0,
}
values_162 = {fault: 0 for fault in (
"FAULT_FSS", "FAULT_FCA", "FAULT_LSS", "FAULT_SLA", "FAULT_HDA", "FAULT_DAS", "FAULT_LFA", "FAULT_DAW",
"FAULT_HBA", "FAULT_ESS",
)}
values_162["VIBRATE"] = 0
return [
_create_ccnc_adrv_message_with_signals(packer, CP, CAN, 0x161, counter, "CCNC_0x161", values_161),
_create_ccnc_adrv_message_with_signals(packer, CP, CAN, 0x162, counter, "CCNC_0x162", values_162),
]
+11 -1
View File
@@ -48,6 +48,11 @@ def apply_kia_ev6_gt_line_longitudinal_params(ret: structs.CarParams) -> None:
ret.vEgoStarting = 0.5
def apply_kia_ev9_longitudinal_params(ret: structs.CarParams) -> None:
ret.startAccel = 0.2
ret.vEgoStarting = 0.5
def apply_ecu_disable_failure_fallback(CP: structs.CarParams, params) -> None:
params.put_bool("EcuDisableFailed", True)
CP.safetyConfigs[-1].safetyParam &= ~HyundaiSafetyFlags.LONG.value
@@ -104,7 +109,7 @@ class CarInterface(CarInterfaceBase):
# Most angle-steering LKA platforms still need stock longitudinal validation.
ret.alphaLongitudinalAvailable = False
ret.enableBsm = 0x1ba in fingerprint[CAN.ECAN]
ret.enableBsm = 0x1ba in fingerprint[CAN.ECAN] or candidate == CAR.KIA_EV9
# Carnival HEV can fingerprint with too little E-CAN traffic to see 0xFA.
if 0xFA in fingerprint[CAN.ECAN] or candidate == CAR.KIA_CARNIVAL_HEV_4TH_GEN:
@@ -233,6 +238,8 @@ class CarInterface(CarInterfaceBase):
if ret.openpilotLongitudinalControl:
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.LONG.value
if candidate in CANFD_ANGLE_LONGITUDINAL_CAR and ret.flags & HyundaiFlags.CCNC:
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.CCNC.value
if ret.flags & HyundaiFlags.HYBRID:
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.HYBRID_GAS.value
elif ret.flags & HyundaiFlags.EV:
@@ -261,6 +268,9 @@ class CarInterface(CarInterfaceBase):
if candidate == CAR.HYUNDAI_IONIQ_6:
ret.longitudinalActuatorDelay = 0.6
if candidate == CAR.KIA_EV9 and ret.openpilotLongitudinalControl:
apply_kia_ev9_longitudinal_params(ret)
if candidate == CAR.KIA_NIRO_PHEV_2022:
ret.stopAccel = -1.4
ret.stoppingDecelRate = 0.5
@@ -8,6 +8,9 @@ from opendbc.car import Bus, ButtonType, gen_empty_fingerprint, structs
from opendbc.car.structs import CarControl, CarParams
from opendbc.car.fw_versions import build_fw_dict, match_fw_to_car
from opendbc.car.hyundai.carcontroller import CarController, Ioniq6LongitudinalTuningState, GenesisG90LongitudinalTuningState, \
EV9LongitudinalTuningState, update_ev9_longitudinal_tuning, \
BlindspotWarningState, update_blindspot_warning, \
reset_egmp_longitudinal_tuning, \
update_ioniq_6_longitudinal_tuning, \
update_genesis_g90_longitudinal_tuning, egmp_dynamic_longitudinal_tuning, \
should_reset_ev6_gt_line_longitudinal_tuning, reset_ev6_gt_line_longitudinal_tuning, \
@@ -239,16 +242,21 @@ class TestHyundaiFingerprint:
fingerprint[ev9_radar_config.bus][ev9_radar_config.start_addr] = ev9_radar_config.expected_length
ev9_car_fw = [CarParams.CarFw(ecu=Ecu.adas, fwVersion=b"", address=0x730, brand="hyundai")]
CP = CarInterface.get_params(CAR.KIA_EV9, fingerprint, ev9_car_fw, True, False, False, None)
assert not CP.alphaLongitudinalAvailable
assert not CP.openpilotLongitudinalControl
assert CP.alphaLongitudinalAvailable
assert CP.openpilotLongitudinalControl
assert not CP.radarUnavailable
assert CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT
assert not (CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.LONG)
assert CP.flags & HyundaiFlags.CCNC
assert egmp_dynamic_longitudinal_tuning(CP)
assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.LONG
assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CANFD_ANGLE_STEERING
assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CCNC
CP = CarInterface.get_params(CAR.KIA_EV9, fingerprint, [], True, False, False, None)
assert not CP.openpilotLongitudinalControl
assert CP.openpilotLongitudinalControl
CP = CarInterface.get_params(CAR.KIA_EV9, fingerprint, ev9_car_fw, False, False, False, None)
assert not CP.openpilotLongitudinalControl
assert not (CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CCNC)
for candidate in HYUNDAI_NON_SCC_CARS:
CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], True, False, False, None)
assert bool(CP.flags & HyundaiFlags.NON_SCC)
@@ -258,6 +266,7 @@ class TestHyundaiFingerprint:
CP = CarInterface.get_params(CAR.KIA_SPORTAGE_HEV_2026, gen_empty_fingerprint(), [], False, False, False, None)
assert CP.steerControlType == CarParams.SteerControlType.angle
assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CANFD_ANGLE_STEERING
assert not (CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CCNC)
fingerprint = gen_empty_fingerprint()
cam_can = CanBus(None, fingerprint).CAM
@@ -557,6 +566,20 @@ class TestHyundaiFingerprint:
events = car_state.create_cruise_button_events(Buttons.CANCEL, Buttons.NONE)
assert [(be.type, be.pressed) for be in events] == [(ButtonType.cancel, True)]
def test_ccnc_angle_long_main_cruise_toggle(self):
car_state = SimpleNamespace(main_cruise_on=False)
ret = SimpleNamespace(
cruiseState=SimpleNamespace(available=True),
buttonEvents=[structs.CarState.ButtonEvent(pressed=True, type=ButtonType.mainCruise)],
)
assert CarState.update_main_cruise(car_state, ret)
ret.buttonEvents = [structs.CarState.ButtonEvent(pressed=False, type=ButtonType.mainCruise)]
assert CarState.update_main_cruise(car_state, ret)
ret.buttonEvents = [structs.CarState.ButtonEvent(pressed=True, type=ButtonType.mainCruise)]
assert not CarState.update_main_cruise(car_state, ret)
def test_palisade_2023_cancel_release_enables_from_standby(self):
toggles = get_test_toggles()
CP = CarInterface.get_params(CAR.HYUNDAI_PALISADE_2023, gen_empty_fingerprint(), [], True, False, False, toggles)
@@ -1228,6 +1251,52 @@ class TestHyundaiFingerprint:
assert not should_use_ev6_gt_line_stop_direct_tracking(False, True, 1.8, -2.05, -1.29)
assert not should_use_ev6_gt_line_stop_direct_tracking(True, True, 1.8, -1.0, -1.29)
def test_ev9_longitudinal_tuning_matches_stock_stop_hold_release_timing(self):
state = update_ev9_longitudinal_tuning(EV9LongitudinalTuningState(), True, True, 1.0)
assert not state.stop_request
state = update_ev9_longitudinal_tuning(state, True, True, 0.4)
assert state.stop_request
assert not state.cruise_standstill
for _ in range(178):
state = update_ev9_longitudinal_tuning(state, True, True, 0.0)
assert state.cruise_standstill
for _ in range(6):
state = update_ev9_longitudinal_tuning(state, True, False, 0.0)
assert state.stop_request
assert not state.cruise_standstill
assert update_ev9_longitudinal_tuning(state, True, False, 0.0) == EV9LongitudinalTuningState()
def test_ev9_blindspot_warning_matches_stock_envelope(self):
state = BlindspotWarningState()
outputs = [update_blindspot_warning(state, True, True) for _ in range(40)]
assert [output.sound_active for output in outputs[:36]] == [True] * 36
assert not any(output.sound_active for output in outputs[36:])
assert [output.mirror_lamp_active for output in outputs[:20]] == [True] * 16 + [False] * 4
assert [output.mirror_lamp_active for output in outputs[20:]] == [True] * 16 + [False] * 4
assert not update_blindspot_warning(state, False, True).sound_active
assert update_blindspot_warning(state, True, True).sound_active is False
update_blindspot_warning(state, False, False)
assert update_blindspot_warning(state, True, True).sound_active
def test_ev9_longitudinal_tuning_resets_accel_before_stop_release(self):
accel_state = Ioniq6LongitudinalTuningState()
for _ in range(4):
accel_state = update_ioniq_6_longitudinal_tuning(
accel_state, accel_cmd=0.2, v_ego=0.0, a_ego=0.0,
long_control_state=LongCtrlState.starting, long_active=True,
low_speed_stop_brake_cap=True,
)
assert accel_state.actual_accel == pytest.approx(0.75)
accel_state = reset_egmp_longitudinal_tuning(accel_state)
assert accel_state.actual_accel == 0.0
assert not accel_state.launch_active
def test_genesis_g90_longitudinal_tuning_softens_final_stop_hold(self):
state = GenesisG90LongitudinalTuningState()
@@ -2251,6 +2320,151 @@ class TestHyundaiFingerprint:
assert parser.vl["BLINDSPOTS_REAR_CORNERS"]["BCW_LtIndSta"] == 2
assert parser.vl["BLINDSPOTS_REAR_CORNERS"]["OSMrrLamp_LtIndSta"] == 2
def test_ev9_blindspot_status_uses_stock_warning_fields(self):
CP = CarParams.new_message()
CP.carFingerprint = CAR.KIA_EV9
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.CANFD_LKA_STEERING | HyundaiFlags.CANFD_LKA_STEERING_ALT)
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
can_bus = CanBus(CP)
parser = CANParser(DBC[CP.carFingerprint][Bus.pt],
[("BLINDSPOTS_REAR_CORNERS", 0), ("BLINDSPOTS_FRONT_CORNER_1", 0)], can_bus.ECAN)
msgs = hyundaicanfd.create_ccnc_blindspot_status_messages(
packer, CP, can_bus, 7, left_blindspot=True, left_escalated=True,
drive_gear=True, left_warning_lamp=True, left_sound_active=True,
)
parser.update([(1, msgs)])
assert parser.vl["BLINDSPOTS_REAR_CORNERS"]["BCW_LtIndSta"] == 2
assert parser.vl["BLINDSPOTS_REAR_CORNERS"]["OSMrrLamp_LtIndSta"] == 2
assert parser.vl["BLINDSPOTS_REAR_CORNERS"]["BCW_LtSndWrngSta"] == 1
assert parser.vl["BLINDSPOTS_REAR_CORNERS"]["BCW_Sta"] == 0
assert parser.vl["BLINDSPOTS_REAR_CORNERS"]["FL_INDICATOR"] == 0
assert parser.vl["BLINDSPOTS_REAR_CORNERS"]["BCW_IndSta"] == 1
assert parser.vl["BLINDSPOTS_REAR_CORNERS"]["BCA_OnOffEquip2Sta"] == 2
assert parser.vl["BLINDSPOTS_REAR_CORNERS"]["BCA_Sta"] == 1
assert parser.vl["BLINDSPOTS_FRONT_CORNER_1"]["NEW_SIGNAL_7"] == 0
def test_ev9_ccnc_status_clears_faults_and_tracks_control_state(self):
CP = CarParams.new_message()
CP.carFingerprint = CAR.KIA_EV9
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.CANFD_LKA_STEERING | HyundaiFlags.CANFD_LKA_STEERING_ALT)
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
can_bus = CanBus(CP)
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("CCNC_0x161", 0), ("CCNC_0x162", 0)], can_bus.ECAN)
parser.update([(1, hyundaicanfd.create_ccnc_angle_long_status_messages(packer, CP, can_bus, 12, hba_icon=2))])
assert parser.vl["CCNC_0x161"]["FCA_ICON"] == 1
assert parser.vl["CCNC_0x161"]["FCA_ALT_ICON"] == 0
assert parser.vl["CCNC_0x161"]["FCA_IMAGE"] == 0
assert parser.vl["CCNC_0x161"]["HBA_ICON"] == 2
assert all(parser.vl["CCNC_0x161"][sound] == 0 for sound in ("SOUNDS_1", "SOUNDS_2", "SOUNDS_3", "SOUNDS_4"))
assert parser.vl["CCNC_0x162"]["VIBRATE"] == 0
assert all(parser.vl["CCNC_0x162"][fault] == 0 for fault in (
"FAULT_FSS", "FAULT_FCA", "FAULT_LSS", "FAULT_SLA", "FAULT_HDA", "FAULT_DAS", "FAULT_LFA", "FAULT_DAW",
"FAULT_HBA", "FAULT_ESS",
))
parser.update([(1, hyundaicanfd.create_ccnc_angle_long_status_messages(
packer, CP, can_bus, 13, main_cruise_enabled=True, steering_available=True, steering_active=False,
))])
assert parser.vl["CCNC_0x161"]["HDA_ICON"] == 1
assert parser.vl["CCNC_0x161"]["LFA_ICON"] == 1
parser.update([(1, hyundaicanfd.create_ccnc_angle_long_status_messages(
packer, CP, can_bus, 14, enabled=True, main_cruise_enabled=True,
steering_available=True, steering_active=True,
))])
assert parser.vl["CCNC_0x161"]["HDA_ICON"] == 2
assert parser.vl["CCNC_0x161"]["LFA_ICON"] == 2
parser.update([(1, hyundaicanfd.create_ccnc_angle_long_status_messages(
packer, CP, can_bus, 15, enabled=True, main_cruise_enabled=True,
steering_available=True, steering_active=False,
))])
assert parser.vl["CCNC_0x161"]["HDA_ICON"] == 2
assert parser.vl["CCNC_0x161"]["LFA_ICON"] == 1
def test_ev9_ccnc_acc_control_uses_packer_counter(self):
CP = CarParams.new_message()
CP.carFingerprint = CAR.KIA_EV9
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.CCNC | HyundaiFlags.CANFD_ANGLE_STEERING |
HyundaiFlags.CANFD_LKA_STEERING | HyundaiFlags.CANFD_LKA_STEERING_ALT)
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
can_bus = CanBus(CP)
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("SCC_CONTROL", 0)], can_bus.ECAN)
messages = [
hyundaicanfd.create_ccnc_acc_control(
packer, can_bus, True, 0.2, False, False, False, 50.0,
1, 27.5, -1.2, True, 10.0,
)
for _ in range(2)
]
parser.update([(1, [messages[0]])])
parser.update([(2, [messages[1]])])
assert parser.can_valid
assert messages[0][1][2] == 0
assert messages[1][1][2] == 1
assert parser.vl["SCC_CONTROL"]["ACC_ObjDist"] == pytest.approx(27.5)
assert parser.vl["SCC_CONTROL"]["ACC_ObjRelSpd"] == pytest.approx(-1.2)
@pytest.mark.parametrize(("steering_pressed", "steering_active"), ((False, True), (True, False)))
def test_ev9_ccnc_steering_icon_tracks_controller_authority(self, monkeypatch, steering_pressed, steering_active):
CP = CarParams.new_message()
CP.carFingerprint = CAR.KIA_EV9
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CCNC |
HyundaiFlags.CANFD_ANGLE_STEERING | HyundaiFlags.CANFD_LKA_STEERING |
HyundaiFlags.CANFD_LKA_STEERING_ALT)
CP.openpilotLongitudinalControl = True
controller = CarController(DBC[CP.carFingerprint], CP)
controller.frame = 5
captured = {}
def capture_ccnc_adrv_messages(*args, **kwargs):
captured["steering_available"] = args[9]
captured["steering_active"] = args[10]
return []
monkeypatch.setattr(hyundaicanfd, "create_ccnc_adrv_messages", capture_ccnc_adrv_messages)
lfa_block_msg = {f"BYTE{i}": 0 for i in range(3, 32) if i != 7}
lfa_block_msg["COUNTER"] = 0
cc = SimpleNamespace(
enabled=True,
latActive=True,
actuators=SimpleNamespace(longControlState=LongCtrlState.pid, accel=0.0),
leftBlinker=False,
rightBlinker=False,
hudControl=SimpleNamespace(),
)
cs = SimpleNamespace(
angle_steering_fault=False,
angle_steering_angle=0.0,
hba_icon=0,
is_metric=True,
left_blindspot_from_radar=False,
right_blindspot_from_radar=False,
lfa_block_msg=lfa_block_msg,
out=SimpleNamespace(
brakePressed=False,
cruiseState=SimpleNamespace(available=True),
gasPressed=False,
gearShifter=structs.CarState.GearShifter.drive,
steeringPressed=steering_pressed,
),
)
controller.create_canfd_msgs(0, True, 0.44, 0.0, 0.0, 0.0, False, cc.hudControl, cs, cc,
get_test_toggles(), lka_icon=2, lfa_icon=2)
assert captured == {"steering_available": True, "steering_active": steering_active}
def test_ioniq_6_blindspot_radar_state_decode(self):
assert decode_ioniq_6_blindspot_radar_state(0x02) == (False, False)
assert decode_ioniq_6_blindspot_radar_state(0x0A) == (False, True)
+9 -4
View File
@@ -783,7 +783,7 @@ class CAR(Platforms):
HyundaiCarDocs("Kia EV9 2025-26", car_parts=CarParts.common([CarHarness.hyundai_r]))
],
CarSpecs(mass=2664, wheelbase=3.1, steerRatio=16),
flags=HyundaiFlags.EV | HyundaiFlags.CANFD_ANGLE_STEERING,
flags=HyundaiFlags.EV | HyundaiFlags.CANFD_ANGLE_STEERING | HyundaiFlags.CCNC,
radar_dbc=HYUNDAI_MRR35_RADAR_DBC,
)
KIA_CARNIVAL_4TH_GEN = HyundaiCanFDPlatformConfig(
@@ -1172,10 +1172,15 @@ CANFD_CAR = CAR.with_flags(HyundaiFlags.CANFD)
CANFD_RADAR_SCC_CAR = CAR.with_flags(HyundaiFlags.RADAR_SCC) # TODO: merge with UNSUPPORTED_LONGITUDINAL_CAR
# CAN-FD cars with ADAS ECUs that work with the communication-control path.
CANFD_SECURITYACCESS_CAR = {CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_IONIQ_6, CAR.HYUNDAI_KONA_EV_2ND_GEN}
CANFD_SECURITYACCESS_CAR = {
CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_IONIQ_6, CAR.HYUNDAI_KONA_EV_2ND_GEN, CAR.KIA_EV9,
}
CANFD_UNSUPPORTED_LONGITUDINAL_CAR = CAR.with_flags(HyundaiFlags.CANFD_NO_RADAR_DISABLE) - CANFD_SECURITYACCESS_CAR # TODO: merge with UNSUPPORTED_LONGITUDINAL_CAR
CANFD_ANGLE_LONGITUDINAL_CAR = set()
CANFD_RADAR_LIVE_LONGITUDINAL_CAR = {CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6, CAR.GENESIS_GV60_EV_1ST_GEN}
CANFD_ANGLE_LONGITUDINAL_CAR = {CAR.KIA_EV9}
CANFD_CORNER_RADAR_BSM_CAR = {CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV9}
CANFD_RADAR_LIVE_LONGITUDINAL_CAR = {
CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6, CAR.KIA_EV9, CAR.GENESIS_GV60_EV_1ST_GEN,
}
RADAR_LIVE_LONGITUDINAL_CAR = CANFD_RADAR_LIVE_LONGITUDINAL_CAR | {
CAR.HYUNDAI_IONIQ,
CAR.HYUNDAI_KONA_EV_2022,