mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-29 19:03:59 +08:00
BigUP WIP: Ugly lateral
This commit is contained in:
@@ -1047,6 +1047,11 @@ class BreadcrumbController:
|
||||
target_idx = int(target.split(":")[-1])
|
||||
while len(gui_app._nav_stack) > target_idx + 1:
|
||||
gui_app.pop_widget()
|
||||
elif target.startswith("action:panel_stack:"):
|
||||
target_idx = int(target.split(":")[-1])
|
||||
while len(layout._panel_stack) > target_idx + 1:
|
||||
layout._panel_stack.pop()
|
||||
layout._commit_navigation()
|
||||
|
||||
@staticmethod
|
||||
def build_path() -> list[tuple[str, str]]:
|
||||
@@ -1079,6 +1084,14 @@ class BreadcrumbController:
|
||||
if hasattr(widget, '_header_title') and widget._header_title:
|
||||
path.append((widget._header_title, f"action:nav_stack:{i+1}"))
|
||||
|
||||
for i, (panel_type, sub_panel_name) in enumerate(layout._panel_stack):
|
||||
panel = layout._panels[panel_type].instance
|
||||
if not panel or not hasattr(panel, '_sub_panels') or sub_panel_name not in panel._sub_panels:
|
||||
continue
|
||||
sub = panel._sub_panels[sub_panel_name]
|
||||
label = sub._header_title if hasattr(sub, '_header_title') and sub._header_title else sub_panel_name
|
||||
path.append((label, f"action:panel_stack:{i}"))
|
||||
|
||||
return path
|
||||
|
||||
def draw(self, rect: rl.Rectangle) -> None:
|
||||
|
||||
@@ -4,21 +4,25 @@ import pyray as rl
|
||||
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
from openpilot.system.ui.widgets import DialogResult
|
||||
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 (
|
||||
AetherAdjustorRow,
|
||||
AetherListMetrics,
|
||||
AetherSettingsView,
|
||||
AetherSliderDialog,
|
||||
DEFAULT_PANEL_STYLE,
|
||||
HubTile,
|
||||
SettingRow,
|
||||
SettingSection,
|
||||
PanelManagerView,
|
||||
TileGrid,
|
||||
draw_list_group_shell,
|
||||
draw_section_header,
|
||||
draw_settings_list_row,
|
||||
SECTION_GAP,
|
||||
SECTION_HEADER_HEIGHT,
|
||||
SECTION_HEADER_GAP,
|
||||
)
|
||||
|
||||
|
||||
@@ -47,8 +51,8 @@ CUSTOM_METRICS = AetherListMetrics(
|
||||
utility_row_height=88,
|
||||
)
|
||||
|
||||
ROW_HEIGHT = CUSTOM_METRICS.row_height
|
||||
PANEL_STYLE = DEFAULT_PANEL_STYLE
|
||||
ALT_ROW_HEIGHT = 88.0
|
||||
|
||||
_LATERAL_TUNE_KEYS = ["TurnDesires", "NNFF", "NNFFLite", "ForceTorqueController"]
|
||||
_ADVANCED_LATERAL_KEYS = ["ForceAutoTune", "ForceAutoTuneOff"]
|
||||
@@ -63,89 +67,253 @@ def _sync_parent(params, parent_key, child_keys):
|
||||
params.put_bool(parent_key, False)
|
||||
|
||||
|
||||
class SteeringManagerView(AetherSettingsView):
|
||||
"""Hub view with 3 category tiles leading to sub-panels."""
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SteeringSubPanelView — two-column sub-panel
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SteeringSubPanelView(PanelManagerView):
|
||||
"""Two-column sub-panel: left = AetherAdjustorRows (values),
|
||||
right = TileGrid of ToggleTile (toggles).
|
||||
Dynamic visibility via _rebuild_content()."""
|
||||
|
||||
METRICS = CUSTOM_METRICS
|
||||
|
||||
@property
|
||||
def vertical_scrolling_disabled(self) -> bool:
|
||||
return True
|
||||
|
||||
def __init__(self, controller, sections, **kwargs):
|
||||
super().__init__(controller, sections, **kwargs)
|
||||
def __init__(self, controller, header_title, header_subtitle,
|
||||
adjustor_defs, toggle_defs, include_alt_row=False):
|
||||
super().__init__()
|
||||
self._controller = controller
|
||||
self._hub_grid = TileGrid(columns=3, padding=12)
|
||||
self._hub_grid.set_touch_valid_callback(lambda: self._scroll_panel.is_touch_valid())
|
||||
self._child(self._hub_grid)
|
||||
self._init_hub()
|
||||
self._header_title = header_title
|
||||
self._header_subtitle = header_subtitle
|
||||
self._adjustor_defs = adjustor_defs
|
||||
self._toggle_defs = toggle_defs
|
||||
self._include_alt_row = include_alt_row
|
||||
self._adjustor_rows: dict[str, AetherAdjustorRow] = {}
|
||||
self._left_container_h = 0.0
|
||||
self._tiles_container_h = 0.0
|
||||
|
||||
self._toggle_grid = TileGrid(
|
||||
columns=2, padding=12, force_square=True,
|
||||
min_tile_height=130.0, max_tile_height=280.0,
|
||||
)
|
||||
self.register_page_grid(self._toggle_grid)
|
||||
|
||||
self._rebuild_content()
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
self._rebuild_adjustors()
|
||||
super()._render(rect)
|
||||
|
||||
def _rebuild_content(self):
|
||||
self._rebuild_adjustors()
|
||||
self._rebuild_toggle_pages()
|
||||
|
||||
def _rebuild_adjustors(self):
|
||||
old_rows = self._adjustor_rows
|
||||
self._adjustor_rows = {}
|
||||
|
||||
for defn in self._adjustor_defs:
|
||||
if defn.get("visible") is not None and not defn["visible"]():
|
||||
continue
|
||||
key = defn["key"]
|
||||
if key in old_rows:
|
||||
self._adjustor_rows[key] = old_rows[key]
|
||||
else:
|
||||
self._adjustor_rows[key] = AetherAdjustorRow(
|
||||
tr(defn["title"]),
|
||||
tr(defn.get("subtitle", "")),
|
||||
defn["min_val"], defn["max_val"], defn["step"],
|
||||
get_value=lambda k=key: self._controller._params.get_float(k),
|
||||
on_change=lambda _v: None,
|
||||
on_commit=None,
|
||||
unit=defn.get("unit", ""),
|
||||
labels=defn.get("labels"),
|
||||
presets=defn.get("presets", []),
|
||||
is_active=lambda: False,
|
||||
set_active=lambda active, k=key: self._show_slider_for(k) if active else None,
|
||||
style=PANEL_STYLE,
|
||||
color=PANEL_STYLE.accent,
|
||||
)
|
||||
|
||||
def _rebuild_toggle_pages(self):
|
||||
visible = [
|
||||
d for d in self._toggle_defs
|
||||
if d.get("visible") is None or d["visible"]()
|
||||
]
|
||||
wrapped = []
|
||||
for d in visible:
|
||||
entry = dict(d)
|
||||
original_set = entry.get("set")
|
||||
if original_set:
|
||||
def make_wrapped(orig):
|
||||
def fn(state):
|
||||
orig(state)
|
||||
self._rebuild_content()
|
||||
return fn
|
||||
entry["set"] = make_wrapped(original_set)
|
||||
wrapped.append(entry)
|
||||
pages = [wrapped[i:i+4] for i in range(0, len(wrapped), 4)]
|
||||
old_page = self._current_page
|
||||
self._toggle_pages = pages
|
||||
self._page_count = max(1, len(pages))
|
||||
self._current_page = min(old_page, self._page_count - 1)
|
||||
self._on_page_changed()
|
||||
|
||||
def _show_slider_for(self, key: str):
|
||||
self._controller._on_select(key)
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
starpilot_state.update(force=True)
|
||||
self._rebuild_content()
|
||||
|
||||
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",
|
||||
},
|
||||
]
|
||||
# ── layout / rendering ──
|
||||
|
||||
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):
|
||||
pass
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
self.set_rect(rect)
|
||||
self._interactive_rects.clear()
|
||||
def _measure_content_height(self, content_width: float) -> float:
|
||||
col_width = (content_width - SECTION_GAP) / 2
|
||||
|
||||
margin_x = 18.0
|
||||
margin_y = 24.0
|
||||
for row in self._adjustor_rows.values():
|
||||
row.custom_row_height = None
|
||||
|
||||
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
|
||||
header_h = self._header_height()
|
||||
left_natural = sum(row.measure_height(col_width) for row in self._adjustor_rows.values())
|
||||
left_natural += 16.0
|
||||
|
||||
self._scroll_rect = rl.Rectangle(grid_x, grid_y, grid_w, grid_h)
|
||||
self._content_height = grid_h
|
||||
tiles_needed_h = self.measure_page_grid_height(self._toggle_grid, col_width - 24) + 24
|
||||
max_natural_h = max(left_natural, tiles_needed_h)
|
||||
section_overhead = SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP
|
||||
|
||||
self._scroll_panel.set_enabled(self.is_visible)
|
||||
self._scroll_offset = self._scroll_panel.update(
|
||||
self._scroll_rect, self._scroll_rect.height
|
||||
alt_consumption = (ALT_ROW_HEIGHT + 12.0) if self._include_alt_row else 0.0
|
||||
|
||||
if self._scroll_rect:
|
||||
available_h = self._scroll_rect.height - header_h - section_overhead - 6.0 - alt_consumption
|
||||
else:
|
||||
available_h = max_natural_h
|
||||
|
||||
max_container_h = max(0.0, available_h)
|
||||
|
||||
left_available = max_container_h - 16.0
|
||||
if self._adjustor_rows and left_available > 0:
|
||||
row_h = max(60.0, left_available / len(self._adjustor_rows))
|
||||
for row in self._adjustor_rows.values():
|
||||
row.custom_row_height = row_h
|
||||
|
||||
self._left_container_h = max_container_h
|
||||
self._tiles_container_h = max_container_h
|
||||
|
||||
return self._compute_two_column_height(header_h + section_overhead + max_container_h + alt_consumption)
|
||||
|
||||
def _header_height(self) -> float:
|
||||
h = 0.0
|
||||
if self._header_title:
|
||||
h += 30 + 6
|
||||
if self._header_subtitle:
|
||||
h += 22 + 4
|
||||
return h
|
||||
|
||||
def _draw_scroll_content(self, rect: rl.Rectangle, content_width: float):
|
||||
y = rect.y + self._scroll_offset
|
||||
|
||||
# Draw panel title + subtitle
|
||||
if self._header_title:
|
||||
rl.draw_text_ex(
|
||||
gui_app.font(FontWeight.SEMI_BOLD), self._header_title,
|
||||
rl.Vector2(rect.x + 8, y), 30, 0, PANEL_STYLE.title_color,
|
||||
)
|
||||
y += 30 + 6
|
||||
if self._header_subtitle:
|
||||
rl.draw_text_ex(
|
||||
gui_app.font(FontWeight.NORMAL), self._header_subtitle,
|
||||
rl.Vector2(rect.x + 8, y), 22, 0, PANEL_STYLE.subtitle_color,
|
||||
)
|
||||
y += 22 + 4
|
||||
else:
|
||||
y += 8
|
||||
|
||||
col_width = (content_width - SECTION_GAP) / 2
|
||||
|
||||
draw_section_header(
|
||||
rl.Rectangle(rect.x, y, col_width, SECTION_HEADER_HEIGHT),
|
||||
tr("Values"), style=PANEL_STYLE,
|
||||
)
|
||||
draw_section_header(
|
||||
rl.Rectangle(rect.x + col_width + SECTION_GAP, y, col_width, SECTION_HEADER_HEIGHT),
|
||||
tr("Toggles"), style=PANEL_STYLE,
|
||||
)
|
||||
y += SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP
|
||||
|
||||
self._draw_adjustor_column(y, rect.x, col_width)
|
||||
self._draw_two_column_tile_grid(
|
||||
self._toggle_grid,
|
||||
rect.x + col_width + SECTION_GAP, y, col_width,
|
||||
self._tiles_container_h, title=None, style=PANEL_STYLE,
|
||||
)
|
||||
|
||||
if self.vertical_scrolling_disabled:
|
||||
self._scroll_offset = 0.0
|
||||
if self._include_alt_row:
|
||||
alt_y = y + self._tiles_container_h + 12.0
|
||||
alt_rect = rl.Rectangle(rect.x, alt_y, content_width, ALT_ROW_HEIGHT)
|
||||
alt_state = self._controller._params.get_bool("AdvancedLateralTune")
|
||||
alt_value = tr("ON") if alt_state else tr("OFF")
|
||||
hovered, pressed = self._interactive_state("static:alt_row", alt_rect)
|
||||
draw_settings_list_row(
|
||||
alt_rect,
|
||||
title=tr("Advanced Lateral Tuning"),
|
||||
value=alt_value,
|
||||
enabled=True,
|
||||
hovered=hovered,
|
||||
pressed=pressed,
|
||||
is_last=False,
|
||||
show_chevron=True,
|
||||
title_size=30,
|
||||
subtitle_size=20,
|
||||
value_size=24,
|
||||
style=PANEL_STYLE,
|
||||
)
|
||||
|
||||
self._draw_scroll_content(self._scroll_rect, self._scroll_rect.width)
|
||||
def _draw_adjustor_column(self, y: float, x: float, width: float):
|
||||
draw_list_group_shell(
|
||||
rl.Rectangle(x, y, width, self._left_container_h),
|
||||
style=PANEL_STYLE,
|
||||
)
|
||||
current_y = y + 8
|
||||
for key, adjustor in self._adjustor_rows.items():
|
||||
row_h = adjustor.measure_height(width)
|
||||
row_rect = rl.Rectangle(x, current_y, width, row_h)
|
||||
adjustor.set_parent_rect(self._scroll_rect)
|
||||
adjustor.render(row_rect)
|
||||
current_y += row_h
|
||||
|
||||
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))
|
||||
# ── mouse forwarding ──
|
||||
|
||||
def _handle_mouse_press(self, mouse_pos):
|
||||
super()._handle_mouse_press(mouse_pos)
|
||||
for adjustor in self._adjustor_rows.values():
|
||||
adjustor._handle_mouse_press(mouse_pos)
|
||||
self._toggle_grid._handle_mouse_press(mouse_pos)
|
||||
|
||||
def _handle_mouse_release(self, mouse_pos):
|
||||
for adjustor in self._adjustor_rows.values():
|
||||
adjustor._handle_mouse_release(mouse_pos)
|
||||
self._toggle_grid._handle_mouse_release(mouse_pos)
|
||||
super()._handle_mouse_release(mouse_pos)
|
||||
|
||||
def _handle_mouse_event(self, mouse_event):
|
||||
super()._handle_mouse_event(mouse_event)
|
||||
for adjustor in self._adjustor_rows.values():
|
||||
adjustor._handle_mouse_event(mouse_event)
|
||||
self._toggle_grid._handle_mouse_event(mouse_event)
|
||||
|
||||
def _activate_target(self, target_id: str | None):
|
||||
if target_id == "static:alt_row":
|
||||
self._controller._navigate_to("tuning")
|
||||
return
|
||||
super()._activate_target(target_id)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
@@ -156,15 +324,9 @@ class StarPilotLateralLayout(_SettingsPage):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
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,
|
||||
)
|
||||
self._build_panels()
|
||||
|
||||
def _build_sub_panels(self):
|
||||
def _build_panels(self):
|
||||
p = self._params
|
||||
cs = starpilot_state.car_state
|
||||
|
||||
@@ -183,185 +345,245 @@ class StarPilotLateralLayout(_SettingsPage):
|
||||
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)),
|
||||
|
||||
sb_adjustors = [
|
||||
{
|
||||
"key": "PauseAOLOnBrake",
|
||||
"title": tr("Pause AOL On Brake"),
|
||||
"subtitle": tr("Pause AOL below this speed while brake is pressed."),
|
||||
"min_val": 0, "max_val": 100, "step": 1,
|
||||
"unit": " mph",
|
||||
"visible": aol_on,
|
||||
},
|
||||
{
|
||||
"key": "PauseLateralSpeed",
|
||||
"title": tr("Pause Lateral Below"),
|
||||
"subtitle": tr("Pause steering below the set speed."),
|
||||
"min_val": 0, "max_val": 100, "step": 1,
|
||||
"unit": " mph",
|
||||
},
|
||||
{
|
||||
"key": "LateralResumeDelay",
|
||||
"title": tr("Resume Delay"),
|
||||
"subtitle": tr("Delay before lateral resumes after signal off. 0 = Off."),
|
||||
"min_val": 0.0, "max_val": 5.0, "step": 0.1,
|
||||
"unit": "s",
|
||||
"labels": {0.0: tr("Off")},
|
||||
"visible": pos_on,
|
||||
},
|
||||
]
|
||||
|
||||
sb_toggles = [
|
||||
{
|
||||
"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),
|
||||
},
|
||||
{
|
||||
"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),
|
||||
},
|
||||
{
|
||||
"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),
|
||||
},
|
||||
]
|
||||
|
||||
# ── 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),
|
||||
|
||||
lc_adjustors = [
|
||||
{
|
||||
"key": "MinimumLaneChangeSpeed",
|
||||
"title": tr("Min Lane Change Speed"),
|
||||
"subtitle": tr("Lowest speed at which openpilot will change lanes."),
|
||||
"min_val": 0, "max_val": 100, "step": 1,
|
||||
"unit": " mph",
|
||||
"visible": lc_on,
|
||||
},
|
||||
{
|
||||
"key": "LaneChangeTime",
|
||||
"title": tr("Lane Change Delay"),
|
||||
"subtitle": tr("Delay before the start of an automatic lane change. 0 = Instant."),
|
||||
"min_val": 0.0, "max_val": 5.0, "step": 0.1,
|
||||
"unit": "s",
|
||||
"labels": {0.0: tr("Instant")},
|
||||
"visible": nlc_on,
|
||||
},
|
||||
{
|
||||
"key": "LaneDetectionWidth",
|
||||
"title": tr("Min Lane Width"),
|
||||
"subtitle": tr("Prevent lane changes into narrower lanes."),
|
||||
"min_val": 0.0, "max_val": 15.0, "step": 0.1,
|
||||
"unit": " ft",
|
||||
"visible": nlc_on,
|
||||
},
|
||||
{
|
||||
"key": "LaneChangeSmoothing",
|
||||
"title": tr("Lane Change Smoothing"),
|
||||
"subtitle": tr("Smoothness of lane change commit. 10 = Stock, 1 = Smoothest."),
|
||||
"min_val": 1, "max_val": 10, "step": 1,
|
||||
"labels": {10.0: tr("Stock")},
|
||||
"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),
|
||||
lc_toggles = [
|
||||
{
|
||||
"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),
|
||||
},
|
||||
{
|
||||
"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),
|
||||
"visible": lc_on,
|
||||
},
|
||||
{
|
||||
"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),
|
||||
"visible": nlc_on,
|
||||
},
|
||||
]
|
||||
|
||||
# ── Build sub-panels ──
|
||||
self._sub_panels["steering_behavior"] = AetherSettingsView(
|
||||
# Combined main panel: Steering Behavior + Lane Changes + ALT drill-down
|
||||
merged_adjustors = sb_adjustors + lc_adjustors
|
||||
merged_toggles = sb_toggles + lc_toggles
|
||||
|
||||
self._manager_view = SteeringSubPanelView(
|
||||
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,
|
||||
tr("Steering"),
|
||||
tr("Configure steering behavior and lane changes."),
|
||||
merged_adjustors, merged_toggles,
|
||||
include_alt_row=True,
|
||||
)
|
||||
|
||||
self._sub_panels["lane_changes"] = AetherSettingsView(
|
||||
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,
|
||||
)
|
||||
# ── Advanced Lateral Tuning (sub-panel, accessed via ALT row) ──
|
||||
|
||||
self._sub_panels["tuning"] = AetherSettingsView(
|
||||
tuning_adjustors = [
|
||||
{
|
||||
"key": "SteerDelay",
|
||||
"title": tr("Actuator Delay"),
|
||||
"subtitle": tr("Time between steering command and vehicle response."),
|
||||
"min_val": 0.01, "max_val": 1.0, "step": 0.01,
|
||||
"unit": "s",
|
||||
"visible": lambda: alt_on() and cs.steerActuatorDelay != 0,
|
||||
},
|
||||
{
|
||||
"key": "SteerFriction",
|
||||
"title": tr("Friction"),
|
||||
"subtitle": tr("Compensates for steering friction around center."),
|
||||
"min_val": 0.0, "max_val": max(1.0, cs.friction * 1.5), "step": 0.01,
|
||||
"visible": lambda: alt_on() and cs.friction != 0,
|
||||
},
|
||||
{
|
||||
"key": "SteerKP",
|
||||
"title": tr("Kp Factor"),
|
||||
"subtitle": tr("How strongly openpilot corrects lateral position."),
|
||||
"min_val": max(0.01, cs.steerKp) * 0.5, "max_val": max(0.01, cs.steerKp) * 1.5, "step": 0.01,
|
||||
"visible": lambda: alt_on() and cs.steerKp != 0,
|
||||
},
|
||||
{
|
||||
"key": "SteerLatAccel",
|
||||
"title": tr("Lateral Acceleration"),
|
||||
"subtitle": tr("Maps steering torque to turning response."),
|
||||
"min_val": max(0.01, cs.latAccelFactor) * 0.5, "max_val": max(0.01, cs.latAccelFactor) * 1.5, "step": 0.01,
|
||||
"visible": lambda: alt_on() and cs.latAccelFactor != 0,
|
||||
},
|
||||
{
|
||||
"key": "SteerRatio",
|
||||
"title": tr("Steer Ratio"),
|
||||
"subtitle": tr("Relationship between steering wheel and road-wheel angle."),
|
||||
"min_val": max(0.01, cs.steerRatio) * 0.5, "max_val": max(0.01, cs.steerRatio) * 1.5, "step": 0.01,
|
||||
"visible": lambda: alt_on() and cs.steerRatio != 0,
|
||||
},
|
||||
]
|
||||
|
||||
tuning_toggles = [
|
||||
{
|
||||
"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),
|
||||
},
|
||||
{
|
||||
"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)),
|
||||
"is_enabled": lambda: cs.hasNNFFLog and not cs.isAngleCar,
|
||||
"disabled_label": tr("Not Available"),
|
||||
"visible": alt_on,
|
||||
},
|
||||
{
|
||||
"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)),
|
||||
"is_enabled": lambda: not cs.isAngleCar,
|
||||
"disabled_label": tr("Not Available"),
|
||||
"visible": alt_on,
|
||||
},
|
||||
{
|
||||
"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)),
|
||||
"is_enabled": lambda: not cs.isTorqueCar and not cs.isAngleCar,
|
||||
"disabled_label": tr("Not Available"),
|
||||
"visible": alt_on,
|
||||
},
|
||||
{
|
||||
"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)),
|
||||
"visible": alt_on,
|
||||
},
|
||||
{
|
||||
"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)),
|
||||
"is_enabled": lambda: not cs.hasAutoTune and cs.isTorqueCar and not cs.isAngleCar,
|
||||
"disabled_label": tr("Not Available"),
|
||||
"visible": alt_on,
|
||||
},
|
||||
{
|
||||
"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)),
|
||||
"is_enabled": lambda: cs.hasAutoTune and cs.isTorqueCar and not cs.isAngleCar,
|
||||
"disabled_label": tr("Not Available"),
|
||||
"visible": alt_on,
|
||||
},
|
||||
]
|
||||
|
||||
self._sub_panels["tuning"] = SteeringSubPanelView(
|
||||
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,
|
||||
tr("Advanced Lateral Tuning"),
|
||||
tr("Fine-tune steering response, feedforward controllers, and auto-tuning."),
|
||||
tuning_adjustors, tuning_toggles,
|
||||
)
|
||||
|
||||
self._wire_sub_panels()
|
||||
|
||||
Reference in New Issue
Block a user