mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-13 03:43:48 +08:00
@@ -632,7 +632,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"SafeModeBackup", {PERSISTENT, JSON, "{}", "{}"}},
|
||||
{"SetSpeedLimit", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"SetSpeedOffset", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"ShowBrakeStatus", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ShowCEMStatus", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ShowCCMStatus", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"ShowCPU", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
|
||||
@@ -349,20 +349,7 @@ def _create_volt_cc_spam_command(CS, actuators, ms_convert, lead_visible):
|
||||
deadband_mph = VOLT_CC_LEAD_REQUEST_DEADBAND_MPH if lead_visible else VOLT_CC_FREE_REQUEST_DEADBAND_MPH
|
||||
request_deadband = deadband_mph * (CV.MPH_TO_KPH if ms_convert == CV.MS_TO_KPH else 1.0)
|
||||
|
||||
target_setpoint = None
|
||||
v_cruise_kph = float(getattr(CS.out, "vCruise", 0.0))
|
||||
if 0.0 < v_cruise_kph < 255.0:
|
||||
is_metric = ms_convert == CV.MS_TO_KPH
|
||||
target_setpoint = int(round(v_cruise_kph if is_metric else v_cruise_kph * CV.KPH_TO_MPH))
|
||||
|
||||
moving_toward_target = target_setpoint is not None and (
|
||||
(accel > 0.0 and speed_setpoint < target_setpoint) or
|
||||
(accel < 0.0 and speed_setpoint > target_setpoint)
|
||||
)
|
||||
if target_setpoint is not None and accel > 0.0 and speed_setpoint >= target_setpoint:
|
||||
return CruiseButtons.INIT, float("inf")
|
||||
|
||||
if not moving_toward_target and abs(requested_setpoint - speed_setpoint) <= request_deadband:
|
||||
if abs(requested_setpoint - speed_setpoint) <= request_deadband:
|
||||
return CruiseButtons.INIT, float("inf")
|
||||
|
||||
if accel == 0.0:
|
||||
|
||||
@@ -947,7 +947,7 @@ class TestGMCarController:
|
||||
|
||||
assert len(msgs) == 1
|
||||
|
||||
def test_volt_cc_redneck_does_not_raise_stock_setpoint_above_max(self):
|
||||
def test_volt_cc_redneck_holds_when_pseudo_speed_request_is_within_deadband(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(2.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
|
||||
cs = SimpleNamespace(
|
||||
@@ -960,7 +960,7 @@ class TestGMCarController:
|
||||
buttons_counter=2,
|
||||
out=SimpleNamespace(
|
||||
vEgo=100.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=100.0 * CV.KPH_TO_MS),
|
||||
cruiseState=SimpleNamespace(speed=99.0 * CV.KPH_TO_MS),
|
||||
vCruise=100.0,
|
||||
),
|
||||
)
|
||||
@@ -970,133 +970,33 @@ class TestGMCarController:
|
||||
)
|
||||
|
||||
assert msgs == []
|
||||
assert controller.apply_speed == 100
|
||||
|
||||
def test_volt_cc_redneck_tracks_max_inside_request_deadband(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
|
||||
cs = SimpleNamespace(
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
flags=GMFlags.NO_CAMERA.value,
|
||||
networkLocation=structs.CarParams.NetworkLocation.gateway,
|
||||
minEnableSpeed=0.0,
|
||||
),
|
||||
buttons_counter=2,
|
||||
out=SimpleNamespace(
|
||||
vEgo=52.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=52.0 * CV.KPH_TO_MS),
|
||||
vCruise=60.0,
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=0.1), SimpleNamespace(is_metric=True),
|
||||
)
|
||||
|
||||
assert len(msgs) == 1
|
||||
assert controller.apply_speed == 53
|
||||
|
||||
def test_volt_cc_redneck_tracks_max_down_inside_request_deadband(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
|
||||
cs = SimpleNamespace(
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
flags=GMFlags.NO_CAMERA.value,
|
||||
networkLocation=structs.CarParams.NetworkLocation.gateway,
|
||||
minEnableSpeed=0.0,
|
||||
),
|
||||
buttons_counter=2,
|
||||
out=SimpleNamespace(
|
||||
vEgo=68.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=68.0 * CV.KPH_TO_MS),
|
||||
vCruise=60.0,
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=-0.1), SimpleNamespace(is_metric=True),
|
||||
)
|
||||
|
||||
assert len(msgs) == 1
|
||||
assert controller.apply_speed == 67
|
||||
|
||||
def test_volt_cc_redneck_holds_small_decel_request_at_max(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
|
||||
cs = SimpleNamespace(
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
flags=GMFlags.NO_CAMERA.value,
|
||||
networkLocation=structs.CarParams.NetworkLocation.gateway,
|
||||
minEnableSpeed=0.0,
|
||||
),
|
||||
buttons_counter=2,
|
||||
out=SimpleNamespace(
|
||||
vEgo=100.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=100.0 * CV.KPH_TO_MS),
|
||||
vCruise=100.0,
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=-0.1), SimpleNamespace(is_metric=True),
|
||||
)
|
||||
|
||||
assert msgs == []
|
||||
assert controller.apply_speed == 100
|
||||
|
||||
def test_volt_cc_redneck_holds_medium_decel_request_at_max_without_lead(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
|
||||
cs = SimpleNamespace(
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
flags=GMFlags.NO_CAMERA.value,
|
||||
networkLocation=structs.CarParams.NetworkLocation.gateway,
|
||||
minEnableSpeed=0.0,
|
||||
),
|
||||
buttons_counter=2,
|
||||
out=SimpleNamespace(
|
||||
vEgo=100.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=100.0 * CV.KPH_TO_MS),
|
||||
vCruise=100.0,
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=-0.5), SimpleNamespace(is_metric=True), lead_visible=False,
|
||||
)
|
||||
|
||||
assert msgs == []
|
||||
assert controller.apply_speed == 100
|
||||
|
||||
def test_volt_cc_redneck_brakes_for_lead_inside_free_road_deadband(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
|
||||
cs = SimpleNamespace(
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
flags=GMFlags.NO_CAMERA.value,
|
||||
networkLocation=structs.CarParams.NetworkLocation.gateway,
|
||||
minEnableSpeed=0.0,
|
||||
),
|
||||
buttons_counter=2,
|
||||
out=SimpleNamespace(
|
||||
vEgo=100.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=100.0 * CV.KPH_TO_MS),
|
||||
vCruise=100.0,
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=-0.5), SimpleNamespace(is_metric=True), lead_visible=True,
|
||||
)
|
||||
|
||||
assert len(msgs) == 1
|
||||
assert controller.apply_speed == 99
|
||||
|
||||
def test_volt_cc_redneck_uses_smaller_request_deadband_with_lead(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(2.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
|
||||
cs = SimpleNamespace(
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
flags=GMFlags.NO_CAMERA.value,
|
||||
networkLocation=structs.CarParams.NetworkLocation.gateway,
|
||||
minEnableSpeed=0.0,
|
||||
),
|
||||
buttons_counter=2,
|
||||
out=SimpleNamespace(
|
||||
vEgo=100.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=99.0 * CV.KPH_TO_MS),
|
||||
vCruise=100.0,
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=0.5), SimpleNamespace(is_metric=True), lead_visible=True,
|
||||
)
|
||||
|
||||
assert len(msgs) == 1
|
||||
assert controller.apply_speed == 100
|
||||
|
||||
def test_volt_cc_redneck_accelerates_when_pseudo_speed_request_exceeds_deadband(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(0.1 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
|
||||
@@ -1143,7 +1043,7 @@ class TestGMCarController:
|
||||
out=SimpleNamespace(
|
||||
vEgo=50.7 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=49.0 * CV.KPH_TO_MS),
|
||||
vCruise=49.0,
|
||||
vCruise=50.0,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ from openpilot.selfdrive.ui.onroad.starpilot.rivian_lateral_mode import rivian_l
|
||||
from openpilot.selfdrive.ui.mici.onroad.speed_limit_utils import resolve_display_speed_limit_ms
|
||||
from openpilot.selfdrive.ui.onroad.starpilot.navigation_card import NavigationCardRenderer
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus
|
||||
from openpilot.selfdrive.ui.onroad.exp_button import get_wheel_tint
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.utils import draw_circle_gradient_compat
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
@@ -195,11 +194,7 @@ class HudRenderer(Widget):
|
||||
controls_state = sm['controlsState']
|
||||
car_state = sm['carState']
|
||||
rivian_lateral_mode.update()
|
||||
self._wheel_tint = get_wheel_tint(
|
||||
getattr(car_state, "brakePressed", False),
|
||||
rivian_lateral_mode.wheel_tint,
|
||||
ui_state.ui_params.get_bool("ShowBrakeStatus"),
|
||||
)
|
||||
self._wheel_tint = rivian_lateral_mode.wheel_tint
|
||||
|
||||
v_cruise_cluster = car_state.vCruiseCluster
|
||||
set_speed = (
|
||||
|
||||
@@ -11,13 +11,6 @@ from openpilot.starpilot.common.experimental_state import (
|
||||
)
|
||||
|
||||
|
||||
BRAKE_WHEEL_COLOR = rl.Color(255, 0, 0, 255)
|
||||
|
||||
|
||||
def get_wheel_tint(brake_pressed: bool, mode_tint: rl.Color | None, brake_status_enabled: bool) -> rl.Color | None:
|
||||
return BRAKE_WHEEL_COLOR if brake_status_enabled and brake_pressed else mode_tint
|
||||
|
||||
|
||||
class ExpButton(Widget):
|
||||
def __init__(self, button_size: int, icon_size: int):
|
||||
super().__init__()
|
||||
@@ -109,13 +102,8 @@ class ExpButton(Widget):
|
||||
texture = self._txt_exp if exp_mode else self._txt_wheel
|
||||
color = self._white_color
|
||||
tint = None
|
||||
wheel_tint = get_wheel_tint(
|
||||
getattr(ui_state.sm["carState"], "brakePressed", False),
|
||||
self.wheel_tint,
|
||||
self._params.get_bool("ShowBrakeStatus"),
|
||||
)
|
||||
if wheel_tint is not None:
|
||||
tint = rl.Color(wheel_tint.r, wheel_tint.g, wheel_tint.b, self._white_color.a)
|
||||
if self.wheel_tint is not None:
|
||||
tint = rl.Color(self.wheel_tint.r, self.wheel_tint.g, self.wheel_tint.b, self._white_color.a)
|
||||
|
||||
rl.draw_circle(center_x, center_y, self._rect.width / 2, self._bg_color)
|
||||
if tint is not None:
|
||||
|
||||
@@ -41,8 +41,6 @@ from openpilot.selfdrive.ui.onroad.starpilot.aethergauge import (
|
||||
_is_lead,
|
||||
_is_stop_light,
|
||||
_lead_data,
|
||||
_to_display_distance,
|
||||
_to_display_speed,
|
||||
)
|
||||
from openpilot.starpilot.common.experimental_state import CEStatus
|
||||
|
||||
@@ -52,7 +50,6 @@ aethergauge.ui_state = mock_ui_state
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_ui_state():
|
||||
mock_ui_state.sm.reset()
|
||||
mock_ui_state.is_metric = False
|
||||
mock_ui_state.conditional_status = CEStatus["OFF"]
|
||||
mock_ui_state.starpilot_toggles.update({
|
||||
"conditional_experimental_mode": True,
|
||||
@@ -138,20 +135,6 @@ def test_is_stop_light():
|
||||
assert not _is_stop_light()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("is_metric", "expected_distance", "expected_speed"),
|
||||
[
|
||||
(False, (33, "ft"), (22, "mph")),
|
||||
(True, (10, "m"), (36, "km/h")),
|
||||
],
|
||||
)
|
||||
def test_aether_gauge_uses_display_units(is_metric, expected_distance, expected_speed):
|
||||
mock_ui_state.is_metric = is_metric
|
||||
|
||||
assert _to_display_distance(10.0) == expected_distance
|
||||
assert _to_display_speed(10.0) == expected_speed
|
||||
|
||||
|
||||
def test_is_curve_speed_follows_csc_activation_without_mode_gate(monkeypatch):
|
||||
monkeypatch.setattr(aethergauge, "_csc_state", lambda: {"active": True, "curvature": 0.002})
|
||||
assert _is_curve_speed()
|
||||
|
||||
@@ -293,32 +293,3 @@ def test_non_mici_wheel_icon_uses_rivian_tint(monkeypatch):
|
||||
assert len(draws["textures"]) == 1
|
||||
texture_color = draws["textures"][0][-1]
|
||||
assert (texture_color.r, texture_color.g, texture_color.b, texture_color.a) == (0x4D, 0x9D, 0xFF, 255)
|
||||
|
||||
|
||||
def test_non_mici_wheel_icon_turns_red_when_brakes_are_pressed(monkeypatch):
|
||||
module, draws = load_exp_button(monkeypatch)
|
||||
button = module.ExpButton(192, 144)
|
||||
button.wheel_tint = FakeColor(0x4D, 0x9D, 0xFF, 255)
|
||||
module.ui_state.ui_params.get_bool = lambda key, *args, **kwargs: key == "ShowBrakeStatus"
|
||||
module.ui_state.sm["carState"].brakePressed = True
|
||||
button._update_state()
|
||||
|
||||
button._render(FakeRectangle(0, 0, 192, 192))
|
||||
|
||||
assert len(draws["textures"]) == 1
|
||||
texture_color = draws["textures"][0][-1]
|
||||
assert (texture_color.r, texture_color.g, texture_color.b, texture_color.a) == (255, 0, 0, 255)
|
||||
|
||||
|
||||
def test_non_mici_wheel_icon_brake_tint_is_disabled_by_default(monkeypatch):
|
||||
module, draws = load_exp_button(monkeypatch)
|
||||
button = module.ExpButton(192, 144)
|
||||
button.wheel_tint = FakeColor(0x4D, 0x9D, 0xFF, 255)
|
||||
module.ui_state.sm["carState"].brakePressed = True
|
||||
button._update_state()
|
||||
|
||||
button._render(FakeRectangle(0, 0, 192, 192))
|
||||
|
||||
assert len(draws["textures"]) == 1
|
||||
texture_color = draws["textures"][0][-1]
|
||||
assert (texture_color.r, texture_color.g, texture_color.b, texture_color.a) == (0x4D, 0x9D, 0xFF, 255)
|
||||
|
||||
@@ -35,19 +35,9 @@ STEER_DT = CarControllerParams.STEER_STEP * DT_CTRL
|
||||
CURVATURE_LOOKAHEAD_MIN = 0.20
|
||||
CURVATURE_LOOKAHEAD_MAX = 0.40
|
||||
MACH_E_TURN_IN_LOOKAHEAD_EXTRA = 0.80
|
||||
MACH_E_LOW_SPEED_TURN_IN_LOOKAHEAD_EXTRA = 1.60
|
||||
MACH_E_LOW_SPEED_TURN_IN_START_SPEED = 2.0
|
||||
MACH_E_LOW_SPEED_TURN_IN_FULL_SPEED = 3.0
|
||||
MACH_E_LOW_SPEED_TURN_IN_MAX_SPEED = 9.0
|
||||
MACH_E_LOW_SPEED_TURN_IN_FADE_SPEED = 12.0
|
||||
MACH_E_TURN_IN_MIN_CURVATURE = 0.002
|
||||
MACH_E_TURN_IN_FULL_CURVATURE = 0.008
|
||||
MACH_E_TURN_IN_LAG_CURVATURE = 0.006
|
||||
MACH_E_DIRECTION_CHANGE_MIN_SPEED = 9.0
|
||||
MACH_E_DIRECTION_CHANGE_MIN_PREVIEW_CURVATURE = 0.0005
|
||||
MACH_E_DIRECTION_CHANGE_FULL_PREVIEW_CURVATURE = 0.002
|
||||
MACH_E_DIRECTION_CHANGE_MIN_LAG_CURVATURE = 0.0008
|
||||
MACH_E_DIRECTION_CHANGE_FULL_LAG_CURVATURE = 0.0015
|
||||
FORD_CURVATURE_LOOKAHEAD = {
|
||||
CAR.FORD_EXPLORER_MK6: 0.20,
|
||||
}
|
||||
@@ -177,11 +167,10 @@ class FordLateralController:
|
||||
def _current_curvature(CS) -> float:
|
||||
return -CS.out.yawRate / max(CS.out.vEgoRaw, 0.1)
|
||||
|
||||
def _blend_and_scale(self, desired: float, predicted: float, v_ego: float, current: float = 0.0,
|
||||
allow_opposite_preview: bool = False) -> tuple[float, int]:
|
||||
def _blend_and_scale(self, desired: float, predicted: float, v_ego: float, current: float = 0.0) -> tuple[float, int]:
|
||||
blend = float(np.interp(abs(desired), [0.0, 0.001], [self.curvature_blend_low, self.curvature_blend_high]))
|
||||
if self.CP.carFingerprint in FORD_CONSERVATIVE_PREVIEW_CARS:
|
||||
if desired * predicted <= 0.0 and not allow_opposite_preview:
|
||||
if desired * predicted <= 0.0:
|
||||
blend = 0.0
|
||||
elif current * predicted > 0.0 and abs(current) > abs(desired) and abs(predicted) > abs(desired):
|
||||
blend *= abs(desired) / abs(predicted)
|
||||
@@ -216,37 +205,6 @@ class FordLateralController:
|
||||
))
|
||||
return curvature_weight * lag_weight
|
||||
|
||||
@staticmethod
|
||||
def _turn_in_lookahead_extra(v_ego: float) -> float:
|
||||
return float(np.interp(
|
||||
v_ego,
|
||||
[MACH_E_LOW_SPEED_TURN_IN_START_SPEED, MACH_E_LOW_SPEED_TURN_IN_FULL_SPEED,
|
||||
MACH_E_LOW_SPEED_TURN_IN_MAX_SPEED, MACH_E_LOW_SPEED_TURN_IN_FADE_SPEED],
|
||||
[MACH_E_TURN_IN_LOOKAHEAD_EXTRA, MACH_E_LOW_SPEED_TURN_IN_LOOKAHEAD_EXTRA,
|
||||
MACH_E_LOW_SPEED_TURN_IN_LOOKAHEAD_EXTRA, MACH_E_TURN_IN_LOOKAHEAD_EXTRA],
|
||||
))
|
||||
|
||||
def _direction_change_preview_weight(self, desired: float, preview: float, current: float) -> float:
|
||||
if self.CP.carFingerprint not in FORD_CONSERVATIVE_PREVIEW_CARS:
|
||||
return 0.0
|
||||
if desired * preview >= 0.0 or desired * self.desired_curvature_last <= 0.0:
|
||||
return 0.0
|
||||
if (abs(desired) >= abs(self.desired_curvature_last) or desired * current <= 0.0 or
|
||||
abs(current) <= abs(desired)):
|
||||
return 0.0
|
||||
|
||||
preview_weight = float(np.interp(
|
||||
abs(preview),
|
||||
[MACH_E_DIRECTION_CHANGE_MIN_PREVIEW_CURVATURE, MACH_E_DIRECTION_CHANGE_FULL_PREVIEW_CURVATURE],
|
||||
[0.0, 1.0],
|
||||
))
|
||||
lag_weight = float(np.interp(
|
||||
abs(current) - abs(desired),
|
||||
[MACH_E_DIRECTION_CHANGE_MIN_LAG_CURVATURE, MACH_E_DIRECTION_CHANGE_FULL_LAG_CURVATURE],
|
||||
[0.0, 1.0],
|
||||
))
|
||||
return preview_weight * lag_weight
|
||||
|
||||
def _manual_turn(self, CC, CS) -> bool:
|
||||
if not CC.latActive:
|
||||
self.human_turn.reset()
|
||||
@@ -310,30 +268,13 @@ class FordLateralController:
|
||||
lookahead = self._curvature_lookahead()
|
||||
predicted = self._predicted_curvature(v_ego, lookahead)
|
||||
desired = float(actuators.curvature)
|
||||
allow_opposite_preview = False
|
||||
if self.CP.carFingerprint in FORD_CONSERVATIVE_PREVIEW_CARS:
|
||||
direction_change_predicted = self._predicted_curvature(v_ego, lookahead + MACH_E_TURN_IN_LOOKAHEAD_EXTRA)
|
||||
direction_change_weight = 0.0
|
||||
if v_ego > MACH_E_DIRECTION_CHANGE_MIN_SPEED and not CS.out.steeringPressed and not self._lane_change()[0]:
|
||||
direction_change_weight = self._direction_change_preview_weight(desired, direction_change_predicted, current)
|
||||
if direction_change_weight > 0.0:
|
||||
predicted = float(np.interp(direction_change_weight, [0.0, 1.0], [predicted, direction_change_predicted]))
|
||||
allow_opposite_preview = True
|
||||
else:
|
||||
turn_in_predicted = direction_change_predicted
|
||||
turn_in_lookahead_extra = self._turn_in_lookahead_extra(v_ego)
|
||||
if (turn_in_lookahead_extra > MACH_E_TURN_IN_LOOKAHEAD_EXTRA and
|
||||
desired * self.desired_curvature_last >= 0.0 and
|
||||
abs(desired) > abs(self.desired_curvature_last)):
|
||||
low_speed_turn_in_predicted = self._predicted_curvature(v_ego, lookahead + turn_in_lookahead_extra)
|
||||
if (desired * low_speed_turn_in_predicted > 0.0 and
|
||||
abs(low_speed_turn_in_predicted) > abs(turn_in_predicted)):
|
||||
turn_in_predicted = low_speed_turn_in_predicted
|
||||
turn_in_weight = self._turn_in_preview_weight(desired, turn_in_predicted, current)
|
||||
if turn_in_weight > 0.0:
|
||||
turn_in_target = float(np.copysign(max(abs(desired), abs(turn_in_predicted)), desired))
|
||||
predicted = float(np.interp(turn_in_weight, [0.0, 1.0], [predicted, turn_in_target]))
|
||||
requested, precision = self._blend_and_scale(desired, predicted, v_ego, current, allow_opposite_preview)
|
||||
turn_in_predicted = self._predicted_curvature(v_ego, lookahead + MACH_E_TURN_IN_LOOKAHEAD_EXTRA)
|
||||
turn_in_weight = self._turn_in_preview_weight(desired, turn_in_predicted, current)
|
||||
if turn_in_weight > 0.0:
|
||||
turn_in_target = float(np.copysign(max(abs(desired), abs(turn_in_predicted)), desired))
|
||||
predicted = float(np.interp(turn_in_weight, [0.0, 1.0], [predicted, turn_in_target]))
|
||||
requested, precision = self._blend_and_scale(desired, predicted, v_ego, current)
|
||||
self.desired_curvature_last = desired
|
||||
|
||||
if v_ego > 9.0:
|
||||
|
||||
@@ -170,110 +170,6 @@ def test_mach_e_turn_in_preview_is_not_carried_into_unwind(controller):
|
||||
desired=0.008, preview=0.009, current=0.004) == 0.0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("speed,expected", (
|
||||
(1.0, 0.80),
|
||||
(2.0, 0.80),
|
||||
(2.5, 1.20),
|
||||
(3.0, 1.60),
|
||||
(8.0, 1.60),
|
||||
(9.0, 1.60),
|
||||
(10.5, 1.20),
|
||||
(12.0, 0.80),
|
||||
(15.0, 0.80),
|
||||
))
|
||||
def test_mach_e_turn_in_lookahead_extra_fades_by_speed(controller, speed, expected):
|
||||
assert controller._turn_in_lookahead_extra(speed) == pytest.approx(expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sign", (1.0, -1.0))
|
||||
def test_mach_e_direction_change_preview_leads_a_lagging_unwind(controller, sign):
|
||||
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
|
||||
controller.desired_curvature_last = sign * 0.002
|
||||
|
||||
weight = controller._direction_change_preview_weight(
|
||||
desired=sign * 0.0015, preview=-sign * 0.002, current=sign * 0.003)
|
||||
|
||||
assert weight == pytest.approx(1.0)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("desired,preview,current,last", (
|
||||
(0.0015, 0.002, 0.003, 0.002), # no predicted direction change
|
||||
(0.002, -0.002, 0.003, 0.0015), # desired curvature is still rising
|
||||
(0.0015, -0.002, -0.001, 0.002), # vehicle already changed direction
|
||||
(0.0015, -0.002, 0.0022, 0.002), # measured unwind lag is too small
|
||||
))
|
||||
def test_mach_e_direction_change_preview_rejects_unrelated_states(
|
||||
controller, desired, preview, current, last):
|
||||
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
|
||||
controller.desired_curvature_last = last
|
||||
|
||||
assert controller._direction_change_preview_weight(desired, preview, current) == 0.0
|
||||
|
||||
|
||||
def test_mach_e_direction_change_preview_can_cross_the_current_desired_path(controller):
|
||||
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
|
||||
|
||||
requested, _ = controller._blend_and_scale(
|
||||
0.0015, -0.002, 15.0, current=0.003, allow_opposite_preview=True)
|
||||
|
||||
assert requested == pytest.approx(0.0001)
|
||||
|
||||
|
||||
def test_mach_e_direction_change_preview_uses_far_path_when_unwind_lags(controller, monkeypatch):
|
||||
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
|
||||
controller.sm["liveDelay"].lateralDelay = 0.4
|
||||
controller.desired_curvature_last = 0.002
|
||||
blend_inputs = []
|
||||
monkeypatch.setattr(
|
||||
controller, "_predicted_curvature",
|
||||
lambda _v_ego, lookahead: 0.002 if lookahead < 1.0 else -0.002,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
controller, "_blend_and_scale",
|
||||
lambda desired, predicted, v_ego, current, allow_opposite_preview=False:
|
||||
blend_inputs.append((desired, predicted, v_ego, current, allow_opposite_preview)) or (0.0, 1),
|
||||
)
|
||||
|
||||
controller.update(
|
||||
SimpleNamespace(latActive=True), car_state(speed=15.0, curvature=0.003),
|
||||
SimpleNamespace(curvature=0.0015),
|
||||
)
|
||||
|
||||
assert blend_inputs == [(pytest.approx(0.0015), pytest.approx(-0.002), pytest.approx(15.0),
|
||||
pytest.approx(0.003), True)]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("speed,steering_pressed,lane_change", (
|
||||
(8.0, False, False),
|
||||
(9.0, False, False),
|
||||
(15.0, True, False),
|
||||
(15.0, False, True),
|
||||
))
|
||||
def test_mach_e_direction_change_preview_is_bypassed_outside_its_operating_state(
|
||||
controller, monkeypatch, speed, steering_pressed, lane_change):
|
||||
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
|
||||
controller.desired_curvature_last = 0.002
|
||||
monkeypatch.setattr(controller, "_predicted_curvature", lambda _v_ego, lookahead: -0.002)
|
||||
monkeypatch.setattr(controller, "_lane_change", lambda: (lane_change, 2 if lane_change else 0))
|
||||
monkeypatch.setattr(
|
||||
controller, "_direction_change_preview_weight",
|
||||
lambda *_args: pytest.fail("direction-change preview must be bypassed"),
|
||||
)
|
||||
|
||||
controller.update(
|
||||
SimpleNamespace(latActive=True),
|
||||
car_state(speed=speed, curvature=0.003, steering_pressed=steering_pressed),
|
||||
SimpleNamespace(curvature=0.0015),
|
||||
)
|
||||
|
||||
|
||||
def test_non_mach_e_direction_change_preview_is_unchanged(controller):
|
||||
controller.desired_curvature_last = 0.002
|
||||
|
||||
assert controller._direction_change_preview_weight(
|
||||
desired=0.0015, preview=-0.002, current=0.003) == 0.0
|
||||
|
||||
|
||||
def test_mach_e_turn_in_preview_uses_extra_model_horizon(controller, monkeypatch):
|
||||
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
|
||||
controller.sm["liveDelay"].lateralDelay = 0.4
|
||||
@@ -287,49 +183,9 @@ def test_mach_e_turn_in_preview_uses_extra_model_horizon(controller, monkeypatch
|
||||
SimpleNamespace(curvature=0.010),
|
||||
)
|
||||
|
||||
assert lookaheads == [pytest.approx(0.4), pytest.approx(1.2), pytest.approx(2.0)]
|
||||
|
||||
|
||||
def test_mach_e_turn_in_preview_keeps_existing_horizon_above_fade_speed(controller, monkeypatch):
|
||||
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
|
||||
controller.sm["liveDelay"].lateralDelay = 0.4
|
||||
controller.desired_curvature_last = 0.007
|
||||
lookaheads = []
|
||||
monkeypatch.setattr(controller, "_predicted_curvature",
|
||||
lambda _v_ego, lookahead: lookaheads.append(lookahead) or 0.012)
|
||||
|
||||
controller.update(
|
||||
SimpleNamespace(latActive=True), car_state(speed=15.0, curvature=0.002),
|
||||
SimpleNamespace(curvature=0.010),
|
||||
)
|
||||
|
||||
assert lookaheads == [pytest.approx(0.4), pytest.approx(1.2)]
|
||||
|
||||
|
||||
def test_mach_e_low_speed_turn_in_preview_cannot_weaken_existing_preview(controller, monkeypatch):
|
||||
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
|
||||
controller.sm["liveDelay"].lateralDelay = 0.4
|
||||
controller.desired_curvature_last = 0.007
|
||||
blend_inputs = []
|
||||
|
||||
def predicted_curvature(_v_ego, lookahead):
|
||||
return {0.4: 0.006, 1.2: 0.010, 2.0: 0.004}[round(lookahead, 1)]
|
||||
|
||||
monkeypatch.setattr(controller, "_predicted_curvature", predicted_curvature)
|
||||
monkeypatch.setattr(
|
||||
controller, "_blend_and_scale",
|
||||
lambda desired, predicted, v_ego, current, allow_opposite_preview=False:
|
||||
blend_inputs.append((desired, predicted)) or (0.0, 1),
|
||||
)
|
||||
|
||||
controller.update(
|
||||
SimpleNamespace(latActive=True), car_state(speed=8.0, curvature=0.002),
|
||||
SimpleNamespace(curvature=0.008),
|
||||
)
|
||||
|
||||
assert blend_inputs == [(pytest.approx(0.008), pytest.approx(0.010))]
|
||||
|
||||
|
||||
def test_non_mach_e_does_not_request_extra_model_horizon(controller, monkeypatch):
|
||||
controller.sm["liveDelay"].lateralDelay = 0.4
|
||||
lookaheads = []
|
||||
|
||||
@@ -2863,16 +2863,6 @@
|
||||
"parent_key": "CustomUI",
|
||||
"settings_tier": "simple"
|
||||
},
|
||||
{
|
||||
"key": "ShowBrakeStatus",
|
||||
"label": "Show Brake Status",
|
||||
"description": "Tint the on-screen steering-wheel icon red while the car reports that the brake pedal is pressed.",
|
||||
"picker_description": "Tints the on-screen steering-wheel icon red while braking.",
|
||||
"data_type": "bool",
|
||||
"ui_type": "toggle",
|
||||
"galaxy_only": true,
|
||||
"settings_tier": "simple"
|
||||
},
|
||||
{
|
||||
"key": "ModelUI",
|
||||
"label": "Model UI",
|
||||
|
||||
@@ -187,18 +187,12 @@ body {
|
||||
.content {
|
||||
color: var(--main-fg);
|
||||
flex-grow: 1;
|
||||
margin-left: 0;
|
||||
margin-left: var(--sidebar-width, 250px);
|
||||
overflow-y: auto;
|
||||
padding-bottom: var(--padding-xl);
|
||||
padding-left: var(--padding-lg);
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
html.galaxy-sidebar-pinned .content {
|
||||
margin-left: var(--sidebar-width, 250px);
|
||||
}
|
||||
}
|
||||
|
||||
.embedded #sidebar,
|
||||
.embedded #sidebar_shell,
|
||||
.embedded #sidebarUnderlay {
|
||||
@@ -316,7 +310,7 @@ a {
|
||||
/* ――― Snackbar wrapper ――― */
|
||||
#snackbar_wrapper {
|
||||
bottom: var(--snackbar-offset, 30px);
|
||||
left: 0;
|
||||
left: var(--sidebar-width);
|
||||
margin: 0 auto;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
@@ -324,12 +318,6 @@ a {
|
||||
z-index: var(--z-overlay);
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
html.galaxy-sidebar-pinned #snackbar_wrapper {
|
||||
left: var(--sidebar-width);
|
||||
}
|
||||
}
|
||||
|
||||
/* ――― Animations ――― */
|
||||
@keyframes fadein {
|
||||
from {
|
||||
@@ -354,7 +342,7 @@ a {
|
||||
}
|
||||
|
||||
/* ――― Breakpoint overrides ――― */
|
||||
@media only screen and (max-width: 767px) {
|
||||
@media only screen and (max-width: var(--breakpoint-md)) {
|
||||
.content {
|
||||
margin-left: 0;
|
||||
}
|
||||
@@ -447,4 +435,4 @@ a {
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: var(--thumb-color);
|
||||
border-radius: var(--border-radius-sm);
|
||||
}
|
||||
}
|
||||
@@ -251,16 +251,9 @@
|
||||
|
||||
.map-wrapper {
|
||||
height: 100vh;
|
||||
left: 0;
|
||||
left: var(--sidebar-width);
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
html.galaxy-sidebar-pinned .map-wrapper {
|
||||
left: var(--sidebar-width);
|
||||
width: calc(100vw - var(--sidebar-width));
|
||||
}
|
||||
width: calc(100vw - var(--sidebar-width));
|
||||
}
|
||||
|
||||
.mapboxgl-popup-content {
|
||||
|
||||
@@ -16,7 +16,7 @@ import { NavKeys } from "/assets/components/navigation/navigation_keys.js?v=app-
|
||||
import { RouteRecordings } from "/assets/components/recordings/dashcam_routes.js"
|
||||
import { SettingsView } from "/assets/components/settings.js?v=router-cycle-fix-5"
|
||||
import { ScreenRecordings } from "/assets/components/recordings/screen_recordings.js"
|
||||
import { Sidebar } from "/assets/components/sidebar.js?v=sidebar-pin-2"
|
||||
import { Sidebar } from "/assets/components/sidebar.js?v=controllers-nav-1"
|
||||
import { SentryMode } from "/assets/components/tools/sentry.js"
|
||||
import { SpeedLimits } from "/assets/components/tools/speed_limits.js"
|
||||
import { ModelManager } from "/assets/components/tools/model_manager.js?v=20260906a"
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
#menu_button {
|
||||
align-items: center;
|
||||
background-color: var(--main-fg);
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
bottom: var(--border-radius-xl);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 50px;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
position: fixed;
|
||||
right: var(--border-radius-xl);
|
||||
width: 50px;
|
||||
@@ -73,12 +70,11 @@
|
||||
font-size: var(--border-radius-xl);
|
||||
height: 100dvh;
|
||||
justify-content: space-between;
|
||||
left: calc(-1 * var(--sidebar-width));
|
||||
left: 0;
|
||||
min-width: var(--sidebar-width);
|
||||
overflow-y: auto;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
transition: var(--transition-fast);
|
||||
width: var(--sidebar-width);
|
||||
z-index: var(--z-sidebar);
|
||||
}
|
||||
@@ -198,29 +194,10 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sidebar-pin-button {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-lg);
|
||||
margin-left: auto;
|
||||
padding: var(--padding-xs);
|
||||
}
|
||||
|
||||
.sidebar-pin-button:hover,
|
||||
.sidebar-pin-button:focus-visible {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.sidebar.visible {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.sidebar.pinned {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.sidebar_header a:last-of-type {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-size-xs);
|
||||
@@ -253,13 +230,15 @@
|
||||
transform: var(--hover-scale-sm);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
@media only screen and (max-width: 768px) and (orientation: portrait) {
|
||||
#sidebarUnderlay {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
left: calc(-1 * var(--sidebar-width));
|
||||
overflow-y: scroll;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.sidebar .menu_section>li>a {
|
||||
@@ -275,10 +254,34 @@
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
#menu_button {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
html.galaxy-sidebar-pinned #menu_button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: var(--breakpoint-md)) {
|
||||
#sidebarUnderlay {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
left: calc(-1 * var(--sidebar-width));
|
||||
overflow-y: scroll;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.sidebar .menu_section>li>a {
|
||||
font-size: var(--padding-sm);
|
||||
}
|
||||
|
||||
.sidebar .title {
|
||||
flex-direction: row;
|
||||
font-size: var(--border-radius-xl);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: var(--breakpoint-md)) {
|
||||
#menu_button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -37,40 +37,6 @@ const MENU_ITEMS = {
|
||||
};
|
||||
|
||||
let galaxyDeveloperMode = false;
|
||||
const SIDEBAR_PINNED_KEY = "galaxySidebarPinned";
|
||||
let sidebarPinnedPreference = null;
|
||||
|
||||
function defaultSidebarPinned() {
|
||||
try {
|
||||
return window.matchMedia?.("(min-width: 768px)")?.matches ?? window.innerWidth >= 768;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function sidebarPinned() {
|
||||
if (sidebarPinnedPreference !== null) return sidebarPinnedPreference;
|
||||
let stored;
|
||||
try {
|
||||
stored = window.localStorage?.getItem(SIDEBAR_PINNED_KEY);
|
||||
} catch {
|
||||
stored = null;
|
||||
}
|
||||
if (stored === "true" || stored === "false") {
|
||||
sidebarPinnedPreference = stored === "true";
|
||||
return sidebarPinnedPreference;
|
||||
}
|
||||
return defaultSidebarPinned();
|
||||
}
|
||||
|
||||
function setSidebarPinned(pinned) {
|
||||
sidebarPinnedPreference = Boolean(pinned);
|
||||
try {
|
||||
window.localStorage?.setItem(SIDEBAR_PINNED_KEY, String(sidebarPinnedPreference));
|
||||
} catch {
|
||||
}
|
||||
document.documentElement.classList.toggle("galaxy-sidebar-pinned", sidebarPinnedPreference);
|
||||
}
|
||||
|
||||
function matchesPath(currentPath, link) {
|
||||
if (link === "/") return currentPath === "/";
|
||||
@@ -126,29 +92,14 @@ function bindSidebarHandlers() {
|
||||
|
||||
if (!menuButton || !underlay) return;
|
||||
|
||||
const pinButton = document.getElementById("sidebar_pin_button");
|
||||
pinButton?.addEventListener("click", () => {
|
||||
const pinned = !sidebarPinned();
|
||||
setSidebarPinned(pinned);
|
||||
const sidebar = document.getElementById("sidebar");
|
||||
hideSidebar();
|
||||
sidebar?.classList.toggle("pinned", pinned);
|
||||
pinButton.setAttribute("aria-pressed", String(pinned));
|
||||
pinButton.setAttribute("title", pinned ? "Unpin navigation" : "Pin navigation");
|
||||
pinButton.querySelector("i")?.classList.toggle("bi-pin-angle-fill", pinned);
|
||||
pinButton.querySelector("i")?.classList.toggle("bi-pin-angle", !pinned);
|
||||
});
|
||||
|
||||
if (menuButton.dataset.boundClick !== "1") {
|
||||
menuButton.dataset.boundClick = "1";
|
||||
if (!window.__theGalaxySidebarMenuBound) {
|
||||
window.__theGalaxySidebarMenuBound = true;
|
||||
menuButton.addEventListener("click", () => {
|
||||
const sidebar = document.getElementById("sidebar");
|
||||
const currentUnderlay = document.getElementById("sidebarUnderlay");
|
||||
if (!sidebar || !currentUnderlay) return;
|
||||
if (sidebar.classList.contains("pinned")) return;
|
||||
const open = sidebar.classList.toggle("visible");
|
||||
currentUnderlay.classList.toggle("hidden", !open);
|
||||
menuButton.setAttribute("aria-expanded", String(open));
|
||||
sidebar.classList.toggle("visible");
|
||||
currentUnderlay.classList.toggle("hidden");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -181,25 +132,19 @@ function renderSidebarIntoShell(currentPath) {
|
||||
if (!shell) return;
|
||||
|
||||
const activePath = currentPath || window.location.pathname;
|
||||
const pinned = sidebarPinned();
|
||||
const sectionsMarkup = Object.entries(MENU_ITEMS)
|
||||
.map(([section, links]) => buildSectionMarkup(section, links, activePath))
|
||||
.join("");
|
||||
|
||||
shell.innerHTML = `
|
||||
<div id="sidebarUnderlay" class="hidden"></div>
|
||||
<div id="sidebar" class="sidebar${pinned ? " pinned" : ""}">
|
||||
<div id="sidebar" class="sidebar">
|
||||
<div>
|
||||
<div class="title">
|
||||
<img class="logo" src="/assets/images/main_logo.png" alt="Galaxy logo" />
|
||||
<div class="title_text sidebar_header">
|
||||
<p>Galaxy</p>
|
||||
</div>
|
||||
<button id="sidebar_pin_button" class="sidebar-pin-button" type="button"
|
||||
aria-label="Toggle pinned navigation" aria-pressed="${pinned}"
|
||||
title="${pinned ? "Unpin navigation" : "Pin navigation"}">
|
||||
<i class="bi ${pinned ? "bi-pin-angle-fill" : "bi-pin-angle"}"></i>
|
||||
</button>
|
||||
</div>
|
||||
<hr />
|
||||
${sectionsMarkup}
|
||||
@@ -212,7 +157,6 @@ function renderSidebarIntoShell(currentPath) {
|
||||
|
||||
export function Sidebar(currentPath) {
|
||||
setTimeout(() => {
|
||||
document.documentElement.classList.toggle("galaxy-sidebar-pinned", sidebarPinned());
|
||||
renderSidebarIntoShell(currentPath);
|
||||
refreshGalaxyDeveloperMode();
|
||||
}, 0);
|
||||
|
||||
@@ -67,7 +67,6 @@ export function showSidebar() {
|
||||
const html = document.documentElement
|
||||
document.getElementById("sidebar")?.classList.add("visible")
|
||||
document.getElementById("sidebarUnderlay")?.classList.remove("hidden")
|
||||
document.getElementById("menu_button")?.setAttribute("aria-expanded", "true")
|
||||
html.classList.add("no_scroll")
|
||||
}
|
||||
|
||||
@@ -78,7 +77,6 @@ export function hideSidebar() {
|
||||
const html = document.documentElement
|
||||
document.getElementById("sidebar")?.classList.remove("visible")
|
||||
document.getElementById("sidebarUnderlay")?.classList.add("hidden")
|
||||
document.getElementById("menu_button")?.setAttribute("aria-expanded", "false")
|
||||
html.classList.remove("no_scroll")
|
||||
}
|
||||
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
<link rel="stylesheet" href="/assets/vendor/fonts/fonts.css">
|
||||
|
||||
<link rel="stylesheet" href="/assets/components/home/home.css">
|
||||
<link rel="stylesheet" href="/assets/components/main.css?v=sidebar-pin-2">
|
||||
<link rel="stylesheet" href="/assets/components/main.css">
|
||||
<link rel="stylesheet" href="/assets/components/modal.css">
|
||||
<link rel="stylesheet" href="/assets/components/navigation/navigation_destination.css?v=sidebar-pin-2">
|
||||
<link rel="stylesheet" href="/assets/components/navigation/navigation_destination.css?v=nav-search-context-2">
|
||||
<link rel="stylesheet" href="/assets/components/navigation/navigation_keys.css?v=app-keys-session-1">
|
||||
<link rel="stylesheet" href="/assets/components/recordings/dashcam_routes.css">
|
||||
<link rel="stylesheet" href="/assets/components/recordings/screen_recordings.css">
|
||||
<link rel="stylesheet" href="/assets/components/settings.css">
|
||||
<link rel="stylesheet" href="/assets/components/sidebar.css?v=sidebar-pin-2">
|
||||
<link rel="stylesheet" href="/assets/components/sidebar.css">
|
||||
<link rel="stylesheet" href="/assets/components/tailscale/tailscale.css">
|
||||
<link rel="stylesheet" href="/assets/components/tools/doors.css">
|
||||
<link rel="stylesheet" href="/assets/components/tools/error_logs.css">
|
||||
@@ -81,9 +81,9 @@
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<!-- Menu button for phone -->
|
||||
<button id="menu_button" type="button" aria-label="Open navigation" aria-controls="sidebar" aria-expanded="false">
|
||||
<div id="menu_button">
|
||||
<i class="bi bi-list"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Snackbar for messages -->
|
||||
<div id="snackbar_wrapper"></div>
|
||||
</body>
|
||||
|
||||
@@ -75,15 +75,6 @@ def test_galaxy_new_ui_is_the_visible_default_choice():
|
||||
assert "Galaxy (old)" in galaxy_default["description"]
|
||||
|
||||
|
||||
def test_brake_status_toggle_is_galaxy_only():
|
||||
setting = _params_by_section(_layout())["Visual (Display & UI)"]["ShowBrakeStatus"]
|
||||
|
||||
assert _declared_default("ShowBrakeStatus") == "0"
|
||||
assert setting["galaxy_only"] is True
|
||||
assert setting["settings_tier"] == "simple"
|
||||
assert setting["ui_type"] == "toggle"
|
||||
|
||||
|
||||
def test_ford_lateral_controls_are_ford_only_and_galaxy_only():
|
||||
lateral = _params_by_section(_layout())["Lateral (Steering)"]
|
||||
ford_keys = {
|
||||
|
||||
@@ -25,9 +25,6 @@ def test_router_and_settings_cache_bust_is_consistent():
|
||||
|
||||
assert "/assets/components/settings.js?v=router-cycle-fix-5" in router
|
||||
assert "/assets/components/router.js?v=router-cycle-fix-8" in index
|
||||
assert "/assets/components/sidebar.js?v=sidebar-pin-2" in router
|
||||
assert "/assets/components/main.css?v=sidebar-pin-2" in index
|
||||
assert "/assets/components/sidebar.css?v=sidebar-pin-2" in index
|
||||
|
||||
|
||||
def test_bluetooth_actions_use_reactive_disabled_bindings():
|
||||
@@ -111,27 +108,6 @@ def test_bluetooth_and_controllers_sidebar_order():
|
||||
controllers = source.index('{ name: "Controllers"')
|
||||
assert toggles < bluetooth < sentry < controllers
|
||||
|
||||
|
||||
def test_sidebar_pin_state_and_responsive_drawer_are_wired():
|
||||
sidebar = SIDEBAR_PATH.read_text(encoding="utf-8")
|
||||
template = INDEX_PATH.read_text(encoding="utf-8")
|
||||
sidebar_css = (REPO_ROOT / "starpilot/system/the_galaxy/assets/components/sidebar.css").read_text(encoding="utf-8")
|
||||
main_css = (REPO_ROOT / "starpilot/system/the_galaxy/assets/components/main.css").read_text(encoding="utf-8")
|
||||
navigation_css = (REPO_ROOT / "starpilot/system/the_galaxy/assets/components/navigation/navigation_destination.css").read_text(encoding="utf-8")
|
||||
|
||||
assert 'window.localStorage?.getItem(SIDEBAR_PINNED_KEY)' in sidebar
|
||||
assert 'stored === "true"' in sidebar and 'defaultSidebarPinned' in sidebar
|
||||
assert "try {" in sidebar
|
||||
assert 'menuButton.dataset.boundClick !== "1"' in sidebar
|
||||
assert 'class="sidebar-pin-button"' in sidebar
|
||||
assert '<button id="menu_button"' in template
|
||||
assert 'aria-controls="sidebar"' in template
|
||||
assert "@media only screen and (max-width: 767px)" in sidebar_css
|
||||
assert "@media only screen and (min-width: 768px)" in sidebar_css
|
||||
assert "@media only screen and (min-width: var(--breakpoint-md))" not in sidebar_css
|
||||
assert "html.galaxy-sidebar-pinned .content" in main_css
|
||||
assert "html.galaxy-sidebar-pinned .map-wrapper" in navigation_css
|
||||
|
||||
def test_model_laboratory_is_wired_into_classic_and_mobile_navigation():
|
||||
router = ROUTER_PATH.read_text(encoding="utf-8")
|
||||
sidebar = SIDEBAR_PATH.read_text(encoding="utf-8")
|
||||
|
||||
Reference in New Issue
Block a user