Files
StarPilot/openpilot/selfdrive/ui/layouts/settings/toggles.py
T

262 lines
11 KiB
Python

from openpilot.starpilot.car.ford.longitudinal_preferences import release_long_available as ford_release_long_available
from openpilot.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 ListItem, multiple_button_item, toggle_item
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.selfdrive.ui.layouts.settings.initial_cruise_speed import InitialCruiseSpeedControl
from openpilot.starpilot.ui.slc_offset_feature import SlcOffsetOwner, native_parked
PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants
# Description constants
DESCRIPTIONS = {
"OpenpilotEnabledToggle": tr_noop(
"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": tr_noop("When enabled, pressing the accelerator pedal will disengage openpilot."),
"LongitudinalPersonality": tr_noop(
"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": 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 openpilot is not engaged."),
'RecordFront': tr_noop("Upload data from the cabin camera and help improve the driver monitoring algorithm."),
"IsMetric": tr_noop("Display speed in km/h instead of mph."),
"RecordAudio": tr_noop("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._slc_offsets = SlcOffsetOwner(self._params, lambda: native_parked(ui_state), lambda: ui_state.CP)
self._metric_source = self._slc_offsets._raw("IsMetric")
self._is_release = self._params.get_bool("IsReleaseBranch")
# param, title, desc, icon, needs_restart
self._toggle_defs = {
"OpenpilotEnabledToggle": (
lambda: tr("Enable openpilot"),
DESCRIPTIONS["OpenpilotEnabledToggle"],
"chffr_wheel.png",
True,
),
"ExperimentalMode": (
lambda: tr("Experimental Mode"),
"",
"experimental_white.png",
False,
),
"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,
),
"AlwaysOnDM": (
lambda: tr("Always-On Driver Monitoring"),
DESCRIPTIONS["AlwaysOnDM"],
"monitoring.png",
False,
),
"RecordFront": (
lambda: tr("Record and Upload Cabin 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,
),
}
self._long_personality_setting = multiple_button_item(
lambda: tr("Driving Personality"),
lambda: tr(DESCRIPTIONS["LongitudinalPersonality"]),
buttons=[lambda: tr("Aggressive"), lambda: tr("Standard"), lambda: tr("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._initial_cruise_speed = ListItem(
title=lambda: tr("Initial Max Speed"),
description=lambda: tr("Max speed when engaging openpilot. If you are already driving faster, your current speed is used. " +
"Resume restores the previous max speed. Changes apply the next time you engage."),
action_item=InitialCruiseSpeedControl(),
icon="speed_limit.png",
)
self._initial_cruise_speed.set_visible(lambda: ui_state.CP is not None and ui_state.has_longitudinal_control and not ui_state.CP.pcmCruise)
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)
# Make description callable for live translation
additional_desc = ""
if needs_restart and not locked:
additional_desc = tr("Changing this setting will restart openpilot 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
# insert longitudinal personality after NDOG toggle
if param == "DisengageOnAccelerator":
self._toggles["LongitudinalPersonality"] = self._long_personality_setting
self._toggles["InitialCruiseSpeedKph"] = self._initial_cruise_speed
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):
super().show_event()
self._scroller.show_event()
self._update_toggles()
def _update_toggles(self):
ui_state.update_params()
self._initial_cruise_speed.action_item.refresh()
e2e_description = tr(
"<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. Mistakes should be expected.<br>"
)
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 = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.")
long_desc = unavailable + " " + tr("openpilot longitudinal control may come in a future update.")
if ui_state.CP.alphaLongitudinalAvailable:
if self._is_release and not ford_release_long_available(ui_state.CP):
long_desc = unavailable + " " + tr("An alpha version of openpilot longitudinal control can be tested, along with " +
"Experimental mode, on non-release branches.")
else:
long_desc = tr("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))
self._metric_source = self._slc_offsets._raw("IsMetric")
# 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 _toggle_callback(self, state: bool, param: str):
if param == "IsMetric":
ok = self._slc_offsets.change_units(state, self._metric_source)
self._toggles[param].action_item.set_state(self._params.get_bool(param))
self._metric_source = self._slc_offsets._raw("IsMetric")
self._update_toggles()
return ok
self._params.put_bool(param, state, block=True)
self._toggles[param].action_item.set_state(state)
if param == "ExperimentalMode":
self._update_experimental_mode_icon()
if self._toggle_defs[param][3]:
self._params.put_bool("OnroadCycleRequested", True, block=True)
return True
def request_toggle(self, param: str, desired: bool) -> bool:
"""Apply a typed shell request through the existing toggle guards."""
if param not in self._toggle_defs:
return False
self._update_toggles()
if not self._toggles[param].action_item.enabled:
return False
if self._params.get_bool(param) != desired or param == "IsMetric" and self._metric_source not in (b"0", b"1"):
return bool(self._toggle_callback(desired, param))
return True
def request_personality(self, index: int) -> bool:
if index not in (0, 1, 2):
return False
self._update_toggles()
if not self._long_personality_setting.action_item.enabled:
return False
if self._params.get("LongitudinalPersonality", return_default=True) != index:
self._set_longitudinal_personality(index)
return True
def _set_longitudinal_personality(self, button_index: int):
self._params.put("LongitudinalPersonality", button_index, block=True)
self._long_personality_setting.action_item.set_selected_button(button_index)