mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-17 22:33:43 +08:00
Fire Firehose
This commit is contained in:
@@ -11,7 +11,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"AlwaysAllowUploads", {PERSISTENT, BOOL, "0"}},
|
||||
{"AlwaysOnDM", {PERSISTENT, BOOL}},
|
||||
{"ApiCache_Device", {PERSISTENT, STRING}},
|
||||
{"ApiCache_FirehoseStats", {PERSISTENT, JSON}},
|
||||
{"AssistNowToken", {PERSISTENT, STRING}},
|
||||
{"AthenadPid", {PERSISTENT, INT}},
|
||||
{"AthenadUploadQueue", {PERSISTENT, JSON}},
|
||||
|
||||
@@ -133,8 +133,8 @@ class TestParams:
|
||||
|
||||
def test_params_get_type(self):
|
||||
# json
|
||||
self.params.put("ApiCache_FirehoseStats", {"a": 0})
|
||||
assert self.params.get("ApiCache_FirehoseStats") == {"a": 0}
|
||||
self.params.put("ApiCache_DriveStats", {"a": 0})
|
||||
assert self.params.get("ApiCache_DriveStats") == {"a": 0}
|
||||
|
||||
# int
|
||||
self.params.put("BootCount", 1441)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@@ -23,7 +23,7 @@ if arch == "Darwin":
|
||||
qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"]
|
||||
|
||||
qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"], LIBS=base_libs)
|
||||
widgets_src = ["qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/prime_state.cc",
|
||||
widgets_src = ["qt/widgets/input.cc", "qt/prime_state.cc",
|
||||
"qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc",
|
||||
"qt/widgets/offroad_alerts.cc", "qt/widgets/prime.cc", "qt/widgets/keyboard.cc",
|
||||
"qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc",
|
||||
@@ -39,7 +39,7 @@ qt_libs = [widgets, qt_util] + base_libs
|
||||
qt_src = ["main.cc", "ui.cc", "qt/sidebar.cc", "qt/body.cc",
|
||||
"qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc",
|
||||
"qt/offroad/software_settings.cc", "qt/offroad/developer_panel.cc", "qt/offroad/onboarding.cc",
|
||||
"qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc", "qt/offroad/firehose.cc",
|
||||
"qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc",
|
||||
"qt/onroad/onroad_home.cc", "qt/onroad/annotated_camera.cc", "qt/onroad/model.cc",
|
||||
"qt/onroad/buttons.cc", "qt/onroad/alerts.cc", "qt/onroad/driver_monitoring.cc", "qt/onroad/hud.cc"]
|
||||
|
||||
|
||||
@@ -54,7 +54,6 @@ class MainLayout(Widget):
|
||||
self._sidebar.set_callbacks(on_settings=self._on_settings_clicked,
|
||||
on_flag=self._on_bookmark_clicked,
|
||||
open_settings=lambda: self.open_settings(PanelType.TOGGLES))
|
||||
self._layouts[MainState.HOME]._setup_widget.set_open_settings_callback(lambda: self.open_settings(PanelType.FIREHOSE))
|
||||
self._layouts[MainState.HOME].set_settings_callback(lambda: self.open_settings(PanelType.TOGGLES))
|
||||
self._layouts[MainState.SETTINGS].set_callbacks(on_close=self._set_mode_for_state)
|
||||
self._layouts[MainState.ONROAD].set_click_callback(self._on_onroad_clicked)
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
import pyray as rl
|
||||
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE
|
||||
from openpilot.system.ui.lib.multilang import tr, trn, tr_noop
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
|
||||
from openpilot.system.ui.lib.wrap_text import wrap_text
|
||||
from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayoutBase
|
||||
|
||||
TITLE = tr_noop("Firehose Mode")
|
||||
DESCRIPTION = tr_noop(
|
||||
"openpilot learns to drive by watching humans, like you, drive.\n\n"
|
||||
+ "Firehose Mode allows you to maximize your training data uploads to improve "
|
||||
+ "openpilot's driving models. More data means bigger models, which means better Experimental Mode."
|
||||
)
|
||||
INSTRUCTIONS = tr_noop(
|
||||
"For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.\n\n"
|
||||
+ "Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.\n\n\n"
|
||||
+ "Frequently Asked Questions\n\n"
|
||||
+ "Does it matter how or where I drive? Nope, just drive as you normally would.\n\n"
|
||||
+ "Do all of my segments get pulled in Firehose Mode? No, we selectively pull a subset of your segments.\n\n"
|
||||
+ "What's a good USB-C adapter? Any fast phone or laptop charger should be fine.\n\n"
|
||||
+ "Does it matter which software I run? Yes, only upstream openpilot (and particular forks) are able to be used for training."
|
||||
)
|
||||
|
||||
|
||||
class FirehoseLayout(FirehoseLayoutBase):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._scroll_panel = GuiScrollPanel()
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
# Calculate content dimensions
|
||||
content_rect = rl.Rectangle(rect.x, rect.y, rect.width, self._content_height)
|
||||
|
||||
# Handle scrolling and render with clipping
|
||||
scroll_offset = self._scroll_panel.update(rect, content_rect)
|
||||
rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height))
|
||||
self._content_height = self._render_content(rect, scroll_offset)
|
||||
rl.end_scissor_mode()
|
||||
|
||||
def _render_content(self, rect: rl.Rectangle, scroll_offset: float) -> int:
|
||||
x = int(rect.x + 40)
|
||||
y = int(rect.y + 40 + scroll_offset)
|
||||
w = int(rect.width - 80)
|
||||
|
||||
# Title (centered)
|
||||
title_text = tr(TITLE) # live translate
|
||||
title_font = gui_app.font(FontWeight.MEDIUM)
|
||||
text_width = measure_text_cached(title_font, title_text, 100).x
|
||||
title_x = rect.x + (rect.width - text_width) / 2
|
||||
rl.draw_text_ex(title_font, title_text, rl.Vector2(round(title_x), round(y)), 100, 0, rl.WHITE)
|
||||
y += 200
|
||||
|
||||
# Description
|
||||
y = self._draw_wrapped_text(x, y, w, tr(DESCRIPTION), gui_app.font(FontWeight.NORMAL), 45, rl.WHITE)
|
||||
y += 40 + 20
|
||||
|
||||
# Separator
|
||||
rl.draw_rectangle(x, y, w, 2, self.GRAY)
|
||||
y += 30 + 20
|
||||
|
||||
# Status
|
||||
status_text, status_color = self._get_status()
|
||||
y = self._draw_wrapped_text(x, y, w, status_text, gui_app.font(FontWeight.BOLD), 60, status_color)
|
||||
y += 20 + 20
|
||||
|
||||
# Contribution count (if available)
|
||||
if self._segment_count > 0:
|
||||
contrib_text = trn("{} segment of your driving is in the training dataset so far.",
|
||||
"{} segments of your driving is in the training dataset so far.", self._segment_count).format(self._segment_count)
|
||||
y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 52, rl.WHITE)
|
||||
y += 20 + 20
|
||||
|
||||
# Separator
|
||||
rl.draw_rectangle(x, y, w, 2, self.GRAY)
|
||||
y += 30 + 20
|
||||
|
||||
# Instructions
|
||||
y = self._draw_wrapped_text(x, y, w, tr(INSTRUCTIONS), gui_app.font(FontWeight.NORMAL), 40, self.LIGHT_GRAY)
|
||||
|
||||
# bottom margin + remove effect of scroll offset
|
||||
return int(round(y - self._scroll_panel.offset + 40))
|
||||
|
||||
def _draw_wrapped_text(self, x, y, width, text, font, font_size, color):
|
||||
wrapped = wrap_text(font, text, font_size, width)
|
||||
for line in wrapped:
|
||||
rl.draw_text_ex(font, line, rl.Vector2(round(x), round(y)), font_size, 0, color)
|
||||
y += font_size * FONT_SCALE
|
||||
return round(y)
|
||||
@@ -4,7 +4,6 @@ from enum import IntEnum
|
||||
from collections.abc import Callable
|
||||
from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.main_panel import StarPilotLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout
|
||||
@@ -37,8 +36,7 @@ class PanelType(IntEnum):
|
||||
NETWORK = 2
|
||||
TOGGLES = 3
|
||||
SOFTWARE = 4
|
||||
FIREHOSE = 5
|
||||
DEVELOPER = 6
|
||||
DEVELOPER = 5
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -67,7 +65,6 @@ class SettingsLayout(Widget):
|
||||
PanelType.NETWORK: PanelInfo(tr_noop("Network"), NetworkUI(wifi_manager)),
|
||||
PanelType.TOGGLES: PanelInfo(tr_noop("Toggles"), TogglesLayout()),
|
||||
PanelType.SOFTWARE: PanelInfo(tr_noop("Software"), SoftwareLayout()),
|
||||
PanelType.FIREHOSE: PanelInfo(tr_noop("Firehose"), FirehoseLayout()),
|
||||
PanelType.DEVELOPER: PanelInfo(tr_noop("Developer"), DeveloperLayout()),
|
||||
}
|
||||
|
||||
@@ -143,9 +140,6 @@ class SettingsLayout(Widget):
|
||||
# Navigation buttons
|
||||
y = rect.y + 300
|
||||
for panel_type, panel_info in self._panels.items():
|
||||
if panel_type == PanelType.FIREHOSE:
|
||||
continue
|
||||
|
||||
button_rect = rl.Rectangle(rect.x + 50, y, rect.width - 150, NAV_BTN_HEIGHT)
|
||||
|
||||
# Button styling
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
import requests
|
||||
import threading
|
||||
import time
|
||||
import pyray as rl
|
||||
|
||||
from openpilot.common.api import api_get
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.selfdrive.ui.lib.api_helpers import get_token
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state, device
|
||||
from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE
|
||||
from openpilot.system.ui.lib.wrap_text import wrap_text
|
||||
from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2
|
||||
from openpilot.system.ui.lib.multilang import tr, trn, tr_noop
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.scroller import NavRawScrollPanel
|
||||
|
||||
TITLE = tr_noop("Firehose Mode")
|
||||
DESCRIPTION = tr_noop(
|
||||
"openpilot learns to drive by watching humans, like you, drive.\n\n"
|
||||
+ "Firehose Mode allows you to maximize your training data uploads to improve "
|
||||
+ "openpilot's driving models. More data means bigger models, which means better Experimental Mode."
|
||||
)
|
||||
INSTRUCTIONS_INTRO = tr_noop(
|
||||
"For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.\n\n"
|
||||
+ "Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card."
|
||||
)
|
||||
FAQ_HEADER = tr_noop("Frequently Asked Questions")
|
||||
FAQ_ITEMS = [
|
||||
(tr_noop("Does it matter how or where I drive?"), tr_noop("Nope, just drive as you normally would.")),
|
||||
(tr_noop("Do all of my segments get pulled in Firehose Mode?"), tr_noop("No, we selectively pull a subset of your segments.")),
|
||||
(tr_noop("What's a good USB-C adapter?"), tr_noop("Any fast phone or laptop charger should be fine.")),
|
||||
(tr_noop("Does it matter which software I run?"), tr_noop("Yes, only upstream openpilot (and particular forks) are able to be used for training.")),
|
||||
]
|
||||
|
||||
|
||||
class FirehoseLayoutBase(Widget):
|
||||
PARAM_KEY = "ApiCache_FirehoseStats"
|
||||
GREEN = rl.Color(46, 204, 113, 255)
|
||||
RED = rl.Color(231, 76, 60, 255)
|
||||
GRAY = rl.Color(68, 68, 68, 255)
|
||||
LIGHT_GRAY = rl.Color(228, 228, 228, 255)
|
||||
UPDATE_INTERVAL = 30 # seconds
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._params = Params()
|
||||
self._session = requests.Session() # reuse session to reduce SSL handshake overhead
|
||||
self._segment_count = self._get_segment_count()
|
||||
|
||||
self._scroll_panel = GuiScrollPanel2(horizontal=False)
|
||||
self._content_height = 0
|
||||
|
||||
self._running = True
|
||||
self._update_thread = threading.Thread(target=self._update_loop, daemon=True)
|
||||
self._update_thread.start()
|
||||
|
||||
def __del__(self):
|
||||
self._running = False
|
||||
try:
|
||||
if self._update_thread and self._update_thread.is_alive():
|
||||
self._update_thread.join(timeout=1.0)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
self._scroll_panel.set_offset(0)
|
||||
|
||||
def _get_segment_count(self) -> int:
|
||||
stats = self._params.get(self.PARAM_KEY)
|
||||
if not stats:
|
||||
return 0
|
||||
try:
|
||||
return int(stats.get("firehose", 0))
|
||||
except Exception:
|
||||
cloudlog.exception(f"Failed to decode firehose stats: {stats}")
|
||||
return 0
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
# compute total content height for scrolling
|
||||
content_height = self._measure_content_height(rect)
|
||||
scroll_offset = self._scroll_panel.update(rect, content_height)
|
||||
|
||||
# start drawing with offset
|
||||
x = rect.x + 40
|
||||
y = rect.y + 40 + scroll_offset
|
||||
w = rect.width - 80
|
||||
|
||||
# Title
|
||||
title_text = tr(TITLE)
|
||||
title_font = gui_app.font(FontWeight.BOLD)
|
||||
title_size = 64
|
||||
rl.draw_text_ex(title_font, title_text, rl.Vector2(x, y), title_size, 0, rl.WHITE)
|
||||
y += int(title_size * FONT_SCALE) + 20
|
||||
|
||||
# Description
|
||||
y = self._draw_wrapped_text(x, y, w, tr(DESCRIPTION), gui_app.font(FontWeight.ROMAN), 36, rl.WHITE)
|
||||
y += 20
|
||||
|
||||
# Separator
|
||||
rl.draw_rectangle_rec(rl.Rectangle(x, y, w, 2), self.GRAY)
|
||||
y += 20
|
||||
|
||||
# Status
|
||||
status_text, status_color = self._get_status()
|
||||
y = self._draw_wrapped_text(x, y, w, status_text, gui_app.font(FontWeight.BOLD), 48, status_color)
|
||||
y += 20
|
||||
|
||||
# Contribution count (if available)
|
||||
if self._segment_count > 0:
|
||||
contrib_text = trn("{} segment of your driving is in the training dataset so far.",
|
||||
"{} segments of your driving is in the training dataset so far.", self._segment_count).format(self._segment_count)
|
||||
y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 42, rl.WHITE)
|
||||
y += 20
|
||||
|
||||
# Separator
|
||||
rl.draw_rectangle_rec(rl.Rectangle(x, y, w, 2), self.GRAY)
|
||||
y += 20
|
||||
|
||||
# Instructions intro
|
||||
y = self._draw_wrapped_text(x, y, w, tr(INSTRUCTIONS_INTRO), gui_app.font(FontWeight.ROMAN), 32, self.LIGHT_GRAY)
|
||||
y += 20
|
||||
|
||||
# FAQ Header
|
||||
y = self._draw_wrapped_text(x, y, w, tr(FAQ_HEADER), gui_app.font(FontWeight.BOLD), 44, rl.WHITE)
|
||||
y += 20
|
||||
|
||||
# FAQ Items
|
||||
for question, answer in FAQ_ITEMS:
|
||||
y = self._draw_wrapped_text(x, y, w, tr(question), gui_app.font(FontWeight.BOLD), 32, self.LIGHT_GRAY)
|
||||
y = self._draw_wrapped_text(x, y, w, tr(answer), gui_app.font(FontWeight.ROMAN), 32, self.LIGHT_GRAY)
|
||||
y += 20
|
||||
|
||||
def _draw_wrapped_text(self, x, y, width, text, font, font_size, color):
|
||||
wrapped = wrap_text(font, text, font_size, width)
|
||||
for line in wrapped:
|
||||
rl.draw_text_ex(font, line, rl.Vector2(x, y), font_size, 0, color)
|
||||
y += int(font_size * FONT_SCALE)
|
||||
return y
|
||||
|
||||
def _measure_content_height(self, rect: rl.Rectangle) -> int:
|
||||
# Rough measurement using the same wrapping as rendering
|
||||
w = int(rect.width - 80)
|
||||
y = 40
|
||||
|
||||
# Title
|
||||
title_size = 72
|
||||
y += int(title_size * FONT_SCALE) + 20
|
||||
|
||||
# Description
|
||||
desc_lines = wrap_text(gui_app.font(FontWeight.ROMAN), tr(DESCRIPTION), 36, w)
|
||||
y += int(len(desc_lines) * 36 * FONT_SCALE) + 20
|
||||
|
||||
# Separator + Status
|
||||
y += 2 + 20
|
||||
status_text, _ = self._get_status()
|
||||
status_lines = wrap_text(gui_app.font(FontWeight.BOLD), status_text, 48, w)
|
||||
y += int(len(status_lines) * 48 * FONT_SCALE) + 20
|
||||
|
||||
# Contribution count
|
||||
if self._segment_count > 0:
|
||||
contrib_text = trn("{} segment of your driving is in the training dataset so far.",
|
||||
"{} segments of your driving is in the training dataset so far.", self._segment_count).format(self._segment_count)
|
||||
contrib_lines = wrap_text(gui_app.font(FontWeight.BOLD), contrib_text, 42, w)
|
||||
y += int(len(contrib_lines) * 42 * FONT_SCALE) + 20
|
||||
|
||||
# Separator + Instructions
|
||||
y += 2 + 20
|
||||
|
||||
# Instructions intro
|
||||
intro_lines = wrap_text(gui_app.font(FontWeight.ROMAN), tr(INSTRUCTIONS_INTRO), 32, w)
|
||||
y += int(len(intro_lines) * 32 * FONT_SCALE) + 20
|
||||
|
||||
# FAQ Header
|
||||
faq_header_lines = wrap_text(gui_app.font(FontWeight.BOLD), tr(FAQ_HEADER), 44, w)
|
||||
y += int(len(faq_header_lines) * 44 * FONT_SCALE) + 20
|
||||
|
||||
# FAQ Items
|
||||
for question, answer in FAQ_ITEMS:
|
||||
q_lines = wrap_text(gui_app.font(FontWeight.BOLD), tr(question), 32, w)
|
||||
y += int(len(q_lines) * 32 * FONT_SCALE)
|
||||
a_lines = wrap_text(gui_app.font(FontWeight.ROMAN), tr(answer), 32, w)
|
||||
y += int(len(a_lines) * 32 * FONT_SCALE) + 20
|
||||
|
||||
# bottom padding
|
||||
y += 40
|
||||
return y
|
||||
|
||||
def _get_status(self) -> tuple[str, rl.Color]:
|
||||
network_type = ui_state.sm["deviceState"].networkType
|
||||
network_metered = ui_state.sm["deviceState"].networkMetered
|
||||
|
||||
if not network_metered and network_type != 0: # Not metered and connected
|
||||
return tr("ACTIVE"), self.GREEN
|
||||
else:
|
||||
return tr("INACTIVE: connect to an unmetered network"), self.RED
|
||||
|
||||
def _fetch_firehose_stats(self):
|
||||
try:
|
||||
dongle_id = self._params.get("DongleId")
|
||||
if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID:
|
||||
return
|
||||
identity_token = get_token(dongle_id)
|
||||
response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token, session=self._session)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
self._segment_count = data.get("firehose", 0)
|
||||
self._params.put(self.PARAM_KEY, data)
|
||||
except Exception as e:
|
||||
cloudlog.error(f"Failed to fetch firehose stats: {e}")
|
||||
|
||||
def _update_loop(self):
|
||||
while self._running:
|
||||
if not ui_state.started and device._awake:
|
||||
self._fetch_firehose_stats()
|
||||
time.sleep(self.UPDATE_INTERVAL)
|
||||
|
||||
|
||||
class FirehoseLayout(NavRawScrollPanel, FirehoseLayoutBase):
|
||||
pass
|
||||
@@ -1,112 +0,0 @@
|
||||
#include "selfdrive/ui/qt/offroad/firehose.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
#include "selfdrive/ui/qt/offroad/settings.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QFrame>
|
||||
#include <QScrollArea>
|
||||
#include <QStackedLayout>
|
||||
#include <QProgressBar>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QTimer>
|
||||
|
||||
FirehosePanel::FirehosePanel(SettingsWindow *parent) : QWidget((QWidget*)parent) {
|
||||
layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(40, 40, 40, 40);
|
||||
layout->setSpacing(20);
|
||||
|
||||
// header
|
||||
QLabel *title = new QLabel(tr("Firehose Mode"));
|
||||
title->setStyleSheet("font-size: 100px; font-weight: 500; font-family: 'Noto Color Emoji';");
|
||||
layout->addWidget(title, 0, Qt::AlignCenter);
|
||||
|
||||
// Create a container for the content
|
||||
QFrame *content = new QFrame();
|
||||
content->setStyleSheet("background-color: #292929; border-radius: 15px; padding: 20px;");
|
||||
QVBoxLayout *content_layout = new QVBoxLayout(content);
|
||||
content_layout->setSpacing(20);
|
||||
|
||||
// Top description
|
||||
QLabel *description = new QLabel(tr("openpilot learns to drive by watching humans, like you, drive.\n\nFirehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode."));
|
||||
description->setStyleSheet("font-size: 45px; padding-bottom: 20px;");
|
||||
description->setWordWrap(true);
|
||||
content_layout->addWidget(description);
|
||||
|
||||
// Add a separator
|
||||
QFrame *line = new QFrame();
|
||||
line->setFrameShape(QFrame::HLine);
|
||||
line->setFrameShadow(QFrame::Sunken);
|
||||
line->setStyleSheet("background-color: #444444; margin-top: 5px; margin-bottom: 5px;");
|
||||
content_layout->addWidget(line);
|
||||
|
||||
toggle_label = new QLabel(tr("Firehose Mode: ACTIVE"));
|
||||
toggle_label->setStyleSheet("font-size: 60px; font-weight: bold; color: white;");
|
||||
content_layout->addWidget(toggle_label);
|
||||
|
||||
// Add contribution label
|
||||
contribution_label = new QLabel();
|
||||
contribution_label->setStyleSheet("font-size: 52px; margin-top: 10px; margin-bottom: 10px;");
|
||||
contribution_label->setWordWrap(true);
|
||||
contribution_label->hide();
|
||||
content_layout->addWidget(contribution_label);
|
||||
|
||||
// Add a separator before detailed instructions
|
||||
QFrame *line2 = new QFrame();
|
||||
line2->setFrameShape(QFrame::HLine);
|
||||
line2->setFrameShadow(QFrame::Sunken);
|
||||
line2->setStyleSheet("background-color: #444444; margin-top: 10px; margin-bottom: 10px;");
|
||||
content_layout->addWidget(line2);
|
||||
|
||||
// Detailed instructions at the bottom
|
||||
detailed_instructions = new QLabel(tr(
|
||||
"For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br>"
|
||||
"<br>"
|
||||
"Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br>"
|
||||
"<br><br>"
|
||||
"<b>Frequently Asked Questions</b><br><br>"
|
||||
"<i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br>"
|
||||
"<i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br>"
|
||||
"<i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br>"
|
||||
"<i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training."
|
||||
));
|
||||
detailed_instructions->setStyleSheet("font-size: 40px; color: #E4E4E4;");
|
||||
detailed_instructions->setWordWrap(true);
|
||||
content_layout->addWidget(detailed_instructions);
|
||||
|
||||
layout->addWidget(content, 1);
|
||||
|
||||
// Set up the API request for firehose stats
|
||||
const QString dongle_id = QString::fromStdString(Params().get("DongleId"));
|
||||
firehose_stats = new RequestRepeater(this, CommaApi::BASE_URL + "/v1/devices/" + dongle_id + "/firehose_stats",
|
||||
"ApiCache_FirehoseStats", 30, false);
|
||||
QObject::connect(firehose_stats, &RequestRepeater::requestDone, [=](const QString &response, bool success) {
|
||||
if (success) {
|
||||
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
|
||||
QJsonObject json = doc.object();
|
||||
int count = json["firehose"].toInt();
|
||||
contribution_label->setText(tr("<b>%n segment(s)</b> of your driving is in the training dataset so far.", "", count));
|
||||
contribution_label->show();
|
||||
}
|
||||
});
|
||||
|
||||
QObject::connect(uiState(), &UIState::uiUpdate, this, &FirehosePanel::refresh);
|
||||
}
|
||||
|
||||
void FirehosePanel::refresh() {
|
||||
auto deviceState = (*uiState()->sm)["deviceState"].getDeviceState();
|
||||
auto networkType = deviceState.getNetworkType();
|
||||
bool networkMetered = deviceState.getNetworkMetered();
|
||||
|
||||
bool is_active = !networkMetered && (networkType != cereal::DeviceState::NetworkType::NONE);
|
||||
if (is_active) {
|
||||
toggle_label->setText(tr("ACTIVE"));
|
||||
toggle_label->setStyleSheet("font-size: 60px; font-weight: bold; color: #2ecc71;");
|
||||
} else {
|
||||
toggle_label->setText(tr("<span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network"));
|
||||
toggle_label->setStyleSheet("font-size: 60px;");
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
#include "selfdrive/ui/qt/request_repeater.h"
|
||||
|
||||
// Forward declarations
|
||||
class SettingsWindow;
|
||||
|
||||
class FirehosePanel : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FirehosePanel(SettingsWindow *parent);
|
||||
|
||||
private:
|
||||
QVBoxLayout *layout;
|
||||
|
||||
QLabel *detailed_instructions;
|
||||
QLabel *contribution_label;
|
||||
QLabel *toggle_label;
|
||||
|
||||
RequestRepeater *firehose_stats;
|
||||
|
||||
private slots:
|
||||
void refresh();
|
||||
};
|
||||
@@ -1,118 +0,0 @@
|
||||
/****************************************************************************
|
||||
** Meta object code from reading C++ file 'firehose.h'
|
||||
**
|
||||
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.12.8)
|
||||
**
|
||||
** WARNING! All changes made in this file will be lost!
|
||||
*****************************************************************************/
|
||||
|
||||
#include "firehose.h"
|
||||
#include <QtCore/qbytearray.h>
|
||||
#include <QtCore/qmetatype.h>
|
||||
#if !defined(Q_MOC_OUTPUT_REVISION)
|
||||
#error "The header file 'firehose.h' doesn't include <QObject>."
|
||||
#elif Q_MOC_OUTPUT_REVISION != 67
|
||||
#error "This file was generated using the moc from 5.12.8. It"
|
||||
#error "cannot be used with the include files from this version of Qt."
|
||||
#error "(The moc has changed too much.)"
|
||||
#endif
|
||||
|
||||
QT_BEGIN_MOC_NAMESPACE
|
||||
QT_WARNING_PUSH
|
||||
QT_WARNING_DISABLE_DEPRECATED
|
||||
struct qt_meta_stringdata_FirehosePanel_t {
|
||||
QByteArrayData data[3];
|
||||
char stringdata0[23];
|
||||
};
|
||||
#define QT_MOC_LITERAL(idx, ofs, len) \
|
||||
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
|
||||
qptrdiff(offsetof(qt_meta_stringdata_FirehosePanel_t, stringdata0) + ofs \
|
||||
- idx * sizeof(QByteArrayData)) \
|
||||
)
|
||||
static const qt_meta_stringdata_FirehosePanel_t qt_meta_stringdata_FirehosePanel = {
|
||||
{
|
||||
QT_MOC_LITERAL(0, 0, 13), // "FirehosePanel"
|
||||
QT_MOC_LITERAL(1, 14, 7), // "refresh"
|
||||
QT_MOC_LITERAL(2, 22, 0) // ""
|
||||
|
||||
},
|
||||
"FirehosePanel\0refresh\0"
|
||||
};
|
||||
#undef QT_MOC_LITERAL
|
||||
|
||||
static const uint qt_meta_data_FirehosePanel[] = {
|
||||
|
||||
// content:
|
||||
8, // revision
|
||||
0, // classname
|
||||
0, 0, // classinfo
|
||||
1, 14, // methods
|
||||
0, 0, // properties
|
||||
0, 0, // enums/sets
|
||||
0, 0, // constructors
|
||||
0, // flags
|
||||
0, // signalCount
|
||||
|
||||
// slots: name, argc, parameters, tag, flags
|
||||
1, 0, 19, 2, 0x08 /* Private */,
|
||||
|
||||
// slots: parameters
|
||||
QMetaType::Void,
|
||||
|
||||
0 // eod
|
||||
};
|
||||
|
||||
void FirehosePanel::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
|
||||
{
|
||||
if (_c == QMetaObject::InvokeMetaMethod) {
|
||||
auto *_t = static_cast<FirehosePanel *>(_o);
|
||||
Q_UNUSED(_t)
|
||||
switch (_id) {
|
||||
case 0: _t->refresh(); break;
|
||||
default: ;
|
||||
}
|
||||
}
|
||||
Q_UNUSED(_a);
|
||||
}
|
||||
|
||||
QT_INIT_METAOBJECT const QMetaObject FirehosePanel::staticMetaObject = { {
|
||||
&QWidget::staticMetaObject,
|
||||
qt_meta_stringdata_FirehosePanel.data,
|
||||
qt_meta_data_FirehosePanel,
|
||||
qt_static_metacall,
|
||||
nullptr,
|
||||
nullptr
|
||||
} };
|
||||
|
||||
|
||||
const QMetaObject *FirehosePanel::metaObject() const
|
||||
{
|
||||
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
|
||||
}
|
||||
|
||||
void *FirehosePanel::qt_metacast(const char *_clname)
|
||||
{
|
||||
if (!_clname) return nullptr;
|
||||
if (!strcmp(_clname, qt_meta_stringdata_FirehosePanel.stringdata0))
|
||||
return static_cast<void*>(this);
|
||||
return QWidget::qt_metacast(_clname);
|
||||
}
|
||||
|
||||
int FirehosePanel::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
|
||||
{
|
||||
_id = QWidget::qt_metacall(_c, _id, _a);
|
||||
if (_id < 0)
|
||||
return _id;
|
||||
if (_c == QMetaObject::InvokeMetaMethod) {
|
||||
if (_id < 1)
|
||||
qt_static_metacall(this, _c, _id, _a);
|
||||
_id -= 1;
|
||||
} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
|
||||
if (_id < 1)
|
||||
*reinterpret_cast<int*>(_a[0]) = -1;
|
||||
_id -= 1;
|
||||
}
|
||||
return _id;
|
||||
}
|
||||
QT_WARNING_POP
|
||||
QT_END_MOC_NAMESPACE
|
||||
@@ -20,7 +20,6 @@
|
||||
#include "selfdrive/ui/qt/widgets/prime.h"
|
||||
#include "selfdrive/ui/qt/widgets/scrollview.h"
|
||||
#include "selfdrive/ui/qt/offroad/developer_panel.h"
|
||||
#include "selfdrive/ui/qt/offroad/firehose.h"
|
||||
|
||||
#include "starpilot/ui/qt/offroad/starpilot_settings.h"
|
||||
|
||||
|
||||
@@ -141,6 +141,3 @@ private:
|
||||
Params params;
|
||||
ParamWatcher *fs_watch;
|
||||
};
|
||||
|
||||
// Forward declaration
|
||||
class FirehosePanel;
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
/****************************************************************************
|
||||
** Meta object code from reading C++ file 'wifi.h'
|
||||
**
|
||||
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.12.8)
|
||||
**
|
||||
** WARNING! All changes made in this file will be lost!
|
||||
*****************************************************************************/
|
||||
|
||||
#include "wifi.h"
|
||||
#include <QtCore/qbytearray.h>
|
||||
#include <QtCore/qmetatype.h>
|
||||
#if !defined(Q_MOC_OUTPUT_REVISION)
|
||||
#error "The header file 'wifi.h' doesn't include <QObject>."
|
||||
#elif Q_MOC_OUTPUT_REVISION != 67
|
||||
#error "This file was generated using the moc from 5.12.8. It"
|
||||
#error "cannot be used with the include files from this version of Qt."
|
||||
#error "(The moc has changed too much.)"
|
||||
#endif
|
||||
|
||||
QT_BEGIN_MOC_NAMESPACE
|
||||
QT_WARNING_PUSH
|
||||
QT_WARNING_DISABLE_DEPRECATED
|
||||
struct qt_meta_stringdata_WiFiPromptWidget_t {
|
||||
QByteArrayData data[5];
|
||||
char stringdata0[43];
|
||||
};
|
||||
#define QT_MOC_LITERAL(idx, ofs, len) \
|
||||
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
|
||||
qptrdiff(offsetof(qt_meta_stringdata_WiFiPromptWidget_t, stringdata0) + ofs \
|
||||
- idx * sizeof(QByteArrayData)) \
|
||||
)
|
||||
static const qt_meta_stringdata_WiFiPromptWidget_t qt_meta_stringdata_WiFiPromptWidget = {
|
||||
{
|
||||
QT_MOC_LITERAL(0, 0, 16), // "WiFiPromptWidget"
|
||||
QT_MOC_LITERAL(1, 17, 12), // "openSettings"
|
||||
QT_MOC_LITERAL(2, 30, 0), // ""
|
||||
QT_MOC_LITERAL(3, 31, 5), // "index"
|
||||
QT_MOC_LITERAL(4, 37, 5) // "param"
|
||||
|
||||
},
|
||||
"WiFiPromptWidget\0openSettings\0\0index\0"
|
||||
"param"
|
||||
};
|
||||
#undef QT_MOC_LITERAL
|
||||
|
||||
static const uint qt_meta_data_WiFiPromptWidget[] = {
|
||||
|
||||
// content:
|
||||
8, // revision
|
||||
0, // classname
|
||||
0, 0, // classinfo
|
||||
3, 14, // methods
|
||||
0, 0, // properties
|
||||
0, 0, // enums/sets
|
||||
0, 0, // constructors
|
||||
0, // flags
|
||||
3, // signalCount
|
||||
|
||||
// signals: name, argc, parameters, tag, flags
|
||||
1, 2, 29, 2, 0x06 /* Public */,
|
||||
1, 1, 34, 2, 0x26 /* Public | MethodCloned */,
|
||||
1, 0, 37, 2, 0x26 /* Public | MethodCloned */,
|
||||
|
||||
// signals: parameters
|
||||
QMetaType::Void, QMetaType::Int, QMetaType::QString, 3, 4,
|
||||
QMetaType::Void, QMetaType::Int, 3,
|
||||
QMetaType::Void,
|
||||
|
||||
0 // eod
|
||||
};
|
||||
|
||||
void WiFiPromptWidget::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
|
||||
{
|
||||
if (_c == QMetaObject::InvokeMetaMethod) {
|
||||
auto *_t = static_cast<WiFiPromptWidget *>(_o);
|
||||
Q_UNUSED(_t)
|
||||
switch (_id) {
|
||||
case 0: _t->openSettings((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< const QString(*)>(_a[2]))); break;
|
||||
case 1: _t->openSettings((*reinterpret_cast< int(*)>(_a[1]))); break;
|
||||
case 2: _t->openSettings(); break;
|
||||
default: ;
|
||||
}
|
||||
} else if (_c == QMetaObject::IndexOfMethod) {
|
||||
int *result = reinterpret_cast<int *>(_a[0]);
|
||||
{
|
||||
using _t = void (WiFiPromptWidget::*)(int , const QString & );
|
||||
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&WiFiPromptWidget::openSettings)) {
|
||||
*result = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QT_INIT_METAOBJECT const QMetaObject WiFiPromptWidget::staticMetaObject = { {
|
||||
&QFrame::staticMetaObject,
|
||||
qt_meta_stringdata_WiFiPromptWidget.data,
|
||||
qt_meta_data_WiFiPromptWidget,
|
||||
qt_static_metacall,
|
||||
nullptr,
|
||||
nullptr
|
||||
} };
|
||||
|
||||
|
||||
const QMetaObject *WiFiPromptWidget::metaObject() const
|
||||
{
|
||||
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
|
||||
}
|
||||
|
||||
void *WiFiPromptWidget::qt_metacast(const char *_clname)
|
||||
{
|
||||
if (!_clname) return nullptr;
|
||||
if (!strcmp(_clname, qt_meta_stringdata_WiFiPromptWidget.stringdata0))
|
||||
return static_cast<void*>(this);
|
||||
return QFrame::qt_metacast(_clname);
|
||||
}
|
||||
|
||||
int WiFiPromptWidget::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
|
||||
{
|
||||
_id = QFrame::qt_metacall(_c, _id, _a);
|
||||
if (_id < 0)
|
||||
return _id;
|
||||
if (_c == QMetaObject::InvokeMetaMethod) {
|
||||
if (_id < 3)
|
||||
qt_static_metacall(this, _c, _id, _a);
|
||||
_id -= 3;
|
||||
} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
|
||||
if (_id < 3)
|
||||
*reinterpret_cast<int*>(_a[0]) = -1;
|
||||
_id -= 3;
|
||||
}
|
||||
return _id;
|
||||
}
|
||||
|
||||
// SIGNAL 0
|
||||
void WiFiPromptWidget::openSettings(int _t1, const QString & _t2)
|
||||
{
|
||||
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)), const_cast<void*>(reinterpret_cast<const void*>(&_t2)) };
|
||||
QMetaObject::activate(this, &staticMetaObject, 0, _a);
|
||||
}
|
||||
QT_WARNING_POP
|
||||
QT_END_MOC_NAMESPACE
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "selfdrive/ui/qt/request_repeater.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/qt/qt_window.h"
|
||||
#include "selfdrive/ui/qt/widgets/wifi.h"
|
||||
|
||||
using qrcodegen::QrCode;
|
||||
|
||||
@@ -232,10 +231,6 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) {
|
||||
QVBoxLayout *content_layout = new QVBoxLayout(content);
|
||||
content_layout->setContentsMargins(0, 0, 0, 0);
|
||||
content_layout->setSpacing(30);
|
||||
|
||||
WiFiPromptWidget *wifi_prompt = new WiFiPromptWidget;
|
||||
QObject::connect(wifi_prompt, &WiFiPromptWidget::openSettings, this, &SetupWidget::openSettings);
|
||||
content_layout->addWidget(wifi_prompt);
|
||||
content_layout->addStretch();
|
||||
|
||||
mainLayout->addWidget(content);
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
#include "selfdrive/ui/qt/widgets/wifi.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPixmap>
|
||||
#include <QPushButton>
|
||||
|
||||
WiFiPromptWidget::WiFiPromptWidget(QWidget *parent) : QFrame(parent) {
|
||||
// Setup Firehose Mode
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->setContentsMargins(56, 40, 56, 40);
|
||||
main_layout->setSpacing(42);
|
||||
|
||||
QLabel *title = new QLabel(tr("<span style='font-family: \"Noto Color Emoji\";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span>"));
|
||||
title->setStyleSheet("font-size: 64px; font-weight: 500;");
|
||||
main_layout->addWidget(title);
|
||||
|
||||
QLabel *desc = new QLabel(tr("Maximize your training data uploads to improve openpilot's driving models."));
|
||||
desc->setStyleSheet("font-size: 40px; font-weight: 400;");
|
||||
desc->setWordWrap(true);
|
||||
main_layout->addWidget(desc);
|
||||
|
||||
QPushButton *settings_btn = new QPushButton(tr("Open"));
|
||||
connect(settings_btn, &QPushButton::clicked, [=]() { emit openSettings(1, "FirehosePanel"); });
|
||||
settings_btn->setStyleSheet(R"(
|
||||
QPushButton {
|
||||
font-size: 48px;
|
||||
font-weight: 500;
|
||||
border-radius: 10px;
|
||||
background-color: #465BEA;
|
||||
padding: 32px;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #3049F4;
|
||||
}
|
||||
)");
|
||||
main_layout->addWidget(settings_btn);
|
||||
|
||||
setStyleSheet(R"(
|
||||
WiFiPromptWidget {
|
||||
background-color: #333333;
|
||||
border-radius: 10px;
|
||||
}
|
||||
)");
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QFrame>
|
||||
#include <QWidget>
|
||||
|
||||
class WiFiPromptWidget : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WiFiPromptWidget(QWidget* parent = 0);
|
||||
|
||||
signals:
|
||||
void openSettings(int index = 0, const QString ¶m = "");
|
||||
};
|
||||
@@ -103,11 +103,6 @@ def setup_settings_software_branch_switcher(click, pm: PubMaster):
|
||||
click(1984, 449)
|
||||
|
||||
|
||||
def setup_settings_firehose(click, pm: PubMaster):
|
||||
setup_settings(click, pm)
|
||||
click(278, 845)
|
||||
|
||||
|
||||
def setup_settings_developer(click, pm: PubMaster):
|
||||
CP = car.CarParams()
|
||||
CP.alphaLongitudinalAvailable = True # show alpha long control toggle
|
||||
@@ -226,7 +221,6 @@ CASES = {
|
||||
"settings_software_download": setup_settings_software_download,
|
||||
"settings_software_release_notes": setup_settings_software_release_notes,
|
||||
"settings_software_branch_switcher": setup_settings_software_branch_switcher,
|
||||
"settings_firehose": setup_settings_firehose,
|
||||
"settings_developer": setup_settings_developer,
|
||||
"keyboard": setup_keyboard,
|
||||
"pair_device": setup_pair_device,
|
||||
|
||||
@@ -8,27 +8,24 @@ from openpilot.system.ui.lib.wrap_text import wrap_text
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.confirm_dialog import alert_dialog
|
||||
from openpilot.system.ui.widgets.button import Button, ButtonStyle
|
||||
from openpilot.system.ui.widgets.label import Label
|
||||
|
||||
|
||||
LOGO_WIDTH = 750
|
||||
LOGO_HEIGHT = 770
|
||||
|
||||
|
||||
class SetupWidget(Widget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._open_settings_callback = None
|
||||
self._pairing_dialog: PairingDialog | None = None
|
||||
self._pair_device_btn = Button(lambda: tr("Pair device"), self._show_pairing, button_style=ButtonStyle.PRIMARY)
|
||||
self._open_settings_btn = Button(lambda: tr("Open"), lambda: self._open_settings_callback() if self._open_settings_callback else None,
|
||||
button_style=ButtonStyle.PRIMARY)
|
||||
self._firehose_label = Label(lambda: tr("🔥 Firehose Mode 🔥"), font_weight=FontWeight.MEDIUM, font_size=64)
|
||||
|
||||
def set_open_settings_callback(self, callback):
|
||||
self._open_settings_callback = callback
|
||||
self._logo_texture = gui_app.texture("images/StarPilotLogo.png", LOGO_WIDTH, LOGO_HEIGHT)
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
if not ui_state.prime_state.is_paired():
|
||||
self._render_registration(rect)
|
||||
else:
|
||||
self._render_firehose_prompt(rect)
|
||||
self._render_logo(rect)
|
||||
|
||||
def _render_registration(self, rect: rl.Rectangle):
|
||||
"""Render registration prompt."""
|
||||
@@ -55,36 +52,12 @@ class SetupWidget(Widget):
|
||||
button_rect = rl.Rectangle(x, y + 30, w, 200)
|
||||
self._pair_device_btn.render(button_rect)
|
||||
|
||||
def _render_firehose_prompt(self, rect: rl.Rectangle):
|
||||
"""Render firehose prompt widget."""
|
||||
|
||||
rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 500), 0.04, 20, rl.Color(51, 51, 51, 255))
|
||||
|
||||
# Content margins (56, 40, 56, 40)
|
||||
x = rect.x + 56
|
||||
y = rect.y + 40
|
||||
w = rect.width - 112
|
||||
spacing = 42
|
||||
|
||||
# Title with fire emojis
|
||||
self._firehose_label.render(rl.Rectangle(rect.x, y, rect.width, 64))
|
||||
y += 64 + spacing
|
||||
|
||||
# Description
|
||||
desc_font = gui_app.font(FontWeight.NORMAL)
|
||||
desc_text = tr("Maximize your training data uploads to improve openpilot's driving models.")
|
||||
wrapped_desc = wrap_text(desc_font, desc_text, 40, int(w))
|
||||
|
||||
for line in wrapped_desc:
|
||||
rl.draw_text_ex(desc_font, line, rl.Vector2(x, y), 40, 0, rl.WHITE)
|
||||
y += 40 * FONT_SCALE
|
||||
|
||||
y += spacing
|
||||
|
||||
# Open button
|
||||
button_height = 48 + 64 # font size + padding
|
||||
button_rect = rl.Rectangle(x, y, w, button_height)
|
||||
self._open_settings_btn.render(button_rect)
|
||||
def _render_logo(self, rect: rl.Rectangle):
|
||||
tex_w = self._logo_texture.width
|
||||
tex_h = self._logo_texture.height
|
||||
x = rect.x + (rect.width - tex_w) / 2
|
||||
y = rect.y + (rect.height - tex_h) / 2
|
||||
rl.draw_texture(self._logo_texture, int(x), int(y), rl.WHITE)
|
||||
|
||||
def _show_pairing(self):
|
||||
if not system_time_valid():
|
||||
|
||||
Reference in New Issue
Block a user