diff --git a/.github/workflows/compile_bluescreens.yaml b/.github/workflows/compile_bluescreens.yaml index c17cf0830..184bc96fc 100644 --- a/.github/workflows/compile_bluescreens.yaml +++ b/.github/workflows/compile_bluescreens.yaml @@ -165,6 +165,10 @@ jobs: "panda/board/obj/panda_h7.bin.signed" "panda/board/obj/panda_remote.bin.signed" "panda/board/obj/panda_h7_remote.bin.signed" + "panda/board/obj/panda_can_ignition_only.bin.signed" + "panda/board/obj/panda_h7_can_ignition_only.bin.signed" + "panda/board/obj/panda_remote_can_ignition_only.bin.signed" + "panda/board/obj/panda_h7_remote_can_ignition_only.bin.signed" "panda/board/obj/panda_jungle_h7.bin.signed" "panda/board/obj/body_h7.bin.signed" ) diff --git a/common/params_keys.h b/common/params_keys.h index f46414262..988da57aa 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -286,6 +286,7 @@ inline static std::unordered_map keys = { {"FlashPanda", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}}, {"GMDashSpoofOffsets", {PERSISTENT, BOOL, "0", "0", 2}}, {"GMPedalLongitudinal", {PERSISTENT, BOOL, "1", "1", 2}}, + {"IgnoreIgnitionLine", {PERSISTENT, BOOL, "0", "0"}}, {"LongPitch", {PERSISTENT, BOOL, "1", "0", 2}}, {"RemoteStartBootsComma", {PERSISTENT, BOOL, "0", "0"}}, {"RemapCancelToDistance", {PERSISTENT, BOOL, "0", "0"}}, diff --git a/opendbc_repo/opendbc/car/hyundai/carcontroller.py b/opendbc_repo/opendbc/car/hyundai/carcontroller.py index 12befdac3..d8201bbc7 100644 --- a/opendbc_repo/opendbc/car/hyundai/carcontroller.py +++ b/opendbc_repo/opendbc/car/hyundai/carcontroller.py @@ -4,7 +4,7 @@ import numpy as np from opendbc.can import CANPacker from opendbc.car import Bus, DT_CTRL, make_tester_present_msg, rate_limit, structs from opendbc.car.common.filter_simple import FirstOrderFilter -from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_steer_angle_limits_vm, common_fault_avoidance +from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_steer_angle_limits_vm, common_fault_avoidance, get_max_angle_delta_vm from opendbc.car.common.conversions import Conversions as CV from opendbc.car.hyundai import hyundaicanfd, hyundaican from opendbc.car.hyundai.hyundaicanfd import CanBus @@ -64,6 +64,10 @@ REDNECK_BUTTON_COPIES_TIME = 7 REDNECK_BUTTON_COPIES_TIME_IMPERIAL = [REDNECK_BUTTON_COPIES_TIME + 3, 70] REDNECK_BUTTON_COPIES_TIME_METRIC = [REDNECK_BUTTON_COPIES_TIME, 40] ANGLE_SAFETY_BASELINE_MODEL = str(CAR.KIA_SPORTAGE_HEV_2026) +DEFAULT_ANGLE_SMOOTHING_VEGO_BP = [5.0, 10.0, 20.0] +DEFAULT_ANGLE_SMOOTHING_ALPHA_V = [0.2, 0.1, 0.0] +KIA_EV9_ANGLE_SMOOTHING_VEGO_BP = [0.0, 8.5, 11.0, 13.8, 18.0] +KIA_EV9_ANGLE_SMOOTHING_ALPHA_V = [0.05, 0.1, 0.3, 0.6, 1.0] def egmp_dynamic_longitudinal_tuning(CP) -> bool: @@ -220,6 +224,22 @@ def get_baseline_safety_cp(): return CarInterface.get_non_essential_params(ANGLE_SAFETY_BASELINE_MODEL) +def get_angle_smoothing_alpha(CP, v_ego: float) -> float: + if CP.carFingerprint == CAR.KIA_EV9: + return float(np.interp(v_ego, KIA_EV9_ANGLE_SMOOTHING_VEGO_BP, KIA_EV9_ANGLE_SMOOTHING_ALPHA_V)) + return float(np.interp(v_ego, DEFAULT_ANGLE_SMOOTHING_VEGO_BP, DEFAULT_ANGLE_SMOOTHING_ALPHA_V)) + + +def apply_steer_angle_limits_vm_checked(apply_angle: float, apply_angle_last: float, v_ego_raw: float, + steering_angle: float, lat_active: bool, limits, VM: VehicleModel) -> float | None: + new_apply_angle = apply_steer_angle_limits_vm(apply_angle, apply_angle_last, v_ego_raw, steering_angle, lat_active, limits, VM) + v_ego_raw = max(v_ego_raw, 1.0) + max_angle_delta = min(get_max_angle_delta_vm(v_ego_raw, VM, limits), limits.ANGLE_LIMITS.MAX_ANGLE_RATE) + safety_violation = lat_active and not np.isclose(new_apply_angle, + rate_limit(new_apply_angle, apply_angle_last, -max_angle_delta, max_angle_delta)) + return None if safety_violation else new_apply_angle + + def compute_torque_reduction_gain(steering_torque, v_ego, lat_active, last_gain): if lat_active: ceiling = np.interp(v_ego, [0.5, 1.5], [1.0, 0.85]) @@ -382,15 +402,16 @@ class CarController(CarControllerBase): -self.params.ANGLE_LIMITS.STEER_ANGLE_MAX, self.params.ANGLE_LIMITS.STEER_ANGLE_MAX)) - self.angle_filter.update_alpha(float(np.interp(CS.out.vEgo, [5.0, 10.0, 20.0], [0.2, 0.1, 0.0]))) + self.angle_filter.update_alpha(get_angle_smoothing_alpha(self.CP, CS.out.vEgo)) desired_angle = self.angle_filter.update(desired_angle) - apply_angle = apply_steer_angle_limits_vm(desired_angle, self.apply_angle_last, v_ego_raw, - CS.out.steeringAngleDeg, CC.latActive, self.params, self.VM) + angle_limit_fn = apply_steer_angle_limits_vm_checked if self.CP.carFingerprint == CAR.KIA_EV9 else apply_steer_angle_limits_vm + apply_angle = angle_limit_fn(desired_angle, self.apply_angle_last, v_ego_raw, + CS.out.steeringAngleDeg, CC.latActive, self.params, self.VM) if str(self.CP.carFingerprint) != ANGLE_SAFETY_BASELINE_MODEL: - apply_angle = apply_steer_angle_limits_vm(apply_angle or desired_angle, self.apply_angle_last, v_ego_raw, - CS.out.steeringAngleDeg, CC.latActive, self.params, self.BASELINE_VM) + apply_angle = angle_limit_fn(apply_angle or desired_angle, self.apply_angle_last, v_ego_raw, + CS.out.steeringAngleDeg, CC.latActive, self.params, self.BASELINE_VM) apply_torque = compute_torque_reduction_gain(CS.out.steeringTorque, v_ego_raw, CC.latActive, self.apply_torque_last) apply_steer_req = CC.latActive and apply_torque != 0.0 @@ -650,7 +671,8 @@ class CarController(CarControllerBase): # 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)) + can_sends.append(hyundaicanfd.create_accelerator_brake_alt_spoof(0, self.frame // 4, 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: diff --git a/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py b/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py index f1fec6c37..00467c3d3 100644 --- a/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py +++ b/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py @@ -96,6 +96,8 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque, lfa_base_values=None, lkas_base_values=None, lka_icon=None): if lka_icon is None: lka_icon = 2 if enabled else 1 + ev9_angle_lkas_alt = str(CP.carFingerprint) == "KIA_EV9" and CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING and \ + CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT control_values = { "LKA_MODE": 2, @@ -128,6 +130,24 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque, lkas_values["ADAS_StrAnglReqVal"] = apply_angle lkas_values["LKAS_ANGLE_ACTIVE"] = 2 if lat_active else 1 lkas_values["ADAS_ACIAnglTqRedcGainVal"] = apply_torque if lat_active else 0.0 + if ev9_angle_lkas_alt: + lkas_values.update({ + "LKA_MODE": 0, + "LKA_AVAILABLE": 3 if lat_active else 0, + "LKA_WARNING": 0, + "LKA_ICON": lka_icon, + "FCA_SYSWARN": 0, + "TORQUE_REQUEST": 0, + "STEER_REQ": 0, + "LFA_BUTTON": 0, + "LKA_ASSIST": 0, + "DAMP_FACTOR": 100, + "HAS_LANE_SAFETY": 0, + }) + # These signals overlap DAMP_FACTOR in the local DBC naming; omitting them + # preserves the stock angle-steering damping byte expected by the ADAS ECU. + lkas_values.pop("STEER_MODE", None) + lkas_values.pop("NEW_SIGNAL_2", None) ret = [] if CP.flags & HyundaiFlags.CANFD_LKA_STEERING: @@ -709,11 +729,15 @@ def hkg_can_fd_checksum(address: int, sig, d: bytearray) -> int: # radar stops publishing real object tracks. Spoof this message ourselves with valid CRC # and current pedal state so the radar keeps tracking. # Length is 24 bytes on Ioniq 6 (DBC declares 32 for ICE Hyundais, but EV firmware uses 24). -# Byte template captured from a real ADAS broadcast; bytes 6-23 appear static / config. +# Byte templates captured from real ADAS broadcasts; only checksum, counter, +# 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") -def create_accelerator_brake_alt_spoof(bus: int, counter: int, brake_pressed: bool, accelerator_pressed: bool) -> CanData: - d = bytearray(_ACCEL_BRAKE_ALT_TEMPLATE) +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 + 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) d[22] = (d[22] & ~0x01) | (0x01 if accelerator_pressed else 0x00) # ACCELERATOR_PEDAL_PRESSED (bit 176) diff --git a/opendbc_repo/opendbc/car/hyundai/interface.py b/opendbc_repo/opendbc/car/hyundai/interface.py index 797c6ee0e..468d578d8 100644 --- a/opendbc_repo/opendbc/car/hyundai/interface.py +++ b/opendbc_repo/opendbc/car/hyundai/interface.py @@ -4,6 +4,7 @@ from opendbc.car.hyundai.hyundaicanfd import CanBus from opendbc.car.hyundai.values import HyundaiFlags, CAR, CarControllerParams, \ CANFD_UNSUPPORTED_LONGITUDINAL_CAR, \ CANFD_SECURITYACCESS_CAR, \ + CANFD_ANGLE_LONGITUDINAL_CAR, \ CANFD_RADAR_LIVE_LONGITUDINAL_CAR, \ RADAR_LIVE_LONGITUDINAL_CAR, \ UNSUPPORTED_LONGITUDINAL_CAR, HyundaiSafetyFlags, \ @@ -99,8 +100,8 @@ class CarInterface(CarInterfaceBase): # this needs to be figured out for cars without an ADAS ECU # Cars in CANFD_SECURITYACCESS_CAR are known to have ADAS ECUs that work with SecurityAccess ret.alphaLongitudinalAvailable = False - if lka_steering and ret.flags & HyundaiFlags.CANFD_ANGLE_STEERING: - # Angle-steering LKA platforms still need stock longitudinal validation. + if lka_steering and ret.flags & HyundaiFlags.CANFD_ANGLE_STEERING and candidate not in CANFD_ANGLE_LONGITUDINAL_CAR: + # Most angle-steering LKA platforms still need stock longitudinal validation. ret.alphaLongitudinalAvailable = False ret.enableBsm = 0x1ba in fingerprint[CAN.ECAN] diff --git a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py index c5a0f5599..f61158134 100644 --- a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py +++ b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py @@ -10,7 +10,8 @@ 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, egmp_dynamic_longitudinal_tuning, \ - should_reset_ev6_gt_line_longitudinal_tuning, reset_ev6_gt_line_longitudinal_tuning + should_reset_ev6_gt_line_longitudinal_tuning, reset_ev6_gt_line_longitudinal_tuning, \ + get_angle_smoothing_alpha, apply_steer_angle_limits_vm_checked from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, decode_ioniq_6_blindspot_radar_state from opendbc.car.hyundai.interface import CarInterface from opendbc.car.hyundai import hyundaican, hyundaicanfd @@ -22,6 +23,7 @@ from opendbc.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR UNSUPPORTED_LONGITUDINAL_CAR, PLATFORM_CODE_ECUS, HYUNDAI_VERSION_REQUEST_LONG, \ LEGACY_LONGITUDINAL_CAR, CarControllerParams, DBC, HyundaiFlags, get_platform_codes, HyundaiSafetyFlags, \ HyundaiStarPilotSafetyFlags, Buttons, kia_ev6_gt_line_longitudinal_tuning +from opendbc.car.vehicle_model import VehicleModel LongCtrlState = CarControl.Actuators.LongControlState from opendbc.car.hyundai.fingerprints import FW_VERSIONS @@ -168,6 +170,7 @@ class TestHyundaiFingerprint: (CAR.GENESIS_GV60_EV_1ST_GEN, MRR30_RADAR_START_ADDR), (CAR.HYUNDAI_KONA_EV_2ND_GEN, MRR35_RADAR_START_ADDR), (CAR.HYUNDAI_IONIQ_9, MRR35_RADAR_START_ADDR), + (CAR.KIA_EV9, MRR35_RADAR_START_ADDR), ): radar_config = get_radar_track_config(candidate) assert radar_config.start_addr == radar_addr @@ -226,6 +229,21 @@ class TestHyundaiFingerprint: assert CP.openpilotLongitudinalControl assert not CP.radarUnavailable + ev9_radar_config = get_radar_track_config(CAR.KIA_EV9) + fingerprint = gen_empty_fingerprint() + fingerprint[CanBus(None, fingerprint).CAM][0x110] = 32 + 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 CP.openpilotLongitudinalControl + assert not CP.radarUnavailable + assert CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT + assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.LONG + assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CANFD_ANGLE_STEERING + + CP = CarInterface.get_params(CAR.KIA_EV9, fingerprint, [], True, False, False, None) + assert not CP.openpilotLongitudinalControl + 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) @@ -260,6 +278,22 @@ class TestHyundaiFingerprint: assert CP.steerControlType == CarParams.SteerControlType.angle assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CANFD_ANGLE_STEERING + def test_ev9_angle_smoothing_is_ev9_specific(self): + ev9_cp = SimpleNamespace(carFingerprint=CAR.KIA_EV9) + other_cp = SimpleNamespace(carFingerprint=CAR.KIA_SPORTAGE_HEV_2026) + + assert get_angle_smoothing_alpha(ev9_cp, 0.0) == pytest.approx(0.05) + assert get_angle_smoothing_alpha(ev9_cp, 13.8) == pytest.approx(0.6) + assert get_angle_smoothing_alpha(ev9_cp, 20.0) == pytest.approx(1.0) + assert get_angle_smoothing_alpha(other_cp, 20.0) == pytest.approx(0.0) + + def test_checked_angle_limiter_blocks_rate_accel_conflict(self): + CP = CarInterface.get_non_essential_params(CAR.KIA_EV9) + params = CarControllerParams(CP) + + assert apply_steer_angle_limits_vm_checked(0.0, 100.0, 20.0, 100.0, True, + params, VehicleModel(CP)) is None + def test_ccnc_hda2_lka_layout_does_not_set_ccnc_safety_param(self): fingerprint = gen_empty_fingerprint() cam_can = CanBus(None, fingerprint).CAM @@ -1554,6 +1588,67 @@ class TestHyundaiFingerprint: assert parser.vl["LKAS_ALT"]["STEER_REQ"] == 1 assert parser.vl["LKAS_ALT"]["LKA_ICON"] == 2 + def test_ev9_angle_lkas_alt_uses_angle_status_fields(self): + CP = CarParams.new_message() + CP.carFingerprint = CAR.KIA_EV9 + CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | 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], [("LKAS_ALT", 0)], can_bus.ACAN) + + stock_lkas = { + "CHECKSUM": 1234, + "COUNTER": 42, + "LKA_MODE": 2, + "LKA_AVAILABLE": 0, + "LKA_WARNING": 1, + "LKA_ICON": 1, + "FCA_SYSWARN": 1, + "TORQUE_REQUEST": 17, + "STEER_REQ": 1, + "LFA_BUTTON": 1, + "LKA_ASSIST": 1, + "STEER_MODE": 5, + "NEW_SIGNAL_2": 0, + "LKAS_ANGLE_ACTIVE": 1, + "HAS_LANE_SAFETY": 1, + "ADAS_StrAnglReqVal": 12.3, + "ADAS_ACIAnglTqRedcGainVal": 0.42, + "DAMP_FACTOR": 0, + } + + msgs = hyundaicanfd.create_steering_messages(packer, CP, can_bus, True, True, 0.44, -31.5, + lkas_base_values=stock_lkas, lka_icon=2) + lkas_msgs = [msg for msg in msgs if msg[0] == 0x110] + assert len(lkas_msgs) == 1 + + parser.update([(1, lkas_msgs)]) + + assert parser.can_valid + assert parser.vl["LKAS_ALT"]["LKA_MODE"] == 0 + assert parser.vl["LKAS_ALT"]["LKA_AVAILABLE"] == 3 + assert parser.vl["LKAS_ALT"]["LKA_WARNING"] == 0 + assert parser.vl["LKAS_ALT"]["LKA_ICON"] == 2 + assert parser.vl["LKAS_ALT"]["FCA_SYSWARN"] == 0 + assert parser.vl["LKAS_ALT"]["TORQUE_REQUEST"] == 0 + assert parser.vl["LKAS_ALT"]["STEER_REQ"] == 0 + assert parser.vl["LKAS_ALT"]["LFA_BUTTON"] == 0 + assert parser.vl["LKAS_ALT"]["LKA_ASSIST"] == 0 + assert parser.vl["LKAS_ALT"]["DAMP_FACTOR"] == 100 + assert parser.vl["LKAS_ALT"]["LKAS_ANGLE_ACTIVE"] == 2 + assert parser.vl["LKAS_ALT"]["HAS_LANE_SAFETY"] == 0 + assert parser.vl["LKAS_ALT"]["ADAS_StrAnglReqVal"] == pytest.approx(-31.5) + assert parser.vl["LKAS_ALT"]["ADAS_ACIAnglTqRedcGainVal"] == pytest.approx(0.44) + + def test_ev9_accelerator_brake_alt_spoof_matches_route_template(self): + msg = hyundaicanfd.create_accelerator_brake_alt_spoof(0, 0x66, True, False, CAR.KIA_EV9) + + assert msg.address == 0x100 + assert msg.src == 0 + assert msg.dat.hex() == "470c6600ff006f00e80400001201030055ffff0000000000" + def test_ioniq_6_lfahda_cluster_allows_lfa_icon_override(self): CP = CarParams.new_message() CP.carFingerprint = CAR.HYUNDAI_IONIQ_6 diff --git a/opendbc_repo/opendbc/car/hyundai/values.py b/opendbc_repo/opendbc/car/hyundai/values.py index 96e19dcb2..bfbf9934c 100644 --- a/opendbc_repo/opendbc/car/hyundai/values.py +++ b/opendbc_repo/opendbc/car/hyundai/values.py @@ -1129,7 +1129,9 @@ CANFD_RADAR_SCC_CAR = CAR.with_flags(HyundaiFlags.RADAR_SCC) # TODO: merge with # 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_UNSUPPORTED_LONGITUDINAL_CAR = CAR.with_flags(HyundaiFlags.CANFD_NO_RADAR_DISABLE) - CANFD_SECURITYACCESS_CAR # TODO: merge with UNSUPPORTED_LONGITUDINAL_CAR -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_RADAR_LIVE_LONGITUDINAL_CAR = {CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6, CAR.GENESIS_GV60_EV_1ST_GEN, + CAR.KIA_EV9} RADAR_LIVE_LONGITUDINAL_CAR = CANFD_RADAR_LIVE_LONGITUDINAL_CAR | { CAR.HYUNDAI_IONIQ, CAR.HYUNDAI_KONA_EV_2022, diff --git a/opendbc_repo/opendbc/car/toyota/interface.py b/opendbc_repo/opendbc/car/toyota/interface.py index 18a88fc5c..57febbcb9 100644 --- a/opendbc_repo/opendbc/car/toyota/interface.py +++ b/opendbc_repo/opendbc/car/toyota/interface.py @@ -68,10 +68,11 @@ class CarInterface(CarInterfaceBase): if candidate == CAR.TOYOTA_PRIUS: stop_and_go = True + ret.flags |= ToyotaFlags.HYBRID.value # Only give steer angle deadzone to for bad angle sensor prius for fw in car_fw: if fw.ecu == "eps" and not fw.fwVersion == b'8965B47060\x00\x00\x00\x00\x00\x00': - ret.steerActuatorDelay = 0.25 + ret.steerActuatorDelay = 0.14 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg=0.3) elif candidate in (CAR.LEXUS_RX, CAR.LEXUS_RX_TSS2): @@ -154,7 +155,9 @@ class CarInterface(CarInterfaceBase): # to a negative value, so it won't matter. ret.minEnableSpeed = -1. if (stop_and_go or ret.enableGasInterceptorDEPRECATED) else MIN_ACC_SPEED - if candidate in TSS2_CAR or ret.enableGasInterceptorDEPRECATED: + prius_long_defaults = candidate == CAR.TOYOTA_PRIUS and ret.openpilotLongitudinalControl + + if candidate in TSS2_CAR or ret.enableGasInterceptorDEPRECATED or prius_long_defaults: ret.flags |= ToyotaFlags.RAISED_ACCEL_LIMIT.value ret.vEgoStopping = 0.25 diff --git a/opendbc_repo/opendbc/car/toyota/tests/test_toyota.py b/opendbc_repo/opendbc/car/toyota/tests/test_toyota.py index 88793485e..d71aa912e 100644 --- a/opendbc_repo/opendbc/car/toyota/tests/test_toyota.py +++ b/opendbc_repo/opendbc/car/toyota/tests/test_toyota.py @@ -61,6 +61,24 @@ class TestToyotaInterfaces: assert car_params.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value assert car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.ALLOW_AEB + def test_prius_openpilot_long_uses_hybrid_long_defaults(self): + car_params = CarInterface.get_params( + CAR.TOYOTA_PRIUS, + {0: {0x2FF: 8}}, + [], + alpha_long=False, + is_release=False, + docs=False, + starpilot_toggles=SimpleNamespace(), + ) + + assert car_params.openpilotLongitudinalControl + assert car_params.flags & ToyotaFlags.HYBRID.value + assert car_params.flags & ToyotaFlags.RAISED_ACCEL_LIMIT.value + assert abs(car_params.longitudinalActuatorDelay - 0.05) < 1e-6 + assert abs(car_params.vEgoStopping - 0.25) < 1e-6 + assert abs(car_params.vEgoStarting - 0.25) < 1e-6 + def test_essential_ecus(self, subtests): # Asserts standard ECUs exist for each platform common_ecus = {Ecu.fwdRadar, Ecu.fwdCamera} diff --git a/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h b/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h index f589cbf5a..12fb32428 100644 --- a/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h +++ b/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h @@ -221,7 +221,8 @@ static bool hyundai_canfd_tx_hook(const CANPacket_t *msg) { } // steering - const unsigned int steer_addr = (hyundai_canfd_lka_steering && !hyundai_longitudinal) ? hyundai_canfd_get_lka_addr() : 0x12aU; + const unsigned int steer_addr = (hyundai_canfd_lka_steering && (hyundai_canfd_angle_steering || !hyundai_longitudinal)) ? + hyundai_canfd_get_lka_addr() : 0x12aU; if (msg->addr == steer_addr) { if (hyundai_canfd_angle_steering) { const int lkas_angle_active = (msg->data[9] >> 4U) & 0x3U; @@ -334,6 +335,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_ALT_LONG_TX_MSGS[] = { + HYUNDAI_CANFD_LKA_STEERING_ALT_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) + {0x51, 0, 32, .check_relay = false}, // ADRV_0x51 + {0x100, 0, 24, .check_relay = false}, // ACCELERATOR_BRAKE_ALT radar heartbeat spoof + {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) @@ -382,7 +398,11 @@ static safety_config hyundai_canfd_init(uint16_t param) { }; SET_RX_CHECKS(hyundai_canfd_lka_steering_long_rx_checks, ret); - SET_TX_MSGS(HYUNDAI_CANFD_LKA_STEERING_LONG_TX_MSGS, ret); + if (hyundai_canfd_lka_steering_alt) { + SET_TX_MSGS(HYUNDAI_CANFD_LKA_STEERING_ALT_LONG_TX_MSGS, ret); + } else { + SET_TX_MSGS(HYUNDAI_CANFD_LKA_STEERING_LONG_TX_MSGS, ret); + } } else { // Longitudinal checks for LFA steering diff --git a/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py b/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py index 77d367d0a..d45cb83e2 100755 --- a/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py +++ b/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py @@ -559,6 +559,61 @@ class TestHyundaiCanfdLKASteeringLongEV(HyundaiLongitudinalBase, TestHyundaiCanf return self.packer.make_can_msg_safety("SCC_CONTROL", 1, values) +class TestHyundaiCanfdLKASteeringAltAngleLongEV(HyundaiLongitudinalBase, TestHyundaiCanfdAngleSteering): + + TX_MSGS = [[0x110, 0], [0x1CF, 1], [0x362, 0], [0x51, 0], [0x100, 0], [0x730, 1], [0x12a, 1], [0x160, 1], + [0x1ba, 1], [0x1e0, 1], [0x1e5, 1], [0x31a, 1], [0x3b5, 1], [0x3c1, 1], + [0x1a0, 1], [0x1ea, 1], [0x200, 1], [0x345, 1], [0x1da, 1]] + + RELAY_MALFUNCTION_ADDRS = {0: (0x110, 0x362), 1: (0x1a0,)} # LKAS_ALT, CAM_0x362, SCC_CONTROL + FWD_BLACKLISTED_ADDRS = {0: MRR35_RADAR_TRACK_ADDRS, 2: [0x110, 0x362]} + + DISABLED_ECU_UDS_MSG = (0x730, 1) + DISABLED_ECU_ACTUATION_MSG = (0x1a0, 1) + + PT_BUS = 1 + SCC_BUS = 1 + BUTTONS_TX_BUS = 1 + STEER_MSG = "LKAS_ALT" + GAS_MSG = ("ACCELERATOR", "ACCELERATOR_PEDAL") + SAFETY_PARAM = HyundaiSafetyFlags.CANFD_LKA_STEERING | HyundaiSafetyFlags.CANFD_LKA_STEERING_ALT | \ + HyundaiSafetyFlags.CANFD_ANGLE_STEERING | HyundaiSafetyFlags.LONG | HyundaiSafetyFlags.EV_GAS + + def _angle_cmd_msg(self, angle, enabled, increment_timer=True, gain_raw=250): + if increment_timer: + self.safety.set_timer(self.angle_cmd_cnt * int(1e6 / self.LATERAL_FREQUENCY)) + self.angle_cmd_cnt += 1 + + values = { + "LKA_MODE": 0, + "LKA_AVAILABLE": 3 if enabled else 0, + "LKA_WARNING": 0, + "LKA_ICON": 2 if enabled else 1, + "FCA_SYSWARN": 0, + "TORQUE_REQUEST": 0, + "STEER_REQ": 0, + "LFA_BUTTON": 0, + "LKA_ASSIST": 0, + "DAMP_FACTOR": 100, + "LKAS_ANGLE_ACTIVE": 2 if enabled else 1, + "HAS_LANE_SAFETY": 0, + "ADAS_StrAnglReqVal": angle, + "ADAS_ACIAnglTqRedcGainVal": gain_raw * 0.004 if enabled or gain_raw != 250 else 0.0, + } + return self.packer.make_can_msg_safety("LKAS_ALT", 0, values) + + def _accel_msg(self, accel, aeb_req=False, aeb_decel=0): + values = { + "aReqRaw": accel, + "aReqValue": accel, + } + return self.packer.make_can_msg_safety("SCC_CONTROL", 1, values) + + def _tx_acc_state_msg(self, main_on): + values = {"MainMode_ACC": int(main_on), "ACCMode": 0} + return self.packer.make_can_msg_safety("SCC_CONTROL", 1, values) + + # Tests longitudinal for ICE, hybrid, EV cars with LFA steering class TestHyundaiCanfdLFASteeringLongBase(HyundaiLongitudinalBase, TestHyundaiCanfdLFASteeringBase): diff --git a/panda/SConscript b/panda/SConscript index 5b20be162..a607df245 100644 --- a/panda/SConscript +++ b/panda/SConscript @@ -169,6 +169,10 @@ build_project("panda", base_project_f4, "./board/main.c", []) build_project("panda_h7", base_project_h7, "./board/main.c", []) build_project("panda_remote", base_project_f4, "./board/main.c", ["-DPANDA_GM_REMOTE_START_C9"]) build_project("panda_h7_remote", base_project_h7, "./board/main.c", ["-DPANDA_GM_REMOTE_START_C9"]) +build_project("panda_can_ignition_only", base_project_f4, "./board/main.c", ["-DPANDA_IGNORE_IGNITION_LINE"]) +build_project("panda_h7_can_ignition_only", base_project_h7, "./board/main.c", ["-DPANDA_IGNORE_IGNITION_LINE"]) +build_project("panda_remote_can_ignition_only", base_project_f4, "./board/main.c", ["-DPANDA_GM_REMOTE_START_C9", "-DPANDA_IGNORE_IGNITION_LINE"]) +build_project("panda_h7_remote_can_ignition_only", base_project_h7, "./board/main.c", ["-DPANDA_GM_REMOTE_START_C9", "-DPANDA_IGNORE_IGNITION_LINE"]) # panda jungle fw flags = [ diff --git a/panda/board/main.c b/panda/board/main.c index a1bb0c508..e092d6e38 100644 --- a/panda/board/main.c +++ b/panda/board/main.c @@ -27,9 +27,21 @@ #include "board/obj/gitversion.h" #include "board/can_comms.h" + +static bool panda_ignition_line(void); + #include "board/main_comms.h" +static bool panda_ignition_line(void) { + #ifdef PANDA_IGNORE_IGNITION_LINE + return false; + #else + return harness_check_ignition(); + #endif +} + + // ********************* Serial debugging ********************* void debug_ring_callback(uart_ring *ring) { @@ -176,7 +188,7 @@ static void tick_handler(void) { const bool recent_heartbeat = heartbeat_counter == 0U; // tick drivers at 1Hz - bool started = harness_check_ignition() || ignition_can; + bool started = panda_ignition_line() || ignition_can; bootkick_tick(started, recent_heartbeat); // increase heartbeat counter and cap it at the uint32 limit diff --git a/panda/board/main_comms.h b/panda/board/main_comms.h index 4b362e495..7d528a3c0 100644 --- a/panda/board/main_comms.h +++ b/panda/board/main_comms.h @@ -12,7 +12,7 @@ static int get_health_pkt(void *dat) { health->voltage_pkt = current_board->read_voltage_mV(); health->current_pkt = current_board->read_current_mA(); - health->ignition_line_pkt = (uint8_t)(harness_check_ignition()); + health->ignition_line_pkt = (uint8_t)(panda_ignition_line()); health->ignition_can_pkt = ignition_can; health->controls_allowed_pkt = controls_allowed; diff --git a/panda/board/obj/panda_can_ignition_only.bin.signed b/panda/board/obj/panda_can_ignition_only.bin.signed new file mode 100644 index 000000000..9147aecf3 Binary files /dev/null and b/panda/board/obj/panda_can_ignition_only.bin.signed differ diff --git a/panda/board/obj/panda_h7_can_ignition_only.bin.signed b/panda/board/obj/panda_h7_can_ignition_only.bin.signed new file mode 100644 index 000000000..cbcc44b3a Binary files /dev/null and b/panda/board/obj/panda_h7_can_ignition_only.bin.signed differ diff --git a/panda/board/obj/panda_h7_remote_can_ignition_only.bin.signed b/panda/board/obj/panda_h7_remote_can_ignition_only.bin.signed new file mode 100644 index 000000000..786adb867 Binary files /dev/null and b/panda/board/obj/panda_h7_remote_can_ignition_only.bin.signed differ diff --git a/panda/board/obj/panda_remote_can_ignition_only.bin.signed b/panda/board/obj/panda_remote_can_ignition_only.bin.signed new file mode 100644 index 000000000..b6fa980c3 Binary files /dev/null and b/panda/board/obj/panda_remote_can_ignition_only.bin.signed differ diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 253890803..3b37dcb70 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -649,28 +649,28 @@ VOLT_PLEXY_FRICTION_SCALE_MULT_RIGHT = 1.05 VOLT_PLEXY_FRICTION_THRESHOLD_MULT_LEFT = 1.00 VOLT_PLEXY_FRICTION_THRESHOLD_MULT_RIGHT = 0.96 VOLT_PLEXY_CENTER_TAPER_REDUCTION_MULT = 0.94 -PRIUS_TRANSITION_SPEED = 10.0 +PRIUS_TRANSITION_SPEED = 8.5 PRIUS_PHASE_SCALE = 0.09 -PRIUS_FF_GAIN_LEFT = 0.12 -PRIUS_FF_GAIN_RIGHT = 0.13 -PRIUS_FF_ONSET = 0.12 -PRIUS_FF_ONSET_WIDTH = 0.07 -PRIUS_FF_CUTOFF = 1.45 -PRIUS_FF_CUTOFF_WIDTH = 0.36 +PRIUS_FF_GAIN_LEFT = 0.08 +PRIUS_FF_GAIN_RIGHT = 0.09 +PRIUS_FF_ONSET = 0.15 +PRIUS_FF_ONSET_WIDTH = 0.08 +PRIUS_FF_CUTOFF = 1.30 +PRIUS_FF_CUTOFF_WIDTH = 0.32 PRIUS_FRICTION_LAT_RISE = 0.18 PRIUS_FRICTION_JERK_RISE = 0.22 -PRIUS_TURN_IN_BOOST_LEFT = 0.60 -PRIUS_TURN_IN_BOOST_RIGHT = 0.56 -PRIUS_UNWIND_TAPER_LEFT = 0.58 -PRIUS_UNWIND_TAPER_RIGHT = 0.84 -PRIUS_TURN_IN_THRESHOLD_REDUCTION_LEFT = 0.22 -PRIUS_TURN_IN_THRESHOLD_REDUCTION_RIGHT = 0.22 -PRIUS_UNWIND_THRESHOLD_INCREASE_LEFT = 0.36 -PRIUS_UNWIND_THRESHOLD_INCREASE_RIGHT = 0.52 -PRIUS_TURN_IN_FRICTION_BOOST_LEFT = 0.10 -PRIUS_TURN_IN_FRICTION_BOOST_RIGHT = 0.10 -PRIUS_UNWIND_FRICTION_REDUCTION_LEFT = 0.22 -PRIUS_UNWIND_FRICTION_REDUCTION_RIGHT = 0.32 +PRIUS_TURN_IN_BOOST_LEFT = 0.28 +PRIUS_TURN_IN_BOOST_RIGHT = 0.25 +PRIUS_UNWIND_TAPER_LEFT = 0.22 +PRIUS_UNWIND_TAPER_RIGHT = 0.34 +PRIUS_TURN_IN_THRESHOLD_REDUCTION_LEFT = 0.16 +PRIUS_TURN_IN_THRESHOLD_REDUCTION_RIGHT = 0.16 +PRIUS_UNWIND_THRESHOLD_INCREASE_LEFT = 0.22 +PRIUS_UNWIND_THRESHOLD_INCREASE_RIGHT = 0.32 +PRIUS_TURN_IN_FRICTION_BOOST_LEFT = 0.07 +PRIUS_TURN_IN_FRICTION_BOOST_RIGHT = 0.07 +PRIUS_UNWIND_FRICTION_REDUCTION_LEFT = 0.12 +PRIUS_UNWIND_FRICTION_REDUCTION_RIGHT = 0.18 TRAILER_LOAD_FULL_ASSIST_KG = 15000.0 * CV.LB_TO_KG TRAILER_LATERAL_MIN_SPEED = 15.0 * CV.MPH_TO_MS diff --git a/selfdrive/pandad/panda.cc b/selfdrive/pandad/panda.cc index 676f05d16..4d9e4a6c3 100644 --- a/selfdrive/pandad/panda.cc +++ b/selfdrive/pandad/panda.cc @@ -137,7 +137,16 @@ std::optional Panda::get_serial() { bool Panda::up_to_date() { if (auto fw_sig = get_firmware_version()) { - for (auto fn : { "panda.bin.signed", "panda_h7.bin.signed", "panda_remote.bin.signed", "panda_h7_remote.bin.signed" }) { + for (auto fn : { + "panda.bin.signed", + "panda_h7.bin.signed", + "panda_remote.bin.signed", + "panda_h7_remote.bin.signed", + "panda_can_ignition_only.bin.signed", + "panda_h7_can_ignition_only.bin.signed", + "panda_remote_can_ignition_only.bin.signed", + "panda_h7_remote_can_ignition_only.bin.signed", + }) { auto content = util::read_file(std::string("../../panda/board/obj/") + fn); if (content.size() >= fw_sig->size() && memcmp(content.data() + content.size() - fw_sig->size(), fw_sig->data(), fw_sig->size()) == 0) { diff --git a/selfdrive/pandad/pandad.cc b/selfdrive/pandad/pandad.cc index bcb430747..d3f757ff6 100644 --- a/selfdrive/pandad/pandad.cc +++ b/selfdrive/pandad/pandad.cc @@ -217,7 +217,8 @@ void fill_panda_can_state(cereal::PandaState::PandaCanState::Builder &cs, const cs.setCanCoreResetCnt(can_health.can_core_reset_cnt); } -std::optional send_panda_states(PubMaster *pm, const std::vector &pandas, bool is_onroad, bool spoofing_started) { +std::optional send_panda_states(PubMaster *pm, const std::vector &pandas, bool is_onroad, + bool spoofing_started, bool ignore_ignition_line) { bool ignition_local = false; const uint32_t pandas_cnt = pandas.size(); @@ -265,6 +266,12 @@ std::optional send_panda_states(PubMaster *pm, const std::vector health.ignition_line_pkt = 0; } + // Work around harness-box false ignition by relying only on CAN ignition. + // This is only appropriate for vehicles with reliable panda CAN ignition support. + if (ignore_ignition_line) { + health.ignition_line_pkt = 0; + } + ignition_local |= ((health.ignition_line_pkt != 0) || (health.ignition_can_pkt != 0)); pandaStates.push_back(health); @@ -353,14 +360,15 @@ void send_peripheral_state(Panda *panda, PubMaster *pm) { pm->send("peripheralState", msg); } -void process_panda_state(std::vector &pandas, PubMaster *pm, bool engaged, bool is_onroad, bool spoofing_started) { +void process_panda_state(std::vector &pandas, PubMaster *pm, bool engaged, bool is_onroad, + bool spoofing_started, bool ignore_ignition_line) { std::vector connected_serials; for (Panda *p : pandas) { connected_serials.push_back(p->hw_serial()); } { - auto ignition_opt = send_panda_states(pm, pandas, is_onroad, spoofing_started); + auto ignition_opt = send_panda_states(pm, pandas, is_onroad, spoofing_started, ignore_ignition_line); if (!ignition_opt) { LOGE("Failed to get ignition_opt"); return; @@ -494,7 +502,8 @@ void pandad_run(std::vector &pandas) { sm["selfdriveState"].getSelfdriveState().getEnabled() || preap_aol_engaged ); is_onroad = params.getBool("IsOnroad"); - process_panda_state(pandas, &pm, engaged, is_onroad, spoofing_started); + const bool ignore_ignition_line = params.getBool("IgnoreIgnitionLine"); + process_panda_state(pandas, &pm, engaged, is_onroad, spoofing_started, ignore_ignition_line); panda_safety.configureSafetyMode(is_onroad); } diff --git a/selfdrive/pandad/pandad.py b/selfdrive/pandad/pandad.py index b95eb9ed2..79d1fa95c 100755 --- a/selfdrive/pandad/pandad.py +++ b/selfdrive/pandad/pandad.py @@ -13,20 +13,33 @@ from openpilot.system.hardware import HARDWARE from openpilot.common.swaglog import cloudlog -def get_expected_firmware_path(panda: Panda, remote_start: bool) -> str: - app_fn = panda.get_mcu_type().config.app_fn +def get_selected_firmware_name(app_fn: str, remote_start: bool, ignore_ignition_line: bool) -> str: + if not remote_start and not ignore_ignition_line: + return app_fn + + h7 = app_fn == "panda_h7.bin.signed" + name_parts = ["panda_h7" if h7 else "panda"] if remote_start: - remote_fn = "panda_h7_remote.bin.signed" if app_fn == "panda_h7.bin.signed" else "panda_remote.bin.signed" - remote_path = os.path.join(FW_PATH, remote_fn) - if os.path.isfile(remote_path): - return remote_path - cloudlog.warning(f"Remote-start panda firmware not found: {remote_path}, falling back to default") + name_parts.append("remote") + if ignore_ignition_line: + name_parts.append("can_ignition_only") + return "_".join(name_parts) + ".bin.signed" + + +def get_expected_firmware_path(panda: Panda, remote_start: bool, ignore_ignition_line: bool) -> str: + app_fn = panda.get_mcu_type().config.app_fn + selected_fn = get_selected_firmware_name(app_fn, remote_start, ignore_ignition_line) + if selected_fn != app_fn: + selected_path = os.path.join(FW_PATH, selected_fn) + if os.path.isfile(selected_path): + return selected_path + cloudlog.warning(f"Selected panda firmware not found: {selected_path}, falling back to default") return os.path.join(FW_PATH, app_fn) -def get_expected_signature(panda: Panda, remote_start: bool) -> bytes: +def get_expected_signature(panda: Panda, remote_start: bool, ignore_ignition_line: bool) -> bytes: try: - fn = get_expected_firmware_path(panda, remote_start) + fn = get_expected_firmware_path(panda, remote_start, ignore_ignition_line) return Panda.get_signature_from_firmware(fn) except Exception: cloudlog.exception("Error computing expected signature") @@ -40,7 +53,14 @@ def get_remote_start_boots_comma(params: Params) -> bool: return False -def flash_panda(panda_serial: str, remote_start: bool) -> Panda: +def get_ignore_ignition_line(params: Params) -> bool: + try: + return params.get_bool("IgnoreIgnitionLine") + except UnknownKeyName: + return False + + +def flash_panda(panda_serial: str, remote_start: bool, ignore_ignition_line: bool) -> Panda: try: panda = Panda(panda_serial) except PandaProtocolMismatch: @@ -48,8 +68,8 @@ def flash_panda(panda_serial: str, remote_start: bool) -> Panda: HARDWARE.recover_internal_panda() raise - fw_path = get_expected_firmware_path(panda, remote_start) - fw_signature = get_expected_signature(panda, remote_start) + fw_path = get_expected_firmware_path(panda, remote_start, ignore_ignition_line) + fw_signature = get_expected_signature(panda, remote_start, ignore_ignition_line) internal_panda = panda.is_internal() panda_version = "bootstub" if panda.bootstub else panda.get_version() @@ -133,8 +153,9 @@ def main() -> None: # Flash pandas pandas: list[Panda] = [] remote_start = get_remote_start_boots_comma(params) + ignore_ignition_line = get_ignore_ignition_line(params) for serial in panda_serials: - pandas.append(flash_panda(serial, remote_start)) + pandas.append(flash_panda(serial, remote_start, ignore_ignition_line)) # Ensure internal panda is present if expected internal_pandas = [panda for panda in pandas if panda.is_internal()] @@ -186,7 +207,7 @@ def main() -> None: first_run = False # run pandad with all connected serials as arguments - if get_remote_start_boots_comma(params): + if get_remote_start_boots_comma(params) or get_ignore_ignition_line(params): os.environ["BOARDD_SKIP_FW_CHECK"] = "1" else: os.environ.pop("BOARDD_SKIP_FW_CHECK", None) diff --git a/selfdrive/ui/layouts/settings/starpilot/vehicle.py b/selfdrive/ui/layouts/settings/starpilot/vehicle.py index 4fafb02cf..983632e1d 100644 --- a/selfdrive/ui/layouts/settings/starpilot/vehicle.py +++ b/selfdrive/ui/layouts/settings/starpilot/vehicle.py @@ -1,12 +1,15 @@ from __future__ import annotations +import threading +import time + import pyray as rl from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.system.ui.widgets import DialogResult, Widget -from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog +from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dialog from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog @@ -143,10 +146,16 @@ class VehicleSettingsManagerView(PanelManagerView): "set_state": lambda s: self._controller._on_toggle("GMDashSpoofOffsets"), }) if cs.isGM and cs.hasOpenpilotLongitudinal: + toggles.append({ + "title": tr("CAN Ignition Only"), + "subtitle": tr("Use Panda firmware that ignores the physical ignition line and starts only from CAN ignition."), + "get_state": lambda: self._controller._params.get_bool("IgnoreIgnitionLine"), + "set_state": lambda s: self._controller._on_panda_firmware_toggle("IgnoreIgnitionLine", tr("CAN Ignition Only requires a Panda firmware update.")), + }) toggles.append({ "title": tr("Remote Start Panda"), "get_state": lambda: self._controller._params.get_bool("RemoteStartBootsComma"), - "set_state": lambda s: self._controller._on_toggle("RemoteStartBootsComma"), + "set_state": lambda s: self._controller._on_panda_firmware_toggle("RemoteStartBootsComma", tr("Remote Start requires a Panda firmware update.")), }) if cs.isGM and cs.isVolt and not cs.hasSNG: toggles.append({ @@ -559,6 +568,29 @@ class StarPilotVehicleSettingsLayout(_SettingsPage): if param_key == "ForceFingerprint": self._manager_view._rebuild_toggle_grid() + def _on_panda_firmware_toggle(self, param_key: str, prompt: str): + current = self._params.get_bool(param_key) if self._params.get(param_key) is not None else False + new_state = not current + + def flash_and_reboot(): + self._params_memory.put_bool("FlashPanda", True) + while self._params_memory.get_bool("FlashPanda"): + time.sleep(0.1) + HARDWARE.reboot() + + def on_confirm(res): + if res != DialogResult.CONFIRM: + starpilot_state.update(force=True) + self._manager_view._rebuild_toggle_grid() + return + self._params.put_bool(param_key, new_state) + threading.Thread(target=flash_and_reboot, daemon=True).start() + starpilot_state.update(force=True) + self._manager_view._rebuild_toggle_grid() + gui_app.push_widget(alert_dialog(tr("Panda flashing started. Device will reboot when finished."))) + + gui_app.push_widget(ConfirmDialog(prompt, tr("Flash"), callback=on_confirm)) + def _on_select(self, key: str): if key in ("CarMake", "CarModel") and not self._params.get_bool("ForceFingerprint"): return diff --git a/starpilot/common/starpilot_utilities.py b/starpilot/common/starpilot_utilities.py index bee39ce79..9271837f7 100644 --- a/starpilot/common/starpilot_utilities.py +++ b/starpilot/common/starpilot_utilities.py @@ -173,26 +173,43 @@ def extract_zip(zip_file, extract_path): print(f"Extraction completed!") +def get_selected_panda_firmware_name(app_fn, remote_start, ignore_ignition_line): + if not remote_start and not ignore_ignition_line: + return app_fn + + h7 = app_fn == "panda_h7.bin.signed" + name_parts = ["panda_h7" if h7 else "panda"] + if remote_start: + name_parts.append("remote") + if ignore_ignition_line: + name_parts.append("can_ignition_only") + return "_".join(name_parts) + ".bin.signed" + + def flash_panda(params_memory): params = Params() try: remote_start = params.get_bool("RemoteStartBootsComma") except Exception: remote_start = False + try: + ignore_ignition_line = params.get_bool("IgnoreIgnitionLine") + except Exception: + ignore_ignition_line = False for serial in Panda.list(): try: with Panda(serial=serial) as panda: print(f"Flashing Panda {serial}") flash_fn = None - if remote_start: - app_fn = panda.get_mcu_type().config.app_fn - remote_fn = "panda_h7_remote.bin.signed" if app_fn == "panda_h7.bin.signed" else "panda_remote.bin.signed" - candidate = os.path.join(FW_PATH, remote_fn) + app_fn = panda.get_mcu_type().config.app_fn + selected_fn = get_selected_panda_firmware_name(app_fn, remote_start, ignore_ignition_line) + if selected_fn != app_fn: + candidate = os.path.join(FW_PATH, selected_fn) if os.path.isfile(candidate): flash_fn = candidate else: - print(f"Remote-start panda firmware missing: {candidate}. Falling back to default firmware.") + print(f"Selected panda firmware missing: {candidate}. Falling back to default firmware.") panda.flash(fn=flash_fn) except Exception as exception: print(f"Failed to flash Panda {serial}: {exception}") diff --git a/starpilot/common/starpilot_variables.py b/starpilot/common/starpilot_variables.py index adfcb0ddf..1d3845eef 100644 --- a/starpilot/common/starpilot_variables.py +++ b/starpilot/common/starpilot_variables.py @@ -1361,6 +1361,7 @@ class StarPilotVariables: "GMDashSpoofOffsets", condition=toggle.car_make == "gm" and toggle.has_pedal, ) + toggle.ignore_ignition_line = self.get_value("IgnoreIgnitionLine", condition=toggle.car_make == "gm") toggle.long_pitch = self.get_value( "LongPitch", condition=toggle.openpilot_longitudinal and toggle.car_make == "gm", diff --git a/starpilot/system/the_galaxy/assets/components/tools/device_settings_layout.json b/starpilot/system/the_galaxy/assets/components/tools/device_settings_layout.json index 1d8ae1752..7b2eed841 100644 --- a/starpilot/system/the_galaxy/assets/components/tools/device_settings_layout.json +++ b/starpilot/system/the_galaxy/assets/components/tools/device_settings_layout.json @@ -2480,6 +2480,13 @@ "data_type": "bool", "ui_type": "toggle" }, + { + "key": "IgnoreIgnitionLine", + "label": "Use CAN Ignition Only", + "description": "Use Panda firmware that ignores the physical ignition line and starts from CAN ignition only.\n\nRequires a Panda flash. Use this only on vehicles with reliable CAN ignition when the harness box reports false ignition.", + "data_type": "bool", + "ui_type": "toggle" + }, { "key": "LongPitch", "label": "Smooth Pedal Response on Hills", diff --git a/starpilot/ui/qt/offroad/vehicle_settings.cc b/starpilot/ui/qt/offroad/vehicle_settings.cc index 29e2a7bfc..51e57590f 100644 --- a/starpilot/ui/qt/offroad/vehicle_settings.cc +++ b/starpilot/ui/qt/offroad/vehicle_settings.cc @@ -173,6 +173,7 @@ StarPilotVehiclesPanel::StarPilotVehiclesPanel(StarPilotSettingsWindow *parent, {"GMToggles", tr("General Motors Settings"), tr("StarPilot features for General Motors vehicles."), ""}, {"GMPedalLongitudinal", tr("Use Pedal For Longitudinal"), tr("Use the pedal interceptor for full longitudinal control on supported GM vehicles."), ""}, {"GMDashSpoofOffsets", tr("Apply Offsets To Dash Spoof"), tr("On GM pedal-long cars, add the configured set-speed offset to the spoofed dash set speed so it matches the on-screen set speed."), ""}, + {"IgnoreIgnitionLine", tr("Use CAN Ignition Only"), tr("Use CAN ignition only and ignore the physical ignition line in Panda firmware.

