mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-07 22:52:06 +08:00
BigUI WIP: Start of Lateral design pass
This commit is contained in:
@@ -2840,6 +2840,8 @@ class AetherSettingsView(PanelManagerView):
|
||||
row = self._find_row(target_id)
|
||||
if row is None:
|
||||
return
|
||||
if row.enabled is not None and not row.enabled():
|
||||
return
|
||||
if row.navigate_to:
|
||||
self._controller._navigate_to(row.navigate_to)
|
||||
elif row.on_click:
|
||||
|
||||
@@ -12,17 +12,18 @@ from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import _SettingsPage
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import (
|
||||
AetherListMetrics,
|
||||
AetherListColors,
|
||||
AetherSettingsView,
|
||||
AetherSliderDialog,
|
||||
BACK_BTN,
|
||||
DEFAULT_PANEL_STYLE,
|
||||
PanelManagerView,
|
||||
HubTile,
|
||||
SettingRow,
|
||||
SettingSection,
|
||||
TileGrid,
|
||||
ToggleTile,
|
||||
draw_list_group_shell,
|
||||
draw_section_header,
|
||||
draw_selection_list_row,
|
||||
draw_settings_panel_header,
|
||||
_with_alpha,
|
||||
_draw_back_button,
|
||||
_draw_rounded_fill,
|
||||
_draw_rounded_stroke,
|
||||
_point_hits,
|
||||
)
|
||||
|
||||
|
||||
@@ -51,9 +52,6 @@ CUSTOM_METRICS = AetherListMetrics(
|
||||
utility_row_height=88,
|
||||
)
|
||||
|
||||
SECTION_GAP = CUSTOM_METRICS.section_gap
|
||||
SECTION_HEADER_HEIGHT = CUSTOM_METRICS.section_header_height
|
||||
SECTION_HEADER_GAP = CUSTOM_METRICS.section_header_gap
|
||||
ROW_HEIGHT = CUSTOM_METRICS.row_height
|
||||
PANEL_STYLE = DEFAULT_PANEL_STYLE
|
||||
|
||||
@@ -61,11 +59,6 @@ _LATERAL_TUNE_KEYS = ["TurnDesires", "NNFF", "NNFFLite", "ForceTorqueController"
|
||||
_ADVANCED_LATERAL_KEYS = ["ForceAutoTune", "ForceAutoTuneOff"]
|
||||
|
||||
|
||||
def _ensure(params, key):
|
||||
if not params.get_bool(key):
|
||||
params.put_bool(key, True)
|
||||
|
||||
|
||||
def _sync_parent(params, parent_key, child_keys):
|
||||
if any(params.get_bool(k) for k in child_keys):
|
||||
if not params.get_bool(parent_key):
|
||||
@@ -75,431 +68,351 @@ def _sync_parent(params, parent_key, child_keys):
|
||||
params.put_bool(parent_key, False)
|
||||
|
||||
|
||||
class SteeringManagerView(PanelManagerView):
|
||||
METRICS = CUSTOM_METRICS
|
||||
class _BackButtonSettingsView(AetherSettingsView):
|
||||
"""AetherSettingsView with a back button pill drawn in the scroll content header."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._back_btn_rect = None
|
||||
self._has_header = False
|
||||
|
||||
def _target_at(self, mouse_pos):
|
||||
if self._back_btn_rect and _point_hits(mouse_pos, self._back_btn_rect, None, pad_x=8, pad_y=8):
|
||||
return BACK_BTN
|
||||
return super()._target_at(mouse_pos)
|
||||
|
||||
def _activate_target(self, target_id):
|
||||
if target_id == BACK_BTN:
|
||||
self._controller._go_back()
|
||||
else:
|
||||
super()._activate_target(target_id)
|
||||
|
||||
def _draw_scroll_content(self, rect, width):
|
||||
pill_h = 52
|
||||
y = rect.y + self._scroll_offset
|
||||
pill_rect = rl.Rectangle(rect.x, y, rect.width, pill_h)
|
||||
center_y = pill_rect.y + pill_h / 2
|
||||
|
||||
_draw_rounded_fill(pill_rect, rl.Color(18, 16, 24, 200), radius_px=14)
|
||||
_draw_rounded_stroke(pill_rect, rl.Color(255, 255, 255, 22), radius_px=14)
|
||||
|
||||
mouse_pos = gui_app.last_mouse_event.pos
|
||||
is_pressed = getattr(self, '_pressed_target', None) == BACK_BTN
|
||||
is_hovered = _point_hits(mouse_pos, pill_rect, None, pad_x=0, pad_y=0) and \
|
||||
self._back_btn_rect and _point_hits(mouse_pos, self._back_btn_rect, None, pad_x=8, pad_y=8)
|
||||
self._back_btn_rect = _draw_back_button(pill_rect, center_y, is_pressed, is_hovered)
|
||||
|
||||
crumb_x = pill_rect.x + 58
|
||||
crumb_w = pill_rect.width - (crumb_x - pill_rect.x) - 20
|
||||
crumb_rect = rl.Rectangle(crumb_x, pill_rect.y, crumb_w, pill_h)
|
||||
self._breadcrumbs.draw(crumb_rect)
|
||||
|
||||
self._scroll_offset += pill_h + 12
|
||||
super()._draw_scroll_content(rect, width)
|
||||
|
||||
|
||||
class SteeringManagerView(AetherSettingsView):
|
||||
"""Hub view with 3 category tiles leading to sub-panels."""
|
||||
|
||||
@property
|
||||
def vertical_scrolling_disabled(self) -> bool:
|
||||
return True
|
||||
|
||||
def __init__(self, controller: "StarPilotLateralLayout"):
|
||||
super().__init__()
|
||||
def __init__(self, controller, sections, **kwargs):
|
||||
super().__init__(controller, sections, **kwargs)
|
||||
self._controller = controller
|
||||
self._shell_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
|
||||
self._toggle_grid = TileGrid(columns=2, padding=12, force_square=True, min_tile_width=100, min_tile_height=130.0, max_tile_height=280.0)
|
||||
self._toggle_grid.set_touch_valid_callback(lambda: self._scroll_panel.is_touch_valid())
|
||||
self._child(self._toggle_grid)
|
||||
self._hub_grid = TileGrid(columns=2, padding=12)
|
||||
self._hub_grid.set_touch_valid_callback(lambda: self._scroll_panel.is_touch_valid())
|
||||
self._child(self._hub_grid)
|
||||
self._init_hub()
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
starpilot_state.update(force=True)
|
||||
self._rebuild_toggle_grid()
|
||||
|
||||
def _on_frame_created(self, frame) -> None:
|
||||
self._shell_rect = frame.shell
|
||||
def _init_hub(self):
|
||||
hub_data = [
|
||||
{
|
||||
"key": "steering_behavior",
|
||||
"title": tr("Steering Behavior"),
|
||||
"desc": tr("Configure when steering engages, pauses, and resumes."),
|
||||
"icon": "steering",
|
||||
"color": "#8B5CF6",
|
||||
},
|
||||
{
|
||||
"key": "lane_changes",
|
||||
"title": tr("Lane Changes"),
|
||||
"desc": tr("Configure lane change behavior, speed thresholds, and timing."),
|
||||
"icon": "navigate",
|
||||
"color": "#8B5CF6",
|
||||
},
|
||||
{
|
||||
"key": "tuning",
|
||||
"title": tr("Advanced Lateral Tuning"),
|
||||
"desc": tr("Fine-tune steering response, feedforward, and auto-tuning."),
|
||||
"icon": "system",
|
||||
"color": "#8B5CF6",
|
||||
},
|
||||
]
|
||||
|
||||
def _activate_target(self, target_id: str | None):
|
||||
if not target_id:
|
||||
return
|
||||
prefix, _, value = target_id.partition(":")
|
||||
if prefix == "select":
|
||||
self._controller._on_select(value)
|
||||
self._hub_grid.clear()
|
||||
for d in hub_data:
|
||||
self._hub_grid.add_tile(HubTile(
|
||||
title=d["title"],
|
||||
desc=d["desc"],
|
||||
icon_key=d["icon"],
|
||||
on_click=lambda k=d["key"]: self._controller._navigate_to(k),
|
||||
bg_color=d["color"],
|
||||
))
|
||||
|
||||
def _draw_header(self, rect: rl.Rectangle):
|
||||
pass
|
||||
|
||||
def _build_toggle_defs(self) -> list[dict]:
|
||||
p = self._controller._params
|
||||
cs = starpilot_state.car_state
|
||||
toggles: list[dict] = []
|
||||
|
||||
toggles.append({
|
||||
"key": "AdvancedLateralTune",
|
||||
"title": tr("Advanced Lateral Tuning"),
|
||||
"subtitle": tr("Fine-tune steering response and auto-tuning."),
|
||||
"get": lambda: p.get_bool("AdvancedLateralTune"),
|
||||
"set": lambda s: p.put_bool("AdvancedLateralTune", s),
|
||||
})
|
||||
|
||||
toggles.append({
|
||||
"key": "AlwaysOnLateral",
|
||||
"title": tr("Always On Lateral"),
|
||||
"subtitle": tr("Steering stays active when ACC is off."),
|
||||
"get": lambda: p.get_bool("AlwaysOnLateral"),
|
||||
"set": lambda s: (_confirm_reboot_toggle(p, "AlwaysOnLateral", s)
|
||||
if s else p.put_bool("AlwaysOnLateral", False)),
|
||||
})
|
||||
|
||||
toggles.append({
|
||||
"key": "LaneChanges",
|
||||
"title": tr("Lane Changes"),
|
||||
"subtitle": tr("Allow openpilot to change lanes."),
|
||||
"get": lambda: p.get_bool("LaneChanges"),
|
||||
"set": lambda s: p.put_bool("LaneChanges", s),
|
||||
})
|
||||
|
||||
toggles.append({
|
||||
"key": "PauseLateralOnSignal",
|
||||
"title": tr("Turn Signal Only"),
|
||||
"subtitle": tr("Only pause steering when turn signal is active."),
|
||||
"get": lambda: p.get_bool("PauseLateralOnSignal"),
|
||||
"set": lambda s: p.put_bool("PauseLateralOnSignal", s),
|
||||
})
|
||||
|
||||
toggles.append({
|
||||
"key": "NudgelessLaneChange",
|
||||
"title": tr("Auto Lane Changes"),
|
||||
"subtitle": tr("Signal triggers automatic lane change."),
|
||||
"get": lambda: p.get_bool("NudgelessLaneChange"),
|
||||
"set": lambda s: p.put_bool("NudgelessLaneChange", s),
|
||||
})
|
||||
|
||||
toggles.append({
|
||||
"key": "OneLaneChange",
|
||||
"title": tr("One Per Signal"),
|
||||
"subtitle": tr("One lane change per signal activation."),
|
||||
"get": lambda: p.get_bool("OneLaneChange"),
|
||||
"set": lambda s: p.put_bool("OneLaneChange", s),
|
||||
})
|
||||
|
||||
toggles.append({
|
||||
"key": "TurnDesires",
|
||||
"title": tr("Force Turn Desires"),
|
||||
"subtitle": tr("Follow turn intent below min lane change speed."),
|
||||
"get": lambda: p.get_bool("TurnDesires"),
|
||||
"set": lambda s: (p.put_bool("TurnDesires", s),
|
||||
_sync_parent(p, "LateralTune", _LATERAL_TUNE_KEYS)),
|
||||
})
|
||||
|
||||
toggles.append({
|
||||
"key": "NavDesiresAllowed",
|
||||
"title": tr("Use Route Desires"),
|
||||
"subtitle": tr("Allow navigation to request lane keep and turns."),
|
||||
"get": lambda: p.get_bool("NavDesiresAllowed"),
|
||||
"set": lambda s: p.put_bool("NavDesiresAllowed", s),
|
||||
})
|
||||
|
||||
toggles.append({
|
||||
"key": "NNFF",
|
||||
"title": tr("NNFF"),
|
||||
"subtitle": tr("Neural net feedforward steering controller."),
|
||||
"get": lambda: p.get_bool("NNFF"),
|
||||
"set": lambda s: (p.put_bool("NNFF", s),
|
||||
s and p.put_bool("NNFFLite", False),
|
||||
_sync_parent(p, "LateralTune", _LATERAL_TUNE_KEYS)),
|
||||
"enabled": cs.hasNNFFLog and not cs.isAngleCar,
|
||||
"disabled_label": tr("Not Available"),
|
||||
})
|
||||
|
||||
toggles.append({
|
||||
"key": "NNFFLite",
|
||||
"title": tr("NNFF Lite"),
|
||||
"subtitle": tr("Lightweight NNFF when full model is off."),
|
||||
"get": lambda: p.get_bool("NNFFLite"),
|
||||
"set": lambda s: (p.put_bool("NNFFLite", s),
|
||||
_sync_parent(p, "LateralTune", _LATERAL_TUNE_KEYS)),
|
||||
"enabled": not cs.isAngleCar,
|
||||
"disabled_label": tr("Not Available"),
|
||||
})
|
||||
|
||||
toggles.append({
|
||||
"key": "ForceAutoTune",
|
||||
"title": tr("Force Auto-Tune On"),
|
||||
"subtitle": tr("Force-enable live auto-tuning for friction and lateral accel."),
|
||||
"get": lambda: p.get_bool("ForceAutoTune"),
|
||||
"set": lambda s: (p.put_bool("ForceAutoTune", s),
|
||||
s and p.put_bool("ForceAutoTuneOff", False),
|
||||
_sync_parent(p, "AdvancedLateralTune", _ADVANCED_LATERAL_KEYS)),
|
||||
"enabled": not cs.hasAutoTune and cs.isTorqueCar and not cs.isAngleCar,
|
||||
"disabled_label": tr("Not Available"),
|
||||
})
|
||||
|
||||
toggles.append({
|
||||
"key": "ForceAutoTuneOff",
|
||||
"title": tr("Force Auto-Tune Off"),
|
||||
"subtitle": tr("Force-disable auto-tuning and use your set values."),
|
||||
"get": lambda: p.get_bool("ForceAutoTuneOff"),
|
||||
"set": lambda s: (p.put_bool("ForceAutoTuneOff", s),
|
||||
s and p.put_bool("ForceAutoTune", False),
|
||||
_sync_parent(p, "AdvancedLateralTune", _ADVANCED_LATERAL_KEYS)),
|
||||
"enabled": cs.hasAutoTune and cs.isTorqueCar and not cs.isAngleCar,
|
||||
"disabled_label": tr("Not Available"),
|
||||
})
|
||||
|
||||
toggles.append({
|
||||
"key": "ForceTorqueController",
|
||||
"title": tr("Force Torque Ctrl"),
|
||||
"subtitle": tr("Torque-based steering for smoother lane keeping."),
|
||||
"get": lambda: p.get_bool("ForceTorqueController"),
|
||||
"set": lambda s: (p.put_bool("ForceTorqueController", s),
|
||||
_sync_parent(p, "LateralTune", _LATERAL_TUNE_KEYS)),
|
||||
"enabled": not cs.isTorqueCar and not cs.isAngleCar,
|
||||
"disabled_label": tr("Not Available"),
|
||||
})
|
||||
|
||||
return toggles
|
||||
|
||||
def _rebuild_toggle_grid(self):
|
||||
self._page_grid = self._toggle_grid
|
||||
defs = self._build_toggle_defs()
|
||||
self._set_toggle_pages([defs[i:i+4] for i in range(0, len(defs), 4)])
|
||||
|
||||
def _measure_content_height(self, width: float) -> float:
|
||||
sections = self._build_left_sections()
|
||||
left_h = self._stacked_section_height([s["height"] for s in sections if s.get("visible", True)])
|
||||
|
||||
tiles_height = 0.0
|
||||
if self._toggle_grid.tiles:
|
||||
if not self._uses_two_columns(width):
|
||||
self._toggle_grid._columns = 3
|
||||
tiles_content_h = self._toggle_grid.measure_height(width - 24)
|
||||
tiles_height = SECTION_GAP + self._section_block_height(tiles_content_h + 24)
|
||||
|
||||
if self._uses_two_columns(width):
|
||||
column_w = self._column_width(width)
|
||||
self._toggle_grid._columns = 2
|
||||
grid_content_h = self.measure_page_grid_height(self._toggle_grid, column_w - 24) + 24
|
||||
grid_container_h = grid_content_h + SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP
|
||||
available_h = self._scroll_rect.height if self._scroll_rect else left_h
|
||||
container_h = max(left_h, grid_container_h, available_h)
|
||||
self._grid_container_h = container_h
|
||||
return self._compute_two_column_height(container_h)
|
||||
return left_h + tiles_height
|
||||
|
||||
def _draw_scroll_content(self, rect: rl.Rectangle, width: float):
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
self.set_rect(rect)
|
||||
self._interactive_rects.clear()
|
||||
y = rect.y + self._scroll_offset
|
||||
self._draw_panel_content(y, rect.x, width)
|
||||
|
||||
def _draw_panel_content(self, y: float, x: float, width: float):
|
||||
sections = self._build_left_sections()
|
||||
margin_x = 18.0
|
||||
margin_y = 24.0
|
||||
|
||||
drawn_first = False
|
||||
grid_x = rect.x + margin_x
|
||||
grid_y = rect.y + margin_y
|
||||
grid_w = rect.width - margin_x * 2
|
||||
grid_h = rect.y + rect.height - grid_y - margin_y
|
||||
|
||||
if self._uses_two_columns(width):
|
||||
column_w = self._column_width(width)
|
||||
curr_y = y
|
||||
self._scroll_rect = rl.Rectangle(grid_x, grid_y, grid_w, grid_h)
|
||||
self._content_height = grid_h
|
||||
|
||||
for section in sections:
|
||||
if not section.get("rows"):
|
||||
continue
|
||||
if drawn_first:
|
||||
curr_y += SECTION_GAP
|
||||
drawn_first = True
|
||||
draw_section_header(rl.Rectangle(x, curr_y, column_w, SECTION_HEADER_HEIGHT),
|
||||
section["title"], style=PANEL_STYLE)
|
||||
curr_y += SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP
|
||||
row_count = len(section["rows"])
|
||||
container_rect = rl.Rectangle(x, curr_y, column_w, row_count * ROW_HEIGHT)
|
||||
draw_list_group_shell(container_rect, style=PANEL_STYLE)
|
||||
for index, row in enumerate(section["rows"]):
|
||||
row_rect = rl.Rectangle(x, curr_y + index * ROW_HEIGHT, column_w, ROW_HEIGHT)
|
||||
self._draw_row(row_rect, row, is_last=index == row_count - 1)
|
||||
curr_y += row_count * ROW_HEIGHT
|
||||
|
||||
if self._toggle_grid.tiles:
|
||||
rx = x + column_w + self.COLUMN_GAP
|
||||
self._draw_two_column_tile_grid(self._toggle_grid, rx, y, column_w, self._grid_container_h, title=tr("Toggles"), style=PANEL_STYLE)
|
||||
else:
|
||||
curr_y = y
|
||||
for section in sections:
|
||||
if not section.get("rows"):
|
||||
continue
|
||||
if drawn_first:
|
||||
curr_y += SECTION_GAP
|
||||
drawn_first = True
|
||||
draw_section_header(rl.Rectangle(x, curr_y, width, SECTION_HEADER_HEIGHT),
|
||||
section["title"], style=PANEL_STYLE)
|
||||
curr_y += SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP
|
||||
row_count = len(section["rows"])
|
||||
container_rect = rl.Rectangle(x, curr_y, width, row_count * ROW_HEIGHT)
|
||||
draw_list_group_shell(container_rect, style=PANEL_STYLE)
|
||||
for index, row in enumerate(section["rows"]):
|
||||
row_rect = rl.Rectangle(x, curr_y + index * ROW_HEIGHT, width, ROW_HEIGHT)
|
||||
self._draw_row(row_rect, row, is_last=index == row_count - 1)
|
||||
curr_y += row_count * ROW_HEIGHT
|
||||
|
||||
if self._toggle_grid.tiles:
|
||||
curr_y += SECTION_GAP
|
||||
draw_section_header(rl.Rectangle(x, curr_y, width, SECTION_HEADER_HEIGHT),
|
||||
tr("Toggles"), style=PANEL_STYLE)
|
||||
curr_y += SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP
|
||||
|
||||
self._toggle_grid._columns = 3
|
||||
avail_w = width - 24
|
||||
tiles_content_h = self._toggle_grid.measure_height(avail_w)
|
||||
|
||||
draw_list_group_shell(rl.Rectangle(x, curr_y, width, tiles_content_h + 24), style=PANEL_STYLE)
|
||||
self._render_page_grid(self._toggle_grid, rl.Rectangle(x + 12, curr_y + 12, avail_w, tiles_content_h))
|
||||
|
||||
def _draw_row(self, rect: rl.Rectangle, row: dict, is_last: bool):
|
||||
target_id = row["target_id"]
|
||||
is_enabled = row.get("is_enabled", True)
|
||||
hovered, pressed = self._interactive_state(target_id, rect) if is_enabled else (False, False)
|
||||
|
||||
draw_selection_list_row(
|
||||
rect, title=row["title"], subtitle=row.get("subtitle", ""),
|
||||
action_text=row["get_value"](), hovered=hovered and is_enabled,
|
||||
pressed=pressed and is_enabled, is_last=is_last,
|
||||
action_width=188, action_pill=True,
|
||||
action_pill_width=row.get("pill_width", 108), action_pill_height=44,
|
||||
title_size=34, subtitle_size=22, action_text_size=18,
|
||||
row_separator=PANEL_STYLE.divider_color,
|
||||
action_fill=PANEL_STYLE.current_fill if is_enabled else _with_alpha(PANEL_STYLE.current_fill, 120),
|
||||
action_border=PANEL_STYLE.current_border if is_enabled else _with_alpha(PANEL_STYLE.current_border, 100),
|
||||
action_text_color=AetherListColors.HEADER if is_enabled else AetherListColors.MUTED,
|
||||
self._scroll_panel.set_enabled(self.is_visible)
|
||||
self._scroll_offset = self._scroll_panel.update(
|
||||
self._scroll_rect, self._scroll_rect.height
|
||||
)
|
||||
|
||||
def _build_left_sections(self) -> list[dict]:
|
||||
p = self._controller._params
|
||||
cs = starpilot_state.car_state
|
||||
sections: list[dict] = []
|
||||
if self.vertical_scrolling_disabled:
|
||||
self._scroll_offset = 0.0
|
||||
|
||||
alt = p.get_bool("AdvancedLateralTune")
|
||||
aol = p.get_bool("AlwaysOnLateral")
|
||||
lc = p.get_bool("LaneChanges")
|
||||
nlc = p.get_bool("NudgelessLaneChange")
|
||||
pos = p.get_bool("PauseLateralOnSignal")
|
||||
self._draw_scroll_content(self._scroll_rect, self._scroll_rect.width)
|
||||
|
||||
if alt:
|
||||
rows: list[dict] = []
|
||||
if cs.steerActuatorDelay != 0:
|
||||
rows.append({
|
||||
"target_id": "select:SteerDelay",
|
||||
"title": tr("Actuator Delay"),
|
||||
"subtitle": tr("Time between steering command and vehicle response."),
|
||||
"get_value": lambda: f"{p.get_float('SteerDelay'):.2f}s",
|
||||
"pill_width": 120,
|
||||
})
|
||||
if cs.friction != 0:
|
||||
rows.append({
|
||||
"target_id": "select:SteerFriction",
|
||||
"title": tr("Friction"),
|
||||
"subtitle": tr("Compensates for steering friction around center."),
|
||||
"get_value": lambda: f"{p.get_float('SteerFriction'):.3f}",
|
||||
"pill_width": 120,
|
||||
})
|
||||
if cs.steerKp != 0:
|
||||
rows.append({
|
||||
"target_id": "select:SteerKP",
|
||||
"title": tr("Kp Factor"),
|
||||
"subtitle": tr("How strongly openpilot corrects lateral position."),
|
||||
"get_value": lambda: f"{p.get_float('SteerKP'):.2f}",
|
||||
"pill_width": 120,
|
||||
})
|
||||
if cs.latAccelFactor != 0:
|
||||
rows.append({
|
||||
"target_id": "select:SteerLatAccel",
|
||||
"title": tr("Lateral Acceleration"),
|
||||
"subtitle": tr("Maps steering torque to turning response."),
|
||||
"get_value": lambda: f"{p.get_float('SteerLatAccel'):.2f}",
|
||||
"pill_width": 120,
|
||||
})
|
||||
if cs.steerRatio != 0:
|
||||
rows.append({
|
||||
"target_id": "select:SteerRatio",
|
||||
"title": tr("Steer Ratio"),
|
||||
"subtitle": tr("Relationship between steering wheel and road-wheel angle."),
|
||||
"get_value": lambda: f"{p.get_float('SteerRatio'):.2f}",
|
||||
"pill_width": 120,
|
||||
})
|
||||
sections.append({
|
||||
"title": tr("Advanced Tuning"),
|
||||
"rows": rows,
|
||||
"visible": True,
|
||||
"height": self._section_block_height(self._section_height(len(rows), ROW_HEIGHT)),
|
||||
})
|
||||
|
||||
if aol:
|
||||
aol_rows = [{
|
||||
"target_id": "select:PauseAOLOnBrake",
|
||||
"title": tr("Pause AOL On Brake"),
|
||||
"subtitle": tr("Pause AOL below this speed while brake is pressed."),
|
||||
"get_value": lambda: f"{p.get_int('PauseAOLOnBrake')} mph",
|
||||
"pill_width": 140,
|
||||
}]
|
||||
sections.append({
|
||||
"title": tr("Always On Lateral"),
|
||||
"rows": aol_rows,
|
||||
"visible": True,
|
||||
"height": self._section_block_height(self._section_height(len(aol_rows), ROW_HEIGHT)),
|
||||
})
|
||||
|
||||
if lc:
|
||||
lc_rows: list[dict] = [{
|
||||
"target_id": "select:MinimumLaneChangeSpeed",
|
||||
"title": tr("Min Lane Change Speed"),
|
||||
"subtitle": tr("Lowest speed at which openpilot will change lanes."),
|
||||
"get_value": lambda: f"{p.get_int('MinimumLaneChangeSpeed')} mph",
|
||||
"pill_width": 140,
|
||||
}]
|
||||
if nlc:
|
||||
lc_rows.append({
|
||||
"target_id": "select:LaneChangeTime",
|
||||
"title": tr("Lane Change Delay"),
|
||||
"subtitle": tr("Delay before the start of an automatic lane change. 0 = Instant."),
|
||||
"get_value": lambda: tr("Instant") if p.get_float("LaneChangeTime") == 0
|
||||
else f"{p.get_float('LaneChangeTime'):.1f}s",
|
||||
"pill_width": 120,
|
||||
})
|
||||
lc_rows.append({
|
||||
"target_id": "select:LaneDetectionWidth",
|
||||
"title": tr("Min Lane Width"),
|
||||
"subtitle": tr("Prevent lane changes into narrower lanes."),
|
||||
"get_value": lambda: f"{p.get_float('LaneDetectionWidth'):.1f} ft",
|
||||
"pill_width": 120,
|
||||
})
|
||||
lc_rows.append({
|
||||
"target_id": "select:LaneChangeSmoothing",
|
||||
"title": tr("Lane Change Smoothing"),
|
||||
"subtitle": tr("Smoothness of lane change commit. 10 = Stock, 1 = Smoothest."),
|
||||
"get_value": lambda: tr("Stock") if p.get_int("LaneChangeSmoothing") == 10
|
||||
else f"{p.get_int('LaneChangeSmoothing')}",
|
||||
"pill_width": 120,
|
||||
})
|
||||
sections.append({
|
||||
"title": tr("Lane Changes"),
|
||||
"rows": lc_rows,
|
||||
"visible": True,
|
||||
"height": self._section_block_height(self._section_height(len(lc_rows), ROW_HEIGHT)),
|
||||
})
|
||||
|
||||
paus_rows: list[dict] = [{
|
||||
"target_id": "select:PauseLateralSpeed",
|
||||
"title": tr("Pause Lateral Below"),
|
||||
"subtitle": tr("Pause steering below the set speed."),
|
||||
"get_value": lambda: f"{p.get_int('PauseLateralSpeed')} mph",
|
||||
"pill_width": 140,
|
||||
}]
|
||||
if pos:
|
||||
paus_rows.append({
|
||||
"target_id": "select:LateralResumeDelay",
|
||||
"title": tr("Resume Delay"),
|
||||
"subtitle": tr("Delay before lateral resumes after signal off. 0 = Off."),
|
||||
"get_value": lambda: tr("Off") if p.get_float("LateralResumeDelay") == 0
|
||||
else f"{p.get_float('LateralResumeDelay'):.1f}s",
|
||||
"pill_width": 120,
|
||||
})
|
||||
sections.append({
|
||||
"title": tr("Pause Lateral"),
|
||||
"rows": paus_rows,
|
||||
"visible": True,
|
||||
"height": self._section_block_height(self._section_height(len(paus_rows), ROW_HEIGHT)),
|
||||
})
|
||||
|
||||
return sections
|
||||
def _draw_scroll_content(self, rect: rl.Rectangle, width: float):
|
||||
y = rect.y + self._scroll_offset
|
||||
self._hub_grid.set_parent_rect(self._scroll_rect)
|
||||
self._hub_grid.render(rl.Rectangle(rect.x, y, width, rect.height))
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# StarPilotLateralLayout — top-level hybrid panel
|
||||
# StarPilotLateralLayout — controller
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class StarPilotLateralLayout(_SettingsPage):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._manager_view = SteeringManagerView(self)
|
||||
self._build_sub_panels()
|
||||
self._manager_view = SteeringManagerView(
|
||||
self, [],
|
||||
header_title=tr("Steering"),
|
||||
header_subtitle=tr("Configure steering behavior, lane changes, and tuning parameters."),
|
||||
panel_style=PANEL_STYLE,
|
||||
)
|
||||
|
||||
def _build_sub_panels(self):
|
||||
p = self._params
|
||||
cs = starpilot_state.car_state
|
||||
|
||||
def alt_on():
|
||||
return p.get_bool("AdvancedLateralTune")
|
||||
|
||||
def aol_on():
|
||||
return p.get_bool("AlwaysOnLateral")
|
||||
|
||||
def lc_on():
|
||||
return p.get_bool("LaneChanges")
|
||||
|
||||
def nlc_on():
|
||||
return lc_on() and p.get_bool("NudgelessLaneChange")
|
||||
|
||||
def pos_on():
|
||||
return p.get_bool("PauseLateralOnSignal")
|
||||
|
||||
def _show(key):
|
||||
return lambda: self._on_select(key)
|
||||
|
||||
# ── Steering Behavior ──
|
||||
self._steering_behavior_rows = [
|
||||
SettingRow("AlwaysOnLateral", "toggle", tr("Always On Lateral"),
|
||||
subtitle=tr("Steering stays active when ACC is off."),
|
||||
get_state=lambda: p.get_bool("AlwaysOnLateral"),
|
||||
set_state=lambda s: _confirm_reboot_toggle(p, "AlwaysOnLateral", s) if s else p.put_bool("AlwaysOnLateral", False)),
|
||||
SettingRow("PauseAOLOnBrake", "value", tr("Pause AOL On Brake"),
|
||||
subtitle=tr("Pause AOL below this speed while brake is pressed."),
|
||||
get_value=lambda: f"{p.get_int('PauseAOLOnBrake')} mph",
|
||||
on_click=_show("PauseAOLOnBrake"),
|
||||
visible=aol_on),
|
||||
SettingRow("PauseLateralOnSignal", "toggle", tr("Turn Signal Only"),
|
||||
subtitle=tr("Only pause steering when turn signal is active."),
|
||||
get_state=lambda: p.get_bool("PauseLateralOnSignal"),
|
||||
set_state=lambda s: p.put_bool("PauseLateralOnSignal", s)),
|
||||
SettingRow("PauseLateralSpeed", "value", tr("Pause Lateral Below"),
|
||||
subtitle=tr("Pause steering below the set speed."),
|
||||
get_value=lambda: f"{p.get_int('PauseLateralSpeed')} mph",
|
||||
on_click=_show("PauseLateralSpeed")),
|
||||
SettingRow("LateralResumeDelay", "value", tr("Resume Delay"),
|
||||
subtitle=tr("Delay before lateral resumes after signal off. 0 = Off."),
|
||||
get_value=lambda: tr("Off") if p.get_float("LateralResumeDelay") == 0 else f"{p.get_float('LateralResumeDelay'):.1f}s",
|
||||
on_click=_show("LateralResumeDelay"),
|
||||
visible=pos_on),
|
||||
SettingRow("NavDesiresAllowed", "toggle", tr("Use Route Desires"),
|
||||
subtitle=tr("Allow navigation to request lane keep and turns."),
|
||||
get_state=lambda: p.get_bool("NavDesiresAllowed"),
|
||||
set_state=lambda s: p.put_bool("NavDesiresAllowed", s)),
|
||||
]
|
||||
|
||||
# ── Lane Changes ──
|
||||
self._lane_change_rows = [
|
||||
SettingRow("LaneChanges", "toggle", tr("Lane Changes"),
|
||||
subtitle=tr("Allow openpilot to change lanes."),
|
||||
get_state=lambda: p.get_bool("LaneChanges"),
|
||||
set_state=lambda s: p.put_bool("LaneChanges", s)),
|
||||
SettingRow("NudgelessLaneChange", "toggle", tr("Auto Lane Changes"),
|
||||
subtitle=tr("Signal triggers automatic lane change."),
|
||||
get_state=lambda: p.get_bool("NudgelessLaneChange"),
|
||||
set_state=lambda s: p.put_bool("NudgelessLaneChange", s),
|
||||
visible=lc_on),
|
||||
SettingRow("OneLaneChange", "toggle", tr("One Per Signal"),
|
||||
subtitle=tr("One lane change per signal activation."),
|
||||
get_state=lambda: p.get_bool("OneLaneChange"),
|
||||
set_state=lambda s: p.put_bool("OneLaneChange", s),
|
||||
visible=nlc_on),
|
||||
SettingRow("MinimumLaneChangeSpeed", "value", tr("Min Lane Change Speed"),
|
||||
subtitle=tr("Lowest speed at which openpilot will change lanes."),
|
||||
get_value=lambda: f"{p.get_int('MinimumLaneChangeSpeed')} mph",
|
||||
on_click=_show("MinimumLaneChangeSpeed"),
|
||||
visible=lc_on),
|
||||
SettingRow("LaneChangeTime", "value", tr("Lane Change Delay"),
|
||||
subtitle=tr("Delay before the start of an automatic lane change. 0 = Instant."),
|
||||
get_value=lambda: tr("Instant") if p.get_float("LaneChangeTime") == 0 else f"{p.get_float('LaneChangeTime'):.1f}s",
|
||||
on_click=_show("LaneChangeTime"),
|
||||
visible=nlc_on),
|
||||
SettingRow("LaneDetectionWidth", "value", tr("Min Lane Width"),
|
||||
subtitle=tr("Prevent lane changes into narrower lanes."),
|
||||
get_value=lambda: f"{p.get_float('LaneDetectionWidth'):.1f} ft",
|
||||
on_click=_show("LaneDetectionWidth"),
|
||||
visible=nlc_on),
|
||||
SettingRow("LaneChangeSmoothing", "value", tr("Lane Change Smoothing"),
|
||||
subtitle=tr("Smoothness of lane change commit. 10 = Stock, 1 = Smoothest."),
|
||||
get_value=lambda: tr("Stock") if p.get_int("LaneChangeSmoothing") == 10 else f"{p.get_int('LaneChangeSmoothing')}",
|
||||
on_click=_show("LaneChangeSmoothing"),
|
||||
visible=lc_on),
|
||||
]
|
||||
|
||||
# ── Advanced Lateral Tuning ──
|
||||
self._tuning_rows = [
|
||||
SettingRow("AdvancedLateralTune", "toggle", tr("Advanced Lateral Tuning"),
|
||||
subtitle=tr("Fine-tune steering response and auto-tuning."),
|
||||
get_state=lambda: p.get_bool("AdvancedLateralTune"),
|
||||
set_state=lambda s: p.put_bool("AdvancedLateralTune", s)),
|
||||
SettingRow("NNFF", "toggle", tr("NNFF"),
|
||||
subtitle=tr("Neural net feedforward steering controller."),
|
||||
get_state=lambda: p.get_bool("NNFF"),
|
||||
set_state=lambda s: (p.put_bool("NNFF", s),
|
||||
s and p.put_bool("NNFFLite", False),
|
||||
_sync_parent(p, "LateralTune", _LATERAL_TUNE_KEYS)),
|
||||
enabled=lambda: cs.hasNNFFLog and not cs.isAngleCar,
|
||||
disabled_label=tr("Not Available"),
|
||||
visible=alt_on),
|
||||
SettingRow("NNFFLite", "toggle", tr("NNFF Lite"),
|
||||
subtitle=tr("Lightweight NNFF when full model is off."),
|
||||
get_state=lambda: p.get_bool("NNFFLite"),
|
||||
set_state=lambda s: (p.put_bool("NNFFLite", s),
|
||||
_sync_parent(p, "LateralTune", _LATERAL_TUNE_KEYS)),
|
||||
enabled=lambda: not cs.isAngleCar,
|
||||
disabled_label=tr("Not Available"),
|
||||
visible=alt_on),
|
||||
SettingRow("ForceTorqueController", "toggle", tr("Force Torque Ctrl"),
|
||||
subtitle=tr("Torque-based steering for smoother lane keeping."),
|
||||
get_state=lambda: p.get_bool("ForceTorqueController"),
|
||||
set_state=lambda s: (p.put_bool("ForceTorqueController", s),
|
||||
_sync_parent(p, "LateralTune", _LATERAL_TUNE_KEYS)),
|
||||
enabled=lambda: not cs.isTorqueCar and not cs.isAngleCar,
|
||||
disabled_label=tr("Not Available"),
|
||||
visible=alt_on),
|
||||
SettingRow("TurnDesires", "toggle", tr("Force Turn Desires"),
|
||||
subtitle=tr("Follow turn intent below min lane change speed."),
|
||||
get_state=lambda: p.get_bool("TurnDesires"),
|
||||
set_state=lambda s: (p.put_bool("TurnDesires", s),
|
||||
_sync_parent(p, "LateralTune", _LATERAL_TUNE_KEYS)),
|
||||
visible=alt_on),
|
||||
SettingRow("ForceAutoTune", "toggle", tr("Force Auto-Tune On"),
|
||||
subtitle=tr("Force-enable live auto-tuning for friction and lateral accel."),
|
||||
get_state=lambda: p.get_bool("ForceAutoTune"),
|
||||
set_state=lambda s: (p.put_bool("ForceAutoTune", s),
|
||||
s and p.put_bool("ForceAutoTuneOff", False),
|
||||
_sync_parent(p, "AdvancedLateralTune", _ADVANCED_LATERAL_KEYS)),
|
||||
enabled=lambda: not cs.hasAutoTune and cs.isTorqueCar and not cs.isAngleCar,
|
||||
disabled_label=tr("Not Available"),
|
||||
visible=alt_on),
|
||||
SettingRow("ForceAutoTuneOff", "toggle", tr("Force Auto-Tune Off"),
|
||||
subtitle=tr("Force-disable auto-tuning and use your set values."),
|
||||
get_state=lambda: p.get_bool("ForceAutoTuneOff"),
|
||||
set_state=lambda s: (p.put_bool("ForceAutoTuneOff", s),
|
||||
s and p.put_bool("ForceAutoTune", False),
|
||||
_sync_parent(p, "AdvancedLateralTune", _ADVANCED_LATERAL_KEYS)),
|
||||
enabled=lambda: cs.hasAutoTune and cs.isTorqueCar and not cs.isAngleCar,
|
||||
disabled_label=tr("Not Available"),
|
||||
visible=alt_on),
|
||||
SettingRow("SteerDelay", "value", tr("Actuator Delay"),
|
||||
subtitle=tr("Time between steering command and vehicle response."),
|
||||
get_value=lambda: f"{p.get_float('SteerDelay'):.2f}s",
|
||||
on_click=_show("SteerDelay"),
|
||||
visible=lambda: alt_on() and cs.steerActuatorDelay != 0),
|
||||
SettingRow("SteerFriction", "value", tr("Friction"),
|
||||
subtitle=tr("Compensates for steering friction around center."),
|
||||
get_value=lambda: f"{p.get_float('SteerFriction'):.3f}",
|
||||
on_click=_show("SteerFriction"),
|
||||
visible=lambda: alt_on() and cs.friction != 0),
|
||||
SettingRow("SteerKP", "value", tr("Kp Factor"),
|
||||
subtitle=tr("How strongly openpilot corrects lateral position."),
|
||||
get_value=lambda: f"{p.get_float('SteerKP'):.2f}",
|
||||
on_click=_show("SteerKP"),
|
||||
visible=lambda: alt_on() and cs.steerKp != 0),
|
||||
SettingRow("SteerLatAccel", "value", tr("Lateral Acceleration"),
|
||||
subtitle=tr("Maps steering torque to turning response."),
|
||||
get_value=lambda: f"{p.get_float('SteerLatAccel'):.2f}",
|
||||
on_click=_show("SteerLatAccel"),
|
||||
visible=lambda: alt_on() and cs.latAccelFactor != 0),
|
||||
SettingRow("SteerRatio", "value", tr("Steer Ratio"),
|
||||
subtitle=tr("Relationship between steering wheel and road-wheel angle."),
|
||||
get_value=lambda: f"{p.get_float('SteerRatio'):.2f}",
|
||||
on_click=_show("SteerRatio"),
|
||||
visible=lambda: alt_on() and cs.steerRatio != 0),
|
||||
]
|
||||
|
||||
# ── Build sub-panels ──
|
||||
self._sub_panels["steering_behavior"] = _BackButtonSettingsView(
|
||||
self,
|
||||
[SettingSection(tr("Steering Behavior"), self._steering_behavior_rows, row_height=ROW_HEIGHT)],
|
||||
header_title=tr("Steering Behavior"),
|
||||
header_subtitle=tr("Configure when steering engages, pauses, and resumes."),
|
||||
panel_style=PANEL_STYLE,
|
||||
metrics=CUSTOM_METRICS,
|
||||
)
|
||||
|
||||
self._sub_panels["lane_changes"] = _BackButtonSettingsView(
|
||||
self,
|
||||
[SettingSection(tr("Lane Changes"), self._lane_change_rows, row_height=ROW_HEIGHT)],
|
||||
header_title=tr("Lane Changes"),
|
||||
header_subtitle=tr("Configure lane change behavior, speed thresholds, and timing."),
|
||||
panel_style=PANEL_STYLE,
|
||||
metrics=CUSTOM_METRICS,
|
||||
)
|
||||
|
||||
self._sub_panels["tuning"] = _BackButtonSettingsView(
|
||||
self,
|
||||
[SettingSection(tr("Advanced Lateral Tuning"), self._tuning_rows, row_height=ROW_HEIGHT)],
|
||||
header_title=tr("Advanced Lateral Tuning"),
|
||||
header_subtitle=tr("Fine-tune steering response, feedforward controllers, and auto-tuning."),
|
||||
panel_style=PANEL_STYLE,
|
||||
metrics=CUSTOM_METRICS,
|
||||
)
|
||||
|
||||
self._wire_sub_panels()
|
||||
|
||||
def _on_select(self, key: str):
|
||||
if key == "PauseAOLOnBrake":
|
||||
@@ -540,4 +453,4 @@ class StarPilotLateralLayout(_SettingsPage):
|
||||
self._params.put_int("LaneChangeSmoothing", int(val))
|
||||
current = self._params.get_int("LaneChangeSmoothing") if self._params.get_int("LaneChangeSmoothing") > 0 else 10
|
||||
gui_app.push_widget(AetherSliderDialog(tr("Lane Change Smoothing"), 1, 10, 1, current, on_close,
|
||||
color=self.SLIDER_COLOR))
|
||||
color=self.SLIDER_COLOR))
|
||||
|
||||
Reference in New Issue
Block a user