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 chill mode. Experimental mode enables alpha-level features 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"<h1>{self._toggles['ExperimentalMode'].title}</h1><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)
 | |
| 
 |