From 2ef545b987ad25b3a2663ec177f7706a93ba3baa Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Tue, 25 Aug 2026 10:00:34 -0500 Subject: [PATCH] gmornin --- common/params_keys.h | 1 - .../opendbc/car/hyundai/carcontroller.py | 25 +- opendbc_repo/opendbc/car/hyundai/carstate.py | 12 +- .../opendbc/car/hyundai/hyundaican.py | 5 +- .../opendbc/car/hyundai/hyundaicanfd.py | 30 ++- .../opendbc/car/hyundai/tests/test_hyundai.py | 112 +++++++-- opendbc_repo/opendbc/car/hyundai/values.py | 1 + opendbc_repo/opendbc/car/interfaces.py | 7 + .../opendbc/car/subaru/tests/test_subaru.py | 12 +- opendbc_repo/opendbc/car/subaru/values.py | 5 + .../opendbc/car/tests/test_car_interfaces.py | 1 - .../opendbc/safety/modes/hyundai_canfd.h | 61 ++++- .../safety/tests/test_hyundai_canfd.py | 46 +++- panda/board/obj/gitversion.h | 2 +- panda/board/obj/version | 2 +- selfdrive/car/cruise.py | 4 - selfdrive/car/tests/test_cruise_speed.py | 33 --- selfdrive/car/tests/test_models.py | 1 - selfdrive/controls/lib/latcontrol_torque.py | 6 + .../controls/lib/latcontrol_vehicle_tunes.py | 25 +- .../controls/lib/longcontrol_vehicle_tunes.py | 9 +- .../controls/lib/longitudinal_planner.py | 50 +++- .../lib/longitudinal_vehicle_tunes.py | 120 +++++++++ selfdrive/controls/tests/test_latcontrol.py | 31 +++ selfdrive/controls/tests/test_longcontrol.py | 11 + .../tests/test_longitudinal_planner.py | 48 ++++ .../controls/tests/test_starpilot_vcruise.py | 44 +++- .../ui/layouts/settings/starpilot/lateral.py | 118 --------- .../ui/mici/layouts/settings/developer.py | 22 +- selfdrive/ui/translations/main_uk.ts | 4 - .../common/assets/device_settings_layout.json | 232 ++++++++++++++++++ starpilot/common/favorite_slots.py | 3 + starpilot/common/safe_mode.py | 1 - starpilot/common/starpilot_variables.py | 1 - starpilot/common/tests/test_favorite_slots.py | 12 + starpilot/controls/lib/starpilot_vcruise.py | 26 +- .../components/tools/device_settings.js | 14 +- .../tests/test_device_settings_layout.py | 60 ++++- .../tests/test_navigation_params.py | 16 +- system/manager/launch_param_migrations.py | 21 +- system/manager/manager.py | 4 +- .../test/test_launch_param_migrations.py | 11 + system/manager/test/test_manager.py | 4 + tools/StarPilot/feasibleparams.txt | 1 - 44 files changed, 977 insertions(+), 277 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index a6783491a..9111c2443 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -549,7 +549,6 @@ inline static std::unordered_map keys = { {"RelaxedJerkDeceleration", {PERSISTENT, FLOAT, "100.0", "100.0", 3}}, {"RelaxedJerkSpeed", {PERSISTENT, FLOAT, "100.0", "100.0", 3}}, {"RelaxedJerkSpeedDecrease", {PERSISTENT, FLOAT, "100.0", "100.0", 3}}, - {"ReverseCruise", {PERSISTENT, BOOL, "0", "0", 1}}, {"RivianAngleControl", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}}, {"RivianAngleSaturated", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL, "0", "0"}}, {"RivianToiRecoveryFailed", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL, "0", "0"}}, diff --git a/opendbc_repo/opendbc/car/hyundai/carcontroller.py b/opendbc_repo/opendbc/car/hyundai/carcontroller.py index 670a150c0..eab034279 100644 --- a/opendbc_repo/opendbc/car/hyundai/carcontroller.py +++ b/opendbc_repo/opendbc/car/hyundai/carcontroller.py @@ -9,7 +9,7 @@ from opendbc.car.common.conversions import Conversions as CV from opendbc.car.hyundai import hyundaicanfd, hyundaican from opendbc.car.hyundai.hyundaicanfd import CanBus from opendbc.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \ - CANFD_RADAR_LIVE_LONGITUDINAL_CAR, kia_ev6_gt_line_longitudinal_tuning, \ + CANFD_RADAR_LIVE_LONGITUDINAL_CAR, CANFD_ALT_BUTTONS_RESUME_CAR, kia_ev6_gt_line_longitudinal_tuning, \ KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID from opendbc.car.interfaces import CarControllerBase from opendbc.car.vehicle_model import VehicleModel @@ -789,6 +789,7 @@ class CarController(CarControllerBase): # TODO: unclear if this is needed jerk = 3.0 if actuators.longControlState == LongCtrlState.pid else 1.0 use_fca = self.CP.flags & HyundaiFlags.USE_FCA.value + main_cruise_enabled = getattr(CS, "main_cruise_on", False) if getattr(CS, "main_cruise_tracking", False) else True if blended_hda2: stopping = stopping and CS.out.vEgoRaw < 0.1 can_sends.extend(hyundaican.create_acc_commands_can_canfd_blended_hda2( @@ -804,7 +805,8 @@ class CarController(CarControllerBase): else: can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled, accel, jerk, int(self.frame / 2), hud_control, set_speed_in_units, stopping, - CC.cruiseControl.override, use_fca, self.CP)) + CC.cruiseControl.override, use_fca, self.CP, + main_cruise_enabled)) # 20 Hz LFA MFA message if self.frame % 5 == 0 and (self.CP.flags & HyundaiFlags.SEND_LFA.value or (self.long_active_ecu and blended_hda2)): @@ -866,14 +868,7 @@ class CarController(CarControllerBase): steering_msg_active, apply_torque, apply_angle, CS.stock_lfa_msg if preserve_stock_lfa_status else None, CS.stock_lkas_msg if preserve_stock_lkas else None, - lka_icon=lka_icon, - send_lfa_status=self.ecu_disable_failed and - self.CP.carFingerprint == CAR.KIA_EV9)) - elif self.ecu_disable_failed and self.CP.carFingerprint == CAR.KIA_EV9: - can_sends.extend(hyundaicanfd.create_steering_messages( - self.packer, self.CP, self.CAN, CC.enabled, False, 0.0, 0.0, - CS.stock_lfa_msg, lka_icon=lka_icon, send_lfa_status=True, lfa_only=True, - )) + lka_icon=lka_icon)) direct_steering_active = ccnc_angle_long and drive_gear and CC.latActive and self.direct_angle_request_allowed and not CS.angle_steering_fault inactive_steering_angle = float(np.clip(CS.angle_steering_angle, -self.params.ANGLE_LIMITS.STEER_ANGLE_MAX, @@ -1047,9 +1042,13 @@ class CarController(CarControllerBase): # cruise standstill resume elif CC.cruiseControl.resume: - if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS: - # TODO: resume for alt button cars - pass + if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS and self.CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR: + for _ in range(20): + can_sends.append(hyundaicanfd.create_buttons( + self.packer, self.CP, self.CAN, (CS.buttons_counter + 1) % 0x100, + Buttons.RES_ACCEL, base_values=CS.cruise_buttons_msg, + )) + self.last_button_frame = self.frame else: for _ in range(20): can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, CS.buttons_counter + 1, Buttons.RES_ACCEL)) diff --git a/opendbc_repo/opendbc/car/hyundai/carstate.py b/opendbc_repo/opendbc/car/hyundai/carstate.py index d5fe75a77..773962f8d 100644 --- a/opendbc_repo/opendbc/car/hyundai/carstate.py +++ b/opendbc_repo/opendbc/car/hyundai/carstate.py @@ -9,6 +9,7 @@ from opendbc.car.common.conversions import Conversions as CV from opendbc.car.hyundai.hyundaicanfd import CanBus from opendbc.car.hyundai.values import HyundaiFlags, HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags, CAR, DBC, Buttons, CarControllerParams, \ CANFD_ANGLE_LONGITUDINAL_CAR, CANFD_CORNER_RADAR_BSM_CAR, \ + CANFD_ALT_BUTTONS_RESUME_CAR, \ hyundai_cancel_button_enables_cruise, ALT_BUS_LDA_BUTTON_CARS, ALT_BUS_LDA_BUTTON_SWL_STAT_CARS from opendbc.car.interfaces import CarStateBase @@ -132,6 +133,7 @@ class CarState(CarStateBase): self.is_metric = False self.buttons_counter = 0 self.main_cruise_on = False + self.main_cruise_tracking = bool(getattr(FPCP, "flags", 0) & HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING) self.cruise_info = {} self.msg_161 = {} @@ -558,7 +560,9 @@ class CarState(CarStateBase): self.main_buttons.extend(cp.vl_all[self.cruise_btns_msg_canfd]["ADAPTIVE_CRUISE_MAIN_BTN"]) self.lda_button = cp.vl[self.cruise_btns_msg_canfd]["LDA_BTN"] self.left_paddle = 0 - if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6: + if self.CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR: + self.cruise_buttons_msg = copy.copy(cp.vl[self.cruise_btns_msg_canfd]) + elif self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6: self.cruise_buttons_msg = copy.copy(cp.vl["CRUISE_BUTTONS"]) self.left_paddle = cp.vl["CRUISE_BUTTONS"]["LEFT_PADDLE"] self.buttons_counter = cp.vl[self.cruise_btns_msg_canfd]["COUNTER"] @@ -591,7 +595,7 @@ class CarState(CarStateBase): *create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise}), *create_button_events(self.lda_button, prev_lda_button, {1: ButtonType.lkas}), *create_button_events(self.left_paddle, prev_left_paddle, {1: ButtonType.altButton2})] - if self.CP.openpilotLongitudinalControl and self.CP.carFingerprint == CAR.KIA_EV9: + if self.CP.openpilotLongitudinalControl and (self.CP.carFingerprint == CAR.KIA_EV9 or self.main_cruise_tracking): ret.cruiseState.available = self.update_main_cruise(ret) ret.blockPcmEnable = not self.recent_button_interaction() @@ -618,7 +622,9 @@ class CarState(CarStateBase): def get_can_parsers_canfd(self, CP): msgs = [] cam_msgs = [] - if not (CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS): + if CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR: + msgs.append(("CRUISE_BUTTONS_ALT", 50)) + elif not (CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS): # The EV9 can stop publishing this during the non-ECU-disabled startup # state. Keep decoding it when present without making CAN invalid. msgs += [ diff --git a/opendbc_repo/opendbc/car/hyundai/hyundaican.py b/opendbc_repo/opendbc/car/hyundai/hyundaican.py index 203352146..8afb033bc 100644 --- a/opendbc_repo/opendbc/car/hyundai/hyundaican.py +++ b/opendbc_repo/opendbc/car/hyundai/hyundaican.py @@ -284,11 +284,12 @@ def create_acc_commands_can_canfd_blended_hda2(packer, enabled, accel, accel_las return commands -def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed, stopping, long_override, use_fca, CP): +def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed, stopping, long_override, use_fca, CP, + main_cruise_enabled=True): commands = [] scc11_values = { - "MainMode_ACC": 1, + "MainMode_ACC": int(bool(main_cruise_enabled)), "TauGapSet": hud_control.leadDistanceBars, "VSetDis": set_speed if enabled else 0, "AliveCounterACC": idx % 0x10, diff --git a/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py b/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py index 911f0c1d8..a17bc7a9e 100644 --- a/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py +++ b/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py @@ -3,7 +3,7 @@ import numpy as np from opendbc.car import CanBusBase, CanData from opendbc.car.common.conversions import Conversions as CV from opendbc.car.crc import CRC16_XMODEM -from opendbc.car.hyundai.values import HyundaiFlags, CAR +from opendbc.car.hyundai.values import HyundaiFlags, CAR, CANFD_ALT_BUTTONS_RESUME_CAR def _set_value(msg: bytearray, sig, ival: int) -> None: @@ -152,17 +152,14 @@ def create_angle_adas_cmd(packer, CAN, apply_angle: float, lat_active: bool, tor def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque, apply_angle, - lfa_base_values=None, lkas_base_values=None, lka_icon=None, - send_lfa_status=False, lfa_only=False): + lfa_base_values=None, lkas_base_values=None, lka_icon=None): if lka_icon is None: lka_icon = 2 if enabled else 1 if CP.carFingerprint == CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN and CP.flags & HyundaiFlags.CANFD_LKA_STEERING: ret = [] - if CP.openpilotLongitudinalControl or send_lfa_status: + if CP.openpilotLongitudinalControl: ret.append(_create_gv70_lka_status_msg(packer, CAN, "LFA", CAN.ECAN, enabled, lat_active, apply_torque)) - if lfa_only: - return ret ret.append(_create_gv70_lka_status_msg(packer, CAN, "LKAS", CAN.ACAN, enabled, lat_active, apply_torque)) return ret @@ -258,10 +255,8 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque, ret = [] if CP.flags & HyundaiFlags.CANFD_LKA_STEERING: lkas_msg = "LKAS_ALT" if CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT else "LKAS" - if (CP.openpilotLongitudinalControl and not CP.flags & HyundaiFlags.CAN_CANFD_BLENDED) or send_lfa_status: + if CP.openpilotLongitudinalControl and not CP.flags & HyundaiFlags.CAN_CANFD_BLENDED: ret.append(packer.make_can_msg("LFA", CAN.ECAN, lfa_values)) - if lfa_only: - return ret ret.append(packer.make_can_msg(lkas_msg, CAN.ACAN, lkas_values)) else: if CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING: @@ -311,16 +306,27 @@ def create_suppress_lfa(packer, CAN, lfa_block_msg, lka_steering_alt): def create_buttons(packer, CP, CAN, cnt, btn=0, base_values=None, left_paddle=False, right_paddle=False): - values = {k: v for k, v in base_values.items() if k not in ("_CHECKSUM", "COUNTER")} if base_values else {} + values = {k: v for k, v in base_values.items() if k not in ("CHECKSUM", "_CHECKSUM", "COUNTER")} if base_values else {} values.update({ "COUNTER": cnt, "SET_ME_1": 1, "CRUISE_BUTTONS": btn, - "LEFT_PADDLE": int(left_paddle), - "RIGHT_PADDLE": int(right_paddle), }) + if not (CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS and CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR): + values.update({ + "LEFT_PADDLE": int(left_paddle), + "RIGHT_PADDLE": int(right_paddle), + }) bus = CAN.ECAN if CP.flags & HyundaiFlags.CANFD_LKA_STEERING else CAN.CAM + if CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS and CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR: + address, dat, bus = packer.make_can_msg("CRUISE_BUTTONS_ALT", bus, values) + dat = bytearray(dat) + checksum = hkg_can_fd_checksum(address, None, dat) + dat[0] = checksum & 0xFF + dat[1] = (checksum >> 8) & 0xFF + return address, bytes(dat), bus + return packer.make_can_msg("CRUISE_BUTTONS", bus, values) diff --git a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py index b3aa43aaf..bf3849b2e 100644 --- a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py +++ b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py @@ -25,7 +25,7 @@ from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, dec get_canfd_cruise_available from opendbc.car.hyundai.interface import CarInterface, KIA_EV9_ACCEL_MAX from opendbc.car.hyundai import hyundaican, hyundaicanfd -from opendbc.car.hyundai.hyundaicanfd import CanBus +from opendbc.car.hyundai.hyundaicanfd import CanBus, hkg_can_fd_checksum from opendbc.car.hyundai.radar_interface import MRREVO14F_RADAR_START_ADDR, MRR30_RADAR_START_ADDR, MRR35_RADAR_START_ADDR, \ RADAR_START_ADDR, get_radar_track_config from opendbc.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, DATELESS_FUZZY_CARS, \ @@ -522,6 +522,27 @@ class TestHyundaiFingerprint: k4_cp = CarInterface.get_params(CAR.KIA_K4_2025, fingerprint, [], False, False, False, None) assert not (k4_cp.flags & HyundaiFlags.CANFD_ALT_BUTTONS) + @pytest.mark.parametrize("candidate", (CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN)) + def test_carnival_hda1_resume_uses_alternate_button_frame(self, candidate): + fingerprint = gen_empty_fingerprint() + fingerprint[0] = {0x1AA: 16} + CP = CarInterface.get_params(candidate, fingerprint, [], False, False, False, None) + assert CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS + assert not CP.openpilotLongitudinalControl + assert "CRUISE_BUTTONS_ALT" in { + state.name for state in CarState(CP, None).get_can_parsers(CP)[Bus.pt].message_states.values() + } + + packer = CANPacker(DBC[CP.carFingerprint][Bus.pt]) + address, dat, bus = hyundaicanfd.create_buttons( + packer, CP, CanBus(CP), 0x41, Buttons.RES_ACCEL, + base_values={"SET_ME_1": 1, "DISTANCE_UNIT": 0}, + ) + assert (address, bus) == (0x1AA, CanBus(CP).CAM) + assert dat[2] == 0x41 + assert (dat[4] >> 4) & 0x7 == Buttons.RES_ACCEL + assert int.from_bytes(dat[:2], "little") == hkg_can_fd_checksum(address, None, bytearray(dat)) + def test_ioniq_6_hda1_layout_stays_non_lka(self): fingerprint = gen_empty_fingerprint() fingerprint[1] = {0x100: 8, 0x110: 8} @@ -706,14 +727,18 @@ class TestHyundaiFingerprint: assert combined_safety_param & HyundaiSafetyFlags.LONG assert combined_safety_param & HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE - @pytest.mark.parametrize("candidate", (CAR.HYUNDAI_ELANTRA_2021, CAR.HYUNDAI_SONATA_HYBRID)) - def test_legacy_hyundai_long_does_not_gate_availability_on_main_cruise(self, candidate): + @pytest.mark.parametrize("candidate, tracks_main_cruise", ( + (CAR.HYUNDAI_ELANTRA_2021, False), + (CAR.HYUNDAI_ELANTRA_HEV_2024, True), + (CAR.HYUNDAI_SONATA_HYBRID, True), + )) + def test_legacy_hyundai_long_main_cruise_tracking_is_vehicle_specific(self, candidate, tracks_main_cruise): toggles = get_test_toggles() CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], True, False, False, toggles) FPCP = CarInterface.get_starpilot_params( candidate, gen_empty_fingerprint(), [], CP, toggles, ) - assert not (FPCP.flags & HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING) + assert bool(FPCP.flags & HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING) is tracks_main_cruise ioniq_cp = CarInterface.get_params(CAR.HYUNDAI_IONIQ_6, gen_empty_fingerprint(), [], True, False, False, toggles) ioniq_fpcp = CarInterface.get_starpilot_params( @@ -2452,6 +2477,22 @@ class TestHyundaiFingerprint: assert parser.vl["SCC14"]["ComfortBandLower"] == pytest.approx(0.0) assert parser.vl["SCC14"]["JerkLowerLimit"] == pytest.approx(5.0) + def test_can_acc_commands_follow_sonata_main_cruise_state(self): + CP = CarParams.new_message() + CP.carFingerprint = CAR.HYUNDAI_SONATA_HYBRID + + packer = CANPacker(DBC[CP.carFingerprint][Bus.pt]) + parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("SCC11", 0)], 0) + + msgs = hyundaican.create_acc_commands(packer, enabled=False, accel=0.0, upper_jerk=1.0, idx=3, + hud_control=SimpleNamespace(leadDistanceBars=3, leadVisible=False), set_speed=42, + stopping=False, long_override=False, use_fca=False, CP=CP, + main_cruise_enabled=False) + parser.update([(1, msgs)]) + + assert parser.can_valid + assert parser.vl["SCC11"]["MainMode_ACC"] == 0 + def test_can_acc_commands_use_enabled_fca_status(self): CP = CarParams.new_message() CP.carFingerprint = CAR.GENESIS_G90 @@ -2670,26 +2711,47 @@ class TestHyundaiFingerprint: ("LKAS", can_bus.ACAN), ] - def test_ev9_fallback_keeps_lfa_status_without_longitudinal_control(self): + def test_ev9_fallback_active_lateral_uses_lkas_without_injecting_lfa(self): CP = CarParams.new_message() CP.carFingerprint = CAR.KIA_EV9 CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CCNC | HyundaiFlags.CANFD_ANGLE_STEERING | HyundaiFlags.CANFD_LKA_STEERING | HyundaiFlags.CANFD_LKA_STEERING_ALT) - CP.openpilotLongitudinalControl = False + CP.openpilotLongitudinalControl = True + controller = CarController(DBC[CP.carFingerprint], CP) + controller.frame = 1 + controller.ecu_disable_failed = True + controller.long_active_ecu = False + CP.openpilotLongitudinalControl = False packer = CANPacker(DBC[CP.carFingerprint][Bus.pt]) can_bus = CanBus(CP) - msgs = hyundaicanfd.create_steering_messages( - packer, CP, can_bus, True, True, 0.44, -31.5, send_lfa_status=True, + cc = SimpleNamespace( + enabled=True, + latActive=True, + actuators=SimpleNamespace(longControlState=LongCtrlState.off), + cruiseControl=SimpleNamespace(cancel=False, resume=False), + leftBlinker=False, + rightBlinker=False, + hudControl=SimpleNamespace(), + ) + cs = SimpleNamespace( + stock_lfa_msg={}, + stock_lkas_msg={}, + out=SimpleNamespace( + standstill=False, + steeringAngleDeg=-31.5, + gearShifter=structs.CarState.GearShifter.drive, + ), ) + msgs = controller.create_canfd_msgs(0, True, 0.44, -31.5, 0.0, 0.0, False, + cc.hudControl, cs, cc, get_test_toggles(), lka_icon=2, lfa_icon=2) assert [(packer.dbc.addr_to_msg[addr].name, bus) for addr, _, bus in msgs] == [ - ("LFA", can_bus.ECAN), ("LKAS_ALT", can_bus.ACAN), ] - def test_ev9_fallback_lfa_only_does_not_send_lkas_at_standstill(self): + def test_ev9_fallback_does_not_inject_lfa_while_parked(self): CP = CarParams.new_message() CP.carFingerprint = CAR.KIA_EV9 CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CCNC | @@ -2697,15 +2759,31 @@ class TestHyundaiFingerprint: HyundaiFlags.CANFD_LKA_STEERING_ALT) CP.openpilotLongitudinalControl = False - packer = CANPacker(DBC[CP.carFingerprint][Bus.pt]) - can_bus = CanBus(CP) - msgs = hyundaicanfd.create_steering_messages( - packer, CP, can_bus, True, False, 0.0, 0.0, send_lfa_status=True, lfa_only=True, + controller = CarController(DBC[CP.carFingerprint], CP) + controller.ecu_disable_failed = True + cc = SimpleNamespace( + enabled=False, + latActive=False, + actuators=SimpleNamespace(longControlState=LongCtrlState.off), + cruiseControl=SimpleNamespace(cancel=False, resume=False), + leftBlinker=False, + rightBlinker=False, + hudControl=SimpleNamespace(), + ) + cs = SimpleNamespace( + stock_lfa_msg={}, + stock_lkas_msg={}, + out=SimpleNamespace( + standstill=True, + steeringAngleDeg=0.0, + gearShifter=structs.CarState.GearShifter.park, + ), ) - assert [(packer.dbc.addr_to_msg[addr].name, bus) for addr, _, bus in msgs] == [ - ("LFA", can_bus.ECAN), - ] + msgs = controller.create_canfd_msgs(0, False, 0.0, 0.0, 0.0, 0.0, False, + cc.hudControl, cs, cc, get_test_toggles(), lka_icon=1, lfa_icon=1) + + assert not [msg for msg in msgs if msg[0] in (0x110, 0x12A)] def test_kia_ev6_lkas_helper_preserves_stock_camera_fields_with_stock_long(self): CP = CarParams.new_message() diff --git a/opendbc_repo/opendbc/car/hyundai/values.py b/opendbc_repo/opendbc/car/hyundai/values.py index df10d1df3..a9f6eef43 100644 --- a/opendbc_repo/opendbc/car/hyundai/values.py +++ b/opendbc_repo/opendbc/car/hyundai/values.py @@ -1183,6 +1183,7 @@ CANFD_SECURITYACCESS_CAR = { } CANFD_UNSUPPORTED_LONGITUDINAL_CAR = CAR.with_flags(HyundaiFlags.CANFD_NO_RADAR_DISABLE) - CANFD_SECURITYACCESS_CAR # TODO: merge with UNSUPPORTED_LONGITUDINAL_CAR CANFD_ANGLE_LONGITUDINAL_CAR = {CAR.KIA_EV9, CAR.HYUNDAI_IONIQ_5_PE} +CANFD_ALT_BUTTONS_RESUME_CAR = {CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN} CANFD_CORNER_RADAR_BSM_CAR = {CAR.HYUNDAI_IONIQ_6, CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9} CANFD_RADAR_LIVE_LONGITUDINAL_CAR = { CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_IONIQ_5_PE, CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6, CAR.KIA_EV9, CAR.GENESIS_GV60_EV_1ST_GEN, diff --git a/opendbc_repo/opendbc/car/interfaces.py b/opendbc_repo/opendbc/car/interfaces.py index c8f4a5c59..46baf0172 100644 --- a/opendbc_repo/opendbc/car/interfaces.py +++ b/opendbc_repo/opendbc/car/interfaces.py @@ -245,6 +245,13 @@ class CarInterfaceBase(ABC): fp_ret.pcmCruiseSpeed = False CP.openpilotLongitudinalControl = True + # These classic Hyundai hybrids need their stock ACC main state tracked while + # using OP long. Their cluster/EPS state becomes inconsistent when AOL remains + # active after the physical ACC main state changes. + if candidate in (HYUNDAI.HYUNDAI_SONATA_HYBRID, HYUNDAI.HYUNDAI_ELANTRA_HEV_2024) and \ + CP.openpilotLongitudinalControl: + fp_ret.flags |= HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING.value + hyundai_has_lda_button = not (CP.flags & HyundaiFlags.CANFD) and ( 0x391 in fingerprint[0] or 0x50C in fingerprint[0] or diff --git a/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py b/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py index 0c2535ef1..3a3f29ee1 100644 --- a/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py +++ b/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py @@ -429,14 +429,24 @@ def test_angle_controller_tracks_driver_override(): CP = CarInterface.get_non_essential_params(CAR.SUBARU_CROSSTREK_2025) controller = CarController({}, CP) CC = SimpleNamespace(latActive=True, actuators=SimpleNamespace(steeringAngleDeg=15.0)) - CS = SimpleNamespace(out=SimpleNamespace(vEgoRaw=15.0, steeringAngleDeg=2.0, steeringTorque=250.0)) + CS = SimpleNamespace(out=SimpleNamespace(vEgoRaw=15.0, steeringAngleDeg=2.0, steeringTorque=175.0)) msg = controller.lateral_angle(CC, CS) assert controller.driver_override + assert controller.p.STEER_OVERRIDE_TORQUE_HIGH == 150 + assert controller.p.STEER_OVERRIDE_TORQUE_LOW == 100 assert controller.apply_steer_last == CS.out.steeringAngleDeg assert msg[0] == 0x124 + CS.out.steeringTorque = 125.0 + controller.lateral_angle(CC, CS) + assert controller.driver_override + + CS.out.steeringTorque = 75.0 + controller.lateral_angle(CC, CS) + assert not controller.driver_override + def test_ascent_angle_controller_uses_fixed_angle_rate_limits(): CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023) diff --git a/opendbc_repo/opendbc/car/subaru/values.py b/opendbc_repo/opendbc/car/subaru/values.py index ea1192ad5..9b0b25398 100644 --- a/opendbc_repo/opendbc/car/subaru/values.py +++ b/opendbc_repo/opendbc/car/subaru/values.py @@ -37,6 +37,11 @@ class CarControllerParams: self.STEER_OVERRIDE_TORQUE_HIGH = 200 self.STEER_OVERRIDE_TORQUE_LOW = 150 + # Crosstrek 2025 reports manual parking-lot inputs below the generic handoff threshold. + if CP.carFingerprint == CAR.SUBARU_CROSSTREK_2025: + self.STEER_OVERRIDE_TORQUE_HIGH = 150 + self.STEER_OVERRIDE_TORQUE_LOW = 100 + if CP.flags & SubaruFlags.GLOBAL_GEN2: # TODO: lower rate limits, this reaches min/max in 0.5s which negatively affects tuning self.STEER_MAX = 1500 diff --git a/opendbc_repo/opendbc/car/tests/test_car_interfaces.py b/opendbc_repo/opendbc/car/tests/test_car_interfaces.py index 3e05208a3..5676c2ccc 100644 --- a/opendbc_repo/opendbc/car/tests/test_car_interfaces.py +++ b/opendbc_repo/opendbc/car/tests/test_car_interfaces.py @@ -74,7 +74,6 @@ def get_test_starpilot_toggles() -> SimpleNamespace: disable_openpilot_long=False, force_fingerprint=False, lock_doors=False, - reverse_cruise_increase=False, sng_hack=False, subaru_sng=False, subaru_sng_manual_parking_brake=False, diff --git a/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h b/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h index d2be6f95b..c47ff06f0 100644 --- a/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h +++ b/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h @@ -6,6 +6,9 @@ #define HYUNDAI_CANFD_CRUISE_BUTTON_TX_MSGS(bus) \ {0x1CF, bus, 8, .check_relay = false}, /* CRUISE_BUTTON */ \ +#define HYUNDAI_CANFD_ALT_CRUISE_BUTTON_TX_MSGS(bus) \ + {0x1AA, bus, 16, .check_relay = false}, /* CRUISE_BUTTONS_ALT */ \ + #define HYUNDAI_CANFD_LKA_STEERING_COMMON_TX_MSGS(a_can, e_can) \ HYUNDAI_CANFD_CRUISE_BUTTON_TX_MSGS(e_can) \ {0x50, a_can, 16, .check_relay = (a_can) == 0}, /* LKAS */ \ @@ -291,8 +294,8 @@ static bool hyundai_canfd_tx_hook(const CANPacket_t *msg) { } // cruise buttons check - if (msg->addr == 0x1cfU) { - int button = msg->data[2] & 0x7U; + if ((msg->addr == 0x1cfU) || (hyundai_canfd_alt_buttons && (msg->addr == 0x1aaU))) { + int button = (msg->addr == 0x1aaU) ? ((msg->data[4] >> 4U) & 0x7U) : (msg->data[2] & 0x7U); bool is_cancel = (button == HYUNDAI_BTN_CANCEL); bool is_resume = (button == HYUNDAI_BTN_RESUME); bool is_set = (button == HYUNDAI_BTN_SET); @@ -427,11 +430,6 @@ static safety_config hyundai_canfd_init(uint16_t param) { {0x1DA, 1, 32, .check_relay = false}, // ADRV_0x1da }; - static const CanMsg HYUNDAI_CANFD_CCNC_ANGLE_FALLBACK_TX_MSGS[] = { - HYUNDAI_CANFD_LKA_STEERING_ALT_COMMON_TX_MSGS(0, 1) - {0x12A, 1, 16, .check_relay = false}, // LFA status - }; - static const CanMsg HYUNDAI_CANFD_LFA_STEERING_TX_MSGS[] = { HYUNDAI_CANFD_CRUISE_BUTTON_TX_MSGS(2) HYUNDAI_CANFD_LFA_STEERING_COMMON_TX_MSGS(0) @@ -455,6 +453,12 @@ static safety_config hyundai_canfd_init(uint16_t param) { HYUNDAI_CANFD_SCC_CONTROL_COMMON_TX_MSGS(0, (longitudinal)) \ {0x160, 0, 16, .check_relay = (longitudinal)}, /* ADRV_0x160 */ \ +#define HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_ALT_BUTTONS_TX_MSGS(longitudinal) \ + HYUNDAI_CANFD_ALT_CRUISE_BUTTON_TX_MSGS(2) \ + HYUNDAI_CANFD_LFA_STEERING_COMMON_TX_MSGS(0) \ + HYUNDAI_CANFD_SCC_CONTROL_COMMON_TX_MSGS(0, (longitudinal)) \ + {0x160, 0, 16, .check_relay = (longitudinal)}, /* ADRV_0x160 */ \ + #define HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_CCNC_TX_MSGS(longitudinal) \ HYUNDAI_CANFD_CRUISE_BUTTON_TX_MSGS(2) \ HYUNDAI_CANFD_LFA_STEERING_COMMON_TX_MSGS(0) \ @@ -464,6 +468,15 @@ static safety_config hyundai_canfd_init(uint16_t param) { {0x7C4, 2, 8, .check_relay = true}, /* camera support frame */ \ {0xEA, 2, 24, .check_relay = true}, /* MDPS support frame */ \ +#define HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_CCNC_ALT_BUTTONS_TX_MSGS(longitudinal) \ + HYUNDAI_CANFD_ALT_CRUISE_BUTTON_TX_MSGS(2) \ + HYUNDAI_CANFD_LFA_STEERING_COMMON_TX_MSGS(0) \ + HYUNDAI_CANFD_SCC_CONTROL_COMMON_TX_MSGS(0, (longitudinal)) \ + {0x161, 0, 32, .check_relay = true}, /* CCNC_0x161 */ \ + {0x162, 0, 32, .check_relay = true}, /* CCNC_0x162 */ \ + {0x7C4, 2, 8, .check_relay = true}, /* camera support frame */ \ + {0xEA, 2, 24, .check_relay = true}, /* MDPS support frame */ \ + hyundai_common_init(param); gen_crc_lookup_table_16(0x1021, hyundai_canfd_crc_lut); @@ -527,7 +540,19 @@ static safety_config hyundai_canfd_init(uint16_t param) { if (hyundai_camera_scc) { if (hyundai_ccnc) { - SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_ccnc_tx_msgs, ret); + if (hyundai_canfd_alt_buttons) { + static CanMsg hyundai_canfd_lfa_steering_camera_scc_ccnc_alt_buttons_tx_msgs[] = { + HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_CCNC_ALT_BUTTONS_TX_MSGS(true) + }; + SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_ccnc_alt_buttons_tx_msgs, ret); + } else { + SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_ccnc_tx_msgs, ret); + } + } else if (hyundai_canfd_alt_buttons) { + static CanMsg hyundai_canfd_lfa_steering_camera_scc_alt_buttons_tx_msgs[] = { + HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_ALT_BUTTONS_TX_MSGS(true) + }; + SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_alt_buttons_tx_msgs, ret); } else { SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_tx_msgs, ret); } @@ -555,9 +580,7 @@ static safety_config hyundai_canfd_init(uint16_t param) { } else { SET_RX_CHECKS(hyundai_canfd_lka_steering_rx_checks, ret); } - if (hyundai_ccnc && hyundai_canfd_angle_steering && hyundai_canfd_lka_steering_alt) { - SET_TX_MSGS(HYUNDAI_CANFD_CCNC_ANGLE_FALLBACK_TX_MSGS, ret); - } else if (hyundai_canfd_lka_steering_alt) { + if (hyundai_canfd_lka_steering_alt) { if (hyundai_canfd_alt_buttons) { SET_TX_MSGS(HYUNDAI_CANFD_LKA_STEERING_ALT_ALT_BUTTONS_TX_MSGS, ret); } else { @@ -613,8 +636,22 @@ static safety_config hyundai_canfd_init(uint16_t param) { HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_CCNC_TX_MSGS(false) }; + static CanMsg hyundai_canfd_lfa_steering_camera_scc_alt_buttons_tx_msgs[] = { + HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_ALT_BUTTONS_TX_MSGS(false) + }; + + static CanMsg hyundai_canfd_lfa_steering_camera_scc_ccnc_alt_buttons_tx_msgs[] = { + HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_CCNC_ALT_BUTTONS_TX_MSGS(false) + }; + if (hyundai_ccnc) { - SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_ccnc_tx_msgs, ret); + if (hyundai_canfd_alt_buttons) { + SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_ccnc_alt_buttons_tx_msgs, ret); + } else { + SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_ccnc_tx_msgs, ret); + } + } else if (hyundai_canfd_alt_buttons) { + SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_alt_buttons_tx_msgs, ret); } else { SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_tx_msgs, ret); } diff --git a/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py b/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py index 1d3771758..c9400dece 100755 --- a/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py +++ b/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py @@ -459,12 +459,45 @@ class TestHyundaiCanfdAltButtonFlagIsolation(unittest.TestCase): self.safety.safety_rx_hook(self._button_msg(lka=True)) self.safety.safety_rx_hook(self._button_msg()) self.assertTrue(self.safety.get_lkas_on()) - self.safety.safety_rx_hook(self._button_msg(main=True)) self.safety.safety_rx_hook(self._button_msg()) self.assertTrue(self.safety.get_lkas_on()) +class TestHyundaiCanfdCcncAltButtonResume(unittest.TestCase): + TX_MSGS = [[0x1AA, 2]] + + def setUp(self): + self.packer = CANPackerSafety("hyundai_canfd_generated") + self.safety = libsafety_py.libsafety + self.safety.set_safety_hooks( + CarParams.SafetyModel.hyundaiCanfd, + HyundaiSafetyFlags.CCNC | HyundaiSafetyFlags.CAMERA_SCC | HyundaiSafetyFlags.CANFD_ALT_BUTTONS, + ) + self.safety.init_tests() + + def _resume_msg(self): + return self.packer.make_can_msg_safety( + "CRUISE_BUTTONS_ALT", 2, {"CRUISE_BUTTONS": Buttons.RESUME}, + ) + + def test_resume_allowed_only_when_controls_are_allowed(self): + self.safety.set_controls_allowed(True) + self.assertTrue(self.safety.safety_tx_hook(self._resume_msg())) + + self.safety.set_controls_allowed(False) + self.assertFalse(self.safety.safety_tx_hook(self._resume_msg())) + + def test_alternate_button_frame_is_blocked_without_flag(self): + self.safety.set_safety_hooks( + CarParams.SafetyModel.hyundaiCanfd, + HyundaiSafetyFlags.CCNC | HyundaiSafetyFlags.CAMERA_SCC, + ) + self.safety.init_tests() + self.safety.set_controls_allowed(True) + self.assertFalse(self.safety.safety_tx_hook(self._resume_msg())) + + class TestHyundaiCanfdCCNCSupportFrames(common.SafetyTestBase): TX_MSGS = [[0x161, 0], [0x162, 0], [0x7C4, 2], [0xEA, 2]] @@ -793,12 +826,19 @@ class TestHyundaiCanfdLKASteeringAltAngleLongEV(HyundaiLongitudinalBase, TestHyu with self.subTest(address=address): self.assertFalse(self._tx(common.make_msg(1 if address != 0x51 else 0, address, length))) - def test_ccnc_angle_fallback_allows_lfa_status_without_longitudinal_control(self): + def test_ccnc_angle_fallback_allows_lateral_only(self): fallback_param = (self.SAFETY_PARAM & ~HyundaiSafetyFlags.LONG) | HyundaiSafetyFlags.CCNC self.safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, fallback_param) self.safety.init_tests() - self.assertTrue(self._tx(common.make_msg(1, 0x12A, 16))) + self._rx(self._gear_msg(5)) + self._reset_speed_measurement(self.STANDSTILL_THRESHOLD + 1) + self._reset_angle_measurement(0) + self._set_prev_desired_angle(0) + self.safety.set_controls_allowed(True) + + self.assertTrue(self._tx(self._angle_cmd_msg(0, enabled=True))) + self.assertFalse(self._tx(common.make_msg(1, 0x12A, 16))) self.assertFalse(self._tx(common.make_msg(1, 0x1A0, 32))) def test_ccnc_angle_long_uses_second_mdps_angle(self): diff --git a/panda/board/obj/gitversion.h b/panda/board/obj/gitversion.h index 1bda7f1c9..8966e2dda 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-46ae2472-DEBUG"; +const uint8_t gitversion[19] = "DEV-83cec26d-DEBUG"; diff --git a/panda/board/obj/version b/panda/board/obj/version index 89a2508c0..5de3a0ac0 100644 --- a/panda/board/obj/version +++ b/panda/board/obj/version @@ -1 +1 @@ -DEV-46ae2472-DEBUG \ No newline at end of file +DEV-83cec26d-DEBUG \ No newline at end of file diff --git a/selfdrive/car/cruise.py b/selfdrive/car/cruise.py index 8e57fc70b..b929d5e8b 100644 --- a/selfdrive/car/cruise.py +++ b/selfdrive/car/cruise.py @@ -69,10 +69,6 @@ class VCruiseHelper: def _get_cruise_delta_intervals(self, starpilot_toggles: SimpleNamespace) -> tuple[float, float]: short_interval = self._get_cruise_delta_interval(getattr(starpilot_toggles, "cruise_increase", None)) long_interval = self._get_cruise_delta_interval(getattr(starpilot_toggles, "cruise_increase_long", None)) - - if getattr(starpilot_toggles, "reverse_cruise_increase", False): - return long_interval, short_interval - return short_interval, long_interval @property diff --git a/selfdrive/car/tests/test_cruise_speed.py b/selfdrive/car/tests/test_cruise_speed.py index 299b3fc0f..eec2b5b45 100644 --- a/selfdrive/car/tests/test_cruise_speed.py +++ b/selfdrive/car/tests/test_cruise_speed.py @@ -73,7 +73,6 @@ class TestVCruiseHelper: cruise_increase=1, cruise_increase_long=5, is_metric=False, - reverse_cruise_increase=False, set_speed_limit=False, ) self.reset_cruise_speed_state() @@ -493,7 +492,6 @@ class TestVCruiseHelperRedneck: cruise_increase=1, cruise_increase_long=5, is_metric=False, - reverse_cruise_increase=False, set_speed_limit=False, ) @@ -555,34 +553,3 @@ class TestVCruiseHelperRedneck: assert self.v_cruise_helper.v_cruise_kph == pytest.approx(75 * CV.MPH_TO_KPH) assert self.v_cruise_helper.v_cruise_cluster_kph == pytest.approx(75 * CV.MPH_TO_KPH) - - def test_reverse_cruise_increase_swaps_short_and_long_press_intervals(self): - self.enable(55 * CV.MPH_TO_MS, experimental_mode=False) - initial_v_cruise_kph = self.v_cruise_helper.v_cruise_kph - self.starpilot_toggles.cruise_increase = 1 - self.starpilot_toggles.cruise_increase_long = 5 - self.starpilot_toggles.reverse_cruise_increase = True - - pressed_cs = car.CarState(cruiseState={"available": True}) - pressed_cs.buttonEvents = [ButtonEvent(type=ButtonType.accelCruise, pressed=True)] - self.v_cruise_helper.update_v_cruise( - pressed_cs, - enabled=True, - is_metric=False, - speed_limit_changed=False, - starpilot_toggles=self.starpilot_toggles, - ) - - released_cs = car.CarState(cruiseState={"available": True}) - released_cs.buttonEvents = [ButtonEvent(type=ButtonType.accelCruise, pressed=False)] - self.v_cruise_helper.update_v_cruise( - released_cs, - enabled=True, - is_metric=False, - speed_limit_changed=False, - starpilot_toggles=self.starpilot_toggles, - ) - - reversed_interval = 5 * IMPERIAL_INCREMENT - expected_kph = math.ceil(initial_v_cruise_kph / reversed_interval) * reversed_interval - assert self.v_cruise_helper.v_cruise_kph == pytest.approx(expected_kph) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 65b629919..f6666359b 100644 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -44,7 +44,6 @@ def get_test_starpilot_toggles() -> SimpleNamespace: disable_openpilot_long=False, force_fingerprint=False, lock_doors=False, - reverse_cruise_increase=False, sng_hack=False, subaru_sng=False, subaru_sng_manual_parking_brake=False, diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index cf3fb7132..6a89184ef 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -93,6 +93,7 @@ class LatControlTorque(LatControl): self.low_speed_reset_threshold = max(CP.minSteerSpeed, MIN_LATERAL_CONTROL_SPEED) self.steer_release_i_decay = 0.8 self.prev_steering_pressed = False + self.prev_output_torque = 0.0 self.debug_counter = 0 self.prev_desired_lateral_accel = 0.0 self.starpilot_lateral_state = custom.StarPilotLateralState.new_message() @@ -231,6 +232,7 @@ class LatControlTorque(LatControl): future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2 if not active: output_torque = 0.0 + self.prev_output_torque = 0.0 pid_log.active = False self._clear_starpilot_lateral_state() self.pid.reset() @@ -541,6 +543,9 @@ class LatControlTorque(LatControl): -low_speed_center_output_limit, low_speed_center_output_limit, )) + output_torque = get_bolt_2022_2023_low_speed_center_output( + output_torque, self.prev_output_torque, setpoint, CS.vEgo, + ) elif self.is_bolt_2017: output_torque *= get_bolt_2017_torque_scale(setpoint, desired_lateral_jerk, CS.vEgo) elif bolt_2018_2021_tuned_path_active: @@ -658,6 +663,7 @@ class LatControlTorque(LatControl): self.starpilot_lateral_state.lowSpeedFactor = float(low_speed_factor) self.starpilot_lateral_state.unwindDetected = bool(unwind_detected) pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited)) + self.prev_output_torque = float(output_torque) if DEBUG_TORQUE_TUNE and self.is_bolt: self.debug_counter += 1 diff --git a/selfdrive/controls/lib/latcontrol_vehicle_tunes.py b/selfdrive/controls/lib/latcontrol_vehicle_tunes.py index cd3577d93..320eec781 100644 --- a/selfdrive/controls/lib/latcontrol_vehicle_tunes.py +++ b/selfdrive/controls/lib/latcontrol_vehicle_tunes.py @@ -376,6 +376,8 @@ BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SPEED = 2.5 BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SPEED_WIDTH = 0.7 BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SPEED_MAX = 7.2 BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SPEED_MAX_WIDTH = 0.5 +BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SCALE_MIN = 0.62 +BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_ALPHA_MIN = 0.28 BOLT_2022_2023_CENTER_FRICTION_THRESHOLD_BUMP = 0.080 BOLT_2022_2023_CENTER_FRICTION_THRESHOLD_LAT = 0.18 BOLT_2022_2023_CENTER_FRICTION_THRESHOLD_LAT_WIDTH = 0.06 @@ -457,7 +459,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.08 +SONATA_HYBRID_CENTER_OUTPUT_TAPER_MAX = 0.14 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 @@ -2267,6 +2269,27 @@ def get_bolt_2022_2023_low_speed_center_output_limit(desired_lateral_accel: floa return 1.0 - reduction +def get_bolt_2022_2023_low_speed_center_output(output_torque: float, prev_output_torque: float, + desired_lateral_accel: float, v_ego: float) -> float: + """Damp low-speed center reversals without reducing real turn authority.""" + speed_weight = _bolt_2022_2023_sigmoid( + (v_ego - BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SPEED) / + BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SPEED_WIDTH + ) * _bolt_2022_2023_sigmoid( + (BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SPEED_MAX - v_ego) / + BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SPEED_MAX_WIDTH + ) + center_weight = _bolt_2022_2023_sigmoid( + (BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_LAT - abs(desired_lateral_accel)) / + BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_LAT_WIDTH + ) + envelope = speed_weight * center_weight + output_scale = 1.0 - ((1.0 - BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SCALE_MIN) * envelope) + output_alpha = 1.0 - ((1.0 - BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_ALPHA_MIN) * envelope) + limited_output = output_torque * output_scale + return float(prev_output_torque + output_alpha * (limited_output - prev_output_torque)) + + def get_bolt_2022_2023_friction_threshold(v_ego: float, desired_lateral_accel: float = 0.0, desired_lateral_jerk: float = 0.0) -> float: base_threshold = get_gm_base_friction_threshold(v_ego) center_weight = _bolt_2022_2023_sigmoid( diff --git a/selfdrive/controls/lib/longcontrol_vehicle_tunes.py b/selfdrive/controls/lib/longcontrol_vehicle_tunes.py index d36a7431b..c41f5f9b0 100644 --- a/selfdrive/controls/lib/longcontrol_vehicle_tunes.py +++ b/selfdrive/controls/lib/longcontrol_vehicle_tunes.py @@ -123,6 +123,13 @@ class LongControlVehicleTuning: getattr(CP, "carFingerprint", None) in (CAR.CHEVROLET_SILVERADO, CAR.CHEVROLET_SILVERADO_CC) and not CP.enableGasInterceptorDEPRECATED ) + self.is_toyota_sienna = bool( + CP.brand == "toyota" and + str(getattr(CP, "carFingerprint", "")) in ( + str(TOYOTA_CAR.TOYOTA_SIENNA), + str(TOYOTA_CAR.TOYOTA_SIENNA_4TH_GEN), + ) + ) self.is_toyota_sienna_4g = bool( CP.brand == "toyota" and str(getattr(CP, "carFingerprint", "")) == str(TOYOTA_CAR.TOYOTA_SIENNA_4TH_GEN) @@ -268,7 +275,7 @@ class LongControlVehicleTuning: def shape_toyota_sienna_accel_target(self, a_target, v_ego, should_stop, leads=None): """Smooth Sienna lead braking only while there is still comfortable stopping room.""" - if not self.is_toyota_sienna_4g or should_stop: + if not self.is_toyota_sienna or should_stop: self.toyota_sienna_target_filter_initialized = False return a_target diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 991f38778..01b544cf1 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -33,6 +33,11 @@ from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import ( get_toyota_sienna_post_departure_restop_cap, get_untracked_slow_lead_decel_scale, get_toyota_prius_stopped_lead_obstacle_bias, + get_honda_crv_5g_stopped_lead_obstacle_bias, + get_honda_crv_5g_low_speed_stopped_lead_cap, + allow_honda_crv_5g_vision_gap_settle, + get_standstill_gap_settle_max_extra_gap, + get_standstill_stopped_lead_guard_distance_margin, ) from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N from openpilot.selfdrive.car.cruise import V_CRUISE_UNSET @@ -1365,8 +1370,9 @@ class LongitudinalPlanner: )) @staticmethod - def is_radar_standstill_gap_settle_candidate(lead, v_ego, target_gap, active=False): - if lead is None or not lead.status or not bool(getattr(lead, "radar", False)): + def is_radar_standstill_gap_settle_candidate(lead, v_ego, target_gap, active=False, + allow_vision=False, max_extra_gap=RADAR_STANDSTILL_GAP_SETTLE_MAX_EXTRA_GAP): + if lead is None or not lead.status or (not bool(getattr(lead, "radar", False)) and not allow_vision): return False if float(v_ego) > RADAR_STANDSTILL_GAP_SETTLE_MAX_EGO_SPEED: return False @@ -1374,15 +1380,20 @@ class LongitudinalPlanner: return False if abs(float(getattr(lead, "vLead", 0.0))) > RADAR_STANDSTILL_GAP_SETTLE_MAX_LEAD_SPEED: return False + if allow_vision and ( + bool(getattr(lead, "radar", False)) or + float(getattr(lead, "modelProb", 0.0)) < 0.99 + ): + return False lead_gap = float(getattr(lead, "dRel", 0.0)) min_margin = RADAR_STANDSTILL_GAP_SETTLE_EXIT_MARGIN if active else RADAR_STANDSTILL_GAP_SETTLE_ENTRY_MARGIN return bool( lead_gap > target_gap + min_margin and - lead_gap <= target_gap + RADAR_STANDSTILL_GAP_SETTLE_MAX_EXTRA_GAP + lead_gap <= target_gap + float(max_extra_gap) ) - def update_radar_standstill_gap_settle(self, sm, target_gap): + def update_radar_standstill_gap_settle(self, sm, target_gap, allow_vision=False, max_extra_gap=None): vetoed = bool( getattr(sm["carState"], "brakePressed", False) or getattr(sm["carState"], "gasPressed", False) or @@ -1396,6 +1407,8 @@ class LongitudinalPlanner: float(sm["carState"].vEgo), target_gap, active=self.radar_standstill_gap_settle_active, + allow_vision=allow_vision, + max_extra_gap=(RADAR_STANDSTILL_GAP_SETTLE_MAX_EXTRA_GAP if max_extra_gap is None else max_extra_gap), ) ] @@ -1621,7 +1634,7 @@ class LongitudinalPlanner: lead_delta = lead_speed - float(v_ego) max_distance = max( STANDSTILL_STOPPED_LEAD_GUARD_MIN_DISTANCE, - float(stop_distance) + STANDSTILL_STOPPED_LEAD_GUARD_DISTANCE_MARGIN, + float(stop_distance) + get_standstill_stopped_lead_guard_distance_margin(self.CP), ) if ( float(getattr(lead, "dRel", float("inf"))) > max_distance or @@ -2209,7 +2222,7 @@ class LongitudinalPlanner: get_force_stop_distance_bias(self.CP.carFingerprint) ) - prius_lead_obstacle_bias = (0.0, 0.0) + stopped_lead_obstacle_bias = (0.0, 0.0) if ( self.mode == 'acc' and not bool(getattr(sm['modelV2'].action, 'shouldStop', False)) and @@ -2217,9 +2230,12 @@ class LongitudinalPlanner: not bool(getattr(sm['starpilotPlan'], 'forcingStop', False)) and not bool(getattr(sm['carState'], 'standstill', False)) ): - prius_lead_obstacle_bias = ( - get_toyota_prius_stopped_lead_obstacle_bias(self.CP, self.lead_one, scene_v_ego), - get_toyota_prius_stopped_lead_obstacle_bias(self.CP, self.lead_two, scene_v_ego), + stopped_lead_obstacle_bias = tuple( + max( + get_toyota_prius_stopped_lead_obstacle_bias(self.CP, lead, scene_v_ego), + get_honda_crv_5g_stopped_lead_obstacle_bias(self.CP, lead, scene_v_ego), + ) + for lead in (self.lead_one, self.lead_two) ) self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, @@ -2230,7 +2246,7 @@ class LongitudinalPlanner: stop_x=force_stop_x, silverado_early_follow=early_truck_follow, modelV2=sm['modelV2'], - lead_obstacle_bias=prius_lead_obstacle_bias) + lead_obstacle_bias=stopped_lead_obstacle_bias) self.a_desired_trajectory_full = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.a_solution) self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution) @@ -2377,6 +2393,11 @@ class LongitudinalPlanner: if rav4_early_lead_cap is not None: rav4_early_lead_caps.append(rav4_early_lead_cap) cap = self.get_close_lead_brake_cap(lead, v_ego, output_accel_min) + if cap is not None: + close_lead_caps.append(cap) + cap = get_honda_crv_5g_low_speed_stopped_lead_cap( + self.CP, lead, v_ego, vision_cap_accel_min, + ) if cap is not None: close_lead_caps.append(cap) slow_stop_cap = self.get_vision_slow_stopped_lead_cap(lead, v_ego, vision_cap_accel_min, effective_t_follow) @@ -2498,8 +2519,13 @@ class LongitudinalPlanner: self.slow_creep_lead_depart_elapsed >= STANDSTILL_LEAD_CREEP_RELEASE_CONFIRM_TIME ) radar_gap_settle_active = False - if allow_radar_standstill_gap_settle(self.CP): - radar_gap_settle_active = self.update_radar_standstill_gap_settle(sm, standstill_nudge_gap) + if allow_radar_standstill_gap_settle(self.CP) or allow_honda_crv_5g_vision_gap_settle(self.CP): + radar_gap_settle_active = self.update_radar_standstill_gap_settle( + sm, + standstill_nudge_gap, + allow_vision=allow_honda_crv_5g_vision_gap_settle(self.CP), + max_extra_gap=get_standstill_gap_settle_max_extra_gap(self.CP), + ) else: self.radar_standstill_gap_settle_elapsed = 0.0 self.radar_standstill_gap_settle_active = False diff --git a/selfdrive/controls/lib/longitudinal_vehicle_tunes.py b/selfdrive/controls/lib/longitudinal_vehicle_tunes.py index 48ef9c312..27d441a03 100644 --- a/selfdrive/controls/lib/longitudinal_vehicle_tunes.py +++ b/selfdrive/controls/lib/longitudinal_vehicle_tunes.py @@ -47,12 +47,32 @@ TOYOTA_PRIUS_STOPPED_LEAD_MIN_CLOSING_SPEED = 0.15 TOYOTA_PRIUS_STOPPED_LEAD_MAX_DISTANCE = 80.0 TOYOTA_PRIUS_STOPPED_LEAD_RAMP_DISTANCE = 10.0 TOYOTA_PRIUS_STOPPED_LEAD_MAX_LATERAL_OFFSET = 1.75 +HONDA_CRV_5G_STOPPED_LEAD_OBSTACLE_BIAS_M = 1.0 +HONDA_CRV_5G_STOPPED_LEAD_MAX_EGO_SPEED = 22.0 +HONDA_CRV_5G_STOPPED_LEAD_MAX_SPEED = 1.0 +HONDA_CRV_5G_STOPPED_LEAD_MIN_CLOSING_SPEED = 0.15 +HONDA_CRV_5G_STOPPED_LEAD_MAX_DISTANCE = 80.0 +HONDA_CRV_5G_STOPPED_LEAD_RAMP_DISTANCE = 10.0 +HONDA_CRV_5G_STOPPED_LEAD_MAX_LATERAL_OFFSET = 1.75 +HONDA_CRV_5G_LOW_SPEED_STOP_MAX_EGO_SPEED = 4.5 +HONDA_CRV_5G_LOW_SPEED_STOP_MAX_LEAD_SPEED = 0.5 +HONDA_CRV_5G_LOW_SPEED_STOP_MIN_MODEL_PROB = 0.99 +HONDA_CRV_5G_LOW_SPEED_STOP_MAX_DISTANCE = 12.0 +HONDA_CRV_5G_LOW_SPEED_STOP_MIN_DISTANCE = 6.5 +HONDA_CRV_5G_LOW_SPEED_STOP_MIN_CLOSING_SPEED = 0.15 +HONDA_CRV_5G_LOW_SPEED_STOP_MAX_LEAD_ACCEL = 0.25 +HONDA_CRV_5G_LOW_SPEED_STOP_MAX_DECEL = 0.45 +HONDA_CRV_5G_LOW_SPEED_STOP_MIN_DECEL = 0.12 +HONDA_CRV_5G_GAP_SETTLE_MAX_EXTRA_GAP = 7.0 +HONDA_CRV_5G_GUARD_DISTANCE_MARGIN = 1.5 TOYOTA_CAMRY_TSS2_FORCE_STOP_HANDOFF_M = 4.5 # The Camry's force-stop path otherwise consumes the model endpoint before the # normal MPC stop-distance margin can be applied. Keep it within the forward # offset range exposed by the Force Stop setting. TOYOTA_CAMRY_TSS2_FORCE_STOP_DISTANCE_BIAS_M = 6.0 DEFAULT_FORCE_STOP_HANDOFF_M = 6.0 +HYUNDAI_SANTA_FE_2022_FORCE_STOP_REANCHOR_SPEED_TOLERANCE = 0.25 +HYUNDAI_SANTA_FE_2022_FORCE_STOP_LOW_SPEED_HOLD = 2.5 def get_toyota_prius_stopped_lead_obstacle_bias(CP, lead, v_ego): @@ -84,6 +104,92 @@ def get_toyota_prius_stopped_lead_obstacle_bias(CP, lead, v_ego): return float(min(bias, max(distance - 0.5, 0.0))) +def is_honda_crv_5g(CP): + return ( + getattr(CP, "brand", "") == "honda" and + str(getattr(CP, "carFingerprint", "")) == "HONDA_CRV_5G" + ) + + +def get_honda_crv_5g_stopped_lead_obstacle_bias(CP, lead, v_ego): + """Bring the CR-V's vision stopped-lead target in without changing stops.""" + if ( + not is_honda_crv_5g(CP) or + lead is None or not bool(getattr(lead, "status", False)) or + float(v_ego) <= 0.0 or float(v_ego) > HONDA_CRV_5G_STOPPED_LEAD_MAX_EGO_SPEED or + float(getattr(lead, "vLead", 0.0)) > HONDA_CRV_5G_STOPPED_LEAD_MAX_SPEED or + bool(getattr(lead, "radar", False)) or + float(getattr(lead, "modelProb", 0.0)) < 0.95 or + abs(float(getattr(lead, "yRel", 0.0))) > HONDA_CRV_5G_STOPPED_LEAD_MAX_LATERAL_OFFSET + ): + return 0.0 + + distance = float(getattr(lead, "dRel", float("inf"))) + closing_speed = float(v_ego) - float(getattr(lead, "vLead", 0.0)) + if ( + distance <= 0.0 or distance > HONDA_CRV_5G_STOPPED_LEAD_MAX_DISTANCE or + closing_speed < HONDA_CRV_5G_STOPPED_LEAD_MIN_CLOSING_SPEED + ): + return 0.0 + + strength = np.clip( + (HONDA_CRV_5G_STOPPED_LEAD_MAX_DISTANCE - distance) / + (HONDA_CRV_5G_STOPPED_LEAD_MAX_DISTANCE - HONDA_CRV_5G_STOPPED_LEAD_RAMP_DISTANCE), + 0.0, 1.0, + ) + bias = HONDA_CRV_5G_STOPPED_LEAD_OBSTACLE_BIAS_M * strength + return float(min(bias, max(distance - 0.5, 0.0))) + + +def get_honda_crv_5g_low_speed_stopped_lead_cap(CP, lead, v_ego, accel_min): + """Bleed a CR-V crawl into the normal standstill gap without a hard jab.""" + if ( + not is_honda_crv_5g(CP) or + lead is None or not bool(getattr(lead, "status", False)) or + bool(getattr(lead, "radar", False)) or + float(getattr(lead, "modelProb", 0.0)) < HONDA_CRV_5G_LOW_SPEED_STOP_MIN_MODEL_PROB or + float(v_ego) <= 0.0 or float(v_ego) > HONDA_CRV_5G_LOW_SPEED_STOP_MAX_EGO_SPEED + ): + return None + + lead_speed = max(float(getattr(lead, "vLead", 0.0)), 0.0) + distance = float(getattr(lead, "dRel", float("inf"))) + if ( + lead_speed > HONDA_CRV_5G_LOW_SPEED_STOP_MAX_LEAD_SPEED or + float(getattr(lead, "aLeadK", 0.0)) > HONDA_CRV_5G_LOW_SPEED_STOP_MAX_LEAD_ACCEL or + distance < HONDA_CRV_5G_LOW_SPEED_STOP_MIN_DISTANCE or + distance > HONDA_CRV_5G_LOW_SPEED_STOP_MAX_DISTANCE or + float(v_ego) - lead_speed < HONDA_CRV_5G_LOW_SPEED_STOP_MIN_CLOSING_SPEED or + abs(float(getattr(lead, "yRel", 0.0))) > HONDA_CRV_5G_STOPPED_LEAD_MAX_LATERAL_OFFSET + ): + return None + + available_gap = max(distance - 6.0, 1.0) + required_decel = float(v_ego) ** 2 / (2.0 * available_gap) + decel = float(np.clip( + required_decel * 0.85, + HONDA_CRV_5G_LOW_SPEED_STOP_MIN_DECEL, + HONDA_CRV_5G_LOW_SPEED_STOP_MAX_DECEL, + )) + return max(float(accel_min), -decel) + + +def allow_honda_crv_5g_vision_gap_settle(CP): + return is_honda_crv_5g(CP) + + +def get_standstill_gap_settle_max_extra_gap(CP): + if is_honda_crv_5g(CP): + return HONDA_CRV_5G_GAP_SETTLE_MAX_EXTRA_GAP + return 1.5 + + +def get_standstill_stopped_lead_guard_distance_margin(CP): + if is_honda_crv_5g(CP): + return HONDA_CRV_5G_GUARD_DISTANCE_MARGIN + return 3.0 + + def is_toyota_rav4_tss2_post_departure_tune(CP): """Identify RAV4 TSS2 variants that need normal catch-up caps after departure.""" return ( @@ -285,3 +391,17 @@ def get_force_stop_distance_bias(car_fingerprint): if str(car_fingerprint) == "TOYOTA_CAMRY_TSS2": return TOYOTA_CAMRY_TSS2_FORCE_STOP_DISTANCE_BIAS_M return 0.0 + + +def get_force_stop_reanchor_speed_tolerance(car_params): + """Keep the Santa Fe stop distance from reopening after braking begins.""" + if str(getattr(car_params, "carFingerprint", car_params)) == "HYUNDAI_SANTA_FE_2022": + return HYUNDAI_SANTA_FE_2022_FORCE_STOP_REANCHOR_SPEED_TOLERANCE + return None + + +def get_force_stop_low_speed_hold(car_params): + """Keep a committed Santa Fe stop from releasing while it is still rolling.""" + if str(getattr(car_params, "carFingerprint", car_params)) == "HYUNDAI_SANTA_FE_2022": + return HYUNDAI_SANTA_FE_2022_FORCE_STOP_LOW_SPEED_HOLD + return None diff --git a/selfdrive/controls/tests/test_latcontrol.py b/selfdrive/controls/tests/test_latcontrol.py index 5863a6f64..0d048adcf 100644 --- a/selfdrive/controls/tests/test_latcontrol.py +++ b/selfdrive/controls/tests/test_latcontrol.py @@ -65,6 +65,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import ( get_bolt_2022_2023_ff_scale, get_bolt_2022_2023_center_output_scale, get_bolt_2022_2023_low_speed_center_output_limit, + get_bolt_2022_2023_low_speed_center_output, get_bolt_2022_2023_friction_scale, get_bolt_2022_2023_friction_threshold, get_trailer_lateral_ff_scale, @@ -333,6 +334,15 @@ class TestLatControl: assert low_speed_turn > 0.98 assert normal_speed_center > 0.98 + def test_bolt_2022_2023_low_speed_center_output_damps_reversals(self): + low_speed = get_bolt_2022_2023_low_speed_center_output(1.0, -1.0, 0.05, 4.2) + large_turn = get_bolt_2022_2023_low_speed_center_output(1.0, -1.0, 0.40, 4.2) + highway = get_bolt_2022_2023_low_speed_center_output(1.0, -1.0, 0.05, 9.0) + + assert abs(low_speed) < 0.50 + assert abs(large_turn) > abs(low_speed) + assert highway > low_speed + def test_bolt_2022_2023_friction_threshold_curve(self): base = get_gm_base_friction_threshold(6.0) left_turn_in = get_bolt_2022_2023_friction_threshold(6.0, 0.7, 0.8) @@ -906,6 +916,7 @@ class TestLatControl: assert low_speed > center assert turn > center assert turn > 0.99 + assert center > 0.85 def test_ioniq_5_ff_scale_curve(self): assert get_ioniq_5_ff_scale(0.0, 0.0, 20.0) == 1.0 @@ -1488,6 +1499,26 @@ class TestLatControl: assert lac_log.active + def test_bolt_2022_2023_low_speed_center_output_update_path(self, monkeypatch): + calls = [] + + def record_call(output_torque, prev_output_torque, desired_lateral_accel, v_ego): + calls.append((output_torque, prev_output_torque, desired_lateral_accel, v_ego)) + return 0.0 + + monkeypatch.setattr(latcontrol_torque, "get_bolt_2022_2023_low_speed_center_output", record_call) + controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(GM.CHEVROLET_BOLT_ACC_2022_2023) + CS.vEgo = 4.0 + + output, _, lac_log = controller.update( + True, CS, VM, params, False, 0.0025, False, 0.2, None, None, starpilot_toggles, + ) + + assert lac_log.active + assert output == 0.0 + assert calls + assert calls[0][3] == pytest.approx(4.0) + def test_volt_standard_testing_ground_update_path(self, monkeypatch): controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(GM.CHEVROLET_VOLT_ASCM) monkeypatch.setattr(latcontrol_torque, "volt_standard_lateral_testing_ground_active", lambda: True) diff --git a/selfdrive/controls/tests/test_longcontrol.py b/selfdrive/controls/tests/test_longcontrol.py index 799282b3a..93eeebf67 100644 --- a/selfdrive/controls/tests/test_longcontrol.py +++ b/selfdrive/controls/tests/test_longcontrol.py @@ -1235,6 +1235,17 @@ def test_toyota_sienna_target_filter_smooths_mild_high_speed_handoffs(): assert -0.20 < filtered < 0.30 + +def test_toyota_sienna_2019_target_filter_smooths_mild_high_speed_handoffs(): + CP = make_longcontrol_cp(brand="toyota", carFingerprint="TOYOTA_SIENNA") + tuning = vehicle_tunes.LongControlVehicleTuning(CP) + + assert tuning.shape_toyota_sienna_accel_target(0.30, 20.0, False) == pytest.approx(0.30) + filtered = tuning.shape_toyota_sienna_accel_target(-0.20, 20.0, False) + + assert -0.20 < filtered < 0.30 + + def test_toyota_sienna_target_filter_smooths_nonurgent_low_speed_lead_braking(): 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_longitudinal_planner.py b/selfdrive/controls/tests/test_longitudinal_planner.py index 5f9147c94..0ecd5b851 100644 --- a/selfdrive/controls/tests/test_longitudinal_planner.py +++ b/selfdrive/controls/tests/test_longitudinal_planner.py @@ -28,6 +28,10 @@ from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import ( get_far_follow_output_slew_rates, get_follow_prebrake_min_headway, get_honda_accord_lead_departure_tune, + get_honda_crv_5g_stopped_lead_obstacle_bias, + get_honda_crv_5g_low_speed_stopped_lead_cap, + allow_honda_crv_5g_vision_gap_settle, + get_standstill_gap_settle_max_extra_gap, get_toyota_prius_stopped_lead_obstacle_bias, get_toyota_rav4_tss2_lead_departure_tune, get_toyota_rav4_tss2_early_lead_cap, @@ -116,6 +120,50 @@ def test_prius_stopped_lead_obstacle_bias_does_not_apply_at_standstill_or_to_dep assert get_toyota_prius_stopped_lead_obstacle_bias(prius, departing_lead, v_ego=8.0) == pytest.approx(0.0) +def test_honda_crv_5g_stopped_lead_tune_is_vehicle_specific(): + crv = CarInterface.get_non_essential_params(CAR.HONDA_CRV_5G) + civic = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC) + stopped_lead = make_lead(status=True, d_rel=18.0, v_lead=0.2, model_prob=0.99) + + bias = get_honda_crv_5g_stopped_lead_obstacle_bias(crv, stopped_lead, v_ego=8.0) + assert 0.0 < bias < 1.0 + assert get_honda_crv_5g_stopped_lead_obstacle_bias(civic, stopped_lead, v_ego=8.0) == pytest.approx(0.0) + assert get_honda_crv_5g_low_speed_stopped_lead_cap( + crv, make_lead(status=True, d_rel=10.0, v_lead=0.1, model_prob=0.99), v_ego=1.6, accel_min=-0.5, + ) == pytest.approx(-0.272) + assert get_honda_crv_5g_low_speed_stopped_lead_cap( + civic, make_lead(status=True, d_rel=10.0, v_lead=0.1, model_prob=0.99), v_ego=1.6, accel_min=-0.5, + ) is None + assert allow_honda_crv_5g_vision_gap_settle(crv) + assert not allow_honda_crv_5g_vision_gap_settle(civic) + assert get_standstill_gap_settle_max_extra_gap(crv) > get_standstill_gap_settle_max_extra_gap(civic) + + +@pytest.mark.parametrize("model_version", ["v11", "v12", "v13", "v14", "v15"]) +def test_honda_crv_5g_vision_lead_gap_settle_is_bounded(model_version): + CP = CarInterface.get_non_essential_params(CAR.HONDA_CRV_5G) + planner = LongitudinalPlanner(CP, init_v=0.0) + sm = make_sm( + 0.0, + desired_accel=-0.12, + min_accel=-0.5, + experimental_mode=True, + tracking_lead=False, + lead_one=make_lead(status=True, d_rel=11.0, v_lead=0.0, a_lead=0.0, model_prob=0.99), + ) + sm["carState"].standstill = True + sm["controlsState"].longControlState = LongCtrlState.stopping + sm["modelV2"].action.shouldStop = True + + frames = int(round(longitudinal_planner_module.RADAR_STANDSTILL_GAP_SETTLE_CONFIRM_TIME / planner.dt)) + 2 + for _ in range(frames): + planner.update(sm, make_toggles(model_version)) + + assert planner.radar_standstill_gap_settle_active + assert not planner.output_should_stop + assert planner.output_a_target == pytest.approx(longitudinal_planner_module.RADAR_STANDSTILL_GAP_SETTLE_ACCEL) + + def test_mpc_duplicate_vision_filter_smooths_distance_jumps_per_track(): mpc = LongitudinalMpc() mpc.set_cur_state(27.0, 0.0) diff --git a/selfdrive/controls/tests/test_starpilot_vcruise.py b/selfdrive/controls/tests/test_starpilot_vcruise.py index 0c972a90a..47189c1d5 100644 --- a/selfdrive/controls/tests/test_starpilot_vcruise.py +++ b/selfdrive/controls/tests/test_starpilot_vcruise.py @@ -16,6 +16,8 @@ from openpilot.starpilot.controls.lib.starpilot_vcruise import ( from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import ( get_force_stop_distance_bias, get_force_stop_handoff_distance, + get_force_stop_low_speed_hold, + get_force_stop_reanchor_speed_tolerance, ) from types import SimpleNamespace @@ -58,7 +60,7 @@ def make_vcruise(*, red_light=False, raw_model_stopped=False, forcing_stop=False return planner, vcruise -def make_sm(*, standstill=True, min_steer_speed=0.0): +def make_sm(*, standstill=True, min_steer_speed=0.0, car_fingerprint=""): return { "carControl": SimpleNamespace(longActive=True), "carState": SimpleNamespace( @@ -71,7 +73,7 @@ def make_sm(*, standstill=True, min_steer_speed=0.0): rightBlinker=False, steeringAngleDeg=0.0, ), - "carParams": SimpleNamespace(minSteerSpeed=min_steer_speed), + "carParams": SimpleNamespace(minSteerSpeed=min_steer_speed, carFingerprint=car_fingerprint), "starpilotCarState": SimpleNamespace(accelPressed=False, dashboardStopSign=0, dashboardSpeedLimit=0), "onroadEvents": [], } @@ -130,6 +132,16 @@ def test_camry_tss2_gets_forward_force_stop_bias_only(): assert get_force_stop_distance_bias("TOYOTA_RAV4_TSS2") == pytest.approx(0.0) +def test_santa_fe_force_stop_tune_only_applies_to_that_car(): + santa_fe = SimpleNamespace(carFingerprint="HYUNDAI_SANTA_FE_2022") + other = SimpleNamespace(carFingerprint="HYUNDAI_SANTA_FE_2021") + + assert get_force_stop_reanchor_speed_tolerance(santa_fe) == pytest.approx(0.25) + assert get_force_stop_low_speed_hold(santa_fe) == pytest.approx(2.5) + assert get_force_stop_reanchor_speed_tolerance(other) is None + assert get_force_stop_low_speed_hold(other) is None + + def test_curve_speed_controller_holds_target_through_brief_detector_dropout(): planner, vcruise = make_vcruise() sm = make_sm(standstill=False) @@ -520,6 +532,34 @@ def test_force_stop_reanchors_when_model_reopens_path_without_stop_action(): assert result > 5.0 +def test_santa_fe_force_stop_does_not_reanchor_after_braking(): + planner, vcruise = make_vcruise(red_light=False, raw_model_stopped=False, forcing_stop=True) + planner.model_length = 40.0 + vcruise.tracked_model_length = 10.0 + vcruise.force_stop_entry_speed = 12.0 + sm = make_sm(standstill=False, car_fingerprint="HYUNDAI_SANTA_FE_2022") + sm["modelV2"] = SimpleNamespace(action=SimpleNamespace(shouldStop=False)) + + result = update_vcruise(vcruise, sm, make_toggles(), now=0.0, v_ego=5.0) + + assert vcruise.tracked_model_length < 10.0 + assert result < 5.0 + + +def test_santa_fe_force_stop_holds_through_low_speed_detector_dropout(): + planner, vcruise = make_vcruise(red_light=True, raw_model_stopped=False, forcing_stop=True) + vcruise.force_stop_entry_speed = 12.0 + sm = make_sm(standstill=False, car_fingerprint="HYUNDAI_SANTA_FE_2022") + toggles = make_toggles() + + update_vcruise(vcruise, sm, toggles, now=0.0, v_ego=2.0) + planner.starpilot_cem.stop_light_detected = False + result = update_vcruise(vcruise, sm, toggles, now=0.75, v_ego=2.0) + + assert vcruise.forcing_stop + assert result == pytest.approx(0.0) + + def test_force_stop_does_not_reanchor_committed_model_stop(): planner, vcruise = make_vcruise(red_light=False, raw_model_stopped=False, forcing_stop=True) planner.model_length = 40.0 diff --git a/selfdrive/ui/layouts/settings/starpilot/lateral.py b/selfdrive/ui/layouts/settings/starpilot/lateral.py index 04d95b6ea..0693b30a2 100644 --- a/selfdrive/ui/layouts/settings/starpilot/lateral.py +++ b/selfdrive/ui/layouts/settings/starpilot/lateral.py @@ -5,7 +5,6 @@ from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.system.ui.widgets import DialogResult -from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import _SettingsPage from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import ( @@ -73,13 +72,6 @@ class SteeringManagerView(CardHubManagerView): "on_click": lambda: self._controller._navigate_to("advanced"), }, ] - if starpilot_state.car_state.isFord: - cards.append({ - "title": tr("Ford Lateral Tuning"), - "desc": tr("Select the Ford steering strategy and tune prediction, lane-change, and speed response."), - "icon": "steering", - "on_click": lambda: self._controller._navigate_to("ford"), - }) return cards @@ -330,95 +322,6 @@ class StarPilotLateralLayout(_SettingsPage): ), ] - # ── 4. Ford Lateral Tuning ── - def ford_curvature_mode(): - return p.get_int("FordLateralMode") == 1 - - def ford_angle_mode(): - return p.get_int("FordLateralMode") == 2 - - def ford_enhanced_mode(): - return p.get_int("FordLateralMode") != 0 - - self._ford_rows = [ - SettingRow( - "FordLateralMode", "value", tr_noop("Steering Strategy"), - subtitle=tr_noop("Curvature is the tuned default. Angle is available for comparison; Native preserves the original controls."), - get_value=self._get_ford_lateral_mode, - on_click=self._show_ford_lateral_mode, - ), - SettingRow( - "FordHumanTurnDetection", "toggle", tr_noop("Manual Turn Release"), - subtitle=tr_noop("Yield during an intentional manual turn while keeping the Ford steering session ready."), - get_state=lambda: p.get_bool("FordHumanTurnDetection"), - set_state=lambda s: p.put_bool("FordHumanTurnDetection", s), - visible=ford_enhanced_mode, - ), - SettingRow( - "FordHandsFreeCluster", "toggle", tr_noop("Hands-Free Cluster Display"), - subtitle=tr_noop("Show the vehicle's hands-free assistance graphic while lateral control is active. Driver monitoring requirements do not change."), - get_state=lambda: p.get_bool("FordHandsFreeCluster"), - set_state=lambda s: p.put_bool("FordHandsFreeCluster", s), - visible=ford_enhanced_mode, - ), - SettingRow( - "FordCurvatureBlendLow", "value", tr_noop("Small-Curve Prediction"), - subtitle=tr_noop("Blend model-predicted curvature into gentle turns."), - get_value=lambda: f"{p.get_float('FordCurvatureBlendLow') * 100:.0f}%", - on_click=lambda: self._show_slider("FordCurvatureBlendLow", 0.0, 1.0, step=0.05, unit="", value_type="float"), - visible=ford_curvature_mode, - ), - SettingRow( - "FordCurvatureBlendHigh", "value", tr_noop("Large-Curve Prediction"), - subtitle=tr_noop("Blend model-predicted curvature into tighter turns."), - get_value=lambda: f"{p.get_float('FordCurvatureBlendHigh') * 100:.0f}%", - on_click=lambda: self._show_slider("FordCurvatureBlendHigh", 0.0, 1.0, step=0.05, unit="", value_type="float"), - visible=ford_curvature_mode, - ), - SettingRow( - "FordCurvatureLaneChangeFactor", "value", tr_noop("Curvature Lane-Change Factor"), - subtitle=tr_noop("Scale steering during high-speed lane changes in Curvature mode."), - get_value=lambda: f"{p.get_float('FordCurvatureLaneChangeFactor'):.2f}x", - on_click=lambda: self._show_slider("FordCurvatureLaneChangeFactor", 0.5, 1.25, step=0.05, unit="x", value_type="float"), - visible=ford_curvature_mode, - ), - SettingRow( - "FordAngleBlend", "value", tr_noop("Angle Prediction Blend"), - subtitle=tr_noop("Blend model prediction into the path-angle command."), - get_value=lambda: f"{p.get_float('FordAngleBlend') * 100:.0f}%", - on_click=lambda: self._show_slider("FordAngleBlend", 0.0, 1.0, step=0.05, unit="", value_type="float"), - visible=ford_angle_mode, - ), - SettingRow( - "FordAngleLowSpeedFactor", "value", tr_noop("Low-Speed Angle Response"), - subtitle=tr_noop("Adjust path-angle strength at lower speeds and higher curvature."), - get_value=lambda: f"{p.get_float('FordAngleLowSpeedFactor'):.2f}x", - on_click=lambda: self._show_slider("FordAngleLowSpeedFactor", 0.5, 1.5, step=0.05, unit="x", value_type="float"), - visible=ford_angle_mode, - ), - SettingRow( - "FordAngleHighSpeedFactor", "value", tr_noop("High-Speed Angle Response"), - subtitle=tr_noop("Adjust path-angle strength through larger highway curves."), - get_value=lambda: f"{p.get_float('FordAngleHighSpeedFactor'):.2f}x", - on_click=lambda: self._show_slider("FordAngleHighSpeedFactor", 0.5, 1.5, step=0.05, unit="x", value_type="float"), - visible=ford_angle_mode, - ), - SettingRow( - "FordAngleHighSpeedDamping", "value", tr_noop("High-Speed Damping"), - subtitle=tr_noop("Dampen small steering corrections at highway speed."), - get_value=lambda: f"{p.get_float('FordAngleHighSpeedDamping'):.2f}x", - on_click=lambda: self._show_slider("FordAngleHighSpeedDamping", 0.25, 1.25, step=0.05, unit="x", value_type="float"), - visible=ford_angle_mode, - ), - SettingRow( - "FordAngleLaneChangeFactor", "value", tr_noop("Angle Lane-Change Factor"), - subtitle=tr_noop("Scale steering during high-speed lane changes in Angle mode."), - get_value=lambda: f"{p.get_float('FordAngleLaneChangeFactor'):.2f}x", - on_click=lambda: self._show_slider("FordAngleLaneChangeFactor", 0.5, 1.5, step=0.05, unit="x", value_type="float"), - visible=ford_angle_mode, - ), - ] - self._manager_view = SteeringManagerView( self, header_title=tr_noop("Steering"), @@ -462,13 +365,6 @@ class StarPilotLateralLayout(_SettingsPage): parent_toggle=pt_advanced, panel_style=PANEL_STYLE, ) - self._sub_panels["ford"] = AetherSettingsView( - self, - [SettingSection(title="", rows=self._ford_rows)], - header_title=tr_noop("Ford Lateral Tuning"), - header_subtitle=tr_noop("Tune Ford-specific polynomial steering while retaining the native strategy as a fallback."), - panel_style=PANEL_STYLE, - ) self._wire_sub_panels() def _on_pause_lateral_speed_clicked(self): @@ -509,17 +405,3 @@ class StarPilotLateralLayout(_SettingsPage): current = self._params.get_int("LaneChangeSmoothing") if self._params.get_int("LaneChangeSmoothing") > 0 else 5 gui_app.push_widget(AetherSliderDialog(tr("Lane Change Smoothing"), 1, 10, 1, current, on_close, color=self.SLIDER_COLOR)) - - def _get_ford_lateral_mode(self) -> str: - return tr(("Native", "Curvature", "Angle")[max(0, min(2, self._params.get_int("FordLateralMode")))]) - - def _show_ford_lateral_mode(self): - options = [tr("Native"), tr("Curvature"), tr("Angle")] - current = options[max(0, min(2, self._params.get_int("FordLateralMode")))] - - def on_select(res): - if res == DialogResult.CONFIRM and dialog.selection in options: - self._params.put_int("FordLateralMode", options.index(dialog.selection)) - - dialog = MultiOptionDialog(tr("Ford Steering Strategy"), options, current, callback=on_select) - gui_app.push_widget(dialog) diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index 5c33d0b7a..caaf157e1 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -56,9 +56,6 @@ class DeveloperLayoutMici(NavScroller): self._long_maneuver_toggle = BigToggle("longitudinal maneuver mode", initial_state=ui_state.params.get_bool("LongitudinalManeuverMode"), toggle_callback=self._on_long_maneuver_mode) - self._lat_maneuver_toggle = BigToggle("lateral maneuver mode", - initial_state=ui_state.params.get_bool("LateralManeuverMode"), - toggle_callback=self._on_lat_maneuver_mode) self._alpha_long_toggle = BigToggle("alpha longitudinal", initial_state=ui_state.params.get_bool("AlphaLongitudinalEnabled"), toggle_callback=self._on_alpha_long_enabled) @@ -73,7 +70,6 @@ class DeveloperLayoutMici(NavScroller): self._disable_wide_road_toggle, self._joystick_toggle, self._long_maneuver_toggle, - self._lat_maneuver_toggle, self._alpha_long_toggle, self._debug_mode_toggle, ]) @@ -85,7 +81,6 @@ class DeveloperLayoutMici(NavScroller): ("DisableWideRoad", self._disable_wide_road_toggle), ("JoystickDebugMode", self._joystick_toggle), ("LongitudinalManeuverMode", self._long_maneuver_toggle), - ("LateralManeuverMode", self._lat_maneuver_toggle), ("AlphaLongitudinalEnabled", self._alpha_long_toggle), ("ShowDebugInfo", self._debug_mode_toggle), ) @@ -94,7 +89,7 @@ class DeveloperLayoutMici(NavScroller): self._disable_wide_road_toggle, self._joystick_toggle, ) - engaged_blocked_toggles = (self._long_maneuver_toggle, self._lat_maneuver_toggle, self._alpha_long_toggle) + engaged_blocked_toggles = (self._long_maneuver_toggle, self._alpha_long_toggle) # Disable toggles that require offroad for item in onroad_blocked_toggles: @@ -139,12 +134,8 @@ class DeveloperLayoutMici(NavScroller): if not long_man_enabled: self._long_maneuver_toggle.set_checked(False) ui_state.params.put_bool("LongitudinalManeuverMode", False) - - lat_man_enabled = ui_state.is_offroad() - self._lat_maneuver_toggle.set_enabled(lat_man_enabled) else: self._long_maneuver_toggle.set_enabled(False) - self._lat_maneuver_toggle.set_enabled(False) self._alpha_long_toggle.set_visible(False) # Refresh toggles from params to mirror external changes @@ -156,23 +147,12 @@ class DeveloperLayoutMici(NavScroller): ui_state.params.put_bool("LongitudinalManeuverMode", False) self._long_maneuver_toggle.set_checked(False) ui_state.params.put_bool("LateralManeuverMode", False) - self._lat_maneuver_toggle.set_checked(False) def _on_long_maneuver_mode(self, state: bool): ui_state.params.put_bool("LongitudinalManeuverMode", state) ui_state.params.put_bool("JoystickDebugMode", False) self._joystick_toggle.set_checked(False) ui_state.params.put_bool("LateralManeuverMode", False) - self._lat_maneuver_toggle.set_checked(False) - restart_needed_callback(state) - - def _on_lat_maneuver_mode(self, state: bool): - ui_state.params.put_bool("LateralManeuverMode", state) - ui_state.params.put_bool("ExperimentalMode", False) - ui_state.params.put_bool("JoystickDebugMode", False) - self._joystick_toggle.set_checked(False) - ui_state.params.put_bool("LongitudinalManeuverMode", False) - self._long_maneuver_toggle.set_checked(False) restart_needed_callback(state) def _on_alpha_long_enabled(self, state: bool): diff --git a/selfdrive/ui/translations/main_uk.ts b/selfdrive/ui/translations/main_uk.ts index dc93b11a5..e5e6f0f6c 100644 --- a/selfdrive/ui/translations/main_uk.ts +++ b/selfdrive/ui/translations/main_uk.ts @@ -1666,10 +1666,6 @@ Map Accel/Decel to Gears Прив'язати прискорення/сповільнення до передач - - Reverse Cruise Increase - Змінити довге натискання - Speed Limit Controller Контролер лімітів швидк. diff --git a/starpilot/common/assets/device_settings_layout.json b/starpilot/common/assets/device_settings_layout.json index 00cc1f9d7..a24bfa421 100644 --- a/starpilot/common/assets/device_settings_layout.json +++ b/starpilot/common/assets/device_settings_layout.json @@ -364,6 +364,238 @@ "parent_key": "QOLLateral", "settings_tier": "simple" }, + { + "key": "FordLateralMode", + "label": "Ford Steering Strategy", + "description": "Choose the Ford lateral controller. Curvature is the tuned default, Angle uses path-angle control, and Native preserves the original Ford controls.", + "picker_description": "Chooses Native, Curvature, or Angle steering on Ford vehicles.", + "data_type": "int", + "ui_type": "dropdown", + "options": [ + { + "value": 0, + "label": "Native" + }, + { + "value": 1, + "label": "Curvature" + }, + { + "value": 2, + "label": "Angle" + } + ], + "is_parent_toggle": true, + "galaxy_only": true, + "vehicle_makes": [ + "Ford" + ], + "settings_tier": "simple" + }, + { + "key": "FordHumanTurnDetection", + "label": "Manual Turn Release", + "description": "Yield during an intentional manual turn while keeping the Ford steering session ready.", + "data_type": "bool", + "ui_type": "toggle", + "parent_key": "FordLateralMode", + "visible_when_key": "FordLateralMode", + "visible_when_values": [ + 1, + 2 + ], + "galaxy_only": true, + "vehicle_makes": [ + "Ford" + ], + "settings_tier": "simple" + }, + { + "key": "FordHandsFreeCluster", + "label": "Hands-Free Cluster Display", + "description": "Show the hands-free assistance graphic on supported CAN-FD Ford clusters while lateral control is active. Driver monitoring requirements do not change.", + "data_type": "bool", + "ui_type": "toggle", + "parent_key": "FordLateralMode", + "visible_when_key": "FordLateralMode", + "visible_when_values": [ + 1, + 2 + ], + "galaxy_only": true, + "vehicle_makes": [ + "Ford" + ], + "settings_tier": "simple" + }, + { + "key": "FordCurvatureBlendLow", + "label": "Small-Curve Prediction Blend", + "description": "Blend model-predicted curvature into gentle turns. 0 uses planner curvature only; 1 uses model prediction only.", + "data_type": "float", + "ui_type": "numeric", + "min": 0.0, + "max": 1.0, + "step": 0.05, + "precision": 2, + "parent_key": "FordLateralMode", + "visible_when_key": "FordLateralMode", + "visible_when_values": [ + 1 + ], + "galaxy_only": true, + "vehicle_makes": [ + "Ford" + ], + "settings_tier": "simple" + }, + { + "key": "FordCurvatureBlendHigh", + "label": "Large-Curve Prediction Blend", + "description": "Blend model-predicted curvature into tighter turns. 0 uses planner curvature only; 1 uses model prediction only.", + "data_type": "float", + "ui_type": "numeric", + "min": 0.0, + "max": 1.0, + "step": 0.05, + "precision": 2, + "parent_key": "FordLateralMode", + "visible_when_key": "FordLateralMode", + "visible_when_values": [ + 1 + ], + "galaxy_only": true, + "vehicle_makes": [ + "Ford" + ], + "settings_tier": "simple" + }, + { + "key": "FordCurvatureLaneChangeFactor", + "label": "Curvature Lane-Change Factor", + "description": "Scale steering during high-speed lane changes in Curvature mode.", + "data_type": "float", + "ui_type": "numeric", + "min": 0.5, + "max": 1.25, + "step": 0.05, + "precision": 2, + "parent_key": "FordLateralMode", + "visible_when_key": "FordLateralMode", + "visible_when_values": [ + 1 + ], + "galaxy_only": true, + "vehicle_makes": [ + "Ford" + ], + "settings_tier": "simple" + }, + { + "key": "FordAngleBlend", + "label": "Angle Prediction Blend", + "description": "Blend model prediction into the Ford path-angle command. 0 uses planner curvature only; 1 uses model prediction only.", + "data_type": "float", + "ui_type": "numeric", + "min": 0.0, + "max": 1.0, + "step": 0.05, + "precision": 2, + "parent_key": "FordLateralMode", + "visible_when_key": "FordLateralMode", + "visible_when_values": [ + 2 + ], + "galaxy_only": true, + "vehicle_makes": [ + "Ford" + ], + "settings_tier": "simple" + }, + { + "key": "FordAngleLowSpeedFactor", + "label": "Low-Speed Angle Response", + "description": "Adjust path-angle strength at lower speeds and higher curvature.", + "data_type": "float", + "ui_type": "numeric", + "min": 0.5, + "max": 1.5, + "step": 0.05, + "precision": 2, + "parent_key": "FordLateralMode", + "visible_when_key": "FordLateralMode", + "visible_when_values": [ + 2 + ], + "galaxy_only": true, + "vehicle_makes": [ + "Ford" + ], + "settings_tier": "simple" + }, + { + "key": "FordAngleHighSpeedFactor", + "label": "High-Speed Angle Response", + "description": "Adjust path-angle strength through larger highway curves.", + "data_type": "float", + "ui_type": "numeric", + "min": 0.5, + "max": 1.5, + "step": 0.05, + "precision": 2, + "parent_key": "FordLateralMode", + "visible_when_key": "FordLateralMode", + "visible_when_values": [ + 2 + ], + "galaxy_only": true, + "vehicle_makes": [ + "Ford" + ], + "settings_tier": "simple" + }, + { + "key": "FordAngleHighSpeedDamping", + "label": "High-Speed Angle Damping", + "description": "Dampen small steering corrections at highway speed.", + "data_type": "float", + "ui_type": "numeric", + "min": 0.25, + "max": 1.25, + "step": 0.05, + "precision": 2, + "parent_key": "FordLateralMode", + "visible_when_key": "FordLateralMode", + "visible_when_values": [ + 2 + ], + "galaxy_only": true, + "vehicle_makes": [ + "Ford" + ], + "settings_tier": "simple" + }, + { + "key": "FordAngleLaneChangeFactor", + "label": "Angle Lane-Change Factor", + "description": "Scale steering during high-speed lane changes in Angle mode.", + "data_type": "float", + "ui_type": "numeric", + "min": 0.5, + "max": 1.5, + "step": 0.05, + "precision": 2, + "parent_key": "FordLateralMode", + "visible_when_key": "FordLateralMode", + "visible_when_values": [ + 2 + ], + "galaxy_only": true, + "vehicle_makes": [ + "Ford" + ], + "settings_tier": "simple" + }, { "key": "VASMEnabled", "label": "Enable V-ASM", diff --git a/starpilot/common/favorite_slots.py b/starpilot/common/favorite_slots.py index 9da1b7b02..ff9dd56d0 100644 --- a/starpilot/common/favorite_slots.py +++ b/starpilot/common/favorite_slots.py @@ -128,6 +128,9 @@ def build_favorite_slot_options(is_eligible_param: Callable[[str], bool], *, options = [dict(option) for option in FAVORITE_ACTION_OPTIONS] for key, param_data in catalog_map.items(): + if param_data.get("galaxy_only"): + continue + ui_type = str(param_data.get("ui_type") or "") data_type = str(param_data.get("data_type") or "") raw_options = param_data.get("options") diff --git a/starpilot/common/safe_mode.py b/starpilot/common/safe_mode.py index 8b2cdb3d1..71e393e35 100644 --- a/starpilot/common/safe_mode.py +++ b/starpilot/common/safe_mode.py @@ -91,7 +91,6 @@ SAFE_MODE_MANAGED_KEYS = ( "MapGears", "MapAcceleration", "MapDeceleration", - "ReverseCruise", "SetSpeedOffset", "WeatherPresets", "IncreaseFollowingLowVisibility", diff --git a/starpilot/common/starpilot_variables.py b/starpilot/common/starpilot_variables.py index 033e5b7be..69281f1be 100644 --- a/starpilot/common/starpilot_variables.py +++ b/starpilot/common/starpilot_variables.py @@ -1278,7 +1278,6 @@ class StarPilotVariables: map_gears = self.get_value("MapGears", condition=quality_of_life_longitudinal) toggle.map_acceleration = self.get_value("MapAcceleration", condition=map_gears) toggle.map_deceleration = self.get_value("MapDeceleration", condition=map_gears) - toggle.reverse_cruise_increase = self.get_value("ReverseCruise", condition=quality_of_life_cruise) toggle.set_speed_offset = self.get_value("SetSpeedOffset", cast=float, condition=(quality_of_life_longitudinal and not pcm_cruise), conversion=(1 if toggle.is_metric else CV.MPH_TO_KPH)) toggle.weather_presets = self.get_value("WeatherPresets", condition=quality_of_life_longitudinal) toggle.increase_following_distance_low_visibility = self.get_value("IncreaseFollowingLowVisibility", cast=float, condition=toggle.weather_presets) diff --git a/starpilot/common/tests/test_favorite_slots.py b/starpilot/common/tests/test_favorite_slots.py index 342254a03..d7b48e658 100644 --- a/starpilot/common/tests/test_favorite_slots.py +++ b/starpilot/common/tests/test_favorite_slots.py @@ -88,6 +88,18 @@ def test_shared_settings_catalog_is_common_and_well_formed(): assert len(keys) == len(set(keys)) +def test_galaxy_only_ford_controls_are_not_available_to_device_favorites(): + ford_keys = { + "FordLateralMode", + "FordHumanTurnDetection", + "FordHandsFreeCluster", + } + + options = build_favorite_slot_options(lambda _key: True, alpha_longitudinal_available=True) + + assert ford_keys.isdisjoint({option["key"] for option in options}) + + def test_load_favorite_slots_filters_non_bool_keys(): params = FakeParams() params.put(FAVORITE_SLOTS_PARAM, [ diff --git a/starpilot/controls/lib/starpilot_vcruise.py b/starpilot/controls/lib/starpilot_vcruise.py index 9c16f75b8..fcd60bce8 100644 --- a/starpilot/controls/lib/starpilot_vcruise.py +++ b/starpilot/controls/lib/starpilot_vcruise.py @@ -11,6 +11,8 @@ from openpilot.starpilot.controls.lib.speed_limit_controller import SpeedLimitCo from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import ( get_force_stop_distance_bias, get_force_stop_handoff_distance, + get_force_stop_low_speed_hold, + get_force_stop_reanchor_speed_tolerance, ) CSC_MIN_SPEED = CITY_SPEED_LIMIT * CV.MPH_TO_MS @@ -166,6 +168,7 @@ class StarPilotVCruise: self.override_force_stop_timer = 0 self.force_stop_timer = 0.0 + self.force_stop_entry_speed = None self.activation_gate_active = False self.standstill_force_stop_hold = False self.standstill_force_stop_clear_since = 0.0 @@ -358,6 +361,8 @@ class StarPilotVCruise: car_params = sm["carParams"] except (KeyError, IndexError, TypeError, AttributeError): car_params = None + force_stop_reanchor_speed_tolerance = get_force_stop_reanchor_speed_tolerance(car_params) + force_stop_low_speed_hold = get_force_stop_low_speed_hold(car_params) 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 @@ -490,6 +495,17 @@ class StarPilotVCruise: not stop_light_detected and not dash_active ) + low_speed_stop_commit = bool( + light_stop_cleared and + force_stop_low_speed_hold is not None and + self.force_stop_entry_speed is not None and + v_ego <= force_stop_low_speed_hold and + v_ego < self.force_stop_entry_speed - 0.25 + ) + # The Santa Fe's model stop signal can blink off after the car has already + # committed to the stop. Do not turn that late dropout into a throttle + # release while the vehicle is still rolling through the sign. + light_stop_cleared &= not low_speed_stop_commit if light_stop_cleared: if self.force_stop_light_clear_since is None: self.force_stop_light_clear_since = now @@ -593,6 +609,8 @@ class StarPilotVCruise: v_cruise = 0.0 elif force_stop_enabled and not self.override_force_stop: + if self.force_stop_entry_speed is None and not sm["carState"].standstill: + self.force_stop_entry_speed = v_ego self.forcing_stop |= not sm["carState"].standstill or self.standstill_force_stop_hold if self.standstill_force_stop_hold: @@ -614,7 +632,12 @@ class StarPilotVCruise: not dash_active and self.tracked_model_length > force_stop_handoff_m and not model_wants_stop and - model_length > self.tracked_model_length + FORCE_STOP_DISTANCE_REANCHOR_MIN_GAP + model_length > self.tracked_model_length + FORCE_STOP_DISTANCE_REANCHOR_MIN_GAP and + ( + force_stop_reanchor_speed_tolerance is None or + self.force_stop_entry_speed is None or + v_ego >= self.force_stop_entry_speed - force_stop_reanchor_speed_tolerance + ) ): self.tracked_model_length = model_length else: @@ -646,6 +669,7 @@ class StarPilotVCruise: else: self.forcing_stop = False + self.force_stop_entry_speed = None self._clear_standstill_force_stop_hold() # Latch is only meaningful during an active force-stop cycle self.stop_sign_confirmed = False diff --git a/starpilot/system/the_galaxy/assets/components/tools/device_settings.js b/starpilot/system/the_galaxy/assets/components/tools/device_settings.js index b16823652..20b875bd0 100644 --- a/starpilot/system/the_galaxy/assets/components/tools/device_settings.js +++ b/starpilot/system/the_galaxy/assets/components/tools/device_settings.js @@ -11,7 +11,7 @@ const FAVORITE_OPTION_COLLATOR = new Intl.Collator(undefined, { numeric: true, s const FAVORITE_ACTION_PREFIX = "__starpilot_favorite_action__:" const GALAXY_DEVELOPER_MODE_KEY = "GalaxyDeveloperMode" const HIDDEN_SECTION_NAMES = new Set(["Model & Customization"]) -const HIDDEN_SETTING_KEYS = new Set(["HumanAcceleration", "ReverseCruise"]) +const HIDDEN_SETTING_KEYS = new Set(["HumanAcceleration"]) const GM_MAKES = ["Buick", "Cadillac", "Chevrolet", "GMC", "Holden"] const HKG_MAKES = ["Genesis", "Hyundai", "Kia"] const VEHICLE_SETTING_MAKES = { @@ -96,16 +96,22 @@ function normalizeVehicleMake(value) { } function isVehicleSettingVisible(section, param) { - if (section.name !== "Vehicle") return true - const allowedMakes = VEHICLE_SETTING_MAKES[param.key] + const allowedMakes = param.vehicle_makes || (section.name === "Vehicle" ? VEHICLE_SETTING_MAKES[param.key] : null) if (!allowedMakes) return true const selectedMake = normalizeVehicleMake(state.values.CarMake) return allowedMakes.some(make => normalizeVehicleMake(make) === selectedMake) } +function matchesSettingValueCondition(param) { + if (!param.visible_when_key) return true + const allowedValues = Array.isArray(param.visible_when_values) ? param.visible_when_values : [] + const currentValue = toSelectValue(state.values[param.visible_when_key]) + return allowedValues.some(value => toSelectValue(value) === currentValue) +} + function isSettingVisible(section, param) { // This policy controls Galaxy rendering only; hidden params retain their stored values. - if (HIDDEN_SETTING_KEYS.has(param.key) || !isVehicleSettingVisible(section, param)) return false + if (HIDDEN_SETTING_KEYS.has(param.key) || !isVehicleSettingVisible(section, param) || !matchesSettingValueCondition(param)) return false if (param.requires_capability && !state.values[param.requires_capability]) return false if (RADAR_REQUIRED_KEYS.has(param.key) && !state.values.HasRadar) return false if (param.key === "AlphaLongitudinalEnabled" && !state.values.AlphaLongitudinalAvailable) return false diff --git a/starpilot/system/the_galaxy/tests/test_device_settings_layout.py b/starpilot/system/the_galaxy/tests/test_device_settings_layout.py index 65c832517..22a6ee8ce 100644 --- a/starpilot/system/the_galaxy/tests/test_device_settings_layout.py +++ b/starpilot/system/the_galaxy/tests/test_device_settings_layout.py @@ -35,7 +35,7 @@ def test_galaxy_layout_removes_obsolete_and_duplicate_controls(): all_keys = {key for params in sections.values() for key in params} assert "Model & Customization" not in sections - assert {"HumanAcceleration", "ReverseCruise"}.isdisjoint(all_keys) + assert "HumanAcceleration" not in all_keys assert "DisableWideRoad" in sections["Visual (Display & UI)"] assert sum( param.get("key") == "DisableWideRoad" @@ -66,6 +66,58 @@ def test_galaxy_layout_contains_basic_mode_controls(): assert {"AlphaLongitudinalEnabled", "ForceOffroad", "GalaxyDeveloperMode"} <= sections["Developer"].keys() +def test_ford_lateral_controls_are_ford_only_and_galaxy_only(): + lateral = _params_by_section(_layout())["Lateral (Steering)"] + ford_keys = { + "FordLateralMode", + "FordHumanTurnDetection", + "FordHandsFreeCluster", + "FordCurvatureBlendLow", + "FordCurvatureBlendHigh", + "FordCurvatureLaneChangeFactor", + "FordAngleBlend", + "FordAngleLowSpeedFactor", + "FordAngleHighSpeedFactor", + "FordAngleHighSpeedDamping", + "FordAngleLaneChangeFactor", + } + + assert ford_keys <= lateral.keys() + assert all(lateral[key]["galaxy_only"] is True for key in ford_keys) + assert all(lateral[key]["vehicle_makes"] == ["Ford"] for key in ford_keys) + assert all(lateral[key]["settings_tier"] == "simple" for key in ford_keys) + + mode = lateral["FordLateralMode"] + assert mode["ui_type"] == "dropdown" + assert mode["data_type"] == "int" + assert mode["is_parent_toggle"] is True + assert {option["label"]: option["value"] for option in mode["options"]} == { + "Native": 0, + "Curvature": 1, + "Angle": 2, + } + assert _declared_default("FordLateralMode") == "1" + + common_keys = {"FordHumanTurnDetection", "FordHandsFreeCluster"} + curvature_keys = {"FordCurvatureBlendLow", "FordCurvatureBlendHigh", "FordCurvatureLaneChangeFactor"} + angle_keys = { + "FordAngleBlend", + "FordAngleLowSpeedFactor", + "FordAngleHighSpeedFactor", + "FordAngleHighSpeedDamping", + "FordAngleLaneChangeFactor", + } + assert all(lateral[key]["visible_when_values"] == [1, 2] for key in common_keys) + assert all(lateral[key]["visible_when_values"] == [1] for key in curvature_keys) + assert all(lateral[key]["visible_when_values"] == [2] for key in angle_keys) + assert all(lateral[key]["parent_key"] == "FordLateralMode" for key in ford_keys - {"FordLateralMode"}) + + device_ui_root = REPO_ROOT / "selfdrive/ui" + for path in device_ui_root.rglob("*.py"): + source = path.read_text(encoding="utf-8") + assert all(key not in source for key in ford_keys) + + def test_device_shutdown_uses_literal_hours(): device_shutdown = _params_by_section(_layout())["Device & Data"]["DeviceShutdown"] @@ -290,7 +342,11 @@ def test_pip_preview_is_under_driving_screen_widgets_and_configured_only_in_gala assert _declared_default("PIPPreviewEnabled") == "0" assert _declared_default("PIPPreviewShowOnBlinker") == "0" assert _declared_default("PIPPreviewShowOnBSM") == "0" - assert '"{\\"width\\":1928,\\"height\\":1208,\\"center_left\\":[315,548],\\"center_right\\":[1571,539],\\"crop_size\\":580}"' in PARAM_KEYS_PATH.read_text(encoding="utf-8") + annotation_default = ( + '"{\\"width\\":1928,\\"height\\":1208,\\"center_left\\":[315,548],' + + '\\"center_right\\":[1571,539],\\"crop_size\\":580}"' + ) + assert annotation_default in PARAM_KEYS_PATH.read_text(encoding="utf-8") physical_settings = ( REPO_ROOT / "selfdrive/ui/layouts/settings/starpilot/aethergrid.py", diff --git a/starpilot/system/the_galaxy/tests/test_navigation_params.py b/starpilot/system/the_galaxy/tests/test_navigation_params.py index 176559455..072d4c5ed 100644 --- a/starpilot/system/the_galaxy/tests/test_navigation_params.py +++ b/starpilot/system/the_galaxy/tests/test_navigation_params.py @@ -95,10 +95,11 @@ def _params_client(monkeypatch, values, device_type): the_galaxy, "_get_param_type_info", lambda: ( - {"AlphaLongitudinalEnabled", "ForceOffroad"}, + {"AlphaLongitudinalEnabled", "ForceOffroad", "FordLateralMode"}, { "AlphaLongitudinalEnabled": bool, "ForceOffroad": bool, + "FordLateralMode": int, }, ), ) @@ -255,6 +256,19 @@ def test_device_settings_layout_asset_is_served_from_common_catalog(monkeypatch) assert response.get_json() == the_galaxy.load_settings_catalog() +def test_ford_lateral_mode_is_editable_through_galaxy(monkeypatch): + client, fake_params = _params_client(monkeypatch, { + "CarMake": "Ford", + "FordLateralMode": 1, + }, "mici") + + response = client.put("/api/params", json={"key": "FordLateralMode", "value": 2, "label": "Angle"}) + + assert response.status_code == 200 + assert fake_params.values["FordLateralMode"] == "2" + assert ("FordLateralMode", "2") in fake_params.writes + + def test_favorite_slot_options_include_virtual_cruise_actions(monkeypatch): monkeypatch.setattr(the_galaxy, "_favorite_slot_options", None) monkeypatch.setattr(the_galaxy, "_get_param_type_info", lambda: (set(), {})) diff --git a/system/manager/launch_param_migrations.py b/system/manager/launch_param_migrations.py index adf09a002..6df89b322 100644 --- a/system/manager/launch_param_migrations.py +++ b/system/manager/launch_param_migrations.py @@ -22,6 +22,7 @@ DEVELOPER_METRIC_DISPLAY_KEYS = ( ) DEVICE_SHUTDOWN_KEY = "DeviceShutdown" CAMERA_VIEW_KEY = "CameraView" +REVERSE_CRUISE_KEY = "ReverseCruise" DEFAULT_STEER_KP = 0.6 LEGACY_STEER_KP = 0.7 @@ -38,6 +39,7 @@ LANE_CHANGE_SMOOTHING_MIGRATION_MARKER = ".starpilot_lane_change_smoothing_defau SPEED_LIMIT_VISIBILITY_MIGRATION_MARKER = ".starpilot_speed_limit_visibility_v1" DEVICE_SHUTDOWN_HOURS_MIGRATION_MARKER = ".starpilot_device_shutdown_hours_v1" CAMERA_VIEW_DEFAULT_MIGRATION_MARKER = ".starpilot_camera_view_default_v1" +REVERSE_CRUISE_REMOVAL_MIGRATION_MARKER = ".starpilot_remove_reverse_cruise_v1" MARKER_DIRNAME = ".starpilot_param_migrations" LATERAL_METHOD_PARAM_SUFFIXES = ( @@ -145,6 +147,10 @@ def _camera_view_default_marker_path(params: ParamsLike) -> Path: return _marker_dir_path(params) / CAMERA_VIEW_DEFAULT_MIGRATION_MARKER +def _reverse_cruise_removal_marker_path(params: ParamsLike) -> Path: + return _marker_dir_path(params) / REVERSE_CRUISE_REMOVAL_MIGRATION_MARKER + + def _marker_dir_path(params: ParamsLike) -> Path: params_path = Path(params.get_param_path()) # Params.clear_all() removes unknown files inside the params directory, so @@ -326,6 +332,15 @@ def _apply_camera_view_default_migration(params: ParamsLike, marker: Path) -> No marker.touch() +def _remove_reverse_cruise_param(params: ParamsLike, marker: Path) -> None: + if marker.exists(): + return + + marker.parent.mkdir(parents=True, exist_ok=True) + Path(params.get_param_path(REVERSE_CRUISE_KEY)).unlink(missing_ok=True) + marker.touch() + + def apply_launch_param_migrations(params: ParamsLike, marker_path: Path | None = None, branch_defaults_marker_path: Path | None = None, acceleration_profile_marker_path: Path | None = None, @@ -336,7 +351,8 @@ def apply_launch_param_migrations(params: ParamsLike, marker_path: Path | None = lane_change_smoothing_marker_path: Path | None = None, speed_limit_visibility_marker_path: Path | None = None, device_shutdown_hours_marker_path: Path | None = None, - camera_view_default_marker_path: Path | None = None) -> None: + camera_view_default_marker_path: Path | None = None, + reverse_cruise_removal_marker_path: Path | None = None) -> None: _apply_legacy_launch_param_migrations(params, marker_path or _default_marker_path(params)) # Keep branch-default rollout on its own marker so older installs that already # have the legacy marker still receive this one-time param reset. @@ -368,6 +384,9 @@ def apply_launch_param_migrations(params: ParamsLike, marker_path: Path | None = _apply_camera_view_default_migration( params, camera_view_default_marker_path or _camera_view_default_marker_path(params) ) + _remove_reverse_cruise_param( + params, reverse_cruise_removal_marker_path or _reverse_cruise_removal_marker_path(params) + ) def main() -> int: diff --git a/system/manager/manager.py b/system/manager/manager.py index 034b2f4c4..b38009dcb 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -70,7 +70,9 @@ STARPILOT_PARAMS_CACHE_MIGRATION_FLAG = Path("/data") / "starpilot_params_cache_ STARPILOT_DEFAULT_MODEL_MIGRATION_FLAG = Path("/data") / "starpilot_default_model_rdf_v4" STARPILOT_CE_MODEL_STOP_TIME_MIGRATION_FLAG = Path("/data") / "starpilot_ce_model_stop_time_v2" STARPILOT_LEGACY_CACHE_MARKER_KEYS = ("RemapCancelToDistance",) -STARPILOT_REMOVED_PARAM_KEYS = ("CoastUpToLeads", "HumanAcceleration", "HumanFollowing", "PrioritizeSmoothFollowing") +STARPILOT_REMOVED_PARAM_KEYS = ( + "CoastUpToLeads", "HumanAcceleration", "HumanFollowing", "PrioritizeSmoothFollowing", "ReverseCruise", +) LEGACY_CARMODEL_MIGRATIONS = { "CHEVROLET_BOLT_CC_2019_2021": "CHEVROLET_BOLT_CC_2018_2021", } diff --git a/system/manager/test/test_launch_param_migrations.py b/system/manager/test/test_launch_param_migrations.py index d8624d6df..eedeb25d4 100644 --- a/system/manager/test/test_launch_param_migrations.py +++ b/system/manager/test/test_launch_param_migrations.py @@ -14,6 +14,7 @@ from openpilot.system.manager.launch_param_migrations import ( LAUNCH_PARAM_MIGRATION_MARKER, LATERAL_METHOD_REBRAND_MIGRATION_MARKER, MARKER_DIRNAME, + REVERSE_CRUISE_REMOVAL_MIGRATION_MARKER, STANDARD_ACCELERATION_PROFILE, SPEED_LIMIT_VISIBILITY_MIGRATION_MARKER, LEGACY_UI_SELECTION_MIGRATION_MARKER, @@ -177,6 +178,16 @@ def test_apply_launch_param_migrations_preserves_custom_camera_view(tmp_path): assert params.get_int("CameraView") == 0 +def test_apply_launch_param_migrations_removes_reverse_cruise_param(tmp_path): + params = FileBackedFakeParams(tmp_path / "params") + params.put_bool("ReverseCruise", True) + + apply_launch_param_migrations(params) + + assert not Path(params.get_param_path("ReverseCruise")).exists() + assert marker_path(tmp_path, REVERSE_CRUISE_REMOVAL_MIGRATION_MARKER).is_file() + + def test_apply_launch_param_migrations_applies_branch_defaults_for_existing_installs(tmp_path): params = FileBackedFakeParams(tmp_path / "params") diff --git a/system/manager/test/test_manager.py b/system/manager/test/test_manager.py index 2ad3eded5..6b07077d1 100644 --- a/system/manager/test/test_manager.py +++ b/system/manager/test/test_manager.py @@ -392,10 +392,12 @@ class TestManager: "CoastUpToLeads": True, "HumanAcceleration": True, "HumanFollowing": True, + "ReverseCruise": True, }) params_cache = FileBackedFakeParams(tmp_path / "cache", { "HumanFollowing": False, "PrioritizeSmoothFollowing": True, + "ReverseCruise": True, }) manager.cleanup_removed_starpilot_params(params, params_cache) @@ -403,8 +405,10 @@ class TestManager: assert not Path(params.get_param_path("CoastUpToLeads")).exists() assert not Path(params.get_param_path("HumanAcceleration")).exists() assert not Path(params.get_param_path("HumanFollowing")).exists() + assert not Path(params.get_param_path("ReverseCruise")).exists() assert not Path(params_cache.get_param_path("HumanFollowing")).exists() assert not Path(params_cache.get_param_path("PrioritizeSmoothFollowing")).exists() + assert not Path(params_cache.get_param_path("ReverseCruise")).exists() def test_migrate_legacy_starpilot_params_cache_copies_marker_sources(self, tmp_path, monkeypatch): monkeypatch.setattr(manager, "STARPILOT_PARAMS_CACHE_MIGRATION_FLAG", tmp_path / "starpilot_params_cache_v1") diff --git a/tools/StarPilot/feasibleparams.txt b/tools/StarPilot/feasibleparams.txt index d462f47c0..e80425930 100644 --- a/tools/StarPilot/feasibleparams.txt +++ b/tools/StarPilot/feasibleparams.txt @@ -266,7 +266,6 @@ RelaxedJerkSpeedDecrease RelaxedPersonalityProfile RemapCancelToDistance RemoteStartBootsComma -ReverseCruise RoadEdgesWidth RoadNameUI RotatingWheel