from openpilot.common.params import Params
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.confirm_dialog import DialogResult
from openpilot.system.ui.widgets.list_view import ListItem, ButtonAction, ITEM_TEXT_VALUE_COLOR, TextAction
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.frogpilot.common.frogpilot_variables import update_frogpilot_toggles
# Button function mappings
BUTTON_FUNCTIONS = {
0: "No Action",
3: "Pause Steering",
}
LONGITUDINAL_FUNCTIONS = {
1: "Change \"Personality Profile\"",
2: "Force openpilot to Coast",
4: "Pause Acceleration/Braking",
5: "Toggle \"Experimental Mode\" On/Off",
6: "Toggle \"Traffic Mode\" On/Off",
}
# Button parameter configurations
WHEEL_TOGGLES = [
("DistanceButtonControl", "Distance Button", "Action performed when the \"Distance\" button is pressed."),
("LongDistanceButtonControl", "Distance Button (Long Press)", "Action performed when the \"Distance\" button is pressed for more than 0.5 seconds."),
("VeryLongDistanceButtonControl", "Distance Button (Very Long Press)", "Action performed when the \"Distance\" button is pressed for more than 2.5 seconds."),
("LKASButtonControl", "LKAS Button", "Action performed when the \"LKAS\" button is pressed."),
]
class FrogPilotWheelPanel(Widget):
def __init__(self):
super().__init__()
self._params = Params()
self._toggles = {}
self._tuning_level = 0
# Car capabilities
self._has_openpilot_longitudinal = False
self._is_subaru = False
self._lkas_allowed_for_aol = False
# Pending dialog action tracking
self._pending_action = None # "select_function"
self._pending_param = None
self._build_panel()
ui_state.add_offroad_transition_callback(self._update_toggles)
def _build_panel(self):
items = []
for param, title, desc in WHEEL_TOGGLES:
control = ListItem(
title=title,
description=desc,
action_item=ButtonAction(
text="SELECT",
callback=lambda p=param: self._on_button_click(p),
),
)
# Store reference for updating value display
self._toggles[param] = control
items.append(control)
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _get_function_name(self, param: str) -> str:
"""Get the display name for the currently selected function."""
value = self._params.get_int(param)
# Check both base and longitudinal functions
all_functions = {**BUTTON_FUNCTIONS}
if self._has_openpilot_longitudinal:
all_functions.update(LONGITUDINAL_FUNCTIONS)
return all_functions.get(value, "No Action")
def _on_button_click(self, param: str):
"""Handle button click to open function selection dialog."""
# Build available functions list
functions = dict(BUTTON_FUNCTIONS)
if self._has_openpilot_longitudinal:
functions.update(LONGITUDINAL_FUNCTIONS)
# Get current selection
current_value = self._params.get_int(param)
current_name = functions.get(current_value, "No Action")
# Show selection dialog
self._pending_action = "select_function"
self._pending_param = param
gui_app.set_modal_overlay(MultiOptionDialog(
"Select a function to assign to this button",
list(functions.values()),
current_name,
))
def handle_dialog_result(self, result: DialogResult, selection: str = ""):
"""Handle dialog results for pending actions."""
action = self._pending_action
param = self._pending_param
self._pending_action = None
self._pending_param = None
if action == "select_function":
if result != DialogResult.CONFIRM or not selection:
return
# Find the function ID for the selected name
all_functions = {**BUTTON_FUNCTIONS, **LONGITUDINAL_FUNCTIONS}
function_id = None
for fid, name in all_functions.items():
if name == selection:
function_id = fid
break
if function_id is not None and param:
self._params.put_int(param, function_id)
update_frogpilot_toggles()
def _load_car_capabilities(self):
"""Load car capabilities from frogpilot variables."""
try:
from openpilot.frogpilot.common.frogpilot_variables import get_frogpilot_toggles
toggles = get_frogpilot_toggles()
self._has_openpilot_longitudinal = getattr(toggles, "has_openpilot_longitudinal", False)
self._is_subaru = getattr(toggles, "is_subaru", False)
self._lkas_allowed_for_aol = getattr(toggles, "lkas_allowed_for_aol", False)
except Exception:
pass
def _update_toggles(self):
self._tuning_level = self._params.get_int("TuningLevel") or 0
self._load_car_capabilities()
# LKAS button visibility
lkas_visible = True
if self._is_subaru:
lkas_visible = False
elif self._lkas_allowed_for_aol:
aol_enabled = self._params.get_bool("AlwaysOnLateral")
aol_lkas = self._params.get_bool("AlwaysOnLateralLKAS")
if aol_enabled and aol_lkas:
lkas_visible = False
lkas_control = self._toggles.get("LKASButtonControl")
if lkas_control and hasattr(lkas_control, "set_visible"):
lkas_control.set_visible(lkas_visible)
def show_event(self):
super().show_event()
self._scroller.show_event()
self._load_car_capabilities()
self._update_toggles()
def _render(self, rect):
self._scroller.render(rect)