always I-pedal

This commit is contained in:
firestar5683
2026-05-04 20:30:03 -05:00
parent aed0b7fdb9
commit 1ddbc24a14
10 changed files with 183 additions and 9 deletions
+1
View File
@@ -159,6 +159,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> 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}},
@@ -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))
@@ -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
@@ -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)
@@ -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)
@@ -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
@@ -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"),
+1
View File
@@ -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)
@@ -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",
+19
View File
@@ -68,6 +68,21 @@ StarPilotWheelPanel::StarPilotWheelPanel(StarPilotSettingsWindow *parent, bool f
update();
});
ParamControl *alwaysIPedalToggle = new ParamControl(
"AlwaysIPedal",
tr("Always I-Pedal"),
tr("<b>Automatically spoof the left paddle when shifting into Drive</b> 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<std::tuple<QString, QString, QString, QString>> wheelToggles {
{"DistanceButtonControl", tr("Distance Button"), tr("<b>Action performed when the \"Distance\" button is pressed.</b>"), "../../starpilot/assets/toggle_icons/icon_mute.png"},
{"LongDistanceButtonControl", tr("Distance Button (Long Press)"), tr("<b>Action performed when the \"Distance\" button is pressed for more than 0.5 seconds.</b>"), "../../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" ||