diff --git a/common/params_keys.h b/common/params_keys.h index d3cd47dcb..0886a97b0 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -159,6 +159,7 @@ inline static std::unordered_map keys = { {"AlertVolumeControl", {PERSISTENT, BOOL, "0", "0", 2}}, {"AlwaysOnLateral", {PERSISTENT, BOOL, "1", "0", 0}}, {"AlwaysOnLateralLKAS", {PERSISTENT, BOOL, "1", "0", 2}}, + {"AlwaysIPedal", {PERSISTENT, BOOL, "0", "0", 2}}, {"ApiCache_DriveStats", {PERSISTENT, JSON, "{}", "{}"}}, {"AutomaticallyDownloadModels", {PERSISTENT, BOOL, "1", "0", 1}}, {"AutomaticUpdates", {PERSISTENT, BOOL, "1", "1", 0}}, diff --git a/opendbc_repo/opendbc/car/hyundai/carcontroller.py b/opendbc_repo/opendbc/car/hyundai/carcontroller.py index 59b05e9b0..d8108398d 100644 --- a/opendbc_repo/opendbc/car/hyundai/carcontroller.py +++ b/opendbc_repo/opendbc/car/hyundai/carcontroller.py @@ -53,6 +53,8 @@ IONIQ_6_STOP_RELEASE_JERK_BP = [0.0, 0.15, 0.5] IONIQ_6_STOP_RELEASE_JERK_V = [3.6 * IONIQ_6_RESPONSE_MULTIPLIER, 4.2 * IONIQ_6_RESPONSE_MULTIPLIER, 4.8 * IONIQ_6_RESPONSE_MULTIPLIER] +IONIQ_6_IPEDAL_PRESS_SEND_COUNT = 6 +IONIQ_6_IPEDAL_RETRY_WAIT_FRAMES = 30 @dataclass @@ -235,8 +237,55 @@ class CarController(CarControllerBase): self._ioniq_6_lane_change_ui_side = None self._ioniq_6_lane_change_ui_trigger_frames = 0 self._ioniq_6_long_tuning = Ioniq6LongitudinalTuningState() + self._ioniq_6_always_ipedal_pending = False + self._ioniq_6_always_ipedal_press_remaining = 0 + self._ioniq_6_always_ipedal_retry_frame = 0 + self._ioniq_6_always_ipedal_counter = 0 + self._ioniq_6_last_gear = structs.CarState.GearShifter.unknown self._genesis_g90_long_tuning = GenesisG90LongitudinalTuningState() + def _reset_ioniq_6_always_ipedal(self) -> None: + self._ioniq_6_always_ipedal_pending = False + self._ioniq_6_always_ipedal_press_remaining = 0 + self._ioniq_6_always_ipedal_retry_frame = 0 + + def _update_ioniq_6_always_ipedal(self, CC, CS, starpilot_toggles): + can_sends = [] + + if self.CP.carFingerprint != CAR.HYUNDAI_IONIQ_6 or not getattr(starpilot_toggles, "always_ipedal", False): + self._reset_ioniq_6_always_ipedal() + self._ioniq_6_last_gear = CS.out.gearShifter + return can_sends + + drive = CS.out.gearShifter == structs.CarState.GearShifter.drive + drive_edge = drive and self._ioniq_6_last_gear != structs.CarState.GearShifter.drive + + if drive_edge: + self._ioniq_6_always_ipedal_pending = not CS.ipedal_active + self._ioniq_6_always_ipedal_press_remaining = 0 + self._ioniq_6_always_ipedal_retry_frame = self.frame + elif not drive or CS.ipedal_active: + self._reset_ioniq_6_always_ipedal() + elif CC.enabled: + self._ioniq_6_always_ipedal_pending = False + self._ioniq_6_always_ipedal_press_remaining = 0 + + if drive 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_PRESS_SEND_COUNT + self._ioniq_6_always_ipedal_counter = (int(CS.buttons_counter) + 1) % 0x10 + + if self._ioniq_6_always_ipedal_press_remaining > 0 and self.frame % 2 == 0: + can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, self._ioniq_6_always_ipedal_counter, + base_values=CS.cruise_buttons_msg, left_paddle=True)) + self._ioniq_6_always_ipedal_counter = (self._ioniq_6_always_ipedal_counter + 1) % 0x10 + self._ioniq_6_always_ipedal_press_remaining -= 1 + if self._ioniq_6_always_ipedal_press_remaining == 0: + self._ioniq_6_always_ipedal_retry_frame = self.frame + IONIQ_6_IPEDAL_RETRY_WAIT_FRAMES + + self._ioniq_6_last_gear = CS.out.gearShifter + return can_sends + def update(self, CC, CS, now_nanos, starpilot_toggles): actuators = CC.actuators hud_control = CC.hudControl @@ -348,7 +397,7 @@ class CarController(CarControllerBase): # *** CAN/CAN FD specific *** if self.CP.flags & HyundaiFlags.CANFD: can_sends.extend(self.create_canfd_msgs(now_nanos, apply_steer_req, apply_torque, apply_angle, set_speed_in_units, accel, - stopping, hud_control, CS, CC)) + stopping, hud_control, CS, CC, starpilot_toggles)) else: can_sends.extend(self.create_can_msgs(apply_steer_req, apply_torque, torque_fault, set_speed_in_units, accel, stopping, hud_control, actuators, CS, CC)) @@ -428,7 +477,7 @@ class CarController(CarControllerBase): return can_sends - def create_canfd_msgs(self, now_nanos, apply_steer_req, apply_torque, apply_angle, set_speed_in_units, accel, stopping, hud_control, CS, CC): + def create_canfd_msgs(self, now_nanos, apply_steer_req, apply_torque, apply_angle, set_speed_in_units, accel, stopping, hud_control, CS, CC, starpilot_toggles): can_sends = [] lka_steering = self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING @@ -479,6 +528,8 @@ class CarController(CarControllerBase): if self._ioniq_6_lane_change_ui_trigger_frames > 0: self._ioniq_6_lane_change_ui_trigger_frames -= 1 + can_sends.extend(self._update_ioniq_6_always_ipedal(CC, CS, starpilot_toggles)) + if self.long_active_ecu: if lka_steering: can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame)) diff --git a/opendbc_repo/opendbc/car/hyundai/carstate.py b/opendbc_repo/opendbc/car/hyundai/carstate.py index cafe7cdd3..f231e366c 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_IPEDAL_REGEN_STATE = 0x50 +IONIQ_6_IPEDAL_REGEN_STATE_2 = 0x03 def calculate_canfd_speed_limit(CP, FPCP, cp, cp_cam, speed_factor): @@ -43,6 +45,10 @@ def decode_ioniq_6_blindspot_radar_state(state: int) -> tuple[bool, bool]: return bool(state_int & IONIQ_6_BLINDSPOT_LEFT_MASK), bool(state_int & IONIQ_6_BLINDSPOT_RIGHT_MASK) +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 + + class CarState(CarStateBase): @staticmethod def get_canfd_blinker_sig_names(car_fingerprint, use_alt_lamp: bool) -> tuple[str, str]: @@ -61,6 +67,8 @@ class CarState(CarStateBase): self.mode_button = 0 self.custom_button = 0 self.cancel_button_enable_in_progress = False + self.cruise_buttons_msg = {} + self.ipedal_active = False self.gear_msg_canfd = "ACCELERATOR" if CP.flags & HyundaiFlags.EV else \ "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else \ @@ -309,6 +317,7 @@ class CarState(CarStateBase): self.is_metric = cp.vl["CRUISE_BUTTONS_ALT"]["DISTANCE_UNIT"] != 1 speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS + self.ipedal_active = False if self.CP.flags & (HyundaiFlags.EV | HyundaiFlags.HYBRID): ret.gasPressed = cp.vl[self.accelerator_msg_canfd]["ACCELERATOR_PEDAL"] > 1e-5 @@ -386,6 +395,9 @@ class CarState(CarStateBase): # TODO: find this message on ICE & HYBRID cars + cruise control signals (if exists) if self.CP.flags & HyundaiFlags.EV: ret.cruiseState.nonAdaptive = cp.vl["MANUAL_SPEED_LIMIT_ASSIST"]["MSLA_ENABLED"] == 1 + if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6: + msla = cp.vl["MANUAL_SPEED_LIMIT_ASSIST"] + self.ipedal_active = decode_ioniq_6_ipedal_state(msla["EV_REGEN_STATE"], msla["EV_REGEN_STATE_2"]) prev_cruise_buttons = self.cruise_buttons[-1] prev_main_buttons = self.main_buttons[-1] @@ -396,6 +408,7 @@ class CarState(CarStateBase): self.lda_button = cp.vl[self.cruise_btns_msg_canfd]["LDA_BTN"] self.left_paddle = 0 if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6: + self.cruise_buttons_msg = copy.copy(cp.vl["CRUISE_BUTTONS"]) self.left_paddle = cp.vl["CRUISE_BUTTONS"]["LEFT_PADDLE"] self.buttons_counter = cp.vl[self.cruise_btns_msg_canfd]["COUNTER"] ret.accFaulted = cp.vl["TCS"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED diff --git a/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py b/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py index 37d0f65a1..f5c2944f2 100644 --- a/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py +++ b/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py @@ -156,12 +156,15 @@ def create_suppress_lfa(packer, CAN, lfa_block_msg, lka_steering_alt): return packer.make_can_msg(suppress_msg, CAN.ACAN, values) -def create_buttons(packer, CP, CAN, cnt, btn): - values = { +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({ "COUNTER": cnt, "SET_ME_1": 1, "CRUISE_BUTTONS": btn, - } + "LEFT_PADDLE": int(left_paddle), + "RIGHT_PADDLE": int(right_paddle), + }) bus = CAN.ECAN if CP.flags & HyundaiFlags.CANFD_LKA_STEERING else CAN.CAM return packer.make_can_msg("CRUISE_BUTTONS", bus, values) diff --git a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py index cd383e825..363d1b77c 100644 --- a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py +++ b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py @@ -7,9 +7,9 @@ from opendbc.can import CANPacker, CANParser 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 Ioniq6LongitudinalTuningState, GenesisG90LongitudinalTuningState, \ +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 +from opendbc.car.hyundai.carstate import CarState, decode_ioniq_6_blindspot_radar_state, decode_ioniq_6_ipedal_state from opendbc.car.hyundai.interface import CarInterface from opendbc.car.hyundai import hyundaican, hyundaicanfd from opendbc.car.hyundai.hyundaicanfd import CanBus @@ -53,7 +53,7 @@ CANFD_EXPECTED_ECUS = {Ecu.fwdCamera, Ecu.fwdRadar} def get_test_toggles() -> SimpleNamespace: - return SimpleNamespace(always_on_lateral_lkas=False, force_torque_controller=False, nnff=False, nnff_lite=False) + return SimpleNamespace(always_ipedal=False, always_on_lateral_lkas=False, force_torque_controller=False, nnff=False, nnff_lite=False) class TestHyundaiFingerprint: @@ -294,6 +294,78 @@ class TestHyundaiFingerprint: ret = update(0, 3) assert any(be.type == ButtonType.altButton2 and not be.pressed for be in ret.buttonEvents) + def test_ioniq_6_ipedal_state_decode(self): + assert not decode_ioniq_6_ipedal_state(0x3C, 0x01) + assert decode_ioniq_6_ipedal_state(0x50, 0x03) + + def test_ioniq_6_msla_regen_signals_decode(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], [("MANUAL_SPEED_LIMIT_ASSIST", 0)], can_bus.ECAN) + + msg = packer.make_can_msg("MANUAL_SPEED_LIMIT_ASSIST", can_bus.ECAN, { + "MSLA_STATUS": 0, + "MSLA_ENABLED": 0, + "MAX_SPEED": 0, + "MAX_SPEED_COPY": 0, + "EV_REGEN_STATE": 0x50, + "EV_REGEN_STATE_2": 0x03, + }) + parser.update([(1, [msg])]) + + assert parser.can_valid + assert parser.vl["MANUAL_SPEED_LIMIT_ASSIST"]["EV_REGEN_STATE"] == 0x50 + assert parser.vl["MANUAL_SPEED_LIMIT_ASSIST"]["EV_REGEN_STATE_2"] == 0x03 + + def test_ioniq_6_always_ipedal_spoofs_left_paddle_until_latched(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) + can_bus = CanBus(CP) + parser_bus = can_bus.ECAN if CP.flags & HyundaiFlags.CANFD_LKA_STEERING else can_bus.CAM + parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("CRUISE_BUTTONS", 0)], parser_bus) + + cs = SimpleNamespace( + out=SimpleNamespace(gearShifter=structs.CarState.GearShifter.drive), + ipedal_active=False, + 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, + }, + ) + cc = SimpleNamespace(enabled=False) + + controller._ioniq_6_last_gear = structs.CarState.GearShifter.reverse + sends = controller._update_ioniq_6_always_ipedal(cc, cs, toggles) + parser.update([(1, sends)]) + + assert sends + assert parser.vl["CRUISE_BUTTONS"]["LEFT_PADDLE"] == 1 + assert parser.vl["CRUISE_BUTTONS"]["COUNTER"] == 6 + assert controller._ioniq_6_always_ipedal_pending + + controller.frame = 2 + cs.ipedal_active = True + sends = controller._update_ioniq_6_always_ipedal(cc, cs, toggles) + + assert sends == [] + assert not controller._ioniq_6_always_ipedal_pending + assert controller._ioniq_6_always_ipedal_press_remaining == 0 + 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 fb9b336c1..91b1f60a5 100644 --- a/opendbc_repo/opendbc/dbc/hyundai_canfd_generated.dbc +++ b/opendbc_repo/opendbc/dbc/hyundai_canfd_generated.dbc @@ -822,8 +822,10 @@ BO_ 736 MANUAL_SPEED_LIMIT_ASSIST: 32 XXX SG_ COUNTER : 16|8@1+ (1,0) [0|255] "" XXX SG_ MSLA_STATUS : 26|2@1+ (1,0) [0|3] "" XXX SG_ MSLA_ENABLED : 38|1@1+ (1,0) [0|1] "" XXX + SG_ EV_REGEN_STATE : 104|8@1+ (1,0) [0|255] "" XXX SG_ MAX_SPEED : 55|8@0+ (1,0) [0|255] "" XXX SG_ MAX_SPEED_COPY : 144|8@1+ (1,0) [0|255] "" XXX + SG_ EV_REGEN_STATE_2 : 184|8@1+ (1,0) [0|255] "" XXX BO_ 837 ADRV_0x345: 8 ADRV SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX diff --git a/selfdrive/ui/layouts/settings/starpilot/vehicle.py b/selfdrive/ui/layouts/settings/starpilot/vehicle.py index dff312025..4e24773e4 100644 --- a/selfdrive/ui/layouts/settings/starpilot/vehicle.py +++ b/selfdrive/ui/layouts/settings/starpilot/vehicle.py @@ -177,7 +177,7 @@ class VehicleSettingsManagerView(Widget): count = 1 if cs.isGM: count += 4 if cs.isGM and cs.isVolt and not cs.hasSNG: count += 1 - if cs.isHKG and cs.isHKGCanFd: count += 1 + if cs.isHKG and cs.isHKGCanFd: count += 2 if cs.isSubaru: count += 1 if cs.isToyota: count += 4 if cs.isToyota and not cs.hasSNG: count += 1 @@ -469,6 +469,11 @@ class VehicleSettingsManagerView(Widget): rows.append({"target_id": "toggle:RemapCancelToDistance", "type": "toggle", "title": tr("Remap Cancel Button"), "subtitle": tr("Remap the Cancel button to act as the Distance button."), "get_state": lambda: self._controller._params.get_bool("RemapCancelToDistance")}) + if cs.isHKGCanFd: + rows.append({"target_id": "toggle:AlwaysIPedal", "type": "toggle", + "title": tr("Always I-Pedal"), + "subtitle": tr("Spoof the left paddle after shifting into Drive until i-Pedal latches."), + "get_state": lambda: self._controller._params.get_bool("AlwaysIPedal")}) if cs.isHKGCanFd and cs.hasOpenpilotLongitudinal: rows.append({"target_id": "toggle:NostalgiaMode", "type": "toggle", "title": tr("Nostalgia Mode"), diff --git a/starpilot/common/starpilot_variables.py b/starpilot/common/starpilot_variables.py index 5dcf8c4c7..1329f4ae3 100644 --- a/starpilot/common/starpilot_variables.py +++ b/starpilot/common/starpilot_variables.py @@ -769,6 +769,7 @@ class StarPilotVariables: toggle.no_uploads = self.get_value("NoUploads", condition=device_management and not self.vetting_branch) toggle.no_onroad_uploads = self.get_value("DisableOnroadUploads", condition=toggle.no_uploads) + toggle.always_ipedal = self.get_value("AlwaysIPedal", condition=toggle.car_model == HYUNDAI_CAR.HYUNDAI_IONIQ_6) toggle.nostalgia_mode = self.get_value("NostalgiaMode", condition=toggle.openpilot_longitudinal and toggle.car_model == HYUNDAI_CAR.HYUNDAI_IONIQ_6) distance_button_control = self.get_value("DistanceButtonControl", cast=float) diff --git a/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json b/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json index 3a4ab4e19..870bc3e0f 100644 --- a/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json +++ b/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json @@ -2323,6 +2323,13 @@ "data_type": "bool", "ui_type": "toggle" }, + { + "key": "AlwaysIPedal", + "label": "Always I-Pedal", + "description": "Automatically spoof the left paddle when shifting into Drive until i-Pedal latches on supported Hyundai CAN-FD cars.", + "data_type": "bool", + "ui_type": "toggle" + }, { "key": "NostalgiaMode", "label": "Nostalgia Mode", diff --git a/starpilot/ui/qt/offroad/wheel_settings.cc b/starpilot/ui/qt/offroad/wheel_settings.cc index 0ed338b49..acaf64a5d 100644 --- a/starpilot/ui/qt/offroad/wheel_settings.cc +++ b/starpilot/ui/qt/offroad/wheel_settings.cc @@ -68,6 +68,21 @@ StarPilotWheelPanel::StarPilotWheelPanel(StarPilotSettingsWindow *parent, bool f update(); }); + ParamControl *alwaysIPedalToggle = new ParamControl( + "AlwaysIPedal", + tr("Always I-Pedal"), + tr("Automatically spoof the left paddle when shifting into Drive until i-Pedal latches on supported Hyundai CAN-FD cars."), + "../../starpilot/assets/toggle_icons/icon_mute.png" + ); + toggles["AlwaysIPedal"] = alwaysIPedalToggle; + addItem(alwaysIPedalToggle); + QObject::connect(alwaysIPedalToggle, &AbstractControl::hideDescriptionEvent, [this]() { + update(); + }); + QObject::connect(alwaysIPedalToggle, &AbstractControl::showDescriptionEvent, [this]() { + update(); + }); + const std::vector> wheelToggles { {"DistanceButtonControl", tr("Distance Button"), tr("Action performed when the \"Distance\" button is pressed."), "../../starpilot/assets/toggle_icons/icon_mute.png"}, {"LongDistanceButtonControl", tr("Distance Button (Long Press)"), tr("Action performed when the \"Distance\" button is pressed for more than 0.5 seconds."), "../../starpilot/assets/toggle_icons/icon_mute.png"}, @@ -148,6 +163,10 @@ void StarPilotWheelPanel::updateToggles() { setVisible &= parent->hasOpenpilotLongitudinal; } + if (!showAllToggles && key == "AlwaysIPedal") { + setVisible &= parent->isHKGCanFd; + } + if (!showAllToggles && ( key == "ModeButtonControl" || key == "LongModeButtonControl" ||