mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-20 15:54:13 +08:00
SLC break my kneecaps
This commit is contained in:
@@ -434,10 +434,13 @@ class Car:
|
||||
starpilot_plan = self.sm['starpilotPlan']
|
||||
starpilot_target_speed = float(starpilot_plan.vCruise)
|
||||
if self.starpilot_toggles.speed_limit_controller:
|
||||
slc_target_speed = max(
|
||||
float(starpilot_plan.slcOverriddenSpeed),
|
||||
float(starpilot_plan.slcSpeedLimit) + float(starpilot_plan.slcSpeedLimitOffset),
|
||||
overridden_speed = float(starpilot_plan.slcOverriddenSpeed)
|
||||
slc_limit = float(starpilot_plan.slcSpeedLimit) + float(starpilot_plan.slcSpeedLimitOffset)
|
||||
allow_lower_override = (
|
||||
getattr(self.starpilot_toggles, "redneck_cruise", False) and
|
||||
getattr(self.starpilot_toggles, "speed_limit_controller_override_set_speed", False)
|
||||
)
|
||||
slc_target_speed = overridden_speed if allow_lower_override and overridden_speed > 0 else max(overridden_speed, slc_limit)
|
||||
|
||||
# Use acceleration projection only when SLC has no resolved target.
|
||||
if self.CP.openpilotLongitudinalControl and slc_target_speed <= 0.0:
|
||||
|
||||
@@ -248,6 +248,41 @@ class TestRedneckCruise(unittest.TestCase):
|
||||
self.assertAlmostEqual(slc_target, target_speed)
|
||||
self.assertFalse(lead_present)
|
||||
|
||||
def test_card_target_speed_honors_lower_redneck_slc_override(self):
|
||||
starpilot_plan = SimpleNamespace(
|
||||
vCruise=65.0 * CV.KPH_TO_MS,
|
||||
slcOverriddenSpeed=55.0 * CV.KPH_TO_MS,
|
||||
slcSpeedLimit=65.0 * CV.KPH_TO_MS,
|
||||
slcSpeedLimitOffset=0.0,
|
||||
)
|
||||
sm = MagicMock()
|
||||
sm.seen = {"starpilotPlan": True, "longitudinalPlan": False, "radarState": False}
|
||||
sm.valid = sm.seen.copy()
|
||||
sm.__getitem__.side_effect = {"starpilotPlan": starpilot_plan}.__getitem__
|
||||
card = SimpleNamespace(
|
||||
CP=SimpleNamespace(openpilotLongitudinalControl=True),
|
||||
sm=sm,
|
||||
starpilot_toggles=SimpleNamespace(
|
||||
speed_limit_controller=True,
|
||||
redneck_cruise=True,
|
||||
speed_limit_controller_override_set_speed=True,
|
||||
),
|
||||
)
|
||||
car_state = SimpleNamespace(
|
||||
vEgo=60.0 * CV.KPH_TO_MS,
|
||||
vCruise=65.0,
|
||||
cruiseState=SimpleNamespace(speedCluster=65.0 * CV.KPH_TO_MS),
|
||||
)
|
||||
car_control = SimpleNamespace(
|
||||
actuators=SimpleNamespace(accel=0.0),
|
||||
hudControl=SimpleNamespace(leadVisible=False),
|
||||
)
|
||||
|
||||
target_speed, lead_present = Car._get_redneck_target_speed(card, car_state, car_control)
|
||||
|
||||
self.assertAlmostEqual(55.0 * CV.KPH_TO_MS, target_speed)
|
||||
self.assertFalse(lead_present)
|
||||
|
||||
def test_target_speed_returns_plan_minimum_when_slowing_down(self):
|
||||
target_speed = select_redneck_target_speed(
|
||||
120.0,
|
||||
|
||||
@@ -49,6 +49,7 @@ def make_toggles(**overrides):
|
||||
"speed_limit_confirmation_lower": False,
|
||||
"speed_limit_controller_override_manual": True,
|
||||
"speed_limit_controller_override_set_speed": False,
|
||||
"redneck_cruise": False,
|
||||
"speed_limit_filler": False,
|
||||
"speed_limit_offset1": 0.0,
|
||||
"speed_limit_offset2": 0.0,
|
||||
@@ -487,6 +488,30 @@ def test_set_speed_override_clears_on_new_speed_zone():
|
||||
controller.shutdown()
|
||||
|
||||
|
||||
def test_redneck_set_speed_mode_overrides_in_both_directions():
|
||||
controller = make_controller(
|
||||
speed_limit_controller_override_manual=False,
|
||||
speed_limit_controller_override_set_speed=True,
|
||||
redneck_cruise=True,
|
||||
)
|
||||
try:
|
||||
controller.source = "Dashboard"
|
||||
controller.target = mph(45)
|
||||
controller.last_valid_limit = mph(45)
|
||||
|
||||
controller.update_override(mph(45), 0.0, mph(45), 0.0, make_sm(gas_pressed=False))
|
||||
controller.update_override(mph(60), 0.0, mph(45), 0.0, make_sm(gas_pressed=False))
|
||||
assert controller.override_slc
|
||||
assert controller.overridden_speed == pytest.approx(mph(60))
|
||||
|
||||
# A manual decrease below the posted limit must become the new redneck target.
|
||||
controller.update_override(mph(35), 0.0, mph(45), 0.0, make_sm(gas_pressed=False))
|
||||
assert controller.override_slc
|
||||
assert controller.overridden_speed == pytest.approx(mph(35))
|
||||
finally:
|
||||
controller.shutdown()
|
||||
|
||||
|
||||
def test_gas_pedal_mode_ignores_set_speed_without_gas():
|
||||
# Set With Gas Pedal mode: a high set speed alone must NOT override; gas is still required.
|
||||
controller = make_controller(
|
||||
|
||||
@@ -244,6 +244,20 @@ def test_active_slc_control_target_applies_offset_and_cluster_diff():
|
||||
assert target == pytest.approx((48.0 * CV.MPH_TO_MS) - 0.4)
|
||||
|
||||
|
||||
def test_active_slc_control_target_allows_lower_redneck_override():
|
||||
target = get_active_slc_control_target(
|
||||
speed_limit_controller=True,
|
||||
set_speed_limit=False,
|
||||
slc_target=65.0 * CV.MPH_TO_MS,
|
||||
slc_offset=0.0,
|
||||
overridden_speed=35.0 * CV.MPH_TO_MS,
|
||||
v_ego_diff=0.4,
|
||||
allow_lower_override=True,
|
||||
)
|
||||
|
||||
assert target == pytest.approx((35.0 * CV.MPH_TO_MS) - 0.4)
|
||||
|
||||
|
||||
def test_slc_lead_drop_relaxed_target_softens_map_stepdown_for_harmless_lead():
|
||||
raw_target = 55.0 * CV.MPH_TO_MS
|
||||
previous_target = 65.0 * CV.MPH_TO_MS
|
||||
|
||||
@@ -129,7 +129,15 @@ class SpeedLimitController:
|
||||
target_with_offset = target_to_use + self.get_offset(target_to_use)
|
||||
if target_with_offset <= 0 or not self.override_mode_enabled:
|
||||
return False
|
||||
return self.overridden_speed > target_with_offset or (gas_pressed and v_ego > target_with_offset)
|
||||
bidirectional_set_speed = (
|
||||
getattr(self.starpilot_toggles, "redneck_cruise", False) and
|
||||
getattr(self.starpilot_toggles, "speed_limit_controller_override_set_speed", False)
|
||||
)
|
||||
return (
|
||||
(bidirectional_set_speed and self.overridden_speed > 0) or
|
||||
self.overridden_speed > target_with_offset or
|
||||
(gas_pressed and v_ego > target_with_offset)
|
||||
)
|
||||
|
||||
def clear_override_for_source_limit(self, desired_source, desired_target, had_override):
|
||||
if desired_source == "None" or desired_target <= 0:
|
||||
@@ -484,12 +492,12 @@ class SpeedLimitController:
|
||||
self.map_speed_limit = self.next_speed_limit
|
||||
|
||||
def update_override(self, v_cruise, v_cruise_diff, v_ego, v_ego_diff, sm):
|
||||
# A +/- press that raises the set speed is the gesture that (re)arms Max Set Speed
|
||||
# override. Detect the rising edge on the raw set speed (button-driven, no cluster jitter);
|
||||
# requiring a fresh edge is what makes the override clear per speed zone — once a new posted
|
||||
# limit wipes it (clear_override_for_source_limit), a steady high set speed will not re-arm.
|
||||
# Detect +/- changes on the raw set speed (button-driven, no cluster jitter). Requiring a
|
||||
# fresh edge is what makes the override clear per speed zone — once a new posted limit wipes
|
||||
# it, a steady set speed will not re-arm.
|
||||
prev_v_cruise = self._prev_v_cruise
|
||||
self._prev_v_cruise = v_cruise
|
||||
set_speed_changed = prev_v_cruise is not None and abs(v_cruise - prev_v_cruise) > SET_SPEED_RAISE_EPS
|
||||
set_speed_raised = prev_v_cruise is not None and v_cruise > prev_v_cruise + SET_SPEED_RAISE_EPS
|
||||
|
||||
if not sm["selfdriveState"].enabled:
|
||||
@@ -508,13 +516,24 @@ class SpeedLimitController:
|
||||
target_to_use = self.target_to_use
|
||||
offset = self.get_offset(target_to_use)
|
||||
set_speed = v_cruise + v_cruise_diff
|
||||
self.override_slc = self.override_slc and self.overridden_speed > target_to_use + offset > 0
|
||||
bidirectional_set_speed = (
|
||||
getattr(self.starpilot_toggles, "redneck_cruise", False) and
|
||||
getattr(self.starpilot_toggles, "speed_limit_controller_override_set_speed", False)
|
||||
)
|
||||
self.override_slc = self.override_slc and (
|
||||
(bidirectional_set_speed and self.overridden_speed > 0) or
|
||||
self.overridden_speed > target_to_use + offset > 0
|
||||
)
|
||||
self.override_slc |= not self.override_requires_gas_release and sm["carState"].gasPressed and v_ego > target_to_use + offset > 0
|
||||
# Max Set Speed mode: raising the set speed (+/-) above the posted limit overrides the
|
||||
# SLC hold directly, no gas pedal required. Only a fresh +/- press arms it, so entering a
|
||||
# new speed zone clears the override until the driver raises the set speed again.
|
||||
self.override_slc |= (self.starpilot_toggles.speed_limit_controller_override_set_speed
|
||||
and set_speed_raised and set_speed > target_to_use + offset > 0)
|
||||
# Redneck Max Set Speed mode uses +/- as a direct, bidirectional SLC override. The normal
|
||||
# mode retains its existing upward-only behavior for full-long cars.
|
||||
self.override_slc |= (
|
||||
self.starpilot_toggles.speed_limit_controller_override_set_speed and
|
||||
target_to_use + offset > 0 and
|
||||
set_speed > 0 and
|
||||
((bidirectional_set_speed and set_speed_changed) or
|
||||
(not bidirectional_set_speed and set_speed_raised and set_speed > target_to_use + offset > 0))
|
||||
)
|
||||
|
||||
if self.override_slc:
|
||||
if self.starpilot_toggles.speed_limit_controller_override_manual:
|
||||
|
||||
@@ -221,6 +221,8 @@ class StarPilotAcceleration:
|
||||
getattr(self.starpilot_planner.starpilot_vcruise, "slc_offset", 0.0),
|
||||
getattr(getattr(self.starpilot_planner.starpilot_vcruise, "slc", None), "overridden_speed", 0.0),
|
||||
v_ego_diff,
|
||||
allow_lower_override=(getattr(starpilot_toggles, "redneck_cruise", False) and
|
||||
getattr(starpilot_toggles, "speed_limit_controller_override_set_speed", False)),
|
||||
)
|
||||
v_target = float(self.starpilot_planner.v_cruise or raw_v_cruise)
|
||||
if effective_slc_target > 0.0:
|
||||
|
||||
@@ -70,13 +70,17 @@ OFFSET_FT_MIN = -20
|
||||
OFFSET_FT_MAX = 20
|
||||
|
||||
|
||||
def get_active_slc_control_target(speed_limit_controller, set_speed_limit, slc_target, slc_offset, overridden_speed, v_ego_diff):
|
||||
def get_active_slc_control_target(speed_limit_controller, set_speed_limit, slc_target, slc_offset, overridden_speed,
|
||||
v_ego_diff, allow_lower_override=False):
|
||||
# `SetSpeedLimit` only controls engage-time set-speed initialization. Ongoing
|
||||
# SLC speed matching must remain active whenever Speed Limit Controller is on.
|
||||
if not speed_limit_controller:
|
||||
return 0.0
|
||||
|
||||
base_target = max(float(overridden_speed), float(slc_target) + float(slc_offset))
|
||||
if allow_lower_override and overridden_speed > 0:
|
||||
base_target = float(overridden_speed)
|
||||
else:
|
||||
base_target = max(float(overridden_speed), float(slc_target) + float(slc_offset))
|
||||
if base_target <= 0.0:
|
||||
return 0.0
|
||||
|
||||
@@ -590,6 +594,8 @@ class StarPilotVCruise:
|
||||
self.slc_offset,
|
||||
self.slc.overridden_speed,
|
||||
v_ego_diff,
|
||||
allow_lower_override=(getattr(starpilot_toggles, "redneck_cruise", False) and
|
||||
getattr(starpilot_toggles, "speed_limit_controller_override_set_speed", False)),
|
||||
)
|
||||
slc_control_target = get_slc_lead_drop_relaxed_target(
|
||||
slc_control_target,
|
||||
|
||||
@@ -18,6 +18,8 @@ from openpilot.starpilot.common.favorite_slots import FAVORITE_ACTION_TRAFFIC_MO
|
||||
from openpilot.starpilot.common.starpilot_utilities import is_FrogsGoMoo
|
||||
from openpilot.starpilot.common.starpilot_variables import ERROR_LOGS_PATH, GearShifter, NON_DRIVING_GEARS
|
||||
|
||||
HYUNDAI_MAIN_CRUISE_AOL_CONFIRM_TIMEOUT_FRAMES = 100
|
||||
|
||||
|
||||
class StarPilotCard:
|
||||
@staticmethod
|
||||
@@ -44,6 +46,9 @@ class StarPilotCard:
|
||||
self.CP.brand == "hyundai" and not (hyundai_flags & HyundaiFlags.CANFD) and not hyundai_aol_before_engagement
|
||||
)
|
||||
self.hyundai_aol_ready = False
|
||||
self.g70_main_cruise_aol_pending = False
|
||||
self.g70_main_cruise_aol_pending_frames = 0
|
||||
self.prev_cruise_available = None
|
||||
self.prev_active = False
|
||||
self.prev_cruise_enabled = False
|
||||
self.decel_pressed = False
|
||||
@@ -130,6 +135,15 @@ class StarPilotCard:
|
||||
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)
|
||||
g70_main_cruise_aol_managed = (
|
||||
getattr(self.CP, "carFingerprint", None) == HYUNDAI_CAR.GENESIS_G70_2020
|
||||
and starpilot_toggles.main_cruise_aol_toggle
|
||||
)
|
||||
|
||||
if carState.gearShifter in NON_DRIVING_GEARS or not g70_main_cruise_aol_managed:
|
||||
self.g70_main_cruise_aol_pending = False
|
||||
self.g70_main_cruise_aol_pending_frames = 0
|
||||
|
||||
hyundai_aol_needs_engagement = self.hyundai_aol_needs_engagement and not starpilot_toggles.always_on_lateral_lkas
|
||||
|
||||
if hyundai_aol_needs_engagement:
|
||||
@@ -153,10 +167,28 @@ class StarPilotCard:
|
||||
if starpilot_toggles.main_cruise_aol_toggle:
|
||||
if hyundai_aol_needs_engagement:
|
||||
self.hyundai_aol_ready = True
|
||||
self.always_on_lateral_allowed = not self.always_on_lateral_allowed
|
||||
if g70_main_cruise_aol_managed:
|
||||
# The G70 reports the main-cruise transition after the button press.
|
||||
# Wait for that state change before sending active LKAS11 torque.
|
||||
self.g70_main_cruise_aol_pending = True
|
||||
self.g70_main_cruise_aol_pending_frames = 0
|
||||
else:
|
||||
self.always_on_lateral_allowed = not self.always_on_lateral_allowed
|
||||
elif starpilot_toggles.main_cruise_slc_adopt and starpilot_toggles.speed_limit_controller:
|
||||
self.params_memory.put_bool("SLCAdoptSpeedLimit", True)
|
||||
|
||||
cruise_available_changed = self.prev_cruise_available is not None and carState.cruiseState.available != self.prev_cruise_available
|
||||
if self.g70_main_cruise_aol_pending:
|
||||
if cruise_available_changed:
|
||||
self.always_on_lateral_allowed = carState.cruiseState.available
|
||||
self.g70_main_cruise_aol_pending = False
|
||||
self.g70_main_cruise_aol_pending_frames = 0
|
||||
else:
|
||||
self.g70_main_cruise_aol_pending_frames += 1
|
||||
if self.g70_main_cruise_aol_pending_frames >= HYUNDAI_MAIN_CRUISE_AOL_CONFIRM_TIMEOUT_FRAMES:
|
||||
self.g70_main_cruise_aol_pending = False
|
||||
self.g70_main_cruise_aol_pending_frames = 0
|
||||
|
||||
if starpilot_toggles.always_on_lateral_main and not button_managed_aol:
|
||||
car_fingerprint = getattr(self.CP, "carFingerprint", None)
|
||||
pcm_cruise = getattr(self.CP, "pcmCruise", False)
|
||||
@@ -179,6 +211,7 @@ class StarPilotCard:
|
||||
|
||||
self.prev_active = sm["selfdriveState"].active
|
||||
self.prev_cruise_enabled = carState.cruiseState.enabled
|
||||
self.prev_cruise_available = carState.cruiseState.available
|
||||
|
||||
self.always_on_lateral_enabled = self.always_on_lateral_allowed and self.always_on_lateral_set
|
||||
self.always_on_lateral_enabled &= carState.gearShifter not in NON_DRIVING_GEARS
|
||||
|
||||
@@ -409,14 +409,51 @@ def test_genesis_g90_main_cruise_button_toggles_aol_immediately(monkeypatch, tmp
|
||||
assert ret.alwaysOnLateralEnabled is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fingerprint", [spc.HYUNDAI_CAR.GENESIS_G70_2020, spc.HYUNDAI_CAR.HYUNDAI_PALISADE])
|
||||
def test_legacy_hyundai_main_cruise_button_toggles_aol_immediately(monkeypatch, tmp_path, fingerprint):
|
||||
def test_genesis_g70_main_cruise_button_waits_for_cruise_availability(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="hyundai", carFingerprint=fingerprint),
|
||||
SimpleNamespace(brand="hyundai", carFingerprint=spc.HYUNDAI_CAR.GENESIS_G70_2020),
|
||||
SimpleNamespace(alternativeExperience=spc.ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL),
|
||||
)
|
||||
|
||||
car_state = make_car_state(button_events=[SimpleNamespace(type=spc.ButtonType.mainCruise, pressed=True)])
|
||||
starpilot_car_state = SimpleNamespace(distancePressed=False)
|
||||
sm = make_sm()
|
||||
toggles = make_toggles(always_on_lateral=True, main_cruise_aol_toggle=True)
|
||||
|
||||
card.update(make_car_state(), starpilot_car_state, sm, toggles)
|
||||
ret = card.update(car_state, starpilot_car_state, sm, toggles)
|
||||
assert ret.alwaysOnLateralAllowed is False
|
||||
assert ret.alwaysOnLateralEnabled is False
|
||||
|
||||
car_state.buttonEvents = []
|
||||
car_state.cruiseState.available = True
|
||||
ret = card.update(car_state, starpilot_car_state, sm, toggles)
|
||||
assert ret.alwaysOnLateralAllowed is True
|
||||
assert ret.alwaysOnLateralEnabled is True
|
||||
|
||||
car_state.buttonEvents = [SimpleNamespace(type=spc.ButtonType.mainCruise, pressed=True)]
|
||||
ret = card.update(car_state, starpilot_car_state, sm, toggles)
|
||||
assert ret.alwaysOnLateralAllowed is True
|
||||
assert ret.alwaysOnLateralEnabled is True
|
||||
|
||||
car_state.buttonEvents = []
|
||||
car_state.cruiseState.available = False
|
||||
ret = card.update(car_state, starpilot_car_state, sm, toggles)
|
||||
assert ret.alwaysOnLateralAllowed is False
|
||||
assert ret.alwaysOnLateralEnabled is False
|
||||
|
||||
|
||||
def test_legacy_hyundai_main_cruise_button_toggles_aol_immediately(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="hyundai", carFingerprint=spc.HYUNDAI_CAR.HYUNDAI_PALISADE),
|
||||
SimpleNamespace(alternativeExperience=spc.ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user