ui: Developer panel extension (#1521)

* ui: developer panel

* comment out

* double translate

* quickboot and more efficient file checking

* use HtmlModalSP!

* ui: `HtmlModalSP`

* less

* lint

* less

* just use existing dir on PC

* grammar

* match official

* rename

* biiig

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
This commit is contained in:
James Vecellio-Grant
2025-12-12 21:50:57 -08:00
committed by GitHub
parent 46257aca02
commit cab2a28e10
3 changed files with 110 additions and 4 deletions

View File

@@ -0,0 +1,106 @@
"""
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.
"""
import datetime
import os
from pathlib import Path
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout
from openpilot.system.hardware import PC
from openpilot.system.hardware.hw import Paths
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.widgets import DialogResult
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
from openpilot.system.ui.widgets.list_view import button_item
from openpilot.system.ui.sunnypilot.widgets.html_render import HtmlModalSP
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp
PREBUILT_PATH = os.path.join(Paths.comma_home(), "prebuilt") if PC else "/data/openpilot/prebuilt"
class DeveloperLayoutSP(DeveloperLayout):
def __init__(self):
super().__init__()
self.error_log_path = os.path.join(Paths.crash_log_root(), "error.log")
self._is_release_branch: bool = self._is_release or ui_state.params.get_bool("IsReleaseSpBranch")
self._is_development_branch: bool = ui_state.params.get_bool("IsTestedBranch") or ui_state.params.get_bool("IsDevelopmentBranch")
self._initialize_items()
for item in self.items:
self._scroller.add_widget(item)
def _initialize_items(self):
self.show_advanced_controls = toggle_item_sp(tr("Show Advanced Controls"),
tr("Toggle visibility of advanced sunnypilot controls.<br>This only changes the visibility of the toggles; " +
"it does not change the actual enabled/disabled state."), param="ShowAdvancedControls")
self.enable_github_runner_toggle = toggle_item_sp(tr("GitHub Runner Service"), tr("Enables or disables the GitHub runner service."),
param="EnableGithubRunner")
self.enable_copyparty_toggle = toggle_item_sp(tr("copyparty Service"),
tr("copyparty is a very capable file server, you can use it to download your routes, view your logs " +
"and even make some edits on some files from your browser. " +
"Requires you to connect to your comma locally via its IP address."), param="EnableCopyparty")
self.prebuilt_toggle = toggle_item_sp(tr("Quickboot Mode"), "", param="QuickBootToggle", callback=self._on_prebuilt_toggled)
self.error_log_btn = button_item(tr("Error Log"), tr("VIEW"), tr("View the error log for sunnypilot crashes."), callback=self._on_error_log_clicked)
self.items: list = [self.show_advanced_controls, self.enable_github_runner_toggle, self.enable_copyparty_toggle, self.prebuilt_toggle, self.error_log_btn,]
@staticmethod
def _on_prebuilt_toggled(state):
if state:
Path(PREBUILT_PATH).touch(exist_ok=True)
else:
os.remove(PREBUILT_PATH)
ui_state.params.put_bool("QuickBootToggle", state)
def _on_delete_confirm(self, result):
if result == DialogResult.CONFIRM:
if os.path.exists(self.error_log_path):
os.remove(self.error_log_path)
def _on_error_log_closed(self, result, log_exists):
if result == DialogResult.CONFIRM and log_exists:
dialog2 = ConfirmDialog(tr("Would you like to delete this log?"), tr("Yes"), tr("No"), rich=False)
gui_app.set_modal_overlay(dialog2, callback=self._on_delete_confirm)
def _on_error_log_clicked(self):
text = ""
if os.path.exists(self.error_log_path):
text = f"<b>{datetime.datetime.fromtimestamp(os.path.getmtime(self.error_log_path)).strftime('%d-%b-%Y %H:%M:%S').upper()}</b><br><br>"
try:
with open(self.error_log_path) as file:
text += file.read()
except Exception:
pass
dialog = HtmlModalSP(text=text, callback=lambda result: self._on_error_log_closed(result, os.path.exists(self.error_log_path)))
gui_app.set_modal_overlay(dialog)
def _update_state(self):
disable_updates = ui_state.params.get_bool("DisableUpdates")
show_advanced = ui_state.params.get_bool("ShowAdvancedControls")
if (prebuilt_file := os.path.exists(PREBUILT_PATH)) != ui_state.params.get_bool("QuickBootToggle"):
ui_state.params.put_bool("QuickBootToggle", prebuilt_file)
self.prebuilt_toggle.action_item.set_state(prebuilt_file)
self.prebuilt_toggle.set_visible(show_advanced and not (self._is_release_branch or self._is_development_branch))
self.prebuilt_toggle.action_item.set_enabled(disable_updates)
if disable_updates:
self.prebuilt_toggle.set_description(tr("When toggled on, this creates a prebuilt file to allow accelerated boot times. When toggled off, it " +
"removes the prebuilt file so compilation of locally edited cpp files can be made."))
else:
self.prebuilt_toggle.set_description(tr("Quickboot mode requires updates to be disabled.<br>Enable 'Disable Updates' in the Software panel first."))
self.enable_copyparty_toggle.set_visible(show_advanced)
self.enable_github_runner_toggle.set_visible(show_advanced and not self._is_release_branch)
self.error_log_btn.set_visible(not self._is_release_branch)

View File

@@ -9,7 +9,6 @@ from enum import IntEnum
import pyray as rl
from openpilot.selfdrive.ui.layouts.settings import settings as OP
from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.device import DeviceLayoutSP
from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.software import SoftwareLayoutSP
@@ -31,6 +30,7 @@ from openpilot.selfdrive.ui.sunnypilot.layouts.settings.steering import Steering
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.cruise import CruiseLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.visuals import VisualsLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.display import DisplayLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.developer import DeveloperLayoutSP
# from openpilot.selfdrive.ui.sunnypilot.layouts.settings.navigation import NavigationLayout
@@ -125,7 +125,7 @@ class SettingsLayoutSP(OP.SettingsLayout):
OP.PanelType.TRIPS: PanelInfo(tr_noop("Trips"), TripsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_trips.png"),
OP.PanelType.VEHICLE: PanelInfo(tr_noop("Vehicle"), VehicleLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_vehicle.png"),
OP.PanelType.FIREHOSE: PanelInfo(tr_noop("Firehose"), FirehoseLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_firehose.png"),
OP.PanelType.DEVELOPER: PanelInfo(tr_noop("Developer"), DeveloperLayout(), icon="icons/shell.png"),
OP.PanelType.DEVELOPER: PanelInfo(tr_noop("Developer"), DeveloperLayoutSP(), icon="icons/shell.png"),
}
def _draw_sidebar(self, rect: rl.Rectangle):

View File

@@ -258,11 +258,11 @@
"description": ""
},
"EnableCopyparty": {
"title": "Enable Copyparty",
"title": "copyparty Service",
"description": ""
},
"EnableGithubRunner": {
"title": "Enable GitHub Runner",
"title": "GitHub Runner Service",
"description": ""
},
"EnableSunnylinkUploader": {