This commit is contained in:
firestar5683
2026-08-17 20:50:35 -05:00
parent d3cfea6a8a
commit 7177b312f4
22 changed files with 473 additions and 66 deletions
Binary file not shown.
+2
View File
@@ -617,6 +617,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"VisionSpeedLimitAutoBookmark", {PERSISTENT, BOOL, "0", "0", 0}},
{"VisionSpeedLimitAutoPreserveSegment", {PERSISTENT, BOOL, "0", "0", 0}},
{"VisionSpeedLimitDetection", {PERSISTENT, BOOL, "1", "0", 0}},
{"VisionSpeedLimitLowLimitFilter", {PERSISTENT, BOOL, "0", "0", 0}},
{"VisionSpeedLimitLowLimitThreshold", {PERSISTENT, INT, "25", "25", 0}},
{"VisionSpeedLimitTrainingCollector", {PERSISTENT, BOOL, "1", "1", 0}},
{"StandardFollow", {PERSISTENT, FLOAT, "1.45", "1.45", 2}},
{"StandardFollowHigh", {PERSISTENT, FLOAT, "1.2", "1.2", 2}},
Binary file not shown.
+3 -4
View File
@@ -233,7 +233,9 @@ class CarState(CarStateBase):
return button_events
def create_lkas_button_events(self, cp: CANParser, prev_lda_button: int) -> list[structs.CarState.ButtonEvent]:
if self.CP.carFingerprint == CAR.HYUNDAI_SONATA_HYBRID:
if self.CP.carFingerprint == CAR.HYUNDAI_SONATA:
self.lda_button = int(cp.vl["BCM_PO_11"]["LDA_BTN"]) if cp.ts_nanos["BCM_PO_11"]["LDA_BTN"] > 0 else 0
elif self.CP.carFingerprint == CAR.HYUNDAI_SONATA_HYBRID:
self.lda_button = self.get_sonata_hybrid_lkas_button_state(cp)
else:
source_states = (
@@ -425,9 +427,6 @@ class CarState(CarStateBase):
*create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise}),
*lkas_button_events]
if getattr(self.FPCP, "flags", 0) & HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING:
ret.cruiseState.available = self.update_main_cruise(ret)
ret.blockPcmEnable = not self.recent_button_interaction()
# low speed steer alert hysteresis logic (only for cars with steer cut off above 10 m/s)
@@ -676,20 +676,14 @@ class TestHyundaiFingerprint:
)
assert not (minimal_fpcp.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_SYNC)
def test_classic_hyundai_long_tracks_main_cruise_state(self):
@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):
toggles = get_test_toggles()
classic_cp = CarInterface.get_params(CAR.HYUNDAI_ELANTRA_2021, gen_empty_fingerprint(), [], True, False, False, toggles)
classic_fpcp = CarInterface.get_starpilot_params(
CAR.HYUNDAI_ELANTRA_2021, gen_empty_fingerprint(), [], classic_cp, 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 classic_fpcp.flags & HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING
car_state = CarState(classic_cp, classic_fpcp)
ret = SimpleNamespace(
cruiseState=SimpleNamespace(available=True),
buttonEvents=[structs.CarState.ButtonEvent(pressed=True, type=ButtonType.mainCruise)],
)
assert car_state.update_main_cruise(ret)
assert not (FPCP.flags & HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING)
ioniq_cp = CarInterface.get_params(CAR.HYUNDAI_IONIQ_6, gen_empty_fingerprint(), [], True, False, False, toggles)
ioniq_fpcp = CarInterface.get_starpilot_params(
@@ -1486,7 +1480,7 @@ class TestHyundaiFingerprint:
assert Bus.alt not in can_parsers
def test_sonata_alt_bus_clu13_swl_stat_lkas_button_event(self):
def test_sonata_uses_main_bus_bcm_lkas_button_event(self):
toggles = get_test_toggles()
fingerprint = gen_empty_fingerprint()
fingerprint[0][0x391] = 8
@@ -1498,16 +1492,26 @@ class TestHyundaiFingerprint:
can_parsers = car_state.get_can_parsers(CP)
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
assert Bus.alt not in can_parsers
def update(lkas_button: int, frame: int):
msg = packer.make_can_msg("CLU13", 1, {
"CF_Clu_SWL_Stat": lkas_button,
})
can_parsers[Bus.alt].update([(frame, [msg])])
msgs = [
packer.make_can_msg("CLU13", 0, {
"CF_Clu_LdwsLkasSW": 0,
"CF_Clu_SWL_Stat": 4,
}),
packer.make_can_msg("BCM_PO_11", 0, {
"LDA_BTN": lkas_button,
}),
]
can_parsers[Bus.pt].update([(frame, msgs)])
return car_state.update(can_parsers, toggles)[0]
update(0, 1)
ret = update(4, 2)
ret = update(1, 2)
assert any(be.type == ButtonType.lkas and be.pressed for be in ret.buttonEvents)
ret = update(0, 3)
assert any(be.type == ButtonType.lkas and not be.pressed for be in ret.buttonEvents)
def test_genesis_g90_does_not_use_alt_bus_lkas_parser(self):
+2 -12
View File
@@ -973,18 +973,8 @@ KIA_EV6_GT_LINE_LONG_TUNING_VDS_PREFIXES = frozenset({
})
# These classic HKG platforms publish the LKAS button on CLU13 over the alt bus.
# Keep G90 excluded until its alt-bus path is route-proven without the recent
# engage/disengage regression.
ALT_BUS_LDA_BUTTON_CARS = frozenset({
CAR.HYUNDAI_SONATA,
})
# On these Sonata layouts the alt-bus LKAS button pulses through the CLU13
# steering-wheel-status field instead of the dedicated LKAS bit.
ALT_BUS_LDA_BUTTON_SWL_STAT_CARS = frozenset({
CAR.HYUNDAI_SONATA,
})
ALT_BUS_LDA_BUTTON_CARS = frozenset()
ALT_BUS_LDA_BUTTON_SWL_STAT_CARS = frozenset()
def hyundai_cancel_button_enables_cruise(car_fingerprint) -> bool:
-3
View File
@@ -232,9 +232,6 @@ class CarInterfaceBase(ABC):
fp_ret.flags |= int(HondaStarPilotFlags.HAS_CAMERA_MESSAGES)
elif platform in HYUNDAI:
if CP.openpilotLongitudinalControl and not (CP.flags & HyundaiFlags.CANFD):
fp_ret.flags |= HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING.value
if candidate in CANFD_CAR:
hda2 = Ecu.adas in [fw.ecu for fw in car_fw]
CAN = CanBus(None, fingerprint, bool(CP.flags & HyundaiFlags.CANFD_LKA_STEERING))
@@ -513,13 +513,8 @@ class CarController(CarControllerBase):
pcm_accel_cmd = float(np.clip(pcm_accel_cmd, self.params.ACCEL_MIN, self.params.ACCEL_MAX))
main_accel_cmd = 0. if self.CP.flags & ToyotaFlags.SECOC.value else pcm_accel_cmd
# Toyota's physical distance-button hold can collide with StarPilot's wheel-button
# actions and trip a temporary EPS fault. Suppress native long-press handling while
# the physical gap button is held so ACC only sees the hold as a plain button press.
allow_long_press = 0 if bool(getattr(CS, "distance_button", False)) else None
can_sends.append(toyotacan.create_accel_command(self.packer, main_accel_cmd, pcm_cancel_cmd, self.permit_braking, self.standstill_req, lead,
CS.acc_type, fcw_alert, self.distance_button, starpilot_toggles.reverse_cruise_increase,
allow_long_press))
CS.acc_type, fcw_alert, self.distance_button, starpilot_toggles.reverse_cruise_increase))
if self.CP.flags & ToyotaFlags.SECOC.value:
acc_cmd_2 = toyotacan.create_accel_command_2(self.packer, pcm_accel_cmd)
acc_cmd_2 = add_mac(self.secoc_key,
@@ -538,9 +533,8 @@ class CarController(CarControllerBase):
if self.CP.carFingerprint in UNSUPPORTED_DSU_CAR:
can_sends.append(toyotacan.create_acc_cancel_command(self.packer))
else:
allow_long_press = 0 if bool(getattr(CS, "distance_button", False)) else None
can_sends.append(toyotacan.create_accel_command(self.packer, 0, pcm_cancel_cmd, True, False, lead, CS.acc_type, False,
self.distance_button, starpilot_toggles.reverse_cruise_increase, allow_long_press))
self.distance_button, starpilot_toggles.reverse_cruise_increase))
# *** hud ui ***
if self.CP.carFingerprint != CAR.TOYOTA_PRIUS_V:
@@ -752,21 +752,21 @@ class TestToyotaCarController:
assert parser.vl["LKAS_HUD"]["LEFT_LINE"] == 0
assert parser.vl["LKAS_HUD"]["RIGHT_LINE"] == 0
def test_acc_control_can_suppress_long_press_behavior_while_gap_button_is_held(self):
def test_acc_control_uses_valid_long_press_modes(self):
packer = CANPacker(DBC[CAR.TOYOTA_HIGHLANDER_TSS2][Bus.pt])
parser = CANParser(DBC[CAR.TOYOTA_HIGHLANDER_TSS2][Bus.pt], [("ACC_CONTROL", 0)], 0)
default_msg = toyotacan.create_accel_command(
normal_msg = toyotacan.create_accel_command(
packer, 0.0, False, True, False, False, 1, False, 0, False,
)
parser.update([(1, [default_msg])])
parser.update([(1, [normal_msg])])
assert parser.vl["ACC_CONTROL"]["ALLOW_LONG_PRESS"] == 1
suppressed_msg = toyotacan.create_accel_command(
packer, 0.0, False, True, False, False, 1, False, 0, False, allow_long_press=0,
reverse_msg = toyotacan.create_accel_command(
packer, 0.0, False, True, False, False, 1, False, 0, True,
)
parser.update([(1, [suppressed_msg])])
assert parser.vl["ACC_CONTROL"]["ALLOW_LONG_PRESS"] == 0
parser.update([(1, [reverse_msg])])
assert parser.vl["ACC_CONTROL"]["ALLOW_LONG_PRESS"] == 2
def test_auto_brake_hold_sends_modified_pre_collision_after_timer(self):
controller = self._make_controller()
+2 -3
View File
@@ -41,10 +41,9 @@ def create_lta_steer_command_2(packer, frame):
def create_accel_command(packer, accel, pcm_cancel, permit_braking, standstill_req, lead, acc_type, fcw_alert,
distance, reverse_cruise_active, allow_long_press=None):
distance, reverse_cruise_active):
# TODO: find the exact canceling bit that does not create a chime
if allow_long_press is None:
allow_long_press = 2 if reverse_cruise_active else 1
allow_long_press = 2 if reverse_cruise_active else 1
values = {
"ACCEL_CMD": accel,
+7
View File
@@ -12,6 +12,7 @@ from openpilot.common.swaglog import cloudlog
from opendbc.car.car_helpers import interfaces
from opendbc.car.chrysler.values import pacifica_hybrid_aol_stock_acc_mode
from opendbc.car.gm.values import CAR as GM_CAR
from opendbc.car.honda.values import CAR as HONDA_CAR
from opendbc.car.nissan.values import CAR as NISSAN_CAR
from opendbc.car.vehicle_model import VehicleModel
from openpilot.selfdrive.controls.lib.drive_helpers import MAX_LATERAL_JERK, clip_curvature, get_lateral_active
@@ -23,6 +24,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import (
BOLT_2018_2021_STEER_RATIO_TEST_SCALE,
LatControlTorque,
get_bolt_2017_steer_ratio_scale,
get_honda_accord_steer_ratio_scale,
)
from openpilot.selfdrive.controls.lib.longcontrol import LongControl
from openpilot.selfdrive.car.cruise_state import should_cancel_stock_cruise
@@ -394,10 +396,15 @@ class Controls:
lp = self.sm['liveParameters']
x = max(lp.stiffnessFactor, 0.1)
sr = max(lp.steerRatio, 0.1)
custom_accord_ratio = getattr(self.starpilot_toggles, "steerRatio", self.CP.steerRatio)
accord_ratio_is_explicit = getattr(self.starpilot_toggles, "use_custom_steerRatio", False) and \
abs(custom_accord_ratio - self.CP.steerRatio) > 0.01
if self.CP.carFingerprint == GM_CAR.CHEVROLET_BOLT_CC_2017:
sr *= get_bolt_2017_steer_ratio_scale(CS.vEgo)
elif self.CP.carFingerprint == GM_CAR.CHEVROLET_BOLT_CC_2018_2021:
sr *= BOLT_2018_2021_STEER_RATIO_TEST_SCALE
elif self.CP.carFingerprint == HONDA_CAR.HONDA_ACCORD and not accord_ratio_is_explicit:
sr *= get_honda_accord_steer_ratio_scale(CS.vEgo)
self.VM.update_params(x, sr)
steer_angle_without_offset = math.radians(CS.steeringAngleDeg - lp.angleOffsetDeg)
@@ -483,14 +483,24 @@ class LatControlTorque(LatControl):
ff *= get_genesis_g70_unwind_ff_scale(
setpoint, measurement, desired_lateral_jerk, CS.vEgo,
)
if kia_carnival_active:
ff *= get_kia_carnival_unwind_ff_scale(
setpoint, measurement, desired_lateral_jerk, CS.vEgo,
)
if ioniq_6_active:
vehicle_friction_jerk_deadzone = (
IONIQ_6_2025_FRICTION_JERK_DEADZONE if self.is_ioniq_6_2025 else IONIQ_6_FRICTION_JERK_DEADZONE
)
elif ioniq_5_active:
vehicle_friction_jerk_deadzone = get_ioniq_5_friction_jerk_deadzone(CS.vEgo, setpoint)
elif prius_active:
vehicle_friction_jerk_deadzone = get_prius_friction_jerk_deadzone(CS.vEgo, setpoint)
elif genesis_g70_active:
vehicle_friction_jerk_deadzone = get_genesis_g70_friction_jerk_deadzone(CS.vEgo, setpoint)
elif kia_carnival_active:
vehicle_friction_jerk_deadzone = get_kia_carnival_friction_jerk_deadzone(
CS.vEgo, setpoint, desired_lateral_jerk,
)
else:
vehicle_friction_jerk_deadzone = 0.0
friction_jerk_deadzone = get_center_chatter_friction_jerk_deadzone(
@@ -73,6 +73,7 @@ BOLT_2017_CARS = (
GM_CAR.CHEVROLET_BOLT_CC_2017,
)
BOLT_CARS = BOLT_2022_2023_CARS + BOLT_2018_2021_CARS + BOLT_2017_CARS
HONDA_ACCORD_STEER_RATIO_SCALE = 14.0 / 16.33
VOLT_STANDARD_CARS = (
GM_CAR.CHEVROLET_VOLT,
GM_CAR.CHEVROLET_VOLT_2019,
@@ -547,6 +548,24 @@ KIA_CARNIVAL_HIGHWAY_TRANSITION_JERK = 0.45
KIA_CARNIVAL_HIGHWAY_TRANSITION_JERK_WIDTH = 0.15
KIA_CARNIVAL_HIGHWAY_TRANSITION_LAT_CUTOFF = 1.20
KIA_CARNIVAL_HIGHWAY_TRANSITION_LAT_WIDTH = 0.20
KIA_CARNIVAL_UNWIND_FRICTION_JERK_DEADZONE_MAX = 0.34
KIA_CARNIVAL_UNWIND_FRICTION_JERK_DEADZONE_SPEED = 15.0
KIA_CARNIVAL_UNWIND_FRICTION_JERK_DEADZONE_SPEED_WIDTH = 2.0
KIA_CARNIVAL_UNWIND_FRICTION_JERK_DEADZONE_SPEED_CUTOFF = 23.0
KIA_CARNIVAL_UNWIND_FRICTION_JERK_DEADZONE_SPEED_CUTOFF_WIDTH = 2.0
KIA_CARNIVAL_UNWIND_FRICTION_JERK_DEADZONE_LAT = 0.35
KIA_CARNIVAL_UNWIND_FRICTION_JERK_DEADZONE_LAT_WIDTH = 0.18
KIA_CARNIVAL_UNWIND_FRICTION_JERK_DEADZONE_JERK = 0.65
KIA_CARNIVAL_UNWIND_FRICTION_JERK_DEADZONE_JERK_WIDTH = 0.25
KIA_CARNIVAL_UNWIND_FF_REDUCTION_MAX = 0.45
KIA_CARNIVAL_UNWIND_FF_SPEED = 15.0
KIA_CARNIVAL_UNWIND_FF_SPEED_WIDTH = 2.0
KIA_CARNIVAL_UNWIND_FF_SPEED_CUTOFF = 23.0
KIA_CARNIVAL_UNWIND_FF_SPEED_CUTOFF_WIDTH = 2.0
KIA_CARNIVAL_UNWIND_FF_OVERSHOOT = 0.20
KIA_CARNIVAL_UNWIND_FF_OVERSHOOT_WIDTH = 0.12
KIA_CARNIVAL_UNWIND_FF_JERK = 0.65
KIA_CARNIVAL_UNWIND_FF_JERK_WIDTH = 0.25
TUCSON_4TH_GEN_CENTER_TAPER_MAX = 0.44
TUCSON_4TH_GEN_CENTER_TAPER_LAT = 0.28
@@ -688,6 +707,11 @@ IONIQ_5_LOW_SPEED_CENTER_LAT = 0.40
IONIQ_5_LOW_SPEED_CENTER_LAT_WIDTH = 0.10
IONIQ_5_LOW_SPEED_CENTER_JERK = 0.40
IONIQ_5_LOW_SPEED_CENTER_JERK_WIDTH = 0.12
IONIQ_5_FRICTION_JERK_DEADZONE_MAX = 0.30
IONIQ_5_FRICTION_JERK_DEADZONE_LAT = 1.25
IONIQ_5_FRICTION_JERK_DEADZONE_LAT_WIDTH = 0.35
IONIQ_5_FRICTION_JERK_DEADZONE_SPEED = 18.0
IONIQ_5_FRICTION_JERK_DEADZONE_SPEED_WIDTH = 3.0
IONIQ_EV_OLD_BASE_LAT_ACCEL_FACTOR_MULT = 1.16
IONIQ_EV_OLD_FF_REDUCTION_LEFT = 0.16
@@ -1082,9 +1106,6 @@ TOYOTA_COROLLA_TSS2_CENTER_OUTPUT_TAPER_SPEED = 4.5
TOYOTA_COROLLA_TSS2_CENTER_OUTPUT_TAPER_SPEED_WIDTH = 1.5
LEXUS_IS_PHASE_SCALE = 0.10
# The Lexus route still fell short during a clean high-speed turn-in while
# already at the controller limit. Keep this correction small and phase-gated
# so straight-line behavior and unwind tuning are unchanged.
LEXUS_IS_TURN_IN_FF_BOOST_LEFT = 0.06
LEXUS_IS_TURN_IN_FF_BOOST_RIGHT = 0.06
LEXUS_IS_UNWIND_FF_REDUCTION_LEFT = 0.10
@@ -1886,6 +1907,10 @@ def get_bolt_2017_steer_ratio_scale(v_ego: float) -> float:
return 1.0 + ((BOLT_2017_STEER_RATIO_TEST_SCALE - 1.0) * _bolt_2017_high_speed_factor(v_ego))
def get_honda_accord_steer_ratio_scale(_v_ego: float) -> float:
return HONDA_ACCORD_STEER_RATIO_SCALE
def get_bolt_2017_center_taper_scale(desired_lateral_accel: float, v_ego: float) -> float:
center_window = _bolt_2017_sigmoid((BOLT_2017_CENTER_TAPER_LAT - abs(desired_lateral_accel)) / BOLT_2017_CENTER_TAPER_WIDTH)
return 1.0 - (BOLT_2017_CENTER_TAPER_GAIN * _bolt_2017_high_speed_factor(v_ego) * center_window)
@@ -2528,6 +2553,41 @@ def get_kia_carnival_highway_transition_output_scale(desired_lateral_accel: floa
return 1.0 - (KIA_CARNIVAL_HIGHWAY_TRANSITION_TAPER_MAX * speed_weight * jerk_weight * lat_weight)
def get_kia_carnival_friction_jerk_deadzone(v_ego: float, desired_lateral_accel: float,
desired_lateral_jerk: float) -> float:
"""Reduce abrupt friction reversals during mid-speed curve exits only."""
speed_weight = _sigmoid((v_ego - KIA_CARNIVAL_UNWIND_FRICTION_JERK_DEADZONE_SPEED) /
KIA_CARNIVAL_UNWIND_FRICTION_JERK_DEADZONE_SPEED_WIDTH)
speed_cutoff = _sigmoid((KIA_CARNIVAL_UNWIND_FRICTION_JERK_DEADZONE_SPEED_CUTOFF - v_ego) /
KIA_CARNIVAL_UNWIND_FRICTION_JERK_DEADZONE_SPEED_CUTOFF_WIDTH)
center_weight = _sigmoid((KIA_CARNIVAL_UNWIND_FRICTION_JERK_DEADZONE_LAT - abs(desired_lateral_accel)) /
KIA_CARNIVAL_UNWIND_FRICTION_JERK_DEADZONE_LAT_WIDTH)
jerk_weight = _sigmoid((abs(desired_lateral_jerk) - KIA_CARNIVAL_UNWIND_FRICTION_JERK_DEADZONE_JERK) /
KIA_CARNIVAL_UNWIND_FRICTION_JERK_DEADZONE_JERK_WIDTH)
return KIA_CARNIVAL_UNWIND_FRICTION_JERK_DEADZONE_MAX * speed_weight * speed_cutoff * center_weight * jerk_weight
def get_kia_carnival_unwind_ff_scale(setpoint: float, measured_lateral_accel: float,
desired_lateral_jerk: float, v_ego: float) -> float:
"""Remove stale turn feedforward when the measured response carries through an unwind."""
if setpoint * desired_lateral_jerk >= 0.0:
return 1.0
overshoot = max(abs(measured_lateral_accel) - abs(setpoint), 0.0)
if overshoot <= 0.0:
return 1.0
speed_weight = (_sigmoid((v_ego - KIA_CARNIVAL_UNWIND_FF_SPEED) /
KIA_CARNIVAL_UNWIND_FF_SPEED_WIDTH) *
_sigmoid((KIA_CARNIVAL_UNWIND_FF_SPEED_CUTOFF - v_ego) /
KIA_CARNIVAL_UNWIND_FF_SPEED_CUTOFF_WIDTH))
overshoot_weight = _sigmoid((overshoot - KIA_CARNIVAL_UNWIND_FF_OVERSHOOT) /
KIA_CARNIVAL_UNWIND_FF_OVERSHOOT_WIDTH)
jerk_weight = _sigmoid((abs(desired_lateral_jerk) - KIA_CARNIVAL_UNWIND_FF_JERK) /
KIA_CARNIVAL_UNWIND_FF_JERK_WIDTH)
return 1.0 - (KIA_CARNIVAL_UNWIND_FF_REDUCTION_MAX * speed_weight * overshoot_weight * jerk_weight)
def _tucson_4th_gen_center_weights(desired_lateral_accel: float, v_ego: float) -> tuple[float, float]:
speed_weight = _sigmoid((TUCSON_4TH_GEN_CENTER_TAPER_SPEED_MAX - v_ego) / TUCSON_4TH_GEN_CENTER_TAPER_SPEED_WIDTH)
center_weight = _sigmoid((TUCSON_4TH_GEN_CENTER_TAPER_LAT - abs(desired_lateral_accel)) / TUCSON_4TH_GEN_CENTER_TAPER_LAT_WIDTH)
@@ -3029,6 +3089,15 @@ def get_ioniq_5_low_speed_output_limit(desired_lateral_accel: float,
return float(np.clip(limit, IONIQ_5_LOW_SPEED_OUTPUT_LIMIT_BASE, 1.0))
def get_ioniq_5_friction_jerk_deadzone(v_ego: float, desired_lateral_accel: float) -> float:
"""Suppress high-speed friction reversals without reducing steady-turn torque."""
speed_weight = _ioniq_5_sigmoid((max(v_ego, 0.0) - IONIQ_5_FRICTION_JERK_DEADZONE_SPEED) /
IONIQ_5_FRICTION_JERK_DEADZONE_SPEED_WIDTH)
curve_weight = _ioniq_5_sigmoid((IONIQ_5_FRICTION_JERK_DEADZONE_LAT - abs(desired_lateral_accel)) /
IONIQ_5_FRICTION_JERK_DEADZONE_LAT_WIDTH)
return IONIQ_5_FRICTION_JERK_DEADZONE_MAX * speed_weight * curve_weight
def _ioniq_ev_old_sigmoid(x: float) -> float:
return _sigmoid(x)
@@ -84,6 +84,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import (
get_genesis_gv70_high_speed_error_scale,
get_genesis_gv70_unwind_ff_scale,
get_elantra_non_scc_ff_scale,
get_honda_accord_steer_ratio_scale,
get_palisade_ff_scale,
get_palisade_center_output_scale,
get_palisade_center_taper_scale,
@@ -113,6 +114,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import (
get_ioniq_5_friction_scale,
get_ioniq_5_friction_threshold,
get_ioniq_5_center_taper_scale,
get_ioniq_5_friction_jerk_deadzone,
get_ioniq_5_low_speed_output_limit,
get_ioniq_ev_old_center_taper_scale,
get_ioniq_ev_old_ff_scale,
@@ -132,8 +134,10 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import (
get_kia_forte_ff_scale,
get_kia_carnival_center_taper_scale,
get_kia_carnival_friction_center_fade_scale,
get_kia_carnival_friction_jerk_deadzone,
get_kia_carnival_friction_threshold,
get_kia_carnival_highway_transition_output_scale,
get_kia_carnival_unwind_ff_scale,
get_kia_stinger_2022_center_taper_scale,
get_kia_stinger_2022_friction_threshold,
get_tucson_4th_gen_center_taper_scale,
@@ -668,6 +672,30 @@ class TestLatControl:
assert low_speed_abrupt > 0.99
assert large_curve_abrupt > 0.96
def test_kia_carnival_unwind_friction_jerk_deadzone_is_mid_speed_and_center_gated(self):
low_speed = get_kia_carnival_friction_jerk_deadzone(8.5, 0.0, 1.5)
mid_speed_center = get_kia_carnival_friction_jerk_deadzone(18.0, 0.0, 1.5)
mid_speed_curve = get_kia_carnival_friction_jerk_deadzone(18.0, 0.8, 1.5)
high_speed = get_kia_carnival_friction_jerk_deadzone(30.0, 0.0, 1.5)
calm_transition = get_kia_carnival_friction_jerk_deadzone(18.0, 0.0, 0.2)
assert low_speed < 0.02
assert mid_speed_center > 0.18
assert mid_speed_curve < 0.05
assert high_speed < 0.05
assert calm_transition < 0.05
def test_kia_carnival_unwind_ff_scale_only_reduces_overshoot(self):
steady_turn = get_kia_carnival_unwind_ff_scale(0.80, 0.90, 0.60, 18.0)
clean_unwind = get_kia_carnival_unwind_ff_scale(0.20, 0.20, -1.5, 18.0)
overshooting_unwind = get_kia_carnival_unwind_ff_scale(0.20, 0.90, -1.5, 18.0)
highway_overshoot = get_kia_carnival_unwind_ff_scale(0.20, 0.90, -1.5, 30.0)
assert steady_turn == pytest.approx(1.0)
assert clean_unwind == pytest.approx(1.0)
assert overshooting_unwind < 0.70
assert highway_overshoot > overshooting_unwind
def test_genesis_g90_ff_scale_curve(self):
assert get_genesis_g90_ff_scale(0.0, 0.0, 20.0) == 1.0
assert get_genesis_g90_ff_scale(0.5, 0.0, 20.0) > get_genesis_g90_ff_scale(-0.5, 0.0, 20.0)
@@ -904,6 +932,16 @@ class TestLatControl:
assert unwind_right_scale <= unwind_left_scale
assert get_ioniq_5_friction_threshold(25.0, 0.0, 0.0) >= get_hkg_canfd_base_friction_threshold(25.0)
def test_ioniq_5_friction_jerk_deadzone_is_high_speed_curve_gated(self):
low_speed = get_ioniq_5_friction_jerk_deadzone(8.0, 0.9)
high_speed_center = get_ioniq_5_friction_jerk_deadzone(25.0, 0.0)
high_speed_curve = get_ioniq_5_friction_jerk_deadzone(25.0, 0.9)
high_lateral_accel = get_ioniq_5_friction_jerk_deadzone(25.0, 2.0)
assert low_speed < 0.02
assert high_speed_center > high_speed_curve > 0.0
assert high_lateral_accel < high_speed_curve
def test_rav4_prime_phase_shaping(self):
left_turn_in = get_rav4_prime_ff_scale(1.0, 0.8, 13.0)
right_turn_in = get_rav4_prime_ff_scale(-1.0, -0.8, 13.0)
@@ -1665,6 +1703,11 @@ class TestLatControl:
assert controller.pid._k_p[1] == pytest.approx([value * 2.0 for value in base_kp_v])
assert controller.pid._k_i[1] == pytest.approx([value * 1.25 for value in base_ki_v])
def test_honda_accord_steer_ratio_calibration(self):
expected_scale = 14.0 / 16.33
assert get_honda_accord_steer_ratio_scale(0.0) == pytest.approx(expected_scale)
assert get_honda_accord_steer_ratio_scale(20.0) == pytest.approx(expected_scale)
def test_subaru_impreza_pid_output_scale_preserves_small_errors(self):
assert get_subaru_impreza_pid_output_scale(0.0) == 1.0
assert get_subaru_impreza_pid_output_scale(0.75) == 1.0
@@ -63,6 +63,8 @@ def make_toggles(**overrides):
"speed_limit_priority_highest": False,
"speed_limit_priority_lowest": False,
"vision_speed_limit_detection": False,
"vision_speed_limit_low_limit_filter": False,
"vision_speed_limit_low_limit_threshold": mph(25),
}
defaults.update(overrides)
return SimpleNamespace(**defaults)
@@ -96,6 +98,107 @@ def mph(value):
return value * CV.MPH_TO_MS
@pytest.mark.parametrize("limit_mph", [15, 25])
def test_low_vision_limit_filter_blocks_configured_boundary(limit_mph):
controller = make_controller(
speed_limit_priority1="Vision",
vision_speed_limit_detection=True,
vision_speed_limit_low_limit_filter=True,
vision_speed_limit_low_limit_threshold=mph(25),
)
try:
controller.starpilot_planner.params_memory.put_float("VisionSpeedLimit", mph(limit_mph))
sm = make_sm(gas_pressed=False, v_cruise_kph=25 * CV.MPH_TO_KPH)
controller.update_limits(0.0, datetime.now(timezone.utc), False, mph(25), mph(20), sm)
assert controller.vision_limit == pytest.approx(mph(limit_mph))
assert controller.target == 0
assert controller.source == "None"
finally:
controller.shutdown()
def test_low_vision_limit_filter_allows_limit_above_threshold():
controller = make_controller(
speed_limit_priority1="Vision",
vision_speed_limit_detection=True,
vision_speed_limit_low_limit_filter=True,
vision_speed_limit_low_limit_threshold=mph(25),
)
try:
controller.starpilot_planner.params_memory.put_float("VisionSpeedLimit", mph(30))
sm = make_sm(gas_pressed=False, v_cruise_kph=30 * CV.MPH_TO_KPH)
controller.update_limits(0.0, datetime.now(timezone.utc), False, mph(30), mph(25), sm)
assert controller.target == pytest.approx(mph(30))
assert controller.source == "Vision"
finally:
controller.shutdown()
def test_low_vision_limit_filter_is_action_only_for_display():
controller = make_controller(
speed_limit_priority1="Vision",
vision_speed_limit_detection=True,
vision_speed_limit_low_limit_filter=True,
vision_speed_limit_low_limit_threshold=mph(25),
)
try:
controller.starpilot_planner.params_memory.put_float("VisionSpeedLimit", mph(15))
sm = make_sm(gas_pressed=False, v_cruise_kph=20 * CV.MPH_TO_KPH)
controller.update_limits(0.0, datetime.now(timezone.utc), False, mph(20), mph(15), sm, display_only=True)
assert controller.target == pytest.approx(mph(15))
assert controller.source == "Vision"
finally:
controller.shutdown()
def test_low_vision_limit_filter_does_not_filter_dashboard_source():
controller = make_controller(
speed_limit_priority1="Vision",
speed_limit_priority2="Dashboard",
vision_speed_limit_detection=True,
vision_speed_limit_low_limit_filter=True,
vision_speed_limit_low_limit_threshold=mph(25),
)
try:
controller.starpilot_planner.params_memory.put_float("VisionSpeedLimit", mph(15))
sm = make_sm(gas_pressed=False, v_cruise_kph=20 * CV.MPH_TO_KPH)
controller.update_limits(mph(15), datetime.now(timezone.utc), False, mph(20), mph(15), sm)
assert controller.target == pytest.approx(mph(15))
assert controller.source == "Dashboard"
finally:
controller.shutdown()
def test_low_vision_limit_filter_does_not_restore_filtered_vision_fallback():
controller = make_controller(
speed_limit_priority1="Vision",
slc_fallback_previous_speed_limit=True,
vision_speed_limit_detection=True,
vision_speed_limit_low_limit_filter=True,
vision_speed_limit_low_limit_threshold=mph(25),
)
try:
controller.previous_source = "Vision"
controller.previous_target = mph(15)
controller.starpilot_planner.params_memory.put_float("VisionSpeedLimit", mph(15))
sm = make_sm(gas_pressed=False, v_cruise_kph=20 * CV.MPH_TO_KPH)
controller.update_limits(0.0, datetime.now(timezone.utc), False, mph(20), mph(15), sm)
assert controller.target == 0
assert controller.source == "None"
finally:
controller.shutdown()
def test_large_vision_delta_requires_three_detector_frames():
controller = make_controller(
speed_limit_priority1="Vision",
+2
View File
@@ -153,6 +153,8 @@ SAFE_MODE_MANAGED_KEYS = (
"Offset7",
"SpeedLimitFiller",
"VisionSpeedLimitDetection",
"VisionSpeedLimitLowLimitFilter",
"VisionSpeedLimitLowLimitThreshold",
"VASMEnabled",
"CustomPersonalities",
"TrafficPersonalityProfile",
+10
View File
@@ -1341,6 +1341,16 @@ class StarPilotVariables:
toggle.speed_limit_filler = self.get_value("SpeedLimitFiller")
toggle.vision_speed_limit_detection = self.get_value("VisionSpeedLimitDetection")
toggle.vision_speed_limit_low_limit_filter = self.get_value(
"VisionSpeedLimitLowLimitFilter",
condition=toggle.speed_limit_controller and toggle.vision_speed_limit_detection,
)
toggle.vision_speed_limit_low_limit_threshold = self.get_value(
"VisionSpeedLimitLowLimitThreshold",
cast=float,
condition=toggle.vision_speed_limit_low_limit_filter,
conversion=speed_conversion,
)
toggle.v_asm_enabled = self.get_value("VASMEnabled")
toggle.startup_alert_top = self.get_value("StartupMessageTop", cast=str, default="")
@@ -139,6 +139,12 @@ class SpeedLimitController:
(gas_pressed and v_ego > target_with_offset)
)
def low_vision_limit_filtered(self, limit):
return (
getattr(self.starpilot_toggles, "vision_speed_limit_low_limit_filter", False) and
0 < limit <= max(getattr(self.starpilot_toggles, "vision_speed_limit_low_limit_threshold", 0), 0)
)
def clear_override_for_source_limit(self, desired_source, desired_target, had_override):
if desired_source == "None" or desired_target <= 0:
return
@@ -347,6 +353,8 @@ class SpeedLimitController:
vision_enabled = getattr(self.starpilot_toggles, "vision_speed_limit_detection", False)
self.vision_limit = self.starpilot_planner.params_memory.get_float("VisionSpeedLimit") if vision_enabled else 0
usable_vision_limit = self.vision_limit
if not display_only and self.low_vision_limit_filtered(usable_vision_limit):
usable_vision_limit = 0
# The planner clamps V_CRUISE_UNSET to V_CRUISE_MAX, so plausibility must use the raw selected speed.
raw_set_speed_kph = float(sm["carState"].vCruise)
selected_set_speed = raw_set_speed_kph * CV.KPH_TO_MS if 0 < raw_set_speed_kph < V_CRUISE_UNSET else 0
@@ -406,7 +414,8 @@ class SpeedLimitController:
desired_target = self.mapbox_limit
if not display_only and desired_target == 0:
if self.previous_target > 0 and self.starpilot_toggles.slc_fallback_previous_speed_limit:
previous_vision_limit_filtered = self.previous_source == "Vision" and self.low_vision_limit_filtered(self.previous_target)
if self.previous_target > 0 and self.starpilot_toggles.slc_fallback_previous_speed_limit and not previous_vision_limit_filtered:
desired_source = self.previous_source
desired_target = self.previous_target
+48 -7
View File
@@ -53,6 +53,7 @@ class StarPilotCard:
self.prev_cruise_enabled = False
self.decel_pressed = False
self.cancelPressed_previously = False
self.cancel_pulse_glide_suppressed = False
self.distancePressed_previously = False
self.force_coast = False
self.pulse_and_glide = False
@@ -89,6 +90,7 @@ class StarPilotCard:
elif getattr(starpilot_toggles, f"pulse_and_glide_via_{key}"):
if getattr(sm["carControl"], "longActive", False):
self.pulse_and_glide = not self.pulse_and_glide
return True
elif getattr(starpilot_toggles, f"pause_lateral_via_{key}"):
self.pause_lateral = not self.pause_lateral
elif getattr(starpilot_toggles, f"pause_longitudinal_via_{key}"):
@@ -136,6 +138,34 @@ class StarPilotCard:
def update(self, carState, starpilotCarState, sm, starpilot_toggles):
self.switchback_mode_enabled = self.params_memory.get_bool("SwitchbackModeEnabled")
self._handle_favorite_traffic_mode_action(sm)
pulse_glide_cancel_override = bool(getattr(sm["carControl"], "longActive", False)) and any(
getattr(starpilot_toggles, f"pulse_and_glide_via_cancel{suffix}", False)
for suffix in ("", "_long", "_very_long")
)
cancel_pressed = bool(getattr(starpilotCarState, "cancelPressed", False))
if pulse_glide_cancel_override:
carState.buttonEvents = [
be for be in carState.buttonEvents
if not (
self._button_type_raw(be) == int(ButtonType.cancel) and
(be.pressed or self.cancel_pulse_glide_suppressed)
)
]
lkas_pressed = any(
self._button_type_raw(be) == int(ButtonType.lkas) and be.pressed
for be in carState.buttonEvents
)
pulse_glide_lkas_override = bool(getattr(sm["carControl"], "longActive", False)) and getattr(
starpilot_toggles, "pulse_and_glide_via_lkas", False
)
if pulse_glide_lkas_override:
carState.buttonEvents = [
be for be in carState.buttonEvents
if self._button_type_raw(be) != int(ButtonType.lkas)
]
button_event_types = [self._button_type_raw(be) for be in carState.buttonEvents]
button_aol_supported = self.CP.brand == "hyundai" or starpilot_toggles.lkas_allowed_for_aol
button_managed_aol = starpilot_toggles.always_on_lateral_lkas or (button_aol_supported and starpilot_toggles.main_cruise_aol_toggle)
@@ -254,7 +284,6 @@ class StarPilotCard:
self.handle_button_event("distance_long", sm, starpilot_toggles)
self.handle_button_event("distance_very_long", sm, starpilot_toggles)
cancel_pressed = bool(getattr(starpilotCarState, "cancelPressed", False))
if cancel_pressed:
self.cancel_counter += 1
elif not self.cancelPressed_previously:
@@ -262,15 +291,27 @@ class StarPilotCard:
self.cancelPressed_previously = cancel_pressed
if not cancel_pressed and 1 <= self.cancel_counter < self.long_press_threshold:
self.handle_button_event("cancel", sm, starpilot_toggles)
pulse_glide_cancel_consumed = False
if not cancel_pressed and self.cancel_pulse_glide_suppressed:
pass
elif not cancel_pressed and 1 <= self.cancel_counter < self.long_press_threshold:
pulse_glide_cancel_consumed = self.handle_button_event("cancel", sm, starpilot_toggles) or False
elif self.cancel_counter == self.long_press_threshold:
self.handle_button_event("cancel_long", sm, starpilot_toggles)
pulse_glide_cancel_consumed = self.handle_button_event("cancel_long", sm, starpilot_toggles) or False
elif self.cancel_counter == self.very_long_press_threshold:
self.handle_button_event("cancel_long", sm, starpilot_toggles)
self.handle_button_event("cancel_very_long", sm, starpilot_toggles)
pulse_glide_cancel_consumed = self.handle_button_event("cancel_long", sm, starpilot_toggles) or False
pulse_glide_cancel_consumed |= self.handle_button_event("cancel_very_long", sm, starpilot_toggles) or False
if any(be.pressed and be_type == ButtonType.lkas for be, be_type in zip(carState.buttonEvents, button_event_types, strict=False)):
if pulse_glide_cancel_consumed:
self.cancel_pulse_glide_suppressed = True
carState.buttonEvents = [
be for be in carState.buttonEvents
if self._button_type_raw(be) != int(ButtonType.cancel)
]
elif not cancel_pressed and self.cancel_pulse_glide_suppressed:
self.cancel_pulse_glide_suppressed = False
if lkas_pressed:
self.handle_button_event("lkas", sm, starpilot_toggles)
if getattr(starpilot_toggles, "has_canfd_media_buttons", False):
@@ -73,11 +73,20 @@ def make_toggles(**overrides):
"bookmark_via_cancel": False,
"bookmark_via_cancel_long": False,
"bookmark_via_cancel_very_long": False,
"experimental_mode_via_cancel": False,
"experimental_mode_via_cancel_long": False,
"experimental_mode_via_cancel_very_long": False,
"force_coast_via_cancel": False,
"force_coast_via_cancel_long": False,
"force_coast_via_cancel_very_long": False,
"bookmark_via_lkas": False,
"conditional_experimental_mode": False,
"experimental_mode_via_lkas": False,
"force_coast_via_lkas": False,
"pulse_and_glide_available": False,
"pulse_and_glide_via_cancel": False,
"pulse_and_glide_via_cancel_long": False,
"pulse_and_glide_via_cancel_very_long": False,
"pulse_and_glide_via_lkas": False,
"lkas_allowed_for_aol": False,
"main_cruise_aol_toggle": False,
@@ -117,6 +126,83 @@ def test_pulse_and_glide_requires_developer_access_and_active_longitudinal(monke
assert result.pulseAndGlide is False
def test_pulse_and_glide_consumes_native_cancel_when_mapped(monkeypatch, tmp_path):
monkeypatch.setattr(spc, "Params", FakeParams)
monkeypatch.setattr(spc, "is_FrogsGoMoo", lambda: False)
monkeypatch.setattr(spc, "ERROR_LOGS_PATH", tmp_path)
card = spc.StarPilotCard(SimpleNamespace(brand="gm"), SimpleNamespace(alternativeExperience=0))
sm = make_sm()
sm["carControl"].longActive = True
toggles = make_toggles(
pulse_and_glide_available=True,
pulse_and_glide_via_cancel=True,
)
starpilot_car_state = SimpleNamespace(distancePressed=False, cancelPressed=True)
press = make_car_state(button_events=[SimpleNamespace(type=spc.ButtonType.cancel, pressed=True)])
card.update(press, starpilot_car_state, sm, toggles)
assert press.buttonEvents == []
starpilot_car_state.cancelPressed = False
release = make_car_state(button_events=[SimpleNamespace(type=spc.ButtonType.cancel, pressed=False)])
result = card.update(release, starpilot_car_state, sm, toggles)
assert card.pulse_and_glide is True
assert result.pulseAndGlide is True
assert release.buttonEvents == []
def test_pulse_and_glide_consumes_lkas_when_mapped(monkeypatch, tmp_path):
monkeypatch.setattr(spc, "Params", FakeParams)
monkeypatch.setattr(spc, "is_FrogsGoMoo", lambda: False)
monkeypatch.setattr(spc, "ERROR_LOGS_PATH", tmp_path)
card = spc.StarPilotCard(SimpleNamespace(brand="gm"), SimpleNamespace(alternativeExperience=0))
sm = make_sm()
sm["carControl"].longActive = True
toggles = make_toggles(
pulse_and_glide_available=True,
pulse_and_glide_via_lkas=True,
)
starpilot_car_state = SimpleNamespace(distancePressed=False)
car_state = make_car_state(button_events=[SimpleNamespace(type=spc.ButtonType.lkas, pressed=True)])
result = card.update(car_state, starpilot_car_state, sm, toggles)
assert card.pulse_and_glide is True
assert result.pulseAndGlide is True
assert car_state.buttonEvents == []
def test_pulse_and_glide_long_cancel_consumes_release_after_threshold(monkeypatch, tmp_path):
monkeypatch.setattr(spc, "Params", FakeParams)
monkeypatch.setattr(spc, "is_FrogsGoMoo", lambda: False)
monkeypatch.setattr(spc, "ERROR_LOGS_PATH", tmp_path)
card = spc.StarPilotCard(SimpleNamespace(brand="gm"), SimpleNamespace(alternativeExperience=0))
sm = make_sm()
sm["carControl"].longActive = True
toggles = make_toggles(
pulse_and_glide_available=True,
pulse_and_glide_via_cancel_long=True,
)
starpilot_car_state = SimpleNamespace(distancePressed=False, cancelPressed=True)
for frame in range(card.long_press_threshold):
button_events = [SimpleNamespace(type=spc.ButtonType.cancel, pressed=True)] if frame == 0 else []
card.update(make_car_state(button_events=button_events), starpilot_car_state, sm, toggles)
assert card.pulse_and_glide is True
starpilot_car_state.cancelPressed = False
release = make_car_state(button_events=[SimpleNamespace(type=spc.ButtonType.cancel, pressed=False)])
card.update(release, starpilot_car_state, sm, toggles)
assert card.pulse_and_glide is True
assert release.buttonEvents == []
def make_car_state(available=False, enabled=False, button_events=None, brake_pressed=False, gas_pressed=False):
return SimpleNamespace(
buttonEvents=button_events or [],
@@ -1607,6 +1607,27 @@
"parent_key": "SpeedLimitController",
"settings_tier": "advanced"
},
{
"key": "VisionSpeedLimitLowLimitFilter",
"label": "Ignore Low Vision Speed Limits",
"description": "Prevent SLC from acting on vision-detected speed limits at or below the configured threshold. Detection, display, debugging, and training collection remain active.",
"data_type": "bool",
"ui_type": "toggle",
"parent_key": "VisionSpeedLimitDetection",
"settings_tier": "advanced"
},
{
"key": "VisionSpeedLimitLowLimitThreshold",
"label": "Ignore At or Below",
"description": "Vision-detected limits at or below this value will not control speed. The value uses your selected mph or km/h unit.",
"data_type": "int",
"ui_type": "numeric",
"min": 5,
"max": 80,
"step": 5,
"parent_key": "VisionSpeedLimitLowLimitFilter",
"settings_tier": "advanced"
},
{
"key": "VisionSpeedLimitAutoBookmark",
"label": "Auto-Bookmark Vision Signs",
@@ -197,6 +197,27 @@ def test_vasm_is_default_off_and_configured_only_in_galaxy():
assert all("VASM" not in path.read_text(encoding="utf-8") for path in physical_settings)
def test_low_vision_limit_filter_is_default_off_and_configured_only_in_galaxy():
sections = _params_by_section(_layout())
longitudinal = sections["Longitudinal (Speed & Following)"]
toggle = longitudinal["VisionSpeedLimitLowLimitFilter"]
threshold = longitudinal["VisionSpeedLimitLowLimitThreshold"]
assert toggle["parent_key"] == "VisionSpeedLimitDetection"
assert threshold["parent_key"] == "VisionSpeedLimitLowLimitFilter"
assert threshold["min"] == 5
assert threshold["max"] == 80
assert threshold["step"] == 5
assert _declared_default("VisionSpeedLimitLowLimitFilter") == "0"
assert _declared_default("VisionSpeedLimitLowLimitThreshold") == "25"
physical_settings = (
REPO_ROOT / "selfdrive/ui/layouts/settings/starpilot/longitudinal.py",
REPO_ROOT / "selfdrive/ui/layouts/settings/starpilot/aethergrid.py",
)
assert all("VisionSpeedLimitLowLimit" not in path.read_text(encoding="utf-8") for path in physical_settings)
def test_pip_preview_is_under_driving_screen_widgets_and_configured_only_in_galaxy():
sections = _params_by_section(_layout())
visual = sections["Visual (Display & UI)"]