Requires a Panda flash. Use this only on vehicles with reliable CAN ignition when the harness box reports false ignition."), ""}, {"LongPitch", tr("Smooth Pedal Response on Hills"), tr("Smoothen acceleration and braking when driving downhill/uphill."), ""}, {"RemoteStartBootsComma", tr("Remote Start Boots comma"), tr("Use the remote-start GM panda firmware at boot.

Required for GM remote-start startup signal behavior."), ""}, {"RemapCancelToDistance", tr("Remap Cancel Button"), tr("On pedal-interceptor Bolts, treat the steering-wheel CANCEL button as an extra mappable button."), ""}, @@ -300,24 +301,28 @@ StarPilotVehiclesPanel::StarPilotVehiclesPanel(StarPilotSettingsWindow *parent, }); } - ParamControl *remoteStartToggle = static_cast(toggles["RemoteStartBootsComma"]); - QObject::connect(remoteStartToggle, &ToggleControl::toggleFlipped, [parent, remoteStartToggle, this](bool state) { - const QString prompt = tr("Remote Start requires a Panda firmware update. Flash the Panda now?"); - if (!StarPilotConfirmationDialog::yesorno(prompt, this)) { - params.putBool("RemoteStartBootsComma", !state); - remoteStartToggle->refresh(); - return; - } - - std::thread([parent, this]() { - parent->keepScreenOn = true; - params_memory.putBool("FlashPanda", true); - while (params_memory.getBool("FlashPanda")) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + auto connectPandaFlashToggle = [parent, this](const QString &key, const QString &prompt) { + ParamControl *toggle = static_cast(toggles[key]); + QObject::connect(toggle, &ToggleControl::toggleFlipped, [parent, key, prompt, toggle, this](bool state) { + if (!StarPilotConfirmationDialog::yesorno(prompt, this)) { + params.putBool(key.toStdString(), !state); + toggle->refresh(); + return; } - Hardware::reboot(); - }).detach(); - }); + + std::thread([parent, this]() { + parent->keepScreenOn = true; + params_memory.putBool("FlashPanda", true); + while (params_memory.getBool("FlashPanda")) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + Hardware::reboot(); + }).detach(); + }); + }; + + connectPandaFlashToggle("IgnoreIgnitionLine", tr("CAN Ignition Only requires a Panda firmware update. Flash the Panda now?")); + connectPandaFlashToggle("RemoteStartBootsComma", tr("Remote Start requires a Panda firmware update. Flash the Panda now?")); openDescriptions(forceOpenDescriptions, toggles); diff --git a/starpilot/ui/qt/offroad/vehicle_settings.h b/starpilot/ui/qt/offroad/vehicle_settings.h index a9ca211d9..ef240e9b2 100644 --- a/starpilot/ui/qt/offroad/vehicle_settings.h +++ b/starpilot/ui/qt/offroad/vehicle_settings.h @@ -23,7 +23,7 @@ private: std::map toggles; - QSet gmKeys = {"GMPedalLongitudinal", "GMDashSpoofOffsets", "LongPitch", "RemoteStartBootsComma", "RemapCancelToDistance", "VoltSNG"}; + QSet gmKeys = {"GMPedalLongitudinal", "GMDashSpoofOffsets", "IgnoreIgnitionLine", "LongPitch", "RemoteStartBootsComma", "RemapCancelToDistance", "VoltSNG"}; QSet longitudinalKeys = {"FrogsGoMoosTweak", "GMDashSpoofOffsets", "LongPitch", "RemapCancelToDistance", "SNGHack", "VoltSNG"}; QSet subaruKeys = {"SubaruSNG", "SubaruSNGManualParkingBrake"}; QSet toyotaKeys = {"ClusterOffset", "FrogsGoMoosTweak", "LockDoorsTimer", "SNGHack", "ToyotaDoors"}; diff --git a/tools/StarPilot/feasibleparams.txt b/tools/StarPilot/feasibleparams.txt index 340381d69..da0a161bd 100644 --- a/tools/StarPilot/feasibleparams.txt +++ b/tools/StarPilot/feasibleparams.txt @@ -114,6 +114,7 @@ ForceTorqueController FrogsGoMoosTweak GMDashSpoofOffsets GMPedalLongitudinal +IgnoreIgnitionLine GoatScream GoatScreamCriticalAlerts GreenLightAlert