from cereal import log
from openpilot.common.params import Params, UnknownKeyName
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.list_view import multiple_button_item, toggle_item
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets import DialogResult
from openpilot.selfdrive.ui.ui_state import ui_state
if gui_app.iqpilot_ui():
from openpilot.system.ui.iqwidgets.widgets.list_view import toggle_item
from openpilot.system.ui.iqwidgets.widgets.list_view import multiple_button_item
from openpilot.iqpilot.ui.layouts.settings.iq_dynamic import IQDynamicLayout
PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants
PERSONALITY_DISPLAY_TO_PARAM = [PERSONALITY_TO_INT["relaxed"], PERSONALITY_TO_INT["standard"], PERSONALITY_TO_INT["aggressive"]]
PERSONALITY_PARAM_TO_DISPLAY = {param: idx for idx, param in enumerate(PERSONALITY_DISPLAY_TO_PARAM)}
# Description constants
DESCRIPTIONS = {
"OpenpilotEnabledToggle": tr_noop(
"Use the IQ.Pilot system for adaptive cruise control and lane keep driver assistance. " +
"Your attention is required at all times to use this feature."
),
"DisengageOnAccelerator": tr_noop("When enabled, pressing the accelerator pedal will disengage IQ.Pilot."),
"LongitudinalPersonality": tr_noop(
"Standard is recommended. In aggressive mode, IQ.Pilot will follow lead cars closer and be more aggressive with the gas and brake. " +
"In relaxed mode IQ.Pilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " +
"your steering wheel distance button."
),
"IQSpeedAssistMode": tr_noop(
"Controls IQ.Pilot speed limit behavior. Off disables speed limit features, Information only displays limits, Warning highlights overspeed, and Control adjusts set speed using detected limits."
),
"IsLdwEnabled": tr_noop(
"Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line " +
"without a turn signal activated while driving over 31 mph (50 km/h)."
),
"AlwaysOnDM": tr_noop("Enable driver monitoring even when IQ.Pilot is not engaged."),
"DashcamEnabled": tr_noop("Record and upload driving data and video. Disabling this stops all recording! No logs, no video, no audio."),
'RecordFront': tr_noop("Upload data from the driver facing camera and help improve the driver monitoring algorithm."),
"IsMetric": tr_noop("Display speed in km/h instead of mph."),
"IQAutoUnits": tr_noop(
"Set the units from the device location. Speeds switch to km/h everywhere except the United States, " +
"the United Kingdom and Liberia, and are re-checked when you cross a border."
),
"RecordAudio": tr_noop("Record and store microphone audio while driving. The audio will be included in the dashcam video in Konn3kt."),
"LongitudinalControlMode": tr_noop(
"Choose longitudinal behavior: IQ.Pilot (IQ longitudinal + end-to-end), "
"IQ.Dynamic (IQ longitudinal + dynamic mode), IQ.Standard (IQ longitudinal + relaxed personality), "
"or Stock ACC."
),
}
class TogglesLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
# Keep IQ.Pilot enabled by default; the UI no longer exposes this toggle.
self._params.put_bool("OpenpilotEnabledToggle", True)
# param, title, desc, icon, needs_restart
self._toggle_defs = {
"DisengageOnAccelerator": (
lambda: tr("Disengage on Accelerator Pedal"),
DESCRIPTIONS["DisengageOnAccelerator"],
"disengage_on_accelerator.png",
False,
),
"IsLdwEnabled": (
lambda: tr("Enable Lane Departure Warnings"),
DESCRIPTIONS["IsLdwEnabled"],
"warning.png",
False,
),
"DashcamEnabled": (
lambda: tr("Enable Dashcam"),
DESCRIPTIONS["DashcamEnabled"],
"camera.png",
True,
),
"RecordFront": (
lambda: tr("Record and Upload Driver Camera"),
DESCRIPTIONS["RecordFront"],
"monitoring.png",
True,
),
"RecordAudio": (
lambda: tr("Record and Upload Microphone Audio"),
DESCRIPTIONS["RecordAudio"],
"microphone.png",
True,
),
"IsMetric": (
lambda: tr("Use Metric System"),
DESCRIPTIONS["IsMetric"],
"metric.png",
False,
),
"IQAutoUnits": (
lambda: tr("Set Units From Location"),
DESCRIPTIONS["IQAutoUnits"],
"metric.png",
False,
),
}
self._long_personality_setting = multiple_button_item(
lambda: tr("Driving Personality"),
lambda: tr(DESCRIPTIONS["LongitudinalPersonality"]),
buttons=[lambda: tr("Relaxed"), lambda: tr("Standard"), lambda: tr("Aggressive")],
button_width=300,
callback=self._set_longitudinal_personality,
selected_index=PERSONALITY_PARAM_TO_DISPLAY.get(self._params.get("LongitudinalPersonality", return_default=True), 1),
icon="speed_limit.png"
)
self._speed_limit_mode_setting = multiple_button_item(
lambda: tr("Speed Limit"),
lambda: tr(DESCRIPTIONS["IQSpeedAssistMode"]),
buttons=[lambda: tr("Off"), lambda: tr("Info"), lambda: tr("Warning"), lambda: tr("Control")],
button_width=220,
callback=self._set_speed_limit_mode,
selected_index=self._params.get("IQSpeedAssistMode", return_default=True),
icon="speed_limit.png",
)
self._longitudinal_control_mode_setting = multiple_button_item(
lambda: tr("Longitudinal Control"),
lambda: tr(DESCRIPTIONS["LongitudinalControlMode"]),
buttons=[lambda: tr("Stock ACC"), lambda: tr("IQ.Standard"), lambda: tr("IQ.Dynamic"), lambda: tr("IQ.Pilot")],
button_width=250,
callback=self._set_longitudinal_control_mode,
selected_index=self._get_longitudinal_control_mode_index(),
icon="experimental_white.png",
)
self._toggles = {}
self._locked_toggles = set()
self._toggles["LongitudinalControlMode"] = self._longitudinal_control_mode_setting
self._toggles["LongitudinalPersonality"] = self._long_personality_setting
self._toggles["IQSpeedAssistMode"] = self._speed_limit_mode_setting
for param, (title, desc, icon, needs_restart) in self._toggle_defs.items():
initial_state = self._params.get_bool(param)
toggle = toggle_item(
title,
desc,
initial_state,
callback=lambda state, p=param: self._toggle_callback(state, p),
icon=icon,
)
try:
locked = self._params.get_bool(param + "Lock")
except UnknownKeyName:
locked = False
toggle.action_item.set_enabled(not locked)
# Make description callable for live translation
additional_desc = ""
if needs_restart and not locked:
additional_desc = tr("Changing this setting will restart IQ.Pilot if the car is powered on.")
toggle.set_description(lambda og_desc=toggle.description, add_desc=additional_desc: tr(og_desc) + (" " + tr(add_desc) if add_desc else ""))
# track for engaged state updates
if locked:
self._locked_toggles.add(param)
self._toggles[param] = toggle
self._scroller = Scroller(list(self._toggles.values()), line_separator=True, spacing=0)
self._iq_dynamic_panel: "IQDynamicLayout | None" = None
self._show_iq_dynamic = False
if gui_app.iqpilot_ui():
self._iq_dynamic_panel = IQDynamicLayout(self._close_iq_dynamic_panel)
ui_state.add_engaged_transition_callback(self._update_toggles)
def _update_state(self):
if ui_state.sm.updated["selfdriveState"]:
personality = PERSONALITY_TO_INT[ui_state.sm["selfdriveState"].personality]
if personality != ui_state.personality and ui_state.started:
self._long_personality_setting.action_item.set_selected_button(PERSONALITY_PARAM_TO_DISPLAY.get(personality, 1))
ui_state.personality = personality
self._speed_limit_mode_setting.action_item.set_selected_button(self._params.get("IQSpeedAssistMode", return_default=True))
def _close_iq_dynamic_panel(self):
self._show_iq_dynamic = False
def set_cruise_panel_callback(self, callback: "Callable") -> None:
"""Register callback invoked on double-click of IQ.Dynamic (button index 2)."""
action = self._longitudinal_control_mode_setting.action_item
if hasattr(action, 'set_double_click_callback'):
action.set_double_click_callback(2, callback)
def show_event(self):
self._show_iq_dynamic = False
self._scroller.show_event()
self._update_toggles()
def _update_toggles(self):
ui_state.update_params()
e2e_description = tr(
"Longitudinal Control modes:
" +
"IQ.Pilot features are listed below:
" +
"
{self._toggles['LongitudinalControlMode'].description}
") dlg = ConfirmDialog(content, tr("Enable"), rich=True) gui_app.set_modal_overlay(dlg, callback=confirm_callback) else: self._apply_longitudinal_control_mode(button_index) self._update_toggles()