mid day stuffs

This commit is contained in:
firestar5683
2026-06-03 11:01:28 -05:00
parent 3e591f311c
commit c80365de78
9 changed files with 364 additions and 32 deletions
@@ -25,7 +25,7 @@ ACCEL_WINDUP_LIMIT = 4.0 * DT_CTRL * 3 # m/s^2 / frame
ACCEL_WINDDOWN_LIMIT = -4.0 * DT_CTRL * 3 # m/s^2 / frame
ACCEL_PID_UNWIND = 0.03 * DT_CTRL * 3 # m/s^2 / frame
PRIUS_INTEGRAL_MISMATCH_UNWIND = 8.0
PRIUS_POSITIVE_FEEDFORWARD_SCALE = 0.5
PRIUS_POSITIVE_FEEDFORWARD_SCALE = 0.7
MAX_PITCH_COMPENSATION = 1.5 # m/s^2
TOYOTA_COAST_BRAKE_MIN_SPEED = 15.0 # m/s
@@ -142,6 +142,25 @@ def limit_interceptor_stopping_accel(pcm_accel_cmd: float, target_accel: float,
return max(pcm_accel_cmd, max(stop_floor, planner_floor))
def limit_prius_stopping_accel(pcm_accel_cmd: float, target_accel: float, stopping: bool, v_ego: float, lead_visible: bool) -> float:
if not stopping or pcm_accel_cmd >= 0.0 or v_ego >= 1.5:
return pcm_accel_cmd
# Prius can hold onto a stale full negative stop command at standstill even after the
# planner has already softened. Keep enough brake to hold the stop, but let the command
# unwind toward the live planner target so launches are not delayed and stop transitions
# are less abrupt.
if target_accel <= -1.8:
return pcm_accel_cmd
stop_floor = float(np.interp(v_ego,
[0.0, 0.2, 0.5, 0.9, 1.5],
[-0.96, -1.00, -1.08, -1.18, -1.35] if lead_visible else [-0.84, -0.88, -0.96, -1.08, -1.24]))
target_buffer = float(np.interp(v_ego, [0.0, 0.5, 1.5], [0.06, 0.10, 0.16]))
planner_floor = float(target_accel) - target_buffer
return max(pcm_accel_cmd, max(stop_floor, planner_floor))
class CarController(CarControllerBase):
def __init__(self, dbc_names, CP):
super().__init__(dbc_names, CP)
@@ -410,6 +429,8 @@ class CarController(CarControllerBase):
if self.CP.enableGasInterceptorDEPRECATED:
pcm_accel_cmd = limit_interceptor_pcm_accel(pcm_accel_cmd, actuators.accel, stopping, CS.out.vEgo)
pcm_accel_cmd = limit_interceptor_stopping_accel(pcm_accel_cmd, actuators.accel, stopping, CS.out.vEgo, bool(hud_control.leadVisible))
elif self.CP.carFingerprint == CAR.TOYOTA_PRIUS:
pcm_accel_cmd = limit_prius_stopping_accel(pcm_accel_cmd, actuators.accel, stopping, CS.out.vEgo, lead)
pcm_accel_cmd = float(np.clip(pcm_accel_cmd, self.params.ACCEL_MIN, self.params.ACCEL_MAX))
@@ -7,7 +7,8 @@ from opendbc.can import CANPacker, CANParser
from opendbc.car.structs import CarParams
from opendbc.car.fw_versions import build_fw_dict
from opendbc.car.toyota import toyotacan
from opendbc.car.toyota.carcontroller import CarController, limit_interceptor_pcm_accel, limit_interceptor_stopping_accel, update_permit_braking
from opendbc.car.toyota.carcontroller import CarController, limit_interceptor_pcm_accel, limit_interceptor_stopping_accel, \
limit_prius_stopping_accel, update_permit_braking
from opendbc.car.toyota.carstate import calculate_interceptor_gas_pressed
from opendbc.car.toyota.fingerprints import FW_VERSIONS
from opendbc.car.toyota.interface import CarInterface
@@ -252,6 +253,14 @@ class TestToyotaCarController:
assert update_permit_braking(False, 0.10, True, True, 25.0, False) is True
assert update_permit_braking(False, 0.10, False, False, 25.0, False) is True
def test_prius_stopping_accel_unwinds_stale_stop_hold(self):
limited = limit_prius_stopping_accel(-3.28, -0.05, True, 0.0, True)
assert -1.5 < limited < 0.0
def test_prius_stopping_accel_keeps_hard_stop_commands(self):
limited = limit_prius_stopping_accel(-3.28, -2.0, True, 0.0, True)
assert limited == -3.28
def test_sng_hack_clears_existing_standstill_latch(self):
controller = self._make_controller(standstill_req=True, last_standstill=True)
+100
View File
@@ -6,6 +6,7 @@ from cereal import log
from opendbc.car.gm.values import CAR as GM_CAR
from opendbc.car.honda.values import CAR as HONDA_CAR, HondaFlags
from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR
from opendbc.car.toyota.values import CAR as TOYOTA_CAR
from opendbc.car.lateral import get_friction
from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY, CV
from openpilot.common.filter_simple import FirstOrderFilter
@@ -143,6 +144,9 @@ KIA_EV6_CARS = (
KIA_FORTE_CARS = (
HYUNDAI_CAR.KIA_FORTE,
)
PRIUS_CARS = (
TOYOTA_CAR.TOYOTA_PRIUS,
)
BOLT_2017_LATERAL_TESTING_GROUND_ID = testing_ground.id_3
BOLT_2017_STEER_RATIO_TEST_SCALE = 1.045
@@ -551,6 +555,28 @@ VOLT_PLEXY_TURN_IN_FRICTION_BOOST_LEFT = 0.08
VOLT_PLEXY_TURN_IN_FRICTION_BOOST_RIGHT = 0.06
VOLT_PLEXY_UNWIND_FRICTION_REDUCTION_LEFT = 0.16
VOLT_PLEXY_UNWIND_FRICTION_REDUCTION_RIGHT = 0.40
PRIUS_TRANSITION_SPEED = 10.0
PRIUS_PHASE_SCALE = 0.09
PRIUS_FF_GAIN_LEFT = 0.10
PRIUS_FF_GAIN_RIGHT = 0.14
PRIUS_FF_ONSET = 0.16
PRIUS_FF_ONSET_WIDTH = 0.08
PRIUS_FF_CUTOFF = 1.25
PRIUS_FF_CUTOFF_WIDTH = 0.30
PRIUS_FRICTION_LAT_RISE = 0.18
PRIUS_FRICTION_JERK_RISE = 0.22
PRIUS_TURN_IN_BOOST_LEFT = 0.48
PRIUS_TURN_IN_BOOST_RIGHT = 0.62
PRIUS_UNWIND_TAPER_LEFT = 0.44
PRIUS_UNWIND_TAPER_RIGHT = 0.72
PRIUS_TURN_IN_THRESHOLD_REDUCTION_LEFT = 0.18
PRIUS_TURN_IN_THRESHOLD_REDUCTION_RIGHT = 0.24
PRIUS_UNWIND_THRESHOLD_INCREASE_LEFT = 0.28
PRIUS_UNWIND_THRESHOLD_INCREASE_RIGHT = 0.44
PRIUS_TURN_IN_FRICTION_BOOST_LEFT = 0.08
PRIUS_TURN_IN_FRICTION_BOOST_RIGHT = 0.12
PRIUS_UNWIND_FRICTION_REDUCTION_LEFT = 0.14
PRIUS_UNWIND_FRICTION_REDUCTION_RIGHT = 0.24
def _sigmoid(x: float) -> float:
@@ -567,6 +593,74 @@ def get_friction_threshold(v_ego: float) -> float:
return float(np.interp(v_ego, [1 * CV.MPH_TO_MS, 20 * CV.MPH_TO_MS, 75 * CV.MPH_TO_MS], [0.16, 0.19, 0.27]))
def _prius_sigmoid(x: float) -> float:
return _sigmoid(x)
def _prius_low_speed_factor(v_ego: float) -> float:
return 1.0 / (1.0 + (max(v_ego, 0.0) / PRIUS_TRANSITION_SPEED) ** 2)
def _prius_transition_phase(desired_lateral_accel: float, desired_lateral_jerk: float) -> float:
return math.tanh((desired_lateral_accel * desired_lateral_jerk) / PRIUS_PHASE_SCALE)
def _prius_side_value(desired_lateral_accel: float, left_value: float, right_value: float) -> float:
return left_value if desired_lateral_accel >= 0.0 else right_value
def _prius_transition_envelope(v_ego: float, desired_lateral_accel: float, desired_lateral_jerk: float) -> float:
lat_factor = 1.0 - math.exp(-abs(desired_lateral_accel) / PRIUS_FRICTION_LAT_RISE)
jerk_factor = 1.0 - math.exp(-abs(desired_lateral_jerk) / PRIUS_FRICTION_JERK_RISE)
return _prius_low_speed_factor(v_ego) * lat_factor * jerk_factor
def get_prius_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: float, v_ego: float) -> float:
if desired_lateral_accel == 0.0:
return 1.0
gain = _prius_side_value(desired_lateral_accel, PRIUS_FF_GAIN_LEFT, PRIUS_FF_GAIN_RIGHT)
abs_lateral_accel = abs(desired_lateral_accel)
onset = _prius_sigmoid((abs_lateral_accel - PRIUS_FF_ONSET) / PRIUS_FF_ONSET_WIDTH)
cutoff = _prius_sigmoid((PRIUS_FF_CUTOFF - abs_lateral_accel) / PRIUS_FF_CUTOFF_WIDTH)
extra_scale = gain * onset * cutoff
phase = _prius_transition_phase(desired_lateral_accel, desired_lateral_jerk)
turn_in_weight = max(phase, 0.0)
unwind_weight = max(-phase, 0.0)
low_speed_factor = _prius_low_speed_factor(v_ego)
turn_in_boost = 1.0 + (_prius_side_value(desired_lateral_accel, PRIUS_TURN_IN_BOOST_LEFT, PRIUS_TURN_IN_BOOST_RIGHT) *
turn_in_weight * (0.35 + 0.65 * low_speed_factor))
unwind_taper = 1.0 - (_prius_side_value(desired_lateral_accel, PRIUS_UNWIND_TAPER_LEFT, PRIUS_UNWIND_TAPER_RIGHT) *
unwind_weight * (0.35 + 0.65 * low_speed_factor))
return 1.0 + (extra_scale * turn_in_boost * max(unwind_taper, 0.0))
def get_prius_friction_threshold(v_ego: float, desired_lateral_accel: float = 0.0, desired_lateral_jerk: float = 0.0) -> float:
base_threshold = get_friction_threshold(v_ego)
transition_envelope = _prius_transition_envelope(v_ego, desired_lateral_accel, desired_lateral_jerk)
phase = _prius_transition_phase(desired_lateral_accel, desired_lateral_jerk)
turn_in_weight = max(phase, 0.0)
unwind_weight = max(-phase, 0.0)
threshold_scale = 1.0 - (_prius_side_value(desired_lateral_accel, PRIUS_TURN_IN_THRESHOLD_REDUCTION_LEFT, PRIUS_TURN_IN_THRESHOLD_REDUCTION_RIGHT) *
transition_envelope * turn_in_weight)
threshold_scale += (_prius_side_value(desired_lateral_accel, PRIUS_UNWIND_THRESHOLD_INCREASE_LEFT, PRIUS_UNWIND_THRESHOLD_INCREASE_RIGHT) *
transition_envelope * unwind_weight)
return base_threshold * min(max(threshold_scale, 0.86), 1.16)
def get_prius_friction_scale(v_ego: float, desired_lateral_accel: float, desired_lateral_jerk: float) -> float:
transition_envelope = _prius_transition_envelope(v_ego, desired_lateral_accel, desired_lateral_jerk)
phase = _prius_transition_phase(desired_lateral_accel, desired_lateral_jerk)
turn_in_weight = max(phase, 0.0)
unwind_weight = max(-phase, 0.0)
friction_scale = 1.0
friction_scale += (_prius_side_value(desired_lateral_accel, PRIUS_TURN_IN_FRICTION_BOOST_LEFT, PRIUS_TURN_IN_FRICTION_BOOST_RIGHT) *
transition_envelope * turn_in_weight)
friction_scale -= (_prius_side_value(desired_lateral_accel, PRIUS_UNWIND_FRICTION_REDUCTION_LEFT, PRIUS_UNWIND_FRICTION_REDUCTION_RIGHT) *
transition_envelope * unwind_weight)
return min(max(friction_scale, 0.90), 1.14)
def civic_bosch_modified_lateral_testing_ground_active() -> bool:
return testing_ground.use("8", "B")
@@ -1748,6 +1842,7 @@ class LatControlTorque(LatControl):
self.is_volt_standard = CP.carFingerprint in VOLT_STANDARD_CARS
self.is_genesis_g90 = CP.carFingerprint in GENESIS_G90_CARS
self.is_palisade = CP.carFingerprint in PALISADE_CARS
self.is_prius = CP.carFingerprint in PRIUS_CARS
self.is_ioniq_5 = CP.carFingerprint in IONIQ_5_CARS
self.is_ioniq_ev_old = CP.carFingerprint in IONIQ_EV_OLD_CARS
self.is_ioniq_6 = CP.carFingerprint in IONIQ_6_CARS
@@ -1880,6 +1975,7 @@ class LatControlTorque(LatControl):
volt_standard_test_active = self.is_volt_standard and volt_standard_lateral_testing_ground_active()
genesis_g90_test_active = self.is_genesis_g90 and genesis_g90_lateral_testing_ground_active()
palisade_active = self.is_palisade
prius_active = self.is_prius
ioniq_5_active = self.is_ioniq_5
ioniq_ev_old_active = self.is_ioniq_ev_old
ioniq_6_active = self.is_ioniq_6
@@ -1922,6 +2018,10 @@ class LatControlTorque(LatControl):
ff *= get_palisade_ff_scale(setpoint, desired_lateral_jerk, CS.vEgo)
friction_threshold = get_palisade_friction_threshold(CS.vEgo, setpoint, desired_lateral_jerk)
friction_scale = get_palisade_friction_scale(CS.vEgo, setpoint, desired_lateral_jerk)
elif prius_active:
ff *= get_prius_ff_scale(setpoint, desired_lateral_jerk, CS.vEgo)
friction_threshold = get_prius_friction_threshold(CS.vEgo, setpoint, desired_lateral_jerk)
friction_scale = get_prius_friction_scale(CS.vEgo, setpoint, desired_lateral_jerk)
elif ioniq_5_active:
ff *= get_ioniq_5_ff_scale(setpoint, desired_lateral_jerk, CS.vEgo) * ioniq_5_center_taper
friction_threshold = get_ioniq_5_friction_threshold(CS.vEgo, setpoint, desired_lateral_jerk)
+58 -17
View File
@@ -237,6 +237,21 @@ MATCHED_FOLLOW_TRANSITION_MAX_POSITIVE_STEP = 0.18
MATCHED_FOLLOW_TRANSITION_MIN_NEGATIVE_STEP = 0.08
MATCHED_FOLLOW_TRANSITION_MAX_NEGATIVE_STEP = 0.16
MATCHED_FOLLOW_TRANSITION_SIGN_CROSS_STEP = 0.10
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_SPEED = 10.0
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_SPEED = MATCHED_FOLLOW_TRANSITION_MIN_SPEED
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_HEADWAY_MARGIN = 0.45
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_FULL_HEADWAY_MARGIN = 1.00
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_MODEL_PROB = 0.98
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_LEAD_BRAKE = 0.08
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_CLOSING_SPEED = 1.25
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_TTC = 18.0
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_POSITIVE_STEP = 0.06
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_POSITIVE_STEP = 0.10
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_NEGATIVE_STEP = 0.05
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_NEGATIVE_STEP = 0.08
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_SIGN_CROSS_STEP = 0.06
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_TARGET = -0.12
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_DELTA_A = 0.12
NEAR_DUPLICATE_LEAD_TRANSITION_MIN_SPEED = 20.0
NEAR_DUPLICATE_LEAD_TRANSITION_MIN_MODEL_PROB = 0.95
NEAR_DUPLICATE_LEAD_TRANSITION_MAX_LEAD_BRAKE = 0.35
@@ -1215,18 +1230,26 @@ class LongitudinalPlanner:
))
return -max(0.0, cap_decel - relax_decel)
def get_matched_follow_transition_target(self, lead, v_ego, base_t_follow, prev_output_a_target, output_a_target):
def get_matched_follow_transition_target(self, lead, v_ego, base_t_follow, prev_output_a_target, output_a_target,
current_source, tracking_lead_active):
if lead is None or not lead.status:
return None
if float(v_ego) < MATCHED_FOLLOW_TRANSITION_MIN_SPEED:
low_speed_extension_active = (
bool(tracking_lead_active) and
current_source == "cruise" and
LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_SPEED <= float(v_ego) < LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_SPEED
)
if float(v_ego) < MATCHED_FOLLOW_TRANSITION_MIN_SPEED and not low_speed_extension_active:
return None
lead_prob = float(getattr(lead, "modelProb", 0.0))
if lead_prob < MATCHED_FOLLOW_TRANSITION_MIN_MODEL_PROB:
min_model_prob = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_MODEL_PROB if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_MIN_MODEL_PROB
if lead_prob < min_model_prob:
return None
lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0)))
if lead_brake > MATCHED_FOLLOW_TRANSITION_MAX_LEAD_BRAKE:
max_lead_brake = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_LEAD_BRAKE if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_MAX_LEAD_BRAKE
if lead_brake > max_lead_brake:
return None
relative_speed = float(v_ego) - float(lead.vLead)
@@ -1234,49 +1257,65 @@ class LongitudinalPlanner:
return None
closing_speed = max(0.0, relative_speed)
if closing_speed > MATCHED_FOLLOW_TRANSITION_MAX_CLOSING_SPEED:
max_closing_speed = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_CLOSING_SPEED if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_MAX_CLOSING_SPEED
if closing_speed > max_closing_speed:
return None
ttc = float(lead.dRel) / max(closing_speed, 0.1) if closing_speed > 0.1 else float("inf")
if ttc < MATCHED_FOLLOW_TRANSITION_MIN_TTC:
min_ttc = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_TTC if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_MIN_TTC
if ttc < min_ttc:
return None
actual_headway = float(lead.dRel) / max(float(v_ego), 1e-3)
headway_margin = actual_headway - float(base_t_follow)
if headway_margin < MATCHED_FOLLOW_TRANSITION_MIN_HEADWAY_MARGIN:
min_headway_margin = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_HEADWAY_MARGIN if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_MIN_HEADWAY_MARGIN
full_headway_margin = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_FULL_HEADWAY_MARGIN if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_FULL_HEADWAY_MARGIN
if headway_margin < min_headway_margin:
return None
if actual_headway > float(base_t_follow) + STEADY_FOLLOW_BRAKE_CAP_MAX_HEADWAY_ABOVE_TARGET:
return None
target_delta = float(output_a_target) - float(prev_output_a_target)
if abs(target_delta) < 1e-3:
if low_speed_extension_active:
if float(prev_output_a_target) < LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_TARGET:
return None
if float(output_a_target) < LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_TARGET:
return None
if abs(target_delta) < LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_DELTA_A:
return None
elif abs(target_delta) < 1e-3:
return None
headway_factor = float(np.clip(
(headway_margin - MATCHED_FOLLOW_TRANSITION_MIN_HEADWAY_MARGIN) /
max(MATCHED_FOLLOW_TRANSITION_FULL_HEADWAY_MARGIN - MATCHED_FOLLOW_TRANSITION_MIN_HEADWAY_MARGIN, 1e-3),
(headway_margin - min_headway_margin) /
max(full_headway_margin - min_headway_margin, 1e-3),
0.0,
1.0,
))
min_positive_step = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_POSITIVE_STEP if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_MIN_POSITIVE_STEP
max_positive_step = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_POSITIVE_STEP if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_MAX_POSITIVE_STEP
positive_step = float(np.interp(
max(float(lead.vLead) - float(v_ego), 0.0),
[0.0, 1.0],
[MATCHED_FOLLOW_TRANSITION_MIN_POSITIVE_STEP, MATCHED_FOLLOW_TRANSITION_MAX_POSITIVE_STEP],
[min_positive_step, max_positive_step],
))
min_negative_step = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MIN_NEGATIVE_STEP if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_MIN_NEGATIVE_STEP
max_negative_step = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_MAX_NEGATIVE_STEP if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_MAX_NEGATIVE_STEP
negative_step = float(np.interp(
closing_speed,
[0.0, MATCHED_FOLLOW_TRANSITION_MAX_CLOSING_SPEED],
[MATCHED_FOLLOW_TRANSITION_MIN_NEGATIVE_STEP, MATCHED_FOLLOW_TRANSITION_MAX_NEGATIVE_STEP],
[0.0, max_closing_speed],
[min_negative_step, max_negative_step],
))
# The more space we still have, the less abrupt the comfort path should be.
positive_step = float(np.interp(headway_factor, [0.0, 1.0], [positive_step, MATCHED_FOLLOW_TRANSITION_MIN_POSITIVE_STEP]))
negative_step = float(np.interp(headway_factor, [0.0, 1.0], [negative_step, MATCHED_FOLLOW_TRANSITION_MIN_NEGATIVE_STEP]))
positive_step = float(np.interp(headway_factor, [0.0, 1.0], [positive_step, min_positive_step]))
negative_step = float(np.interp(headway_factor, [0.0, 1.0], [negative_step, min_negative_step]))
if float(prev_output_a_target) * float(output_a_target) < 0.0:
positive_step = min(positive_step, MATCHED_FOLLOW_TRANSITION_SIGN_CROSS_STEP)
negative_step = min(negative_step, MATCHED_FOLLOW_TRANSITION_SIGN_CROSS_STEP)
sign_cross_step = LOW_SPEED_MATCHED_FOLLOW_TRANSITION_SIGN_CROSS_STEP if low_speed_extension_active else MATCHED_FOLLOW_TRANSITION_SIGN_CROSS_STEP
positive_step = min(positive_step, sign_cross_step)
negative_step = min(negative_step, sign_cross_step)
lower = float(prev_output_a_target) - negative_step
upper = float(prev_output_a_target) + positive_step
@@ -2042,6 +2081,8 @@ class LongitudinalPlanner:
effective_t_follow,
prev_output_a_target,
output_a_target,
self.mpc.source,
bool(getattr(sm["starpilotPlan"], "trackingLead", False)),
)
if matched_follow_transition_target is not None:
if matched_follow_transition_target < output_a_target:
@@ -43,6 +43,9 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import (
get_palisade_ff_scale,
get_palisade_friction_scale,
get_palisade_friction_threshold,
get_prius_ff_scale,
get_prius_friction_scale,
get_prius_friction_threshold,
get_ioniq_5_ff_scale,
get_ioniq_5_friction_scale,
get_ioniq_5_friction_threshold,
@@ -363,6 +366,41 @@ class TestLatControl:
assert left_turn_in > right_turn_in > base
assert base > left_unwind > right_unwind
def test_prius_ff_scale_curve(self):
assert get_prius_ff_scale(0.0, 0.0, 20.0) == 1.0
steady_left = get_prius_ff_scale(0.7, 0.0, 8.0)
steady_right = get_prius_ff_scale(-0.7, 0.0, 8.0)
turn_in_left = get_prius_ff_scale(0.7, 0.8, 8.0)
turn_in_right = get_prius_ff_scale(-0.7, -0.8, 8.0)
unwind_left = get_prius_ff_scale(0.7, -0.8, 8.0)
unwind_right = get_prius_ff_scale(-0.7, 0.8, 8.0)
assert steady_left > 1.0
assert steady_right > steady_left
assert turn_in_left > steady_left
assert turn_in_right > steady_right
assert unwind_left < steady_left
assert unwind_right < steady_right
assert unwind_right < unwind_left
def test_prius_friction_curves(self):
base_threshold = get_friction_threshold(12.0)
left_turn_in_threshold = get_prius_friction_threshold(6.0, 0.7, 0.8)
right_turn_in_threshold = get_prius_friction_threshold(6.0, -0.7, -0.8)
left_unwind_threshold = get_prius_friction_threshold(6.0, 0.7, -0.8)
right_unwind_threshold = get_prius_friction_threshold(6.0, -0.7, 0.8)
assert left_turn_in_threshold < base_threshold
assert right_turn_in_threshold < left_turn_in_threshold
assert left_unwind_threshold > base_threshold
assert right_unwind_threshold >= left_unwind_threshold
base_scale = get_prius_friction_scale(25.0, 0.7, 0.8)
left_turn_in_scale = get_prius_friction_scale(6.0, 0.7, 0.8)
right_turn_in_scale = get_prius_friction_scale(6.0, -0.7, -0.8)
left_unwind_scale = get_prius_friction_scale(6.0, 0.7, -0.8)
right_unwind_scale = get_prius_friction_scale(6.0, -0.7, 0.8)
assert right_turn_in_scale > left_turn_in_scale > base_scale
assert base_scale > left_unwind_scale > right_unwind_scale
def test_ioniq_5_ff_scale_curve(self):
assert get_ioniq_5_ff_scale(0.0, 0.0, 20.0) == 1.0
steady_left = get_ioniq_5_ff_scale(0.7, 0.0, 12.0)
@@ -1778,6 +1778,8 @@ def test_matched_follow_transition_target_damps_large_comfort_sign_flip():
1.45,
prev_output_a_target=0.12,
output_a_target=-0.40,
current_source="cruise",
tracking_lead_active=True,
)
assert smoothed is not None
@@ -1797,6 +1799,66 @@ def test_matched_follow_transition_target_skips_urgent_closure():
1.45,
prev_output_a_target=0.10,
output_a_target=-0.60,
current_source="cruise",
tracking_lead_active=True,
)
assert smoothed is None
def test_matched_follow_transition_target_damps_low_speed_tracking_cruise_throttle_jitter():
v_ego = 14.5
CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC)
planner = LongitudinalPlanner(CP, init_v=v_ego)
lead = make_lead(status=True, d_rel=31.1, v_lead=14.5, a_lead=0.0, radar=False, model_prob=0.999)
smoothed = planner.get_matched_follow_transition_target(
lead,
v_ego,
1.45,
prev_output_a_target=0.08,
output_a_target=0.46,
current_source="cruise",
tracking_lead_active=True,
)
assert smoothed is not None
assert smoothed == pytest.approx(0.14, abs=1e-6)
def test_matched_follow_transition_target_skips_low_speed_without_tracking():
v_ego = 14.5
CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC)
planner = LongitudinalPlanner(CP, init_v=v_ego)
lead = make_lead(status=True, d_rel=31.1, v_lead=14.5, a_lead=0.0, radar=False, model_prob=0.999)
smoothed = planner.get_matched_follow_transition_target(
lead,
v_ego,
1.45,
prev_output_a_target=0.08,
output_a_target=0.46,
current_source="cruise",
tracking_lead_active=False,
)
assert smoothed is None
def test_matched_follow_transition_target_skips_low_speed_real_braking():
v_ego = 14.5
CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC)
planner = LongitudinalPlanner(CP, init_v=v_ego)
lead = make_lead(status=True, d_rel=29.0, v_lead=13.6, a_lead=0.0, radar=False, model_prob=0.999)
smoothed = planner.get_matched_follow_transition_target(
lead,
v_ego,
1.45,
prev_output_a_target=0.08,
output_a_target=-0.30,
current_source="cruise",
tracking_lead_active=True,
)
assert smoothed is None
@@ -0,0 +1,29 @@
import subprocess
import time
_DELETE_TIMEOUT_S = 1800
_DELETE_RETRY_ATTEMPTS = 20
_DELETE_RETRY_DELAY_S = 0.25
_DIRECTORY_NOT_EMPTY_ERROR = "directory not empty"
def remove_path(path):
# Managed processes can recreate entries under /data/params while rm is finishing.
for attempt in range(_DELETE_RETRY_ATTEMPTS):
result = subprocess.run(
["sudo", "rm", "-rf", "--", path],
capture_output=True,
text=True,
timeout=_DELETE_TIMEOUT_S,
check=False,
)
if result.returncode == 0:
return
error_text = (result.stderr or result.stdout or "sudo rm -rf failed").strip()
can_retry = _DIRECTORY_NOT_EMPTY_ERROR in error_text.lower()
if not can_retry or attempt == _DELETE_RETRY_ATTEMPTS - 1:
raise RuntimeError(f"Failed to remove {path}: {error_text}")
time.sleep(_DELETE_RETRY_DELAY_S)
@@ -0,0 +1,44 @@
import subprocess
import pytest
from openpilot.starpilot.system.the_pond import factory_reset
def test_remove_path_retries_directory_not_empty(monkeypatch):
results = iter(
[
subprocess.CompletedProcess([], 1, stderr="rm: cannot remove '/data/params': Directory not empty"),
subprocess.CompletedProcess([], 0),
]
)
calls = []
sleeps = []
def fake_run(*args, **kwargs):
calls.append((args, kwargs))
return next(results)
monkeypatch.setattr(factory_reset.subprocess, "run", fake_run)
monkeypatch.setattr(factory_reset.time, "sleep", sleeps.append)
factory_reset.remove_path("/data/params")
assert len(calls) == 2
assert calls[0][0][0] == ["sudo", "rm", "-rf", "--", "/data/params"]
assert sleeps == [factory_reset._DELETE_RETRY_DELAY_S]
def test_remove_path_does_not_retry_non_transient_error(monkeypatch):
calls = []
def fake_run(*args, **kwargs):
calls.append((args, kwargs))
return subprocess.CompletedProcess([], 1, stderr="rm: cannot remove '/data/params': Permission denied")
monkeypatch.setattr(factory_reset.subprocess, "run", fake_run)
with pytest.raises(RuntimeError, match="Permission denied"):
factory_reset.remove_path("/data/params")
assert len(calls) == 1
+1 -13
View File
@@ -78,6 +78,7 @@ from openpilot.starpilot.common.testing_grounds import (
TESTING_GROUNDS_STATE_PATH as SHARED_TESTING_GROUNDS_STATE_PATH,
)
from openpilot.starpilot.navigation.destination_store import normalize_destination_payload, update_recent_destinations
from openpilot.starpilot.system.the_pond.factory_reset import remove_path as _run_factory_reset_delete
from openpilot.starpilot.system.the_pond import utilities
DISCORD_WEBHOOK_URL = os.getenv("DISCORD_WEBHOOK_URL")
@@ -633,7 +634,6 @@ _FAST_UPDATE_REBOOT_NOTICE_SECONDS = 6.0
_FAST_UPDATE_FETCH_TIMEOUT_S = 60
_FAST_BRANCH_SWITCH_FETCH_TIMEOUT_S = 60
_FAST_ROLLBACK_FETCH_TIMEOUT_S = 60
_FACTORY_RESET_DELETE_TIMEOUT_S = 1800
_GIT_PROGRESS_PERCENT_RE = re.compile(r'([A-Za-z][A-Za-z /_-]+):\s*([0-9]{1,3})%')
_GIT_SUBMODULE_SECTION_RE = re.compile(r'^\s*\[submodule\s+"[^"]+"\]\s*$', re.MULTILINE)
_ROLLBACK_REF = "refs/starpilot/rollback"
@@ -1572,18 +1572,6 @@ def _set_fast_update_error_state(message, exception):
progressDetail="Update failed. See Last Error below.",
)
def _run_factory_reset_delete(path):
result = subprocess.run(
["sudo", "rm", "-rf", path],
capture_output=True,
text=True,
timeout=_FACTORY_RESET_DELETE_TIMEOUT_S,
check=False,
)
if result.returncode != 0:
error_text = (result.stderr or result.stdout or "sudo rm -rf failed").strip()
raise RuntimeError(f"Failed to remove {path}: {error_text}")
def _factory_reset_worker():
started_at = time.time()