diff --git a/opendbc_repo/opendbc/car/hyundai/carcontroller.py b/opendbc_repo/opendbc/car/hyundai/carcontroller.py index bcaca6d67..8035cd2f5 100644 --- a/opendbc_repo/opendbc/car/hyundai/carcontroller.py +++ b/opendbc_repo/opendbc/car/hyundai/carcontroller.py @@ -55,6 +55,8 @@ IONIQ_6_STOP_RELEASE_JERK_V = [3.6 * IONIQ_6_RESPONSE_MULTIPLIER, 4.8 * IONIQ_6_RESPONSE_MULTIPLIER] IONIQ_6_IPEDAL_PRESS_SEND_COUNT = 6 IONIQ_6_IPEDAL_LATCH_PRESS_SEND_COUNT = 10 +IONIQ_6_MAX_REGEN_STATE = 0x3C +IONIQ_6_MAX_REGEN_STATE_2 = 0x01 IONIQ_6_IPEDAL_REGEN_STATE = 0x50 IONIQ_6_IPEDAL_REGEN_STATE_2_PENDING = 0x01 IONIQ_6_IPEDAL_PROGRESS_RETRY_WAIT_FRAMES = 10 @@ -248,6 +250,7 @@ class CarController(CarControllerBase): self._ioniq_6_last_ipedal_regen_state = 0 self._ioniq_6_last_ipedal_regen_state_2 = 0 self._ioniq_6_last_buttons_counter = 0 + self._ioniq_6_last_regen_control_counter = -1 self._ioniq_6_last_gear = structs.CarState.GearShifter.unknown self._genesis_g90_long_tuning = GenesisG90LongitudinalTuningState() @@ -270,6 +273,7 @@ class CarController(CarControllerBase): self._ioniq_6_last_ipedal_regen_state = int(getattr(CS, "ipedal_regen_state", 0)) self._ioniq_6_last_ipedal_regen_state_2 = int(getattr(CS, "ipedal_regen_state_2", 0)) self._ioniq_6_last_buttons_counter = int(getattr(CS, "buttons_counter", 0)) + self._ioniq_6_last_regen_control_counter = int(getattr(CS, "ioniq_6_regen_control_msg", {}).get("COUNTER", -1)) self._ioniq_6_last_gear = CS.out.gearShifter return can_sends @@ -277,8 +281,13 @@ class CarController(CarControllerBase): regen_state = int(getattr(CS, "ipedal_regen_state", 0)) regen_state_2 = int(getattr(CS, "ipedal_regen_state_2", 0)) buttons_counter = int(getattr(CS, "buttons_counter", 0)) + regen_control_msg = getattr(CS, "ioniq_6_regen_control_msg", {}) + regen_control_counter = int(regen_control_msg.get("COUNTER", -1)) + has_regen_control_msg = bool(regen_control_msg) and getattr(CS, "ioniq_6_regen_control_ts", 0) > 0 regen_state_changed = regen_state != self._ioniq_6_last_ipedal_regen_state or regen_state_2 != self._ioniq_6_last_ipedal_regen_state_2 buttons_counter_changed = buttons_counter != self._ioniq_6_last_buttons_counter + regen_control_counter_changed = regen_control_counter != self._ioniq_6_last_regen_control_counter + max_regen_state = regen_state == IONIQ_6_MAX_REGEN_STATE and regen_state_2 == IONIQ_6_MAX_REGEN_STATE_2 ipedal_latch_pending = regen_state == IONIQ_6_IPEDAL_REGEN_STATE and regen_state_2 == IONIQ_6_IPEDAL_REGEN_STATE_2_PENDING drive = gear == structs.CarState.GearShifter.drive park = gear == structs.CarState.GearShifter.park @@ -302,7 +311,7 @@ class CarController(CarControllerBase): if target_gear and self._ioniq_6_always_ipedal_pending and not CS.ipedal_active and not CC.enabled: if self._ioniq_6_always_ipedal_press_remaining == 0 and self.frame >= self._ioniq_6_always_ipedal_retry_frame: - self._ioniq_6_always_ipedal_press_remaining = IONIQ_6_IPEDAL_LATCH_PRESS_SEND_COUNT if ipedal_latch_pending \ + self._ioniq_6_always_ipedal_press_remaining = IONIQ_6_IPEDAL_LATCH_PRESS_SEND_COUNT if (max_regen_state or ipedal_latch_pending) \ else IONIQ_6_IPEDAL_PRESS_SEND_COUNT # Mirror the current stock counter after seeing the real CRUISE_BUTTONS frame land on the bus. @@ -317,9 +326,16 @@ class CarController(CarControllerBase): retry_wait_frames = IONIQ_6_IPEDAL_PROGRESS_RETRY_WAIT_FRAMES if regen_state_changed else IONIQ_6_IPEDAL_RETRY_WAIT_FRAMES self._ioniq_6_always_ipedal_retry_frame = self.frame + retry_wait_frames + # The drivetrain latch uses a second HKG CAN-FD request path in addition to the left paddle bit. + # Once the cluster-facing regen state reaches max regen, mirror the live stock frame and only + # flip the request bytes that the physical paddle toggles for the final i-Pedal latch step. + if max_regen_state and has_regen_control_msg and regen_control_counter_changed: + can_sends.append(hyundaicanfd.create_ioniq_6_regen_control(self.packer, self.CP, self.CAN, regen_control_msg)) + self._ioniq_6_last_ipedal_regen_state = regen_state self._ioniq_6_last_ipedal_regen_state_2 = regen_state_2 self._ioniq_6_last_buttons_counter = buttons_counter + self._ioniq_6_last_regen_control_counter = regen_control_counter self._ioniq_6_last_gear = gear return can_sends diff --git a/opendbc_repo/opendbc/car/hyundai/carstate.py b/opendbc_repo/opendbc/car/hyundai/carstate.py index 60cab0c87..927581adc 100644 --- a/opendbc_repo/opendbc/car/hyundai/carstate.py +++ b/opendbc_repo/opendbc/car/hyundai/carstate.py @@ -24,6 +24,8 @@ BUTTONS_DICT = {Buttons.RES_ACCEL: ButtonType.accelCruise, Buttons.SET_DECEL: Bu IONIQ_6_BLINDSPOT_RIGHT_MASK = 0x08 IONIQ_6_BLINDSPOT_LEFT_MASK = 0x10 +IONIQ_6_MAX_REGEN_STATE = 0x3C +IONIQ_6_MAX_REGEN_STATE_2 = 0x01 IONIQ_6_IPEDAL_INTERMEDIATE_REGEN_STATE = 0x50 IONIQ_6_IPEDAL_INTERMEDIATE_REGEN_STATE_2 = 0x01 IONIQ_6_IPEDAL_REGEN_STATE = 0x50 @@ -51,6 +53,10 @@ def decode_ioniq_6_ipedal_state(regen_state: int, regen_state_2: int) -> bool: return int(regen_state) == IONIQ_6_IPEDAL_REGEN_STATE and int(regen_state_2) == IONIQ_6_IPEDAL_REGEN_STATE_2 +def decode_ioniq_6_max_regen_state(regen_state: int, regen_state_2: int) -> bool: + return int(regen_state) == IONIQ_6_MAX_REGEN_STATE and int(regen_state_2) == IONIQ_6_MAX_REGEN_STATE_2 + + def decode_ioniq_6_ipedal_intermediate_state(regen_state: int, regen_state_2: int) -> bool: return int(regen_state) == IONIQ_6_IPEDAL_INTERMEDIATE_REGEN_STATE and int(regen_state_2) == IONIQ_6_IPEDAL_INTERMEDIATE_REGEN_STATE_2 @@ -77,6 +83,8 @@ class CarState(CarStateBase): self.ipedal_active = False self.ipedal_regen_state = 0 self.ipedal_regen_state_2 = 0 + self.ioniq_6_regen_control_msg = {} + self.ioniq_6_regen_control_ts = 0 self.gear_msg_canfd = "ACCELERATOR" if CP.flags & HyundaiFlags.EV else \ "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else \ @@ -408,6 +416,9 @@ class CarState(CarStateBase): self.ipedal_regen_state = int(msla.get("EV_REGEN_STATE", 0)) self.ipedal_regen_state_2 = int(msla.get("EV_REGEN_STATE_2", 0)) self.ipedal_active = decode_ioniq_6_ipedal_state(self.ipedal_regen_state, self.ipedal_regen_state_2) + if cp.ts_nanos["IONIQ_6_REGEN_CONTROL"]["CHECKSUM"] > 0: + self.ioniq_6_regen_control_msg = copy.copy(cp.vl["IONIQ_6_REGEN_CONTROL"]) + self.ioniq_6_regen_control_ts = cp.ts_nanos["IONIQ_6_REGEN_CONTROL"]["CHECKSUM"] prev_cruise_buttons = self.cruise_buttons[-1] prev_main_buttons = self.main_buttons[-1] @@ -484,6 +495,8 @@ class CarState(CarStateBase): 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 + if CP.carFingerprint == CAR.HYUNDAI_IONIQ_6: + msgs.append(("IONIQ_6_REGEN_CONTROL", 0)) msgs.append(("STEERING_WHEEL_MEDIA_BUTTONS", 0)) # optional: absent or slower on some CAN-FD variants cam_msgs.append(("ADAS_0x380", 0)) # optional: dashboard stop-sign signal, only on ADAS-equipped HKG CANFD return { diff --git a/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py b/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py index 348553851..97893cc64 100644 --- a/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py +++ b/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py @@ -164,6 +164,8 @@ IONIQ_6_CRUISE_BUTTONS_BASE_CHECKSUMS = ( ) IONIQ_6_CRUISE_BUTTONS_LEFT_PADDLE_CHECKSUM_XOR = 0x77 IONIQ_6_CRUISE_BUTTONS_RIGHT_PADDLE_CHECKSUM_XOR = 0xD4 +IONIQ_6_REGEN_CONTROL_REQ_BYTE24 = 0xC0 +IONIQ_6_REGEN_CONTROL_REQ_BYTE27 = 0x00 def get_ioniq_6_cruise_buttons_next_counter(counter: int) -> int: @@ -200,6 +202,21 @@ def create_ioniq_6_paddle_buttons(packer, CP, CAN, cnt, left_paddle=False, right return address, bytes(dat), bus +def create_ioniq_6_regen_control(packer, CP, CAN, base_values, + req_byte24=IONIQ_6_REGEN_CONTROL_REQ_BYTE24, + req_byte27=IONIQ_6_REGEN_CONTROL_REQ_BYTE27): + values = {k: v for k, v in base_values.items() if k != "CHECKSUM"} + address = packer.dbc.name_to_msg["IONIQ_6_REGEN_CONTROL"].address + dat = bytearray(packer.pack(address, values)) + dat[24] = req_byte24 + dat[27] = req_byte27 + + checksum = hkg_can_fd_checksum(address, None, dat) + dat[0] = checksum & 0xFF + dat[1] = (checksum >> 8) & 0xFF + return address, bytes(dat), CAN.ECAN + + def create_buttons(packer, CP, CAN, cnt, btn=0, base_values=None, left_paddle=False, right_paddle=False): values = {k: v for k, v in base_values.items() if k not in ("_CHECKSUM", "COUNTER")} if base_values else {} values.update({ diff --git a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py index bee77c5c0..7606bd354 100644 --- a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py +++ b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py @@ -10,7 +10,7 @@ from opendbc.car.fw_versions import build_fw_dict, match_fw_to_car from opendbc.car.hyundai.carcontroller import CarController, Ioniq6LongitudinalTuningState, GenesisG90LongitudinalTuningState, \ update_ioniq_6_longitudinal_tuning, update_genesis_g90_longitudinal_tuning from opendbc.car.hyundai.carstate import CarState, decode_ioniq_6_blindspot_radar_state, decode_ioniq_6_ipedal_intermediate_state, \ - decode_ioniq_6_ipedal_state + decode_ioniq_6_ipedal_state, decode_ioniq_6_max_regen_state from opendbc.car.hyundai.interface import CarInterface from opendbc.car.hyundai import hyundaican, hyundaicanfd from opendbc.car.hyundai.hyundaicanfd import CanBus @@ -296,6 +296,7 @@ class TestHyundaiFingerprint: assert any(be.type == ButtonType.altButton2 and not be.pressed for be in ret.buttonEvents) def test_ioniq_6_ipedal_state_decode(self): + assert decode_ioniq_6_max_regen_state(0x3C, 0x01) assert not decode_ioniq_6_ipedal_state(0x3C, 0x01) assert not decode_ioniq_6_ipedal_state(0x50, 0x01) assert decode_ioniq_6_ipedal_intermediate_state(0x50, 0x01) @@ -342,6 +343,26 @@ class TestHyundaiFingerprint: assert hyundaicanfd.get_ioniq_6_cruise_buttons_next_counter(13) == 14 assert hyundaicanfd.get_ioniq_6_cruise_buttons_next_counter(14) == 0 + def test_ioniq_6_regen_control_message_preserves_stock_frame_and_flips_only_ipedal_request_bytes(self): + CP = CarParams.new_message() + CP.carFingerprint = CAR.HYUNDAI_IONIQ_6 + CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.CANFD_LKA_STEERING) + + packer = CANPacker(DBC[CP.carFingerprint][Bus.pt]) + can_bus = CanBus(CP) + parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("IONIQ_6_REGEN_CONTROL", 0)], can_bus.ECAN) + stock_dat = bytes.fromhex("45421440801f000000000000a865170000a000000080c071a80c120e00000000") + parser.update([(1, [(0x25A, stock_dat, can_bus.ECAN)])]) + + msg = hyundaicanfd.create_ioniq_6_regen_control(packer, CP, can_bus, parser.vl["IONIQ_6_REGEN_CONTROL"]) + assert msg[1][2:24] == stock_dat[2:24] + assert msg[1][24] == hyundaicanfd.IONIQ_6_REGEN_CONTROL_REQ_BYTE24 + assert msg[1][25:27] == stock_dat[25:27] + assert msg[1][27] == hyundaicanfd.IONIQ_6_REGEN_CONTROL_REQ_BYTE27 + assert msg[1][28:] == stock_dat[28:] + checksum = hyundaicanfd.hkg_can_fd_checksum(msg[0], None, bytearray(msg[1])) + assert msg[1][0] | (msg[1][1] << 8) == checksum + def test_ioniq_6_always_ipedal_spoofs_left_paddle_in_startup_park_and_drive_until_latched(self): toggles = get_test_toggles() toggles.always_ipedal = True @@ -367,6 +388,8 @@ class TestHyundaiFingerprint: "LEFT_PADDLE": 0, "SET_ME_1": 1, }, + ioniq_6_regen_control_msg={}, + ioniq_6_regen_control_ts=0, ) cc = SimpleNamespace(enabled=False) @@ -426,6 +449,8 @@ class TestHyundaiFingerprint: "LEFT_PADDLE": 0, "SET_ME_1": 1, }, + ioniq_6_regen_control_msg={}, + ioniq_6_regen_control_ts=0, ) cc = SimpleNamespace(enabled=False) @@ -435,6 +460,76 @@ class TestHyundaiFingerprint: assert sends assert controller._ioniq_6_always_ipedal_press_remaining == 9 + def test_ioniq_6_always_ipedal_sends_regen_control_companion_once_max_regen_is_shown(self): + toggles = get_test_toggles() + toggles.always_ipedal = True + CP = CarInterface.get_params(CAR.HYUNDAI_IONIQ_6, gen_empty_fingerprint(), [], True, False, False, toggles) + + controller = CarController(DBC[CP.carFingerprint], CP) + cs = SimpleNamespace( + out=SimpleNamespace(gearShifter=structs.CarState.GearShifter.drive), + ipedal_active=False, + ipedal_regen_state=0x3C, + ipedal_regen_state_2=0x01, + buttons_counter=5, + cruise_buttons_msg={ + "_CHECKSUM": 0, + "COUNTER": 5, + "CRUISE_BUTTONS": 0, + "ADAPTIVE_CRUISE_MAIN_BTN": 0, + "NORMAL_CRUISE_MAIN_BTN": 0, + "LDA_BTN": 0, + "RIGHT_PADDLE": 0, + "LEFT_PADDLE": 0, + "SET_ME_1": 1, + }, + ioniq_6_regen_control_msg={ + "COUNTER": 0x14, + "BYTE3": 0x40, + "BYTE4": 0x80, + "BYTE5": 0x1F, + "BYTE6": 0x00, + "BYTE7": 0x00, + "BYTE8": 0x00, + "BYTE9": 0x00, + "BYTE10": 0x00, + "BYTE11": 0x00, + "BYTE12": 0xA8, + "BYTE13": 0x65, + "BYTE14": 0x17, + "BYTE15": 0x00, + "BYTE16": 0x00, + "BYTE17": 0xA0, + "BYTE18": 0x00, + "BYTE19": 0x00, + "BYTE20": 0x00, + "BYTE21": 0x80, + "BYTE22": 0xC0, + "BYTE23": 0x71, + "BYTE24": 0xA8, + "BYTE25": 0x0C, + "BYTE26": 0x12, + "BYTE27": 0x0E, + "BYTE28": 0x00, + "BYTE29": 0x00, + "BYTE30": 0x00, + "BYTE31": 0x00, + }, + ioniq_6_regen_control_ts=1, + ) + cc = SimpleNamespace(enabled=False) + + controller._ioniq_6_last_gear = structs.CarState.GearShifter.reverse + controller._ioniq_6_last_regen_control_counter = 0x13 + sends = controller._update_ioniq_6_always_ipedal(cc, cs, toggles) + + assert any(msg[0] == 0x1CF for msg in sends) + regen_cmd = next(msg for msg in sends if msg[0] == 0x25A) + assert regen_cmd[1][24] == hyundaicanfd.IONIQ_6_REGEN_CONTROL_REQ_BYTE24 + assert regen_cmd[1][27] == hyundaicanfd.IONIQ_6_REGEN_CONTROL_REQ_BYTE27 + checksum = hyundaicanfd.hkg_can_fd_checksum(regen_cmd[0], None, bytearray(regen_cmd[1])) + assert regen_cmd[1][0] | (regen_cmd[1][1] << 8) == checksum + def test_ioniq_6_longitudinal_params_match_canfd_tune(self): toggles = get_test_toggles() CP = CarInterface.get_params(CAR.HYUNDAI_IONIQ_6, gen_empty_fingerprint(), [], True, False, False, toggles) diff --git a/opendbc_repo/opendbc/dbc/hyundai_canfd_generated.dbc b/opendbc_repo/opendbc/dbc/hyundai_canfd_generated.dbc index c49508bfd..faed2e6f3 100644 --- a/opendbc_repo/opendbc/dbc/hyundai_canfd_generated.dbc +++ b/opendbc_repo/opendbc/dbc/hyundai_canfd_generated.dbc @@ -774,6 +774,39 @@ BO_ 593 RADAR_0x251: 16 FRONT_RADAR SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX SG_ COUNTER : 16|8@1+ (1,0) [0|255] "" XXX +BO_ 602 IONIQ_6_REGEN_CONTROL: 32 XXX + SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX + SG_ COUNTER : 16|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE3 : 24|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE4 : 32|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE5 : 40|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE6 : 48|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE7 : 56|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE8 : 64|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE9 : 72|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE10 : 80|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE11 : 88|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE12 : 96|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE13 : 104|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE14 : 112|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE15 : 120|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE16 : 128|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE17 : 136|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE18 : 144|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE19 : 152|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE20 : 160|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE21 : 168|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE22 : 176|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE23 : 184|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE24 : 192|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE25 : 200|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE26 : 208|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE27 : 216|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE28 : 224|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE29 : 232|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE30 : 240|8@1+ (1,0) [0|255] "" XXX + SG_ BYTE31 : 248|8@1+ (1,0) [0|255] "" XXX + BO_ 687 HOD_FD_01_100ms: 8 XXX SG_ HOD_Dir_Status : 18|3@0+ (1,0) [0|7] "" XXX SG_ NEW_SIGNAL_2 : 32|8@1+ (1,0) [0|255] "" XXX diff --git a/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h b/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h index d0c81a68b..ba89db604 100644 --- a/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h +++ b/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h @@ -6,6 +6,9 @@ #define HYUNDAI_CANFD_CRUISE_BUTTON_TX_MSGS(bus) \ {0x1CF, bus, 8, .check_relay = false}, /* CRUISE_BUTTON */ \ +#define HYUNDAI_CANFD_IPEDAL_REGEN_TX_MSGS(bus) \ + {0x25A, bus, 32, .check_relay = false}, /* IONIQ_6_REGEN_CONTROL */ \ + #define HYUNDAI_CANFD_LKA_STEERING_COMMON_TX_MSGS(a_can, e_can) \ HYUNDAI_CANFD_CRUISE_BUTTON_TX_MSGS(e_can) \ {0x50, a_can, 16, .check_relay = (a_can) == 0}, /* LKAS */ \ @@ -57,6 +60,8 @@ static bool hyundai_canfd_alt_buttons = false; static bool hyundai_canfd_lka_steering_alt = false; static bool hyundai_canfd_angle_steering = false; static bool hyundai_canfd_allow_ipedal_paddle = false; +static bool hyundai_canfd_last_regen_control_seen = false; +static uint8_t hyundai_canfd_last_regen_control[32] = {0U}; static unsigned int hyundai_canfd_get_lka_addr(void) { return hyundai_canfd_lka_steering_alt ? 0x110U : 0x50U; @@ -77,6 +82,17 @@ static uint32_t hyundai_canfd_get_checksum(const CANPacket_t *msg) { return chksum; } +static void hyundai_canfd_rx_all_hook(const CANPacket_t *msg) { + const unsigned pt_bus = hyundai_canfd_lka_steering ? 1U : 0U; + + if (hyundai_canfd_allow_ipedal_paddle && (msg->bus == pt_bus) && (msg->addr == 0x25AU) && (GET_LEN(msg) == 32U)) { + for (int i = 0; i < 32; i++) { + hyundai_canfd_last_regen_control[i] = msg->data[i]; + } + hyundai_canfd_last_regen_control_seen = true; + } +} + static void hyundai_canfd_rx_hook(const CANPacket_t *msg) { const unsigned pt_bus = hyundai_canfd_lka_steering ? 1U : 0U; @@ -249,6 +265,31 @@ static bool hyundai_canfd_tx_hook(const CANPacket_t *msg) { } } + if (msg->addr == 0x25AU) { + const unsigned pt_bus = hyundai_canfd_lka_steering ? 1U : 0U; + bool allowed = hyundai_canfd_allow_ipedal_paddle && + !controls_allowed && + (msg->bus == pt_bus) && + (GET_LEN(msg) == 32U) && + hyundai_canfd_last_regen_control_seen && + (hyundai_canfd_get_checksum(msg) == hyundai_common_canfd_compute_checksum(msg)) && + (msg->data[24] == 0xC0U) && + (msg->data[27] == 0x00U); + + if (allowed) { + for (int i = 2; i < 32; i++) { + if ((i != 24) && (i != 27) && (msg->data[i] != hyundai_canfd_last_regen_control[i])) { + allowed = false; + break; + } + } + } + + if (!allowed) { + tx = false; + } + } + // UDS: only tester present ("\x02\x3E\x80\x00\x00\x00\x00\x00") allowed on diagnostics address if (((msg->addr == 0x730U) && hyundai_canfd_lka_steering) || ((msg->addr == 0x7D0U) && !hyundai_camera_scc)) { if ((GET_BYTES(msg, 0, 4) != 0x00803E02U) || (GET_BYTES(msg, 4, 4) != 0x0U)) { @@ -296,10 +337,20 @@ static safety_config hyundai_canfd_init(uint16_t param) { HYUNDAI_CANFD_LKA_STEERING_COMMON_TX_MSGS(0, 1) }; + static const CanMsg HYUNDAI_CANFD_LKA_STEERING_IPEDAL_TX_MSGS[] = { + HYUNDAI_CANFD_LKA_STEERING_COMMON_TX_MSGS(0, 1) + HYUNDAI_CANFD_IPEDAL_REGEN_TX_MSGS(1) + }; + static const CanMsg HYUNDAI_CANFD_LKA_STEERING_ALT_TX_MSGS[] = { HYUNDAI_CANFD_LKA_STEERING_ALT_COMMON_TX_MSGS(0, 1) }; + static const CanMsg HYUNDAI_CANFD_LKA_STEERING_ALT_IPEDAL_TX_MSGS[] = { + HYUNDAI_CANFD_LKA_STEERING_ALT_COMMON_TX_MSGS(0, 1) + HYUNDAI_CANFD_IPEDAL_REGEN_TX_MSGS(1) + }; + static const CanMsg HYUNDAI_CANFD_LKA_STEERING_LONG_TX_MSGS[] = { HYUNDAI_CANFD_LKA_STEERING_COMMON_TX_MSGS(0, 1) HYUNDAI_CANFD_LFA_STEERING_COMMON_TX_MSGS(1) @@ -314,6 +365,21 @@ static safety_config hyundai_canfd_init(uint16_t param) { {0x1DA, 1, 32, .check_relay = false}, // ADRV_0x1da }; + static const CanMsg HYUNDAI_CANFD_LKA_STEERING_LONG_IPEDAL_TX_MSGS[] = { + HYUNDAI_CANFD_LKA_STEERING_COMMON_TX_MSGS(0, 1) + HYUNDAI_CANFD_LFA_STEERING_COMMON_TX_MSGS(1) + HYUNDAI_CANFD_SCC_CONTROL_COMMON_TX_MSGS(1, true) + HYUNDAI_CANFD_BLINDSPOT_DASH_TX_MSGS(1) + HYUNDAI_CANFD_IPEDAL_REGEN_TX_MSGS(1) + {0x51, 0, 32, .check_relay = false}, // ADRV_0x51 + {0x730, 1, 8, .check_relay = false}, // tester present for ADAS ECU disable + {0x160, 1, 16, .check_relay = false}, // ADRV_0x160 + {0x1EA, 1, 32, .check_relay = false}, // ADRV_0x1ea + {0x200, 1, 8, .check_relay = false}, // ADRV_0x200 + {0x345, 1, 8, .check_relay = false}, // ADRV_0x345 + {0x1DA, 1, 32, .check_relay = false}, // ADRV_0x1da + }; + static const CanMsg HYUNDAI_CANFD_LFA_STEERING_TX_MSGS[] = { HYUNDAI_CANFD_CRUISE_BUTTON_TX_MSGS(2) HYUNDAI_CANFD_LFA_STEERING_COMMON_TX_MSGS(0) @@ -344,6 +410,7 @@ static safety_config hyundai_canfd_init(uint16_t param) { hyundai_canfd_lka_steering_alt = GET_FLAG(param, HYUNDAI_PARAM_CANFD_LKA_STEERING_ALT); hyundai_canfd_angle_steering = GET_FLAG(param, HYUNDAI_PARAM_CANFD_ANGLE_STEERING); hyundai_canfd_allow_ipedal_paddle = GET_FLAG(param, HYUNDAI_PARAM_ALLOW_IPEDAL_PADDLE); + hyundai_canfd_last_regen_control_seen = false; safety_config ret; if (hyundai_longitudinal) { @@ -352,7 +419,12 @@ static safety_config hyundai_canfd_init(uint16_t param) { HYUNDAI_CANFD_STD_BUTTONS_RX_CHECKS(1) }; - ret = BUILD_SAFETY_CFG(hyundai_canfd_lka_steering_long_rx_checks, HYUNDAI_CANFD_LKA_STEERING_LONG_TX_MSGS); + SET_RX_CHECKS(hyundai_canfd_lka_steering_long_rx_checks, ret); + if (hyundai_canfd_allow_ipedal_paddle) { + SET_TX_MSGS(HYUNDAI_CANFD_LKA_STEERING_LONG_IPEDAL_TX_MSGS, ret); + } else { + SET_TX_MSGS(HYUNDAI_CANFD_LKA_STEERING_LONG_TX_MSGS, ret); + } } else { // Longitudinal checks for LFA steering @@ -394,9 +466,17 @@ static safety_config hyundai_canfd_init(uint16_t param) { SET_RX_CHECKS(hyundai_canfd_lka_steering_rx_checks, ret); if (hyundai_canfd_lka_steering_alt) { - SET_TX_MSGS(HYUNDAI_CANFD_LKA_STEERING_ALT_TX_MSGS, ret); + if (hyundai_canfd_allow_ipedal_paddle) { + SET_TX_MSGS(HYUNDAI_CANFD_LKA_STEERING_ALT_IPEDAL_TX_MSGS, ret); + } else { + SET_TX_MSGS(HYUNDAI_CANFD_LKA_STEERING_ALT_TX_MSGS, ret); + } } else { - SET_TX_MSGS(HYUNDAI_CANFD_LKA_STEERING_TX_MSGS, ret); + if (hyundai_canfd_allow_ipedal_paddle) { + SET_TX_MSGS(HYUNDAI_CANFD_LKA_STEERING_IPEDAL_TX_MSGS, ret); + } else { + SET_TX_MSGS(HYUNDAI_CANFD_LKA_STEERING_TX_MSGS, ret); + } } } else if (!hyundai_camera_scc) { @@ -452,6 +532,7 @@ static safety_config hyundai_canfd_init(uint16_t param) { const safety_hooks hyundai_canfd_hooks = { .init = hyundai_canfd_init, + .rx_all = hyundai_canfd_rx_all_hook, .rx = hyundai_canfd_rx_hook, .tx = hyundai_canfd_tx_hook, .get_counter = hyundai_canfd_get_counter, diff --git a/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py b/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py index b5a32fbee..7313fc9a8 100755 --- a/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py +++ b/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py @@ -440,6 +440,52 @@ class TestHyundaiCanfdLKASteeringEV(TestHyundaiCanfdBase): } return self.packer.make_can_msg_safety("CRUISE_BUTTONS", bus, values) + def _regen_control_msg(self, byte24=0xA8, byte27=0x0E, counter=0x14, bus=None): + if bus is None: + bus = self.PT_BUS + values = { + "COUNTER": counter, + "BYTE3": 0x40, + "BYTE4": 0x80, + "BYTE5": 0x1F, + "BYTE6": 0x00, + "BYTE7": 0x00, + "BYTE8": 0x00, + "BYTE9": 0x00, + "BYTE10": 0x00, + "BYTE11": 0x00, + "BYTE12": 0xA8, + "BYTE13": 0x65, + "BYTE14": 0x17, + "BYTE15": 0x00, + "BYTE16": 0x00, + "BYTE17": 0xA0, + "BYTE18": 0x00, + "BYTE19": 0x00, + "BYTE20": 0x00, + "BYTE21": 0x80, + "BYTE22": 0xC0, + "BYTE23": 0x71, + "BYTE24": byte24, + "BYTE25": 0x0C, + "BYTE26": 0x12, + "BYTE27": byte27, + "BYTE28": 0x00, + "BYTE29": 0x00, + "BYTE30": 0x00, + "BYTE31": 0x00, + } + + def fix_checksum(msg): + addr, dat, msg_bus = msg + dat = bytearray(dat) + checksum = hyundaicanfd.hkg_can_fd_checksum(addr, None, dat) + dat[0] = checksum & 0xFF + dat[1] = (checksum >> 8) & 0xFF + return addr, bytes(dat), msg_bus + + return self.packer.make_can_msg_safety("IONIQ_6_REGEN_CONTROL", bus, values, fix_checksum=fix_checksum) + def test_left_paddle_send(self): for controls_allowed in (True, False): self.safety.set_controls_allowed(controls_allowed) @@ -448,6 +494,8 @@ class TestHyundaiCanfdLKASteeringEV(TestHyundaiCanfdBase): class TestHyundaiCanfdLKASteeringEVAlwaysIPedal(TestHyundaiCanfdLKASteeringEV): + TX_MSGS = TestHyundaiCanfdLKASteeringEV.TX_MSGS + [[0x25A, 1]] + def setUp(self): self.packer = CANPackerSafety("hyundai_canfd_generated") self.safety = libsafety_py.libsafety @@ -468,6 +516,19 @@ class TestHyundaiCanfdLKASteeringEVAlwaysIPedal(TestHyundaiCanfdLKASteeringEV): self.safety.set_controls_allowed(True) self.assertFalse(self._tx(self._paddle_msg(left_paddle=1))) + def test_regen_control_send(self): + stock_msg = self._regen_control_msg(byte24=0xA8, byte27=0x0E, counter=0x14) + self._rx(stock_msg) + + self.safety.set_controls_allowed(False) + self.assertTrue(self._tx(self._regen_control_msg(byte24=0xC0, byte27=0x00, counter=0x14))) + self.assertFalse(self._tx(self._regen_control_msg(byte24=0xA8, byte27=0x00, counter=0x14))) + self.assertFalse(self._tx(self._regen_control_msg(byte24=0xC0, byte27=0x0E, counter=0x14))) + self.assertFalse(self._tx(self._regen_control_msg(byte24=0xC0, byte27=0x00, counter=0x15))) + + self.safety.set_controls_allowed(True) + self.assertFalse(self._tx(self._regen_control_msg(byte24=0xC0, byte27=0x00, counter=0x14))) + # TODO: Handle ICE and HEV configurations once we see cars that use the new messages class TestHyundaiCanfdLKASteeringAltEV(TestHyundaiCanfdBase): @@ -519,6 +580,26 @@ class TestHyundaiCanfdLKASteeringLongEV(HyundaiLongitudinalBase, TestHyundaiCanf return self.packer.make_can_msg_safety("SCC_CONTROL", 1, values) +class TestHyundaiCanfdLKASteeringLongEVAlwaysIPedal(TestHyundaiCanfdLKASteeringLongEV): + + TX_MSGS = TestHyundaiCanfdLKASteeringLongEV.TX_MSGS + [[0x25A, 1]] + + def setUp(self): + self.packer = CANPackerSafety("hyundai_canfd_generated") + self.safety = libsafety_py.libsafety + self.safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, HyundaiSafetyFlags.CANFD_LKA_STEERING | + HyundaiSafetyFlags.LONG | HyundaiSafetyFlags.EV_GAS | + HyundaiStarPilotSafetyFlags.ALLOW_IPEDAL_PADDLE) + self.safety.init_tests() + + def test_regen_control_send(self): + stock_msg = self._regen_control_msg(byte24=0xA8, byte27=0x0E, counter=0x14) + self._rx(stock_msg) + + self.safety.set_controls_allowed(False) + self.assertTrue(self._tx(self._regen_control_msg(byte24=0xC0, byte27=0x00, counter=0x14))) + + # Tests longitudinal for ICE, hybrid, EV cars with LFA steering class TestHyundaiCanfdLFASteeringLongBase(HyundaiLongitudinalBase, TestHyundaiCanfdLFASteeringBase):