""" Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.common.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.sunnypilot.widgets.list_view import option_item_sp, multiple_button_item_sp, button_item_sp, \ dual_button_item_sp, Spacer from openpilot.system.ui.widgets import DialogResult from openpilot.system.ui.widgets.button import ButtonStyle from openpilot.system.ui.widgets.confirm_dialog import alert_dialog, ConfirmDialog from openpilot.system.ui.widgets.list_view import text_item from openpilot.system.ui.widgets.scroller_tici import LineSeparator offroad_time_options = { 0: 0, 1: 5, 2: 10, 3: 15, 4: 30, 5: 60, 6: 120, 7: 180, 8: 300, 9: 600, 10: 1440, 11: 1800, } class DeviceLayoutSP(DeviceLayout): def __init__(self): DeviceLayout.__init__(self) self._scroller._line_separator = None def _initialize_items(self): DeviceLayout._initialize_items(self) # Using dual button with no right button for better alignment self._always_offroad_btn = dual_button_item_sp( left_text=lambda: tr("Enable Always Offroad"), left_callback=self._handle_always_offroad, right_text="", right_callback=None, ) self._always_offroad_btn.action_item.right_button.set_visible(False) self._max_time_offroad = option_item_sp( title=lambda: tr("Max Time Offroad"), description=lambda: tr("Device will automatically shutdown after set time once the engine is turned off.\n(30h is the default)"), param="MaxTimeOffroad", min_value=0, max_value=11, value_change_step=1, on_value_changed=None, enabled=True, icon="", value_map=offroad_time_options, label_width=360, use_float_scaling=False, inline=True, label_callback=self._update_max_time_offroad_label ) self._device_wake_mode = multiple_button_item_sp( title=lambda: tr("Wake Up Behavior"), description=self.wake_mode_description, param="DeviceBootMode", buttons=[lambda: tr("Default"), lambda: tr("Offroad")], button_width=364, callback=None, inline=True, ) self._quiet_mode_and_dcam = dual_button_item_sp( left_text=lambda: tr("Quiet Mode"), right_text=lambda: tr("Driver Camera Preview"), left_callback=lambda: ui_state.params.put_bool("QuietMode", not ui_state.params.get_bool("QuietMode")), right_callback=lambda: gui_app.push_widget(DriverCameraDialog()) ) self._quiet_mode_and_dcam.action_item.right_button.set_button_style(ButtonStyle.NORMAL) self._reg_and_training = dual_button_item_sp( left_text=lambda: tr("Regulatory"), left_callback=self._on_regulatory, right_text=lambda: tr("Training Guide"), right_callback=self._on_review_training_guide ) self._reg_and_training.action_item.right_button.set_button_style(ButtonStyle.NORMAL) self._onroad_uploads_and_reset_settings = dual_button_item_sp( left_text=lambda: tr("Onroad Uploads"), left_callback=lambda: ui_state.params.put_bool("OnroadUploads", not ui_state.params.get_bool("OnroadUploads")), right_text=lambda: tr("Reset Settings"), right_callback=self._reset_settings ) self._power_buttons = dual_button_item_sp( left_text=lambda: tr("Reboot"), right_text=lambda: tr("Power Off"), left_callback=self._reboot_prompt, right_callback=self._power_off_prompt ) items = [ text_item(lambda: tr("Dongle ID"), self._params.get("DongleId") or (lambda: tr("N/A"))), LineSeparator(), text_item(lambda: tr("Serial"), self._params.get("HardwareSerial") or (lambda: tr("N/A"))), LineSeparator(), self._pair_device_btn, LineSeparator(), self._reset_calib_btn, LineSeparator(), button_item_sp(lambda: tr("Change Language"), lambda: tr("CHANGE"), callback=self._show_language_dialog), LineSeparator(), self._device_wake_mode, LineSeparator(), self._max_time_offroad, LineSeparator(height=10), self._quiet_mode_and_dcam, self._reg_and_training, self._onroad_uploads_and_reset_settings, Spacer(10), LineSeparator(height=10), self._power_buttons, ] return items def _offroad_transition(self): self._power_buttons.action_item.right_button.set_visible(ui_state.is_offroad()) @staticmethod def wake_mode_description() -> str: def_str = tr("Default: Device will boot/wake-up normally & will be ready to engage.") offrd_str = tr("Offroad: Device will be in Always Offroad mode after boot/wake-up.") header = tr("Controls state of the device after boot/sleep.") return f"{header}\n\n{def_str}\n{offrd_str}" @staticmethod def _reset_settings(): def _do_reset(result: int): if result == DialogResult.CONFIRM: for _key in ui_state.params.all_keys(): ui_state.params.remove(_key) HARDWARE.reboot() def _second_confirm(result: int): if result == DialogResult.CONFIRM: gui_app.push_widget(ConfirmDialog( text=tr("The reset cannot be undone. You have been warned."), confirm_text=tr("Confirm"), callback=_do_reset )) gui_app.push_widget(ConfirmDialog( text=tr("Are you sure you want to reset all sunnypilot settings to default? Once the settings are reset, there is no going back."), confirm_text=tr("Reset"), callback=_second_confirm )) @staticmethod def _handle_always_offroad(): if ui_state.engaged: gui_app.push_widget(alert_dialog(tr("Disengage to Enter Always Offroad Mode"))) return _offroad_mode_state = ui_state.params.get_bool("OffroadMode") _offroad_mode_str = tr("Are you sure you want to exit Always Offroad mode?") if _offroad_mode_state else \ tr("Are you sure you want to enter Always Offroad mode?") def _set_always_offroad(result: int): if result == DialogResult.CONFIRM and not ui_state.engaged: ui_state.params.put_bool("OffroadMode", not _offroad_mode_state) gui_app.push_widget(ConfirmDialog(_offroad_mode_str, tr("Confirm"), callback=lambda result: _set_always_offroad(result))) @staticmethod def _update_max_time_offroad_label(value: int) -> str: label = tr("Always On") if value == 0 else f"{value}" + tr("m") if value < 60 else f"{value // 60}" + tr("h") label += tr(" (Default)") if value == 1800 else "" return label def _update_state(self): super()._update_state() # Handle Always Offroad button always_offroad = ui_state.params.get_bool("OffroadMode") # Text & Color offroad_mode_btn_text = tr("Exit Always Offroad") if always_offroad else tr("Enable Always Offroad") offroad_mode_btn_style = ButtonStyle.PRIMARY if always_offroad else ButtonStyle.DANGER self._always_offroad_btn.action_item.left_button.set_text(offroad_mode_btn_text) self._always_offroad_btn.action_item.left_button.set_button_style(offroad_mode_btn_style) # Position if self._scroller._items.__contains__(self._always_offroad_btn): self._scroller._items.remove(self._always_offroad_btn) if ui_state.is_offroad() and not always_offroad: self._scroller._items.insert(len(self._scroller._items) - 1, self._always_offroad_btn) else: self._scroller._items.insert(0, self._always_offroad_btn) # Quiet Mode button self._quiet_mode_and_dcam.action_item.left_button.set_button_style(ButtonStyle.PRIMARY if ui_state.params.get_bool("QuietMode") else ButtonStyle.NORMAL) # Onroad Uploads self._onroad_uploads_and_reset_settings.action_item.left_button.set_button_style( ButtonStyle.PRIMARY if ui_state.params.get_bool("OnroadUploads") else ButtonStyle.NORMAL ) # Offroad only buttons self._quiet_mode_and_dcam.action_item.right_button.set_enabled(ui_state.is_offroad()) self._reg_and_training.action_item.left_button.set_enabled(ui_state.is_offroad()) self._reg_and_training.action_item.right_button.set_enabled(ui_state.is_offroad()) self._onroad_uploads_and_reset_settings.action_item.right_button.set_enabled(ui_state.is_offroad())