You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
240 lines
9.7 KiB
240 lines
9.7 KiB
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 import Scroller
|
|
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
|
|
from openpilot.system.ui.lib.application import gui_app
|
|
from openpilot.system.ui.widgets import DialogResult
|
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
|
|
|
PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants
|
|
|
|
# Description constants
|
|
DESCRIPTIONS = {
|
|
"OpenpilotEnabledToggle": (
|
|
"Use the openpilot system for adaptive cruise control and lane keep driver assistance. " +
|
|
"Your attention is required at all times to use this feature."
|
|
),
|
|
"DisengageOnAccelerator": "When enabled, pressing the accelerator pedal will disengage openpilot.",
|
|
"LongitudinalPersonality": (
|
|
"Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " +
|
|
"In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " +
|
|
"your steering wheel distance button."
|
|
),
|
|
"IsLdwEnabled": (
|
|
"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": "Enable driver monitoring even when openpilot is not engaged.",
|
|
'RecordFront': "Upload data from the driver facing camera and help improve the driver monitoring algorithm.",
|
|
"IsMetric": "Display speed in km/h instead of mph.",
|
|
"RecordAudio": "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect.",
|
|
}
|
|
|
|
|
|
class TogglesLayout(Widget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._params = Params()
|
|
self._is_release = self._params.get_bool("IsReleaseBranch")
|
|
|
|
# param, title, desc, icon, needs_restart
|
|
self._toggle_defs = {
|
|
"OpenpilotEnabledToggle": (
|
|
"Enable openpilot",
|
|
DESCRIPTIONS["OpenpilotEnabledToggle"],
|
|
"chffr_wheel.png",
|
|
True,
|
|
),
|
|
"ExperimentalMode": (
|
|
"Experimental Mode",
|
|
"",
|
|
"experimental_white.png",
|
|
False,
|
|
),
|
|
"DisengageOnAccelerator": (
|
|
"Disengage on Accelerator Pedal",
|
|
DESCRIPTIONS["DisengageOnAccelerator"],
|
|
"disengage_on_accelerator.png",
|
|
False,
|
|
),
|
|
"IsLdwEnabled": (
|
|
"Enable Lane Departure Warnings",
|
|
DESCRIPTIONS["IsLdwEnabled"],
|
|
"warning.png",
|
|
False,
|
|
),
|
|
"AlwaysOnDM": (
|
|
"Always-On Driver Monitoring",
|
|
DESCRIPTIONS["AlwaysOnDM"],
|
|
"monitoring.png",
|
|
False,
|
|
),
|
|
"RecordFront": (
|
|
"Record and Upload Driver Camera",
|
|
DESCRIPTIONS["RecordFront"],
|
|
"monitoring.png",
|
|
True,
|
|
),
|
|
"RecordAudio": (
|
|
"Record and Upload Microphone Audio",
|
|
DESCRIPTIONS["RecordAudio"],
|
|
"microphone.png",
|
|
True,
|
|
),
|
|
"IsMetric": (
|
|
"Use Metric System",
|
|
DESCRIPTIONS["IsMetric"],
|
|
"metric.png",
|
|
False,
|
|
),
|
|
}
|
|
|
|
self._long_personality_setting = multiple_button_item(
|
|
"Driving Personality",
|
|
DESCRIPTIONS["LongitudinalPersonality"],
|
|
buttons=["Aggressive", "Standard", "Relaxed"],
|
|
button_width=255,
|
|
callback=self._set_longitudinal_personality,
|
|
selected_index=self._params.get("LongitudinalPersonality", return_default=True),
|
|
icon="speed_limit.png"
|
|
)
|
|
|
|
self._toggles = {}
|
|
self._locked_toggles = set()
|
|
for param, (title, desc, icon, needs_restart) in self._toggle_defs.items():
|
|
toggle = toggle_item(
|
|
title,
|
|
desc,
|
|
self._params.get_bool(param),
|
|
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)
|
|
|
|
if needs_restart and not locked:
|
|
toggle.set_description(toggle.description + " Changing this setting will restart openpilot if the car is powered on.")
|
|
|
|
# track for engaged state updates
|
|
if locked:
|
|
self._locked_toggles.add(param)
|
|
|
|
self._toggles[param] = toggle
|
|
|
|
# insert longitudinal personality after NDOG toggle
|
|
if param == "DisengageOnAccelerator":
|
|
self._toggles["LongitudinalPersonality"] = self._long_personality_setting
|
|
|
|
self._update_experimental_mode_icon()
|
|
self._scroller = Scroller(list(self._toggles.values()), line_separator=True, spacing=0)
|
|
|
|
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)
|
|
ui_state.personality = personality
|
|
|
|
def show_event(self):
|
|
self._scroller.show_event()
|
|
self._update_toggles()
|
|
|
|
def _update_toggles(self):
|
|
ui_state.update_params()
|
|
|
|
e2e_description = (
|
|
"openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. " +
|
|
"Experimental features are listed below:<br>" +
|
|
"<h4>End-to-End Longitudinal Control</h4><br>" +
|
|
"Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. " +
|
|
"Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; " +
|
|
"mistakes should be expected.<br>" +
|
|
"<h4>New Driving Visualization</h4><br>" +
|
|
"The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. " +
|
|
"The Experimental mode logo will also be shown in the top right corner."
|
|
)
|
|
|
|
if ui_state.CP is not None:
|
|
if ui_state.has_longitudinal_control:
|
|
self._toggles["ExperimentalMode"].action_item.set_enabled(True)
|
|
self._toggles["ExperimentalMode"].set_description(e2e_description)
|
|
self._long_personality_setting.action_item.set_enabled(True)
|
|
else:
|
|
# no long for now
|
|
self._toggles["ExperimentalMode"].action_item.set_enabled(False)
|
|
self._toggles["ExperimentalMode"].action_item.set_state(False)
|
|
self._long_personality_setting.action_item.set_enabled(False)
|
|
self._params.remove("ExperimentalMode")
|
|
|
|
unavailable = "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control."
|
|
|
|
long_desc = unavailable + " openpilot longitudinal control may come in a future update."
|
|
if ui_state.CP.alphaLongitudinalAvailable:
|
|
if self._is_release:
|
|
long_desc = unavailable + " " + ("An alpha version of openpilot longitudinal control can be tested, along with " +
|
|
"Experimental mode, on non-release branches.")
|
|
else:
|
|
long_desc = "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode."
|
|
|
|
self._toggles["ExperimentalMode"].set_description("<b>" + long_desc + "</b><br><br>" + e2e_description)
|
|
else:
|
|
self._toggles["ExperimentalMode"].set_description(e2e_description)
|
|
|
|
self._update_experimental_mode_icon()
|
|
|
|
# TODO: make a param control list item so we don't need to manage internal state as much here
|
|
# refresh toggles from params to mirror external changes
|
|
for param in self._toggle_defs:
|
|
self._toggles[param].action_item.set_state(self._params.get_bool(param))
|
|
|
|
# these toggles need restart, block while engaged
|
|
for toggle_def in self._toggle_defs:
|
|
if self._toggle_defs[toggle_def][3] and toggle_def not in self._locked_toggles:
|
|
self._toggles[toggle_def].action_item.set_enabled(not ui_state.engaged)
|
|
|
|
def _render(self, rect):
|
|
self._scroller.render(rect)
|
|
|
|
def _update_experimental_mode_icon(self):
|
|
icon = "experimental.png" if self._toggles["ExperimentalMode"].action_item.get_state() else "experimental_white.png"
|
|
self._toggles["ExperimentalMode"].set_icon(icon)
|
|
|
|
def _handle_experimental_mode_toggle(self, state: bool):
|
|
confirmed = self._params.get_bool("ExperimentalModeConfirmed")
|
|
if state and not confirmed:
|
|
def confirm_callback(result: int):
|
|
if result == DialogResult.CONFIRM:
|
|
self._params.put_bool("ExperimentalMode", True)
|
|
self._params.put_bool("ExperimentalModeConfirmed", True)
|
|
else:
|
|
self._toggles["ExperimentalMode"].action_item.set_state(False)
|
|
self._update_experimental_mode_icon()
|
|
|
|
# show confirmation dialog
|
|
content = (f"<h2>{self._toggles['ExperimentalMode'].title}</h2><br>" +
|
|
f"<p>{self._toggles['ExperimentalMode'].description}</p>")
|
|
dlg = ConfirmDialog(content, "Enable", rich=True)
|
|
gui_app.set_modal_overlay(dlg, callback=confirm_callback)
|
|
else:
|
|
self._update_experimental_mode_icon()
|
|
self._params.put_bool("ExperimentalMode", state)
|
|
|
|
def _toggle_callback(self, state: bool, param: str):
|
|
if param == "ExperimentalMode":
|
|
self._handle_experimental_mode_toggle(state)
|
|
return
|
|
|
|
self._params.put_bool(param, state)
|
|
if self._toggle_defs[param][3]:
|
|
self._params.put_bool("OnroadCycleRequested", True)
|
|
|
|
def _set_longitudinal_personality(self, button_index: int):
|
|
self._params.put("LongitudinalPersonality", button_index)
|
|
|