mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
262 lines
11 KiB
Python
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)
|