mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-04 05:13:46 +08:00
SLC
This commit is contained in:
@@ -118,14 +118,17 @@ class TestVCruiseHelper:
|
||||
)
|
||||
assert pressed == (self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last)
|
||||
|
||||
def test_accel_stops_at_slc_target_before_crossing_it(self):
|
||||
@pytest.mark.parametrize(
|
||||
("starting_kph", "waypoint_kph", "expected_speeds"),
|
||||
[(30, 33, (33, 35, 40)), (65, 68, (68, 70, 75))],
|
||||
)
|
||||
def test_accel_stops_at_slc_target_before_crossing_it(self, starting_kph, waypoint_kph, expected_speeds):
|
||||
self.starpilot_toggles.cruise_increase = 5
|
||||
self.v_cruise_helper.v_cruise_kph = 30
|
||||
self.v_cruise_helper.v_cruise_cluster_kph = 30
|
||||
slc_target_with_offset = 33 * CV.KPH_TO_MS
|
||||
self.v_cruise_helper.v_cruise_kph = starting_kph
|
||||
self.v_cruise_helper.v_cruise_cluster_kph = starting_kph
|
||||
slc_target_with_offset = waypoint_kph * CV.KPH_TO_MS
|
||||
|
||||
# A 30 km/h limit with a +3 km/h SLC offset should be an intermediate stop.
|
||||
for expected_kph in (33, 35, 40):
|
||||
for expected_kph in expected_speeds:
|
||||
for pressed in (True, False):
|
||||
CS = car.CarState(cruiseState={"available": True})
|
||||
CS.buttonEvents = [ButtonEvent(type=ButtonType.accelCruise, pressed=pressed)]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -64,6 +64,7 @@ def make_sm(*, set_speed_kph=100.0, lead_one=None, lead_two=None, standstill=Fal
|
||||
"carState": SimpleNamespace(vCruise=set_speed_kph, standstill=standstill, vEgoCluster=v_ego_cluster),
|
||||
"carControl": SimpleNamespace(orientationNED=[0.0, pitch, 0.0]),
|
||||
"controlsState": SimpleNamespace(forceDecel=force_decel),
|
||||
"selfdriveState": SimpleNamespace(personality=1),
|
||||
"radarState": SimpleNamespace(
|
||||
leadOne=lead_one or make_lead(),
|
||||
leadTwo=lead_two or make_lead(),
|
||||
|
||||
@@ -2,6 +2,7 @@ import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from cereal import custom
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.starpilot.common.starpilot_variables import PLANNER_TIME
|
||||
@@ -36,15 +37,26 @@ class FakeParams:
|
||||
def get_float(self, *args, **kwargs):
|
||||
return 0.0
|
||||
|
||||
def get_bool(self, key):
|
||||
return bool(self.values.get(key, False))
|
||||
|
||||
def remove(self, key):
|
||||
self.values.pop(key, None)
|
||||
|
||||
def put_nonblocking(self, key, value):
|
||||
self.values[key] = value
|
||||
self.writes.append((key, value))
|
||||
|
||||
def put_float(self, key, value):
|
||||
self.values[key] = value
|
||||
|
||||
|
||||
def make_vcruise(*, red_light=False, raw_model_stopped=False, forcing_stop=False, nav_state=None, road_curvature=0.0):
|
||||
planner = SimpleNamespace(
|
||||
params=FakeParams(),
|
||||
params_memory=FakeParams({"NavInstructionState": nav_state or {}}),
|
||||
gps_position={},
|
||||
gps_valid=False,
|
||||
lead_one=SimpleNamespace(status=False, dRel=float("inf"), vLead=0.0),
|
||||
starpilot_cem=SimpleNamespace(stop_light_detected=red_light),
|
||||
starpilot_following=SimpleNamespace(following_lead=False),
|
||||
@@ -77,9 +89,14 @@ def make_sm(*, standstill=True, min_steer_speed=0.0, car_fingerprint=""):
|
||||
leftBlinker=False,
|
||||
rightBlinker=False,
|
||||
steeringAngleDeg=0.0,
|
||||
vCruise=72.0,
|
||||
),
|
||||
"liveParameters": SimpleNamespace(angleOffsetDeg=0.0),
|
||||
"mapdOut": SimpleNamespace(nextSpeedLimitDistance=0.0, nextSpeedLimit=0.0, speedLimit=0.0,
|
||||
waySelectionType=custom.WaySelectionType.fail, roadName=""),
|
||||
"selfdriveState": SimpleNamespace(enabled=True),
|
||||
"carParams": SimpleNamespace(minSteerSpeed=min_steer_speed, carFingerprint=car_fingerprint),
|
||||
"starpilotCarState": SimpleNamespace(accelPressed=False, dashboardStopSign=0, dashboardSpeedLimit=0),
|
||||
"starpilotCarState": SimpleNamespace(accelPressed=False, decelPressed=False, dashboardStopSign=0, dashboardSpeedLimit=0),
|
||||
"onroadEvents": [],
|
||||
}
|
||||
|
||||
@@ -105,6 +122,27 @@ def make_toggles():
|
||||
nav_longitudinal_allowed=False,
|
||||
speed_limit_controller=False,
|
||||
show_speed_limits=False,
|
||||
is_metric=False,
|
||||
map_speed_lookahead_higher=0.0,
|
||||
map_speed_lookahead_lower=0.0,
|
||||
slc_fallback_previous_speed_limit=False,
|
||||
slc_fallback_set_speed=False,
|
||||
slc_fallback_experimental_mode=False,
|
||||
slc_mapbox_filler=False,
|
||||
speed_limit_confirmation_higher=False,
|
||||
speed_limit_confirmation_lower=False,
|
||||
speed_limit_priority1="Dashboard",
|
||||
speed_limit_priority2="Map Data",
|
||||
speed_limit_priority_highest=False,
|
||||
speed_limit_priority_lowest=False,
|
||||
vision_speed_limit_detection=False,
|
||||
speed_limit_offset1=0.0,
|
||||
speed_limit_offset2=0.0,
|
||||
speed_limit_offset3=0.0,
|
||||
speed_limit_offset4=0.0,
|
||||
speed_limit_offset5=0.0,
|
||||
speed_limit_offset6=0.0,
|
||||
speed_limit_offset7=0.0,
|
||||
force_stop_distance_offset=0,
|
||||
)
|
||||
|
||||
@@ -112,7 +150,6 @@ def make_toggles():
|
||||
def test_active_slc_control_target_does_not_require_set_speed_limit():
|
||||
target = get_active_slc_control_target(
|
||||
speed_limit_controller=True,
|
||||
set_speed_limit=False,
|
||||
slc_target=45.0 * CV.MPH_TO_MS,
|
||||
slc_offset=3.0 * CV.MPH_TO_MS,
|
||||
overridden_speed=0.0,
|
||||
@@ -145,8 +182,7 @@ def test_active_slc_target_constrains_vcruise_below_csc_minimum(slc_target_mph,
|
||||
|
||||
vcruise.slc.target = slc_target_mph * CV.MPH_TO_MS
|
||||
vcruise.slc.source = "Dashboard"
|
||||
vcruise.slc.update_limits = lambda *_args, **_kwargs: None
|
||||
vcruise.slc.update_override = lambda *_args, **_kwargs: None
|
||||
vcruise.slc.update = lambda *_args, **_kwargs: None
|
||||
|
||||
result = update_vcruise(
|
||||
vcruise,
|
||||
@@ -417,6 +453,8 @@ def test_csc_res_press_defers_to_slc_confirmation():
|
||||
sm = make_sm(standstill=False)
|
||||
toggles = make_toggles()
|
||||
toggles.curve_speed_controller = True
|
||||
toggles.speed_limit_controller = True
|
||||
toggles.speed_limit_confirmation_higher = True
|
||||
|
||||
def set_curve_target(_v_ego, _v_cruise):
|
||||
vcruise.csc.target = 14.0
|
||||
@@ -426,10 +464,12 @@ def test_csc_res_press_defers_to_slc_confirmation():
|
||||
assert vcruise.csc_controlling_speed
|
||||
|
||||
|
||||
vcruise.slc.speed_limit_changed_timer = 1.0
|
||||
vcruise.slc.unconfirmed_speed_limit = 25.0
|
||||
# The candidate and accel press arrive together. SLC consumes the press in
|
||||
# this frame, even though accepting immediately clears the pending state.
|
||||
sm["starpilotCarState"].dashboardSpeedLimit = 45.0 * CV.MPH_TO_MS
|
||||
sm["starpilotCarState"].accelPressed = True
|
||||
update_vcruise(vcruise, sm, toggles, now=80.05, v_ego=20.0)
|
||||
assert vcruise.slc.confirmation_button_consumed
|
||||
assert not vcruise.csc_override
|
||||
|
||||
sm["starpilotCarState"].accelPressed = False
|
||||
@@ -622,7 +662,6 @@ def test_curve_speed_controller_hysteresis_keeps_glow_off_for_marginal_targets()
|
||||
def test_active_slc_control_target_applies_offset_and_cluster_diff():
|
||||
target = get_active_slc_control_target(
|
||||
speed_limit_controller=True,
|
||||
set_speed_limit=True,
|
||||
slc_target=45.0 * CV.MPH_TO_MS,
|
||||
slc_offset=3.0 * CV.MPH_TO_MS,
|
||||
overridden_speed=0.0,
|
||||
@@ -635,7 +674,6 @@ def test_active_slc_control_target_applies_offset_and_cluster_diff():
|
||||
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,
|
||||
|
||||
@@ -1461,6 +1461,8 @@ class StarPilotVariables:
|
||||
speed_limit_confirmation = self.get_value("SLCConfirmation", condition=toggle.speed_limit_controller)
|
||||
toggle.speed_limit_confirmation_higher = self.get_value("SLCConfirmationHigher", condition=speed_limit_confirmation)
|
||||
toggle.speed_limit_confirmation_lower = self.get_value("SLCConfirmationLower", condition=speed_limit_confirmation)
|
||||
# Legacy setting is hidden in the current UI. SLC's pedal and +/- overrides
|
||||
# remain available regardless of its saved value; keep loading it for compatibility.
|
||||
slc_override_method = self.get_value("SLCOverride", cast=float, condition=toggle.speed_limit_controller)
|
||||
toggle.speed_limit_controller_override_manual = slc_override_method == 1
|
||||
toggle.speed_limit_controller_override_set_speed = slc_override_method == 2
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import calendar
|
||||
import json
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
import requests
|
||||
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.starpilot.common.starpilot_utilities import calculate_bearing_offset, is_url_pingable
|
||||
|
||||
|
||||
FREE_MAPBOX_REQUESTS = 100_000
|
||||
|
||||
|
||||
class MapboxSpeedLimit:
|
||||
def __init__(self, params):
|
||||
self.params = params
|
||||
try:
|
||||
self.requests = json.loads(params.get("MapBoxRequests", encoding="utf-8") or "{}")
|
||||
except (TypeError, ValueError):
|
||||
self.requests = {}
|
||||
self.requests.setdefault("total_requests", 0)
|
||||
self.requests.setdefault("max_requests", FREE_MAPBOX_REQUESTS - 28 * 100)
|
||||
|
||||
self.host = "https://api.mapbox.com"
|
||||
self.token = params.get("MapboxSecretKey", encoding="utf-8")
|
||||
self.limit = 0.0
|
||||
self.segment_distance = 0.0
|
||||
self.future = None
|
||||
self.executor = ThreadPoolExecutor(max_workers=1)
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({"Accept-Language": "en"})
|
||||
self.session.headers.update({"User-Agent": "starpilot-mapbox-speed-limit-retriever/1.0 (https://github.com/FrogAi/StarPilot)"})
|
||||
|
||||
def reset(self):
|
||||
# A discarded future may still finish, but only update() can publish its result.
|
||||
if self.future is not None:
|
||||
self.future.cancel()
|
||||
self.future = None
|
||||
self.limit = 0.0
|
||||
self.segment_distance = 0.0
|
||||
|
||||
def shutdown(self):
|
||||
self.reset()
|
||||
self.executor.shutdown(wait=False, cancel_futures=True)
|
||||
self.session.close()
|
||||
|
||||
def _request(self, position, v_ego):
|
||||
if not is_url_pingable(self.host):
|
||||
return 0.0, v_ego
|
||||
|
||||
self.requests["total_requests"] += 1
|
||||
self.params.put_nonblocking("MapBoxRequests", json.dumps(self.requests))
|
||||
|
||||
bearing = position.get("bearing")
|
||||
latitude = position.get("latitude")
|
||||
longitude = position.get("longitude")
|
||||
future_latitude, future_longitude = calculate_bearing_offset(latitude, longitude, bearing, v_ego)
|
||||
url = f"{self.host}/matching/v5/mapbox/driving/{longitude},{latitude};{future_longitude},{future_latitude}.json"
|
||||
params = {
|
||||
"access_token": self.token,
|
||||
"annotations": "maxspeed,distance",
|
||||
"geometries": "polyline6",
|
||||
"overview": "full",
|
||||
"steps": "false",
|
||||
"radiuses": "10;10",
|
||||
"tidy": "true",
|
||||
}
|
||||
response = self.session.get(url, params=params, timeout=10)
|
||||
response.raise_for_status()
|
||||
matchings = response.json().get("matchings") or []
|
||||
if not matchings:
|
||||
return 0.0, v_ego
|
||||
legs = (matchings[0] or {}).get("legs") or []
|
||||
if not legs:
|
||||
return 0.0, v_ego
|
||||
|
||||
annotation = legs[0].get("annotation") or {}
|
||||
distances = annotation.get("distance") or [v_ego]
|
||||
speeds = annotation.get("maxspeed") or []
|
||||
if not speeds:
|
||||
return 0.0, v_ego
|
||||
first = speeds[0]
|
||||
try:
|
||||
speed = float(first.get("speed")) if first.get("speed") != "none" else 0.0
|
||||
except (TypeError, ValueError):
|
||||
speed = 0.0
|
||||
if speed <= 0:
|
||||
return 0.0, v_ego
|
||||
conversion = CV.MPH_TO_MS if first.get("unit", "km/h") == "mph" else CV.KPH_TO_MS
|
||||
return speed * conversion, distances[0]
|
||||
|
||||
def update(self, now, time_validated, v_ego, gps_valid, position, steering_angle, angle_offset):
|
||||
if not gps_valid or not self.token or abs(steering_angle - angle_offset) >= 45:
|
||||
self.reset()
|
||||
return
|
||||
|
||||
if time_validated and now.month != self.requests.get("month"):
|
||||
self.requests.update({
|
||||
"month": now.month,
|
||||
"total_requests": 0,
|
||||
"max_requests": FREE_MAPBOX_REQUESTS - calendar.monthrange(now.year, now.month)[1] * 100,
|
||||
})
|
||||
if self.requests["total_requests"] >= self.requests["max_requests"]:
|
||||
self.reset()
|
||||
return
|
||||
|
||||
if self.future is not None:
|
||||
if not self.future.done():
|
||||
return
|
||||
future = self.future
|
||||
self.future = None
|
||||
try:
|
||||
self.limit, self.segment_distance = future.result()
|
||||
except Exception as exception:
|
||||
print(f"Unexpected error in Mapbox request: {exception}")
|
||||
self.limit, self.segment_distance = 0.0, v_ego
|
||||
return
|
||||
|
||||
if v_ego < 1:
|
||||
return
|
||||
if self.segment_distance > 0:
|
||||
self.segment_distance -= v_ego * DT_MDL
|
||||
return
|
||||
|
||||
try:
|
||||
self.future = self.executor.submit(self._request, dict(position), v_ego)
|
||||
except RuntimeError:
|
||||
self.segment_distance = v_ego
|
||||
return
|
||||
@@ -1,19 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
# PFEIFER - SLC - Modified by FrogAi
|
||||
import calendar
|
||||
import json
|
||||
import requests
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.selfdrive.car.cruise import V_CRUISE_UNSET
|
||||
|
||||
from cereal import custom
|
||||
from openpilot.starpilot.common.starpilot_utilities import calculate_bearing_offset, calculate_distance_to_point, is_url_pingable
|
||||
from openpilot.starpilot.controls.lib.mapbox_speed_limit import MapboxSpeedLimit
|
||||
|
||||
FREE_MAPBOX_REQUESTS = 100_000
|
||||
|
||||
SOURCE_NONE = "None"
|
||||
SOURCE_DASHBOARD = "Dashboard"
|
||||
SOURCE_MAP = "Map Data"
|
||||
SOURCE_VISION = "Vision"
|
||||
SOURCE_MAPBOX = "Mapbox"
|
||||
REAL_SOURCES = (SOURCE_DASHBOARD, SOURCE_MAP, SOURCE_VISION, SOURCE_MAPBOX)
|
||||
|
||||
OFFSET_MAP_IMPERIAL = [
|
||||
(0, 11.2, "speed_limit_offset1"), # 0–24 mph
|
||||
@@ -36,83 +36,76 @@ OFFSET_MAP_METRIC = [
|
||||
]
|
||||
|
||||
SLC_OVERRIDE_DISABLE_CLEAR_TIME = 0.75
|
||||
# Minimum set-speed increase (m/s) counted as a deliberate +/- press. Below the smallest
|
||||
# real step (1 km/h ≈ 0.28 m/s), above cluster/float jitter.
|
||||
SET_SPEED_RAISE_EPS = 0.1
|
||||
SET_SPEED_CHANGE_TOLERANCE_METERS_PER_SECOND = 0.1
|
||||
SAME_LIMIT_TOLERANCE = 1.0
|
||||
VISION_LARGE_REFERENCE_SPEED_DELTA = 30 * CV.MPH_TO_MS
|
||||
VISION_LARGE_SET_SPEED_MIN_SUPPORT = 3
|
||||
VISION_SUPPORT_SPEED_TOLERANCE = 0.5 * CV.MPH_TO_MS
|
||||
|
||||
|
||||
class SpeedLimitController:
|
||||
def __init__(self, StarPilotVCruise):
|
||||
self.starpilot_planner = StarPilotVCruise.starpilot_planner
|
||||
self.starpilot_toggles = None
|
||||
self.mapbox = MapboxSpeedLimit(self.starpilot_planner.params)
|
||||
|
||||
self.calling_mapbox = False
|
||||
self.override_slc = False
|
||||
self.override_disable_timer = 0.0
|
||||
self._prev_v_cruise = None
|
||||
self._persistent_override_speed = 0.0
|
||||
self._set_speed_override_input_consumed = False
|
||||
self.source = SOURCE_NONE
|
||||
self.target = 0.0
|
||||
self.map_speed_limit = 0.0
|
||||
self.next_speed_limit = 0.0
|
||||
self.vision_limit = 0.0
|
||||
self.overridden_speed = 0.0
|
||||
|
||||
self.denied_target = 0
|
||||
self.map_speed_limit = 0
|
||||
self.mapbox_limit = 0
|
||||
self.next_speed_limit = 0
|
||||
self.overridden_speed = 0
|
||||
self.segment_distance = 0
|
||||
self.speed_limit_changed_timer = 0
|
||||
self.target = 0
|
||||
self.unconfirmed_speed_limit = 0
|
||||
self.vision_limit = 0
|
||||
|
||||
self.previous_source = "None"
|
||||
self.source = "None"
|
||||
self.last_valid_limit = max(self.starpilot_planner.params.get_float("PreviousSpeedLimit"), 0.0)
|
||||
self.last_valid_source = SOURCE_NONE # The persisted number has no known live source.
|
||||
self.pending_limit = 0.0
|
||||
self.pending_source = SOURCE_NONE
|
||||
self.confirmation_time = 0.0
|
||||
self.denied_limit = 0.0
|
||||
self.previous_road_name = ""
|
||||
|
||||
self._slc_adopt_counter = 0
|
||||
|
||||
mapbox_requests_raw = self.starpilot_planner.params.get("MapBoxRequests", encoding="utf-8")
|
||||
try:
|
||||
self.mapbox_requests = json.loads(mapbox_requests_raw or "{}")
|
||||
except (TypeError, ValueError):
|
||||
self.mapbox_requests = {}
|
||||
self.mapbox_requests.setdefault("total_requests", 0)
|
||||
self.mapbox_requests.setdefault("max_requests", FREE_MAPBOX_REQUESTS - (28 * 100))
|
||||
|
||||
self.mapbox_host = "https://api.mapbox.com"
|
||||
self.mapbox_token = self.starpilot_planner.params.get("MapboxSecretKey", encoding="utf-8")
|
||||
|
||||
self.previous_target = self.starpilot_planner.params.get_float("PreviousSpeedLimit")
|
||||
self.last_valid_limit = self.previous_target if self.previous_target > 0 else 0
|
||||
|
||||
self.executor = ThreadPoolExecutor(max_workers=1)
|
||||
self.mapbox_future = None
|
||||
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({"Accept-Language": "en"})
|
||||
self.session.headers.update({"User-Agent": "starpilot-mapbox-speed-limit-retriever/1.0 (https://github.com/FrogAi/StarPilot)"})
|
||||
self.set_speed_override = 0.0
|
||||
self.pedal_override = 0.0
|
||||
self.previous_set_speed = None
|
||||
self.consume_set_speed_change = False
|
||||
self.override_disable_time = 0.0
|
||||
self.limit_change_started = False
|
||||
self.confirmation_button_consumed = False
|
||||
self._active_control = False
|
||||
self._using_experimental_fallback = False
|
||||
self._mode = "off"
|
||||
|
||||
def shutdown(self):
|
||||
self.executor.shutdown(wait=False, cancel_futures=True)
|
||||
self.session.close()
|
||||
self.mapbox.shutdown()
|
||||
|
||||
@property
|
||||
def mapbox_limit(self):
|
||||
return self.mapbox.limit
|
||||
|
||||
@property
|
||||
def confirmation_pending(self):
|
||||
return self.pending_limit >= 1
|
||||
|
||||
@property
|
||||
def unconfirmed_speed_limit(self):
|
||||
return self.pending_limit
|
||||
|
||||
@property
|
||||
def experimental_mode(self):
|
||||
return self.target == 0 and bool(getattr(self.starpilot_toggles, "slc_fallback_experimental_mode", False))
|
||||
return self._active_control and self._using_experimental_fallback
|
||||
|
||||
@property
|
||||
def target_to_use(self):
|
||||
if self.source == "None" and self.target > 0 and self.last_valid_limit > 0:
|
||||
if self.target >= self.last_valid_limit:
|
||||
return self.last_valid_limit
|
||||
# Keep Set Speed fallback from arming an override against a higher fake limit.
|
||||
if self.source == SOURCE_NONE and self.target > 0 and self.last_valid_limit > 0:
|
||||
return min(self.target, self.last_valid_limit)
|
||||
return self.target
|
||||
|
||||
def get_offset(self, target_speed):
|
||||
def get_offset(self, limit):
|
||||
if self.starpilot_toggles is None:
|
||||
return 0
|
||||
return 0.0
|
||||
offset_map = OFFSET_MAP_METRIC if self.starpilot_toggles.is_metric else OFFSET_MAP_IMPERIAL
|
||||
return next((getattr(self.starpilot_toggles, offset) for low, high, offset in offset_map if low <= target_speed < high), 0)
|
||||
return next((getattr(self.starpilot_toggles, name) for low, high, name in offset_map if low <= limit < high), 0.0)
|
||||
|
||||
@property
|
||||
def offset(self):
|
||||
@@ -124,458 +117,300 @@ class SpeedLimitController:
|
||||
0 < limit <= max(getattr(self.starpilot_toggles, "vision_speed_limit_low_limit_threshold", 0), 0)
|
||||
)
|
||||
|
||||
def _confirmation_required(self, desired_source, desired_target):
|
||||
return desired_source != "None" and (
|
||||
(desired_target < self.target and self.starpilot_toggles.speed_limit_confirmation_lower) or
|
||||
(desired_target > self.target and self.starpilot_toggles.speed_limit_confirmation_higher)
|
||||
)
|
||||
def reset_control_state(self):
|
||||
self._clear_pending()
|
||||
self.clear_override()
|
||||
self.previous_set_speed = None
|
||||
self.consume_set_speed_change = False
|
||||
self.override_disable_time = 0.0
|
||||
self.limit_change_started = False
|
||||
self.confirmation_button_consumed = False
|
||||
self._active_control = False
|
||||
self._using_experimental_fallback = False
|
||||
self.starpilot_planner.params_memory.remove("SpeedLimitAccepted")
|
||||
self.starpilot_planner.params_memory.remove("SLCAdoptSpeedLimit")
|
||||
|
||||
def clear_override(self):
|
||||
self.override_slc = False
|
||||
self.overridden_speed = 0
|
||||
self._persistent_override_speed = 0.0
|
||||
def _get_vision_limit(self, v_ego, sm, display_only):
|
||||
enabled = getattr(self.starpilot_toggles, "vision_speed_limit_detection", False)
|
||||
self.vision_limit = self.starpilot_planner.params_memory.get_float("VisionSpeedLimit") if enabled else 0.0
|
||||
limit = self.vision_limit
|
||||
if not display_only and self.low_vision_limit_filtered(limit):
|
||||
return 0.0
|
||||
|
||||
def clear_persistent_override(self):
|
||||
self._persistent_override_speed = 0.0
|
||||
|
||||
def clear_persistent_override_for_limit_change(self, previous_limit, new_limit):
|
||||
if self._persistent_override_speed <= 0:
|
||||
return
|
||||
if previous_limit <= 0 or new_limit <= 0 or abs(new_limit - previous_limit) < 0.1:
|
||||
return
|
||||
|
||||
new_target_with_offset = new_limit + self.get_offset(new_limit)
|
||||
if new_limit < previous_limit or self._persistent_override_speed <= new_target_with_offset:
|
||||
self.clear_persistent_override()
|
||||
|
||||
def get_mapbox_speed_limit(self, now, time_validated, v_ego, sm):
|
||||
if not self.starpilot_planner.gps_valid or not self.mapbox_token or abs(sm["carState"].steeringAngleDeg - sm["liveParameters"].angleOffsetDeg) >= 45:
|
||||
self.mapbox_limit = 0
|
||||
self.segment_distance = 0
|
||||
return
|
||||
|
||||
if v_ego < 1:
|
||||
return
|
||||
|
||||
if self.segment_distance > 0:
|
||||
self.segment_distance -= v_ego * DT_MDL
|
||||
return
|
||||
|
||||
if self.calling_mapbox:
|
||||
self.segment_distance = v_ego
|
||||
return
|
||||
|
||||
def make_request():
|
||||
successful = False
|
||||
response_data = None
|
||||
try:
|
||||
if not is_url_pingable(self.mapbox_host):
|
||||
self.segment_distance = 1000
|
||||
successful = True
|
||||
return None
|
||||
|
||||
if time_validated:
|
||||
current_month = now.month
|
||||
if current_month != self.mapbox_requests.get("month"):
|
||||
self.mapbox_requests.update({
|
||||
"month": current_month,
|
||||
"total_requests": 0,
|
||||
"max_requests": FREE_MAPBOX_REQUESTS - calendar.monthrange(now.year, current_month)[1] * 100,
|
||||
})
|
||||
|
||||
self.mapbox_requests["total_requests"] += 1
|
||||
self.starpilot_planner.params.put_nonblocking("MapBoxRequests", json.dumps(self.mapbox_requests))
|
||||
|
||||
current_bearing = self.starpilot_planner.gps_position.get("bearing")
|
||||
current_latitude = self.starpilot_planner.gps_position.get("latitude")
|
||||
current_longitude = self.starpilot_planner.gps_position.get("longitude")
|
||||
|
||||
future_latitude, future_longitude = calculate_bearing_offset(current_latitude, current_longitude, current_bearing, v_ego)
|
||||
|
||||
url = (
|
||||
f"{self.mapbox_host}/matching/v5/mapbox/driving/"
|
||||
f"{current_longitude},{current_latitude};"
|
||||
f"{future_longitude},{future_latitude}.json"
|
||||
)
|
||||
|
||||
mapbox_params = {
|
||||
"access_token": self.mapbox_token,
|
||||
"annotations": "maxspeed,distance",
|
||||
"geometries": "polyline6",
|
||||
"overview": "full",
|
||||
"steps": "false",
|
||||
"radiuses": "10;10",
|
||||
"tidy": "true",
|
||||
}
|
||||
|
||||
response = self.session.get(url, params=mapbox_params, timeout=10)
|
||||
response.raise_for_status()
|
||||
|
||||
successful = True
|
||||
response_data = response.json()
|
||||
except Exception as exception:
|
||||
print(f"Unexpected error in Mapbox request: {exception}")
|
||||
finally:
|
||||
self.calling_mapbox = False
|
||||
|
||||
if not successful:
|
||||
self.mapbox_limit = 0
|
||||
self.segment_distance = v_ego
|
||||
return response_data
|
||||
|
||||
def complete_request(future):
|
||||
try:
|
||||
data = future.result()
|
||||
if data:
|
||||
matchings = data.get("matchings") or []
|
||||
if not matchings:
|
||||
self.mapbox_limit = 0
|
||||
self.segment_distance = v_ego
|
||||
return
|
||||
|
||||
legs = (matchings[0] or {}).get("legs") or []
|
||||
if not legs:
|
||||
self.mapbox_limit = 0
|
||||
self.segment_distance = v_ego
|
||||
return
|
||||
|
||||
annotation = legs[0].get("annotation") or {}
|
||||
|
||||
distances = annotation.get("distance") or [v_ego]
|
||||
segment_distance = distances[0]
|
||||
|
||||
speed_data = annotation.get("maxspeed", [])
|
||||
if speed_data:
|
||||
first_segment_speed = speed_data[0]
|
||||
try:
|
||||
raw_speed = float(first_segment_speed.get("speed") if first_segment_speed.get("speed") != "none" else 0.0)
|
||||
except (ValueError, TypeError):
|
||||
raw_speed = 0.0
|
||||
unit = first_segment_speed.get("unit", "km/h")
|
||||
if raw_speed > 0:
|
||||
if unit == "mph":
|
||||
self.mapbox_limit = raw_speed * CV.MPH_TO_MS
|
||||
else:
|
||||
self.mapbox_limit = raw_speed * CV.KPH_TO_MS
|
||||
self.segment_distance = segment_distance
|
||||
return
|
||||
|
||||
self.mapbox_limit = 0
|
||||
self.segment_distance = v_ego
|
||||
|
||||
except Exception as exception:
|
||||
print(f"Mapbox Callback Error: {exception}")
|
||||
self.mapbox_limit = 0
|
||||
self.segment_distance = v_ego
|
||||
finally:
|
||||
self.mapbox_future = None
|
||||
|
||||
self.calling_mapbox = True
|
||||
try:
|
||||
future = self.executor.submit(make_request)
|
||||
except RuntimeError:
|
||||
self.calling_mapbox = False
|
||||
self.segment_distance = v_ego
|
||||
return
|
||||
|
||||
self.mapbox_future = future
|
||||
future.add_done_callback(complete_request)
|
||||
|
||||
def handle_limit_change(self, desired_source, desired_target, current_road_name, v_ego, sm):
|
||||
self.speed_limit_changed_timer += DT_MDL
|
||||
previous_limit = self.last_valid_limit if self.last_valid_limit > 0 else self.target
|
||||
|
||||
long_active = sm["carControl"].longActive
|
||||
accepted_by_accel_button = sm["starpilotCarState"].accelPressed and long_active
|
||||
confirmation_required = self._confirmation_required(desired_source, desired_target)
|
||||
higher_confirmation = confirmation_required and desired_target > self.target
|
||||
speed_limit_accepted = confirmation_required and accepted_by_accel_button
|
||||
if confirmation_required and not speed_limit_accepted and self._slc_adopt_counter % 4 == 0:
|
||||
speed_limit_accepted = self.starpilot_planner.params_memory.get_bool("SpeedLimitAccepted")
|
||||
if not confirmation_required:
|
||||
self.starpilot_planner.params_memory.remove("SpeedLimitAccepted")
|
||||
self.unconfirmed_speed_limit = 0
|
||||
speed_limit_denied = confirmation_required and (
|
||||
sm["starpilotCarState"].decelPressed or (self.speed_limit_changed_timer >= 30 and long_active)
|
||||
)
|
||||
|
||||
if not long_active and not sm["selfdriveState"].enabled:
|
||||
speed_limit_accepted = True
|
||||
|
||||
if speed_limit_accepted:
|
||||
self.source = desired_source
|
||||
self.target = desired_target
|
||||
self.clear_persistent_override_for_limit_change(previous_limit, desired_target)
|
||||
set_speed_kph = float(sm["carState"].vCruise)
|
||||
target_with_offset = self.target + self.offset
|
||||
if (
|
||||
higher_confirmation
|
||||
and long_active
|
||||
and 0 < set_speed_kph < V_CRUISE_UNSET
|
||||
and set_speed_kph * CV.KPH_TO_MS < target_with_offset
|
||||
):
|
||||
self.starpilot_planner.params_memory.put_float("SLCForceCruiseSpeed", target_with_offset)
|
||||
if accepted_by_accel_button and confirmation_required:
|
||||
self._set_speed_override_input_consumed = True
|
||||
|
||||
self.starpilot_planner.params_memory.remove("SpeedLimitAccepted")
|
||||
|
||||
elif speed_limit_denied:
|
||||
self.starpilot_planner.params_memory.remove("SpeedLimitAccepted")
|
||||
self.denied_target = desired_target
|
||||
|
||||
self.previous_source = desired_source
|
||||
self.previous_target = desired_target
|
||||
self.previous_road_name = current_road_name
|
||||
|
||||
elif desired_target != self.target and not confirmation_required:
|
||||
self.source = desired_source
|
||||
self.target = desired_target
|
||||
self.clear_persistent_override_for_limit_change(previous_limit, desired_target)
|
||||
|
||||
elif desired_target == self.target:
|
||||
self.source = desired_source
|
||||
self.target = desired_target
|
||||
|
||||
else:
|
||||
self.source = "None"
|
||||
self.unconfirmed_speed_limit = desired_target
|
||||
|
||||
if (self.target != self.previous_target or self.previous_road_name != current_road_name) and self.target > 0 and not speed_limit_denied:
|
||||
self.denied_target = 0
|
||||
|
||||
self.previous_source = self.source
|
||||
self.previous_target = self.target
|
||||
self.previous_road_name = current_road_name
|
||||
|
||||
self.starpilot_planner.params.put_nonblocking("PreviousSpeedLimit", float(self.target))
|
||||
|
||||
def update_limits(self, dashboard_speed_limit, now, time_validated, v_cruise, v_ego, sm, display_only=False):
|
||||
self.update_map_speed_limit(v_ego, sm)
|
||||
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
|
||||
reference_speed = selected_set_speed if selected_set_speed > 0 else max(float(v_ego), 0)
|
||||
# vEgo jitters around zero at standstill; do not let that switch the active source.
|
||||
if (
|
||||
usable_vision_limit > 0 and reference_speed > 0 and not sm["carState"].standstill and
|
||||
abs(usable_vision_limit - reference_speed) >= VISION_LARGE_REFERENCE_SPEED_DELTA
|
||||
):
|
||||
support_count = self.starpilot_planner.params_memory.get_int("VisionSpeedLimitSupportCount")
|
||||
support_speed = self.starpilot_planner.params_memory.get_float("VisionSpeedLimitSupportSpeed")
|
||||
if support_count < VISION_LARGE_SET_SPEED_MIN_SUPPORT or abs(support_speed - usable_vision_limit) > VISION_SUPPORT_SPEED_TOLERANCE:
|
||||
usable_vision_limit = 0
|
||||
selected_speed = raw_set_speed_kph * CV.KPH_TO_MS if 0 < raw_set_speed_kph < V_CRUISE_UNSET else 0.0
|
||||
reference_speed = selected_speed if selected_speed > 0 else max(float(v_ego), 0.0)
|
||||
if (limit > 0 and reference_speed > 0 and not sm["carState"].standstill and
|
||||
abs(limit - reference_speed) >= VISION_LARGE_REFERENCE_SPEED_DELTA):
|
||||
memory = self.starpilot_planner.params_memory
|
||||
count = memory.get_int("VisionSpeedLimitSupportCount")
|
||||
support_speed = memory.get_float("VisionSpeedLimitSupportSpeed")
|
||||
if count < VISION_LARGE_SET_SPEED_MIN_SUPPORT or abs(support_speed - limit) > VISION_SUPPORT_SPEED_TOLERANCE:
|
||||
return 0.0
|
||||
return limit
|
||||
|
||||
configured_priorities = {
|
||||
self.starpilot_toggles.speed_limit_priority1,
|
||||
self.starpilot_toggles.speed_limit_priority2,
|
||||
}
|
||||
limits = {
|
||||
"Dashboard": dashboard_speed_limit,
|
||||
"Map Data": self.map_speed_limit,
|
||||
}
|
||||
if "Vision" in configured_priorities:
|
||||
limits["Vision"] = usable_vision_limit
|
||||
filtered_limits = {source: limit for source, limit in limits.items() if limit >= 1}
|
||||
|
||||
if self.starpilot_toggles.speed_limit_priority_highest:
|
||||
desired_source = max(filtered_limits, key=filtered_limits.get, default="None")
|
||||
desired_target = filtered_limits.get(desired_source, 0)
|
||||
|
||||
elif self.starpilot_toggles.speed_limit_priority_lowest:
|
||||
desired_source = min(filtered_limits, key=filtered_limits.get, default="None")
|
||||
desired_target = filtered_limits.get(desired_source, 0)
|
||||
|
||||
elif filtered_limits:
|
||||
for priority in [
|
||||
self.starpilot_toggles.speed_limit_priority1,
|
||||
self.starpilot_toggles.speed_limit_priority2
|
||||
]:
|
||||
if priority in filtered_limits:
|
||||
desired_source = priority
|
||||
desired_target = filtered_limits[desired_source]
|
||||
break
|
||||
else:
|
||||
desired_source = "None"
|
||||
desired_target = 0
|
||||
|
||||
else:
|
||||
desired_source = "None"
|
||||
desired_target = 0
|
||||
|
||||
if desired_target == 0:
|
||||
if self.mapbox_requests["total_requests"] < self.mapbox_requests["max_requests"] and self.starpilot_toggles.slc_mapbox_filler:
|
||||
self.get_mapbox_speed_limit(now, time_validated, v_ego, sm)
|
||||
|
||||
if self.mapbox_limit >= 1:
|
||||
desired_source = "Mapbox"
|
||||
desired_target = self.mapbox_limit
|
||||
|
||||
if not display_only and desired_target == 0:
|
||||
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
|
||||
|
||||
self.target = desired_target
|
||||
|
||||
elif sm["selfdriveState"].enabled and self.starpilot_toggles.slc_fallback_set_speed:
|
||||
desired_source = "None"
|
||||
desired_target = v_cruise
|
||||
else:
|
||||
self.mapbox_limit = 0
|
||||
self.segment_distance = 0
|
||||
|
||||
if display_only:
|
||||
self.speed_limit_changed_timer = 0
|
||||
self.unconfirmed_speed_limit = 0
|
||||
self.clear_override()
|
||||
|
||||
if desired_target >= 1:
|
||||
self.source = desired_source
|
||||
self.target = desired_target
|
||||
else:
|
||||
self.source = "None"
|
||||
self.target = 0
|
||||
|
||||
return
|
||||
|
||||
current_road_name = sm["mapdOut"].roadName if desired_source == "Map Data" else ""
|
||||
current_speed = self.target if (self.source != "None" and self.target > 0) else self.last_valid_limit
|
||||
|
||||
# Do not trigger alerts when shifting to fallback or when re-obtaining the same speed limit
|
||||
is_fallback = desired_source == "None" or desired_target == 0
|
||||
same_speed = desired_target > 0 and current_speed > 0 and abs(desired_target - current_speed) < 1
|
||||
confirmation_required = self._confirmation_required(desired_source, desired_target)
|
||||
denied_same_limit = (
|
||||
confirmation_required and self.denied_target > 0 and
|
||||
abs(desired_target - self.denied_target) < 1
|
||||
)
|
||||
|
||||
if not denied_same_limit:
|
||||
self.denied_target = 0
|
||||
|
||||
if denied_same_limit:
|
||||
self.speed_limit_changed_timer = 0
|
||||
self.unconfirmed_speed_limit = 0
|
||||
elif not is_fallback and not same_speed and (abs(desired_target - self.previous_target) >= 1 or current_speed == 0):
|
||||
self.handle_limit_change(desired_source, desired_target, current_road_name, v_ego, sm)
|
||||
else:
|
||||
self.speed_limit_changed_timer = 0
|
||||
self.unconfirmed_speed_limit = 0
|
||||
if desired_source != self.source or desired_target != self.target:
|
||||
if not is_fallback:
|
||||
self.clear_persistent_override_for_limit_change(current_speed, desired_target)
|
||||
self.source = desired_source
|
||||
self.target = desired_target
|
||||
if desired_source != "None" and desired_target > 0:
|
||||
self.previous_source = desired_source
|
||||
self.previous_target = desired_target
|
||||
if current_road_name != self.previous_road_name and current_road_name != "":
|
||||
self.previous_road_name = current_road_name
|
||||
self.denied_target = 0
|
||||
|
||||
if self.source != "None" and self.target > 0:
|
||||
self.last_valid_limit = self.target
|
||||
|
||||
self._slc_adopt_counter += 1
|
||||
if self._slc_adopt_counter % 4 == 0 and self.starpilot_planner.params_memory.get_bool("SLCAdoptSpeedLimit"):
|
||||
self.starpilot_planner.params_memory.remove("SLCAdoptSpeedLimit")
|
||||
if desired_target > 0:
|
||||
self.clear_override()
|
||||
self.denied_target = 0
|
||||
self.source = desired_source
|
||||
self.target = desired_target
|
||||
self.previous_source = desired_source
|
||||
self.previous_target = desired_target
|
||||
self.speed_limit_changed_timer = 0
|
||||
self.unconfirmed_speed_limit = 0
|
||||
self.starpilot_planner.params.put_nonblocking("PreviousSpeedLimit", float(self.target))
|
||||
self.starpilot_planner.params_memory.put_float("SLCForceCruiseSpeed", self.target + self.offset)
|
||||
|
||||
def update_map_speed_limit(self, v_ego, sm):
|
||||
next_speed_limit_distance = sm["mapdOut"].nextSpeedLimitDistance
|
||||
|
||||
way_sel = sm["mapdOut"].waySelectionType
|
||||
if way_sel in (custom.WaySelectionType.current,
|
||||
custom.WaySelectionType.extended):
|
||||
self.map_speed_limit = sm["mapdOut"].speedLimit
|
||||
self.next_speed_limit = sm["mapdOut"].nextSpeedLimit
|
||||
elif way_sel in (custom.WaySelectionType.predicted,
|
||||
custom.WaySelectionType.possible):
|
||||
speed = sm["mapdOut"].speedLimit
|
||||
def _update_map_speed_limit(self, v_ego, sm):
|
||||
map_data = sm["mapdOut"]
|
||||
way_sel = map_data.waySelectionType
|
||||
if way_sel in (custom.WaySelectionType.current, custom.WaySelectionType.extended):
|
||||
self.map_speed_limit = map_data.speedLimit
|
||||
self.next_speed_limit = map_data.nextSpeedLimit
|
||||
elif way_sel in (custom.WaySelectionType.predicted, custom.WaySelectionType.possible):
|
||||
speed = map_data.speedLimit
|
||||
if speed > 0 and (self.map_speed_limit == 0 or speed < self.map_speed_limit):
|
||||
self.map_speed_limit = speed
|
||||
self.next_speed_limit = 0
|
||||
self.next_speed_limit = 0.0
|
||||
else:
|
||||
self.next_speed_limit = 0
|
||||
# Explicit selection failure means the old current limit is no longer live.
|
||||
self.map_speed_limit = 0.0
|
||||
self.next_speed_limit = 0.0
|
||||
|
||||
if self.next_speed_limit > 0:
|
||||
if self.map_speed_limit < self.next_speed_limit:
|
||||
max_lookahead = self.starpilot_toggles.map_speed_lookahead_higher * v_ego
|
||||
lookahead = self.starpilot_toggles.map_speed_lookahead_higher * v_ego
|
||||
elif self.map_speed_limit > self.next_speed_limit:
|
||||
max_lookahead = self.starpilot_toggles.map_speed_lookahead_lower * v_ego
|
||||
lookahead = self.starpilot_toggles.map_speed_lookahead_lower * v_ego
|
||||
else:
|
||||
max_lookahead = 0
|
||||
|
||||
if next_speed_limit_distance < max_lookahead:
|
||||
lookahead = 0.0
|
||||
if map_data.nextSpeedLimitDistance < lookahead:
|
||||
self.map_speed_limit = self.next_speed_limit
|
||||
|
||||
def update_override(self, v_cruise, v_cruise_diff, v_ego, v_ego_diff, sm):
|
||||
# Detect +/- changes on the raw set speed (button-driven, no cluster jitter). A fresh edge
|
||||
# keeps a cleared override from re-arming while the selected speed stays high.
|
||||
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
|
||||
set_speed_input_consumed = self._set_speed_override_input_consumed
|
||||
# The button and its vCruise update can arrive in adjacent frames. Clear a consumed
|
||||
# confirmation only after this frame has seen the speed change or button release.
|
||||
if set_speed_input_consumed and (set_speed_changed or not sm["starpilotCarState"].accelPressed):
|
||||
self._set_speed_override_input_consumed = False
|
||||
def _select_limit(self, dashboard, map_limit, vision_limit):
|
||||
priorities = (self.starpilot_toggles.speed_limit_priority1, self.starpilot_toggles.speed_limit_priority2)
|
||||
limits = {SOURCE_DASHBOARD: dashboard, SOURCE_MAP: map_limit}
|
||||
if SOURCE_VISION in priorities:
|
||||
limits[SOURCE_VISION] = vision_limit
|
||||
valid = {source: limit for source, limit in limits.items() if limit >= 1}
|
||||
if not valid:
|
||||
return SOURCE_NONE, 0.0
|
||||
if self.starpilot_toggles.speed_limit_priority_highest:
|
||||
source = max(valid, key=valid.get)
|
||||
elif self.starpilot_toggles.speed_limit_priority_lowest:
|
||||
source = min(valid, key=valid.get)
|
||||
else:
|
||||
source = next((name for name in priorities if name in valid), SOURCE_NONE)
|
||||
return source, valid.get(source, 0.0)
|
||||
|
||||
if not sm["selfdriveState"].enabled:
|
||||
self.override_disable_timer += DT_MDL
|
||||
if self.override_disable_timer >= SLC_OVERRIDE_DISABLE_CLEAR_TIME:
|
||||
self.clear_override()
|
||||
def _apply_mapbox_filler(self, source, limit, now, time_validated, v_ego, sm):
|
||||
if source != SOURCE_NONE or not self.starpilot_toggles.slc_mapbox_filler:
|
||||
self.mapbox.reset()
|
||||
return source, limit
|
||||
self.mapbox.update(
|
||||
now, time_validated, v_ego, self.starpilot_planner.gps_valid, self.starpilot_planner.gps_position,
|
||||
sm["carState"].steeringAngleDeg, sm["liveParameters"].angleOffsetDeg,
|
||||
)
|
||||
if self.mapbox.limit >= 1:
|
||||
return SOURCE_MAPBOX, self.mapbox.limit
|
||||
return source, limit
|
||||
|
||||
def _apply_fallback(self, v_cruise, enabled):
|
||||
self._using_experimental_fallback = False
|
||||
previous_vision_filtered = self.last_valid_source == SOURCE_VISION and self.low_vision_limit_filtered(self.last_valid_limit)
|
||||
if self.starpilot_toggles.slc_fallback_previous_speed_limit and self.last_valid_limit > 0 and not previous_vision_filtered:
|
||||
self.source = self.last_valid_source
|
||||
self.target = self.last_valid_limit
|
||||
elif enabled and self.starpilot_toggles.slc_fallback_set_speed:
|
||||
self.source = SOURCE_NONE
|
||||
self.target = v_cruise
|
||||
else:
|
||||
self.source = SOURCE_NONE
|
||||
self.target = 0.0
|
||||
self._using_experimental_fallback = bool(self.starpilot_toggles.slc_fallback_experimental_mode)
|
||||
|
||||
def _confirmation_required(self, limit):
|
||||
current = self.last_valid_limit
|
||||
return ((limit < current and self.starpilot_toggles.speed_limit_confirmation_lower) or
|
||||
(limit > current and self.starpilot_toggles.speed_limit_confirmation_higher))
|
||||
|
||||
def _clear_pending(self):
|
||||
self.pending_limit = 0.0
|
||||
self.pending_source = SOURCE_NONE
|
||||
self.confirmation_time = 0.0
|
||||
|
||||
def _reconcile_set_speed_override(self, old_limit, new_limit):
|
||||
if self.set_speed_override <= 0 or old_limit <= 0 or new_limit <= 0 or abs(new_limit - old_limit) < 0.1:
|
||||
return
|
||||
if (new_limit < old_limit or
|
||||
self.set_speed_override <= new_limit + self.get_offset(new_limit) + SET_SPEED_CHANGE_TOLERANCE_METERS_PER_SECOND):
|
||||
self.set_speed_override = 0.0
|
||||
self.overridden_speed = self.pedal_override
|
||||
|
||||
def _accept_limit(self, source, limit, *, persist=True):
|
||||
assert source in REAL_SOURCES and limit >= 1
|
||||
old_limit = self.last_valid_limit
|
||||
self._reconcile_set_speed_override(old_limit, limit)
|
||||
self.source = source
|
||||
self.target = limit
|
||||
self.last_valid_limit = limit
|
||||
self.last_valid_source = source
|
||||
self.denied_limit = 0.0
|
||||
self._clear_pending()
|
||||
if persist and abs(limit - old_limit) >= 0.1:
|
||||
self.starpilot_planner.params.put_nonblocking("PreviousSpeedLimit", float(limit))
|
||||
|
||||
def _reject_limit(self, limit):
|
||||
self.denied_limit = limit
|
||||
self.source = SOURCE_NONE
|
||||
self._clear_pending()
|
||||
self.starpilot_planner.params_memory.remove("SpeedLimitAccepted")
|
||||
|
||||
def _update_limit(self, source, limit, sm):
|
||||
road_name = sm["mapdOut"].roadName if source == SOURCE_MAP else ""
|
||||
if road_name and road_name != self.previous_road_name:
|
||||
self.denied_limit = 0.0
|
||||
self.previous_road_name = road_name
|
||||
|
||||
if source == SOURCE_NONE:
|
||||
self._clear_pending()
|
||||
self.starpilot_planner.params_memory.remove("SpeedLimitAccepted")
|
||||
return
|
||||
|
||||
self.override_disable_timer = 0.0
|
||||
current = self.last_valid_limit
|
||||
if current > 0 and abs(limit - current) < SAME_LIMIT_TOLERANCE:
|
||||
self._accept_limit(source, limit, persist=False)
|
||||
self.starpilot_planner.params_memory.remove("SpeedLimitAccepted")
|
||||
return
|
||||
|
||||
target_to_use = self.target_to_use
|
||||
target_with_offset = target_to_use + self.get_offset(target_to_use)
|
||||
confirmation_required = self._confirmation_required(limit)
|
||||
if self.denied_limit > 0 and abs(limit - self.denied_limit) < SAME_LIMIT_TOLERANCE:
|
||||
if confirmation_required:
|
||||
self._clear_pending()
|
||||
self.starpilot_planner.params_memory.remove("SpeedLimitAccepted")
|
||||
self.source = SOURCE_NONE
|
||||
return
|
||||
# Turning confirmation off applies the already-announced candidate.
|
||||
self._accept_limit(source, limit)
|
||||
self.starpilot_planner.params_memory.remove("SpeedLimitAccepted")
|
||||
return
|
||||
self.denied_limit = 0.0
|
||||
|
||||
set_speed = v_cruise + v_cruise_diff
|
||||
bidirectional_set_speed = getattr(self.starpilot_toggles, "redneck_cruise", False)
|
||||
|
||||
if self._persistent_override_speed > 0:
|
||||
if bidirectional_set_speed:
|
||||
if set_speed <= 0:
|
||||
self.clear_persistent_override()
|
||||
else:
|
||||
self._persistent_override_speed = set_speed
|
||||
elif set_speed <= 0 or (target_with_offset > 0 and set_speed <= target_with_offset and (self.source != "None" or set_speed_changed)):
|
||||
self.clear_persistent_override()
|
||||
else:
|
||||
self._persistent_override_speed = set_speed
|
||||
elif (
|
||||
target_with_offset > 0
|
||||
and set_speed > 0
|
||||
and not set_speed_input_consumed
|
||||
and ((bidirectional_set_speed and set_speed_changed) or (not bidirectional_set_speed and set_speed_raised and set_speed > target_with_offset))
|
||||
):
|
||||
self._persistent_override_speed = set_speed
|
||||
|
||||
if sm["carState"].gasPressed and v_ego > target_with_offset > 0:
|
||||
self.override_slc = True
|
||||
self.overridden_speed = v_ego + v_ego_diff
|
||||
elif self._persistent_override_speed > 0:
|
||||
self.override_slc = True
|
||||
self.overridden_speed = self._persistent_override_speed
|
||||
if self.pending_limit == 0 or abs(limit - self.pending_limit) >= SAME_LIMIT_TOLERANCE:
|
||||
self.pending_limit = limit
|
||||
self.pending_source = source
|
||||
self.confirmation_time = 0.0
|
||||
self.limit_change_started = True
|
||||
new_pending = True
|
||||
else:
|
||||
self.clear_override()
|
||||
self.pending_source = source
|
||||
new_pending = False
|
||||
|
||||
if not confirmation_required:
|
||||
self._accept_limit(source, limit)
|
||||
self.starpilot_planner.params_memory.remove("SpeedLimitAccepted")
|
||||
return
|
||||
|
||||
self.source = SOURCE_NONE
|
||||
self.confirmation_time += DT_MDL
|
||||
long_active = sm["carControl"].longActive
|
||||
accel_accept = bool(sm["starpilotCarState"].accelPressed and long_active)
|
||||
if new_pending and accel_accept:
|
||||
# This press cannot confirm a candidate that was replaced on this frame.
|
||||
self.confirmation_button_consumed = True
|
||||
self.consume_set_speed_change = True
|
||||
memory = self.starpilot_planner.params_memory
|
||||
ui_accept = memory.get_bool("SpeedLimitAccepted")
|
||||
if ui_accept:
|
||||
memory.remove("SpeedLimitAccepted")
|
||||
|
||||
fully_disengaged = not long_active and not sm["selfdriveState"].enabled
|
||||
if ((accel_accept or ui_accept) and not new_pending) or fully_disengaged:
|
||||
pending_limit, pending_source = self.pending_limit, self.pending_source
|
||||
higher = pending_limit > current
|
||||
self._accept_limit(pending_source, pending_limit)
|
||||
if accel_accept:
|
||||
self.consume_set_speed_change = True
|
||||
self.confirmation_button_consumed = True
|
||||
set_speed_kph = float(sm["carState"].vCruise)
|
||||
target_with_offset = self.target + self.offset
|
||||
if (higher and long_active and 0 < set_speed_kph < V_CRUISE_UNSET and
|
||||
set_speed_kph * CV.KPH_TO_MS < target_with_offset):
|
||||
memory.put_float("SLCForceCruiseSpeed", target_with_offset)
|
||||
elif sm["starpilotCarState"].decelPressed or (self.confirmation_time >= 30 and long_active):
|
||||
self._reject_limit(self.pending_limit)
|
||||
|
||||
def _process_adopt_request(self, source, limit):
|
||||
memory = self.starpilot_planner.params_memory
|
||||
if not memory.get_bool("SLCAdoptSpeedLimit"):
|
||||
return
|
||||
memory.remove("SLCAdoptSpeedLimit")
|
||||
if source not in REAL_SOURCES or limit < 1:
|
||||
return
|
||||
self.clear_override()
|
||||
self.consume_set_speed_change = True
|
||||
self._accept_limit(source, limit)
|
||||
memory.put_float("SLCForceCruiseSpeed", self.target + self.offset)
|
||||
|
||||
def clear_override(self):
|
||||
self.set_speed_override = 0.0
|
||||
self.pedal_override = 0.0
|
||||
self.overridden_speed = 0.0
|
||||
|
||||
def _update_override(self, v_cruise, v_cruise_diff, v_ego, v_ego_diff, sm):
|
||||
previous = self.previous_set_speed
|
||||
self.previous_set_speed = v_cruise
|
||||
changed = previous is not None and abs(v_cruise - previous) > SET_SPEED_CHANGE_TOLERANCE_METERS_PER_SECOND
|
||||
raised = previous is not None and v_cruise > previous + SET_SPEED_CHANGE_TOLERANCE_METERS_PER_SECOND
|
||||
consumed = self.consume_set_speed_change
|
||||
if consumed and (changed or not sm["starpilotCarState"].accelPressed):
|
||||
self.consume_set_speed_change = False
|
||||
|
||||
if not sm["selfdriveState"].enabled:
|
||||
self.override_disable_time += DT_MDL
|
||||
if self.override_disable_time >= SLC_OVERRIDE_DISABLE_CLEAR_TIME:
|
||||
self.clear_override()
|
||||
return
|
||||
self.override_disable_time = 0.0
|
||||
|
||||
target = self.target_to_use
|
||||
target_with_offset = target + self.get_offset(target)
|
||||
set_speed = v_cruise + v_cruise_diff
|
||||
bidirectional = getattr(self.starpilot_toggles, "redneck_cruise", False)
|
||||
if self.set_speed_override > 0:
|
||||
if bidirectional:
|
||||
self.set_speed_override = max(set_speed, 0.0)
|
||||
elif set_speed <= 0 or (target_with_offset > 0 and set_speed <= target_with_offset and
|
||||
(self.source != SOURCE_NONE or changed)):
|
||||
self.set_speed_override = 0.0
|
||||
else:
|
||||
self.set_speed_override = set_speed
|
||||
elif (target_with_offset > 0 and set_speed > 0 and not consumed and
|
||||
((bidirectional and changed) or (not bidirectional and raised and set_speed > target_with_offset))):
|
||||
self.set_speed_override = set_speed
|
||||
|
||||
self.pedal_override = v_ego + v_ego_diff if sm["carState"].gasPressed and v_ego > target_with_offset > 0 else 0.0
|
||||
self.overridden_speed = self.pedal_override or self.set_speed_override
|
||||
|
||||
def update(self, dashboard_speed_limit, now, time_validated, v_cruise, v_cruise_diff, v_ego, v_ego_diff, sm,
|
||||
*, active=True, display_only=False):
|
||||
self.limit_change_started = False
|
||||
self.confirmation_button_consumed = False
|
||||
self._using_experimental_fallback = False
|
||||
mode = "display" if display_only else "active" if active else "off"
|
||||
if mode != self._mode:
|
||||
self.mapbox.reset()
|
||||
self._mode = mode
|
||||
if not active and not display_only:
|
||||
self.reset_control_state()
|
||||
self.mapbox.reset()
|
||||
self.source, self.target = SOURCE_NONE, 0.0
|
||||
self.map_speed_limit = self.next_speed_limit = self.vision_limit = 0.0
|
||||
return
|
||||
|
||||
self._update_map_speed_limit(v_ego, sm)
|
||||
vision_limit = self._get_vision_limit(v_ego, sm, display_only)
|
||||
source, limit = self._select_limit(dashboard_speed_limit, self.map_speed_limit, vision_limit)
|
||||
source, limit = self._apply_mapbox_filler(source, limit, now, time_validated, v_ego, sm)
|
||||
|
||||
if display_only:
|
||||
self.reset_control_state()
|
||||
self.source, self.target = (source, limit) if limit >= 1 else (SOURCE_NONE, 0.0)
|
||||
return
|
||||
|
||||
self._active_control = True
|
||||
if source == SOURCE_NONE:
|
||||
self._update_limit(source, limit, sm)
|
||||
self._apply_fallback(v_cruise, sm["selfdriveState"].enabled)
|
||||
else:
|
||||
self._update_limit(source, limit, sm)
|
||||
self._process_adopt_request(source, limit)
|
||||
self._update_override(v_cruise, v_cruise_diff, v_ego, v_ego_diff, sm)
|
||||
|
||||
@@ -216,7 +216,6 @@ class StarPilotAcceleration:
|
||||
|
||||
effective_slc_target = get_active_slc_control_target(
|
||||
getattr(starpilot_toggles, "speed_limit_controller", False),
|
||||
getattr(starpilot_toggles, "set_speed_limit", False),
|
||||
getattr(self.starpilot_planner.starpilot_vcruise, "slc_target", 0.0),
|
||||
getattr(self.starpilot_planner.starpilot_vcruise, "slc_offset", 0.0),
|
||||
getattr(getattr(self.starpilot_planner.starpilot_vcruise, "slc", None), "overridden_speed", 0.0),
|
||||
@@ -271,7 +270,6 @@ class StarPilotAcceleration:
|
||||
v_ego_diff = v_ego_cluster - v_ego
|
||||
effective_slc_target = get_active_slc_control_target(
|
||||
getattr(starpilot_toggles, "speed_limit_controller", False),
|
||||
getattr(starpilot_toggles, "set_speed_limit", False),
|
||||
getattr(self.starpilot_planner.starpilot_vcruise, "slc_target", 0.0),
|
||||
getattr(self.starpilot_planner.starpilot_vcruise, "slc_offset", 0.0),
|
||||
getattr(getattr(self.starpilot_planner.starpilot_vcruise, "slc", None), "overridden_speed", 0.0),
|
||||
@@ -312,7 +310,6 @@ class StarPilotAcceleration:
|
||||
v_ego_cluster = v_ego
|
||||
effective_slc_target = get_active_slc_control_target(
|
||||
getattr(starpilot_toggles, "speed_limit_controller", False),
|
||||
getattr(starpilot_toggles, "set_speed_limit", False),
|
||||
getattr(self.starpilot_planner.starpilot_vcruise, "slc_target", 0.0),
|
||||
getattr(self.starpilot_planner.starpilot_vcruise, "slc_offset", 0.0),
|
||||
getattr(getattr(self.starpilot_planner.starpilot_vcruise, "slc", None), "overridden_speed", 0.0),
|
||||
|
||||
@@ -189,7 +189,7 @@ class StarPilotEvents:
|
||||
else:
|
||||
self.events.add(StarPilotEventName.openpilotCrashed)
|
||||
|
||||
if self.starpilot_planner.starpilot_vcruise.slc.speed_limit_changed_timer == DT_MDL and starpilot_toggles.speed_limit_changed_alert:
|
||||
if self.starpilot_planner.starpilot_vcruise.slc.limit_change_started and starpilot_toggles.speed_limit_changed_alert:
|
||||
self.events.add(StarPilotEventName.speedLimitChanged)
|
||||
|
||||
self.startup_seen |= sm["starpilotSelfdriveState"].alertText1 == starpilot_toggles.startup_alert_top and sm["starpilotSelfdriveState"].alertText2 == starpilot_toggles.startup_alert_bottom
|
||||
|
||||
@@ -105,10 +105,9 @@ def get_lead_veto_distance(car_params):
|
||||
return LEAD_VETO_M_OVERRIDES.get(fingerprint, LEAD_VETO_M)
|
||||
|
||||
|
||||
def get_active_slc_control_target(speed_limit_controller, set_speed_limit, slc_target, slc_offset, overridden_speed,
|
||||
def get_active_slc_control_target(speed_limit_controller, 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.
|
||||
# SetSpeedLimit controls engage-time initialization; SLC limits ongoing cruise.
|
||||
if not speed_limit_controller:
|
||||
return 0.0
|
||||
|
||||
@@ -568,6 +567,18 @@ class StarPilotVCruise:
|
||||
v_ego_cluster = max(sm["carState"].vEgoCluster, v_ego)
|
||||
v_ego_diff = v_ego_cluster - v_ego
|
||||
|
||||
# Resolve this frame's SLC confirmation before CSC can consume accel/+.
|
||||
self.slc.starpilot_toggles = starpilot_toggles
|
||||
slc_active = starpilot_toggles.speed_limit_controller
|
||||
slc_display_only = not slc_active and starpilot_toggles.show_speed_limits
|
||||
self.slc.update(
|
||||
sm["starpilotCarState"].dashboardSpeedLimit, now, time_validated,
|
||||
v_cruise, v_cruise_diff, v_ego, v_ego_diff, sm,
|
||||
active=slc_active, display_only=slc_display_only,
|
||||
)
|
||||
self.slc_offset = self.slc.offset if slc_active else 0
|
||||
self.slc_target = self.slc.target if (slc_active or slc_display_only) else 0
|
||||
|
||||
# Curve Speed Controller
|
||||
following_lead = bool(getattr(self.starpilot_planner.starpilot_following, "following_lead", False))
|
||||
manual_speed_control = is_manual_speed_control(sm)
|
||||
@@ -586,8 +597,9 @@ class StarPilotVCruise:
|
||||
not self.starpilot_planner.driving_in_curve)
|
||||
csc_was_controlling = self.csc_controlling_speed
|
||||
|
||||
slc_confirmation_pending = self.slc.speed_limit_changed_timer > DT_MDL and self.slc.unconfirmed_speed_limit >= 1
|
||||
csc_accel_button = bool(sm["starpilotCarState"].accelPressed) and not slc_confirmation_pending
|
||||
csc_accel_button = (bool(sm["starpilotCarState"].accelPressed) and
|
||||
not self.slc.confirmation_pending and
|
||||
not self.slc.confirmation_button_consumed)
|
||||
|
||||
|
||||
|
||||
@@ -637,24 +649,6 @@ class StarPilotVCruise:
|
||||
self.csc.handle_override(v_ego, csc_was_controlling, sm, accel_button=csc_accel_button)
|
||||
self.csc.log_data(v_ego, sm)
|
||||
|
||||
# Pfeiferj's Speed Limit Controller
|
||||
self.slc.starpilot_toggles = starpilot_toggles
|
||||
|
||||
if starpilot_toggles.speed_limit_controller:
|
||||
self.slc.update_limits(sm["starpilotCarState"].dashboardSpeedLimit, now, time_validated, v_cruise, v_ego, sm)
|
||||
self.slc.update_override(v_cruise, v_cruise_diff, v_ego, v_ego_diff, sm)
|
||||
|
||||
self.slc_offset = self.slc.offset
|
||||
self.slc_target = self.slc.target
|
||||
elif starpilot_toggles.show_speed_limits:
|
||||
self.slc.update_limits(sm["starpilotCarState"].dashboardSpeedLimit, now, time_validated, v_cruise, v_ego, sm, display_only=True)
|
||||
|
||||
self.slc_offset = 0
|
||||
self.slc_target = self.slc.target
|
||||
else:
|
||||
self.slc_offset = 0
|
||||
self.slc_target = 0
|
||||
|
||||
self.nav_turn_target = self._get_nav_turn_control_target(v_cruise, sm, starpilot_toggles)
|
||||
|
||||
# Single tuning knob (signed feet -> meters). Defense clamp on top of UI bounds.
|
||||
@@ -750,7 +744,6 @@ class StarPilotVCruise:
|
||||
targets.append(self.csc_target)
|
||||
slc_control_target = get_active_slc_control_target(
|
||||
starpilot_toggles.speed_limit_controller,
|
||||
getattr(starpilot_toggles, "set_speed_limit", False),
|
||||
self.slc_target,
|
||||
self.slc_offset,
|
||||
self.slc.overridden_speed,
|
||||
|
||||
@@ -4,7 +4,7 @@ from opendbc.car.chrysler.values import pacifica_hybrid_aol_requires_set_press
|
||||
from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR, HyundaiFlags
|
||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.car.cruise import CRUISE_LONG_PRESS, ButtonType
|
||||
from openpilot.selfdrive.car.cruise import CRUISE_LONG_PRESS, ButtonType, is_speed_limit_confirmation_pending
|
||||
from openpilot.selfdrive.selfdrived.events import ET
|
||||
|
||||
from openpilot.starpilot.common.experimental_state import (
|
||||
@@ -57,6 +57,8 @@ class StarPilotCard:
|
||||
self.params_memory = Params(memory=True)
|
||||
|
||||
self.accel_pressed = False
|
||||
self.confirmation_button_suppressed = set()
|
||||
self.pressed_accel_buttons = set()
|
||||
self.always_on_lateral_allowed = False
|
||||
self.controller_aol_override = None
|
||||
self.pacifica_aol_set_seen = False
|
||||
@@ -271,6 +273,23 @@ class StarPilotCard:
|
||||
]
|
||||
|
||||
button_event_types = [self._button_type_raw(be) for be in carState.buttonEvents]
|
||||
accel_button_types = (int(ButtonType.accelCruise), int(ButtonType.resumeCruise))
|
||||
confirmation_pending = is_speed_limit_confirmation_pending(sm["starpilotPlan"])
|
||||
if confirmation_pending:
|
||||
self.confirmation_button_suppressed.update(self.pressed_accel_buttons)
|
||||
suppressed_releases = set()
|
||||
for be, be_type in zip(carState.buttonEvents, button_event_types, strict=False):
|
||||
if be_type not in accel_button_types:
|
||||
continue
|
||||
if be.pressed:
|
||||
self.pressed_accel_buttons.add(be_type)
|
||||
if confirmation_pending:
|
||||
self.confirmation_button_suppressed.add(be_type)
|
||||
else:
|
||||
self.pressed_accel_buttons.discard(be_type)
|
||||
if be_type in self.confirmation_button_suppressed:
|
||||
self.confirmation_button_suppressed.remove(be_type)
|
||||
suppressed_releases.add(be_type)
|
||||
button_aol_supported = self.always_on_lateral_supported and (
|
||||
self.CP.brand == "hyundai" or starpilot_toggles.lkas_allowed_for_aol
|
||||
)
|
||||
@@ -392,8 +411,11 @@ class StarPilotCard:
|
||||
if not self.always_on_lateral_supported:
|
||||
self.always_on_lateral_allowed = False
|
||||
|
||||
if sm.updated["starpilotPlan"] or any(be_type in (ButtonType.accelCruise, ButtonType.resumeCruise) for be_type in button_event_types):
|
||||
self.accel_pressed = any(be_type in (ButtonType.accelCruise, ButtonType.resumeCruise) for be_type in button_event_types)
|
||||
if sm.updated["starpilotPlan"] or any(be_type in accel_button_types for be_type in button_event_types):
|
||||
self.accel_pressed = any(
|
||||
be_type in accel_button_types and (be.pressed or be_type not in suppressed_releases)
|
||||
for be, be_type in zip(carState.buttonEvents, button_event_types, strict=False)
|
||||
)
|
||||
|
||||
if sm.updated["starpilotPlan"] or any(be_type == ButtonType.decelCruise for be_type in button_event_types):
|
||||
self.decel_pressed = any(be_type == ButtonType.decelCruise for be_type in button_event_types)
|
||||
|
||||
@@ -382,7 +382,7 @@ class StarPilotPlanner:
|
||||
starpilotPlan.slcSpeedLimit = self.starpilot_vcruise.slc_target
|
||||
starpilotPlan.slcSpeedLimitOffset = self.starpilot_vcruise.slc_offset
|
||||
starpilotPlan.slcSpeedLimitSource = self.starpilot_vcruise.slc.source
|
||||
starpilotPlan.speedLimitChanged = self.starpilot_vcruise.slc.speed_limit_changed_timer > DT_MDL
|
||||
starpilotPlan.speedLimitChanged = self.starpilot_vcruise.slc.confirmation_pending
|
||||
starpilotPlan.unconfirmedSlcSpeedLimit = self.starpilot_vcruise.slc.unconfirmed_speed_limit
|
||||
|
||||
starpilotPlan.themeUpdated = theme_updated
|
||||
|
||||
@@ -39,8 +39,8 @@ sys.modules["openpilot.selfdrive.controls.lib.longitudinal_planner"] = _module(
|
||||
)
|
||||
sys.modules["openpilot.starpilot.controls.lib.starpilot_vcruise"] = _module(
|
||||
"openpilot.starpilot.controls.lib.starpilot_vcruise",
|
||||
get_active_slc_control_target=lambda enabled, set_speed_limit, target, offset, overridden_speed, *_args, **_kwargs: (
|
||||
float(overridden_speed or target) + float(offset) if enabled and set_speed_limit else 0.0
|
||||
get_active_slc_control_target=lambda enabled, target, offset, overridden_speed, *_args, **_kwargs: (
|
||||
float(overridden_speed or target) + float(offset) if enabled else 0.0
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ def make_sm():
|
||||
"carControl": SimpleNamespace(longActive=False),
|
||||
"selfdriveState": SimpleNamespace(active=False, alertType=[], experimentalMode=False),
|
||||
"starpilotSelfdriveState": SimpleNamespace(alertType=[]),
|
||||
"starpilotPlan": SimpleNamespace(lateralCheck=True),
|
||||
"starpilotPlan": SimpleNamespace(lateralCheck=True, speedLimitChanged=False, unconfirmedSlcSpeedLimit=0.0),
|
||||
"liveCalibration": SimpleNamespace(calPerc=100),
|
||||
}, updated={"starpilotPlan": False})
|
||||
|
||||
@@ -295,6 +295,37 @@ def make_wrapped_button_event(button_type, pressed):
|
||||
return SimpleNamespace(type=SimpleNamespace(raw=int(button_type)), pressed=pressed)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("pending_before_press", [False, True])
|
||||
def test_slc_confirmation_release_does_not_republish_accel(monkeypatch, tmp_path, pending_before_press):
|
||||
monkeypatch.setattr(spc, "Params", FakeParams)
|
||||
monkeypatch.setattr(spc, "ERROR_LOGS_PATH", tmp_path)
|
||||
card = spc.StarPilotCard(SimpleNamespace(brand="gm"), SimpleNamespace(alternativeExperience=0))
|
||||
sm = make_sm()
|
||||
toggles = make_toggles(speed_limit_controller=True)
|
||||
starpilot_car_state = SimpleNamespace(distancePressed=False)
|
||||
button_type = spc.ButtonType.accelCruise
|
||||
|
||||
if pending_before_press:
|
||||
sm["starpilotPlan"].speedLimitChanged = True
|
||||
sm["starpilotPlan"].unconfirmedSlcSpeedLimit = 20.0
|
||||
pressed = make_car_state(button_events=[make_wrapped_button_event(button_type, True)])
|
||||
assert card.update(pressed, starpilot_car_state, sm, toggles).accelPressed
|
||||
|
||||
if not pending_before_press:
|
||||
sm["starpilotPlan"].speedLimitChanged = True
|
||||
sm["starpilotPlan"].unconfirmedSlcSpeedLimit = 20.0
|
||||
card.update(make_car_state(), starpilot_car_state, sm, toggles)
|
||||
|
||||
sm["starpilotPlan"].speedLimitChanged = False
|
||||
sm["starpilotPlan"].unconfirmedSlcSpeedLimit = 0.0
|
||||
released = make_car_state(button_events=[make_wrapped_button_event(button_type, False)])
|
||||
assert not card.update(released, starpilot_car_state, sm, toggles).accelPressed
|
||||
assert not card.confirmation_button_suppressed
|
||||
|
||||
assert card.update(pressed, starpilot_car_state, sm, toggles).accelPressed
|
||||
assert card.update(released, starpilot_car_state, sm, toggles).accelPressed
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("car_fingerprint", "expect_normalized_release"),
|
||||
(
|
||||
|
||||
Reference in New Issue
Block a user