mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-22 09:42:10 +08:00
feat(hyundai): add EV9 alpha longitudinal support
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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),
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user