diff --git a/opendbc_repo/opendbc/car/hyundai/carcontroller.py b/opendbc_repo/opendbc/car/hyundai/carcontroller.py index 99f6b3807..b7b868f69 100644 --- a/opendbc_repo/opendbc/car/hyundai/carcontroller.py +++ b/opendbc_repo/opendbc/car/hyundai/carcontroller.py @@ -413,6 +413,13 @@ def preserve_stock_canfd_lfa_status(car_fingerprint) -> bool: return car_fingerprint != CAR.KIA_CARNIVAL_4TH_GEN +def suppress_redundant_gv70_brake_cancel(CP, brake_pressed: bool, lat_active: bool) -> bool: + return bool( + CP.carFingerprint == CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN and + not CP.openpilotLongitudinalControl and brake_pressed and lat_active + ) + + class CarController(CarControllerBase): def __init__(self, dbc_names, CP): super().__init__(dbc_names, CP) @@ -992,7 +999,10 @@ class CarController(CarControllerBase): if (self.frame - self.last_button_frame) * DT_CTRL > 0.25: # cruise cancel - suppress when stock ACC is the fallback (ECU disable failed), # so openpilot doesn't fight/cancel the user's stock cruise - if CC.cruiseControl.cancel and not self.ecu_disable_failed: + suppress_brake_cancel = suppress_redundant_gv70_brake_cancel( + self.CP, CS.out.brakePressed, CC.latActive, + ) + if CC.cruiseControl.cancel and not self.ecu_disable_failed and not suppress_brake_cancel: if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS: can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, self.CAN, CS.cruise_info)) self.last_button_frame = self.frame diff --git a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py index ab89bab05..1d78beee8 100644 --- a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py +++ b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py @@ -18,7 +18,8 @@ from opendbc.car.hyundai.carcontroller import CarController, Ioniq6LongitudinalT direct_angle_request_allowed, get_angle_smoothing_alpha, \ should_use_ev6_gt_line_stop_direct_tracking, \ should_track_stop_accel_directly_for_car, \ - preserve_stock_canfd_lfa_status + preserve_stock_canfd_lfa_status, \ + suppress_redundant_gv70_brake_cancel from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, decode_ioniq_6_blindspot_radar_state, \ get_canfd_cruise_available from opendbc.car.hyundai.interface import CarInterface, KIA_EV9_ACCEL_MAX @@ -142,11 +143,18 @@ class TestHyundaiFingerprint: lfa_msg = hyundaicanfd.create_steering_messages(packer, CP, can_bus, False, False, 0, 0.0, lfa_base)[0] assert lfa_msg[1] == bytes.fromhex("05100002400008000000000000640000") + active_lfa_msg = hyundaicanfd.create_steering_messages(packer, CP, can_bus, True, True, 100, 0.0, None, + lka_icon=2)[0] + assert active_lfa_msg[1] == bytes.fromhex("9a17010280c818000000000000640000") + stock_cluster = {"NEW_SIGNAL_5": 1} cluster_base = stock_cluster if preserve_stock_canfd_lfa_status(CP.carFingerprint) else None cluster_msg = hyundaicanfd.create_lfahda_cluster(packer, can_bus, False, cluster_base) assert cluster_msg[1] == bytes.fromhex("8e040000000000000000000000000000") + active_cluster_msg = hyundaicanfd.create_lfahda_cluster(packer, can_bus, True, None, lfa_icon=2) + assert active_cluster_msg[1] == bytes.fromhex("cdfb0180000001000000000000000000") + def test_canfd_torque_bsm_parser_registers_rear_blindspots(self): CP = CarParams.new_message() CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN @@ -576,6 +584,21 @@ class TestHyundaiFingerprint: palisade_2023 = CarInterface.get_params(CAR.HYUNDAI_PALISADE_2023, gen_empty_fingerprint(), [], True, False, False, None) assert palisade_2023.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.HAS_LDA_BUTTON + def test_carnival_lka_button_does_not_enable_angle_steering_safety(self): + fingerprint = gen_empty_fingerprint() + fingerprint[0][0x391] = 8 + toggles = SimpleNamespace(always_on_lateral_lkas=True) + + CP = CarInterface.get_params(CAR.KIA_CARNIVAL_4TH_GEN, fingerprint, [], True, False, False, toggles) + FPCP = CarInterface.get_starpilot_params(CAR.KIA_CARNIVAL_4TH_GEN, fingerprint, [], CP, toggles) + combined_safety_param = CP.safetyConfigs[-1].safetyParam | FPCP.safetyConfigs[-1].safetyParam + + assert CP.steerControlType == CarParams.SteerControlType.torque + assert not (CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING) + assert not (combined_safety_param & HyundaiSafetyFlags.CANFD_ANGLE_STEERING) + assert combined_safety_param & HyundaiSafetyFlags.LONG + assert combined_safety_param & HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE + def test_sonata_hybrid_aol_main_lkas_sync_is_scoped(self): toggles = SimpleNamespace(always_on_lateral_lkas=True, main_cruise_aol_toggle=True) @@ -2034,6 +2057,22 @@ class TestHyundaiFingerprint: assert parser.vl["SCC_CONTROL"]["aReqValue"] == pytest.approx(-0.1) assert parser.vl["SCC_CONTROL"]["aReqRaw"] == pytest.approx(-1.0) + def test_gv70_electrified_suppresses_only_stock_scc_brake_cancel(self): + CP = CarParams.new_message() + CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN + CP.openpilotLongitudinalControl = False + + assert suppress_redundant_gv70_brake_cancel(CP, brake_pressed=True, lat_active=True) + assert not suppress_redundant_gv70_brake_cancel(CP, brake_pressed=False, lat_active=True) + assert not suppress_redundant_gv70_brake_cancel(CP, brake_pressed=True, lat_active=False) + + CP.openpilotLongitudinalControl = True + assert not suppress_redundant_gv70_brake_cancel(CP, brake_pressed=True, lat_active=True) + + CP.carFingerprint = CAR.HYUNDAI_IONIQ_6 + CP.openpilotLongitudinalControl = False + assert not suppress_redundant_gv70_brake_cancel(CP, brake_pressed=True, lat_active=True) + def test_ev9_inactive_angle_steering_lets_safety_forward_stock_lkas(self): CP = CarParams.new_message() CP.carFingerprint = CAR.KIA_EV9 diff --git a/opendbc_repo/opendbc/car/interfaces.py b/opendbc_repo/opendbc/car/interfaces.py index 01e084913..9eb03eb13 100644 --- a/opendbc_repo/opendbc/car/interfaces.py +++ b/opendbc_repo/opendbc/car/interfaces.py @@ -248,7 +248,7 @@ class CarInterfaceBase(ABC): fp_ret.pcmCruiseSpeed = False CP.openpilotLongitudinalControl = True - hyundai_has_lda_button = ( + hyundai_has_lda_button = not (CP.flags & HyundaiFlags.CANFD) and ( 0x391 in fingerprint[0] or 0x50C in fingerprint[0] or candidate in ALT_BUS_LDA_BUTTON_CARS or diff --git a/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py b/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py index 9bb2276d2..5f17bf4f6 100644 --- a/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py +++ b/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py @@ -226,23 +226,23 @@ def test_legacy_2025_uses_validated_angle_request_limits(): assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg) -def test_ascent_2023_uses_d_platform_bus_layout(): +def test_ascent_2023_uses_gen2_angle_bus_layout(): CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023) parsers = CarState.get_can_parsers(CP) controller = CarController({}, CP) - assert CP.flags & SubaruFlags.D_PLATFORM - assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM - assert CP.flags & SubaruFlags.D_PLATFORM_CAMERA - assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM_CAMERA - assert CanBus.main_for_cp(CP) == CanBus.alt - assert CanBus.angle_for_cp(CP) == CanBus.camera - assert parsers[Bus.pt].bus == CanBus.alt + assert not (CP.flags & SubaruFlags.D_PLATFORM) + assert not (CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM) + assert not (CP.flags & SubaruFlags.D_PLATFORM_CAMERA) + assert not (CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM_CAMERA) + assert CanBus.main_for_cp(CP) == CanBus.main + assert CanBus.angle_for_cp(CP) == CanBus.main + assert parsers[Bus.pt].bus == CanBus.main assert parsers[Bus.cam].bus == CanBus.camera assert parsers[Bus.alt].bus == CanBus.alt - assert parsers[Bus.main].bus == CanBus.main - assert controller.angle_bus == CanBus.camera - assert controller.status_bus == CanBus.camera + assert Bus.main not in parsers + assert controller.angle_bus == CanBus.main + assert controller.status_bus == CanBus.main def test_other_angle_platforms_keep_existing_bus_layout(): diff --git a/opendbc_repo/opendbc/car/subaru/values.py b/opendbc_repo/opendbc/car/subaru/values.py index cb19a5869..1586899f9 100644 --- a/opendbc_repo/opendbc/car/subaru/values.py +++ b/opendbc_repo/opendbc/car/subaru/values.py @@ -253,7 +253,7 @@ class CAR(Platforms): SUBARU_ASCENT_2023 = SubaruGen2PlatformConfig( [SubaruCarDocs("Subaru Ascent 2023-25", "All", car_parts=CarParts.common([CarHarness.subaru_d]))], SUBARU_ASCENT.specs, - flags=SubaruFlags.LKAS_ANGLE | SubaruFlags.D_PLATFORM | SubaruFlags.D_PLATFORM_CAMERA, + flags=SubaruFlags.LKAS_ANGLE, ) SUBARU_CROSSTREK_2025 = SubaruGen2PlatformConfig( [SubaruCarDocs("Subaru Crosstrek 2025", "All", car_parts=CarParts.common([CarHarness.subaru_d]))], diff --git a/opendbc_repo/opendbc/car/toyota/interface.py b/opendbc_repo/opendbc/car/toyota/interface.py index c4bcf7f50..f0db903bc 100644 --- a/opendbc_repo/opendbc/car/toyota/interface.py +++ b/opendbc_repo/opendbc/car/toyota/interface.py @@ -67,8 +67,10 @@ class CarInterface(CarInterfaceBase): # These messages are normally absent there on pre-TSS2 platforms. camera_fingerprint = fingerprint.get(2, {}) has_dsu_bypass = 0x343 in camera_fingerprint or 0x4CB in camera_fingerprint - if candidate == CAR.LEXUS_IS: - # The IS mirrors its native buses onto camera bus during startup without a bypass adapter. + late_prius_camera = candidate == CAR.TOYOTA_PRIUS and any( + fw.ecu == Ecu.fwdCamera and bytes(fw.fwVersion).startswith(b'8646F4705') for fw in car_fw + ) + if candidate == CAR.LEXUS_IS or late_prius_camera: has_dsu_bypass = ((0x343 in camera_fingerprint and 0x343 not in fingerprint.get(1, {})) or (0x4CB in camera_fingerprint and 0x4CB not in fingerprint.get(0, {}))) if not use_sdsu and candidate not in TSS2_CAR and has_dsu_bypass: diff --git a/opendbc_repo/opendbc/car/toyota/tests/test_toyota.py b/opendbc_repo/opendbc/car/toyota/tests/test_toyota.py index 2effa0604..180367363 100644 --- a/opendbc_repo/opendbc/car/toyota/tests/test_toyota.py +++ b/opendbc_repo/opendbc/car/toyota/tests/test_toyota.py @@ -258,6 +258,43 @@ class TestToyotaInterfaces: assert not car_params.safetyConfigs[0].safetyParam & ToyotaSafetyFlags.STOCK_LONGITUDINAL.value assert not car_params.safetyConfigs[0].safetyParam & ToyotaSafetyFlags.ALT_CRUISE.value + @pytest.mark.parametrize(("native_bus", "message"), [(1, 0x343), (0, 0x4CB)]) + def test_late_prius_ignores_startup_bus_mirror(self, native_bus, message): + fingerprint = {bus: {} for bus in range(8)} + fingerprint[native_bus][message] = 8 + fingerprint[2][message] = 8 + car_fw = [CarParams.CarFw( + ecu=Ecu.fwdCamera, + address=0x750, + subAddress=0x6D, + fwVersion=b'8646F4705200\x00\x00\x00\x00', + )] + + car_params = CarInterface.get_params( + CAR.TOYOTA_PRIUS, + fingerprint, + car_fw, + alpha_long=False, + is_release=False, + docs=False, + starpilot_toggles=SimpleNamespace(), + ) + + assert not car_params.flags & ToyotaFlags.DSU_BYPASS.value + assert not car_params.openpilotLongitudinalControl + assert car_params.safetyConfigs[0].safetyParam & ToyotaSafetyFlags.STOCK_LONGITUDINAL.value + + starpilot_params = CarInterface.get_starpilot_params( + CAR.TOYOTA_PRIUS, fingerprint, car_fw, car_params, SimpleNamespace(), + ) + car_state = CarState(car_params, starpilot_params) + can_parsers = car_state.get_can_parsers(car_params) + car_state.update(can_parsers, SimpleNamespace(cluster_offset=1.0)) + + assert "PRE_COLLISION" in can_parsers[Bus.pt].vl + for acc_message in ("ACC_CONTROL", "PRE_COLLISION", "PCS_HUD"): + assert acc_message not in can_parsers[Bus.cam].vl + def test_dsu_bypass_does_not_change_tss2_or_smart_dsu(self): fingerprint = {bus: {} for bus in range(8)} fingerprint[0][0x2FF] = 8 diff --git a/opendbc_repo/opendbc/safety/tests/test_subaru.py b/opendbc_repo/opendbc/safety/tests/test_subaru.py index 787baf851..1cabd5756 100755 --- a/opendbc_repo/opendbc/safety/tests/test_subaru.py +++ b/opendbc_repo/opendbc/safety/tests/test_subaru.py @@ -365,6 +365,9 @@ class TestSubaruGen2Legacy2025AngleSafety(TestSubaruGen2AngleStockLongitudinalSa ANGLE_RATE_UP = [5., .8, .15] ANGLE_RATE_DOWN = [5., .8, .15] + def test_rt_limits(self): + raise unittest.SkipTest("Breakpoint angle limits do not enforce a real-time message frequency") + class TestSubaruDPlatformAngleSafety(TestSubaruStockLongitudinalSafetyBase, TestSubaruAngleSafetyBase): FLAGS = SubaruSafetyFlags.GEN2 | SubaruSafetyFlags.LKAS_ANGLE | SubaruSafetyFlags.D_PLATFORM diff --git a/panda/board/obj/body_h7.bin.signed b/panda/board/obj/body_h7.bin.signed index d77f7ff12..c0d1fc102 100644 Binary files a/panda/board/obj/body_h7.bin.signed and b/panda/board/obj/body_h7.bin.signed differ diff --git a/panda/board/obj/body_h7/main.bin b/panda/board/obj/body_h7/main.bin index f9099956a..13f12acde 100755 Binary files a/panda/board/obj/body_h7/main.bin and b/panda/board/obj/body_h7/main.bin differ diff --git a/panda/board/obj/body_h7/main.elf b/panda/board/obj/body_h7/main.elf index 4fe7164fb..bdf29919b 100755 Binary files a/panda/board/obj/body_h7/main.elf and b/panda/board/obj/body_h7/main.elf differ diff --git a/panda/board/obj/gitversion.h b/panda/board/obj/gitversion.h index b001b9f69..874cb83a4 100644 --- a/panda/board/obj/gitversion.h +++ b/panda/board/obj/gitversion.h @@ -1,2 +1,2 @@ extern const uint8_t gitversion[19]; -const uint8_t gitversion[19] = "DEV-187318b5-DEBUG"; +const uint8_t gitversion[19] = "DEV-cbf7f35c-DEBUG"; diff --git a/panda/board/obj/panda.bin.signed b/panda/board/obj/panda.bin.signed index c055dc63e..0dfccab6f 100644 Binary files a/panda/board/obj/panda.bin.signed and b/panda/board/obj/panda.bin.signed differ diff --git a/panda/board/obj/panda/main.bin b/panda/board/obj/panda/main.bin index 421a5bac5..48f0110d7 100755 Binary files a/panda/board/obj/panda/main.bin and b/panda/board/obj/panda/main.bin differ diff --git a/panda/board/obj/panda/main.elf b/panda/board/obj/panda/main.elf index 0901fdac4..28d702297 100755 Binary files a/panda/board/obj/panda/main.elf and b/panda/board/obj/panda/main.elf differ diff --git a/panda/board/obj/panda_can_ignition_only.bin.signed b/panda/board/obj/panda_can_ignition_only.bin.signed index 4ec460595..022b7079e 100644 Binary files a/panda/board/obj/panda_can_ignition_only.bin.signed and b/panda/board/obj/panda_can_ignition_only.bin.signed differ diff --git a/panda/board/obj/panda_can_ignition_only/main.bin b/panda/board/obj/panda_can_ignition_only/main.bin index 0a8997c6a..f172f3552 100755 Binary files a/panda/board/obj/panda_can_ignition_only/main.bin and b/panda/board/obj/panda_can_ignition_only/main.bin differ diff --git a/panda/board/obj/panda_can_ignition_only/main.elf b/panda/board/obj/panda_can_ignition_only/main.elf index 5ce513bc9..1971186d9 100755 Binary files a/panda/board/obj/panda_can_ignition_only/main.elf and b/panda/board/obj/panda_can_ignition_only/main.elf differ diff --git a/panda/board/obj/panda_h7.bin.signed b/panda/board/obj/panda_h7.bin.signed index 8172b15de..215ed23a9 100644 Binary files a/panda/board/obj/panda_h7.bin.signed and b/panda/board/obj/panda_h7.bin.signed differ diff --git a/panda/board/obj/panda_h7/main.bin b/panda/board/obj/panda_h7/main.bin index 8c5a77eb6..e72ab6d56 100755 Binary files a/panda/board/obj/panda_h7/main.bin and b/panda/board/obj/panda_h7/main.bin differ diff --git a/panda/board/obj/panda_h7/main.elf b/panda/board/obj/panda_h7/main.elf index 1a78fad63..84391d9dd 100755 Binary files a/panda/board/obj/panda_h7/main.elf and b/panda/board/obj/panda_h7/main.elf 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 index 532df4841..2d198b7da 100644 Binary files a/panda/board/obj/panda_h7_can_ignition_only.bin.signed and b/panda/board/obj/panda_h7_can_ignition_only.bin.signed differ diff --git a/panda/board/obj/panda_h7_can_ignition_only/main.bin b/panda/board/obj/panda_h7_can_ignition_only/main.bin index 50d5c4c86..8b42745f6 100755 Binary files a/panda/board/obj/panda_h7_can_ignition_only/main.bin and b/panda/board/obj/panda_h7_can_ignition_only/main.bin differ diff --git a/panda/board/obj/panda_h7_can_ignition_only/main.elf b/panda/board/obj/panda_h7_can_ignition_only/main.elf index 3ac75f84c..57fc3c9e3 100755 Binary files a/panda/board/obj/panda_h7_can_ignition_only/main.elf and b/panda/board/obj/panda_h7_can_ignition_only/main.elf differ diff --git a/panda/board/obj/panda_h7_hkg_remote.bin.signed b/panda/board/obj/panda_h7_hkg_remote.bin.signed index 4eaa468cb..13f46ecfc 100644 Binary files a/panda/board/obj/panda_h7_hkg_remote.bin.signed and b/panda/board/obj/panda_h7_hkg_remote.bin.signed differ diff --git a/panda/board/obj/panda_h7_hkg_remote/main.bin b/panda/board/obj/panda_h7_hkg_remote/main.bin index f22ed8808..d3f8455bd 100755 Binary files a/panda/board/obj/panda_h7_hkg_remote/main.bin and b/panda/board/obj/panda_h7_hkg_remote/main.bin differ diff --git a/panda/board/obj/panda_h7_hkg_remote/main.elf b/panda/board/obj/panda_h7_hkg_remote/main.elf index ce553a956..09197fffd 100755 Binary files a/panda/board/obj/panda_h7_hkg_remote/main.elf and b/panda/board/obj/panda_h7_hkg_remote/main.elf differ diff --git a/panda/board/obj/panda_h7_hkg_remote_can_ignition_only.bin.signed b/panda/board/obj/panda_h7_hkg_remote_can_ignition_only.bin.signed index 945ca85a5..d1bc472e2 100644 Binary files a/panda/board/obj/panda_h7_hkg_remote_can_ignition_only.bin.signed and b/panda/board/obj/panda_h7_hkg_remote_can_ignition_only.bin.signed differ diff --git a/panda/board/obj/panda_h7_hkg_remote_can_ignition_only/main.bin b/panda/board/obj/panda_h7_hkg_remote_can_ignition_only/main.bin index a2dba79a0..8b7985231 100755 Binary files a/panda/board/obj/panda_h7_hkg_remote_can_ignition_only/main.bin and b/panda/board/obj/panda_h7_hkg_remote_can_ignition_only/main.bin differ diff --git a/panda/board/obj/panda_h7_hkg_remote_can_ignition_only/main.elf b/panda/board/obj/panda_h7_hkg_remote_can_ignition_only/main.elf index bde5bc419..0f60ff264 100755 Binary files a/panda/board/obj/panda_h7_hkg_remote_can_ignition_only/main.elf and b/panda/board/obj/panda_h7_hkg_remote_can_ignition_only/main.elf differ diff --git a/panda/board/obj/panda_h7_remote.bin.signed b/panda/board/obj/panda_h7_remote.bin.signed index c77bcd92d..b798ec0f9 100644 Binary files a/panda/board/obj/panda_h7_remote.bin.signed and b/panda/board/obj/panda_h7_remote.bin.signed differ diff --git a/panda/board/obj/panda_h7_remote/main.bin b/panda/board/obj/panda_h7_remote/main.bin index 3a4fe8b8b..4d33f77da 100755 Binary files a/panda/board/obj/panda_h7_remote/main.bin and b/panda/board/obj/panda_h7_remote/main.bin differ diff --git a/panda/board/obj/panda_h7_remote/main.elf b/panda/board/obj/panda_h7_remote/main.elf index 89612a1c4..bb80dd36b 100755 Binary files a/panda/board/obj/panda_h7_remote/main.elf and b/panda/board/obj/panda_h7_remote/main.elf 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 index ed86e3bee..dbd0fea37 100644 Binary files a/panda/board/obj/panda_h7_remote_can_ignition_only.bin.signed and b/panda/board/obj/panda_h7_remote_can_ignition_only.bin.signed differ diff --git a/panda/board/obj/panda_h7_remote_can_ignition_only/main.bin b/panda/board/obj/panda_h7_remote_can_ignition_only/main.bin index 03ecbd23c..3f248d5fc 100755 Binary files a/panda/board/obj/panda_h7_remote_can_ignition_only/main.bin and b/panda/board/obj/panda_h7_remote_can_ignition_only/main.bin differ diff --git a/panda/board/obj/panda_h7_remote_can_ignition_only/main.elf b/panda/board/obj/panda_h7_remote_can_ignition_only/main.elf index eefca71f5..176047821 100755 Binary files a/panda/board/obj/panda_h7_remote_can_ignition_only/main.elf and b/panda/board/obj/panda_h7_remote_can_ignition_only/main.elf differ diff --git a/panda/board/obj/panda_hkg_remote.bin.signed b/panda/board/obj/panda_hkg_remote.bin.signed index cb631ec5e..d420d5972 100644 Binary files a/panda/board/obj/panda_hkg_remote.bin.signed and b/panda/board/obj/panda_hkg_remote.bin.signed differ diff --git a/panda/board/obj/panda_hkg_remote/main.bin b/panda/board/obj/panda_hkg_remote/main.bin index 42e3d4043..4473487d3 100755 Binary files a/panda/board/obj/panda_hkg_remote/main.bin and b/panda/board/obj/panda_hkg_remote/main.bin differ diff --git a/panda/board/obj/panda_hkg_remote/main.elf b/panda/board/obj/panda_hkg_remote/main.elf index 55341a8d3..20039bc6c 100755 Binary files a/panda/board/obj/panda_hkg_remote/main.elf and b/panda/board/obj/panda_hkg_remote/main.elf differ diff --git a/panda/board/obj/panda_hkg_remote_can_ignition_only.bin.signed b/panda/board/obj/panda_hkg_remote_can_ignition_only.bin.signed index a0cdde5ad..e5eaad375 100644 Binary files a/panda/board/obj/panda_hkg_remote_can_ignition_only.bin.signed and b/panda/board/obj/panda_hkg_remote_can_ignition_only.bin.signed differ diff --git a/panda/board/obj/panda_hkg_remote_can_ignition_only/main.bin b/panda/board/obj/panda_hkg_remote_can_ignition_only/main.bin index 3ec2740c6..7a58a221e 100755 Binary files a/panda/board/obj/panda_hkg_remote_can_ignition_only/main.bin and b/panda/board/obj/panda_hkg_remote_can_ignition_only/main.bin differ diff --git a/panda/board/obj/panda_hkg_remote_can_ignition_only/main.elf b/panda/board/obj/panda_hkg_remote_can_ignition_only/main.elf index 7e4335fc3..9461f1938 100755 Binary files a/panda/board/obj/panda_hkg_remote_can_ignition_only/main.elf and b/panda/board/obj/panda_hkg_remote_can_ignition_only/main.elf differ diff --git a/panda/board/obj/panda_jungle_h7.bin.signed b/panda/board/obj/panda_jungle_h7.bin.signed index 835d2cca3..4ed3eb38c 100644 Binary files a/panda/board/obj/panda_jungle_h7.bin.signed and b/panda/board/obj/panda_jungle_h7.bin.signed differ diff --git a/panda/board/obj/panda_jungle_h7/main.bin b/panda/board/obj/panda_jungle_h7/main.bin index f3be5bb21..bb9fb25d4 100755 Binary files a/panda/board/obj/panda_jungle_h7/main.bin and b/panda/board/obj/panda_jungle_h7/main.bin differ diff --git a/panda/board/obj/panda_jungle_h7/main.elf b/panda/board/obj/panda_jungle_h7/main.elf index 3c47be345..a58bfc715 100755 Binary files a/panda/board/obj/panda_jungle_h7/main.elf and b/panda/board/obj/panda_jungle_h7/main.elf differ diff --git a/panda/board/obj/panda_remote.bin.signed b/panda/board/obj/panda_remote.bin.signed index aef7dcc76..f3e1f2f28 100644 Binary files a/panda/board/obj/panda_remote.bin.signed and b/panda/board/obj/panda_remote.bin.signed differ diff --git a/panda/board/obj/panda_remote/main.bin b/panda/board/obj/panda_remote/main.bin index 88b1ccf89..33ee8c427 100755 Binary files a/panda/board/obj/panda_remote/main.bin and b/panda/board/obj/panda_remote/main.bin differ diff --git a/panda/board/obj/panda_remote/main.elf b/panda/board/obj/panda_remote/main.elf index 7a0f2a919..358b979f3 100755 Binary files a/panda/board/obj/panda_remote/main.elf and b/panda/board/obj/panda_remote/main.elf 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 index 9f5241c34..f515db8de 100644 Binary files a/panda/board/obj/panda_remote_can_ignition_only.bin.signed and b/panda/board/obj/panda_remote_can_ignition_only.bin.signed differ diff --git a/panda/board/obj/panda_remote_can_ignition_only/main.bin b/panda/board/obj/panda_remote_can_ignition_only/main.bin index fb8f39251..991f9da49 100755 Binary files a/panda/board/obj/panda_remote_can_ignition_only/main.bin and b/panda/board/obj/panda_remote_can_ignition_only/main.bin differ diff --git a/panda/board/obj/panda_remote_can_ignition_only/main.elf b/panda/board/obj/panda_remote_can_ignition_only/main.elf index f3e2bc4c8..93c522bf4 100755 Binary files a/panda/board/obj/panda_remote_can_ignition_only/main.elf and b/panda/board/obj/panda_remote_can_ignition_only/main.elf differ diff --git a/panda/board/obj/version b/panda/board/obj/version index 05d1065a3..f2f9378a1 100644 --- a/panda/board/obj/version +++ b/panda/board/obj/version @@ -1 +1 @@ -DEV-187318b5-DEBUG \ No newline at end of file +DEV-cbf7f35c-DEBUG \ No newline at end of file diff --git a/scripts/model_compiler.py b/scripts/model_compiler.py index bf67056ec..229f555d9 100644 --- a/scripts/model_compiler.py +++ b/scripts/model_compiler.py @@ -1,8 +1,6 @@ #!/usr/bin/env python3 import argparse import codecs -import ctypes -import glob import hashlib import json import os @@ -47,19 +45,6 @@ MODEL_CONTEXT_FREQ = 5 REPOSITORY_FILE_LIMIT = 100_000_000 DEFAULT_MULTIPART_SIZE = 95 * 1024 * 1024 USBGPU_PROBE_ATTEMPTS = 10 -USBGPU_PROBE_TIMEOUT = 2 -USBDEVFS_CONTROL = 0xC0185500 -USBGPU_VID_PIDS = (("add1", "0001"), ("3801", "0001")) -USBGPU_FIRMWARE_PRODUCT = "custom ed4e39b7-CLEAN" - - -class _UsbdevfsControl(ctypes.Structure): - _fields_ = [("request_type", ctypes.c_uint8), ("request", ctypes.c_uint8), - ("value", ctypes.c_uint16), ("index", ctypes.c_uint16), - ("length", ctypes.c_uint16), ("timeout", ctypes.c_uint32), - ("data", ctypes.c_void_p)] - - def build_compile_env(*, supercombo: bool = False) -> dict[str, str]: env = os.environ.copy() existing_pythonpath = env.get("PYTHONPATH", "") @@ -85,79 +70,15 @@ def build_compile_env(*, supercombo: bool = False) -> dict[str, str]: return env -def _probe_external_gpu_link_once() -> tuple[bool, str]: - """Probe the bridge without initializing tinygrad or resetting the USB device.""" - import fcntl +def wait_for_external_gpu() -> None: + """Use openpilot's Chestnut link probe before starting a USB-GPU build.""" + from openpilot.system.hardware.chestnut.flash import link_up - diagnostics: list[str] = [] - for path in glob.glob("/sys/bus/usb/devices/*"): - try: - if not Path(path, "idVendor").is_file(): - continue - vendor = Path(path, "idVendor").read_text().strip().lower() - product = Path(path, "idProduct").read_text().strip().lower() - if (vendor, product) not in USBGPU_VID_PIDS: - continue - bus = int(Path(path, "busnum").read_text()) - device = int(Path(path, "devnum").read_text()) - location = f"usb:{bus}-{device}" - firmware = Path(path, "product").read_text().strip() - if firmware and firmware != USBGPU_FIRMWARE_PRODUCT: - return False, f"{location}: firmware {firmware!r}, expected {USBGPU_FIRMWARE_PRODUCT!r}" - fd = os.open(f"/dev/bus/usb/{bus:03d}/{device:03d}", os.O_RDWR) - except (OSError, ValueError) as exc: - diagnostics.append(f"{path}: open failed ({exc})") - continue - - try: - fcntl.ioctl(fd, USBDEVFS_CONTROL, _UsbdevfsControl(0x40, 0xF3, 1, 0, 0, USBGPU_PROBE_TIMEOUT * 1000, None)) - state = (ctypes.c_ubyte * 1)() - fcntl.ioctl(fd, USBDEVFS_CONTROL, _UsbdevfsControl(0xC0, 0xE4, 0xB450, 0, 1, 1000, ctypes.cast(state, ctypes.c_void_p))) - if state[0] == 0x78: - return True, f"{location}: LTSSM=0x78" - diagnostics.append(f"{location}: LTSSM=0x{state[0]:02X}") - except OSError as exc: - diagnostics.append(f"{location}: control probe failed ({exc})") - finally: - os.close(fd) - return False, diagnostics[-1] if diagnostics else "no ASM2464PD device found" - - -def wait_for_external_gpu(compile_env: dict[str, str]) -> bool: - """Wait for the USB GPU's PCIe link before starting the large model build. - - The dock can enumerate on USB before its PCIe link has finished training. - Probe the bridge's control endpoint directly, like upstream openpilot. Do - not instantiate tinygrad here: opening the GPU resets/claims the USB - interface, and doing that in a probe process can leave the bridge in a state - where the authoritative compiler cannot train the link. - """ - del compile_env # retained in the public helper signature for callers/tests - diagnostics: list[str] = [] - - for attempt in range(USBGPU_PROBE_ATTEMPTS): - if attempt: - time.sleep(1) - try: - ready, detail = _probe_external_gpu_link_once() - except Exception as exc: # probe is advisory; compile_modeld remains authoritative - ready, detail = False, str(exc) - if ready: - return True - if "firmware" in detail and "expected" in detail: - raise RuntimeError( - f"External GPU firmware is out of date: {detail}. " - "Wait for hardwared to flash the dock, or run " - "sudo python3 system/hardware/chestnut/flash.py ed4e39b7." - ) - diagnostics.append(detail) - - detail = diagnostics[-1] if diagnostics else "unknown error" - print( - f"Warning: external GPU link did not become ready after {USBGPU_PROBE_ATTEMPTS} probes: {detail}\n" - " Continuing; compile_modeld will perform the authoritative link wait and initialization." - ) - return False + for _ in range(USBGPU_PROBE_ATTEMPTS): + if link_up(): + return + time.sleep(1) + raise RuntimeError("Chestnut not ready; external GPU PCIe link did not come up") def parse_args() -> argparse.Namespace: @@ -612,7 +533,7 @@ def compile_driving( "TC_OPT": "2", }) command.append("--out-of-band") - wait_for_external_gpu(compile_env) + wait_for_external_gpu() subprocess.run(command, cwd=REPO_ROOT, env=compile_env, check=True) return output_path diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 9799ca2eb..b9965d4a1 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -128,6 +128,7 @@ class LatControlTorque(LatControl): self.is_tucson_4th_gen = CP.carFingerprint in TUCSON_4TH_GEN_CARS self.is_civic_bosch_modified = CP.carFingerprint == HONDA_CAR.HONDA_CIVIC_BOSCH and bool(CP.flags & HondaFlags.EPS_MODIFIED) self.is_silverado = CP.carFingerprint in SILVERADO_CARS + self.is_gmc_yukon_cc = CP.carFingerprint in GMC_YUKON_CC_CARS self.is_ram_1500 = CP.carFingerprint in RAM_1500_CARS self.is_gm = CP.brand == "gm" self.is_hkg_canfd_torque = CP.brand == "hyundai" and bool(CP.flags & HyundaiFlags.CANFD) @@ -289,6 +290,8 @@ class LatControlTorque(LatControl): ff *= ff_scale if self.is_ram_1500: ff *= get_ram_1500_ff_scale(setpoint, desired_lateral_jerk, CS.vEgo) + if self.is_gmc_yukon_cc: + ff *= get_gmc_yukon_cc_ff_scale(setpoint, desired_lateral_jerk, CS.vEgo) trailer_load_kg = float(max(getattr(starpilot_toggles, "trailer_load_kg", 0.0) or 0.0, 0.0)) bolt_2022_2023_tuned_path_active = self.is_bolt_2022_2023 bolt_2018_2021_tuned_path_active = self.is_bolt_2018_2021 @@ -526,6 +529,12 @@ class LatControlTorque(LatControl): output_torque *= get_ioniq_6_highway_transition_output_taper_scale(setpoint, desired_lateral_jerk, CS.vEgo) if self.is_ioniq_6_2025: output_torque *= get_ioniq_6_2025_center_output_scale(setpoint, CS.vEgo) + low_speed_output_limit = get_ioniq_6_2025_low_speed_output_limit(setpoint, desired_lateral_jerk, CS.vEgo) + output_torque = float(np.clip( + output_torque, + -low_speed_output_limit, + low_speed_output_limit, + )) elif self.is_ram_1500 and output_torque * setpoint > 0.0: output_torque *= get_ram_1500_transition_output_scale(setpoint, desired_lateral_jerk, CS.vEgo) elif self.is_kona_non_scc: @@ -563,6 +572,10 @@ class LatControlTorque(LatControl): ) low_speed_output_limit = get_genesis_g70_low_speed_output_limit(setpoint, CS.vEgo) output_torque = float(np.clip(output_torque, -low_speed_output_limit, low_speed_output_limit)) + elif self.is_genesis_gv70: + output_torque *= get_genesis_gv70_high_speed_error_scale( + setpoint, measurement, desired_lateral_jerk, CS.vEgo, + ) elif sonata_hybrid_active: output_torque *= sonata_hybrid_center_taper output_torque *= sonata_hybrid_center_output_taper diff --git a/selfdrive/controls/lib/latcontrol_vehicle_tunes.py b/selfdrive/controls/lib/latcontrol_vehicle_tunes.py index 308ac8e68..375f3611e 100644 --- a/selfdrive/controls/lib/latcontrol_vehicle_tunes.py +++ b/selfdrive/controls/lib/latcontrol_vehicle_tunes.py @@ -84,6 +84,9 @@ SILVERADO_CARS = ( GM_CAR.CHEVROLET_SILVERADO, GM_CAR.CHEVROLET_SILVERADO_CC, ) +GMC_YUKON_CC_CARS = ( + GM_CAR.GMC_YUKON_CC, +) GENESIS_G90_CARS = ( HYUNDAI_CAR.GENESIS_G90, ) @@ -207,6 +210,13 @@ GENESIS_GV70_UNWIND_FF_JERK = 0.10 GENESIS_GV70_UNWIND_FF_JERK_WIDTH = 0.10 GENESIS_GV70_UNWIND_FF_SPEED = 10.0 * CV.MPH_TO_MS GENESIS_GV70_UNWIND_FF_SPEED_WIDTH = 4.0 * CV.MPH_TO_MS +GENESIS_GV70_HIGH_SPEED_ERROR_DAMPING_MAX = 0.18 +GENESIS_GV70_HIGH_SPEED_ERROR_DAMPING_SPEED = 50.0 * CV.MPH_TO_MS +GENESIS_GV70_HIGH_SPEED_ERROR_DAMPING_SPEED_WIDTH = 8.0 * CV.MPH_TO_MS +GENESIS_GV70_HIGH_SPEED_ERROR_DAMPING_ERROR = 0.18 +GENESIS_GV70_HIGH_SPEED_ERROR_DAMPING_ERROR_WIDTH = 0.15 +GENESIS_GV70_HIGH_SPEED_ERROR_DAMPING_JERK = 0.15 +GENESIS_GV70_HIGH_SPEED_ERROR_DAMPING_JERK_WIDTH = 0.10 GENESIS_G70_FRICTION_THRESHOLD_GAIN = 0.10 GENESIS_G70_FRICTION_SPEED_ONSET = 10.0 @@ -240,7 +250,7 @@ GENESIS_G70_LOW_SPEED_ANGLE_DAMPING_ERROR_WIDTH = 3.0 GENESIS_G70_LOW_SPEED_ANGLE_DAMPING_ACTUAL = 8.0 GENESIS_G70_LOW_SPEED_ANGLE_DAMPING_ACTUAL_WIDTH = 4.0 GENESIS_G70_LOW_SPEED_ANGLE_DAMPING_BLEND = 0.50 -GENESIS_G70_LOW_SPEED_OUTPUT_LIMIT_REDUCTION = 0.65 +GENESIS_G70_LOW_SPEED_OUTPUT_LIMIT_REDUCTION = 0.85 GENESIS_G70_LOW_SPEED_OUTPUT_LIMIT_LAT = 0.14 GENESIS_G70_LOW_SPEED_OUTPUT_LIMIT_LAT_WIDTH = 0.05 GENESIS_G70_LOW_SPEED_OUTPUT_LIMIT_SPEED = 6.0 @@ -394,6 +404,14 @@ SILVERADO_CENTER_TAPER_LAT_WIDTH = 0.05 SILVERADO_CENTER_TAPER_SPEED = 12.0 SILVERADO_CENTER_TAPER_SPEED_WIDTH = 2.5 +GMC_YUKON_CC_PHASE_SCALE = 0.14 +GMC_YUKON_CC_PHASE_SPEED_ONSET = 12.0 +GMC_YUKON_CC_PHASE_SPEED_FULL = 30.0 +GMC_YUKON_CC_PHASE_LAT_ONSET = 0.35 +GMC_YUKON_CC_PHASE_LAT_WIDTH = 0.18 +GMC_YUKON_CC_TURN_IN_FF_BOOST = 0.08 +GMC_YUKON_CC_UNWIND_FF_REDUCTION = 0.12 + SONATA_HYBRID_BASE_LAT_ACCEL_FACTOR_MULT = 1.05 SONATA_HYBRID_FF_REDUCTION_LEFT = 0.09 SONATA_HYBRID_FF_REDUCTION_RIGHT = 0.22 @@ -417,7 +435,7 @@ SONATA_HYBRID_LOW_SPEED_CENTER_TAPER_LAT = 0.10 SONATA_HYBRID_LOW_SPEED_CENTER_TAPER_LAT_WIDTH = 0.02 SONATA_HYBRID_LOW_SPEED_CENTER_TAPER_SPEED_MAX = 7.5 SONATA_HYBRID_LOW_SPEED_CENTER_TAPER_SPEED_WIDTH = 1.0 -SONATA_HYBRID_CENTER_OUTPUT_TAPER_MAX = 0.06 +SONATA_HYBRID_CENTER_OUTPUT_TAPER_MAX = 0.08 SONATA_HYBRID_CENTER_OUTPUT_TAPER_LAT = 0.18 SONATA_HYBRID_CENTER_OUTPUT_TAPER_LAT_WIDTH = 0.05 SONATA_HYBRID_CENTER_OUTPUT_TAPER_SPEED = 12.5 @@ -817,7 +835,7 @@ IONIQ_6_FRICTION_CENTER_FADE_SPEED_WIDTH = 2.5 # Newer Ioniq 6 highway center-chatter correction; activation is firmware-gated. IONIQ_6_2025_FRICTION_SCALE_MULT = 0.80 IONIQ_6_2025_FRICTION_JERK_DEADZONE = 0.45 -IONIQ_6_2025_CENTER_OUTPUT_TAPER_MAX = 0.28 +IONIQ_6_2025_CENTER_OUTPUT_TAPER_MAX = 0.32 IONIQ_6_2025_CENTER_OUTPUT_TAPER_LAT = 0.35 IONIQ_6_2025_CENTER_OUTPUT_TAPER_LAT_WIDTH = 0.10 IONIQ_6_2025_CENTER_OUTPUT_TAPER_SPEED = 22.0 @@ -830,6 +848,11 @@ IONIQ_6_2025_LOW_SPEED_CENTER_LAT = 0.22 IONIQ_6_2025_LOW_SPEED_CENTER_LAT_WIDTH = 0.10 IONIQ_6_2025_LOW_SPEED_CENTER_JERK = 0.30 IONIQ_6_2025_LOW_SPEED_CENTER_JERK_WIDTH = 0.13 +IONIQ_6_2025_LOW_SPEED_OUTPUT_LIMIT_BASE = 0.22 +IONIQ_6_2025_LOW_SPEED_OUTPUT_LIMIT_TURN_RELIEF = 0.50 +IONIQ_6_2025_LOW_SPEED_OUTPUT_LIMIT_SPEED_RELIEF = 0.20 +IONIQ_6_2025_LOW_SPEED_OUTPUT_LIMIT_SPEED = 6.5 +IONIQ_6_2025_LOW_SPEED_OUTPUT_LIMIT_SPEED_WIDTH = 1.5 IONIQ_6_HEAVY_DIRECTIONAL_TAPER_LAT_START = 0.90 IONIQ_6_HEAVY_DIRECTIONAL_TAPER_LAT_WIDTH = 0.18 IONIQ_6_HEAVY_DIRECTIONAL_TAPER_BASE_LEFT = 0.03 @@ -851,7 +874,7 @@ KIA_EV6_FF_CUTOFF = 1.90 KIA_EV6_FF_CUTOFF_WIDTH = 0.40 KIA_EV6_TRANSITION_SPEED = 14.5 KIA_EV6_PHASE_SCALE = 0.09 -KIA_EV6_TURN_IN_BOOST_LEFT = 0.62 +KIA_EV6_TURN_IN_BOOST_LEFT = 0.54 KIA_EV6_TURN_IN_BOOST_RIGHT = 0.60 KIA_EV6_UNWIND_TAPER_LEFT = 0.56 KIA_EV6_UNWIND_TAPER_RIGHT = 0.54 @@ -892,7 +915,7 @@ KIA_EV6_LOW_SPEED_CENTER_TAPER_LAT = 0.08 KIA_EV6_LOW_SPEED_CENTER_TAPER_LAT_WIDTH = 0.02 KIA_EV6_LOW_SPEED_CENTER_TAPER_SPEED_MAX = 8.5 KIA_EV6_LOW_SPEED_CENTER_TAPER_SPEED_WIDTH = 1.4 -KIA_EV6_CENTER_OUTPUT_TAPER_MAX = 0.12 +KIA_EV6_CENTER_OUTPUT_TAPER_MAX = 0.14 KIA_EV6_CENTER_OUTPUT_TAPER_LAT = 0.30 KIA_EV6_CENTER_OUTPUT_TAPER_LAT_WIDTH = 0.08 KIA_EV6_CENTER_OUTPUT_TAPER_SPEED = 12.0 @@ -1535,6 +1558,20 @@ def get_ram_1500_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: fl RAM_1500_UNWIND_FF_REDUCTION * unwind_weight) * speed_weight * lat_weight) +def get_gmc_yukon_cc_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: float, v_ego: float) -> float: + """Add turn-in authority and soften the high-speed unwind transient on Yukon CC.""" + phase = math.tanh((desired_lateral_accel * desired_lateral_jerk) / GMC_YUKON_CC_PHASE_SCALE) + turn_in_weight = max(phase, 0.0) + unwind_weight = max(-phase, 0.0) + speed_weight = float(np.interp(v_ego, + [GMC_YUKON_CC_PHASE_SPEED_ONSET, GMC_YUKON_CC_PHASE_SPEED_FULL], + [0.0, 1.0])) + lat_weight = _sigmoid((abs(desired_lateral_accel) - GMC_YUKON_CC_PHASE_LAT_ONSET) / + GMC_YUKON_CC_PHASE_LAT_WIDTH) + return 1.0 + ((GMC_YUKON_CC_TURN_IN_FF_BOOST * turn_in_weight - + GMC_YUKON_CC_UNWIND_FF_REDUCTION * unwind_weight) * speed_weight * lat_weight) + + def get_kona_non_scc_highway_transition_output_scale(desired_lateral_accel: float, desired_lateral_jerk: float, v_ego: float) -> float: speed_weight = float(np.interp(v_ego, [KONA_NON_SCC_TRANSITION_SPEED_ONSET, KONA_NON_SCC_TRANSITION_SPEED_FULL], [0.0, 1.0])) @@ -2621,6 +2658,23 @@ def get_genesis_gv70_unwind_ff_scale(setpoint: float, measured_lateral_accel: fl return 1.0 - GENESIS_GV70_UNWIND_FF_REDUCTION_MAX * overshoot_weight * jerk_weight * speed_weight +def get_genesis_gv70_high_speed_error_scale(setpoint: float, measured_lateral_accel: float, + desired_lateral_jerk: float, v_ego: float) -> float: + tracking_error = abs(measured_lateral_accel - setpoint) + if tracking_error <= 0.0: + return 1.0 + speed_weight = _sigmoid((v_ego - GENESIS_GV70_HIGH_SPEED_ERROR_DAMPING_SPEED) / + GENESIS_GV70_HIGH_SPEED_ERROR_DAMPING_SPEED_WIDTH) + error_weight = _sigmoid((tracking_error - GENESIS_GV70_HIGH_SPEED_ERROR_DAMPING_ERROR) / + GENESIS_GV70_HIGH_SPEED_ERROR_DAMPING_ERROR_WIDTH) + jerk_weight = _sigmoid((abs(desired_lateral_jerk) - GENESIS_GV70_HIGH_SPEED_ERROR_DAMPING_JERK) / + GENESIS_GV70_HIGH_SPEED_ERROR_DAMPING_JERK_WIDTH) + phase_weight = 1.0 if setpoint * desired_lateral_jerk < 0.0 else 0.45 + reduction = (GENESIS_GV70_HIGH_SPEED_ERROR_DAMPING_MAX * speed_weight * error_weight * + (0.35 + (0.65 * jerk_weight)) * phase_weight) + return 1.0 - reduction + + def get_genesis_g70_friction_threshold(v_ego: float, desired_lateral_accel: float = 0.0, desired_lateral_jerk: float = 0.0) -> float: base_threshold = get_standard_friction_threshold(v_ego) @@ -2993,6 +3047,22 @@ def get_ioniq_6_2025_center_output_scale(desired_lateral_accel: float, v_ego: fl return 1.0 - IONIQ_6_2025_CENTER_OUTPUT_TAPER_MAX * speed_weight * center_weight +def get_ioniq_6_2025_low_speed_output_limit(desired_lateral_accel: float, + desired_lateral_jerk: float, v_ego: float) -> float: + """Limit small-signal torque at crawl speed while leaving real turn commands open.""" + speed_weight = _ioniq_6_sigmoid((IONIQ_6_2025_LOW_SPEED_OUTPUT_LIMIT_SPEED - max(v_ego, 0.0)) / + IONIQ_6_2025_LOW_SPEED_OUTPUT_LIMIT_SPEED_WIDTH) + center_weight = _ioniq_6_sigmoid((IONIQ_6_2025_LOW_SPEED_CENTER_LAT - abs(desired_lateral_accel)) / + IONIQ_6_2025_LOW_SPEED_CENTER_LAT_WIDTH) + calm_weight = _ioniq_6_sigmoid((IONIQ_6_2025_LOW_SPEED_CENTER_JERK - abs(desired_lateral_jerk)) / + IONIQ_6_2025_LOW_SPEED_CENTER_JERK_WIDTH) + center_weight *= calm_weight + limit = (IONIQ_6_2025_LOW_SPEED_OUTPUT_LIMIT_BASE + + IONIQ_6_2025_LOW_SPEED_OUTPUT_LIMIT_TURN_RELIEF * (1.0 - center_weight) + + IONIQ_6_2025_LOW_SPEED_OUTPUT_LIMIT_SPEED_RELIEF * (1.0 - speed_weight)) + return float(np.clip(limit, IONIQ_6_2025_LOW_SPEED_OUTPUT_LIMIT_BASE, 1.0)) + + def _ioniq_6_2025_low_speed_center_envelope(desired_lateral_accel: float, desired_lateral_jerk: float, v_ego: float) -> float: speed_weight = _ioniq_6_sigmoid((IONIQ_6_2025_LOW_SPEED_CENTER_SPEED - max(v_ego, 0.0)) / diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index 786d714e5..4ad9ca0e8 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -264,6 +264,9 @@ class LongControl: if output_accel > starpilot_toggles.stopAccel: output_accel = min(output_accel, 0.0) output_accel -= starpilot_toggles.stoppingDecelRate * DT_CTRL + output_accel = self.vehicle_tuning.shape_stopping_accel( + output_accel, a_target, should_stop, CS.vEgo, has_lead, starpilot_toggles.stopAccel, + ) output_accel = self._apply_moving_stop_target_follow(output_accel, a_target, should_stop, CS, starpilot_toggles) self.reset(preserve_stop_release=True) diff --git a/selfdrive/controls/lib/longcontrol_vehicle_tunes.py b/selfdrive/controls/lib/longcontrol_vehicle_tunes.py index c173965f3..1f5f1938e 100644 --- a/selfdrive/controls/lib/longcontrol_vehicle_tunes.py +++ b/selfdrive/controls/lib/longcontrol_vehicle_tunes.py @@ -25,7 +25,7 @@ GM_TRUCK_TARGET_FILTER_DOWN_TAU = 0.06 GM_TRUCK_TARGET_FILTER_BRAKE_BYPASS = -0.65 GM_TRUCK_TARGET_FILTER_DROP_BYPASS = 0.45 TOYOTA_SIENNA_TARGET_FILTER_MIN_SPEED = 12.0 -TOYOTA_SIENNA_TARGET_FILTER_UP_TAU = 0.18 +TOYOTA_SIENNA_TARGET_FILTER_UP_TAU = 0.32 TOYOTA_SIENNA_TARGET_FILTER_DOWN_TAU = 0.24 TOYOTA_SIENNA_LOW_SPEED_ACCEL_UP_TAU = 0.35 TOYOTA_SIENNA_TARGET_FILTER_BRAKE_BYPASS = -0.75 @@ -47,6 +47,7 @@ VOLT_CRUISE_INTEGRATOR_ERROR_MAX = 0.12 VOLT_CRUISE_INTEGRATOR_LEAK = 0.995 SUBARU_IMPREZA_STOP_RELEASE_TIME = 0.75 SUBARU_IMPREZA_STOP_RELEASE_MAX_ACCEL = 0.8 +HYUNDAI_ELANTRA_STOPPING_HOLD_TARGET_GAP = 0.25 def get_bolt_acc_pedal_friction_bias(output_accel, a_target, v_ego): @@ -123,6 +124,9 @@ class LongControlVehicleTuning: CP.brand == "subaru" and getattr(CP, "carFingerprint", None) == SUBARU_CAR.SUBARU_IMPREZA_2020 ) + self.is_hyundai_elantra_2021 = bool( + CP.brand == "hyundai" and str(getattr(CP, "carFingerprint", "")) == "HYUNDAI_ELANTRA_2021" + ) self.is_bolt_acc_pedal_friction_car = bool( CP.brand == "gm" and CP.enableGasInterceptorDEPRECATED and @@ -143,6 +147,16 @@ class LongControlVehicleTuning: self.bolt_start_handoff_frames = 0 self.subaru_stop_release_frames = 0 + def shape_stopping_accel(self, output_accel, a_target, should_stop, v_ego, has_lead, stop_accel): + """Release a stale hard lead brake once the stop target has eased.""" + if ( + not self.is_hyundai_elantra_2021 or + not has_lead or not should_stop or v_ego > 2.0 or + a_target <= stop_accel - HYUNDAI_ELANTRA_STOPPING_HOLD_TARGET_GAP + ): + return output_accel + return max(float(output_accel), float(stop_accel)) + def cap_subaru_stop_release_accel(self, output_accel, stopping_handoff, should_stop): """Prevent an Impreza stop-sign handoff from stepping straight into full throttle.""" if not self.is_subaru_impreza_2020: diff --git a/selfdrive/controls/tests/test_latcontrol.py b/selfdrive/controls/tests/test_latcontrol.py index d7a210e84..288317a31 100644 --- a/selfdrive/controls/tests/test_latcontrol.py +++ b/selfdrive/controls/tests/test_latcontrol.py @@ -38,6 +38,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_vehicle_tunes import ( KIA_FORTE_BASE_LAT_ACCEL_FACTOR_MULT, RAM_1500_BASE_LAT_ACCEL_FACTOR_MULT, RAM_1500_MAX_LAT_JERK_UP, + get_gmc_yukon_cc_ff_scale, get_ram_1500_transition_output_scale, get_ram_1500_ff_scale, get_subaru_impreza_pid_output_scale, @@ -78,6 +79,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import ( get_genesis_g70_low_speed_angle_damping, get_genesis_g70_low_speed_output_limit, get_genesis_gv70_friction_threshold, + get_genesis_gv70_high_speed_error_scale, get_genesis_gv70_unwind_ff_scale, get_elantra_non_scc_ff_scale, get_palisade_ff_scale, @@ -116,6 +118,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import ( get_ioniq_6_friction_threshold, get_ioniq_6_low_speed_angle_assist_torque, get_ioniq_6_2025_center_output_scale, + get_ioniq_6_2025_low_speed_output_limit, is_ioniq_6_2025_model, get_kia_forte_center_taper_scale, get_kia_forte_ff_scale, @@ -793,6 +796,12 @@ class TestLatControl: assert highway_turn == pytest.approx(base, rel=0.01) assert highway_center < center + def test_genesis_gv70_high_speed_error_damping(self): + assert get_genesis_gv70_high_speed_error_scale(0.2, 0.2, 0.8, 20.0) == 1.0 + assert get_genesis_gv70_high_speed_error_scale(-0.7, 0.58, -0.8, 33.5) < 1.0 + assert get_genesis_gv70_high_speed_error_scale(-0.7, 0.58, -0.8, 20.0) > \ + get_genesis_gv70_high_speed_error_scale(-0.7, 0.58, -0.8, 33.5) + def test_genesis_g70_center_chatter_tune(self): base = get_standard_friction_threshold(25.0) center = get_genesis_g70_friction_threshold(25.0, 0.0, 0.0) @@ -808,6 +817,7 @@ class TestLatControl: assert get_genesis_g70_center_output_scale(0.0, 0.0) < get_genesis_g70_center_output_scale(0.0, 10.0) assert get_genesis_g70_low_speed_output_limit(0.0, 2.0) < get_genesis_g70_low_speed_output_limit(0.5, 2.0) assert get_genesis_g70_low_speed_output_limit(0.0, 2.0) < get_genesis_g70_low_speed_output_limit(0.0, 10.0) + assert get_genesis_g70_low_speed_output_limit(0.0, 2.0) < 0.30 assert get_genesis_g70_low_speed_angle_damping(0.0, -20.0, 0.0, 2.0) < 0.0 assert get_genesis_g70_low_speed_angle_damping(0.0, 20.0, 0.0, 2.0) > 0.0 assert get_genesis_g70_curve_unwind_output_scale(0.7, -0.5, 25.0) > 1.0 @@ -958,6 +968,29 @@ class TestLatControl: assert get_ram_1500_ff_scale(1.2, -1.1, 17.0) < 1.0 assert get_ram_1500_ff_scale(1.2, 1.1, 6.0) < get_ram_1500_ff_scale(1.2, 1.1, 17.0) + def test_gmc_yukon_cc_phase_feedforward_curve(self): + assert get_gmc_yukon_cc_ff_scale(0.0, 1.0, 30.0) == pytest.approx(1.0) + assert get_gmc_yukon_cc_ff_scale(1.2, 1.1, 30.0) > 1.0 + assert get_gmc_yukon_cc_ff_scale(1.2, -1.1, 30.0) < 1.0 + assert get_gmc_yukon_cc_ff_scale(1.2, 1.1, 8.0) < get_gmc_yukon_cc_ff_scale(1.2, 1.1, 30.0) + + def test_gmc_yukon_cc_phase_feedforward_update_path(self, monkeypatch): + controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(GM.GMC_YUKON_CC) + CS.vEgo = 25.0 + base_output, _, _ = controller.update( + True, CS, VM, params, False, 0.0025, False, 0.2, None, None, starpilot_toggles, + ) + + monkeypatch.setattr(latcontrol_torque, "get_gmc_yukon_cc_ff_scale", lambda *_args: 0.5) + tuned_controller, tuned_VM, tuned_CS, tuned_params, tuned_toggles = self._build_torque_controller(GM.GMC_YUKON_CC) + tuned_CS.vEgo = 25.0 + tuned_output, _, _ = tuned_controller.update( + True, tuned_CS, tuned_VM, tuned_params, False, 0.0025, False, 0.2, None, None, tuned_toggles, + ) + + assert controller.is_gmc_yukon_cc + assert tuned_output != pytest.approx(base_output) + def test_ram_1500_jerk_limit_update_path(self): controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(CHRYSLER.RAM_1500_5TH_GEN) jerk_samples = [] @@ -1197,6 +1230,8 @@ class TestLatControl: assert low_speed_center_error < low_speed_turn_error assert low_speed_center_error < high_speed_center_error assert low_speed_center_friction < 1.0 + assert get_ioniq_6_2025_low_speed_output_limit(0.02, 0.05, 2.5) < get_ioniq_6_2025_low_speed_output_limit(0.60, 0.80, 2.5) + assert get_ioniq_6_2025_low_speed_output_limit(0.02, 0.05, 2.5) < get_ioniq_6_2025_low_speed_output_limit(0.02, 0.05, 8.0) def test_ioniq_6_center_taper_curve(self): assert get_ioniq_6_center_taper_scale(0.0, 10.0) > get_ioniq_6_center_taper_scale(0.0, 30.0) @@ -1418,6 +1453,17 @@ class TestLatControl: assert controller.torque_params.latAccelFactor == pytest.approx(3.0 * 1.22) assert controller.low_speed_reset_threshold == pytest.approx(0.1 * 0.44704) + def test_ioniq_6_2025_low_speed_output_limit_update_path(self, monkeypatch): + monkeypatch.setattr(latcontrol_torque, "get_ioniq_6_2025_low_speed_output_limit", lambda *_args: 0.05) + controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(HYUNDAI.HYUNDAI_IONIQ_6) + controller.is_ioniq_6_2025 = True + CS.vEgo = 3.2 + + output, _, lac_log = controller.update(True, CS, VM, params, False, 0.0025, False, 0.2, None, None, starpilot_toggles) + + assert lac_log.active + assert abs(output) <= 0.05 + def test_elantra_non_scc_default_update_path(self): controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(HYUNDAI.HYUNDAI_ELANTRA_HEV_2022_NON_SCC) CarInterface = interfaces[HYUNDAI.HYUNDAI_ELANTRA_HEV_2022_NON_SCC] @@ -1807,7 +1853,7 @@ class TestLatControl: def test_kia_ev6_center_output_taper_curve(self): assert get_kia_ev6_center_output_scale(0.0, 10.0) > get_kia_ev6_center_output_scale(0.0, 20.0) assert get_kia_ev6_center_output_scale(0.0, 20.0) < get_kia_ev6_center_output_scale(0.5, 20.0) - assert get_kia_ev6_center_output_scale(0.0, 20.0) > 0.87 + assert get_kia_ev6_center_output_scale(0.0, 20.0) > 0.86 def test_kia_ev6_center_output_taper_update_path(self, monkeypatch): controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(HYUNDAI.KIA_EV6) diff --git a/selfdrive/controls/tests/test_longcontrol.py b/selfdrive/controls/tests/test_longcontrol.py index b06e5bbb2..b2b5d0834 100644 --- a/selfdrive/controls/tests/test_longcontrol.py +++ b/selfdrive/controls/tests/test_longcontrol.py @@ -755,6 +755,15 @@ def test_stopping_state_follows_stronger_moving_stop_target(): assert output_accel < -1.43 +def test_elantra_lead_stop_releases_stale_hard_brake_after_target_eases(): + CP = make_longcontrol_cp(brand="hyundai", carFingerprint="HYUNDAI_ELANTRA_2021") + tuning = vehicle_tunes.LongControlVehicleTuning(CP) + + assert tuning.shape_stopping_accel(-1.20, -0.25, True, 1.0, True, -0.85) == pytest.approx(-0.85) + assert tuning.shape_stopping_accel(-1.20, -1.50, True, 1.0, True, -0.85) == pytest.approx(-1.20) + assert tuning.shape_stopping_accel(-1.20, -0.25, True, 1.0, False, -0.85) == pytest.approx(-1.20) + + def test_volt_testing_ground_handoff_freezes_integrator(monkeypatch): CP = car.CarParams.new_message() CP.brand = "gm" @@ -1177,6 +1186,16 @@ def test_toyota_sienna_target_filter_smooths_mild_high_speed_handoffs(): assert -0.20 < filtered < 0.30 +def test_toyota_sienna_target_filter_unwinds_braking_before_acceleration(): + CP = make_longcontrol_cp(brand="toyota", carFingerprint=TOYOTA_CAR.TOYOTA_SIENNA_4TH_GEN) + tuning = vehicle_tunes.LongControlVehicleTuning(CP) + + tuning.shape_toyota_sienna_accel_target(-1.2, 20.0, False) + recovering = tuning.shape_toyota_sienna_accel_target(1.2, 20.0, False) + + assert recovering == pytest.approx(-1.1272727273) + + def test_toyota_sienna_target_filter_ramps_low_speed_acceleration(): CP = make_longcontrol_cp(brand="toyota", carFingerprint=TOYOTA_CAR.TOYOTA_SIENNA_4TH_GEN) tuning = vehicle_tunes.LongControlVehicleTuning(CP) diff --git a/selfdrive/controls/tests/test_starpilot_vcruise.py b/selfdrive/controls/tests/test_starpilot_vcruise.py index 1e600ac8f..8aadc430a 100644 --- a/selfdrive/controls/tests/test_starpilot_vcruise.py +++ b/selfdrive/controls/tests/test_starpilot_vcruise.py @@ -10,6 +10,7 @@ from openpilot.starpilot.controls.lib.starpilot_vcruise import ( FORCE_STOP_TURN_VETO_STOP_SEEN_HOLD_TIME, StarPilotVCruise, get_active_slc_control_target, + get_lead_veto_distance, get_slc_lead_drop_relaxed_target, ) from types import SimpleNamespace @@ -110,6 +111,11 @@ def test_active_slc_control_target_does_not_require_set_speed_limit(): assert target == pytest.approx((48.0 * CV.MPH_TO_MS) - 0.4) +def test_elantra_gets_lead_veto_margin_before_force_stop(): + assert get_lead_veto_distance(SimpleNamespace(carFingerprint="HYUNDAI_ELANTRA_2021")) == pytest.approx(90.0) + assert get_lead_veto_distance(SimpleNamespace(carFingerprint="OTHER_CAR")) == pytest.approx(75.0) + + def test_curve_speed_controller_holds_target_through_brief_detector_dropout(): planner, vcruise = make_vcruise() sm = make_sm(standstill=False) diff --git a/selfdrive/modeld/tests/test_usbgpu_helpers.py b/selfdrive/modeld/tests/test_usbgpu_helpers.py index 5789c4e47..e40fc7cfb 100644 --- a/selfdrive/modeld/tests/test_usbgpu_helpers.py +++ b/selfdrive/modeld/tests/test_usbgpu_helpers.py @@ -23,28 +23,14 @@ def test_out_of_band_artifact_round_trip(): np.testing.assert_array_equal(restored["weights"], artifact["weights"]) -def test_external_gpu_probe_retries_until_pcie_is_ready(monkeypatch): +def test_external_gpu_probe_matches_upstream_retry_loop(monkeypatch): + from openpilot.system.hardware.chestnut import flash + calls = [] - probe_count = 0 - def probe(): - nonlocal probe_count - probe_count += 1 - calls.append("probe") - return (False, "LTSSM=0x00") if probe_count < 3 else (True, "LTSSM=0x78") - monkeypatch.setattr( - model_compiler, - "_probe_external_gpu_link_once", - probe, - ) + results = iter((False, False, True)) + monkeypatch.setattr(flash, "link_up", lambda: calls.append("probe") or next(results)) monkeypatch.setattr(model_compiler.time, "sleep", lambda seconds: calls.append(("sleep", seconds))) - model_compiler.wait_for_external_gpu({"PYTHONPATH": "/tmp/openpilot"}) + model_compiler.wait_for_external_gpu() assert calls == ["probe", ("sleep", 1), "probe", ("sleep", 1), "probe"] - - -def test_external_gpu_probe_reports_failure(monkeypatch): - monkeypatch.setattr(model_compiler, "_probe_external_gpu_link_once", lambda: (False, "link unavailable")) - monkeypatch.setattr(model_compiler.time, "sleep", lambda _: None) - - assert model_compiler.wait_for_external_gpu({}) is False diff --git a/selfdrive/pandad/tests/test_pandad_firmware.py b/selfdrive/pandad/tests/test_pandad_firmware.py index 5f4138eb2..8220ac87a 100644 --- a/selfdrive/pandad/tests/test_pandad_firmware.py +++ b/selfdrive/pandad/tests/test_pandad_firmware.py @@ -1,5 +1,7 @@ import importlib.util from pathlib import Path +import re +import subprocess import pytest @@ -10,6 +12,19 @@ assert SPEC is not None and SPEC.loader is not None PANDAD = importlib.util.module_from_spec(SPEC) SPEC.loader.exec_module(PANDAD) +REPO_ROOT = PANDAD_PATH.parents[2] +PANDA_H7_FIRMWARE = REPO_ROOT / "panda/board/obj/panda_h7.bin.signed" +PANDA_FIRMWARE_SOURCE_PREFIXES = ( + "opendbc_repo/opendbc/safety/", + "panda/board/", + "panda/crypto/", + "panda/drivers/", +) +PANDA_FIRMWARE_SOURCE_EXCLUSIONS = ( + "opendbc_repo/opendbc/safety/tests/", + "panda/board/obj/", +) + class FakeParams: def __init__(self, ignore_ignition_line): @@ -26,3 +41,26 @@ class FakeParams: ]) def test_ignore_ignition_line_follows_toggle(enabled, expected): assert PANDAD.get_ignore_ignition_line(FakeParams(enabled)) == expected + + +def test_tracked_panda_firmware_includes_current_safety_sources(): + version_match = re.search(rb"DEV-([0-9a-f]{8})-DEBUG", PANDA_H7_FIRMWARE.read_bytes()) + assert version_match is not None + firmware_commit = version_match.group(1).decode() + + try: + changed_files = subprocess.check_output( + ["git", "diff", "--name-only", f"{firmware_commit}..HEAD", "--", *PANDA_FIRMWARE_SOURCE_PREFIXES], + cwd=REPO_ROOT, + text=True, + ).splitlines() + changed_files += subprocess.check_output( + ["git", "diff", "--name-only", "HEAD", "--", *PANDA_FIRMWARE_SOURCE_PREFIXES], + cwd=REPO_ROOT, + text=True, + ).splitlines() + except subprocess.CalledProcessError: + pytest.skip("firmware source commit is unavailable in this checkout") + + stale_sources = sorted({path for path in changed_files if not path.startswith(PANDA_FIRMWARE_SOURCE_EXCLUSIONS)}) + assert stale_sources == [], f"panda firmware must be rebuilt after changing: {stale_sources}" diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index 27c44bf8c..dc8438bf7 100644 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -12,6 +12,7 @@ from msgq.visionipc import VisionIpcClient, VisionStreamType from opendbc.car.chrysler.values import pacifica_hybrid_aol_stock_acc_mode from opendbc.car.gm.values import GMFlags +from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR from openpilot.common.params import Params from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper, DT_CTRL @@ -59,7 +60,8 @@ IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput) def commanded_torque_at_max_for_saturation(CP, output: float) -> bool: torque_controller = (CP.steerControlType == car.CarParams.SteerControlType.torque and CP.lateralTuning.which() == "torque") - return torque_controller and abs(output) > 0.99 + has_controller_grace = CP.carFingerprint == HYUNDAI_CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN + return torque_controller and not has_controller_grace and abs(output) > 0.99 def should_loud_blindspot_alert_without_lateral(CS, sm, starpilot_toggles, combined_left_bsm=None, combined_right_bsm=None) -> bool: @@ -94,6 +96,11 @@ def get_starpilot_alert_filters(current_alert_types: list[str], clear_event_type starpilot_alert_types = list(current_alert_types) starpilot_clear_event_types = set(clear_event_types) + if int(StarPilotEventName.lkasEnable) in starpilot_events.names: + if ET.WARNING not in starpilot_alert_types: + starpilot_alert_types.append(ET.WARNING) + starpilot_clear_event_types.discard(ET.WARNING) + # This alert is explicitly allowed while lateral is paused/off. The state # machine only exposes WARNING while active/AOL, so let this warning through. if StarPilotEventName.laneChangeBlockedLoud in starpilot_events.names: diff --git a/selfdrive/selfdrived/tests/test_blindspot_alerts.py b/selfdrive/selfdrived/tests/test_blindspot_alerts.py index 342d6e9ec..3909ab5a5 100644 --- a/selfdrive/selfdrived/tests/test_blindspot_alerts.py +++ b/selfdrive/selfdrived/tests/test_blindspot_alerts.py @@ -99,6 +99,21 @@ def test_loud_blindspot_alert_survives_disabled_warning_filter(): assert alert_manager.current_alert.alert_type == "laneChangeBlockedLoud/warning" +def test_lkas_enable_sound_survives_disabled_warning_filter(): + events = Events(starpilot=True) + events.add(StarPilotEventName.lkasEnable) + + alert_types, clear_event_types = get_starpilot_alert_filters([ET.PERMANENT], {ET.WARNING}, events) + + alerts = events.create_alerts(alert_types) + alert_manager = AlertManager() + alert_manager.add_many(0, alerts) + alert_manager.process_alerts(0, clear_event_types) + + assert alert_manager.current_alert.alert_type == "lkasEnable/warning" + assert alert_manager.current_alert.audible_alert == log.SelfdriveState.AudibleAlert.engage + + def test_disabled_starpilot_warnings_stay_filtered_without_blindspot_event(): events = Events(starpilot=True) events.add(StarPilotEventName.noLaneAvailable) diff --git a/selfdrive/selfdrived/tests/test_selfdrived.py b/selfdrive/selfdrived/tests/test_selfdrived.py index eecdbfa10..f0ab22c99 100644 --- a/selfdrive/selfdrived/tests/test_selfdrived.py +++ b/selfdrive/selfdrived/tests/test_selfdrived.py @@ -1,4 +1,5 @@ from cereal import car +from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR from openpilot.selfdrive.selfdrived.selfdrived import commanded_torque_at_max_for_saturation @@ -17,3 +18,12 @@ def test_immediate_max_output_saturation_is_torque_controller_only(): CP.lateralTuning.init("torque") CP.steerControlType = car.CarParams.SteerControlType.angle assert not commanded_torque_at_max_for_saturation(CP, 1.0) + + +def test_gv70_uses_normal_saturation_timer_at_max_output(): + CP = car.CarParams.new_message() + CP.carFingerprint = HYUNDAI_CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN + CP.steerControlType = car.CarParams.SteerControlType.torque + CP.lateralTuning.init("torque") + + assert not commanded_torque_at_max_for_saturation(CP, 1.0) diff --git a/starpilot/controls/lib/starpilot_vcruise.py b/starpilot/controls/lib/starpilot_vcruise.py index 3e9c8cbbb..1fdf422dc 100644 --- a/starpilot/controls/lib/starpilot_vcruise.py +++ b/starpilot/controls/lib/starpilot_vcruise.py @@ -45,6 +45,9 @@ ACTIVATION_M = 100.0 # m — CEM/model path activates when model_length < t ACTIVATION_HYSTERESIS_M = 8.0 # m — release margin; absorbs model_length jitter at the gate LEAD_VETO_M = 75.0 # m — lead proximity that vetoes Force Stop (kept off ACTIVATION_M # so raising activation can't silently widen the veto) +LEAD_VETO_M_OVERRIDES = { + "HYUNDAI_ELANTRA_2021": 90.0, +} MPC_HANDOFF_M = 6.0 # m — below this, command 0 and let MPC finish the stop FORCE_STOP_APPROACH_DECEL = 0.65 # m/s^2 — speed ceiling before commit. LOWER = more early # braking; don't go under FORCE_STOP_MODEL_APPROACH_DECEL @@ -70,6 +73,11 @@ OFFSET_FT_MIN = -20 OFFSET_FT_MAX = 20 +def get_lead_veto_distance(car_params): + fingerprint = str(getattr(car_params, "carFingerprint", "")) + return LEAD_VETO_M_OVERRIDES.get(fingerprint, LEAD_VETO_M) + + def get_active_slc_control_target(speed_limit_controller, set_speed_limit, slc_target, slc_offset, overridden_speed, v_ego_diff, allow_lower_override=False): # `SetSpeedLimit` only controls engage-time set-speed initialization. Ongoing @@ -333,8 +341,13 @@ class StarPilotVCruise: # waiting for the tracking_lead filter (~1s ramp). Without this, Force Stop can latch # during the filter's settling window and stay committed for the whole stop. lead = self.starpilot_planner.lead_one + try: + car_params = sm["carParams"] + except (KeyError, IndexError, TypeError, AttributeError): + car_params = None + lead_veto_m = get_lead_veto_distance(car_params) lead_present = (bool(getattr(lead, "status", False)) - and float(getattr(lead, "dRel", float("inf"))) < LEAD_VETO_M + and float(getattr(lead, "dRel", float("inf"))) < lead_veto_m and float(getattr(lead, "vLead", float("inf"))) < v_ego + 2.0) curved_approach_scene = ( abs(float(getattr(self.starpilot_planner, "road_curvature", 0.0))) >= FORCE_STOP_CURVE_VETO_MAX_ROAD_CURVATURE diff --git a/tinygrad_repo/test/unit/test_usb_mmio_scalar.py b/tinygrad_repo/test/unit/test_usb_mmio_scalar.py deleted file mode 100644 index be216db31..000000000 --- a/tinygrad_repo/test/unit/test_usb_mmio_scalar.py +++ /dev/null @@ -1,54 +0,0 @@ -from tinygrad.runtime.support.usb import USBMMIOInterface - - -class FakeUSB: - def __init__(self): - self.calls = [] - - def pcie_mem_req(self, address, value=None, size=4): - self.calls.append(("scalar", address, value, size)) - return 0x11223344 if value is None else None - - def pcie_mem_read(self, address, size): - self.calls.append(("read", address, size)) - return bytes(size) - - def pcie_mem_write(self, address, data): - self.calls.append(("write", address, data)) - - -def test_scalar_mmio_uses_single_tlp(): - usb = FakeUSB() - mmio = USBMMIOInterface(usb, 0x1000, 0x100, "I") - - mmio[2] = 0xAABBCCDD - assert mmio[3] == 0x11223344 - - assert usb.calls == [ - ("scalar", 0x1008, 0xAABBCCDD, 4), - ("scalar", 0x100C, None, 4), - ] - - -def test_slice_mmio_keeps_streaming_path(): - usb = FakeUSB() - mmio = USBMMIOInterface(usb, 0x2000, 0x100, "I") - - mmio[0:2] = b"\x01\x02\x03\x04\x05\x06\x07\x08" - assert mmio[0:2] == bytes(8) - - assert usb.calls == [ - ("write", 0x2000, b"\x01\x02\x03\x04\x05\x06\x07\x08"), - ("read", 0x2000, 8), - ] - - -def test_scalar_mmio_falls_back_to_streaming_transport(): - class StreamingUSB: - def __init__(self): self.data = bytearray(4) - def pcie_mem_read(self, address, size): return self.data[address-0x3000:address-0x3000+size] - def pcie_mem_write(self, address, data): self.data[address-0x3000:address-0x3000+len(data)] = data - - mmio = USBMMIOInterface(StreamingUSB(), 0x3000, 4, "I") - mmio[0] = 0xAABBCCDD - assert mmio[0] == 0xAABBCCDD diff --git a/tinygrad_repo/tinygrad/helpers.py b/tinygrad_repo/tinygrad/helpers.py index 396ac6952..acc3af11a 100644 --- a/tinygrad_repo/tinygrad/helpers.py +++ b/tinygrad_repo/tinygrad/helpers.py @@ -1,7 +1,7 @@ from __future__ import annotations import time START_TIME = time.perf_counter() -import os, functools, platform, re, contextlib, operator, hashlib, pickle, sqlite3, tempfile, pathlib, string, ctypes, sys, gzip, getpass, gc +import os, functools, platform, re, contextlib, operator, hashlib, pickle, sqlite3, tempfile, pathlib, string, ctypes, sys, gzip, getpass, gc, io from collections import defaultdict import subprocess, shutil, math, types, copyreg, inspect, importlib, decimal, itertools, difflib from dataclasses import dataclass, field, replace @@ -492,7 +492,8 @@ def _decompress_zstd(data:bytes) -> bytes: from compression.zstd import decompress return decompress(data) from zstandard import ZstdDecompressor - return ZstdDecompressor().decompress(data) + with ZstdDecompressor().stream_reader(io.BytesIO(data)) as reader: + return reader.read() def fetch_fw(path:str, name:str, sha256:str) -> bytes: if (p:=pathlib.Path(f"/lib/firmware/{path}/{name}.zst")).is_file(): diff --git a/tinygrad_repo/tinygrad/runtime/support/usb.py b/tinygrad_repo/tinygrad/runtime/support/usb.py index a69d5b01a..b7e8b8186 100644 --- a/tinygrad_repo/tinygrad/runtime/support/usb.py +++ b/tinygrad_repo/tinygrad/runtime/support/usb.py @@ -135,9 +135,6 @@ class CustomASM24Controller: address = (bus << 24) | (dev << 19) | (fn << 16) | (byte_addr & 0xfff) return self.pcie_request(fmt_type, address, value, size) - def pcie_mem_req(self, address:int, value:int|None=None, size:int=4): - return self.pcie_request(0x60 if value is not None else 0x20, address, value, size) - def pcie_mem_write(self, address:int, data:bytes): """Streaming PCIe memory write via 0xF0 mode 1 + bulk OUT. Data is little-endian dwords on the wire.""" if not data: return @@ -186,31 +183,16 @@ class USBMMIOInterface(MMIOInterface): if isinstance(index, slice): return ((index.start or 0) * self.el_sz, ((index.stop or len(self))-(index.start or 0)) * self.el_sz) return (index * self.el_sz, self.el_sz) - def _scalar(self, off:int, size:int, value:int|None=None): - assert size in (1, 2, 4, 8), f"invalid scalar PCIe access size {size}" - if not hasattr(self.usb, "pcie_mem_req"): - if value is not None: - self.usb.pcie_mem_write(self.addr + off, value.to_bytes(size, "little")) - return - return int.from_bytes(self.usb.pcie_mem_read(self.addr + off, size), "little") - upper = 0 if size < 8 else self.usb.pcie_mem_req(self.addr + off + 4, value if value is None else value >> 32, 4) - lower = self.usb.pcie_mem_req(self.addr + off, value if value is None else value & 0xffffffff, min(size, 4)) - if value is None: return lower | (upper << 32) - def __getitem__(self, index): off, sz = self._off_from_index(index) if self.pcimem: - if not isinstance(index, slice): return self._scalar(off, sz) assert sz % 4 == 0 and off % 4 == 0, f"pcie_mem_read requires 4-byte aligned access, got off={off}, sz={sz}" data = self.usb.pcie_mem_read(self.addr + off, sz) else: data = self.usb.scsi_read(sz) if self.addr == 0xf000 else self.usb.read(self.addr + off, sz) return int.from_bytes(data, "little") if sz == self.el_sz else data def __setitem__(self, index, data): - off, sz = self._off_from_index(index) - if self.pcimem and not isinstance(index, slice) and isinstance(data, int): - self._scalar(off, sz, data) - return + off, _ = self._off_from_index(index) data = struct.pack(self.fmt, data) if isinstance(data, int) else bytes(data) if not self.pcimem: self.usb.scsi_write(data) if self.addr == 0xf000 else self.usb.write(self.addr + off, data) else: self.usb.pcie_mem_write(self.addr+off, data)