mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-02 06:03:43 +08:00
test2
This commit is contained in:
@@ -241,6 +241,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"CurvatureData", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}},
|
||||
{"CurveSpeedController", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"CurveSpeedControllerNoLead", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"CurveSpeedApproachDecel", {PERSISTENT, FLOAT, "0.6", "0.6", 2, SETTINGS_SIMPLE}},
|
||||
{"CurveSpeedMargin", {PERSISTENT, INT, "85", "85", 2, SETTINGS_SIMPLE}},
|
||||
{"CustomAlerts", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"CustomAccelProfile", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
|
||||
@@ -85,7 +85,12 @@ def test_straight_road_target_is_cruise_speed():
|
||||
|
||||
|
||||
def test_distant_apex_does_not_constrain_until_braking_is_due():
|
||||
_, controller = make_controller(curve_profile=single_apex_profile(0.02, 400.0))
|
||||
# derived from the shipped decel so retuning it doesn't silently invalidate the case
|
||||
_, probe = make_controller()
|
||||
curve_speed = max(float(np.sqrt(probe.lat_accel_for_curvature(0.02) / 0.02)), CSC_MIN_SPEED)
|
||||
beyond_braking = 1.3 * (30.0**2 - curve_speed**2) / (2 * CSC_APPROACH_DECEL)
|
||||
|
||||
_, controller = make_controller(curve_profile=single_apex_profile(0.02, beyond_braking))
|
||||
|
||||
target = converge(controller, 30.0, 30.0)
|
||||
|
||||
@@ -209,6 +214,25 @@ def test_lower_margin_engages_on_gentler_curves():
|
||||
assert aggressive < relaxed
|
||||
|
||||
|
||||
def test_approach_decel_slider_moves_where_braking_starts():
|
||||
# the same curve must bind further out when the approach is planned gentler
|
||||
def bind_distance(decel):
|
||||
planner, controller = make_controller(curve_profile=single_apex_profile(0.01, 200.0))
|
||||
controller.starpilot_toggles = SimpleNamespace(csc_approach_decel=decel)
|
||||
converge(controller, 30.0, 30.0)
|
||||
return controller.target
|
||||
|
||||
assert bind_distance(0.3) < bind_distance(1.5)
|
||||
|
||||
|
||||
def test_approach_decel_falls_back_to_default():
|
||||
_, controller = make_controller()
|
||||
|
||||
assert controller.approach_decel == pytest.approx(CSC_APPROACH_DECEL)
|
||||
controller.starpilot_toggles = SimpleNamespace(csc_approach_decel=0.0)
|
||||
assert controller.approach_decel == pytest.approx(CSC_APPROACH_DECEL)
|
||||
|
||||
|
||||
def test_binding_distance_reports_the_constraining_point():
|
||||
_, controller = make_controller(curve_profile=single_apex_profile(0.02, 150.0))
|
||||
converge(controller, 30.0, 30.0)
|
||||
|
||||
@@ -674,6 +674,12 @@ class StarPilotLongitudinalLayout(_SettingsPage):
|
||||
|
||||
# ── 4. Adaptive Speed Controls Rows (CES + CSC + CCM) ──
|
||||
self._curve_speed_controller_rows = [
|
||||
SettingRow("CurveSpeedApproachDecel", "value", tr_noop("Curve Speed Approach Decel"),
|
||||
subtitle=tr_noop("How hard the slowdown into a curve is planned. Lower starts it sooner and spreads it over more distance; higher waits longer and slows more firmly."),
|
||||
get_value=lambda: f"{self._params.get_float('CurveSpeedApproachDecel'):.1f} m/s²",
|
||||
on_click=lambda: self._show_slider("CurveSpeedApproachDecel", 0.3, 1.5, step=0.1,
|
||||
unit=" m/s²", value_type="float"),
|
||||
visible=csc_on),
|
||||
SettingRow("CurveSpeedMargin", "value", tr_noop("Curve Speed Margin"),
|
||||
subtitle=tr_noop("How much of your learned cornering comfort to use. Lower slows more for curves; 100% matches how you take them yourself."),
|
||||
get_value=lambda: f"{self._params.get_int('CurveSpeedMargin')}%",
|
||||
|
||||
@@ -52,6 +52,9 @@ CRUISING_SPEED = 5 # Roughly the speed cars go when not t
|
||||
CSC_DEFAULT_MARGIN_PERCENT = 85 # Percent of learned cornering comfort the Curve Speed Controller targets
|
||||
CSC_MIN_MARGIN_PERCENT = 70 # Slows the most; 100 would exactly match the driver's own habit
|
||||
CSC_MAX_MARGIN_PERCENT = 100
|
||||
CSC_DEFAULT_APPROACH_DECEL = 0.6 # m/s^2 the approach is planned at; sets how early the slowdown starts
|
||||
CSC_MIN_APPROACH_DECEL = 0.3 # earlier than this saturates against the model's ~10s horizon
|
||||
CSC_MAX_APPROACH_DECEL = 1.5 # later and firmer; matches the old ramp cap
|
||||
DEFAULT_LATERAL_ACCELERATION = 2.0 # m/s^2, typical lateral acceleration when taking curves
|
||||
DISPLAY_MENU_TIMER = 350 # The length of time the following distance menu appears on some GM vehicles to prevent things getting out of sync
|
||||
EARTH_RADIUS = 6378137 # Radius of the Earth in meters
|
||||
@@ -833,6 +836,11 @@ class StarPilotVariables:
|
||||
toggle.csc_margin = self.get_value("CurveSpeedMargin", cast=float, condition=toggle.curve_speed_controller,
|
||||
default=CSC_DEFAULT_MARGIN_PERCENT, min=CSC_MIN_MARGIN_PERCENT,
|
||||
max=CSC_MAX_MARGIN_PERCENT) / 100.0
|
||||
# lower plans the approach over a longer distance, so the slowdown starts sooner
|
||||
toggle.csc_approach_decel = self.get_value("CurveSpeedApproachDecel", cast=float,
|
||||
condition=toggle.curve_speed_controller,
|
||||
default=CSC_DEFAULT_APPROACH_DECEL, min=CSC_MIN_APPROACH_DECEL,
|
||||
max=CSC_MAX_APPROACH_DECEL)
|
||||
|
||||
toggle.goat_scream_alert = self.get_value("GoatScream")
|
||||
toggle.goat_scream_critical_alerts = self.get_value("GoatScreamCriticalAlerts")
|
||||
|
||||
@@ -8,6 +8,7 @@ from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.starpilot.common.starpilot_variables import (
|
||||
CITY_SPEED_LIMIT,
|
||||
CRUISING_SPEED,
|
||||
CSC_DEFAULT_APPROACH_DECEL,
|
||||
CSC_DEFAULT_MARGIN_PERCENT,
|
||||
DEFAULT_LATERAL_ACCELERATION,
|
||||
PLANNER_TIME,
|
||||
@@ -16,7 +17,9 @@ from openpilot.starpilot.common.starpilot_variables import (
|
||||
CALIBRATION_PROGRESS_THRESHOLD = 10 / DT_MDL
|
||||
CSC_MIN_SPEED = CITY_SPEED_LIMIT * CV.MPH_TO_MS
|
||||
|
||||
CSC_APPROACH_DECEL = 1.0
|
||||
# braking distance is (v^2 - v_curve^2) / (2 * this), so lower starts the slowdown
|
||||
# sooner and spreads it further. User-tunable via CurveSpeedApproachDecel.
|
||||
CSC_APPROACH_DECEL = CSC_DEFAULT_APPROACH_DECEL
|
||||
CSC_TARGET_UP_RATE = 3.0
|
||||
CSC_TARGET_DOWN_RATE = 2.5
|
||||
CSC_TARGET_FILTER_RC = 0.4
|
||||
@@ -312,6 +315,11 @@ class CurveSpeedController:
|
||||
margin = getattr(self.starpilot_toggles, "csc_margin", None)
|
||||
return float(margin) if margin else CSC_COMFORT_MARGIN
|
||||
|
||||
@property
|
||||
def approach_decel(self):
|
||||
decel = getattr(self.starpilot_toggles, "csc_approach_decel", None)
|
||||
return float(decel) if decel else CSC_APPROACH_DECEL
|
||||
|
||||
def lat_accel_for_curvature(self, curvature):
|
||||
lat_accel = np.interp(np.abs(curvature), self._curve_k, self._curve_a) * self.comfort_margin
|
||||
|
||||
@@ -339,7 +347,7 @@ class CurveSpeedController:
|
||||
lat_accel = self.lat_accel_for_curvature(curvatures)
|
||||
point_speeds = np.sqrt(lat_accel / np.maximum(curvatures, 1e-4))
|
||||
point_speeds = np.maximum(point_speeds, CSC_MIN_SPEED)
|
||||
allowed_speeds = np.sqrt(point_speeds**2 + 2.0 * CSC_APPROACH_DECEL * np.maximum(distances, 0.0))
|
||||
allowed_speeds = np.sqrt(point_speeds**2 + 2.0 * self.approach_decel * np.maximum(distances, 0.0))
|
||||
binding_index = int(np.argmin(allowed_speeds))
|
||||
raw_target = min(float(allowed_speeds[binding_index]), float(v_cruise))
|
||||
self.binding_distance = float(distances[binding_index]) if raw_target < v_cruise else 0.0
|
||||
|
||||
@@ -757,6 +757,20 @@
|
||||
"is_parent_toggle": true,
|
||||
"settings_tier": "simple"
|
||||
},
|
||||
{
|
||||
"key": "CurveSpeedApproachDecel",
|
||||
"label": "Curve Speed Approach Decel",
|
||||
"description": "How hard the slowdown into a curve is planned. Lower starts it sooner and spreads it over more distance; higher waits longer and slows more firmly.",
|
||||
"data_type": "float",
|
||||
"ui_type": "numeric",
|
||||
"min": 0.3,
|
||||
"max": 1.5,
|
||||
"step": 0.1,
|
||||
"precision": 1,
|
||||
"unit": " m/s²",
|
||||
"parent_key": "CurveSpeedController",
|
||||
"settings_tier": "simple"
|
||||
},
|
||||
{
|
||||
"key": "CurveSpeedMargin",
|
||||
"label": "Curve Speed Margin",
|
||||
|
||||
@@ -23,6 +23,7 @@ M_TO_MILES = 1.0 / 1609.34
|
||||
HIGHWAY_SPEED = 60.0 / MS_TO_MPH # above this, engagement is the over-slowing regression risk
|
||||
CURVE_LAT_ACCEL = 1.3 # MINIMUM_LATERAL_ACCELERATION
|
||||
EPISODE_GAP_S = 1.0
|
||||
V_CRUISE_UNSET = 255
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -35,11 +36,13 @@ class Frame:
|
||||
brake: bool = False
|
||||
accel_pressed: bool = False
|
||||
long_active: bool = False
|
||||
blinker: bool = False # CSC gating input: a blinker suspends it entirely
|
||||
csc_active: bool = False
|
||||
csc_overridden: bool = False
|
||||
csc_training: bool = False
|
||||
csc_speed: float = 0.0
|
||||
v_cruise: float = 0.0
|
||||
v_cruise: float = 0.0 # applied cruise speed, already reduced by CSC
|
||||
set_speed: float = 0.0 # what the driver dialled in, so cuts are measurable
|
||||
learned_lat_accel: float = 0.0
|
||||
binding_distance: float = 0.0
|
||||
|
||||
@@ -66,17 +69,31 @@ class Episode:
|
||||
return self.end - self.start
|
||||
|
||||
|
||||
def read_events(identifier: str):
|
||||
"""A downloaded rlog reads directly; anything else goes through LogReader."""
|
||||
path = Path(identifier)
|
||||
if path.is_file():
|
||||
from cereal import log as capnp_log
|
||||
|
||||
data = path.read_bytes()
|
||||
if data[:4] == b"\x28\xb5\x2f\xfd":
|
||||
import zstandard
|
||||
data = zstandard.ZstdDecompressor().decompress(data, max_output_size=2 << 30)
|
||||
return capnp_log.Event.read_multiple_bytes(data)
|
||||
|
||||
from openpilot.tools.lib.logreader import LogReader, ReadMode # needs the device stack
|
||||
|
||||
return LogReader(identifier, default_mode=ReadMode.AUTO, sort_by_time=True)
|
||||
|
||||
|
||||
def read_frames(identifier: str) -> list[Frame]:
|
||||
"""Join carState/controlsState/starpilotPlan onto the plan's cadence."""
|
||||
from openpilot.tools.lib.logreader import LogReader, ReadMode # heavy; keeps the metrics importable off-device
|
||||
|
||||
frames: list[Frame] = []
|
||||
latest = Frame()
|
||||
t0 = None
|
||||
have_plan = False
|
||||
|
||||
reader = LogReader(identifier, default_mode=ReadMode.AUTO, sort_by_time=True)
|
||||
for msg in reader:
|
||||
for msg in read_events(identifier):
|
||||
which = msg.which()
|
||||
if which == "carState":
|
||||
cs = msg.carState
|
||||
@@ -84,6 +101,9 @@ def read_frames(identifier: str) -> list[Frame]:
|
||||
latest.a_ego = float(cs.aEgo)
|
||||
latest.gas = bool(cs.gasPressed)
|
||||
latest.brake = bool(cs.brakePressed)
|
||||
latest.blinker = bool(cs.leftBlinker or cs.rightBlinker)
|
||||
set_kph = float(cs.vCruise)
|
||||
latest.set_speed = set_kph / 3.6 if 0 < set_kph < V_CRUISE_UNSET else 0.0
|
||||
elif which == "carControl":
|
||||
latest.long_active = bool(msg.carControl.longActive)
|
||||
elif which == "controlsState":
|
||||
@@ -123,15 +143,19 @@ def build_episodes(frames: list[Frame]) -> list[Episode]:
|
||||
binding_distance=f.binding_distance, min_a_ego=f.a_ego)
|
||||
episodes.append(current)
|
||||
current.end = f.t
|
||||
current.peak_cut = max(current.peak_cut, f.v_cruise - f.csc_speed)
|
||||
if f.set_speed > 0:
|
||||
current.peak_cut = max(current.peak_cut, f.set_speed - f.csc_speed)
|
||||
current.peak_lat_accel = max(current.peak_lat_accel, f.lat_accel)
|
||||
current.min_a_ego = min(current.min_a_ego, f.a_ego)
|
||||
current.gas |= f.gas
|
||||
current.brake |= f.brake
|
||||
last_active_t = f.t
|
||||
elif current is not None and (f.t - last_active_t) <= EPISODE_GAP_S:
|
||||
# the cancel lands on the frame CSC releases, so look just past the end
|
||||
# an override releases CSC on the same frame it registers, so the rejection
|
||||
# always lands just past the end of the episode it rejected
|
||||
current.cancelled |= f.csc_overridden or f.accel_pressed
|
||||
current.gas |= f.gas
|
||||
current.brake |= f.brake
|
||||
|
||||
return episodes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user