This commit is contained in:
firestarsdog
2026-03-23 01:42:31 -04:00
parent 4d5d48dbe8
commit 003dfa8f8e
15 changed files with 2177 additions and 400 deletions
+43 -12
View File
@@ -1,6 +1,7 @@
import os
import time
import datetime
from pathlib import Path
from openpilot.common.time_helpers import system_time_valid
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.application import gui_app
@@ -76,15 +77,20 @@ class SoftwareLayout(Widget):
self._branch_btn.action_item.set_value(ui_state.params.get("UpdaterTargetBranch") or "")
self._branch_dialog: MultiOptionDialog | None = None
self._scroller = Scroller([
self._onroad_label,
self._version_item,
self._auto_updates_toggle,
self._download_btn,
self._install_btn,
self._branch_btn,
button_item(lambda: tr("Uninstall"), lambda: tr("UNINSTALL"), callback=self._on_uninstall),
], line_separator=True, spacing=0)
self._scroller = Scroller(
[
self._onroad_label,
self._version_item,
self._auto_updates_toggle,
self._download_btn,
self._install_btn,
self._branch_btn,
button_item(lambda: tr("Uninstall"), lambda: tr("UNINSTALL"), callback=self._on_uninstall),
button_item(lambda: tr("Error Log"), lambda: tr("VIEW"), callback=self._on_error_log),
],
line_separator=True,
spacing=0,
)
def show_event(self):
self._scroller.show_event()
@@ -170,18 +176,43 @@ class SoftwareLayout(Widget):
# Start downloading
self._waiting_for_updater = True
self._waiting_start_ts = time.monotonic()
ui_state.params_memory.put_bool("ManualUpdateInitiated", True)
os.system("pkill -SIGHUP -f system.updated.updated")
def _on_auto_updates_toggle(self, enabled: bool):
ui_state.params.put_bool("AutomaticUpdates", enabled)
def _on_uninstall(self):
def handle_uninstall_confirmation(result):
def handle_step1(result):
if result == DialogResult.CONFIRM:
ui_state.params.put_bool("DoUninstall", True)
def handle_step2(result2):
if result2 == DialogResult.CONFIRM:
def handle_step3(result3):
if result3 == DialogResult.CONFIRM:
ui_state.params.clear_all()
ui_state.params.put_bool("DoUninstall", True)
dialog = ConfirmDialog(tr("This is a complete factory reset and cannot be undone. Are you absolutely sure?"), tr("Reset"))
gui_app.set_modal_overlay(dialog, callback=handle_step3)
else:
ui_state.params.put_bool("DoUninstall", True)
dialog = ConfirmDialog(
tr("Do you want to perform a full factory reset? All saved assets and settings will be permanently deleted!"), tr("Factory Reset"), tr("Skip")
)
gui_app.set_modal_overlay(dialog, callback=handle_step2)
dialog = ConfirmDialog(tr("Are you sure you want to uninstall?"), tr("Uninstall"))
gui_app.set_modal_overlay(dialog, callback=handle_uninstall_confirmation)
gui_app.set_modal_overlay(dialog, callback=handle_step1)
def _on_error_log(self):
try:
txt = Path("/data/error_logs/error.txt").read_text(encoding='utf-8', errors='replace')
except Exception:
txt = tr("No error log found.")
gui_app.set_modal_overlay(ConfirmDialog(txt, tr("OK"), on_close=lambda r: None, rich=True))
def _on_install_update(self):
# Trigger reboot to install update
+135 -19
View File
@@ -3,6 +3,7 @@ import os
import shutil
import threading
import subprocess
from datetime import datetime
from pathlib import Path
from openpilot.system.ui.lib.application import gui_app
@@ -10,41 +11,63 @@ from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets import DialogResult
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dialog
from openpilot.system.ui.widgets.selection_dialog import SelectionDialog
from openpilot.system.ui.widgets.input_dialog import InputDialog
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
class StarPilotDataLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Manage Backups"), "panel": "backups", "icon": "toggle_icons/icon_system.png", "color": "#FA6800"},
{"title": tr_noop("Toggle Backups"), "panel": "toggle_backups", "icon": "toggle_icons/icon_system.png", "color": "#FA6800"},
{"title": tr_noop("Manage Storage"), "panel": "storage", "icon": "toggle_icons/icon_system.png", "color": "#FA6800"},
{"title": tr_noop("Delete Driving Data"), "type": "hub", "on_click": self._on_delete_driving_data, "icon": "toggle_icons/icon_system.png", "color": "#FA6800"},
{"title": tr_noop("Delete Error Logs"), "type": "hub", "on_click": self._on_delete_error_logs, "icon": "toggle_icons/icon_system.png", "color": "#FA6800"},
{
"title": tr_noop("Delete Driving Data"),
"type": "hub",
"on_click": self._on_delete_driving_data,
"icon": "toggle_icons/icon_system.png",
"color": "#FA6800",
},
{
"title": tr_noop("Delete Error Logs"),
"type": "hub",
"on_click": self._on_delete_error_logs,
"icon": "toggle_icons/icon_system.png",
"color": "#FA6800",
},
]
self._sub_panels = {
"backups": StarPilotBackupsLayout(),
"toggle_backups": StarPilotToggleBackupsLayout(),
"storage": StarPilotStorageLayout(),
}
for name, panel in self._sub_panels.items():
if hasattr(panel, 'set_navigate_callback'): panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'): panel.set_back_callback(self._go_back)
if hasattr(panel, 'set_navigate_callback'):
panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'):
panel.set_back_callback(self._go_back)
self._rebuild_grid()
def _on_delete_driving_data(self):
def _do_delete(res):
if res == DialogResult.CONFIRM:
def _task():
drive_paths = ["/data/media/0/realdata/", "/data/media/0/realdata_HD/", "/data/media/0/realdata_konik/"]
for path in drive_paths:
p = Path(path)
if p.exists():
for entry in p.iterdir():
if entry.is_dir(): shutil.rmtree(entry, ignore_errors=True)
if entry.is_dir():
shutil.rmtree(entry, ignore_errors=True)
threading.Thread(target=_task, daemon=True).start()
gui_app.set_modal_overlay(alert_dialog(tr("Driving data deletion started.")))
gui_app.set_modal_overlay(ConfirmDialog(tr("Delete all driving data and footage?"), tr("Delete"), on_close=_do_delete))
def _on_delete_error_logs(self):
@@ -53,8 +76,10 @@ class StarPilotDataLayout(StarPilotPanel):
shutil.rmtree("/data/error_logs", ignore_errors=True)
os.makedirs("/data/error_logs", exist_ok=True)
gui_app.set_modal_overlay(alert_dialog(tr("Error logs deleted.")))
gui_app.set_modal_overlay(ConfirmDialog(tr("Delete all error logs?"), tr("Delete"), on_close=_do_delete))
class StarPilotBackupsLayout(StarPilotPanel):
def __init__(self):
super().__init__()
@@ -67,30 +92,45 @@ class StarPilotBackupsLayout(StarPilotPanel):
def _get_backups(self):
b_dir = Path("/data/backups")
if not b_dir.exists(): return []
if not b_dir.exists():
return []
return [f.name for f in b_dir.glob("*.tar.zst") if "in_progress" not in f.name]
def _on_create_backup(self):
# Simplified backup logic
gui_app.set_modal_overlay(alert_dialog(tr("Backup creation started in background.")))
def _task():
os.makedirs("/data/backups", exist_ok=True)
subprocess.run(["tar", "--use-compress-program=zstd", "-cf", "/data/backups/manual_backup.tar.zst", "/data/openpilot"])
threading.Thread(target=_task, daemon=True).start()
def on_name(res, name):
if res == DialogResult.CONFIRM:
safe_name = name.replace(" ", "_") if name else f"backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
backup_path = f"/data/backups/{safe_name}.tar.zst"
if Path(backup_path).exists():
gui_app.set_modal_overlay(alert_dialog(tr("A backup with this name already exists.")))
return
gui_app.set_modal_overlay(alert_dialog(tr("Backup creation started.")))
def _task():
os.makedirs("/data/backups", exist_ok=True)
subprocess.run(["tar", "--use-compress-program=zstd", "-cf", backup_path, "/data/openpilot"])
threading.Thread(target=_task, daemon=True).start()
gui_app.set_modal_overlay(InputDialog(tr("Name your backup"), "", on_close=on_name))
def _on_restore_backup(self):
backups = self._get_backups()
if not backups:
gui_app.set_modal_overlay(alert_dialog(tr("No backups found.")))
return
def _on_select(res, val):
if res == DialogResult.CONFIRM:
gui_app.set_modal_overlay(alert_dialog(tr("Restoring... device will reboot.")))
def _task():
subprocess.run(["rm", "-rf", "/data/openpilot/*"])
subprocess.run(["tar", "--use-compress-program=zstd", "-xf", f"/data/backups/{val}", "-C", "/"])
os.system("reboot")
threading.Thread(target=_task, daemon=True).start()
gui_app.set_modal_overlay(SelectionDialog(tr("Select Backup"), backups, on_close=_on_select))
def _on_delete_backup(self):
@@ -98,20 +138,96 @@ class StarPilotBackupsLayout(StarPilotPanel):
if not backups:
gui_app.set_modal_overlay(alert_dialog(tr("No backups found.")))
return
def _on_select(res, val):
if res == DialogResult.CONFIRM:
os.remove(f"/data/backups/{val}")
self._rebuild_grid()
gui_app.set_modal_overlay(SelectionDialog(tr("Delete Backup"), backups, on_close=_on_select))
class StarPilotToggleBackupsLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Create Toggle Backup"), "type": "hub", "on_click": self._on_create, "color": "#FA6800"},
{"title": tr_noop("Restore Toggle Backup"), "type": "hub", "on_click": self._on_restore, "color": "#FA6800"},
{"title": tr_noop("Delete Toggle Backup"), "type": "hub", "on_click": self._on_delete, "color": "#FA6800"},
]
self._rebuild_grid()
def _get_backups(self):
b_dir = Path("/data/toggle_backups")
if not b_dir.exists():
return []
return [d.name for d in b_dir.iterdir() if d.is_dir() and "in_progress" not in d.name]
def _on_create(self):
def on_name(res, name):
if res == DialogResult.CONFIRM:
safe_name = name.replace(" ", "_") if name else f"toggle_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
backup_path = Path(f"/data/toggle_backups/{safe_name}")
if backup_path.exists():
gui_app.set_modal_overlay(alert_dialog(tr("A toggle backup with this name already exists.")))
return
os.makedirs(backup_path, exist_ok=True)
shutil.copytree("/data/params/d", str(backup_path), dirs_exist_ok=True)
gui_app.set_modal_overlay(alert_dialog(tr("Toggle backup created.")))
self._rebuild_grid()
gui_app.set_modal_overlay(InputDialog(tr("Name your toggle backup"), "", on_close=on_name))
def _on_restore(self):
backups = self._get_backups()
if not backups:
gui_app.set_modal_overlay(alert_dialog(tr("No toggle backups found.")))
return
def _on_select(res, val):
if res == DialogResult.CONFIRM:
def on_confirm(r2):
if r2 == DialogResult.CONFIRM:
src = Path(f"/data/toggle_backups/{val}")
shutil.copytree(str(src), "/data/params/d", dirs_exist_ok=True)
gui_app.set_modal_overlay(alert_dialog(tr("Toggles restored.")))
self._rebuild_grid()
gui_app.set_modal_overlay(ConfirmDialog(tr("This will overwrite your current toggles."), tr("Restore"), on_close=on_confirm))
gui_app.set_modal_overlay(SelectionDialog(tr("Select Toggle Backup"), backups, on_close=_on_select))
def _on_delete(self):
backups = self._get_backups()
if not backups:
gui_app.set_modal_overlay(alert_dialog(tr("No toggle backups found.")))
return
def _on_select(res, val):
if res == DialogResult.CONFIRM:
shutil.rmtree(f"/data/toggle_backups/{val}", ignore_errors=True)
self._rebuild_grid()
gui_app.set_modal_overlay(SelectionDialog(tr("Delete Toggle Backup"), backups, on_close=_on_select))
class StarPilotStorageLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Driving Data"), "type": "hub", "on_click": self._show_stats, "color": "#FA6800"},
{"title": tr_noop("Driving Data"), "type": "value", "get_value": self._get_storage, "on_click": lambda: None, "color": "#FA6800"},
]
self._rebuild_grid()
def _show_stats(self):
# In a real environment we'd calculate du -sh /data
gui_app.set_modal_overlay(alert_dialog(tr("Storage management not yet fully ported to Python.")))
def _get_storage(self):
paths = ["/data/media/0/osm/offline", "/data/media/0/realdata", "/data/backups"]
total = 0
for p in paths:
pp = Path(p)
if pp.exists():
total += sum(f.stat().st_size for f in pp.rglob('*') if f.is_file())
mb = total / (1024 * 1024)
if mb > 1024:
return f"{(mb / 1024):.2f} GB"
return f"{mb:.2f} MB"
+138 -48
View File
@@ -1,73 +1,113 @@
from __future__ import annotations
from pathlib import Path
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.hardware import HARDWARE
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets import DialogResult
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
from openpilot.system.ui.widgets.selection_dialog import SelectionDialog
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
from openpilot.selfdrive.ui.layouts.settings.starpilot.metro import SliderDialog
class StarPilotDeviceLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{
"title": tr_noop("Screen Settings"),
"panel": "screen",
"icon": "toggle_icons/icon_light.png",
"color": "#FA6800"
},
{
"title": tr_noop("Device Settings"),
"panel": "device_settings",
"icon": "toggle_icons/icon_device.png",
"color": "#FA6800"
},
{"title": tr_noop("Screen Settings"), "panel": "screen", "icon": "toggle_icons/icon_light.png", "color": "#FA6800"},
{"title": tr_noop("Device Settings"), "panel": "device_settings", "icon": "toggle_icons/icon_device.png", "color": "#FA6800"},
{
"title": tr_noop("Device Shutdown"),
"type": "value",
"get_value": self._get_shutdown_timer,
"on_click": self._show_shutdown_selector,
"color": "#FA6800"
"color": "#FA6800",
},
{
"title": tr_noop("Disable Logging"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("NoLogging"),
"set_state": lambda s: self._params.put_bool("NoLogging", s),
"color": "#FA6800"
"color": "#FA6800",
},
{
"title": tr_noop("Disable Uploads"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("NoUploads"),
"set_state": lambda s: self._params.put_bool("NoUploads", s),
"color": "#FA6800"
"color": "#FA6800",
},
{
"title": tr_noop("Disable Onroad Uploads"),
"type": "toggle",
"param": "DisableOnroadUploads",
"get_state": lambda: self._params.get_bool("DisableOnroadUploads"),
"set_state": lambda s: self._params.put_bool("DisableOnroadUploads", s),
"color": "#FA6800",
},
{
"title": tr_noop("High-Quality Recording"),
"type": "toggle",
"param": "HigherBitrate",
"get_state": lambda: self._params.get_bool("HigherBitrate"),
"set_state": lambda s: self._params.put_bool("HigherBitrate", s),
"color": "#FA6800"
"set_state": lambda s: self._on_higher_bitrate_toggle(s),
"color": "#FA6800",
},
]
self._sub_panels = {
"screen": StarPilotScreenLayout(),
"device_settings": StarPilotDeviceManagementLayout(),
}
for name, panel in self._sub_panels.items():
if hasattr(panel, 'set_navigate_callback'): panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'): panel.set_back_callback(self._go_back)
if hasattr(panel, 'set_navigate_callback'):
panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'):
panel.set_back_callback(self._go_back)
self._rebuild_grid()
def _rebuild_grid(self):
no_uploads = self._params.get_bool("NoUploads")
disable_onroad = self._params.get_bool("DisableOnroadUploads")
filtered = []
for cat in self.CATEGORIES:
param = cat.get("param")
if param == "DisableOnroadUploads" and not no_uploads:
continue
if param == "HigherBitrate" and (not no_uploads or disable_onroad):
continue
filtered.append(cat)
original = self.CATEGORIES
self.CATEGORIES = filtered
super()._rebuild_grid()
self.CATEGORIES = original
def _on_higher_bitrate_toggle(self, state):
self._params.put_bool("HigherBitrate", state)
cache_path = Path("/cache/use_HD")
if state:
cache_path.parent.mkdir(parents=True, exist_ok=True)
cache_path.touch()
else:
if cache_path.exists():
cache_path.unlink()
if ui_state.started:
gui_app.set_modal_overlay(
ConfirmDialog(
tr("Reboot required. Reboot now?"), tr("Reboot"), tr("Cancel"), on_close=lambda res: HARDWARE.reboot() if res == DialogResult.CONFIRM else None
)
)
def _get_shutdown_timer(self):
v = self._params.get_int("DeviceShutdown")
if v == 0: return tr("5 mins")
if v <= 3: return f"{v * 15} mins"
if v == 0:
return tr("5 mins")
if v <= 3:
return f"{v * 15} mins"
return f"{v - 3} " + (tr("hour") if v == 4 else tr("hours"))
def _show_shutdown_selector(self):
@@ -75,15 +115,15 @@ class StarPilotDeviceLayout(StarPilotPanel):
if res == DialogResult.CONFIRM:
self._params.put_int("DeviceShutdown", int(val))
self._rebuild_grid()
labels = {0: tr("5 mins")}
for i in range(1, 4): labels[i] = f"{i*15} mins"
for i in range(4, 34): labels[i] = f"{i-3} " + (tr("hour") if i == 4 else tr("hours"))
gui_app.set_modal_overlay(SliderDialog(
tr("Device Shutdown"), 0, 33, 1, self._params.get_int("DeviceShutdown"),
on_close, labels=labels, color="#FA6800"
))
for i in range(1, 4):
labels[i] = f"{i * 15} mins"
for i in range(4, 34):
labels[i] = f"{i - 3} " + (tr("hour") if i == 4 else tr("hours"))
gui_app.set_modal_overlay(SliderDialog(tr("Device Shutdown"), 0, 33, 1, self._params.get_int("DeviceShutdown"), on_close, labels=labels, color="#FA6800"))
class StarPilotScreenLayout(StarPilotPanel):
def __init__(self):
@@ -94,37 +134,45 @@ class StarPilotScreenLayout(StarPilotPanel):
"type": "value",
"get_value": lambda: self._get_brightness("ScreenBrightness"),
"on_click": lambda: self._show_brightness_selector("ScreenBrightness"),
"color": "#FA6800"
"color": "#FA6800",
},
{
"title": tr_noop("Brightness (Onroad)"),
"type": "value",
"get_value": lambda: self._get_brightness("ScreenBrightnessOnroad"),
"on_click": lambda: self._show_brightness_selector("ScreenBrightnessOnroad"),
"color": "#FA6800"
"color": "#FA6800",
},
{
"title": tr_noop("Timeout (Offroad)"),
"type": "value",
"get_value": lambda: f"{self._params.get_int('ScreenTimeout')}s",
"on_click": lambda: self._show_timeout_selector("ScreenTimeout"),
"color": "#FA6800"
"color": "#FA6800",
},
{
"title": tr_noop("Timeout (Onroad)"),
"type": "value",
"get_value": lambda: f"{self._params.get_int('ScreenTimeoutOnroad')}s",
"on_click": lambda: self._show_timeout_selector("ScreenTimeoutOnroad"),
"color": "#FA6800"
"color": "#FA6800",
},
{
"title": tr_noop("Standby Mode"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("StandbyMode"),
"set_state": lambda s: self._params.put_bool("StandbyMode", s),
"color": "#FA6800",
},
{"title": tr_noop("Standby Mode"), "type": "toggle", "get_state": lambda: self._params.get_bool("StandbyMode"), "set_state": lambda s: self._params.put_bool("StandbyMode", s), "color": "#FA6800"},
]
self._rebuild_grid()
def _get_brightness(self, key):
v = self._params.get_int(key)
if v == 0: return tr("Off")
if v == 101: return tr("Auto")
if v == 0:
return tr("Off")
if v == 101:
return tr("Auto")
return f"{v}%"
def _show_brightness_selector(self, key):
@@ -134,33 +182,75 @@ class StarPilotScreenLayout(StarPilotPanel):
self._params.put_int(key, new_v)
HARDWARE.set_brightness(new_v)
self._rebuild_grid()
gui_app.set_modal_overlay(SliderDialog(
tr(key), 0, 101, 1, self._params.get_int(key),
on_close, unit="%", labels={0: tr("Off"), 101: tr("Auto")}, color="#FA6800"
))
gui_app.set_modal_overlay(
SliderDialog(tr(key), 0, 101, 1, self._params.get_int(key), on_close, unit="%", labels={0: tr("Off"), 101: tr("Auto")}, color="#FA6800")
)
def _show_timeout_selector(self, key):
def on_close(res, val):
if res == DialogResult.CONFIRM:
self._params.put_int(key, int(val))
self._rebuild_grid()
gui_app.set_modal_overlay(SliderDialog(tr(key), 5, 60, 5, self._params.get_int(key), on_close, unit="s", color="#FA6800"))
class StarPilotDeviceManagementLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Low-Voltage Cutoff"), "type": "value", "get_value": lambda: f"{self._params.get_float('LowVoltageShutdown'):.1f}V", "on_click": self._show_voltage_selector, "color": "#FA6800"},
{"title": tr_noop("Raise Temp Limits"), "type": "toggle", "get_state": lambda: self._params.get_bool("IncreaseThermalLimits"), "set_state": lambda s: self._params.put_bool("IncreaseThermalLimits", s), "color": "#FA6800"},
{"title": tr_noop("Use Konik Server"), "type": "toggle", "get_state": lambda: self._params.get_bool("UseKonikServer"), "set_state": lambda s: self._params.put_bool("UseKonikServer", s), "color": "#FA6800"},
{
"title": tr_noop("Low-Voltage Cutoff"),
"type": "value",
"get_value": lambda: f"{self._params.get_float('LowVoltageShutdown'):.1f}V",
"on_click": self._show_voltage_selector,
"color": "#FA6800",
},
{
"title": tr_noop("Raise Temp Limits"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("IncreaseThermalLimits"),
"set_state": lambda s: self._params.put_bool("IncreaseThermalLimits", s),
"color": "#FA6800",
},
{
"title": tr_noop("Use Konik Server"),
"type": "toggle",
"get_state": lambda: self._get_konik_state(),
"set_state": lambda s: self._on_konik_toggle(s),
"color": "#FA6800",
},
]
self._rebuild_grid()
def _get_konik_state(self):
if Path("/data/not_vetted").exists():
return True
return self._params.get_bool("UseKonikServer")
def _on_konik_toggle(self, state):
self._params.put_bool("UseKonikServer", state)
cache_path = Path("/cache/use_konik")
if state:
cache_path.parent.mkdir(parents=True, exist_ok=True)
cache_path.touch()
else:
if cache_path.exists():
cache_path.unlink()
if ui_state.started:
gui_app.set_modal_overlay(
ConfirmDialog(
tr("Reboot required. Reboot now?"), tr("Reboot"), tr("Cancel"), on_close=lambda res: HARDWARE.reboot() if res == DialogResult.CONFIRM else None
)
)
def _show_voltage_selector(self):
def on_close(res, val):
if res == DialogResult.CONFIRM:
self._params.put_float("LowVoltageShutdown", float(val))
self._rebuild_grid()
gui_app.set_modal_overlay(SliderDialog(tr("Low-Voltage Cutoff"), 11.8, 12.5, 0.1, self._params.get_float("LowVoltageShutdown"), on_close, unit="V", color="#FA6800"))
gui_app.set_modal_overlay(
SliderDialog(tr("Low-Voltage Cutoff"), 11.8, 12.5, 0.1, self._params.get_float("LowVoltageShutdown"), on_close, unit="V", color="#FA6800")
)
@@ -5,9 +5,11 @@ from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets import DialogResult
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
from openpilot.system.ui.widgets.selection_dialog import SelectionDialog
from openpilot.system.ui.widgets.input_dialog import InputDialog
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
from openpilot.selfdrive.ui.layouts.settings.starpilot.metro import SliderDialog
class StarPilotLongitudinalLayout(StarPilotPanel):
def __init__(self):
super().__init__()
@@ -20,18 +22,15 @@ class StarPilotLongitudinalLayout(StarPilotPanel):
"qol": StarPilotLongitudinalQOLLayout(),
"slc": StarPilotSpeedLimitControllerLayout(),
"weather": StarPilotWeatherLayout(),
# Personality Sub-panels
"traffic_personality": StarPilotPersonalityProfileLayout("Traffic"),
"aggressive_personality": StarPilotPersonalityProfileLayout("Aggressive"),
"standard_personality": StarPilotPersonalityProfileLayout("Standard"),
"relaxed_personality": StarPilotPersonalityProfileLayout("Relaxed"),
# SLC Sub-panels
"slc_offsets": StarPilotSLCOffsetsLayout(),
"slc_qol": StarPilotSLCQOLLayout(),
"slc_visuals": StarPilotSLCVisualsLayout(),
# Weather Sub-panels
"low_visibility": StarPilotWeatherBase("LowVisibility"),
"rain": StarPilotWeatherBase("Rain"),
@@ -51,21 +50,80 @@ class StarPilotLongitudinalLayout(StarPilotPanel):
]
for name, panel in self._sub_panels.items():
if hasattr(panel, 'set_navigate_callback'): panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'): panel.set_back_callback(self._go_back)
if hasattr(panel, 'set_navigate_callback'):
panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'):
panel.set_back_callback(self._go_back)
self._rebuild_grid()
class StarPilotAdvancedLongitudinalLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("EV Tuning"), "type": "toggle", "get_state": lambda: self._params.get_bool("EVTuning"), "set_state": lambda s: self._params.put_bool("EVTuning", s), "color": "#1BA1E2"},
{"title": tr_noop("Truck Tuning"), "type": "toggle", "get_state": lambda: self._params.get_bool("TruckTuning"), "set_state": lambda s: self._params.put_bool("TruckTuning", s), "color": "#1BA1E2"},
{"title": tr_noop("Actuator Delay"), "type": "value", "get_value": lambda: f"{self._params.get_float('LongitudinalActuatorDelay'):.2f}s", "on_click": lambda: self._show_float_selector("LongitudinalActuatorDelay", 0.0, 1.0, 0.01, "s"), "color": "#1BA1E2"},
{"title": tr_noop("Max Acceleration"), "type": "value", "get_value": lambda: f"{self._params.get_float('MaxDesiredAcceleration'):.1f}m/s²", "on_click": lambda: self._show_float_selector("MaxDesiredAcceleration", 0.1, 4.0, 0.1, "m/s²"), "color": "#1BA1E2"},
{"title": tr_noop("Start Accel"), "type": "value", "get_value": lambda: f"{self._params.get_float('StartAccel'):.2f}m/s²", "on_click": lambda: self._show_float_selector("StartAccel", 0.0, 4.0, 0.01, "m/s²"), "color": "#1BA1E2"},
{"title": tr_noop("Stop Accel"), "type": "value", "get_value": lambda: f"{self._params.get_float('StopAccel'):.2f}m/s²", "on_click": lambda: self._show_float_selector("StopAccel", -4.0, 0.0, 0.01, "m/s²"), "color": "#1BA1E2"},
{"title": tr_noop("Stopping Rate"), "type": "value", "get_value": lambda: f"{self._params.get_float('StoppingDecelRate'):.3f}m/s²", "on_click": lambda: self._show_float_selector("StoppingDecelRate", 0.001, 1.0, 0.001, "m/s²"), "color": "#1BA1E2"},
{
"title": tr_noop("EV Tuning"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("EVTuning"),
"set_state": lambda s: self._params.put_bool("EVTuning", s),
"color": "#1BA1E2",
},
{
"title": tr_noop("Truck Tuning"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("TruckTuning"),
"set_state": lambda s: self._params.put_bool("TruckTuning", s),
"color": "#1BA1E2",
},
{
"title": tr_noop("Actuator Delay"),
"type": "value",
"get_value": lambda: f"{self._params.get_float('LongitudinalActuatorDelay'):.2f}s",
"on_click": lambda: self._show_float_selector("LongitudinalActuatorDelay", 0.0, 1.0, 0.01, "s"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Max Acceleration"),
"type": "value",
"get_value": lambda: f"{self._params.get_float('MaxDesiredAcceleration'):.1f}m/s²",
"on_click": lambda: self._show_float_selector("MaxDesiredAcceleration", 0.1, 4.0, 0.1, "m/s²"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Start Accel"),
"type": "value",
"get_value": lambda: f"{self._params.get_float('StartAccel'):.2f}m/s²",
"on_click": lambda: self._show_float_selector("StartAccel", 0.0, 4.0, 0.01, "m/s²"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Stop Accel"),
"type": "value",
"get_value": lambda: f"{self._params.get_float('StopAccel'):.2f}m/s²",
"on_click": lambda: self._show_float_selector("StopAccel", -4.0, 0.0, 0.01, "m/s²"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Stopping Rate"),
"type": "value",
"get_value": lambda: f"{self._params.get_float('StoppingDecelRate'):.3f}m/s²",
"on_click": lambda: self._show_float_selector("StoppingDecelRate", 0.001, 1.0, 0.001, "m/s²"),
"color": "#1BA1E2",
},
{
"title": tr_noop("VEgo Starting"),
"type": "value",
"get_value": lambda: f"{self._params.get_float('VEgoStarting'):.2f}m/s",
"on_click": lambda: self._show_float_selector("VEgoStarting", 0.01, 1.0, 0.01, "m/s"),
"color": "#1BA1E2",
},
{
"title": tr_noop("VEgo Stopping"),
"type": "value",
"get_value": lambda: f"{self._params.get_float('VEgoStopping'):.2f}m/s",
"on_click": lambda: self._show_float_selector("VEgoStopping", 0.01, 1.0, 0.01, "m/s"),
"color": "#1BA1E2",
},
]
self._rebuild_grid()
@@ -74,22 +132,108 @@ class StarPilotAdvancedLongitudinalLayout(StarPilotPanel):
if res == DialogResult.CONFIRM:
self._params.put_float(key, float(val))
self._rebuild_grid()
gui_app.set_modal_overlay(SliderDialog(tr(key), min_v, max_v, step, self._params.get_float(key), on_close, unit=unit, color="#1BA1E2"))
class StarPilotConditionalExperimentalLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Conditional Experimental"), "type": "toggle", "get_state": lambda: self._params.get_bool("ConditionalExperimental"), "set_state": lambda s: self._params.put_bool("ConditionalExperimental", s), "icon": "toggle_icons/icon_conditional.png", "color": "#1BA1E2"},
{"title": tr_noop("Below Speed"), "type": "value", "get_value": lambda: f"{self._params.get_int('CESpeed')} mph", "on_click": lambda: self._show_speed_selector("CESpeed"), "color": "#1BA1E2"},
{"title": tr_noop("Curves"), "type": "toggle", "get_state": lambda: self._params.get_bool("CECurves"), "set_state": lambda s: self._params.put_bool("CECurves", s), "color": "#1BA1E2"},
{"title": tr_noop("Stop Lights"), "type": "toggle", "get_state": lambda: self._params.get_bool("CEStopLights"), "set_state": lambda s: self._params.put_bool("CEStopLights", s), "color": "#1BA1E2"},
{"title": tr_noop("Lead Detected"), "type": "toggle", "get_state": lambda: self._params.get_bool("CELead"), "set_state": lambda s: self._params.put_bool("CELead", s), "color": "#1BA1E2"},
{"title": tr_noop("Slower Lead"), "type": "toggle", "get_state": lambda: self._params.get_bool("CESlowerLead"), "set_state": lambda s: self._params.put_bool("CESlowerLead", s), "color": "#1BA1E2"},
{"title": tr_noop("Stopped Lead"), "type": "toggle", "get_state": lambda: self._params.get_bool("CEStoppedLead"), "set_state": lambda s: self._params.put_bool("CEStoppedLead", s), "color": "#1BA1E2"},
{"title": tr_noop("Predicted Stop"), "type": "value", "get_value": lambda: f"{self._params.get_int('CEModelStopTime')}s", "on_click": lambda: self._show_int_selector("CEModelStopTime", 0, 10, "s"), "color": "#1BA1E2"},
{"title": tr_noop("Signal Below"), "type": "value", "get_value": lambda: f"{self._params.get_int('CESignalSpeed')} mph", "on_click": lambda: self._show_speed_selector("CESignalSpeed"), "color": "#1BA1E2"},
{"title": tr_noop("Status Widget"), "type": "toggle", "get_state": lambda: self._params.get_bool("ShowCEMStatus"), "set_state": lambda s: self._params.put_bool("ShowCEMStatus", s), "color": "#1BA1E2"},
{
"title": tr_noop("Conditional Experimental"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("ConditionalExperimental"),
"set_state": lambda s: self._params.put_bool("ConditionalExperimental", s),
"icon": "toggle_icons/icon_conditional.png",
"color": "#1BA1E2",
},
{
"title": tr_noop("Below Speed"),
"type": "value",
"get_value": lambda: f"{self._params.get_int('CESpeed')} mph",
"on_click": lambda: self._show_speed_selector("CESpeed"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Curves"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("CECurves"),
"set_state": lambda s: self._params.put_bool("CECurves", s),
"color": "#1BA1E2",
},
{
"title": tr_noop("Curves Lead"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("CECurvesLead"),
"set_state": lambda s: self._params.put_bool("CECurvesLead", s),
"visible": lambda: self._params.get_bool("CECurves"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Stop Lights"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("CEStopLights"),
"set_state": lambda s: self._params.put_bool("CEStopLights", s),
"color": "#1BA1E2",
},
{
"title": tr_noop("Lead Detected"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("CELead"),
"set_state": lambda s: self._params.put_bool("CELead", s),
"color": "#1BA1E2",
},
{
"title": tr_noop("Slower Lead"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("CESlowerLead"),
"set_state": lambda s: self._params.put_bool("CESlowerLead", s),
"color": "#1BA1E2",
},
{
"title": tr_noop("Stopped Lead"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("CEStoppedLead"),
"set_state": lambda s: self._params.put_bool("CEStoppedLead", s),
"color": "#1BA1E2",
},
{
"title": tr_noop("Predicted Stop"),
"type": "value",
"get_value": lambda: f"{self._params.get_int('CEModelStopTime')}s",
"on_click": lambda: self._show_int_selector("CEModelStopTime", 0, 10, "s"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Signal Below"),
"type": "value",
"get_value": lambda: f"{self._params.get_int('CESignalSpeed')} mph",
"on_click": lambda: self._show_speed_selector("CESignalSpeed"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Speed Lead"),
"type": "value",
"get_value": lambda: f"{self._params.get_int('CESpeedLead')} mph",
"on_click": lambda: self._show_speed_selector("CESpeedLead"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Signal Lane Detection"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("CESignalLaneDetection"),
"set_state": lambda s: self._params.put_bool("CESignalLaneDetection", s),
"visible": lambda: self._params.get_int("CESignalSpeed") > 0,
"color": "#1BA1E2",
},
{
"title": tr_noop("Status Widget"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("ShowCEMStatus"),
"set_state": lambda s: self._params.put_bool("ShowCEMStatus", s),
"color": "#1BA1E2",
},
]
self._rebuild_grid()
@@ -98,6 +242,7 @@ class StarPilotConditionalExperimentalLayout(StarPilotPanel):
if res == DialogResult.CONFIRM:
self._params.put_int(key, int(val))
self._rebuild_grid()
gui_app.set_modal_overlay(SliderDialog(tr(key), 0, 100, 1, self._params.get_int(key), on_close, unit=" mph", color="#1BA1E2"))
def _show_int_selector(self, key, min_v, max_v, unit=""):
@@ -105,17 +250,62 @@ class StarPilotConditionalExperimentalLayout(StarPilotPanel):
if res == DialogResult.CONFIRM:
self._params.put_int(key, int(val))
self._rebuild_grid()
gui_app.set_modal_overlay(SliderDialog(tr(key), min_v, max_v, 1, self._params.get_int(key), on_close, unit=unit, color="#1BA1E2"))
class StarPilotCurveSpeedLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Curve Speed Controller"), "type": "toggle", "get_state": lambda: self._params.get_bool("CurveSpeedController"), "set_state": lambda s: self._params.put_bool("CurveSpeedController", s), "icon": "toggle_icons/icon_speed_map.png", "color": "#1BA1E2"},
{"title": tr_noop("Status Widget"), "type": "toggle", "get_state": lambda: self._params.get_bool("ShowCSCStatus"), "set_state": lambda s: self._params.put_bool("ShowCSCStatus", s), "color": "#1BA1E2"},
{
"title": tr_noop("Curve Speed Controller"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("CurveSpeedController"),
"set_state": lambda s: self._params.put_bool("CurveSpeedController", s),
"icon": "toggle_icons/icon_speed_map.png",
"color": "#1BA1E2",
},
{
"title": tr_noop("Status Widget"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("ShowCSCStatus"),
"set_state": lambda s: self._params.put_bool("ShowCSCStatus", s),
"color": "#1BA1E2",
},
{
"title": tr_noop("Calibrated Lateral Accel"),
"type": "value",
"get_value": lambda: f"{self._params_memory.get_float('CalibratedLateralAcceleration'):.2f} m/s²",
"on_click": lambda: None,
"color": "#1BA1E2",
},
{
"title": tr_noop("Calibration Progress"),
"type": "value",
"get_value": lambda: f"{self._params_memory.get_float('CalibrationProgress'):.2f}%",
"on_click": lambda: None,
"color": "#1BA1E2",
},
{
"title": tr_noop("Reset Curve Data"),
"type": "hub",
"on_click": lambda: self._reset_curve_data(),
"color": "#1BA1E2",
},
]
self._rebuild_grid()
def _reset_curve_data(self):
def on_close(res):
if res == DialogResult.CONFIRM:
self._params.remove("CalibratedLateralAcceleration")
self._params.remove("CalibrationProgress")
self._rebuild_grid()
gui_app.set_modal_overlay(ConfirmDialog(tr("Reset Curve Data?"), tr("Confirm"), on_close=on_close))
class StarPilotPersonalitiesLayout(StarPilotPanel):
def __init__(self):
super().__init__()
@@ -127,25 +317,88 @@ class StarPilotPersonalitiesLayout(StarPilotPanel):
]
self._rebuild_grid()
class StarPilotPersonalityProfileLayout(StarPilotPanel):
def __init__(self, profile: str):
super().__init__()
self._profile = profile
follow_min = 1.0 if profile == "Traffic" else 0.5
follow_max = 2.5 if profile == "Traffic" else 3.0
self.CATEGORIES = [
{"title": tr_noop("Follow Distance"), "type": "value", "get_value": lambda: f"{self._params.get_float(self._profile + 'Follow'):.2f}s", "on_click": lambda: self._show_float_selector(self._profile + "Follow", 0.5, 3.0, 0.05, "s"), "color": "#1BA1E2"},
{"title": tr_noop("Accel Smoothness"), "type": "value", "get_value": lambda: f"{self._params.get_int(self._profile + 'JerkAcceleration')}%", "on_click": lambda: self._show_int_selector(self._profile + "JerkAcceleration", 25, 200, "%"), "color": "#1BA1E2"},
{"title": tr_noop("Brake Smoothness"), "type": "value", "get_value": lambda: f"{self._params.get_int(self._profile + 'JerkDeceleration')}%", "on_click": lambda: self._show_int_selector(self._profile + "JerkDeceleration", 25, 200, "%"), "color": "#1BA1E2"},
{"title": tr_noop("Safety Gap Bias"), "type": "value", "get_value": lambda: f"{self._params.get_int(self._profile + 'JerkDanger')}%", "on_click": lambda: self._show_int_selector(self._profile + "JerkDanger", 25, 200, "%"), "color": "#1BA1E2"},
{"title": tr_noop("Slowdown Response"), "type": "value", "get_value": lambda: f"{self._params.get_int(self._profile + 'JerkSpeedDecrease')}%", "on_click": lambda: self._show_int_selector(self._profile + "JerkSpeedDecrease", 25, 200, "%"), "color": "#1BA1E2"},
{"title": tr_noop("Speed-Up Response"), "type": "value", "get_value": lambda: f"{self._params.get_int(self._profile + 'JerkSpeed')}%", "on_click": lambda: self._show_int_selector(self._profile + "JerkSpeed", 25, 200, "%"), "color": "#1BA1E2"},
{
"title": tr_noop("Follow Distance"),
"type": "value",
"get_value": lambda: f"{self._params.get_float(self._profile + 'Follow'):.2f}s",
"on_click": lambda: self._show_float_selector(self._profile + "Follow", follow_min, follow_max, 0.05, "s"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Follow High"),
"type": "value",
"get_value": lambda: f"{self._params.get_float(self._profile + 'FollowHigh'):.2f}s",
"on_click": lambda: self._show_float_selector(self._profile + "FollowHigh", 1.0, 3.0, 0.05, "s"),
"visible": lambda: self._profile != "Traffic",
"color": "#1BA1E2",
},
{
"title": tr_noop("Accel Smoothness"),
"type": "value",
"get_value": lambda: f"{self._params.get_int(self._profile + 'JerkAcceleration')}%",
"on_click": lambda: self._show_int_selector(self._profile + "JerkAcceleration", 25, 200, "%"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Brake Smoothness"),
"type": "value",
"get_value": lambda: f"{self._params.get_int(self._profile + 'JerkDeceleration')}%",
"on_click": lambda: self._show_int_selector(self._profile + "JerkDeceleration", 25, 200, "%"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Safety Gap Bias"),
"type": "value",
"get_value": lambda: f"{self._params.get_int(self._profile + 'JerkDanger')}%",
"on_click": lambda: self._show_int_selector(self._profile + "JerkDanger", 25, 200, "%"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Slowdown Response"),
"type": "value",
"get_value": lambda: f"{self._params.get_int(self._profile + 'JerkSpeedDecrease')}%",
"on_click": lambda: self._show_int_selector(self._profile + "JerkSpeedDecrease", 25, 200, "%"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Speed-Up Response"),
"type": "value",
"get_value": lambda: f"{self._params.get_int(self._profile + 'JerkSpeed')}%",
"on_click": lambda: self._show_int_selector(self._profile + "JerkSpeed", 25, 200, "%"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Reset to Defaults"),
"type": "hub",
"on_click": lambda: self._reset_profile(),
"color": "#1BA1E2",
},
]
self._rebuild_grid()
def _reset_profile(self):
def on_close(res):
if res == DialogResult.CONFIRM:
for key in ["Follow", "FollowHigh", "JerkAcceleration", "JerkDeceleration", "JerkDanger", "JerkSpeedDecrease", "JerkSpeed"]:
self._params.remove(self._profile + key)
self._rebuild_grid()
gui_app.set_modal_overlay(ConfirmDialog(tr("Reset to Defaults?"), tr("Confirm"), on_close=on_close))
def _show_float_selector(self, key, min_v, max_v, step, unit=""):
def on_close(res, val):
if res == DialogResult.CONFIRM:
self._params.put_float(key, float(val))
self._rebuild_grid()
gui_app.set_modal_overlay(SliderDialog(tr(key), min_v, max_v, step, self._params.get_float(key), on_close, unit=unit, color="#1BA1E2"))
def _show_int_selector(self, key, min_v, max_v, unit=""):
@@ -153,19 +406,63 @@ class StarPilotPersonalityProfileLayout(StarPilotPanel):
if res == DialogResult.CONFIRM:
self._params.put_int(key, int(val))
self._rebuild_grid()
gui_app.set_modal_overlay(SliderDialog(tr(key), min_v, max_v, 5, self._params.get_int(key), on_close, unit=unit, color="#1BA1E2"))
class StarPilotLongitudinalTuneLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Acceleration Profile"), "type": "value", "get_value": lambda: self._params.get("AccelerationProfile", encoding='utf-8') or "Standard", "on_click": lambda: self._show_selection("AccelerationProfile", ["Standard", "Eco", "Sport", "Sport+"]), "color": "#1BA1E2"},
{"title": tr_noop("Deceleration Profile"), "type": "value", "get_value": lambda: self._params.get("DecelerationProfile", encoding='utf-8') or "Standard", "on_click": lambda: self._show_selection("DecelerationProfile", ["Standard", "Eco", "Sport"]), "color": "#1BA1E2"},
{"title": tr_noop("Human Acceleration"), "type": "toggle", "get_state": lambda: self._params.get_bool("HumanAcceleration"), "set_state": lambda s: self._params.put_bool("HumanAcceleration", s), "color": "#1BA1E2"},
{"title": tr_noop("Human Following"), "type": "toggle", "get_state": lambda: self._params.get_bool("HumanFollowing"), "set_state": lambda s: self._params.put_bool("HumanFollowing", s), "color": "#1BA1E2"},
{"title": tr_noop("Human Lane Changes"), "type": "toggle", "get_state": lambda: self._params.get_bool("HumanLaneChanges"), "set_state": lambda s: self._params.put_bool("HumanLaneChanges", s), "color": "#1BA1E2"},
{"title": tr_noop("Lead Detection"), "type": "value", "get_value": lambda: f"{self._params.get_int('LeadDetectionThreshold')}%", "on_click": lambda: self._show_int_selector("LeadDetectionThreshold", 25, 50, "%"), "color": "#1BA1E2"},
{"title": tr_noop("Taco Tune"), "type": "toggle", "get_state": lambda: self._params.get_bool("TacoTune"), "set_state": lambda s: self._params.put_bool("TacoTune", s), "color": "#1BA1E2"},
{
"title": tr_noop("Acceleration Profile"),
"type": "value",
"get_value": lambda: self._params.get("AccelerationProfile", encoding='utf-8') or "Standard",
"on_click": lambda: self._show_selection("AccelerationProfile", ["Standard", "Eco", "Sport", "Sport+"]),
"color": "#1BA1E2",
},
{
"title": tr_noop("Deceleration Profile"),
"type": "value",
"get_value": lambda: self._params.get("DecelerationProfile", encoding='utf-8') or "Standard",
"on_click": lambda: self._show_selection("DecelerationProfile", ["Standard", "Eco", "Sport"]),
"color": "#1BA1E2",
},
{
"title": tr_noop("Human Acceleration"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("HumanAcceleration"),
"set_state": lambda s: self._params.put_bool("HumanAcceleration", s),
"color": "#1BA1E2",
},
{
"title": tr_noop("Human Following"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("HumanFollowing"),
"set_state": lambda s: self._params.put_bool("HumanFollowing", s),
"color": "#1BA1E2",
},
{
"title": tr_noop("Human Lane Changes"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("HumanLaneChanges"),
"set_state": lambda s: self._params.put_bool("HumanLaneChanges", s),
"color": "#1BA1E2",
},
{
"title": tr_noop("Lead Detection"),
"type": "value",
"get_value": lambda: f"{self._params.get_int('LeadDetectionThreshold')}%",
"on_click": lambda: self._show_int_selector("LeadDetectionThreshold", 25, 50, "%"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Taco Tune"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("TacoTune"),
"set_state": lambda s: self._params.put_bool("TacoTune", s),
"color": "#1BA1E2",
},
]
self._rebuild_grid()
@@ -174,6 +471,7 @@ class StarPilotLongitudinalTuneLayout(StarPilotPanel):
if res == DialogResult.CONFIRM:
self._params.put(key, val)
self._rebuild_grid()
gui_app.set_modal_overlay(SelectionDialog(tr(key), options, self._params.get(key, encoding='utf-8') or "Standard", on_close=on_select))
def _show_int_selector(self, key, min_v, max_v, unit=""):
@@ -181,17 +479,79 @@ class StarPilotLongitudinalTuneLayout(StarPilotPanel):
if res == DialogResult.CONFIRM:
self._params.put_int(key, int(val))
self._rebuild_grid()
gui_app.set_modal_overlay(SliderDialog(tr(key), min_v, max_v, 1, self._params.get_int(key), on_close, unit=unit, color="#1BA1E2"))
class StarPilotLongitudinalQOLLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Cruise Interval"), "type": "value", "get_value": lambda: f"{self._params.get_int('CustomCruise')} mph", "on_click": lambda: self._show_speed_selector("CustomCruise"), "color": "#1BA1E2"},
{"title": tr_noop("Reverse Cruise"), "type": "toggle", "get_state": lambda: self._params.get_bool("ReverseCruise"), "set_state": lambda s: self._params.put_bool("ReverseCruise", s), "color": "#1BA1E2"},
{"title": tr_noop("Force Stops"), "type": "toggle", "get_state": lambda: self._params.get_bool("ForceStops"), "set_state": lambda s: self._params.put_bool("ForceStops", s), "color": "#1BA1E2"},
{"title": tr_noop("Stopped Distance"), "type": "value", "get_value": lambda: f"{self._params.get_int('IncreasedStoppedDistance')} ft", "on_click": lambda: self._show_int_selector("IncreasedStoppedDistance", 0, 10, " ft"), "color": "#1BA1E2"},
{"title": tr_noop("Set Speed Offset"), "type": "value", "get_value": lambda: f"+{self._params.get_int('SetSpeedOffset')} mph", "on_click": lambda: self._show_int_selector("SetSpeedOffset", 0, 99, " mph"), "color": "#1BA1E2"},
{
"title": tr_noop("Cruise Interval"),
"type": "value",
"get_value": lambda: f"{self._params.get_int('CustomCruise')} mph",
"on_click": lambda: self._show_speed_selector("CustomCruise"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Cruise Long"),
"type": "value",
"get_value": lambda: f"{self._params.get_int('CustomCruiseLong')} mph",
"on_click": lambda: self._show_speed_selector("CustomCruiseLong"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Reverse Cruise"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("ReverseCruise"),
"set_state": lambda s: self._params.put_bool("ReverseCruise", s),
"color": "#1BA1E2",
},
{
"title": tr_noop("Force Stops"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("ForceStops"),
"set_state": lambda s: self._params.put_bool("ForceStops", s),
"color": "#1BA1E2",
},
{
"title": tr_noop("Stopped Distance"),
"type": "value",
"get_value": lambda: f"{self._params.get_int('IncreasedStoppedDistance')} ft",
"on_click": lambda: self._show_int_selector("IncreasedStoppedDistance", 0, 10, " ft"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Set Speed Offset"),
"type": "value",
"get_value": lambda: f"+{self._params.get_int('SetSpeedOffset')} mph",
"on_click": lambda: self._show_int_selector("SetSpeedOffset", 0, 99, " mph"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Map Gears"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("MapGears"),
"set_state": lambda s: self._params.put_bool("MapGears", s),
"color": "#1BA1E2",
},
{
"title": tr_noop("Map Acceleration"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("MapAcceleration"),
"set_state": lambda s: self._params.put_bool("MapAcceleration", s),
"visible": lambda: self._params.get_bool("MapGears"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Map Deceleration"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("MapDeceleration"),
"set_state": lambda s: self._params.put_bool("MapDeceleration", s),
"visible": lambda: self._params.get_bool("MapGears"),
"color": "#1BA1E2",
},
]
self._rebuild_grid()
@@ -200,6 +560,7 @@ class StarPilotLongitudinalQOLLayout(StarPilotPanel):
if res == DialogResult.CONFIRM:
self._params.put_int(key, int(val))
self._rebuild_grid()
gui_app.set_modal_overlay(SliderDialog(tr(key), 0, 100, 1, self._params.get_int(key), on_close, unit=" mph", color="#1BA1E2"))
def _show_int_selector(self, key, min_v, max_v, unit=""):
@@ -207,8 +568,10 @@ class StarPilotLongitudinalQOLLayout(StarPilotPanel):
if res == DialogResult.CONFIRM:
self._params.put_int(key, int(val))
self._rebuild_grid()
gui_app.set_modal_overlay(SliderDialog(tr(key), min_v, max_v, 1, self._params.get_int(key), on_close, unit=unit, color="#1BA1E2"))
class StarPilotSpeedLimitControllerLayout(StarPilotPanel):
def __init__(self):
super().__init__()
@@ -216,40 +579,66 @@ class StarPilotSpeedLimitControllerLayout(StarPilotPanel):
{"title": tr_noop("SLC Offsets"), "panel": "slc_offsets", "icon": "toggle_icons/icon_speed_limit.png", "color": "#1BA1E2"},
{"title": tr_noop("SLC Quality of Life"), "panel": "slc_qol", "icon": "toggle_icons/icon_speed_limit.png", "color": "#1BA1E2"},
{"title": tr_noop("SLC Visuals"), "panel": "slc_visuals", "icon": "toggle_icons/icon_speed_limit.png", "color": "#1BA1E2"},
{"title": tr_noop("Fallback Speed"), "type": "value", "get_value": lambda: self._params.get("SLCFallback", encoding='utf-8') or "Set Speed", "on_click": lambda: self._show_selection("SLCFallback", ["Set Speed", "Experimental Mode", "Previous Limit"]), "color": "#1BA1E2"},
{"title": tr_noop("Override Speed"), "type": "value", "get_value": lambda: self._params.get("SLCOverride", encoding='utf-8') or "None", "on_click": lambda: self._show_selection("SLCOverride", ["None", "Set With Gas Pedal", "Max Set Speed"]), "color": "#1BA1E2"},
{"title": tr_noop("Source Priority"), "type": "value", "get_value": lambda: self._params.get("SLCPriority1", encoding='utf-8') or "Dashboard", "on_click": self._on_priority_clicked, "color": "#1BA1E2"},
{
"title": tr_noop("Fallback Speed"),
"type": "value",
"get_value": lambda: self._params.get("SLCFallback", encoding='utf-8') or "Set Speed",
"on_click": lambda: self._show_selection("SLCFallback", ["Set Speed", "Experimental Mode", "Previous Limit"]),
"color": "#1BA1E2",
},
{
"title": tr_noop("Override Speed"),
"type": "value",
"get_value": lambda: self._params.get("SLCOverride", encoding='utf-8') or "None",
"on_click": lambda: self._show_selection("SLCOverride", ["None", "Set With Gas Pedal", "Max Set Speed"]),
"color": "#1BA1E2",
},
{
"title": tr_noop("Source Priority"),
"type": "value",
"get_value": lambda: self._params.get("SLCPriority1", encoding='utf-8') or "Dashboard",
"on_click": self._on_priority_clicked,
"color": "#1BA1E2",
},
]
self._rebuild_grid()
def _on_priority_clicked(self):
options = ["Dashboard", "Map Data", "Highest", "Lowest"]
def on_select(res, val):
if res == DialogResult.CONFIRM:
self._params.put("SLCPriority1", val)
self._rebuild_grid()
gui_app.set_modal_overlay(SelectionDialog(tr("SLC Priority"), options, self._params.get("SLCPriority1", encoding='utf-8') or "Dashboard", on_close=on_select))
gui_app.set_modal_overlay(
SelectionDialog(tr("SLC Priority"), options, self._params.get("SLCPriority1", encoding='utf-8') or "Dashboard", on_close=on_select)
)
def _show_selection(self, key, options):
def on_select(res, val):
if res == DialogResult.CONFIRM:
self._params.put(key, val)
self._rebuild_grid()
gui_app.set_modal_overlay(SelectionDialog(tr(key), options, self._params.get(key, encoding='utf-8') or "None", on_close=on_select))
class StarPilotSLCOffsetsLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = []
for i in range(1, 8):
key = f"Offset{i}"
self.CATEGORIES.append({
"title": tr_noop(f"Offset {i}"),
"type": "value",
"get_value": lambda k=key: f"{self._params.get_int(k)} mph",
"on_click": lambda k=key: self._show_speed_selector(k),
"color": "#1BA1E2"
})
self.CATEGORIES.append(
{
"title": tr_noop(f"Offset {i}"),
"type": "value",
"get_value": lambda k=key: f"{self._params.get_int(k)} mph",
"on_click": lambda k=key: self._show_speed_selector(k),
"color": "#1BA1E2",
}
)
self._rebuild_grid()
def _show_speed_selector(self, key):
@@ -257,17 +646,65 @@ class StarPilotSLCOffsetsLayout(StarPilotPanel):
if res == DialogResult.CONFIRM:
self._params.put_int(key, int(val))
self._rebuild_grid()
gui_app.set_modal_overlay(SliderDialog(tr(key), -99, 100, 1, self._params.get_int(key), on_close, unit=" mph", color="#1BA1E2"))
class StarPilotSLCQOLLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Match Speed on Engage"), "type": "toggle", "get_state": lambda: self._params.get_bool("SetSpeedLimit"), "set_state": lambda s: self._params.put_bool("SetSpeedLimit", s), "color": "#1BA1E2"},
{"title": tr_noop("Confirm New Limits"), "type": "toggle", "get_state": lambda: self._params.get_bool("SLCConfirmation"), "set_state": lambda s: self._params.put_bool("SLCConfirmation", s), "color": "#1BA1E2"},
{"title": tr_noop("Higher Lookahead"), "type": "value", "get_value": lambda: f"{self._params.get_int('SLCLookaheadHigher')}s", "on_click": lambda: self._show_int_selector("SLCLookaheadHigher", 0, 30, "s"), "color": "#1BA1E2"},
{"title": tr_noop("Lower Lookahead"), "type": "value", "get_value": lambda: f"{self._params.get_int('SLCLookaheadLower')}s", "on_click": lambda: self._show_int_selector("SLCLookaheadLower", 0, 30, "s"), "color": "#1BA1E2"},
{"title": tr_noop("Mapbox Fallback"), "type": "toggle", "get_state": lambda: self._params.get_bool("SLCMapboxFiller"), "set_state": lambda s: self._params.put_bool("SLCMapboxFiller", s), "color": "#1BA1E2"},
{
"title": tr_noop("Match Speed on Engage"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("SetSpeedLimit"),
"set_state": lambda s: self._params.put_bool("SetSpeedLimit", s),
"color": "#1BA1E2",
},
{
"title": tr_noop("Confirm New Limits"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("SLCConfirmation"),
"set_state": lambda s: self._params.put_bool("SLCConfirmation", s),
"color": "#1BA1E2",
},
{
"title": tr_noop("Confirm Lower"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("SLCConfirmationLower"),
"set_state": lambda s: self._params.put_bool("SLCConfirmationLower", s),
"visible": lambda: self._params.get_bool("SLCConfirmation"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Confirm Higher"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("SLCConfirmationHigher"),
"set_state": lambda s: self._params.put_bool("SLCConfirmationHigher", s),
"visible": lambda: self._params.get_bool("SLCConfirmation"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Higher Lookahead"),
"type": "value",
"get_value": lambda: f"{self._params.get_int('SLCLookaheadHigher')}s",
"on_click": lambda: self._show_int_selector("SLCLookaheadHigher", 0, 30, "s"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Lower Lookahead"),
"type": "value",
"get_value": lambda: f"{self._params.get_int('SLCLookaheadLower')}s",
"on_click": lambda: self._show_int_selector("SLCLookaheadLower", 0, 30, "s"),
"color": "#1BA1E2",
},
{
"title": tr_noop("Mapbox Fallback"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("SLCMapboxFiller"),
"set_state": lambda s: self._params.put_bool("SLCMapboxFiller", s),
"color": "#1BA1E2",
},
]
self._rebuild_grid()
@@ -276,17 +713,32 @@ class StarPilotSLCQOLLayout(StarPilotPanel):
if res == DialogResult.CONFIRM:
self._params.put_int(key, int(val))
self._rebuild_grid()
gui_app.set_modal_overlay(SliderDialog(tr(key), min_v, max_v, 1, self._params.get_int(key), on_close, unit=unit, color="#1BA1E2"))
class StarPilotSLCVisualsLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Show SLC Offset"), "type": "toggle", "get_state": lambda: self._params.get_bool("ShowSLCOffset"), "set_state": lambda s: self._params.put_bool("ShowSLCOffset", s), "color": "#1BA1E2"},
{"title": tr_noop("Show Sources"), "type": "toggle", "get_state": lambda: self._params.get_bool("SpeedLimitSources"), "set_state": lambda s: self._params.put_bool("SpeedLimitSources", s), "color": "#1BA1E2"},
{
"title": tr_noop("Show SLC Offset"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("ShowSLCOffset"),
"set_state": lambda s: self._params.put_bool("ShowSLCOffset", s),
"color": "#1BA1E2",
},
{
"title": tr_noop("Show Sources"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("SpeedLimitSources"),
"set_state": lambda s: self._params.put_bool("SpeedLimitSources", s),
"color": "#1BA1E2",
},
]
self._rebuild_grid()
class StarPilotWeatherLayout(StarPilotPanel):
def __init__(self):
super().__init__()
@@ -295,18 +747,77 @@ class StarPilotWeatherLayout(StarPilotPanel):
{"title": tr_noop("Rain"), "panel": "rain", "icon": "toggle_icons/icon_rainbow.png", "color": "#1BA1E2"},
{"title": tr_noop("Rainstorms"), "panel": "rainstorm", "icon": "toggle_icons/icon_rainbow.png", "color": "#1BA1E2"},
{"title": tr_noop("Snow"), "panel": "snow", "icon": "toggle_icons/icon_rainbow.png", "color": "#1BA1E2"},
{
"title": tr_noop("Set Weather Key"),
"type": "hub",
"on_click": lambda: self._set_weather_key(),
"color": "#1BA1E2",
},
]
self._rebuild_grid()
def _set_weather_key(self):
options = ["ADD", "REMOVE"]
def on_select(res, val):
if res == DialogResult.CONFIRM:
if val == "ADD":
def on_key(res, text):
if res == DialogResult.CONFIRM:
self._params.put("WeatherAPIKey", text)
self._rebuild_grid()
gui_app.set_modal_overlay(InputDialog(tr("Weather API Key"), on_close=on_key))
elif val == "REMOVE":
def on_confirm(res):
if res == DialogResult.CONFIRM:
self._params.remove("WeatherAPIKey")
self._rebuild_grid()
gui_app.set_modal_overlay(ConfirmDialog(tr("Remove API Key?"), tr("Confirm"), on_close=on_confirm))
gui_app.set_modal_overlay(SelectionDialog(tr("Weather API Key"), options, "ADD", on_close=on_select))
class StarPilotWeatherBase(StarPilotPanel):
def __init__(self, suffix: str):
super().__init__()
self._suffix = suffix
self.CATEGORIES = [
{"title": tr_noop("Following Distance"), "type": "value", "get_value": lambda: f"+{self._params.get_int('IncreaseFollowing' + self._suffix)}s", "on_click": lambda: self._show_value_selector("IncreaseFollowing" + self._suffix, 0, 3, 0.5, "s"), "icon": "toggle_icons/icon_longitudinal_tune.png", "color": "#1BA1E2"},
{"title": tr_noop("Stopped Distance"), "type": "value", "get_value": lambda: f"+{self._params.get_int('IncreasedStoppedDistance' + self._suffix)} ft", "on_click": lambda: self._show_value_selector("IncreasedStoppedDistance" + self._suffix, 0, 10, 1, " ft"), "icon": "toggle_icons/icon_longitudinal_tune.png", "color": "#1BA1E2"},
{"title": tr_noop("Reduce Accel"), "type": "value", "get_value": lambda: f"{self._params.get_int('ReduceAcceleration' + self._suffix)}%", "on_click": lambda: self._show_value_selector("ReduceAcceleration" + self._suffix, 0, 50, 5, "%"), "icon": "toggle_icons/icon_longitudinal_tune.png", "color": "#1BA1E2"},
{"title": tr_noop("Reduce Curve Speed"), "type": "value", "get_value": lambda: f"{self._params.get_int('ReduceLateralAcceleration' + self._suffix)}%", "on_click": lambda: self._show_value_selector("ReduceLateralAcceleration" + self._suffix, 0, 50, 5, "%"), "icon": "toggle_icons/icon_longitudinal_tune.png", "color": "#1BA1E2"},
{
"title": tr_noop("Following Distance"),
"type": "value",
"get_value": lambda: f"+{self._params.get_int('IncreaseFollowing' + self._suffix)}s",
"on_click": lambda: self._show_value_selector("IncreaseFollowing" + self._suffix, 0, 3, 0.5, "s"),
"icon": "toggle_icons/icon_longitudinal_tune.png",
"color": "#1BA1E2",
},
{
"title": tr_noop("Stopped Distance"),
"type": "value",
"get_value": lambda: f"+{self._params.get_int('IncreasedStoppedDistance' + self._suffix)} ft",
"on_click": lambda: self._show_value_selector("IncreasedStoppedDistance" + self._suffix, 0, 10, 1, " ft"),
"icon": "toggle_icons/icon_longitudinal_tune.png",
"color": "#1BA1E2",
},
{
"title": tr_noop("Reduce Accel"),
"type": "value",
"get_value": lambda: f"{self._params.get_int('ReduceAcceleration' + self._suffix)}%",
"on_click": lambda: self._show_value_selector("ReduceAcceleration" + self._suffix, 0, 99, 1, "%"),
"icon": "toggle_icons/icon_longitudinal_tune.png",
"color": "#1BA1E2",
},
{
"title": tr_noop("Reduce Curve Speed"),
"type": "value",
"get_value": lambda: f"{self._params.get_int('ReduceLateralAcceleration' + self._suffix)}%",
"on_click": lambda: self._show_value_selector("ReduceLateralAcceleration" + self._suffix, 0, 99, 1, "%"),
"icon": "toggle_icons/icon_longitudinal_tune.png",
"color": "#1BA1E2",
},
]
self._rebuild_grid()
@@ -315,5 +826,6 @@ class StarPilotWeatherBase(StarPilotPanel):
if res == DialogResult.CONFIRM:
self._params.put_int(key, int(float(val)))
self._rebuild_grid()
curr = self._params.get_int(key)
gui_app.set_modal_overlay(SliderDialog(tr(key), min_v, max_v, step, curr, on_close, unit=unit, color="#1BA1E2"))
@@ -1,15 +1,12 @@
from __future__ import annotations
import os
import shutil
from pathlib import Path
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets import DialogResult
from openpilot.system.ui.widgets.selection_dialog import SelectionDialog
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dialog
from openpilot.system.ui.widgets.input_dialog import InputDialog
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
from openpilot.selfdrive.ui.layouts.settings.starpilot.metro import SliderDialog
class StarPilotNavigationLayout(StarPilotPanel):
def __init__(self):
@@ -20,42 +17,118 @@ class StarPilotNavigationLayout(StarPilotPanel):
self.CATEGORIES = [
{"title": tr_noop("Mapbox Credentials"), "panel": "mapbox", "icon": "toggle_icons/icon_navigate.png", "color": "#8CBF26"},
{"title": tr_noop("Setup Instructions"), "type": "hub", "on_click": self._on_setup, "icon": "toggle_icons/icon_navigate.png", "color": "#8CBF26"},
{"title": tr_noop("Speed Limit Filler"), "type": "toggle", "get_state": lambda: self._params.get_bool("SpeedLimitFiller"), "set_state": lambda s: self._params.put_bool("SpeedLimitFiller", s), "icon": "toggle_icons/icon_speed_limit.png", "color": "#8CBF26"},
{
"title": tr_noop("Speed Limit Filler"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("SpeedLimitFiller"),
"set_state": lambda s: self._params.put_bool("SpeedLimitFiller", s),
"icon": "toggle_icons/icon_speed_limit.png",
"color": "#8CBF26",
},
{"title": tr_noop("Search Destination"), "type": "hub", "on_click": self._on_search, "icon": "toggle_icons/icon_navigate.png", "color": "#8CBF26"},
{"title": tr_noop("Home Address"), "type": "hub", "on_click": self._on_home, "icon": "toggle_icons/icon_navigate.png", "color": "#8CBF26"},
{"title": tr_noop("Work Address"), "type": "hub", "on_click": self._on_work, "icon": "toggle_icons/icon_navigate.png", "color": "#8CBF26"},
{
"title": tr_noop("Home Address"),
"type": "value",
"get_value": lambda: self._params.get("HomeAddress", encoding='utf-8') or tr("Not set"),
"on_click": self._on_home,
"icon": "toggle_icons/icon_navigate.png",
"color": "#8CBF26",
},
{
"title": tr_noop("Work Address"),
"type": "value",
"get_value": lambda: self._params.get("WorkAddress", encoding='utf-8') or tr("Not set"),
"on_click": self._on_work,
"icon": "toggle_icons/icon_navigate.png",
"color": "#8CBF26",
},
]
for name, panel in self._sub_panels.items():
if hasattr(panel, 'set_navigate_callback'): panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'): panel.set_back_callback(self._go_back)
if hasattr(panel, 'set_navigate_callback'):
panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'):
panel.set_back_callback(self._go_back)
self._rebuild_grid()
def _on_setup(self):
gui_app.set_modal_overlay(alert_dialog(tr("Mapbox Setup:\n1. Create account at mapbox.com\n2. Generate Public/Secret keys\n3. Add keys in 'Mapbox Credentials'")))
gui_app.set_modal_overlay(
alert_dialog(tr("Mapbox Setup:\n1. Create account at mapbox.com\n2. Generate Public/Secret keys\n3. Add keys in 'Mapbox Credentials'"))
)
def _on_search(self):
gui_app.set_modal_overlay(alert_dialog(tr("Search not yet implemented.")))
def on_close(res, text):
if res == DialogResult.CONFIRM and text:
self._params.put("SearchAddress", text)
gui_app.set_modal_overlay(InputDialog(tr("Search Destination"), "", on_close=on_close))
def _on_home(self):
gui_app.set_modal_overlay(alert_dialog(tr("Home address set.")))
current = self._params.get("HomeAddress", encoding='utf-8') or ""
def on_close(res, text):
if res == DialogResult.CONFIRM:
self._params.put("HomeAddress", text)
self._rebuild_grid()
gui_app.set_modal_overlay(InputDialog(tr("Home Address"), current, on_close=on_close))
def _on_work(self):
gui_app.set_modal_overlay(alert_dialog(tr("Work address set.")))
current = self._params.get("WorkAddress", encoding='utf-8') or ""
def on_close(res, text):
if res == DialogResult.CONFIRM:
self._params.put("WorkAddress", text)
self._rebuild_grid()
gui_app.set_modal_overlay(InputDialog(tr("Work Address"), current, on_close=on_close))
class StarPilotMapboxLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Public Mapbox Key"), "type": "hub", "on_click": lambda: self._on_key("MapboxPublicKey"), "color": "#8CBF26"},
{"title": tr_noop("Secret Mapbox Key"), "type": "hub", "on_click": lambda: self._on_key("MapboxSecretKey"), "color": "#8CBF26"},
{
"title": tr_noop("Public Mapbox Key"),
"type": "value",
"get_value": self._get_key_display,
"on_click": lambda: self._on_key("MapboxPublicKey", "pk."),
"color": "#8CBF26",
},
{
"title": tr_noop("Secret Mapbox Key"),
"type": "value",
"get_value": self._get_secret_display,
"on_click": lambda: self._on_key("MapboxSecretKey", "sk."),
"color": "#8CBF26",
},
]
self._rebuild_grid()
def _on_key(self, key):
# Simplified keyboard entry for UI port
def _get_key_display(self):
v = self._params.get("MapboxPublicKey", encoding='utf-8') or ""
return f"{v[:8]}..." if v else tr("Not set")
def _get_secret_display(self):
v = self._params.get("MapboxSecretKey", encoding='utf-8') or ""
return "********" if v else tr("Not set")
def _on_key(self, key, prefix):
current = self._params.get(key, encoding='utf-8') or ""
def on_confirm(res):
if current:
def on_remove(res):
if res == DialogResult.CONFIRM:
# In a real build, we'd trigger a keyboard overlay
pass
gui_app.set_modal_overlay(ConfirmDialog(tr(f"Current Key:\n{current[:20]}..."), tr("Change"), on_close=on_confirm))
self._params.remove(key)
self._rebuild_grid()
gui_app.set_modal_overlay(ConfirmDialog(tr(f"Remove your {key.replace('Mapbox', '')} key?"), tr("Remove"), on_close=on_remove))
else:
def on_close(res, text):
if res == DialogResult.CONFIRM and text:
if not text.startswith(prefix):
text = prefix + text
self._params.put(key, text)
self._rebuild_grid()
gui_app.set_modal_overlay(InputDialog(tr(f"Enter {key.replace('Mapbox', 'Mapbox ')}"), "", on_close=on_close))
@@ -9,6 +9,7 @@ from openpilot.common.params import Params
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.widgets import Widget
class StarPilotPanelType(IntEnum):
MAIN = 0
SOUNDS = 1
@@ -25,13 +26,16 @@ class StarPilotPanelType(IntEnum):
VEHICLE = 12
WHEEL = 13
@dataclass
class StarPilotPanelInfo:
name: str
instance: Widget
from openpilot.selfdrive.ui.layouts.settings.starpilot.metro import TileGrid, HubTile, ToggleTile, ValueTile
class StarPilotPanel(Widget):
def __init__(self):
super().__init__()
@@ -54,13 +58,17 @@ class StarPilotPanel(Widget):
def _rebuild_grid(self):
if not self.CATEGORIES:
return
if self._tile_grid is None:
self._tile_grid = TileGrid(columns=None, padding=20)
self._tile_grid.clear()
for cat in self.CATEGORIES:
visible_fn = cat.get("visible")
if visible_fn is not None and not visible_fn():
continue
tile_type = cat.get("type", "hub")
if tile_type == "hub":
on_click = cat.get("on_click")
@@ -73,24 +81,12 @@ class StarPilotPanel(Widget):
icon_path=cat.get("icon"),
on_click=on_click,
starpilot_icon=cat.get("starpilot_icon", True),
bg_color=cat.get("color")
bg_color=cat.get("color"),
)
elif tile_type == "toggle":
tile = ToggleTile(
title=tr(cat["title"]),
get_state=cat["get_state"],
set_state=cat["set_state"],
icon_path=cat.get("icon"),
bg_color=cat.get("color")
)
tile = ToggleTile(title=tr(cat["title"]), get_state=cat["get_state"], set_state=cat["set_state"], icon_path=cat.get("icon"), bg_color=cat.get("color"))
elif tile_type == "value":
tile = ValueTile(
title=tr(cat["title"]),
get_value=cat["get_value"],
on_click=cat["on_click"],
icon_path=cat.get("icon"),
bg_color=cat.get("color")
)
tile = ValueTile(title=tr(cat["title"]), get_value=cat["get_value"], on_click=cat["on_click"], icon_path=cat.get("icon"), bg_color=cat.get("color"))
else:
continue
@@ -5,10 +5,11 @@ from openpilot.system.ui.widgets import DialogResult
from openpilot.system.ui.widgets.selection_dialog import SelectionDialog
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
class StarPilotThemesLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self._sub_panels = {
"personalize": StarPilotPersonalizeLayout(),
}
@@ -19,7 +20,7 @@ class StarPilotThemesLayout(StarPilotPanel):
"panel": "personalize",
"icon": "toggle_icons/icon_frog.png",
"color": "#A200FF",
"desc": tr_noop("Customize the overall look and feel.")
"desc": tr_noop("Customize the overall look and feel."),
},
{
"title": tr_noop("Holiday Themes"),
@@ -27,7 +28,7 @@ class StarPilotThemesLayout(StarPilotPanel):
"get_state": lambda: self._params.get_bool("HolidayThemes"),
"set_state": lambda s: self._params.put_bool("HolidayThemes", s),
"icon": "toggle_icons/icon_calendar.png",
"color": "#A200FF"
"color": "#A200FF",
},
{
"title": tr_noop("Rainbow Path"),
@@ -35,7 +36,7 @@ class StarPilotThemesLayout(StarPilotPanel):
"get_state": lambda: self._params.get_bool("RainbowPath"),
"set_state": lambda s: self._params.put_bool("RainbowPath", s),
"icon": "toggle_icons/icon_rainbow.png",
"color": "#A200FF"
"color": "#A200FF",
},
{
"title": tr_noop("Random Events"),
@@ -43,7 +44,7 @@ class StarPilotThemesLayout(StarPilotPanel):
"get_state": lambda: self._params.get_bool("RandomEvents"),
"set_state": lambda s: self._params.put_bool("RandomEvents", s),
"icon": "toggle_icons/icon_random.png",
"color": "#A200FF"
"color": "#A200FF",
},
{
"title": tr_noop("Random Themes"),
@@ -51,27 +52,97 @@ class StarPilotThemesLayout(StarPilotPanel):
"get_state": lambda: self._params.get_bool("RandomThemes"),
"set_state": lambda s: self._params.put_bool("RandomThemes", s),
"icon": "toggle_icons/icon_random_themes.png",
"color": "#A200FF"
"color": "#A200FF",
},
{"title": tr_noop("Startup Alert"), "type": "hub", "on_click": self._on_startup_alert, "color": "#A200FF"},
]
for name, panel in self._sub_panels.items():
if hasattr(panel, 'set_navigate_callback'): panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'): panel.set_back_callback(self._go_back)
if hasattr(panel, 'set_navigate_callback'):
panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'):
panel.set_back_callback(self._go_back)
self._rebuild_grid()
def _on_startup_alert(self):
options = ["Stock", "FrogPilot", "Clear"]
current_top = self._params.get("StartupMessageTop", encoding='utf-8') or ""
if current_top == "Be ready to take over at any time":
current = "Stock"
elif current_top == "Hop in and buckle up!":
current = "FrogPilot"
else:
current = "Clear"
def on_select(res, val):
if res == DialogResult.CONFIRM:
if val == "Stock":
self._params.put("StartupMessageTop", "Be ready to take over at any time")
self._params.put("StartupMessageBottom", "Always keep hands on wheel and eyes on road")
elif val == "FrogPilot":
self._params.put("StartupMessageTop", "Hop in and buckle up!")
self._params.put("StartupMessageBottom", "Human-tested, frog-approved")
else:
self._params.remove("StartupMessageTop")
self._params.remove("StartupMessageBottom")
gui_app.set_modal_overlay(SelectionDialog(tr("Startup Alert"), options, current, on_close=on_select))
class StarPilotPersonalizeLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Boot Logo"), "type": "value", "get_value": lambda: self._params.get("BootLogo", encoding='utf-8') or "Stock", "on_click": lambda: self._show_theme_selector("BootLogo"), "color": "#A200FF"},
{"title": tr_noop("Color Scheme"), "type": "value", "get_value": lambda: self._params.get("ColorScheme", encoding='utf-8') or "Stock", "on_click": lambda: self._show_theme_selector("ColorScheme"), "color": "#A200FF"},
{"title": tr_noop("Distance Icons"), "type": "value", "get_value": lambda: self._params.get("DistanceIconPack", encoding='utf-8') or "Stock", "on_click": lambda: self._show_theme_selector("DistanceIconPack"), "color": "#A200FF"},
{"title": tr_noop("Icon Pack"), "type": "value", "get_value": lambda: self._params.get("IconPack", encoding='utf-8') or "Stock", "on_click": lambda: self._show_theme_selector("IconPack"), "color": "#A200FF"},
{"title": tr_noop("Turn Signals"), "type": "value", "get_value": lambda: self._params.get("SignalAnimation", encoding='utf-8') or "Stock", "on_click": lambda: self._show_theme_selector("SignalAnimation"), "color": "#A200FF"},
{"title": tr_noop("Sound Pack"), "type": "value", "get_value": lambda: self._params.get("SoundPack", encoding='utf-8') or "Stock", "on_click": lambda: self._show_theme_selector("SoundPack"), "color": "#A200FF"},
{"title": tr_noop("Steering Wheel"), "type": "value", "get_value": lambda: self._params.get("WheelIcon", encoding='utf-8') or "Stock", "on_click": lambda: self._show_theme_selector("WheelIcon"), "color": "#A200FF"},
{
"title": tr_noop("Boot Logo"),
"type": "value",
"get_value": lambda: self._params.get("BootLogo", encoding='utf-8') or "Stock",
"on_click": lambda: self._show_theme_selector("BootLogo"),
"color": "#A200FF",
},
{
"title": tr_noop("Color Scheme"),
"type": "value",
"get_value": lambda: self._params.get("ColorScheme", encoding='utf-8') or "Stock",
"on_click": lambda: self._show_theme_selector("ColorScheme"),
"color": "#A200FF",
},
{
"title": tr_noop("Distance Icons"),
"type": "value",
"get_value": lambda: self._params.get("DistanceIconPack", encoding='utf-8') or "Stock",
"on_click": lambda: self._show_theme_selector("DistanceIconPack"),
"color": "#A200FF",
},
{
"title": tr_noop("Icon Pack"),
"type": "value",
"get_value": lambda: self._params.get("IconPack", encoding='utf-8') or "Stock",
"on_click": lambda: self._show_theme_selector("IconPack"),
"color": "#A200FF",
},
{
"title": tr_noop("Turn Signals"),
"type": "value",
"get_value": lambda: self._params.get("SignalAnimation", encoding='utf-8') or "Stock",
"on_click": lambda: self._show_theme_selector("SignalAnimation"),
"color": "#A200FF",
},
{
"title": tr_noop("Sound Pack"),
"type": "value",
"get_value": lambda: self._params.get("SoundPack", encoding='utf-8') or "Stock",
"on_click": lambda: self._show_theme_selector("SoundPack"),
"color": "#A200FF",
},
{
"title": tr_noop("Steering Wheel"),
"type": "value",
"get_value": lambda: self._params.get("WheelIcon", encoding='utf-8') or "Stock",
"on_click": lambda: self._show_theme_selector("WheelIcon"),
"color": "#A200FF",
},
]
self._rebuild_grid()
@@ -80,10 +151,10 @@ class StarPilotPersonalizeLayout(StarPilotPanel):
# For now, we'll provide a simplified selection based on current param.
themes = ["Stock", "Frog", "Cyberpunk", "Minimal"]
current = self._params.get(key, encoding='utf-8') or "Stock"
def on_select(res, val):
if res == DialogResult.CONFIRM:
self._params.put(key, val)
self._rebuild_grid()
gui_app.set_modal_overlay(SelectionDialog(tr(key), themes, current, on_close=on_select))
@@ -1,28 +1,77 @@
from __future__ import annotations
import json
from openpilot.system.hardware import HARDWARE
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets import DialogResult
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dialog
from openpilot.system.ui.widgets.selection_dialog import SelectionDialog
from openpilot.system.ui.widgets.input_dialog import InputDialog
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
EXCLUDED_KEYS = {
"AvailableModels",
"AvailableModelNames",
"FrogPilotStats",
"GithubSshKeys",
"GithubUsername",
"MapBoxRequests",
"ModelDrivesAndScores",
"OverpassRequests",
"SpeedLimits",
"SpeedLimitsFiltered",
"UpdaterAvailableBranches",
}
REPORT_CATEGORIES = [
"Acceleration feels harsh or jerky",
"An alert was unclear and I'm not sure what it meant",
"Braking is too sudden or uncomfortable",
"I'm not sure if this is normal or a bug:",
"My steering wheel buttons aren't working",
"openpilot disengages when I don't expect it",
"openpilot feels sluggish or slow to respond",
"Something else (please describe)",
]
class StarPilotUtilitiesLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Debug Mode"), "type": "toggle", "get_state": lambda: self._params.get_bool("DebugMode"), "set_state": lambda s: self._params.put_bool("DebugMode", s), "color": "#FA6800"},
{
"title": tr_noop("Debug Mode"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("DebugMode"),
"set_state": lambda s: self._params.put_bool("DebugMode", s),
"color": "#FA6800",
},
{"title": tr_noop("Flash Panda"), "type": "hub", "on_click": self._on_flash_panda, "color": "#FA6800"},
{"title": tr_noop("Force Drive State"), "type": "value", "get_value": self._get_force_drive_state, "on_click": self._on_force_drive_state, "color": "#FA6800"},
{"title": tr_noop("The Pond"), "type": "hub", "on_click": self._on_pond_clicked, "color": "#FA6800"},
{
"title": tr_noop("Force Drive State"),
"type": "value",
"get_value": self._get_force_drive_state,
"on_click": self._on_force_drive_state,
"color": "#FA6800",
},
{
"title": tr_noop("The Pond"),
"type": "value",
"get_value": lambda: tr("Paired") if self._params.get_bool("PondPaired") else tr("Not paired"),
"on_click": self._on_pond_clicked,
"color": "#FA6800",
},
{"title": tr_noop("Report Issue"), "type": "hub", "on_click": self._on_report_issue, "color": "#FA6800"},
{"title": tr_noop("Reset Toggles"), "type": "hub", "on_click": self._on_reset_toggles, "color": "#FA6800"},
{"title": tr_noop("Reset to Defaults"), "type": "hub", "on_click": self._on_reset_defaults, "color": "#FA6800"},
{"title": tr_noop("Reset to Stock"), "type": "hub", "on_click": self._on_reset_stock, "color": "#FA6800"},
]
self._rebuild_grid()
def _get_force_drive_state(self):
if self._params.get_bool("ForceOnroad"): return tr("Onroad")
if self._params.get_bool("ForceOffroad"): return tr("Offroad")
if self._params.get_bool("ForceOnroad"):
return tr("Onroad")
if self._params.get_bool("ForceOffroad"):
return tr("Offroad")
return tr("Default")
def _on_flash_panda(self):
@@ -30,10 +79,12 @@ class StarPilotUtilitiesLayout(StarPilotPanel):
if res == DialogResult.CONFIRM:
self._params_memory.put_bool("FlashPanda", True)
gui_app.set_modal_overlay(alert_dialog(tr("Panda flashing started. Device will reboot when finished.")))
gui_app.set_modal_overlay(ConfirmDialog(tr("Flash Panda firmware?"), tr("Flash"), on_close=_do_flash))
def _on_force_drive_state(self):
options = [tr("Offroad"), tr("Onroad"), tr("Default")]
def on_select(res, val):
if res == DialogResult.CONFIRM:
if val == tr("Offroad"):
@@ -46,23 +97,67 @@ class StarPilotUtilitiesLayout(StarPilotPanel):
self._params.put_bool("ForceOffroad", False)
self._params.put_bool("ForceOnroad", False)
self._rebuild_grid()
current = self._get_force_drive_state()
gui_app.set_modal_overlay(SelectionDialog(tr("Force Drive State"), options, current, on_close=on_select))
def _on_pond_clicked(self):
gui_app.set_modal_overlay(alert_dialog(tr("The Pond pairing not yet implemented in Python.")))
paired = self._params.get_bool("PondPaired")
if paired:
def on_unpair(res):
if res == DialogResult.CONFIRM:
self._params.put_bool("PondPaired", False)
gui_app.set_modal_overlay(alert_dialog(tr("Unpaired from The Pond.")))
self._rebuild_grid()
gui_app.set_modal_overlay(ConfirmDialog(tr("Unpair from The Pond?"), tr("Unpair"), on_close=on_unpair))
else:
gui_app.set_modal_overlay(alert_dialog(tr("Visit frogpilot.com/the_pond to pair your device.")))
def _on_report_issue(self):
gui_app.set_modal_overlay(alert_dialog(tr("Issue reporting not yet implemented in Python.")))
def on_category(res, val):
if res != DialogResult.CONFIRM:
return
discord_user = self._params.get("DiscordUsername", encoding='utf-8') or ""
def _on_reset_toggles(self):
def on_discord(res2, username):
if res2 == DialogResult.CONFIRM and username:
self._params.put("DiscordUsername", username)
report = json.dumps({"DiscordUser": username, "Issue": val})
self._params_memory.put("IssueReported", report)
gui_app.set_modal_overlay(alert_dialog(tr("Issue reported. Thank you!")))
gui_app.set_modal_overlay(InputDialog(tr("Discord Username"), discord_user or "", on_close=on_discord))
gui_app.set_modal_overlay(SelectionDialog(tr("Select Issue"), REPORT_CATEGORIES, on_close=on_category))
def _on_reset_defaults(self):
def _do_reset(res):
if res == DialogResult.CONFIRM:
# Simplified reset logic
all_keys = self._params.all_keys()
for k in all_keys:
if k in EXCLUDED_KEYS:
continue
default = self._params.get_default_value(k)
if default: self._params.put(k, default)
gui_app.set_modal_overlay(alert_dialog(tr("Toggles reset to default.")))
if default is not None:
self._params.put(k, default)
gui_app.set_modal_overlay(alert_dialog(tr("Toggles reset to defaults.")))
self._rebuild_grid()
gui_app.set_modal_overlay(ConfirmDialog(tr("Reset all toggles to default?"), tr("Reset"), on_close=_do_reset))
gui_app.set_modal_overlay(ConfirmDialog(tr("Reset all toggles to defaults?"), tr("Reset"), on_close=_do_reset))
def _on_reset_stock(self):
def _do_reset(res):
if res == DialogResult.CONFIRM:
all_keys = self._params.all_keys()
for k in all_keys:
if k in EXCLUDED_KEYS:
continue
stock = self._params.get_stock_value(k)
if stock is not None:
self._params.put(k, stock)
gui_app.set_modal_overlay(alert_dialog(tr("Toggles reset to stock openpilot.")))
self._rebuild_grid()
gui_app.set_modal_overlay(ConfirmDialog(tr("Reset all toggles to stock openpilot?"), tr("Reset"), on_close=_do_reset))
@@ -10,38 +10,64 @@ from openpilot.system.ui.widgets import DialogResult
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
from openpilot.system.ui.widgets.selection_dialog import SelectionDialog
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
from openpilot.selfdrive.ui.layouts.settings.starpilot.metro import SliderDialog
from openpilot.selfdrive.ui.layouts.settings.starpilot.metro import SliderDialog, TileGrid, HubTile, ToggleTile, ValueTile
from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state
MAKE_TO_FOLDER = {
"acura": "honda", "audi": "volkswagen", "buick": "gm", "cadillac": "gm", "chevrolet": "gm",
"chrysler": "chrysler", "cupra": "volkswagen", "dodge": "chrysler", "ford": "ford",
"genesis": "hyundai", "gmc": "gm", "holden": "gm", "honda": "honda", "hyundai": "hyundai",
"jeep": "chrysler", "kia": "hyundai", "lexus": "toyota", "lincoln": "ford", "man": "volkswagen",
"mazda": "mazda", "nissan": "nissan", "peugeot": "psa", "ram": "chrysler", "rivian": "rivian",
"seat": "volkswagen", "škoda": "volkswagen", "subaru": "subaru", "tesla": "tesla",
"toyota": "toyota", "volkswagen": "volkswagen"
"acura": "honda",
"audi": "volkswagen",
"buick": "gm",
"cadillac": "gm",
"chevrolet": "gm",
"chrysler": "chrysler",
"cupra": "volkswagen",
"dodge": "chrysler",
"ford": "ford",
"genesis": "hyundai",
"gmc": "gm",
"holden": "gm",
"honda": "honda",
"hyundai": "hyundai",
"jeep": "chrysler",
"kia": "hyundai",
"lexus": "toyota",
"lincoln": "ford",
"man": "volkswagen",
"mazda": "mazda",
"nissan": "nissan",
"peugeot": "psa",
"ram": "chrysler",
"rivian": "rivian",
"seat": "volkswagen",
"škoda": "volkswagen",
"subaru": "subaru",
"tesla": "tesla",
"toyota": "toyota",
"volkswagen": "volkswagen",
}
def get_car_names(car_make: str):
folder = MAKE_TO_FOLDER.get(car_make.lower())
if not folder: return [], {}
if not folder:
return [], {}
# Path to values.py in opendbc
values_path = Path(__file__).parents[4] / "opendbc" / "car" / folder / "values.py"
if not values_path.exists():
return [], {}
with open(values_path, "r") as f:
content = f.read()
# Clean comments
content = re.sub(r'#.*', '', content)
# Find platforms and car names
platforms = re.findall(r'(\w+)\s*=\s*\w+\s*\(', content)
car_models = {}
car_names = []
# This is a simplified version of the C++ regex logic
# In values.py, CarDocs often appears as CarDocs("Name", ...)
matches = re.finditer(r'CarDocs\w*\s*\(\s*"([^"]+)"', content)
@@ -51,9 +77,17 @@ def get_car_names(car_make: str):
# Find the platform name by looking backwards for the nearest platform assignment
# For now, we'll just store the name
car_names.append(name)
return sorted(list(set(car_names))), car_models
def _lock_doors_timer_labels():
labels: dict[float, str] = {0.0: tr("Never")}
for i in range(5, 305, 5):
labels[float(i)] = f"{i}s"
return labels
class StarPilotVehicleSettingsLayout(StarPilotPanel):
def __init__(self):
super().__init__()
@@ -64,46 +98,108 @@ class StarPilotVehicleSettingsLayout(StarPilotPanel):
"toyota": StarPilotToyotaVehicleLayout(),
"info": StarPilotVehicleInfoLayout(),
}
self.CATEGORIES = [
{
"title": tr_noop("Car Make"),
"type": "value",
"get_value": lambda: self._params.get("CarMake", encoding='utf-8') or tr("None"),
"on_click": self._on_select_make,
"color": "#FFC40D"
"color": "#FFC40D",
},
{
"title": tr_noop("Car Model"),
"type": "value",
"get_value": lambda: self._params.get("CarModelName", encoding='utf-8') or tr("None"),
"on_click": self._on_select_model,
"color": "#FFC40D"
"color": "#FFC40D",
},
{"title": tr_noop("Disable Fingerprinting"), "type": "toggle", "get_state": lambda: self._params.get_bool("ForceFingerprint"), "set_state": lambda s: self._params.put_bool("ForceFingerprint", s), "color": "#FFC40D"},
{"title": tr_noop("Disable openpilot Long"), "type": "toggle", "get_state": lambda: self._params.get_bool("DisableOpenpilotLongitudinal"), "set_state": self._on_disable_long, "color": "#FFC40D"},
{"title": tr_noop("GM Settings"), "panel": "gm", "icon": "toggle_icons/icon_vehicle.png", "color": "#FFC40D"},
{"title": tr_noop("HKG Settings"), "panel": "hkg", "icon": "toggle_icons/icon_vehicle.png", "color": "#FFC40D"},
{"title": tr_noop("Subaru Settings"), "panel": "subaru", "icon": "toggle_icons/icon_vehicle.png", "color": "#FFC40D"},
{"title": tr_noop("Toyota Settings"), "panel": "toyota", "icon": "toggle_icons/icon_vehicle.png", "color": "#FFC40D"},
{
"title": tr_noop("Disable Fingerprinting"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("ForceFingerprint"),
"set_state": lambda s: self._params.put_bool("ForceFingerprint", s),
"color": "#FFC40D",
},
{
"title": tr_noop("Disable openpilot Long"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("DisableOpenpilotLongitudinal"),
"set_state": self._on_disable_long,
"color": "#FFC40D",
},
{"title": tr_noop("GM Settings"), "panel": "gm", "icon": "toggle_icons/icon_vehicle.png", "color": "#FFC40D", "key": "gm"},
{"title": tr_noop("HKG Settings"), "panel": "hkg", "icon": "toggle_icons/icon_vehicle.png", "color": "#FFC40D", "key": "hkg"},
{"title": tr_noop("Subaru Settings"), "panel": "subaru", "icon": "toggle_icons/icon_vehicle.png", "color": "#FFC40D", "key": "subaru"},
{"title": tr_noop("Toyota Settings"), "panel": "toyota", "icon": "toggle_icons/icon_vehicle.png", "color": "#FFC40D", "key": "toyota"},
{"title": tr_noop("Vehicle Info"), "panel": "info", "icon": "toggle_icons/icon_vehicle.png", "color": "#FFC40D"},
]
for name, panel in self._sub_panels.items():
if hasattr(panel, 'set_navigate_callback'): panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'): panel.set_back_callback(self._go_back)
if hasattr(panel, 'set_navigate_callback'):
panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'):
panel.set_back_callback(self._go_back)
self._rebuild_grid()
def _rebuild_grid(self):
if not self.CATEGORIES:
return
if self._tile_grid is None:
self._tile_grid = TileGrid(columns=None, padding=20)
self._tile_grid.clear()
cs = starpilot_state.car_state
for cat in self.CATEGORIES:
key = cat.get("key")
visible = True
if key == "gm":
visible = cs.isGM
elif key == "hkg":
visible = cs.isHKG
elif key == "subaru":
visible = cs.isSubaru
elif key == "toyota":
visible = cs.isToyota
if not visible:
continue
tile_type = cat.get("type", "hub")
if tile_type == "hub":
on_click = cat.get("on_click")
if on_click is None:
on_click = lambda c=cat: self._navigate_to(c["panel"])
tile = HubTile(
title=tr(cat["title"]),
desc=tr(cat.get("desc", "")),
icon_path=cat.get("icon"),
on_click=on_click,
starpilot_icon=cat.get("starpilot_icon", True),
bg_color=cat.get("color"),
)
elif tile_type == "toggle":
tile = ToggleTile(title=tr(cat["title"]), get_state=cat["get_state"], set_state=cat["set_state"], icon_path=cat.get("icon"), bg_color=cat.get("color"))
elif tile_type == "value":
tile = ValueTile(title=tr(cat["title"]), get_value=cat["get_value"], on_click=cat["on_click"], icon_path=cat.get("icon"), bg_color=cat.get("color"))
else:
continue
self._tile_grid.add_tile(tile)
def _on_select_make(self):
makes = sorted(list(MAKE_TO_FOLDER.keys()))
makes = [m.capitalize() for m in makes]
def on_select(res, val):
if res == DialogResult.CONFIRM:
self._params.put("CarMake", val)
self._params.remove("CarModel")
self._params.remove("CarModelName")
self._rebuild_grid()
gui_app.set_modal_overlay(SelectionDialog(tr("Select Make"), makes, self._params.get("CarMake", encoding='utf-8') or "", on_close=on_select))
def _on_select_model(self):
@@ -111,7 +207,7 @@ class StarPilotVehicleSettingsLayout(StarPilotPanel):
if not make:
gui_app.set_modal_overlay(ConfirmDialog(tr("Please select a Car Make first!"), tr("OK"), on_close=lambda r: None))
return
models, _ = get_car_names(make)
if not models:
gui_app.set_modal_overlay(ConfirmDialog(tr("No models found for this make."), tr("OK"), on_close=lambda r: None))
@@ -122,71 +218,299 @@ class StarPilotVehicleSettingsLayout(StarPilotPanel):
self._params.put("CarModelName", val)
# In a real build we'd map name to platform code here
self._rebuild_grid()
gui_app.set_modal_overlay(SelectionDialog(tr("Select Model"), models, self._params.get("CarModelName", encoding='utf-8') or "", on_close=on_select))
def _on_disable_long(self, state):
if state:
def on_confirm(res):
if res == DialogResult.CONFIRM:
self._params.put_bool("DisableOpenpilotLongitudinal", True)
from openpilot.selfdrive.ui.ui_state import ui_state
if ui_state.started: HARDWARE.reboot()
if ui_state.started:
HARDWARE.reboot()
self._rebuild_grid()
gui_app.set_modal_overlay(ConfirmDialog(tr("Disable openpilot longitudinal control?"), tr("Disable"), on_close=on_confirm))
else:
self._params.put_bool("DisableOpenpilotLongitudinal", False)
self._rebuild_grid()
class StarPilotGMVehicleLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Pedal for Long"), "type": "toggle", "get_state": lambda: self._params.get_bool("GMPedalLongitudinal"), "set_state": lambda s: self._params.put_bool("GMPedalLongitudinal", s), "color": "#FFC40D"},
{"title": tr_noop("Remote Start Panda"), "type": "toggle", "get_state": lambda: self._params.get_bool("RemoteStartBootsComma"), "set_state": lambda s: self._params.put_bool("RemoteStartBootsComma", s), "color": "#FFC40D"},
{"title": tr_noop("Volt SNG Hack"), "type": "toggle", "get_state": lambda: self._params.get_bool("VoltSNG"), "set_state": lambda s: self._params.put_bool("VoltSNG", s), "color": "#FFC40D"},
{
"title": tr_noop("Pedal for Long"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("GMPedalLongitudinal"),
"set_state": lambda s: self._params.put_bool("GMPedalLongitudinal", s),
"color": "#FFC40D",
"key": "GMPedalLongitudinal",
},
{
"title": tr_noop("Remote Start Panda"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("RemoteStartBootsComma"),
"set_state": lambda s: self._params.put_bool("RemoteStartBootsComma", s),
"color": "#FFC40D",
},
{
"title": tr_noop("Volt SNG Hack"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("VoltSNG"),
"set_state": lambda s: self._params.put_bool("VoltSNG", s),
"color": "#FFC40D",
"key": "VoltSNG",
},
]
self._rebuild_grid()
def _rebuild_grid(self):
if not self.CATEGORIES:
return
if self._tile_grid is None:
self._tile_grid = TileGrid(columns=None, padding=20)
self._tile_grid.clear()
cs = starpilot_state.car_state
for cat in self.CATEGORIES:
key = cat.get("key")
visible = True
if key == "GMPedalLongitudinal":
visible = cs.hasPedal or cs.canUsePedal
elif key == "VoltSNG":
visible = cs.isVolt and not cs.hasSNG
if not visible:
continue
tile = ToggleTile(title=tr(cat["title"]), get_state=cat["get_state"], set_state=cat["set_state"], icon_path=cat.get("icon"), bg_color=cat.get("color"))
self._tile_grid.add_tile(tile)
class StarPilotHKGVehicleLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Taco Bell Torque Hack"), "type": "toggle", "get_state": lambda: self._params.get_bool("TacoTuneHacks"), "set_state": lambda s: self._params.put_bool("TacoTuneHacks", s), "color": "#FFC40D"},
{
"title": tr_noop("Taco Bell Torque Hack"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("TacoTuneHacks"),
"set_state": lambda s: self._params.put_bool("TacoTuneHacks", s),
"color": "#FFC40D",
"key": "TacoTuneHacks",
},
]
self._rebuild_grid()
def _rebuild_grid(self):
if not self.CATEGORIES:
return
if self._tile_grid is None:
self._tile_grid = TileGrid(columns=None, padding=20)
self._tile_grid.clear()
cs = starpilot_state.car_state
for cat in self.CATEGORIES:
key = cat.get("key")
visible = True
if key == "TacoTuneHacks":
visible = cs.isHKGCanFd
if not visible:
continue
tile = ToggleTile(title=tr(cat["title"]), get_state=cat["get_state"], set_state=cat["set_state"], icon_path=cat.get("icon"), bg_color=cat.get("color"))
self._tile_grid.add_tile(tile)
class StarPilotSubaruVehicleLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Stop and Go"), "type": "toggle", "get_state": lambda: self._params.get_bool("SubaruSNG"), "set_state": lambda s: self._params.put_bool("SubaruSNG", s), "color": "#FFC40D"},
{
"title": tr_noop("Stop and Go"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("SubaruSNG"),
"set_state": lambda s: self._params.put_bool("SubaruSNG", s),
"color": "#FFC40D",
},
]
self._rebuild_grid()
class StarPilotToyotaVehicleLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Auto Lock Doors"), "type": "toggle", "get_state": lambda: self._params.get_bool("LockDoors"), "set_state": lambda s: self._params.put_bool("LockDoors", s), "color": "#FFC40D"},
{"title": tr_noop("Auto Unlock Doors"), "type": "toggle", "get_state": lambda: self._params.get_bool("UnlockDoors"), "set_state": lambda s: self._params.put_bool("UnlockDoors", s), "color": "#FFC40D"},
{"title": tr_noop("Dashboard Speed Offset"), "type": "value", "get_value": lambda: f"{self._params.get_float('ClusterOffset'):.3f}x", "on_click": self._show_offset_selector, "color": "#FFC40D"},
{"title": tr_noop("Stop-and-Go Hack"), "type": "toggle", "get_state": lambda: self._params.get_bool("SNGHack"), "set_state": lambda s: self._params.put_bool("SNGHack", s), "color": "#FFC40D"},
{
"title": tr_noop("Auto Lock Doors"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("LockDoors"),
"set_state": lambda s: self._params.put_bool("LockDoors", s),
"color": "#FFC40D",
},
{
"title": tr_noop("Auto Unlock Doors"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("UnlockDoors"),
"set_state": lambda s: self._params.put_bool("UnlockDoors", s),
"color": "#FFC40D",
},
{
"title": tr_noop("Lock Doors Timer"),
"type": "value",
"get_value": lambda: _lock_doors_timer_labels().get(self._params.get_int('LockDoorsTimer'), f"{self._params.get_int('LockDoorsTimer')}s"),
"on_click": self._show_lock_timer_selector,
"color": "#FFC40D",
},
{
"title": tr_noop("Dashboard Speed Offset"),
"type": "value",
"get_value": lambda: f"{self._params.get_float('ClusterOffset'):.3f}x",
"on_click": self._show_offset_selector,
"color": "#FFC40D",
},
{
"title": tr_noop("Stop-and-Go Hack"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("SNGHack"),
"set_state": lambda s: self._params.put_bool("SNGHack", s),
"color": "#FFC40D",
"key": "SNGHack",
},
{
"title": tr_noop("FrogsGoMoo Tweak"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("FrogsGoMoosTweak"),
"set_state": lambda s: self._params.put_bool("FrogsGoMoosTweak", s),
"color": "#FFC40D",
"key": "FrogsGoMoosTweak",
},
]
self._rebuild_grid()
def _rebuild_grid(self):
if not self.CATEGORIES:
return
if self._tile_grid is None:
self._tile_grid = TileGrid(columns=None, padding=20)
self._tile_grid.clear()
cs = starpilot_state.car_state
for cat in self.CATEGORIES:
key = cat.get("key")
visible = True
if key == "SNGHack":
visible = not cs.hasSNG
elif key == "FrogsGoMoosTweak":
visible = cs.hasOpenpilotLongitudinal
if not visible:
continue
tile_type = cat.get("type", "hub")
if tile_type == "toggle":
tile = ToggleTile(title=tr(cat["title"]), get_state=cat["get_state"], set_state=cat["set_state"], icon_path=cat.get("icon"), bg_color=cat.get("color"))
elif tile_type == "value":
tile = ValueTile(title=tr(cat["title"]), get_value=cat["get_value"], on_click=cat["on_click"], icon_path=cat.get("icon"), bg_color=cat.get("color"))
else:
continue
self._tile_grid.add_tile(tile)
def _show_lock_timer_selector(self):
def on_close(res, val):
if res == DialogResult.CONFIRM:
self._params.put_int("LockDoorsTimer", int(val))
self._rebuild_grid()
gui_app.set_modal_overlay(
SliderDialog(tr("Lock Doors Timer"), 0, 300, 5, self._params.get_int("LockDoorsTimer"), on_close, labels=_lock_doors_timer_labels(), color="#FFC40D")
)
def _show_offset_selector(self):
def on_close(res, val):
if res == DialogResult.CONFIRM:
self._params.put_float("ClusterOffset", float(val))
self._rebuild_grid()
gui_app.set_modal_overlay(SliderDialog(tr("Dashboard Speed Offset"), 1.000, 1.050, 0.001, self._params.get_float("ClusterOffset"), on_close, unit="x", color="#FFC40D"))
gui_app.set_modal_overlay(
SliderDialog(tr("Dashboard Speed Offset"), 1.000, 1.050, 0.001, self._params.get_float("ClusterOffset"), on_close, unit="x", color="#FFC40D")
)
class StarPilotVehicleInfoLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Radar Support"), "type": "value", "get_value": lambda: tr("Yes") if starpilot_state.car_state.hasRadar else tr("No"), "on_click": lambda: None, "color": "#FFC40D"},
{"title": tr_noop("Longitudinal Support"), "type": "value", "get_value": lambda: tr("Yes") if starpilot_state.car_state.hasOpenpilotLongitudinal else tr("No"), "on_click": lambda: None, "color": "#FFC40D"},
{"title": tr_noop("Blind Spot Support"), "type": "value", "get_value": lambda: tr("Yes") if starpilot_state.car_state.hasBSM else tr("No"), "on_click": lambda: None, "color": "#FFC40D"},
{
"title": tr_noop("Radar Support"),
"type": "value",
"get_value": lambda: tr("Yes") if starpilot_state.car_state.hasRadar else tr("No"),
"on_click": lambda: None,
"color": "#FFC40D",
},
{
"title": tr_noop("Longitudinal Support"),
"type": "value",
"get_value": lambda: tr("Yes") if starpilot_state.car_state.hasOpenpilotLongitudinal else tr("No"),
"on_click": lambda: None,
"color": "#FFC40D",
},
{
"title": tr_noop("Blind Spot Support"),
"type": "value",
"get_value": lambda: tr("Yes") if starpilot_state.car_state.hasBSM else tr("No"),
"on_click": lambda: None,
"color": "#FFC40D",
},
{
"title": tr_noop("Hardware Detected"),
"type": "value",
"get_value": lambda: (
", ".join(
filter(
None,
[
tr("Pedal") if starpilot_state.car_state.canUsePedal else "",
tr("SDSU") if starpilot_state.car_state.canUseSDSU else "",
tr("ZSS") if starpilot_state.car_state.hasZSS else "",
],
)
)
or tr("None")
),
"on_click": lambda: None,
"color": "#FFC40D",
},
{
"title": tr_noop("Pedal Support"),
"type": "value",
"get_value": lambda: tr("Yes") if starpilot_state.car_state.canUsePedal else tr("No"),
"on_click": lambda: None,
"color": "#FFC40D",
},
{
"title": tr_noop("SDSU Support"),
"type": "value",
"get_value": lambda: tr("Yes") if starpilot_state.car_state.canUseSDSU else tr("No"),
"on_click": lambda: None,
"color": "#FFC40D",
},
{
"title": tr_noop("SNG Support"),
"type": "value",
"get_value": lambda: tr("Yes") if starpilot_state.car_state.hasSNG else tr("No"),
"on_click": lambda: None,
"color": "#FFC40D",
},
]
self._rebuild_grid()
@@ -1,10 +1,12 @@
from __future__ import annotations
from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets import DialogResult
from openpilot.system.ui.widgets.selection_dialog import SelectionDialog
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
from openpilot.selfdrive.ui.layouts.settings.starpilot.metro import SliderDialog
from openpilot.selfdrive.ui.layouts.settings.starpilot.metro import TileGrid, ToggleTile, SliderDialog
class StarPilotThemesLayout(StarPilotPanel):
def __init__(self):
@@ -14,16 +16,47 @@ class StarPilotThemesLayout(StarPilotPanel):
}
self.CATEGORIES = [
{"title": tr_noop("Personalize openpilot"), "panel": "personalize", "icon": "toggle_icons/icon_frog.png", "color": "#A200FF"},
{"title": tr_noop("Holiday Themes"), "type": "toggle", "get_state": lambda: self._params.get_bool("HolidayThemes"), "set_state": lambda s: self._params.put_bool("HolidayThemes", s), "icon": "toggle_icons/icon_calendar.png", "color": "#A200FF"},
{"title": tr_noop("Rainbow Path"), "type": "toggle", "get_state": lambda: self._params.get_bool("RainbowPath"), "set_state": lambda s: self._params.put_bool("RainbowPath", s), "icon": "toggle_icons/icon_rainbow.png", "color": "#A200FF"},
{"title": tr_noop("Random Events"), "type": "toggle", "get_state": lambda: self._params.get_bool("RandomEvents"), "set_state": lambda s: self._params.put_bool("RandomEvents", s), "icon": "toggle_icons/icon_random.png", "color": "#A200FF"},
{"title": tr_noop("Random Themes"), "type": "toggle", "get_state": lambda: self._params.get_bool("RandomThemes"), "set_state": lambda s: self._params.put_bool("RandomThemes", s), "icon": "toggle_icons/icon_random_themes.png", "color": "#A200FF"},
{
"title": tr_noop("Holiday Themes"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("HolidayThemes"),
"set_state": lambda s: self._params.put_bool("HolidayThemes", s),
"icon": "toggle_icons/icon_calendar.png",
"color": "#A200FF",
},
{
"title": tr_noop("Rainbow Path"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("RainbowPath"),
"set_state": lambda s: self._params.put_bool("RainbowPath", s),
"icon": "toggle_icons/icon_rainbow.png",
"color": "#A200FF",
},
{
"title": tr_noop("Random Events"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("RandomEvents"),
"set_state": lambda s: self._params.put_bool("RandomEvents", s),
"icon": "toggle_icons/icon_random.png",
"color": "#A200FF",
},
{
"title": tr_noop("Random Themes"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("RandomThemes"),
"set_state": lambda s: self._params.put_bool("RandomThemes", s),
"icon": "toggle_icons/icon_random_themes.png",
"color": "#A200FF",
},
]
for name, panel in self._sub_panels.items():
if hasattr(panel, 'set_navigate_callback'): panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'): panel.set_back_callback(self._go_back)
if hasattr(panel, 'set_navigate_callback'):
panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'):
panel.set_back_callback(self._go_back)
self._rebuild_grid()
class StarPilotPersonalizeLayout(StarPilotPanel):
def __init__(self):
super().__init__()
@@ -41,12 +74,15 @@ class StarPilotPersonalizeLayout(StarPilotPanel):
def _show_theme_selector(self, key):
themes = ["Stock", "Frog", "Cyberpunk", "Minimal"]
current = self._params.get(key, encoding='utf-8') or "Stock"
def on_select(res, val):
if res == DialogResult.CONFIRM:
self._params.put(key, val)
self._rebuild_grid()
gui_app.set_modal_overlay(SelectionDialog(tr(key), themes, current, on_close=on_select))
class StarPilotVisualsLayout(StarPilotPanel):
def __init__(self):
super().__init__()
@@ -65,80 +101,409 @@ class StarPilotVisualsLayout(StarPilotPanel):
{"title": tr_noop("Quality of Life"), "panel": "qol", "icon": "toggle_icons/icon_quality_of_life.png", "color": "#A200FF"},
]
for name, panel in self._sub_panels.items():
if hasattr(panel, 'set_navigate_callback'): panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'): panel.set_back_callback(self._go_back)
if hasattr(panel, 'set_navigate_callback'):
panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'):
panel.set_back_callback(self._go_back)
self._rebuild_grid()
class StarPilotAdvancedVisualsLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Hide Speed"), "type": "toggle", "get_state": lambda: self._params.get_bool("HideSpeed"), "set_state": lambda s: self._params.put_bool("HideSpeed", s), "icon": "toggle_icons/icon_display.png", "color": "#A200FF"},
{"title": tr_noop("Hide Lead Marker"), "type": "toggle", "get_state": lambda: self._params.get_bool("HideLeadMarker"), "set_state": lambda s: self._params.put_bool("HideLeadMarker", s), "icon": "toggle_icons/icon_display.png", "color": "#A200FF"},
{"title": tr_noop("Hide Max Speed"), "type": "toggle", "get_state": lambda: self._params.get_bool("HideMaxSpeed"), "set_state": lambda s: self._params.put_bool("HideMaxSpeed", s), "icon": "toggle_icons/icon_display.png", "color": "#A200FF"},
{"title": tr_noop("Hide Alerts"), "type": "toggle", "get_state": lambda: self._params.get_bool("HideAlerts"), "set_state": lambda s: self._params.put_bool("HideAlerts", s), "icon": "toggle_icons/icon_display.png", "color": "#A200FF"},
{"title": tr_noop("Hide Speed Limit"), "type": "toggle", "get_state": lambda: self._params.get_bool("HideSpeedLimit"), "set_state": lambda s: self._params.put_bool("HideSpeedLimit", s), "icon": "toggle_icons/icon_display.png", "color": "#A200FF"},
{"title": tr_noop("Wheel Speed"), "type": "toggle", "get_state": lambda: self._params.get_bool("WheelSpeed"), "set_state": lambda s: self._params.put_bool("WheelSpeed", s), "icon": "toggle_icons/icon_display.png", "color": "#A200FF"},
{
"title": tr_noop("Hide Speed"),
"type": "toggle",
"key": "HideSpeed",
"get_state": lambda: self._params.get_bool("HideSpeed"),
"set_state": lambda s: self._params.put_bool("HideSpeed", s),
"icon": "toggle_icons/icon_display.png",
"color": "#A200FF",
},
{
"title": tr_noop("Hide Lead Marker"),
"type": "toggle",
"key": "HideLeadMarker",
"get_state": lambda: self._params.get_bool("HideLeadMarker"),
"set_state": lambda s: self._params.put_bool("HideLeadMarker", s),
"icon": "toggle_icons/icon_display.png",
"color": "#A200FF",
},
{
"title": tr_noop("Hide Max Speed"),
"type": "toggle",
"key": "HideMaxSpeed",
"get_state": lambda: self._params.get_bool("HideMaxSpeed"),
"set_state": lambda s: self._params.put_bool("HideMaxSpeed", s),
"icon": "toggle_icons/icon_display.png",
"color": "#A200FF",
},
{
"title": tr_noop("Hide Alerts"),
"type": "toggle",
"key": "HideAlerts",
"get_state": lambda: self._params.get_bool("HideAlerts"),
"set_state": lambda s: self._params.put_bool("HideAlerts", s),
"icon": "toggle_icons/icon_display.png",
"color": "#A200FF",
},
{
"title": tr_noop("Hide Speed Limit"),
"type": "toggle",
"key": "HideSpeedLimit",
"get_state": lambda: self._params.get_bool("HideSpeedLimit"),
"set_state": lambda s: self._params.put_bool("HideSpeedLimit", s),
"icon": "toggle_icons/icon_display.png",
"color": "#A200FF",
},
{
"title": tr_noop("Wheel Speed"),
"type": "toggle",
"key": "WheelSpeed",
"get_state": lambda: self._params.get_bool("WheelSpeed"),
"set_state": lambda s: self._params.put_bool("WheelSpeed", s),
"icon": "toggle_icons/icon_display.png",
"color": "#A200FF",
},
]
self._rebuild_grid()
def _rebuild_grid(self):
if not self.CATEGORIES:
return
if self._tile_grid is None:
self._tile_grid = TileGrid(columns=None, padding=20)
self._tile_grid.clear()
for cat in self.CATEGORIES:
key = cat.get("key")
visible = True
if key == "HideLeadMarker":
visible &= starpilot_state.car_state.hasOpenpilotLongitudinal
if not visible:
continue
tile = ToggleTile(title=tr(cat["title"]), get_state=cat["get_state"], set_state=cat["set_state"], icon_path=cat.get("icon"), bg_color=cat.get("color"))
self._tile_grid.add_tile(tile)
class StarPilotVisualWidgetsLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Acceleration Path"), "type": "toggle", "get_state": lambda: self._params.get_bool("AccelerationPath"), "set_state": lambda s: self._params.put_bool("AccelerationPath", s), "icon": "toggle_icons/icon_road.png", "color": "#A200FF"},
{"title": tr_noop("Adjacent Lanes"), "type": "toggle", "get_state": lambda: self._params.get_bool("AdjacentPath"), "set_state": lambda s: self._params.put_bool("AdjacentPath", s), "icon": "toggle_icons/icon_road.png", "color": "#A200FF"},
{"title": tr_noop("Blind Spot Path"), "type": "toggle", "get_state": lambda: self._params.get_bool("BlindSpotPath"), "set_state": lambda s: self._params.put_bool("BlindSpotPath", s), "icon": "toggle_icons/icon_road.png", "color": "#A200FF"},
{"title": tr_noop("Compass"), "type": "toggle", "get_state": lambda: self._params.get_bool("Compass"), "set_state": lambda s: self._params.put_bool("Compass", s), "icon": "toggle_icons/icon_navigate.png", "color": "#A200FF"},
{"title": tr_noop("Personality Button"), "type": "toggle", "get_state": lambda: self._params.get_bool("OnroadDistanceButton"), "set_state": lambda s: self._params.put_bool("OnroadDistanceButton", s), "icon": "toggle_icons/icon_personality.png", "color": "#A200FF"},
{"title": tr_noop("Pedal Indicators"), "type": "toggle", "get_state": lambda: self._params.get_bool("PedalsOnUI"), "set_state": lambda s: self._params.put_bool("PedalsOnUI", s), "icon": "toggle_icons/icon_display.png", "color": "#A200FF"},
{"title": tr_noop("Rotating Wheel"), "type": "toggle", "get_state": lambda: self._params.get_bool("RotatingWheel"), "set_state": lambda s: self._params.put_bool("RotatingWheel", s), "icon": "toggle_icons/icon_steering.png", "color": "#A200FF"},
{
"title": tr_noop("Acceleration Path"),
"type": "toggle",
"key": "AccelerationPath",
"get_state": lambda: self._params.get_bool("AccelerationPath"),
"set_state": lambda s: self._params.put_bool("AccelerationPath", s),
"icon": "toggle_icons/icon_road.png",
"color": "#A200FF",
},
{
"title": tr_noop("Adjacent Lanes"),
"type": "toggle",
"key": "AdjacentPath",
"get_state": lambda: self._params.get_bool("AdjacentPath"),
"set_state": lambda s: self._params.put_bool("AdjacentPath", s),
"icon": "toggle_icons/icon_road.png",
"color": "#A200FF",
},
{
"title": tr_noop("Blind Spot Path"),
"type": "toggle",
"key": "BlindSpotPath",
"get_state": lambda: self._params.get_bool("BlindSpotPath"),
"set_state": lambda s: self._params.put_bool("BlindSpotPath", s),
"icon": "toggle_icons/icon_road.png",
"color": "#A200FF",
},
{
"title": tr_noop("Compass"),
"type": "toggle",
"key": "Compass",
"get_state": lambda: self._params.get_bool("Compass"),
"set_state": lambda s: self._params.put_bool("Compass", s),
"icon": "toggle_icons/icon_navigate.png",
"color": "#A200FF",
},
{
"title": tr_noop("Personality Button"),
"type": "toggle",
"key": "OnroadDistanceButton",
"get_state": lambda: self._params.get_bool("OnroadDistanceButton"),
"set_state": lambda s: self._params.put_bool("OnroadDistanceButton", s),
"icon": "toggle_icons/icon_personality.png",
"color": "#A200FF",
},
{
"title": tr_noop("Pedal Indicators"),
"type": "toggle",
"key": "PedalsOnUI",
"get_state": lambda: self._params.get_bool("PedalsOnUI"),
"set_state": lambda s: self._params.put_bool("PedalsOnUI", s),
"icon": "toggle_icons/icon_display.png",
"color": "#A200FF",
},
{
"title": tr_noop("Dynamic Pedals"),
"type": "toggle",
"key": "DynamicPedalsOnUI",
"get_state": lambda: self._params.get_bool("DynamicPedalsOnUI"),
"set_state": lambda s: self._set_exclusive_pedal("DynamicPedalsOnUI", "StaticPedalsOnUI", s),
"icon": "toggle_icons/icon_display.png",
"color": "#A200FF",
},
{
"title": tr_noop("Static Pedals"),
"type": "toggle",
"key": "StaticPedalsOnUI",
"get_state": lambda: self._params.get_bool("StaticPedalsOnUI"),
"set_state": lambda s: self._set_exclusive_pedal("StaticPedalsOnUI", "DynamicPedalsOnUI", s),
"icon": "toggle_icons/icon_display.png",
"color": "#A200FF",
},
{
"title": tr_noop("Rotating Wheel"),
"type": "toggle",
"key": "RotatingWheel",
"get_state": lambda: self._params.get_bool("RotatingWheel"),
"set_state": lambda s: self._params.put_bool("RotatingWheel", s),
"icon": "toggle_icons/icon_steering.png",
"color": "#A200FF",
},
]
self._rebuild_grid()
def _set_exclusive_pedal(self, key, other_key, state):
self._params.put_bool(key, state)
if state:
self._params.put_bool(other_key, False)
self._rebuild_grid()
def _rebuild_grid(self):
if not self.CATEGORIES:
return
if self._tile_grid is None:
self._tile_grid = TileGrid(columns=None, padding=20)
self._tile_grid.clear()
pedals_on_ui = self._params.get_bool("PedalsOnUI")
for cat in self.CATEGORIES:
key = cat.get("key")
visible = True
if key == "AccelerationPath":
visible &= starpilot_state.car_state.hasOpenpilotLongitudinal
elif key == "BlindSpotPath":
visible &= starpilot_state.car_state.hasBSM
elif key == "PedalsOnUI":
visible &= starpilot_state.car_state.hasOpenpilotLongitudinal
elif key == "DynamicPedalsOnUI":
visible &= starpilot_state.car_state.hasOpenpilotLongitudinal and pedals_on_ui
elif key == "StaticPedalsOnUI":
visible &= starpilot_state.car_state.hasOpenpilotLongitudinal and pedals_on_ui
if not visible:
continue
tile = ToggleTile(title=tr(cat["title"]), get_state=cat["get_state"], set_state=cat["set_state"], icon_path=cat.get("icon"), bg_color=cat.get("color"))
self._tile_grid.add_tile(tile)
class StarPilotModelUILayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Dynamic Path"), "type": "toggle", "get_state": lambda: self._params.get_bool("DynamicPathWidth"), "set_state": lambda s: self._params.put_bool("DynamicPathWidth", s), "icon": "toggle_icons/icon_road.png", "color": "#A200FF"},
{"title": tr_noop("Lane Line Width"), "type": "value", "get_value": lambda: f"{self._params.get_int('LaneLinesWidth')}in", "on_click": lambda: self._show_int_selector("LaneLinesWidth", 0, 24, "in"), "icon": "toggle_icons/icon_road.png", "color": "#A200FF"},
{"title": tr_noop("Path Edge Width"), "type": "value", "get_value": lambda: f"{self._params.get_int('PathEdgeWidth')}%", "on_click": lambda: self._show_int_selector("PathEdgeWidth", 0, 100, "%"), "icon": "toggle_icons/icon_road.png", "color": "#A200FF"},
{"title": tr_noop("Path Width"), "type": "value", "get_value": lambda: f"{self._params.get_float('PathWidth'):.1f}ft", "on_click": lambda: self._show_float_selector("PathWidth", 0, 10, 0.1, "ft"), "icon": "toggle_icons/icon_road.png", "color": "#A200FF"},
{"title": tr_noop("Road Edge Width"), "type": "value", "get_value": lambda: f"{self._params.get_int('RoadEdgesWidth')}in", "on_click": lambda: self._show_int_selector("RoadEdgesWidth", 0, 24, "in"), "icon": "toggle_icons/icon_road.png", "color": "#A200FF"},
{
"title": tr_noop("Dynamic Path"),
"type": "toggle",
"key": "DynamicPathWidth",
"get_state": lambda: self._params.get_bool("DynamicPathWidth"),
"set_state": lambda s: self._params.put_bool("DynamicPathWidth", s),
"icon": "toggle_icons/icon_road.png",
"color": "#A200FF",
},
{
"title": tr_noop("Lane Line Width"),
"type": "value",
"key": "LaneLinesWidth",
"get_value": lambda: self._get_lane_lines_display(),
"on_click": lambda: self._show_int_selector("LaneLinesWidth", 0, 24, self._get_lane_lines_unit()),
"icon": "toggle_icons/icon_road.png",
"color": "#A200FF",
},
{
"title": tr_noop("Path Edge Width"),
"type": "value",
"key": "PathEdgeWidth",
"get_value": lambda: f"{self._params.get_int('PathEdgeWidth')}%",
"on_click": lambda: self._show_int_selector("PathEdgeWidth", 0, 100, "%"),
"icon": "toggle_icons/icon_road.png",
"color": "#A200FF",
},
{
"title": tr_noop("Path Width"),
"type": "value",
"key": "PathWidth",
"get_value": lambda: self._get_path_width_display(),
"on_click": lambda: self._show_path_width_selector(),
"icon": "toggle_icons/icon_road.png",
"color": "#A200FF",
},
{
"title": tr_noop("Road Edge Width"),
"type": "value",
"key": "RoadEdgesWidth",
"get_value": lambda: self._get_road_edges_display(),
"on_click": lambda: self._show_int_selector("RoadEdgesWidth", 0, 24, self._get_road_edges_unit()),
"icon": "toggle_icons/icon_road.png",
"color": "#A200FF",
},
]
self._rebuild_grid()
def _get_lane_lines_unit(self):
return "cm" if self._params.get_bool("IsMetric") else "in"
def _get_lane_lines_display(self):
val = self._params.get_int('LaneLinesWidth')
if self._params.get_bool("IsMetric"):
return f"{int(val * 2.54)}cm"
return f"{val}in"
def _get_path_width_unit(self):
return "m" if self._params.get_bool("IsMetric") else "ft"
def _get_path_width_display(self):
val = self._params.get_float('PathWidth')
if self._params.get_bool("IsMetric"):
return f"{val / 3.28084:.1f}m"
return f"{val:.1f}ft"
def _get_road_edges_unit(self):
return "cm" if self._params.get_bool("IsMetric") else "in"
def _get_road_edges_display(self):
val = self._params.get_int('RoadEdgesWidth')
if self._params.get_bool("IsMetric"):
return f"{int(val * 2.54)}cm"
return f"{val}in"
def _show_path_width_selector(self):
if self._params.get_bool("IsMetric"):
self._show_float_selector("PathWidth", 0, 10, 0.1, "m", convert=lambda v: v / 3.28084, unconvert=lambda v: v * 3.28084)
else:
self._show_float_selector("PathWidth", 0, 10, 0.1, "ft")
def _show_int_selector(self, key, min_v, max_v, unit=""):
def on_close(res, val):
if res == DialogResult.CONFIRM:
self._params.put_int(key, int(val))
self._rebuild_grid()
gui_app.set_modal_overlay(SliderDialog(tr(key), min_v, max_v, 1, self._params.get_int(key), on_close, unit=unit, color="#A200FF"))
def _show_float_selector(self, key, min_v, max_v, step, unit=""):
def _show_float_selector(self, key, min_v, max_v, step, unit="", convert=None, unconvert=None):
current = self._params.get_float(key)
if convert:
current = convert(current)
def on_close(res, val):
if res == DialogResult.CONFIRM:
self._params.put_float(key, float(val))
v = float(val)
if unconvert:
v = unconvert(v)
self._params.put_float(key, v)
self._rebuild_grid()
gui_app.set_modal_overlay(SliderDialog(tr(key), min_v, max_v, step, self._params.get_float(key), on_close, unit=unit, color="#A200FF"))
gui_app.set_modal_overlay(SliderDialog(tr(key), min_v, max_v, step, current, on_close, unit=unit, color="#A200FF"))
class StarPilotNavigationVisualsLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CATEGORIES = [
{"title": tr_noop("Road Name"), "type": "toggle", "get_state": lambda: self._params.get_bool("RoadNameUI"), "set_state": lambda s: self._params.put_bool("RoadNameUI", s), "icon": "toggle_icons/icon_navigate.png", "color": "#A200FF"},
{"title": tr_noop("Speed Limits"), "type": "toggle", "get_state": lambda: self._params.get_bool("ShowSpeedLimits"), "set_state": lambda s: self._params.put_bool("ShowSpeedLimits", s), "icon": "toggle_icons/icon_speed_limit.png", "color": "#A200FF"},
{"title": tr_noop("Mapbox Limits"), "type": "toggle", "get_state": lambda: self._params.get_bool("SLCMapboxFiller"), "set_state": lambda s: self._params.put_bool("SLCMapboxFiller", s), "icon": "toggle_icons/icon_speed_limit.png", "color": "#A200FF"},
{"title": tr_noop("Vienna Signs"), "type": "toggle", "get_state": lambda: self._params.get_bool("UseVienna"), "set_state": lambda s: self._params.put_bool("UseVienna", s), "icon": "toggle_icons/icon_speed_limit.png", "color": "#A200FF"},
{
"title": tr_noop("Road Name"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("RoadNameUI"),
"set_state": lambda s: self._params.put_bool("RoadNameUI", s),
"icon": "toggle_icons/icon_navigate.png",
"color": "#A200FF",
},
{
"title": tr_noop("Speed Limits"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("ShowSpeedLimits"),
"set_state": lambda s: self._params.put_bool("ShowSpeedLimits", s),
"icon": "toggle_icons/icon_speed_limit.png",
"color": "#A200FF",
},
{
"title": tr_noop("Mapbox Limits"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("SLCMapboxFiller"),
"set_state": lambda s: self._params.put_bool("SLCMapboxFiller", s),
"icon": "toggle_icons/icon_speed_limit.png",
"color": "#A200FF",
},
{
"title": tr_noop("Vienna Signs"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("UseVienna"),
"set_state": lambda s: self._params.put_bool("UseVienna", s),
"icon": "toggle_icons/icon_speed_limit.png",
"color": "#A200FF",
},
]
self._rebuild_grid()
class StarPilotVisualQOLLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self.CAMERA_VIEWS = ["Auto", "Driver", "Standard", "Wide"]
self.CATEGORIES = [
{"title": tr_noop("Camera View"), "type": "toggle", "get_state": lambda: self._params.get_bool("CameraView"), "set_state": lambda s: self._params.put_bool("CameraView", s), "icon": "toggle_icons/icon_display.png", "color": "#A200FF"},
{"title": tr_noop("Driver Camera"), "type": "toggle", "get_state": lambda: self._params.get_bool("DriverCamera"), "set_state": lambda s: self._params.put_bool("DriverCamera", s), "icon": "toggle_icons/icon_display.png", "color": "#A200FF"},
{"title": tr_noop("Stopped Timer"), "type": "toggle", "get_state": lambda: self._params.get_bool("StoppedTimer"), "set_state": lambda s: self._params.put_bool("StoppedTimer", s), "icon": "toggle_icons/icon_display.png", "color": "#A200FF"},
{
"title": tr_noop("Camera View"),
"type": "value",
"key": "CameraView",
"get_value": lambda: tr(self.CAMERA_VIEWS[self._params.get_int('CameraView')]),
"on_click": lambda: self._show_camera_view_selector(),
"icon": "toggle_icons/icon_display.png",
"color": "#A200FF",
},
{
"title": tr_noop("Driver Camera"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("DriverCamera"),
"set_state": lambda s: self._params.put_bool("DriverCamera", s),
"icon": "toggle_icons/icon_display.png",
"color": "#A200FF",
},
{
"title": tr_noop("Stopped Timer"),
"type": "toggle",
"get_state": lambda: self._params.get_bool("StoppedTimer"),
"set_state": lambda s: self._params.put_bool("StoppedTimer", s),
"icon": "toggle_icons/icon_display.png",
"color": "#A200FF",
},
]
self._rebuild_grid()
def _show_camera_view_selector(self):
current = self._params.get_int("CameraView")
def on_select(res, val):
if res == DialogResult.CONFIRM:
idx = self.CAMERA_VIEWS.index(val)
self._params.put_int("CameraView", idx)
self._rebuild_grid()
gui_app.set_modal_overlay(SelectionDialog(tr("Camera View"), self.CAMERA_VIEWS, self.CAMERA_VIEWS[current], on_close=on_select))
@@ -6,6 +6,10 @@ from openpilot.system.ui.widgets import DialogResult
from openpilot.system.ui.widgets.selection_dialog import SelectionDialog
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
ACTION_NAMES = ["No Action", "Change Personality", "Force Coast", "Pause Steering", "Pause Accel/Brake", "Toggle Experimental", "Toggle Traffic"]
ACTION_IDS = {name: i for i, name in enumerate(ACTION_NAMES)}
class StarPilotWheelLayout(StarPilotPanel):
def __init__(self):
super().__init__()
@@ -15,8 +19,89 @@ class StarPilotWheelLayout(StarPilotPanel):
"type": "toggle",
"get_state": lambda: self._params.get_bool("RemapCancelToDistance"),
"set_state": lambda s: self._params.put_bool("RemapCancelToDistance", s),
"icon": "toggle_icons/icon_steering.png",
"color": "#FFC40D"
"color": "#FFC40D",
},
{
"title": tr_noop("Distance Button"),
"type": "value",
"get_value": lambda: self._get_action_name("DistanceButtonControl"),
"on_click": lambda: self._show_action_picker("DistanceButtonControl"),
"color": "#FFC40D",
},
{
"title": tr_noop("Distance (Long Press)"),
"type": "value",
"get_value": lambda: self._get_action_name("LongDistanceButtonControl"),
"on_click": lambda: self._show_action_picker("LongDistanceButtonControl"),
"color": "#FFC40D",
},
{
"title": tr_noop("Distance (Very Long)"),
"type": "value",
"get_value": lambda: self._get_action_name("VeryLongDistanceButtonControl"),
"on_click": lambda: self._show_action_picker("VeryLongDistanceButtonControl"),
"color": "#FFC40D",
},
{
"title": tr_noop("LKAS Button"),
"type": "value",
"get_value": lambda: self._get_action_name("LKASButtonControl"),
"on_click": lambda: self._show_action_picker("LKASButtonControl"),
"key": "LKASButtonControl",
"color": "#FFC40D",
},
]
self._rebuild_grid()
def _get_action_name(self, key):
idx = self._params.get_int(key)
if 0 <= idx < len(ACTION_NAMES):
return ACTION_NAMES[idx]
return ACTION_NAMES[0]
def _get_available_actions(self):
actions = list(ACTION_NAMES[:1]) # No Action
cs = starpilot_state.car_state
if cs.hasOpenpilotLongitudinal:
actions.extend(ACTION_NAMES[1:])
return actions
def _show_action_picker(self, key):
actions = self._get_available_actions()
current = self._get_action_name(key)
if current not in actions:
current = actions[0]
def on_select(res, val):
if res == DialogResult.CONFIRM:
self._params.put_int(key, ACTION_IDS.get(val, 0))
self._rebuild_grid()
gui_app.set_modal_overlay(SelectionDialog(tr(key), actions, current, on_close=on_select))
def _rebuild_grid(self):
if not self.CATEGORIES:
return
if self._tile_grid is None:
self._tile_grid = __import__('openpilot.selfdrive.ui.layouts.settings.starpilot.metro', fromlist=['TileGrid']).TileGrid(columns=None, padding=20)
self._tile_grid.clear()
cs = starpilot_state.car_state
for cat in self.CATEGORIES:
key = cat.get("key")
visible = True
if key == "LKASButtonControl":
visible &= not cs.isSubaru
if not visible:
continue
tile_type = cat.get("type", "hub")
if tile_type == "toggle":
from openpilot.selfdrive.ui.layouts.settings.starpilot.metro import ToggleTile
tile = ToggleTile(title=tr(cat["title"]), get_state=cat["get_state"], set_state=cat["set_state"], bg_color=cat.get("color"))
elif tile_type == "value":
from openpilot.selfdrive.ui.layouts.settings.starpilot.metro import ValueTile
tile = ValueTile(title=tr(cat["title"]), get_value=cat["get_value"], on_click=cat["on_click"], bg_color=cat.get("color"))
else:
continue
self._tile_grid.add_tile(tile)
+39 -1
View File
@@ -225,6 +225,7 @@ class GuiApplication:
self._modal_overlay = ModalOverlay()
self._modal_overlay_shown = False
self._modal_overlay_tick: Callable[[], None] | None = None
self._nav_stack: list = []
self._mouse = MouseState(self._scale)
self._mouse_events: list[MouseEvent] = []
@@ -369,6 +370,41 @@ class GuiApplication:
def set_modal_overlay_tick(self, tick_function: Callable | None):
self._modal_overlay_tick = tick_function
def push_widget(self, widget):
if widget in self._nav_stack:
return
if self._nav_stack:
prev = self._nav_stack[-1]
if hasattr(prev, 'set_enabled'):
prev.set_enabled(False)
self._nav_stack.append(widget)
if hasattr(widget, 'show_event'):
widget.show_event()
if hasattr(widget, 'set_enabled'):
widget.set_enabled(True)
def pop_widget(self, idx: int | None = None):
if len(self._nav_stack) < 2:
return
idx_to_pop = len(self._nav_stack) - 1 if idx is None else idx
if idx_to_pop <= 0 or idx_to_pop >= len(self._nav_stack):
return
if idx_to_pop == len(self._nav_stack) - 1:
prev = self._nav_stack[idx_to_pop - 1]
if hasattr(prev, 'set_enabled'):
prev.set_enabled(True)
widget = self._nav_stack.pop(idx_to_pop)
if hasattr(widget, 'hide_event'):
widget.hide_event()
def _render_nav_stack(self) -> bool:
if not self._nav_stack:
return False
widget = self._nav_stack[-1]
if hasattr(widget, 'render'):
widget.render(rl.Rectangle(0, 0, self.width, self.height))
return True
def set_should_render(self, should_render: bool):
self._should_render = should_render
@@ -523,7 +559,9 @@ class GuiApplication:
rl.clear_background(rl.BLACK)
# Handle modal overlay rendering and input processing
if self._handle_modal_overlay():
if self._render_nav_stack():
yield False
elif self._handle_modal_overlay():
# Allow a Widget to still run a function while overlay is shown
if self._modal_overlay_tick is not None:
self._modal_overlay_tick()
+17
View File
@@ -34,6 +34,7 @@ class Widget(abc.ABC):
self._click_callback: Callable[[], None] | None = None
self._multi_touch = False
self.__was_awake = True
self._children: list = []
@property
def rect(self) -> rl.Rectangle:
@@ -180,9 +181,25 @@ class Widget(abc.ABC):
def show_event(self):
"""Optionally handle show event. Parent must manually call this"""
for child in self._children:
child.show_event()
def hide_event(self):
"""Optionally handle hide event. Parent must manually call this"""
for child in self._children:
child.hide_event()
def _child(self, widget):
"""Register a child widget for lifecycle propagation."""
assert widget not in self._children, f"{type(widget).__name__} already a child of {type(self).__name__}"
self._children.append(widget)
return widget
def dismiss(self, callback: Callable[[], None] | None = None):
"""Dismiss this widget from the nav stack."""
gui_app.pop_widget()
if callback:
callback()
SWIPE_AWAY_THRESHOLD = 80 # px to dismiss after releasing
+19 -94
View File
@@ -1,118 +1,43 @@
import pyray as rl
from collections.abc import Callable
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.widgets import Widget, DialogResult
from openpilot.system.ui.widgets.button import Button, ButtonStyle
from openpilot.system.ui.widgets.label import Label, FontWeight
from openpilot.system.ui.widgets.keyboard import Keyboard, KeyboardLayout
from openpilot.system.ui.widgets.keyboard import Keyboard
MARGIN = 50
BUTTON_HEIGHT = 160
OUTER_MARGIN_X = 200
OUTER_MARGIN_Y = 150
BACKGROUND_COLOR = rl.Color(27, 27, 27, 255)
class InputDialog(Widget):
def __init__(self, title: str, default_text: str = "", hint_text: str = "", on_close: Callable[[DialogResult, str], None] | None = None):
super().__init__()
self._title = title
self._text = default_text
self._hint = hint_text
self._default_text = default_text
self._on_close = on_close
self._dialog_result = DialogResult.NO_ACTION
self._title_label = Label(title, 70, FontWeight.BOLD, text_color=rl.Color(201, 201, 201, 255))
self._cancel_button = Button("Cancel", self._cancel_button_callback)
self._confirm_button = Button("Confirm", self._confirm_button_callback, button_style=ButtonStyle.PRIMARY)
self._keyboard = Keyboard(self._on_key_pressed, self._on_keyboard_done, layout=KeyboardLayout.QWERTY)
self._font = gui_app.font(FontWeight.MEDIUM)
def _on_key_pressed(self, key: str):
if key == "\b":
self._text = self._text[:-1]
else:
self._text += key
self._keyboard = Keyboard(callback=self._on_keyboard_result)
self._keyboard.set_title(title)
self._keyboard.set_text(default_text)
def _on_keyboard_done(self):
self._confirm_button_callback()
def _cancel_button_callback(self):
self._dialog_result = DialogResult.CANCEL
def _on_keyboard_result(self, result: DialogResult):
if self._dialog_result != DialogResult.NO_ACTION:
return
self._dialog_result = result
if self._on_close:
self._on_close(self._dialog_result, self._text)
def _confirm_button_callback(self):
self._dialog_result = DialogResult.CONFIRM
if self._on_close:
self._on_close(self._dialog_result, self._text)
self._on_close(result, self._keyboard.text)
@property
def result(self) -> DialogResult:
return self._dialog_result
@property
def text(self) -> str:
return self._text
return self._keyboard.text
def show_event(self):
super().show_event()
self._dialog_result = DialogResult.NO_ACTION
self._keyboard.show_event()
self._keyboard.clear()
if self._default_text:
self._keyboard.set_text(self._default_text)
def _render(self, rect: rl.Rectangle):
# Dim background
rl.draw_rectangle(0, 0, int(rect.width), int(rect.height), rl.Color(0, 0, 0, 200))
# Dialog Box
dialog_rect = rl.Rectangle(
rect.x + OUTER_MARGIN_X,
rect.y + OUTER_MARGIN_Y,
rect.width - 2 * OUTER_MARGIN_X,
rect.height - 2 * OUTER_MARGIN_Y,
)
rl.draw_rectangle_rounded(dialog_rect, 0.05, 10, BACKGROUND_COLOR)
# Title
title_rect = rl.Rectangle(dialog_rect.x + MARGIN, dialog_rect.y + MARGIN, dialog_rect.width - 2 * MARGIN, 100)
self._title_label.render(title_rect)
# Text Input Field
input_rect = rl.Rectangle(dialog_rect.x + MARGIN, title_rect.y + title_rect.height + 40, dialog_rect.width - 2 * MARGIN, 120)
rl.draw_rectangle_rounded(input_rect, 0.1, 10, rl.Color(40, 40, 40, 255))
display_text = self._text
text_color = rl.WHITE
if not display_text:
display_text = self._hint
text_color = rl.Color(128, 128, 128, 255)
text_size = rl.measure_text_ex(self._font, display_text, 50, 0)
text_pos = rl.Vector2(input_rect.x + 40, input_rect.y + (input_rect.height - text_size.y) / 2)
rl.draw_text_ex(self._font, display_text, text_pos, 50, 0, text_color)
# Blinking cursor
if (rl.get_time() % 1.0) < 0.5:
cursor_x = text_pos.x + (text_size.x if self._text else 0) + 5
rl.draw_rectangle(int(cursor_x), int(text_pos.y), 4, 50, rl.WHITE)
# Keyboard
keyboard_rect = rl.Rectangle(
dialog_rect.x + MARGIN,
input_rect.y + input_rect.height + 40,
dialog_rect.width - 2 * MARGIN,
400
)
self._keyboard.render(keyboard_rect)
# Buttons
btn_y = dialog_rect.y + dialog_rect.height - BUTTON_HEIGHT - MARGIN
btn_width = (dialog_rect.width - 3 * MARGIN) / 2
cancel_rect = rl.Rectangle(dialog_rect.x + MARGIN, btn_y, btn_width, BUTTON_HEIGHT)
confirm_rect = rl.Rectangle(dialog_rect.x + 2 * MARGIN + btn_width, btn_y, btn_width, BUTTON_HEIGHT)
self._cancel_button.render(cancel_rect)
self._confirm_button.render(confirm_rect)
def _render(self, rect):
self._keyboard.render(rect)
return self._dialog_result
+49 -10
View File
@@ -1,12 +1,13 @@
from functools import partial
import time
from typing import Literal
from collections.abc import Callable
import pyray as rl
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets import Widget, DialogResult
from openpilot.system.ui.widgets.button import ButtonStyle, Button
from openpilot.system.ui.widgets.inputbox import InputBox
from openpilot.system.ui.widgets.label import Label
@@ -58,7 +59,14 @@ KEYBOARD_LAYOUTS = {
class Keyboard(Widget):
def __init__(self, max_text_size: int = 255, min_text_size: int = 0, password_mode: bool = False, show_password_toggle: bool = False):
def __init__(
self,
max_text_size: int = 255,
min_text_size: int = 0,
password_mode: bool = False,
show_password_toggle: bool = False,
callback: Callable[[DialogResult], None] | None = None,
):
super().__init__()
self._layout_name: Literal["lowercase", "uppercase", "numbers", "specials"] = "lowercase"
self._caps_lock = False
@@ -71,6 +79,7 @@ class Keyboard(Widget):
self._input_box = InputBox(max_text_size)
self._password_mode = password_mode
self._show_password_toggle = show_password_toggle
self._callback = callback
# Backspace key repeat tracking
self._backspace_pressed: bool = False
@@ -78,6 +87,8 @@ class Keyboard(Widget):
self._backspace_last_repeat: float = 0.0
self._render_return_status = -1
self._first_render = False
self._skip_input = False
self._cancel_button = Button(lambda: tr("Cancel"), self._cancel_button_callback)
self._eye_button = Button("", self._eye_button_callback, button_style=ButtonStyle.TRANSPARENT)
@@ -98,12 +109,18 @@ class Keyboard(Widget):
for _, key in enumerate(keys):
if key in self._key_icons:
texture = self._key_icons[key]
self._all_keys[key] = Button("", partial(self._key_callback, key), icon=texture,
button_style=ButtonStyle.PRIMARY if key == ENTER_KEY else ButtonStyle.KEYBOARD, multi_touch=True)
self._all_keys[key] = Button(
"",
partial(self._key_callback, key),
icon=texture,
button_style=ButtonStyle.PRIMARY if key == ENTER_KEY else ButtonStyle.KEYBOARD,
multi_touch=True,
)
else:
self._all_keys[key] = Button(key, partial(self._key_callback, key), button_style=ButtonStyle.KEYBOARD, font_size=85, multi_touch=True)
self._all_keys[CAPS_LOCK_KEY] = Button("", partial(self._key_callback, CAPS_LOCK_KEY), icon=self._key_icons[CAPS_LOCK_KEY],
button_style=ButtonStyle.KEYBOARD, multi_touch=True)
self._all_keys[CAPS_LOCK_KEY] = Button(
"", partial(self._key_callback, CAPS_LOCK_KEY), icon=self._key_icons[CAPS_LOCK_KEY], button_style=ButtonStyle.KEYBOARD, multi_touch=True
)
def set_text(self, text: str):
self._input_box.text = text
@@ -122,20 +139,42 @@ class Keyboard(Widget):
self._title.set_text(title)
self._sub_title.set_text(sub_title)
def _eye_button_callback(self):
self._password_mode = not self._password_mode
def set_callback(self, callback: Callable[[DialogResult], None] | None):
self._callback = callback
def show_event(self):
super().show_event()
self._skip_input = True
def _process_mouse_events(self):
if not self._skip_input:
super()._process_mouse_events()
def _cancel_button_callback(self):
self.clear()
self._render_return_status = 0
if self in gui_app._nav_stack:
gui_app.pop_widget()
else:
self._render_return_status = 0
if self._callback:
self._callback(DialogResult.CANCEL)
def _eye_button_callback(self):
self._password_mode = not self._password_mode
def _key_callback(self, k):
if k == ENTER_KEY:
self._render_return_status = 1
if self in gui_app._nav_stack:
gui_app.pop_widget()
else:
self._render_return_status = 1
if self._callback:
self._callback(DialogResult.CONFIRM)
else:
self.handle_key_press(k)
def _render(self, rect: rl.Rectangle):
self._skip_input = False
rect = rl.Rectangle(rect.x + CONTENT_MARGIN, rect.y + CONTENT_MARGIN, rect.width - 2 * CONTENT_MARGIN, rect.height - 2 * CONTENT_MARGIN)
self._title.render(rl.Rectangle(rect.x, rect.y, rect.width, 95))
self._sub_title.render(rl.Rectangle(rect.x, rect.y + 95, rect.width, 60))