The Final Countdown

This commit is contained in:
firestar5683
2026-09-08 18:19:19 -05:00
parent 2504441a4e
commit 0976fd804d
42 changed files with 445 additions and 247 deletions
+2 -1
View File
@@ -317,7 +317,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"DeveloperSidebarMetric7", {PERSISTENT, INT, "7", "0", 3}},
{"DeveloperUI", {PERSISTENT, BOOL, "0", "0", 3}},
{"GalaxyDeveloperMode", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
{"GalaxyMobileDefault", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 0, SETTINGS_ADVANCED}},
{"GalaxyMobileDefault", {PERSISTENT | DONT_LOG, BOOL, "1", "1", 0, SETTINGS_SIMPLE}},
{"DeveloperWidgets", {PERSISTENT, BOOL, "1", "0", 3}},
{"DeviceManagement", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
{"DeviceShutdown", {PERSISTENT, INT, "6", "6", 1, SETTINGS_SIMPLE}},
@@ -609,6 +609,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"RelaxedJerkDeceleration", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"RelaxedJerkSpeed", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"RelaxedJerkSpeedDecrease", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"ReverseCruise", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
{"RivianAngleControl", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"RivianAngleSaturated", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL, "0", "0"}},
{"RivianToiRecoveryFailed", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL, "0", "0"}},
+1 -11
View File
@@ -72,11 +72,7 @@ class VCruiseHelper:
return short_interval, long_interval
def _uses_software_cruise(self) -> bool:
# Some cars, including Toyota TSS2, keep pcmCruise enabled while
# openpilot owns longitudinal control. In that case the software cruise
# target must be used so custom short/hold intervals are honored.
return bool(self.gm_cc_only or self.redneck_non_pcm or not self.CP.pcmCruise or
getattr(self.CP, "openpilotLongitudinalControl", False))
return bool(self.gm_cc_only or self.redneck_non_pcm or not self.CP.pcmCruise)
@property
def v_cruise_initialized(self):
@@ -229,12 +225,6 @@ class VCruiseHelper:
self.v_cruise_kph = float(np.clip(initialized_speed_limit_kph, V_CRUISE_MIN, V_CRUISE_MAX))
elif self.redneck_non_pcm and CS.cruiseState.speedCluster > 0:
self.v_cruise_kph = float(np.clip(CS.cruiseState.speedCluster * CV.MS_TO_KPH, V_CRUISE_MIN, V_CRUISE_MAX))
elif self.CP.pcmCruise and CS.cruiseState.speed > 0:
# Keep PCM/dash set speed as the starting target when software cruise
# takes over, while allowing subsequent button presses to use custom
# intervals. This preserves Toyota's stock engage behavior.
pcm_speed_kph = CS.cruiseState.speed * CV.MS_TO_KPH
self.v_cruise_kph = float(np.clip(pcm_speed_kph, V_CRUISE_MIN, V_CRUISE_MAX))
else:
self.v_cruise_kph = int(round(np.clip(CS.vEgo * CV.MS_TO_KPH, engage_floor_kph, V_CRUISE_MAX)))
+21 -82
View File
@@ -498,95 +498,34 @@ class TestVCruiseHelper:
assert self.v_cruise_helper.v_cruise_kph == pytest.approx(initial_v_cruise_kph + IMPERIAL_INCREMENT)
def test_pcm_cruise_uses_pcm_speed(self):
CP = car.CarParams(pcmCruise=True, openpilotLongitudinalControl=False)
@pytest.mark.parametrize("openpilot_longitudinal", [False, True])
def test_pcm_cruise_always_tracks_pcm_speed(self, openpilot_longitudinal):
CP = car.CarParams(pcmCruise=True, openpilotLongitudinalControl=openpilot_longitudinal)
helper = VCruiseHelper(CP)
toggles = SimpleNamespace(cruise_increase=5, cruise_increase_long=1, set_speed_limit=False)
pcm_speed_kph = 72.0
pcm_cluster_speed_kph = 71.0
helper.initialize_v_cruise(car.CarState(vEgo=40 * CV.KPH_TO_MS), False, False, toggles)
assert not helper.v_cruise_initialized
cs = car.CarState(
cruiseState={
"available": True,
"speed": pcm_speed_kph * CV.KPH_TO_MS,
"speedCluster": pcm_cluster_speed_kph * CV.KPH_TO_MS,
},
samples = (
(72.0, 71.0, None),
(25.0, 25.0, {"type": ButtonType.decelCruise, "pressed": True}),
(65.0, 65.0, {"type": ButtonType.decelCruise, "pressed": False}),
(90.0, 90.0, {"type": ButtonType.accelCruise, "pressed": True}),
(5.0, 5.0, {"type": ButtonType.accelCruise, "pressed": False}),
)
helper.update_v_cruise(cs, True, True, False, toggles)
assert helper.v_cruise_kph == pytest.approx(pcm_speed_kph)
assert helper.v_cruise_cluster_kph == pytest.approx(pcm_cluster_speed_kph)
next_pcm_speed_kph = 74.0
next_cs = car.CarState(
cruiseState={
"available": True,
"speed": next_pcm_speed_kph * CV.KPH_TO_MS,
"speedCluster": next_pcm_speed_kph * CV.KPH_TO_MS,
},
buttonEvents=[{"type": ButtonType.accelCruise, "pressed": False}],
)
helper.update_v_cruise(next_cs, True, True, False, toggles)
assert helper.v_cruise_kph == pytest.approx(next_pcm_speed_kph)
def test_openpilot_longitudinal_pcm_cruise_uses_custom_intervals(self):
CP = car.CarParams(pcmCruise=True, openpilotLongitudinalControl=True)
helper = VCruiseHelper(CP)
toggles = SimpleNamespace(cruise_increase=5, cruise_increase_long=1, set_speed_limit=False)
initial_speed_kph = 40.0
helper.initialize_v_cruise(car.CarState(vEgo=initial_speed_kph * CV.KPH_TO_MS), False, False, toggles)
assert helper.v_cruise_kph == pytest.approx(initial_speed_kph)
press_cs = car.CarState(
cruiseState={"available": True},
buttonEvents=[{"type": ButtonType.accelCruise, "pressed": True}],
)
helper.update_v_cruise(press_cs, True, True, False, toggles)
release_cs = car.CarState(
cruiseState={"available": True},
buttonEvents=[{"type": ButtonType.accelCruise, "pressed": False}],
)
helper.update_v_cruise(release_cs, True, True, False, toggles)
assert helper.v_cruise_kph == pytest.approx(initial_speed_kph + 5)
helper.update_v_cruise(press_cs, True, True, False, toggles)
for _ in range(50):
helper.update_v_cruise(car.CarState(cruiseState={"available": True}), True, True, False, toggles)
assert helper.v_cruise_kph == pytest.approx(initial_speed_kph + 5 + 1)
def test_openpilot_longitudinal_pcm_cruise_starts_from_pcm_set_speed(self):
CP = car.CarParams(pcmCruise=True, openpilotLongitudinalControl=True)
helper = VCruiseHelper(CP)
toggles = SimpleNamespace(cruise_increase=5, cruise_increase_long=1, set_speed_limit=False)
pcm_speed_kph = 72.0
helper.initialize_v_cruise(
car.CarState(
vEgo=40 * CV.KPH_TO_MS,
cruiseState={"available": True, "speed": pcm_speed_kph * CV.KPH_TO_MS},
),
False,
False,
toggles,
)
assert helper.v_cruise_kph == pytest.approx(pcm_speed_kph)
helper.update_v_cruise(
car.CarState(
cruiseState={"available": True, "speed": 74 * CV.KPH_TO_MS},
),
True,
True,
False,
toggles,
)
assert helper.v_cruise_kph == pytest.approx(pcm_speed_kph)
for pcm_speed_kph, pcm_cluster_speed_kph, button_event in samples:
cs = car.CarState(
cruiseState={
"available": True,
"speed": pcm_speed_kph * CV.KPH_TO_MS,
"speedCluster": pcm_cluster_speed_kph * CV.KPH_TO_MS,
},
buttonEvents=[] if button_event is None else [button_event],
)
helper.update_v_cruise(cs, True, True, False, toggles)
assert helper.v_cruise_kph == pytest.approx(pcm_speed_kph)
assert helper.v_cruise_cluster_kph == pytest.approx(pcm_cluster_speed_kph)
class TestVCruiseHelperRedneck:
@@ -639,6 +639,9 @@ class LatControlTorque(LatControl):
output_torque *= tucson_4th_gen_center_taper
elif genesis_g70_active:
output_torque *= genesis_g70_center_output_taper
output_torque *= get_genesis_g70_high_speed_transition_scale(
setpoint, desired_lateral_jerk, CS.vEgo,
)
output_torque *= get_genesis_g70_curve_unwind_output_scale(setpoint, desired_lateral_jerk, CS.vEgo)
output_torque *= get_genesis_g70_high_speed_error_scale(
setpoint, measurement, desired_lateral_jerk, CS.vEgo,
@@ -289,6 +289,13 @@ GENESIS_G70_CENTER_OUTPUT_TAPER_LAT = 0.30
GENESIS_G70_CENTER_OUTPUT_TAPER_LAT_WIDTH = 0.10
GENESIS_G70_CENTER_OUTPUT_TAPER_SPEED = 18.0
GENESIS_G70_CENTER_OUTPUT_TAPER_SPEED_WIDTH = 3.0
GENESIS_G70_HIGH_SPEED_TRANSITION_DAMPING_MAX = 0.18
GENESIS_G70_HIGH_SPEED_TRANSITION_DAMPING_SPEED = 45.0 * CV.MPH_TO_MS
GENESIS_G70_HIGH_SPEED_TRANSITION_DAMPING_SPEED_WIDTH = 8.0 * CV.MPH_TO_MS
GENESIS_G70_HIGH_SPEED_TRANSITION_DAMPING_LAT = 0.45
GENESIS_G70_HIGH_SPEED_TRANSITION_DAMPING_LAT_WIDTH = 0.15
GENESIS_G70_HIGH_SPEED_TRANSITION_DAMPING_JERK = 0.35
GENESIS_G70_HIGH_SPEED_TRANSITION_DAMPING_JERK_WIDTH = 0.15
GENESIS_G70_LOW_SPEED_CENTER_TAPER_MAX = 0.06
GENESIS_G70_LOW_SPEED_CENTER_TAPER_LAT = 0.14
GENESIS_G70_LOW_SPEED_CENTER_TAPER_LAT_WIDTH = 0.05
@@ -3246,6 +3253,18 @@ def get_genesis_g70_center_output_scale(desired_lateral_accel: float, v_ego: flo
return 1.0 - reduction
def get_genesis_g70_high_speed_transition_scale(desired_lateral_accel: float,
desired_lateral_jerk: float, v_ego: float) -> float:
speed_weight = _sigmoid((v_ego - GENESIS_G70_HIGH_SPEED_TRANSITION_DAMPING_SPEED) /
GENESIS_G70_HIGH_SPEED_TRANSITION_DAMPING_SPEED_WIDTH)
center_weight = _sigmoid((GENESIS_G70_HIGH_SPEED_TRANSITION_DAMPING_LAT - abs(desired_lateral_accel)) /
GENESIS_G70_HIGH_SPEED_TRANSITION_DAMPING_LAT_WIDTH)
jerk_weight = _sigmoid((abs(desired_lateral_jerk) - GENESIS_G70_HIGH_SPEED_TRANSITION_DAMPING_JERK) /
GENESIS_G70_HIGH_SPEED_TRANSITION_DAMPING_JERK_WIDTH)
reduction = (GENESIS_G70_HIGH_SPEED_TRANSITION_DAMPING_MAX * speed_weight * center_weight * jerk_weight)
return 1.0 - reduction
def get_genesis_g70_low_speed_angle_damping(desired_angle_deg: float, actual_angle_deg: float,
current_output_torque: float, v_ego: float) -> float:
angle_error = desired_angle_deg - actual_angle_deg
@@ -54,6 +54,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_vehicle_tunes import (
get_rav4_tss2_pid_output,
get_subaru_impreza_pid_output_scale,
get_genesis_gv70_low_speed_center_overshoot_scale,
get_genesis_g70_high_speed_transition_scale,
normalize_flm_overrides,
set_flm_runtime_overrides,
)
@@ -960,6 +961,12 @@ class TestLatControl:
assert get_genesis_g70_low_speed_output_limit(0.0, 2.0) < 0.30
assert get_genesis_g70_low_speed_angle_damping(0.0, -20.0, 0.0, 2.0) < 0.0
assert get_genesis_g70_low_speed_angle_damping(0.0, 20.0, 0.0, 2.0) > 0.0
assert get_genesis_g70_high_speed_transition_scale(0.0, 0.8, 65.0 * 0.44704) < \
get_genesis_g70_high_speed_transition_scale(0.0, 0.1, 65.0 * 0.44704)
assert get_genesis_g70_high_speed_transition_scale(1.0, 0.8, 65.0 * 0.44704) > \
get_genesis_g70_high_speed_transition_scale(0.0, 0.8, 65.0 * 0.44704)
assert get_genesis_g70_high_speed_transition_scale(0.0, 0.8, 20.0 * 0.44704) > \
get_genesis_g70_high_speed_transition_scale(0.0, 0.8, 65.0 * 0.44704)
assert 0.90 < get_genesis_g70_curve_unwind_output_scale(0.7, -0.5, 25.0) < 1.0
assert get_genesis_g70_curve_unwind_output_scale(0.7, 0.5, 25.0) == 1.0
assert get_genesis_g70_angle_output_scale(55.0, 1.0) > get_genesis_g70_angle_output_scale(85.0, 1.0)
+1 -21
View File
@@ -32,6 +32,7 @@ class DeveloperLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
self._params.put_bool("LongitudinalManeuverMode", False)
# Build items and keep references for callbacks/state updates
self._adb_toggle = toggle_item(
@@ -59,13 +60,6 @@ class DeveloperLayout(Widget):
enabled=ui_state.is_offroad,
)
self._long_maneuver_toggle = toggle_item(
lambda: tr("Longitudinal Maneuver Mode"),
description="",
initial_state=self._params.get_bool("LongitudinalManeuverMode"),
callback=self._on_long_maneuver_mode,
)
self._alpha_long_toggle = toggle_item(
lambda: tr("openpilot Longitudinal Control (Alpha)"),
description=lambda: tr(DESCRIPTIONS["alpha_longitudinal"]),
@@ -87,7 +81,6 @@ class DeveloperLayout(Widget):
self._ssh_toggle,
self._ssh_keys,
self._joystick_toggle,
self._long_maneuver_toggle,
self._alpha_long_toggle,
self._ui_debug_toggle,
], line_separator=True, spacing=0)
@@ -114,13 +107,7 @@ class DeveloperLayout(Widget):
else:
self._alpha_long_toggle.set_visible(True)
long_man_enabled = ui_state.has_longitudinal_control and ui_state.is_offroad()
self._long_maneuver_toggle.action_item.set_enabled(long_man_enabled)
if not long_man_enabled:
self._long_maneuver_toggle.action_item.set_state(False)
self._params.put_bool("LongitudinalManeuverMode", False)
else:
self._long_maneuver_toggle.action_item.set_enabled(False)
self._alpha_long_toggle.set_visible(False)
# TODO: make a param control list item so we don't need to manage internal state as much here
@@ -129,7 +116,6 @@ class DeveloperLayout(Widget):
("AdbEnabled", self._adb_toggle),
("SshEnabled", self._ssh_toggle),
("JoystickDebugMode", self._joystick_toggle),
("LongitudinalManeuverMode", self._long_maneuver_toggle),
("AlphaLongitudinalEnabled", self._alpha_long_toggle),
("ShowDebugInfo", self._ui_debug_toggle),
):
@@ -149,12 +135,6 @@ class DeveloperLayout(Widget):
def _on_joystick_debug_mode(self, state: bool):
self._params.put_bool("JoystickDebugMode", state)
self._params.put_bool("LongitudinalManeuverMode", False)
self._long_maneuver_toggle.action_item.set_state(False)
def _on_long_maneuver_mode(self, state: bool):
self._params.put_bool("LongitudinalManeuverMode", state)
self._params.put_bool("JoystickDebugMode", False)
self._joystick_toggle.action_item.set_state(False)
def _on_alpha_long_enabled(self, state: bool):
if state:
@@ -731,7 +731,7 @@ class StarPilotLongitudinalLayout(_SettingsPage):
unit=self._speed_unit(),
value_type="float",
current_value=max(1, self._params.get_float("CustomCruise"))),
visible=lambda: self._params.get_bool("QOLLongitudinal")),
visible=lambda: self._params.get_bool("QOLLongitudinal") and not starpilot_state.car_state.isToyota),
SettingRow("CustomCruiseLong", "value", tr_noop("Cruise Long"),
subtitle="",
get_value=lambda: f"{max(1, self._params.get_float('CustomCruiseLong')):g}{self._speed_unit()}",
@@ -739,7 +739,12 @@ class StarPilotLongitudinalLayout(_SettingsPage):
unit=self._speed_unit(),
value_type="float",
current_value=max(1, self._params.get_float("CustomCruiseLong"))),
visible=lambda: self._params.get_bool("QOLLongitudinal")),
visible=lambda: self._params.get_bool("QOLLongitudinal") and not starpilot_state.car_state.isToyota),
SettingRow("ReverseCruise", "toggle", tr_noop("Reverse Cruise Increase"),
subtitle=tr_noop("Swap Toyota/Lexus cruise increments: short press changes the dash set speed by 5; hold changes it by 1."),
get_state=lambda: self._params.get_bool("ReverseCruise"),
set_state=lambda s: self._params.put_bool("ReverseCruise", s),
visible=lambda: self._params.get_bool("QOLLongitudinal") and starpilot_state.car_state.isToyota),
SettingRow("ForceStops", "toggle", tr_noop("Force Stops"),
subtitle="",
get_state=lambda: self._params.get_bool("ForceStops"),
@@ -12,6 +12,7 @@ class DeveloperLayoutMici(NavScroller):
def __init__(self):
super().__init__()
self._ssh_fetcher = SshKeyFetcher(ui_state.params)
ui_state.params.put_bool("LongitudinalManeuverMode", False)
def github_username_callback(username: str):
if username:
@@ -45,7 +46,6 @@ class DeveloperLayoutMici(NavScroller):
self._ssh_keys_btn = BigButton("SSH keys", "Not set" if not github_username else github_username, icon=txt_ssh)
self._ssh_keys_btn.set_click_callback(ssh_keys_callback)
# adb, ssh, ssh keys, debug mode, joystick debug mode, longitudinal maneuver mode, ip address
# ******** Main Scroller ********
self._adb_toggle = BigCircleParamControl(gui_app.texture("icons_mici/adb_short.png", 82, 82), "AdbEnabled", icon_offset=(0, 12))
self._ssh_toggle = BigCircleParamControl(gui_app.texture("icons_mici/ssh_short.png", 82, 82), "SshEnabled", icon_offset=(0, 12))
@@ -53,9 +53,6 @@ class DeveloperLayoutMici(NavScroller):
self._joystick_toggle = BigToggle("joystick debug mode",
initial_state=ui_state.params.get_bool("JoystickDebugMode"),
toggle_callback=self._on_joystick_debug_mode)
self._long_maneuver_toggle = BigToggle("longitudinal maneuver mode",
initial_state=ui_state.params.get_bool("LongitudinalManeuverMode"),
toggle_callback=self._on_long_maneuver_mode)
self._alpha_long_toggle = BigToggle("alpha longitudinal",
initial_state=ui_state.params.get_bool("AlphaLongitudinalEnabled"),
toggle_callback=self._on_alpha_long_enabled)
@@ -69,7 +66,6 @@ class DeveloperLayoutMici(NavScroller):
self._ssh_keys_btn,
self._disable_wide_road_toggle,
self._joystick_toggle,
self._long_maneuver_toggle,
self._alpha_long_toggle,
self._debug_mode_toggle,
])
@@ -80,7 +76,6 @@ class DeveloperLayoutMici(NavScroller):
("SshEnabled", self._ssh_toggle),
("DisableWideRoad", self._disable_wide_road_toggle),
("JoystickDebugMode", self._joystick_toggle),
("LongitudinalManeuverMode", self._long_maneuver_toggle),
("AlphaLongitudinalEnabled", self._alpha_long_toggle),
("ShowDebugInfo", self._debug_mode_toggle),
)
@@ -89,7 +84,7 @@ class DeveloperLayoutMici(NavScroller):
self._disable_wide_road_toggle,
self._joystick_toggle,
)
engaged_blocked_toggles = (self._long_maneuver_toggle, self._alpha_long_toggle)
engaged_blocked_toggles = (self._alpha_long_toggle,)
# Disable toggles that require offroad
for item in onroad_blocked_toggles:
@@ -129,13 +124,7 @@ class DeveloperLayoutMici(NavScroller):
else:
self._alpha_long_toggle.set_visible(True)
long_man_enabled = ui_state.has_longitudinal_control and ui_state.is_offroad()
self._long_maneuver_toggle.set_enabled(long_man_enabled)
if not long_man_enabled:
self._long_maneuver_toggle.set_checked(False)
ui_state.params.put_bool("LongitudinalManeuverMode", False)
else:
self._long_maneuver_toggle.set_enabled(False)
self._alpha_long_toggle.set_visible(False)
# Refresh toggles from params to mirror external changes
@@ -145,16 +134,8 @@ class DeveloperLayoutMici(NavScroller):
def _on_joystick_debug_mode(self, state: bool):
ui_state.params.put_bool("JoystickDebugMode", state)
ui_state.params.put_bool("LongitudinalManeuverMode", False)
self._long_maneuver_toggle.set_checked(False)
ui_state.params.put_bool("LateralManeuverMode", False)
def _on_long_maneuver_mode(self, state: bool):
ui_state.params.put_bool("LongitudinalManeuverMode", state)
ui_state.params.put_bool("JoystickDebugMode", False)
self._joystick_toggle.set_checked(False)
ui_state.params.put_bool("LateralManeuverMode", False)
restart_needed_callback(state)
def _on_alpha_long_enabled(self, state: bool):
# TODO: show confirmation dialog before enabling
ui_state.params.put_bool("AlphaLongitudinalEnabled", state)
@@ -1704,6 +1704,10 @@
"precision": 0,
"unit_type": "vehicle_speed",
"metric_max": 150.0,
"excluded_vehicle_makes": [
"Lexus",
"Toyota"
],
"parent_key": "QOLLongitudinal",
"settings_tier": "simple"
},
@@ -1719,6 +1723,24 @@
"precision": 0,
"unit_type": "vehicle_speed",
"metric_max": 150.0,
"excluded_vehicle_makes": [
"Lexus",
"Toyota"
],
"parent_key": "QOLLongitudinal",
"settings_tier": "simple"
},
{
"key": "ReverseCruise",
"label": "Reverse Cruise Increase",
"description": "Reverse Toyota/Lexus cruise-button behavior so a short press changes the dashboard set speed by 5 and a hold changes it by 1.",
"picker_description": "Swaps Toyota/Lexus short-press and hold cruise increments.",
"data_type": "bool",
"ui_type": "toggle",
"vehicle_makes": [
"Lexus",
"Toyota"
],
"parent_key": "QOLLongitudinal",
"settings_tier": "simple"
},
@@ -4829,12 +4851,12 @@
},
{
"key": "GalaxyMobileDefault",
"label": "Try the Big Dipper Web UI",
"description": "Open the Big Dipper at the top-level Galaxy link instead of the classic Galaxy. The classic UI remains available at /classic and Big Dipper at /mobile regardless of this toggle.",
"picker_description": "Serve the Big Dipper as the default landing page.",
"label": "Use Galaxy (new) by Default",
"description": "Open Galaxy (new) at the top-level Galaxy link. Turn this off to use Galaxy (old) instead. Galaxy (old) remains available at /classic and Galaxy (new) at /mobile.",
"picker_description": "Serve Galaxy (new) as the default landing page.",
"data_type": "bool",
"ui_type": "toggle",
"settings_tier": "advanced"
"settings_tier": "simple"
},
{
"key": "AlphaLongitudinalEnabled",
+19 -2
View File
@@ -390,6 +390,16 @@ def speed_limit_controller_available(openpilot_longitudinal: bool, redneck_cruis
return openpilot_longitudinal or redneck_cruise
def software_cruise_intervals_available(quality_of_life: bool, car_make: str, pcm_cruise: bool,
openpilot_longitudinal: bool, pcm_cruise_speed: bool) -> bool:
return bool(quality_of_life and not (car_make == "toyota" and pcm_cruise) and
(openpilot_longitudinal or not pcm_cruise_speed))
def reverse_cruise_available(quality_of_life: bool, car_make: str, pcm_cruise: bool) -> bool:
return bool(quality_of_life and car_make == "toyota" and pcm_cruise)
def migrate_cancel_button_controls(params: Params | None = None) -> bool:
params = params or Params(return_defaults=True)
if params.get_bool(CANCEL_BUTTON_MIGRATION_KEY) or not params.get_bool("RemapCancelToDistance"):
@@ -1345,10 +1355,17 @@ class StarPilotVariables:
toggle.pause_lateral_below_signal = self.get_value("PauseLateralOnSignal", condition=toggle.pause_lateral_below_speed != 0)
toggle.pause_lateral_signal_delay = self.get_value("LateralResumeDelay", cast=float, condition=toggle.pause_lateral_below_signal, default=0.0, min=0.0, max=5.0)
quality_of_life_longitudinal = toggle.openpilot_longitudinal and self.get_value("QOLLongitudinal")
quality_of_life_cruise = self.get_value("QOLLongitudinal") and (toggle.openpilot_longitudinal or not FPCP.pcmCruiseSpeed)
quality_of_life = self.get_value("QOLLongitudinal")
quality_of_life_longitudinal = toggle.openpilot_longitudinal and quality_of_life
quality_of_life_cruise = software_cruise_intervals_available(
quality_of_life, toggle.car_make, pcm_cruise, toggle.openpilot_longitudinal, FPCP.pcmCruiseSpeed,
)
toggle.cruise_increase = self.get_value("CustomCruise", cast=float, condition=quality_of_life_cruise, default=1.0)
toggle.cruise_increase_long = self.get_value("CustomCruiseLong", cast=float, condition=quality_of_life_cruise, default=5.0)
toggle.reverse_cruise_increase = self.get_value(
"ReverseCruise",
condition=reverse_cruise_available(quality_of_life, toggle.car_make, pcm_cruise),
)
toggle.force_stops = self.get_value("ForceStops", condition=quality_of_life_longitudinal)
toggle.force_stop_distance_offset = self.get_value("ForceStopDistanceOffset", cast=int, condition=(quality_of_life_longitudinal and toggle.force_stops))
toggle.force_standstill = self.get_value("ForceStandstill", condition=quality_of_life_longitudinal)
@@ -328,4 +328,14 @@ def test_set_speed_limit_unavailable_on_stock_pcm_without_helper():
def test_speed_limit_controller_available_on_openpilot_longitudinal_or_redneck():
assert spv.speed_limit_controller_available(openpilot_longitudinal=True, redneck_cruise=False) is True
assert spv.speed_limit_controller_available(openpilot_longitudinal=False, redneck_cruise=True) is True
def test_toyota_pcm_cruise_uses_hardware_reverse_instead_of_software_intervals():
assert spv.software_cruise_intervals_available(True, "toyota", True, True, True) is False
assert spv.reverse_cruise_available(True, "toyota", True) is True
def test_non_toyota_software_cruise_keeps_custom_intervals():
assert spv.software_cruise_intervals_available(True, "hyundai", False, True, True) is True
assert spv.reverse_cruise_available(True, "hyundai", False) is False
assert spv.speed_limit_controller_available(openpilot_longitudinal=False, redneck_cruise=False) is False
@@ -10,7 +10,6 @@ import { VehicleFeatures } from "/assets/components/tools/vehicle_features.js"
import { TSKManager } from "/assets/components/tools/tsk_manager.js"
import { GalaxyPairing } from "/assets/components/tools/galaxy.js"
import { Home } from "/assets/components/home/home.js"
import { LongitudinalManeuvers } from "/assets/components/tools/longitudinal_maneuvers.js"
import { MapsManager } from "/assets/components/tools/maps.js"
import { NavDestination } from "/assets/components/navigation/navigation_destination.js?v=nav-search-context-2"
import { NavKeys } from "/assets/components/navigation/navigation_keys.js?v=app-keys-session-1"
@@ -91,7 +90,6 @@ function Root() {
createRoute("model_laboratory", "/model_laboratory", ModelLaboratory),
createRoute("tuning", "/tuning", Tuning),
createRoute("lateral_maneuvers", "/lateral_maneuvers", Tuning),
createRoute("longitudinal_maneuvers", "/longitudinal_maneuvers", LongitudinalManeuvers),
createRoute("maps", "/manage_maps", MapsManager),
createRoute("plots", "/plots", LivePlots),
createRoute("thememaker", "/theme_maker", ThemeMaker),
@@ -18,7 +18,6 @@ const MENU_ITEMS = {
{ name: "Sentry Mode", link: "/sentry", icon: "bi-shield-exclamation" },
{ name: "Controllers", link: "/wheel-controls", icon: "bi-controller" },
{ name: "Lateral Tuning", link: "/tuning", icon: "bi-sign-turn-right" },
{ name: "Long Maneuvers", link: "/longitudinal_maneuvers", icon: "bi-signpost-split" },
{ name: "Maps", link: "/manage_maps", icon: "bi-map" },
{ name: "Navigation", link: "/set_navigation_destination", icon: "bi-geo-alt-fill" },
{ name: "App Keys", link: "/manage_navigation_keys", icon: "bi-key-fill" },
@@ -1,5 +1,5 @@
import { html, reactive } from "/assets/vendor/arrow-core.js"
import { formatNumericParamValue, resolveVehicleUnitParam, vehicleSpeedUnit } from "/assets/mobile/js/params.js"
import { formatNumericParamValue, resolveVehicleUnitParam } from "/assets/mobile/js/params.js"
const endpointOptionsCache = {}
const endpointOptionsInflight = {}
@@ -102,9 +102,11 @@ function normalizeVehicleMake(value) {
function isVehicleSettingVisible(section, param) {
const allowedMakes = param.vehicle_makes || (section.name === "Vehicle" ? VEHICLE_SETTING_MAKES[param.key] : null)
if (!allowedMakes) return true
const selectedMake = normalizeVehicleMake(state.values.CarMake)
return allowedMakes.some(make => normalizeVehicleMake(make) === selectedMake)
if (allowedMakes && !allowedMakes.some(make => normalizeVehicleMake(make) === selectedMake)) return false
const excludedMakes = param.excluded_vehicle_makes || []
return !excludedMakes.some(make => normalizeVehicleMake(make) === selectedMake)
}
function matchesSettingValueCondition(param) {
@@ -1818,11 +1820,6 @@ export function DeviceSettings({ params }) {
<div class="ds-wrapper">
<h2>Toggles</h2>
<div class="ds-unit-note">
<i class="bi bi-speedometer2"></i>
<span>Vehicle-unit speed settings use <strong>${() => vehicleSpeedUnit(state.values)}</strong> and follow the comma's <em>Use Metric System</em> toggle. Each control shows its adjustment step.</span>
</div>
<div class="ds-search-row">
<input
class="ds-search"
@@ -138,10 +138,13 @@
.dh-donut {
--dh-value: 0;
display: grid;
align-items: center;
display: flex;
flex-direction: column;
flex: 0 0 auto;
gap: 3px;
height: 116px;
place-items: center;
justify-content: center;
position: relative;
width: 116px;
}
@@ -610,6 +610,48 @@ ul { list-style: none; margin: 0; padding: 0; }
font-size: 1.2rem;
}
.gx-row.gx-diagnostic-row--changed {
background: rgba(139, 108, 197, 0.18) !important;
box-shadow: inset 3px 0 var(--primary);
}
.gx-update-progress {
display: grid;
gap: 6px;
margin-top: var(--sp-2);
}
.gx-update-progress__track {
background: rgba(255, 255, 255, 0.12);
border-radius: var(--radius-full);
height: 12px;
overflow: hidden;
}
.gx-update-progress__fill {
background: linear-gradient(90deg, #5ec8c8 0%, #8b6cc5 100%);
border-radius: inherit;
height: 100%;
transition: width 0.4s ease;
}
.gx-update-progress__fill--error {
background: linear-gradient(90deg, #b43a3a 0%, #de5656 100%);
}
.gx-update-progress__meta {
align-items: center;
display: flex;
font-size: var(--fs-sm);
gap: var(--sp-2);
justify-content: space-between;
}
.gx-update-progress small {
color: var(--text-muted);
overflow-wrap: anywhere;
}
[data-theme="light"] .gx-card {
border: 1px solid rgba(120, 73, 232, 0.22);
}
@@ -7,7 +7,7 @@
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Big Dipper">
<meta name="apple-mobile-web-app-title" content="Galaxy">
<meta name="format-detection" content="telephone=no">
<meta name="theme-color" content="#8b6cc5" />
<link rel="manifest" href="/assets/mobile/manifest.json" crossorigin="use-credentials">
@@ -26,7 +26,7 @@
}
</script>
<title>Big Dipper</title>
<title>Galaxy</title>
</head>
<body>
@@ -8,12 +8,12 @@ import { Logs } from "./views/Logs.js"
import { Tuning } from "./views/Tuning.js"
import { Navigation } from "./views/Navigation.js"
import { Vehicle } from "./views/Vehicle.js"
import { Bluetooth } from "./views/Bluetooth.js"
import { SystemTools } from "./views/SystemTools.js"
import { ToolEmbed } from "./views/ToolEmbed.js"
import { Doors } from "./views/Doors.js"
import { Galaxy } from "./views/Galaxy.js"
import { Tsk } from "./views/Tsk.js"
import { Sentry } from "./views/Sentry.js"
import { ModelManager } from "./views/ModelManager.js"
import { Plots } from "./views/Plots.js"
import { TestingGround } from "./views/TestingGround.js"
@@ -43,12 +43,13 @@ const VIEWS = {
"/tuning": Tuning,
"/navigation": Navigation,
"/vehicle": Vehicle,
"/bluetooth": Bluetooth,
"/system": SystemTools,
"/embed": ToolEmbed,
"/manage_doors": Doors,
"/galaxy": Galaxy,
"/manage_tsk": Tsk,
"/sentry": Sentry,
"/sentry": Cameras,
"/manage_models": ModelManager,
"/plots": Plots,
"/testing_ground": TestingGround,
@@ -7,12 +7,12 @@ const NAV = {
{ name: "Recordings", link: "/recordings", icon: "bi-camera-reels" },
],
tools: [
{ name: "Bluetooth", link: "/bluetooth", icon: "bi-bluetooth" },
{ name: "Cameras & Monitoring", link: "/cameras", icon: "bi-camera-video" },
{ name: "Galaxy", link: "/galaxy", icon: "bi-globe2" },
{ name: "Logs & Diagnostics", link: "/logs", icon: "bi-exclamation-triangle" },
{ name: "Model Manager", link: "/manage_models", icon: "bi-cpu" },
{ name: "Navigation & Maps", link: "/navigation", icon: "bi-map" },
{ name: "Sentry Mode", link: "/sentry", icon: "bi-shield-exclamation" },
{ name: "System Tools", link: "/system", icon: "bi-arrow-repeat" },
{ name: "Model Laboratory", link: "/model_laboratory", icon: "bi-bezier2" },
{ name: "Plots", link: "/plots", icon: "bi-graph-up-arrow" },
@@ -102,7 +102,7 @@ export const AppShell = {
<button type="button" class="gx-icon-btn gx-menu-btn" aria-label="Menu" @click="store.drawerOpen = true">
<i class="bi bi-list"></i>
</button>
<span class="gx-appbar__title">Big Dipper</span>
<span class="gx-appbar__title">Galaxy</span>
<div class="gx-searchwrap">
<input ref="searchInput" class="gx-search gx-appbar__search" type="search" placeholder="Search toggles..."
v-model="search" aria-label="Search toggles" />
@@ -128,8 +128,8 @@ export const AppShell = {
</transition>
<aside class="gx-drawer" :class="{ open: store.drawerOpen }">
<div class="gx-drawer__header">
<img class="gx-logo" src="/assets/images/main_logo.png" alt="Big Dipper logo" />
<span class="gx-drawer-title">Big Dipper</span>
<img class="gx-logo" src="/assets/images/main_logo.png" alt="Galaxy logo" />
<span class="gx-drawer-title">Galaxy</span>
</div>
<div class="gx-nav-section">
<div class="gx-nav-section__title">Main</div>
@@ -20,6 +20,7 @@ export const BluetoothPanel = {
availableDevices() { return this.devices.filter((d) => !d.paired && !d.trusted && !d.connected) },
},
methods: {
address,
async refresh() {
try {
const p = await api.getBluetoothStatus()
@@ -216,6 +216,7 @@ export const TroubleshootPanel = {
<div v-if="!itemsVisible(section).length" class="gx-empty">No settings are currently different from their defaults.</div>
<div v-else style="display:grid; gap:8px; grid-template-columns:repeat(auto-fill,minmax(260px,1fr)); padding:0 var(--sp-3) var(--sp-3);">
<div v-for="item in itemsVisible(section)" :key="item.label" class="gx-row"
:class="{ 'gx-diagnostic-row--changed': isChanged(item) }"
style="border:none; background:var(--surface); border-radius:var(--radius-md); margin:0; padding:10px 12px; flex-direction:column; align-items:stretch; gap:8px;">
<div style="display:flex; align-items:center; gap:6px; min-width:0;">
<span class="gx-row__label" style="font-size:var(--fs-sm); overflow-wrap:anywhere;">{{ item.label }}</span>
@@ -91,7 +91,7 @@ export function goBack() {
window.location.hash = prev
}
const NATIVE_ROOTS = new Set(["/", "/settings", "/tools", "/recordings", "/logs", "/tuning", "/navigation", "/vehicle", "/system", "/embed", "/manage_doors", "/galaxy", "/manage_tsk", "/sentry", "/manage_models", "/plots", "/testing_ground", "/theme_maker", "/model_laboratory", "/cameras"])
const NATIVE_ROOTS = new Set(["/", "/settings", "/tools", "/recordings", "/logs", "/tuning", "/navigation", "/vehicle", "/bluetooth", "/system", "/embed", "/manage_doors", "/galaxy", "/manage_tsk", "/sentry", "/manage_models", "/plots", "/testing_ground", "/theme_maker", "/model_laboratory", "/cameras"])
export function toolHref(link) {
const path = link.split("?")[0]
@@ -0,0 +1,15 @@
import { BluetoothPanel } from "../components/BluetoothPanel.js"
import { GalaxySection } from "../components/GalaxySection.js"
export const Bluetooth = {
name: "Bluetooth",
components: { BluetoothPanel, GalaxySection },
template: `
<div class="gx-view">
<h2 style="margin-top:0;">Bluetooth</h2>
<GalaxySection title="Bluetooth Devices" icon="bi-bluetooth" :collapsible="false">
<BluetoothPanel />
</GalaxySection>
</div>
`,
}
@@ -1,18 +1,20 @@
import { GalaxyTabs } from "../components/GalaxyTabs.js"
import { useTabRouting } from "../composables.js"
import { Sentry } from "./Sentry.js"
import { Vasm } from "./Vasm.js"
import { Pip } from "./Pip.js"
const TABS = {
sentry: "Sentry Mode",
vasm: "V-ASM Spot Monitor",
pip: "PiP Side Camera",
}
export const Cameras = {
name: "Cameras",
components: { Vasm, Pip, GalaxyTabs },
components: { Sentry, Vasm, Pip, GalaxyTabs },
setup() {
return useTabRouting("/cameras", { vasm: "vasm", pip: "pip" })
return useTabRouting("/cameras", { sentry: "sentry", vasm: "vasm", pip: "pip" })
},
data() { return { TABS } },
template: `
@@ -20,7 +22,11 @@ export const Cameras = {
<h2 style="margin-top:0;">Cameras & Monitoring</h2>
<GalaxyTabs :items="TABS" :active="tab" @select="selectTab" />
<template v-if="tab === 'vasm'">
<template v-if="tab === 'sentry'">
<Sentry />
</template>
<template v-else-if="tab === 'vasm'">
<Vasm :embedded="true" />
</template>
@@ -4,6 +4,13 @@ import { PwaInstallSection, isFirestarOrigin } from "../components/PwaInstallSec
const isTunnel = () => isFirestarOrigin()
function localDeviceUrl(ip) {
const raw = String(ip || "").trim()
if (!raw || raw === "unknown") return ""
const host = raw.includes(":") && !raw.startsWith("[") ? `[${raw}]` : raw
return `http://${host}:8082`
}
export const Galaxy = {
name: "Galaxy",
components: { PwaInstallSection },
@@ -14,10 +21,17 @@ export const Galaxy = {
url: "",
password: "",
submitting: false,
localUrl: "",
}
},
async mounted() {
if (this.isTunnel) return
if (this.isTunnel) {
try {
const status = await api.getDeviceStatus()
this.localUrl = localDeviceUrl(status?.lanIp)
} catch (e) {}
return
}
try {
const data = await api.getGalaxyStatus()
this.paired = !!data?.paired
@@ -76,7 +90,11 @@ export const Galaxy = {
<i class="bi bi-satellite gx-alert__icon"></i>
<div class="gx-alert__body">
<strong>Galaxy Pairing Unavailable via Galaxy</strong>
<span>Galaxy pairing requires a direct connection. Connect to your device's local network to use this feature.</span>
<span>
Galaxy pairing requires a direct connection. If you are on the same local network, connect here:
<a v-if="localUrl" :href="localUrl" style="color:inherit; font-weight:var(--fw-bold); overflow-wrap:anywhere;">{{ localUrl }}</a>
<span v-else>your device's local IP on port 8082.</span>
</span>
</div>
</div>
</section>
@@ -253,7 +253,7 @@ export const Home = {
name: m.name,
label: `${toInt(m.drives)} ${toNum(m.drives) === 1 ? "drive" : "drives"} using this model`,
}))
return { hasModels: true, style: `background: conic-gradient(${segments.join(", ")})`, rows }
return { hasModels: true, style: `conic-gradient(${segments.join(", ")})`, rows }
},
storageView() {
@@ -510,7 +510,7 @@ export const Home = {
<section class="gx-card dh-card">
<div class="dh-card__head"><i class="bi bi-stars"></i><span>Most used models</span></div>
<div v-if="modelView.hasModels" class="dh-body dh-models">
<div class="dh-chart-ring" :style="{ background: modelView.style }"></div>
<div class="dh-chart-ring" :style="{ backgroundImage: modelView.style }" role="img" aria-label="Model usage share"></div>
<div class="dh-models__list">
<div v-for="m in modelView.rows" :key="m.name" class="dh-model">
<span class="dh-swatch" :style="{ background: m.color }"></span>
@@ -5,9 +5,9 @@ import { TroubleshootPanel } from "../components/TroubleshootPanel.js"
import { GalaxyTabs } from "../components/GalaxyTabs.js"
const TABS = {
troubleshoot: "Troubleshoot",
errors: "Error Logs",
tmux: "Tmux Live Log",
troubleshoot: "Troubleshoot",
}
function parseLogDate(filename) {
@@ -35,7 +35,7 @@ export const Logs = {
}
},
setup() {
return useTabRouting("/logs", { errors: "errors", tmux: "tmux", troubleshoot: "troubleshoot" })
return useTabRouting("/logs", { troubleshoot: "troubleshoot", errors: "errors", tmux: "tmux" })
},
created() {
this.stream = useLogStream({ endpoint: "/api/tmux_log/live", snapshotFn: () => api.tmuxSnapshot(), interval: 2000 })
@@ -52,6 +52,13 @@ function normalizeRoute(r) {
}
}
function localDeviceUrl(ip) {
const raw = String(ip || "").trim()
if (!raw || raw === "unknown") return ""
const host = raw.includes(":") && !raw.startsWith("[") ? `[${raw}]` : raw
return `http://${host}:8082`
}
export const Recordings = {
name: "Recordings",
components: { GalaxyTabs, GxNotice },
@@ -75,6 +82,7 @@ export const Recordings = {
logsRoute: null,
logsData: null,
onFirestar: isFirestarOrigin(),
localUrl: "",
// Screen recordings subtab
screenLoading: false,
screenError: "",
@@ -335,7 +343,14 @@ export const Recordings = {
},
},
async mounted() {
if (!this.onFirestar) await this.loadRoutes()
if (this.onFirestar) {
try {
const status = await api.getDeviceStatus()
this.localUrl = localDeviceUrl(status?.lanIp)
} catch (e) {}
return
}
await this.loadRoutes()
},
beforeUnmount() {
this.controller?.abort()
@@ -503,8 +518,11 @@ export const Recordings = {
</Teleport>
</template>
<GxNotice v-else tone="info" icon="bi-satellite" title="Recordings Unavailable via Galaxy"
text="Loading recordings requires a direct connection. Connect to your device's local network to use this feature." />
<GxNotice v-else tone="info" icon="bi-satellite" title="Recordings unavailable via Galaxy">
Recordings are unavailable via Galaxy for bandwidth reasons. If you are on the same local network, connect here:
<a v-if="localUrl" :href="localUrl" style="color:inherit; font-weight:var(--fw-bold); overflow-wrap:anywhere;">{{ localUrl }}</a>
<span v-else>your device's local IP on port 8082.</span>
</GxNotice>
</div>
`,
}
@@ -2,7 +2,7 @@ import { api, showSnackbar } from "../api.js"
import { navigate, store } from "../store.js"
import {
applyParamChange, countAdvancedHiddenByDeveloperMode, GALAXY_DEVELOPER_MODE_KEY, isSettingVisible,
resolveVehicleUnitParam, slugifySectionName, vehicleSpeedUnit,
resolveVehicleUnitParam, slugifySectionName,
} from "../params.js"
import { SettingTree } from "../components/SettingTree.js"
import { GalaxyToggleCard } from "../components/GalaxyToggleCard.js"
@@ -39,7 +39,6 @@ export const Settings = {
return this.sections.find((s) => s.slug === this.activeSectionSlug) || this.sections[0]
},
hiddenAdvancedCount() { return countAdvancedHiddenByDeveloperMode(this.layout, this.values) },
speedUnit() { return vehicleSpeedUnit(this.values) },
searchActive() { return !!this.searchTerm },
searchTerm: {
get() { return store.search },
@@ -120,11 +119,6 @@ export const Settings = {
<div>
<h2 style="margin-top:0;">Toggles</h2>
<div class="gx-unit-note">
<i class="bi bi-speedometer2"></i>
<span>Vehicle-unit speed settings use <strong>{{ speedUnit }}</strong> and follow the comma's <em>Use Metric System</em> toggle. Each control shows its adjustment step.</span>
</div>
<DevModeBanner :hidden-count="hiddenAdvancedCount" :dev-mode-on="devModeOn" />
<div v-if="loading" class="gx-loading">Loading configuration...</div>
@@ -30,11 +30,17 @@ export const SystemTools = {
profileBusy: "",
}
},
created() { this.poll = usePolling(() => this.loadFastStatus(), { interval: 3000 }); this.poll.start() },
created() {
this.poll = usePolling(() => this.loadFastStatus(), {
interval: 1000,
enabled: () => !this.fastStatus || !!this.fastStatus.running,
})
this.poll.start()
},
mounted() { this.loadBranches(); this.loadProfiles() },
beforeUnmount() { this.poll?.destroy() },
computed: {
updateAvailable() { return !!this.fastStatus?.updateAvailable && !this.fastStatus?.running },
updateAvailable() { return this.checkedForUpdates && !!this.fastStatus?.updateAvailable && !this.fastStatus?.running },
factoryResetStatus() {
const s = this.fastStatus
if (!s || String(s?.lastMode || "").trim() !== "factory-reset") return null
@@ -53,6 +59,7 @@ export const SystemTools = {
},
methods: {
shortCommit,
toPercent,
async loadBranches() {
try {
const data = await api.getUpdateBranches()
@@ -65,8 +72,15 @@ export const SystemTools = {
this.branchLoading = false
}
},
async loadFastStatus() {
try { this.fastStatus = await api.getUpdateFastStatus() } catch (e) { this.fastStatus = null }
async loadFastStatus({ throwOnError = false } = {}) {
try {
const status = await api.getUpdateFastStatus()
if (!status) throw new Error("Update status unavailable")
this.fastStatus = status
} catch (e) {
this.fastStatus = null
if (throwOnError) throw e
}
},
async backupToggles() {
try {
@@ -160,6 +174,7 @@ export const SystemTools = {
try {
await api.setUpdateBranch(branch)
showSnackbar(`Switching to ${branch}...`)
await this.loadFastStatus()
} catch (e) {
showSnackbar(e?.message || "Switch failed.", "error")
}
@@ -168,7 +183,7 @@ export const SystemTools = {
if (this.busy) return
this.busy = "check"
try {
await this.loadFastStatus()
await this.loadFastStatus({ throwOnError: true })
this.checkedForUpdates = true
const st = this.fastStatus
if (st?.running) showSnackbar("An update is already running.")
@@ -227,6 +242,7 @@ export const SystemTools = {
try {
await api.factoryReset()
showSnackbar("SAVE ME initiated — factory resetting...")
await this.loadFastStatus()
} catch (e) {
showSnackbar(e?.message || "Factory reset failed.", "error")
}
@@ -256,14 +272,27 @@ export const SystemTools = {
<i class="bi bi-arrow-repeat"></i>
<span class="gx-section__title">Update Status</span>
<span v-if="fastStatus.running" class="gx-chip" style="background:var(--primary);color:var(--on-primary);">{{ fastStatus.progressPercent }}%</span>
<span v-else-if="fastStatus.updateAvailable" class="gx-chip" style="background:var(--warning);color:var(--black);">Update available</span>
<span v-else class="gx-chip">Up to date</span>
<span v-else-if="updateAvailable" class="gx-chip" style="background:var(--warning);color:var(--black);">Update available</span>
<span v-else-if="checkedForUpdates" class="gx-chip">Up to date</span>
<span v-else class="gx-chip">Not checked</span>
</div>
<div style="padding: var(--sp-3); display:grid; gap:6px;">
<div class="gx-row" style="border-top:none; min-height:0; padding:4px 0;"><span class="gx-row__label">Branch</span><span class="gx-row__value">{{ fastStatus.branch || currentBranch || '' }}</span></div>
<div v-if="fastStatus.running" class="gx-row" style="border-top:none; min-height:0; padding:4px 0;"><span class="gx-row__label">Stage</span><span class="gx-row__value">{{ fastStatus.stage }} · {{ fastStatus.progressLabel }}</span></div>
<div class="gx-row" style="border-top:none; min-height:0; padding:4px 0;"><span class="gx-row__label">Local</span><span class="gx-row__value" style="font-family:monospace;">{{ shortCommit(fastStatus.localCommit) }}</span></div>
<div class="gx-row" style="border-top:none; min-height:0; padding:4px 0;"><span class="gx-row__label">Remote</span><span class="gx-row__value" style="font-family:monospace;">{{ shortCommit(fastStatus.remoteCommit) }}</span></div>
<div v-if="fastStatus.running" class="gx-update-progress" role="progressbar" aria-label="Update progress"
:aria-valuenow="Math.round(fastStatus.progressPercent || 0)" aria-valuemin="0" aria-valuemax="100">
<div class="gx-update-progress__track">
<div class="gx-update-progress__fill" :class="{ 'gx-update-progress__fill--error': fastStatus.stage === 'error' }"
:style="{ width: toPercent(fastStatus.progressPercent) + '%' }"></div>
</div>
<div class="gx-update-progress__meta">
<span>Step {{ fastStatus.progressStep || 0 }}/{{ fastStatus.progressTotalSteps || 5 }}: {{ fastStatus.progressLabel || fastStatus.stage || 'Updating' }}</span>
<strong>{{ Math.round(toPercent(fastStatus.progressPercent)) }}%</strong>
</div>
<small v-if="fastStatus.progressDetail">{{ fastStatus.progressDetail }}</small>
</div>
<div v-if="fastStatus.message" class="gx-note">{{ fastStatus.message }}</div>
<div v-if="fastStatus.warning && (fastStatus.running || fastStatus.updateAvailable)" class="gx-note gx-note--danger">{{ fastStatus.warning }}</div>
<div v-if="fastStatus.agnosUpdate?.available && fastStatus.agnosUpdate?.warnings?.length" style="margin-top:4px;">
@@ -287,7 +316,7 @@ export const SystemTools = {
<i v-if="busy === 'check'" class="bi bi-arrow-repeat gx-spin"></i>
<i v-else class="bi bi-search"></i> {{ busy === 'check' ? 'Checking...' : 'Check for Updates' }}
</button>
<button type="button" class="gx-btn" :disabled="!updateAvailable || !!busy || isOnroad" @click="applyFastUpdate">
<button v-if="updateAvailable" type="button" class="gx-btn" :disabled="!!busy || isOnroad" @click="applyFastUpdate">
<i class="bi bi-arrow-up-circle"></i> {{ busy === 'fast' ? 'Updating...' : 'Update Now' }}
</button>
<button type="button" class="gx-btn gx-btn--tonal" :disabled="!!busy || isOnroad" @click="runUpdate('recover')">Recover</button>
@@ -1,17 +1,17 @@
import { navigate, toolHref } from "../store.js"
const TOOLS = [
{ name: "Cameras & Monitoring", link: "/cameras", icon: "bi-camera-video", desc: "PiP side camera & V-ASM spot monitor" },
{ name: "Bluetooth", link: "/bluetooth", icon: "bi-bluetooth", desc: "Pair devices, controllers, & audio" },
{ name: "Cameras & Monitoring", link: "/cameras", icon: "bi-camera-video", desc: "Sentry, PiP side camera, & V-ASM spot monitor" },
{ name: "Galaxy & App Install", link: "/galaxy", icon: "bi-globe2", desc: "Remote access, pairing, & app install" },
{ name: "Logs & Diagnostics", link: "/logs", icon: "bi-exclamation-triangle", desc: "Error logs, tmux, troubleshoot" },
{ name: "Model Manager", link: "/manage_models", icon: "bi-cpu", desc: "Install/swap models" },
{ name: "Model Laboratory", link: "/model_laboratory", icon: "bi-bezier2", desc: "Pair lateral and longitudinal models" },
{ name: "Navigation & Maps", link: "/navigation", icon: "bi-map", desc: "Offline maps & destinations" },
{ name: "Sentry Mode", link: "/sentry", icon: "bi-shield-exclamation", desc: "Sentry alerts & security" },
{ name: "System Tools", link: "/system", icon: "bi-arrow-repeat", desc: "Backup, restore, updates" },
{ name: "Theme Maker", link: "/theme_maker", icon: "bi-palette-fill", desc: "Customize the look" },
{ name: "Tuning, Plots & Testing", link: "/tuning", icon: "bi-sign-turn-right", desc: "Steering & speed tuning, live plots, testing grounds" },
{ name: "Vehicle Controls", link: "/vehicle", icon: "bi-car-front", desc: "Controllers, bluetooth, vehicle features" },
{ name: "Vehicle Controls", link: "/vehicle", icon: "bi-car-front", desc: "Controllers & vehicle features" },
].sort((a, b) => a.name.localeCompare(b.name))
export const Tools = {
@@ -1,5 +1,4 @@
import { LateralTuningPanel } from "../components/LateralTuningPanel.js"
import { LongitudinalManeuvers } from "../components/LongitudinalManeuvers.js"
import { GalaxyTabs } from "../components/GalaxyTabs.js"
import { Plots } from "./Plots.js"
import { TestingGround } from "./TestingGround.js"
@@ -7,16 +6,15 @@ import { useTabRouting } from "../composables.js"
const TABS = {
lateral: "Lateral Tuning",
long: "Long Maneuvers",
plots: "Plots",
testing: "Testing Ground",
}
export const Tuning = {
name: "Tuning",
components: { LateralTuningPanel, LongitudinalManeuvers, Plots, TestingGround, GalaxyTabs },
components: { LateralTuningPanel, Plots, TestingGround, GalaxyTabs },
setup() {
return useTabRouting("/tuning", { lateral: "lateral", long: "long", plots: "plots", testing: "testing" })
return useTabRouting("/tuning", { lateral: "lateral", plots: "plots", testing: "testing" })
},
data() { return { TABS } },
template: `
@@ -29,10 +27,6 @@ export const Tuning = {
<LateralTuningPanel />
</template>
<template v-else-if="tab === 'long'">
<LongitudinalManeuvers />
</template>
<template v-else-if="tab === 'plots'">
<Plots :embedded="true" />
</template>
@@ -1,7 +1,6 @@
import { api, showSnackbar } from "../api.js"
import { navigate, toolHref } from "../store.js"
import { WheelControls } from "../components/WheelControls.js"
import { BluetoothPanel } from "../components/BluetoothPanel.js"
import { GalaxySection } from "../components/GalaxySection.js"
import { GalaxyTabs } from "../components/GalaxyTabs.js"
import { useTabRouting } from "../composables.js"
@@ -13,13 +12,12 @@ const FEATURES = [
const TABS = {
controllers: "Controllers",
bluetooth: "Bluetooth",
features: "Vehicle Features",
}
export const Vehicle = {
name: "Vehicle",
components: { WheelControls, BluetoothPanel, GalaxySection, GalaxyTabs },
components: { WheelControls, GalaxySection, GalaxyTabs },
data() {
return {
TABS,
@@ -29,7 +27,7 @@ export const Vehicle = {
}
},
setup() {
return useTabRouting("/vehicle", { controllers: "controllers", bluetooth: "bluetooth", features: "features" })
return useTabRouting("/vehicle", { controllers: "controllers", features: "features" })
},
computed: {
featureList() { return this.features },
@@ -71,10 +69,6 @@ export const Vehicle = {
<WheelControls />
</template>
<template v-else-if="tab === 'bluetooth'">
<BluetoothPanel />
</template>
<template v-else>
<GalaxySection title="Vehicle Features" icon="bi-check2-square">
<div style="padding: var(--sp-3); display:grid; gap:8px;">
@@ -52,10 +52,15 @@ def test_device_settings_speed_units_follow_the_vehicle():
assert 'from "/assets/mobile/js/params.js"' in source
assert "resolveVehicleUnitParam" in source
assert "formatNumericParamValue" in source
assert "vehicleSpeedUnit(state.values)" in source
assert "unit_search_terms" in source
assert "Use Metric System" in source
assert "per click" in source
assert "ds-unit-note" not in source
def test_device_settings_supports_vehicle_make_exclusions():
source = _device_settings()
assert "excluded_vehicle_makes" in source
def test_lane_center_offset_can_step_below_zero():
@@ -66,6 +66,15 @@ def test_galaxy_layout_contains_basic_mode_controls():
assert {"AlphaLongitudinalEnabled", "ForceOffroad", "GalaxyDeveloperMode"} <= sections["Developer"].keys()
def test_galaxy_new_ui_is_the_visible_default_choice():
galaxy_default = _params_by_section(_layout())["Developer"]["GalaxyMobileDefault"]
assert _declared_default("GalaxyMobileDefault") == "1"
assert galaxy_default["settings_tier"] == "simple"
assert galaxy_default["label"] == "Use Galaxy (new) by Default"
assert "Galaxy (old)" in galaxy_default["description"]
def test_ford_lateral_controls_are_ford_only_and_galaxy_only():
lateral = _params_by_section(_layout())["Lateral (Steering)"]
ford_keys = {
@@ -142,6 +151,15 @@ def test_speed_settings_follow_vehicle_units_with_one_unit_steps():
assert params["PulseGlideSpeedDelta"]["imperial_max"] == 15
def test_cruise_controls_are_split_between_toyota_and_software_cruise():
longitudinal = _params_by_section(_layout())["Longitudinal (Speed & Following)"]
assert longitudinal["CustomCruise"]["excluded_vehicle_makes"] == ["Lexus", "Toyota"]
assert longitudinal["CustomCruiseLong"]["excluded_vehicle_makes"] == ["Lexus", "Toyota"]
assert longitudinal["ReverseCruise"]["vehicle_makes"] == ["Lexus", "Toyota"]
assert _declared_default("ReverseCruise") == "0"
def test_curve_speed_controller_no_lead_toggle_is_nested_under_csc():
csc_no_lead = _params_by_section(_layout())["Longitudinal (Speed & Following)"]["CurveSpeedControllerNoLead"]
@@ -44,6 +44,7 @@ def test_ui_app_shell_files_exist():
"js/views/Tuning.js",
"js/views/Navigation.js",
"js/views/Vehicle.js",
"js/views/Bluetooth.js",
"js/views/SystemTools.js",
]
for rel in required:
@@ -55,6 +56,8 @@ def test_ui_index_wires_vue_and_mount_point():
assert 'id="galaxy-app"' in index
assert 'src="/assets/mobile/js/app.js"' in index
assert '"vue": "/assets/vendor/vue/vue.esm-browser.js"' in index
assert '<title>Galaxy</title>' in index
assert 'apple-mobile-web-app-title" content="Galaxy"' in index
def test_ui_uses_same_backend_endpoints():
@@ -107,7 +110,7 @@ def test_ui_ports_all_tool_views():
"js/views/Recordings.js": ["/api/routes", "getRoutesStream", "getRouteLogs"],
"js/views/Logs.js": ["getErrorLogs", "tmuxSnapshot"],
"js/components/TroubleshootPanel.js": ["getTroubleshoot", "resetTroubleshootSection", "GalaxyConfirm"],
"js/views/Tuning.js": ["LateralTuningPanel", "LongitudinalManeuvers"],
"js/views/Tuning.js": ["LateralTuningPanel"],
"js/views/Navigation.js": ["getNavigation", "setNavigation", "MapsPanel", "NavigationKeysPanel"],
"js/views/ToolEmbed.js": ["/manage_maps", "/manage_navigation_keys"],
"js/views/SystemTools.js": [
@@ -122,7 +125,9 @@ def test_ui_ports_all_tool_views():
for ep in endpoints:
assert ep in src, f"{rel} should use api.{ep}"
vehicle = _read("js/views/Vehicle.js")
assert "WheelControls" in vehicle and "BluetoothPanel" in vehicle and "carFeaturesCheck" in vehicle
bluetooth = _read("js/views/Bluetooth.js")
assert "WheelControls" in vehicle and "BluetoothPanel" not in vehicle and "carFeaturesCheck" in vehicle
assert "BluetoothPanel" in bluetooth
def test_ui_routes_ported_views_natively_no_classic_fallback():
@@ -130,16 +135,16 @@ def test_ui_routes_ported_views_natively_no_classic_fallback():
shell = _read("js/components/AppShell.js")
tools = _read("js/views/Tools.js")
for view in ["Recordings", "Logs", "Tuning", "Navigation", "Vehicle", "SystemTools"]:
for view in ["Recordings", "Logs", "Tuning", "Navigation", "Vehicle", "Bluetooth", "SystemTools"]:
assert view in app, f"app.js should register {view}"
# Ported routes must resolve natively in the Vue app (zero /classic redirect).
for route in ["/recordings", "/logs", "/tuning", "/navigation", "/vehicle", "/system"]:
for route in ["/recordings", "/logs", "/tuning", "/navigation", "/vehicle", "/bluetooth", "/system"]:
assert route in shell, f"AppShell should route {route} natively"
assert route in app, f"app.js should resolve {route} natively"
# Tools grid routes the native categories (Recordings lives in the bottom nav
# and is intentionally absent from the Tools page).
for tool in ["/tuning", "/logs", "/navigation", "/vehicle", "/system"]:
for tool in ["/tuning", "/logs", "/navigation", "/vehicle", "/bluetooth", "/system"]:
assert tool in tools, f"Tools grid should route {tool} natively"
assert "/cameras" in tools, "Tools grid should route the camera hub natively"
assert "/manage_v_asm" not in tools and "/manage_pip_sidecam" not in tools
@@ -204,7 +209,7 @@ def test_ui_speed_units_follow_the_vehicle():
assert "displayParam" in card and "formatNumericParamValue" in card
assert "sliderStepDisplay" in card and "Step:" in card
assert ':values="values"' in settings
assert "Use Metric System" in settings
assert "gx-unit-note" not in settings
def test_ui_centralizes_api_and_uses_composables():
@@ -226,10 +231,10 @@ def test_ui_centralizes_api_and_uses_composables():
def test_ui_schema_driven_param_engine_reused():
tuning = _read("js/views/Tuning.js")
assert "GalaxyEmbed" not in tuning and 'src="/tuning"' not in tuning, "Tuning must be native, not a classic embed"
assert "LateralTuningPanel" in tuning and "LongitudinalManeuvers" in tuning
assert "LateralTuningPanel" in tuning and "LongitudinalManeuvers" not in tuning
vehicle = _read("js/views/Vehicle.js")
assert "ParamSections" not in vehicle, "Vehicle must not render redundant toggles"
assert "WheelControls" in vehicle and "BluetoothPanel" in vehicle
assert "WheelControls" in vehicle and "BluetoothPanel" not in vehicle
assert "GalaxySection" in vehicle
engine = _read("js/components/ParamSections.js")
assert "SettingTree" in engine
@@ -285,6 +290,7 @@ def test_ui_has_bottom_navigation_and_drawer():
assert "gx-drawer" in shell
assert "gx-appbar" in shell
assert "Search toggles" in shell
assert ">Galaxy</span>" in shell
def test_ui_search_visible_on_mobile_and_content_full_width():
@@ -342,8 +348,6 @@ def test_ui_galaxy_background_is_css_only_and_lightweight():
def test_galaxy_py_serves_classic_at_root_and_new_ui_at_mobile():
source = GALAXY_PY.read_text(encoding="utf-8")
# The classic Galaxy SPA is the default landing at / (original behaviour) unless
# the "New Galaxy by Default" (GalaxyMobileDefault) toggle is enabled.
assert '@app.route("/", methods=["GET"])' in source
assert 'render_template("index.html")' in source
assert 'params.get_bool("GalaxyMobileDefault")' in source
@@ -358,7 +362,8 @@ def test_galaxy_py_serves_classic_at_root_and_new_ui_at_mobile():
def test_ui_manifest_is_valid_pwa_manifest():
manifest = json.loads((UI_ROOT / "manifest.json").read_text(encoding="utf-8"))
assert manifest["display"] == "standalone"
assert manifest["name"]
assert manifest["name"] == "Galaxy"
assert manifest["short_name"] == "Galaxy"
assert manifest["icons"]
assert "start_url" not in manifest
@@ -417,7 +422,6 @@ def test_ui_all_remaining_classic_tools_native_no_embed():
# Standalone native views + their routes.
native = {
"/sentry": "Sentry",
"/manage_models": "ModelManager",
"/plots": "Plots",
"/testing_ground": "TestingGround",
@@ -431,6 +435,10 @@ def test_ui_all_remaining_classic_tools_native_no_embed():
assert src, f"missing view: {view}"
assert "GalaxyEmbed" not in src and "fetch(" not in src, f"{view} should be native with no raw fetch"
assert '"/sentry": Cameras' in app
sentry = _read("js/views/Sentry.js")
assert "GalaxyEmbed" not in sentry and "fetch(" not in sentry
# Navigation maps + App Keys and Tuning lateral are native tabs now.
nav = _read("js/views/Navigation.js")
assert "GalaxyEmbed" not in nav and "MapsPanel" in nav and "NavigationKeysPanel" in nav
@@ -466,8 +474,9 @@ def test_ui_cameras_hub_vasm_and_pip_native_no_embed():
assert "/cameras" in app and "Cameras" in app, "app.js should register the camera hub"
assert "/cameras" in store, "store NATIVE_ROOTS should include /cameras"
assert "GalaxyEmbed" not in cameras and "fetch(" not in cameras
assert "Vasm" in cameras and "Pip" in cameras, "camera hub should embed V-ASM and PiP"
assert "Sentry" in cameras and "Vasm" in cameras and "Pip" in cameras, "camera hub should embed Sentry, V-ASM, and PiP"
assert "GalaxyTabs" in cameras
assert cameras.index('sentry: "Sentry Mode"') < cameras.index('vasm: "V-ASM Spot Monitor"')
# Removed standalone pages are no longer routed or listed as native roots.
for route in ["/manage_v_asm", "/manage_pip_sidecam"]:
@@ -487,6 +496,40 @@ def test_ui_cameras_hub_vasm_and_pip_native_no_embed():
assert method in api, f"api.js should expose {method}"
def test_ui_mobile_polish_regressions():
system = _read("js/views/SystemTools.js")
css = _read("css/material.css")
assert 'button v-if="updateAvailable"' in system
assert "checkedForUpdates && !!this.fastStatus?.updateAvailable" in system
assert "gx-update-progress__fill" in system
assert "linear-gradient(90deg, #5ec8c8 0%, #8b6cc5 100%)" in css
bluetooth = _read("js/components/BluetoothPanel.js")
assert "methods: {\n address," in bluetooth
logs = _read("js/views/Logs.js")
assert logs.index('troubleshoot: "Troubleshoot"') < logs.index('errors: "Error Logs"') < logs.index('tmux: "Tmux Live Log"')
troubleshoot = _read("js/components/TroubleshootPanel.js")
assert "gx-diagnostic-row--changed" in troubleshoot and ".gx-row.gx-diagnostic-row--changed" in css
recordings = _read("js/views/Recordings.js")
galaxy = _read("js/views/Galaxy.js")
assert "bandwidth reasons" in recordings and "status?.lanIp" in recordings
assert "status?.lanIp" in galaxy
assert ':href="localUrl"' in recordings and ':href="localUrl"' in galaxy
home = _read("js/views/Home.js")
home_css = _read("css/home.css")
assert "backgroundImage: modelView.style" in home
assert "display: flex" in home_css and "flex-direction: column" in home_css
tuning = _read("js/views/Tuning.js")
classic_sidebar = (REPO_ROOT / "starpilot/system/the_galaxy/assets/components/sidebar.js").read_text(encoding="utf-8")
c4_developer = (REPO_ROOT / "selfdrive/ui/layouts/settings/developer.py").read_text(encoding="utf-8")
assert "LongitudinalManeuvers" not in tuning and "Long Maneuvers" not in classic_sidebar
assert 'tr("Longitudinal Maneuver Mode")' not in c4_developer
def _node_exe():
candidates = [
shutil.which("node"),
+2 -2
View File
@@ -5305,12 +5305,12 @@ def setup(app):
def mobile_manifest():
manifest_path = Path(app.static_folder) / "mobile" / "manifest.json"
if not manifest_path.is_file():
return jsonify({"error": "Big Dipper manifest not found"}), 404
return jsonify({"error": "Galaxy manifest not found"}), 404
try:
manifest_data = json.loads(manifest_path.read_text(encoding="utf-8"))
except (OSError, ValueError, json.JSONDecodeError):
return jsonify({"error": "Big Dipper manifest is invalid"}), 500
return jsonify({"error": "Galaxy manifest is invalid"}), 500
slug = _read_galaxy_text(_get_galaxy_dir() / "glxyslug")
if re.fullmatch(r"[A-Za-z0-9]{16}", slug):
+30 -9
View File
@@ -22,7 +22,7 @@ DEVELOPER_METRIC_DISPLAY_KEYS = (
)
DEVICE_SHUTDOWN_KEY = "DeviceShutdown"
CAMERA_VIEW_KEY = "CameraView"
REVERSE_CRUISE_KEY = "ReverseCruise"
GALAXY_NEW_DEFAULT_KEY = "GalaxyMobileDefault"
DEFAULT_STEER_KP = 0.6
LEGACY_STEER_KP = 0.7
@@ -39,7 +39,8 @@ LANE_CHANGE_SMOOTHING_MIGRATION_MARKER = ".starpilot_lane_change_smoothing_defau
SPEED_LIMIT_VISIBILITY_MIGRATION_MARKER = ".starpilot_speed_limit_visibility_v1"
DEVICE_SHUTDOWN_HOURS_MIGRATION_MARKER = ".starpilot_device_shutdown_hours_v1"
CAMERA_VIEW_DEFAULT_MIGRATION_MARKER = ".starpilot_camera_view_default_v1"
REVERSE_CRUISE_REMOVAL_MIGRATION_MARKER = ".starpilot_remove_reverse_cruise_v1"
REVERSE_CRUISE_RESTORE_MIGRATION_MARKER = ".starpilot_restore_reverse_cruise_v1"
GALAXY_NEW_DEFAULT_MIGRATION_MARKER = ".starpilot_galaxy_new_default_v1"
MARKER_DIRNAME = ".starpilot_param_migrations"
LATERAL_METHOD_PARAM_SUFFIXES = (
@@ -147,8 +148,12 @@ def _camera_view_default_marker_path(params: ParamsLike) -> Path:
return _marker_dir_path(params) / CAMERA_VIEW_DEFAULT_MIGRATION_MARKER
def _reverse_cruise_removal_marker_path(params: ParamsLike) -> Path:
return _marker_dir_path(params) / REVERSE_CRUISE_REMOVAL_MIGRATION_MARKER
def _reverse_cruise_restore_marker_path(params: ParamsLike) -> Path:
return _marker_dir_path(params) / REVERSE_CRUISE_RESTORE_MIGRATION_MARKER
def _galaxy_new_default_marker_path(params: ParamsLike) -> Path:
return _marker_dir_path(params) / GALAXY_NEW_DEFAULT_MIGRATION_MARKER
def _marker_dir_path(params: ParamsLike) -> Path:
@@ -332,12 +337,24 @@ def _apply_camera_view_default_migration(params: ParamsLike, marker: Path) -> No
marker.touch()
def _remove_reverse_cruise_param(params: ParamsLike, marker: Path) -> None:
def _restore_reverse_cruise_param(params: ParamsLike, marker: Path) -> None:
if marker.exists():
return
marker.parent.mkdir(parents=True, exist_ok=True)
Path(params.get_param_path(REVERSE_CRUISE_KEY)).unlink(missing_ok=True)
if (not _param_file_exists(params, "ReverseCruise") and
_approx_equal(params.get_float("CustomCruise"), 5.0) and
_approx_equal(params.get_float("CustomCruiseLong"), 1.0)):
params.put_bool("ReverseCruise", True)
marker.touch()
def _enable_galaxy_new_default(params: ParamsLike, marker: Path) -> None:
if marker.exists():
return
marker.parent.mkdir(parents=True, exist_ok=True)
params.put_bool(GALAXY_NEW_DEFAULT_KEY, True)
marker.touch()
@@ -352,7 +369,8 @@ def apply_launch_param_migrations(params: ParamsLike, marker_path: Path | None =
speed_limit_visibility_marker_path: Path | None = None,
device_shutdown_hours_marker_path: Path | None = None,
camera_view_default_marker_path: Path | None = None,
reverse_cruise_removal_marker_path: Path | None = None) -> None:
reverse_cruise_restore_marker_path: Path | None = None,
galaxy_new_default_marker_path: Path | None = None) -> None:
_apply_legacy_launch_param_migrations(params, marker_path or _default_marker_path(params))
# Keep branch-default rollout on its own marker so older installs that already
# have the legacy marker still receive this one-time param reset.
@@ -384,8 +402,11 @@ def apply_launch_param_migrations(params: ParamsLike, marker_path: Path | None =
_apply_camera_view_default_migration(
params, camera_view_default_marker_path or _camera_view_default_marker_path(params)
)
_remove_reverse_cruise_param(
params, reverse_cruise_removal_marker_path or _reverse_cruise_removal_marker_path(params)
_restore_reverse_cruise_param(
params, reverse_cruise_restore_marker_path or _reverse_cruise_restore_marker_path(params)
)
_enable_galaxy_new_default(
params, galaxy_new_default_marker_path or _galaxy_new_default_marker_path(params)
)
+1 -1
View File
@@ -71,7 +71,7 @@ STARPILOT_DEFAULT_MODEL_MIGRATION_FLAG = Path("/data") / "starpilot_default_mode
STARPILOT_CE_MODEL_STOP_TIME_MIGRATION_FLAG = Path("/data") / "starpilot_ce_model_stop_time_v2"
STARPILOT_LEGACY_CACHE_MARKER_KEYS = ("RemapCancelToDistance",)
STARPILOT_REMOVED_PARAM_KEYS = (
"CoastUpToLeads", "HumanAcceleration", "HumanFollowing", "PrioritizeSmoothFollowing", "ReverseCruise",
"CoastUpToLeads", "HumanAcceleration", "HumanFollowing", "PrioritizeSmoothFollowing",
)
LEGACY_CARMODEL_MIGRATIONS = {
"CHEVROLET_BOLT_CC_2019_2021": "CHEVROLET_BOLT_CC_2018_2021",
@@ -7,6 +7,7 @@ from openpilot.system.manager.launch_param_migrations import (
DEFAULT_CAMERA_VIEW,
DEVELOPER_METRIC_DISPLAY_KEYS,
DEVELOPER_METRIC_DISPLAY_MIGRATION_MARKER,
GALAXY_NEW_DEFAULT_MIGRATION_MARKER,
DEFAULT_LANE_CHANGE_SMOOTHING,
DEFAULT_STEER_KP,
DEVICE_SHUTDOWN_HOURS_MIGRATION_MARKER,
@@ -14,7 +15,7 @@ from openpilot.system.manager.launch_param_migrations import (
LAUNCH_PARAM_MIGRATION_MARKER,
LATERAL_METHOD_REBRAND_MIGRATION_MARKER,
MARKER_DIRNAME,
REVERSE_CRUISE_REMOVAL_MIGRATION_MARKER,
REVERSE_CRUISE_RESTORE_MIGRATION_MARKER,
STANDARD_ACCELERATION_PROFILE,
SPEED_LIMIT_VISIBILITY_MIGRATION_MARKER,
LEGACY_UI_SELECTION_MIGRATION_MARKER,
@@ -178,14 +179,42 @@ def test_apply_launch_param_migrations_preserves_custom_camera_view(tmp_path):
assert params.get_int("CameraView") == 0
def test_apply_launch_param_migrations_removes_reverse_cruise_param(tmp_path):
def test_apply_launch_param_migrations_restores_reverse_cruise_from_swapped_intervals(tmp_path):
params = FileBackedFakeParams(tmp_path / "params")
params.put_bool("ReverseCruise", True)
params.put_float("CustomCruise", 5.0)
params.put_float("CustomCruiseLong", 1.0)
apply_launch_param_migrations(params)
assert not Path(params.get_param_path("ReverseCruise")).exists()
assert marker_path(tmp_path, REVERSE_CRUISE_REMOVAL_MIGRATION_MARKER).is_file()
assert params.get_bool("ReverseCruise")
assert marker_path(tmp_path, REVERSE_CRUISE_RESTORE_MIGRATION_MARKER).is_file()
def test_apply_launch_param_migrations_preserves_explicit_reverse_cruise_choice(tmp_path):
params = FileBackedFakeParams(tmp_path / "params")
params.put_float("CustomCruise", 5.0)
params.put_float("CustomCruiseLong", 1.0)
params.put_bool("ReverseCruise", False)
apply_launch_param_migrations(params)
assert not params.get_bool("ReverseCruise")
def test_apply_launch_param_migrations_enables_galaxy_new_default_once(tmp_path):
params = FileBackedFakeParams(tmp_path / "params")
params.put_bool("GalaxyMobileDefault", False)
apply_launch_param_migrations(params)
assert params.get_bool("GalaxyMobileDefault")
marker = marker_path(tmp_path, GALAXY_NEW_DEFAULT_MIGRATION_MARKER)
assert marker.is_file()
params.put_bool("GalaxyMobileDefault", False)
apply_launch_param_migrations(params)
assert not params.get_bool("GalaxyMobileDefault")
def test_apply_launch_param_migrations_applies_branch_defaults_for_existing_installs(tmp_path):
+1 -3
View File
@@ -397,7 +397,6 @@ class TestManager:
params_cache = FileBackedFakeParams(tmp_path / "cache", {
"HumanFollowing": False,
"PrioritizeSmoothFollowing": True,
"ReverseCruise": True,
})
manager.cleanup_removed_starpilot_params(params, params_cache)
@@ -405,10 +404,9 @@ class TestManager:
assert not Path(params.get_param_path("CoastUpToLeads")).exists()
assert not Path(params.get_param_path("HumanAcceleration")).exists()
assert not Path(params.get_param_path("HumanFollowing")).exists()
assert not Path(params.get_param_path("ReverseCruise")).exists()
assert params.get_bool("ReverseCruise")
assert not Path(params_cache.get_param_path("HumanFollowing")).exists()
assert not Path(params_cache.get_param_path("PrioritizeSmoothFollowing")).exists()
assert not Path(params_cache.get_param_path("ReverseCruise")).exists()
def test_migrate_legacy_starpilot_params_cache_copies_marker_sources(self, tmp_path, monkeypatch):
monkeypatch.setattr(manager, "STARPILOT_PARAMS_CACHE_MIGRATION_FLAG", tmp_path / "starpilot_params_cache_v1")