This commit is contained in:
infiniteCable2
2026-02-09 18:26:48 +01:00
20 changed files with 821 additions and 55 deletions
+2 -1
View File
@@ -114,6 +114,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"SnoozeUpdate", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
{"SshEnabled", {PERSISTENT | BACKUP, BOOL}},
{"TermsVersion", {PERSISTENT, STRING}},
{"TorqueBar", {PERSISTENT | BACKUP, BOOL, "0"}},
{"TrainingVersion", {PERSISTENT, STRING}},
{"UbloxAvailable", {PERSISTENT, BOOL}},
{"UpdateAvailable", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
@@ -261,7 +262,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"OsmStateTitle", {PERSISTENT, STRING}},
{"OsmWayTest", {PERSISTENT, STRING}},
{"RoadName", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
{"RoadNameToggle", {PERSISTENT, STRING}},
{"RoadNameToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
// Speed Limit
{"SpeedLimitMode", {PERSISTENT | BACKUP, INT, "1"}},
+9 -7
View File
@@ -146,9 +146,11 @@ def arc_bar_pts(cx: float, cy: float,
class TorqueBar(Widget):
def __init__(self, demo: bool = False):
def __init__(self, demo: bool = False, scale: float = 1.0, always: bool = False):
super().__init__()
self._demo = demo
self._scale = scale
self._always = always
self._torque_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps)
self._torque_line_alpha_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps)
@@ -180,8 +182,8 @@ class TorqueBar(Widget):
def _render(self, rect: rl.Rectangle) -> None:
# adjust y pos with torque
torque_line_offset = np.interp(abs(self._torque_filter.x), [0.5, 1], [22, 26])
torque_line_height = np.interp(abs(self._torque_filter.x), [0.5, 1], [14, 56])
torque_line_offset = np.interp(abs(self._torque_filter.x), [0.5, 1], [22 * self._scale, 26 * self._scale])
torque_line_height = np.interp(abs(self._torque_filter.x), [0.5, 1], [14 * self._scale, 56 * self._scale])
# animate alpha and angle span
if not self._demo:
@@ -195,7 +197,7 @@ class TorqueBar(Widget):
torque_line_bg_color = rl.Color(255, 255, 255, int(255 * 0.15 * self._torque_line_alpha_filter.x))
# draw curved line polygon torque bar
torque_line_radius = 1200
torque_line_radius = 1200 * self._scale
top_angle = -90
torque_bg_angle_span = self._torque_line_alpha_filter.x * TORQUE_ANGLE_SPAN
torque_start_angle = top_angle - torque_bg_angle_span / 2
@@ -207,13 +209,13 @@ class TorqueBar(Widget):
cy = rect.y + rect.height + torque_line_radius - torque_line_offset
# draw bg torque indicator line
bg_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, torque_start_angle, torque_end_angle)
bg_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, torque_start_angle, torque_end_angle, cap_radius=7 * self._scale)
draw_polygon(rect, bg_pts, color=torque_line_bg_color)
# draw torque indicator line
a0s = top_angle
a1s = a0s + torque_bg_angle_span / 2 * self._torque_filter.x
sl_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, a0s, a1s)
sl_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, a0s, a1s, cap_radius=7 * self._scale)
# draw beautiful gradient from center to 65% of the bg torque bar width
start_grad_pt = cx / rect.width
@@ -252,5 +254,5 @@ class TorqueBar(Widget):
# draw center torque bar dot
if abs(self._torque_filter.x) < 0.5:
dot_y = self._rect.y + self._rect.height - torque_line_offset - torque_line_height / 2
rl.draw_circle(int(cx), int(dot_y), 10 // 2,
rl.draw_circle(int(cx), int(dot_y), (10 // 2 * self._scale),
rl.Color(182, 182, 182, int(255 * 0.9 * self._torque_line_alpha_filter.x)))
+5 -3
View File
@@ -15,7 +15,7 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCamera
from openpilot.common.transformations.orientation import rot_from_euler
if gui_app.sunnypilot_ui():
from openpilot.selfdrive.ui.sunnypilot.onroad.augmented_road_view import BORDER_COLORS_SP
from openpilot.selfdrive.ui.sunnypilot.onroad.augmented_road_view import BORDER_COLORS_SP, AugmentedRoadViewSP
from openpilot.selfdrive.ui.sunnypilot.onroad.driver_state import DriverStateRendererSP as DriverStateRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.hud_renderer import HudRendererSP as HudRenderer
from openpilot.selfdrive.ui.sunnypilot.ui_state import OnroadTimerStatus
@@ -38,9 +38,10 @@ ROAD_CAM_MIN_SPEED = 15.0 # m/s (34 mph)
INF_POINT = np.array([1000.0, 0.0, 0.0])
class AugmentedRoadView(CameraView):
class AugmentedRoadView(CameraView, AugmentedRoadViewSP):
def __init__(self, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD):
super().__init__("camerad", stream_type)
CameraView.__init__(self, "camerad", stream_type)
AugmentedRoadViewSP.__init__(self)
self._set_placeholder_color(BORDER_COLORS[UIStatus.DISENGAGED])
self.device_camera: DeviceCameraConfig | None = None
@@ -92,6 +93,7 @@ class AugmentedRoadView(CameraView):
# Draw all UI overlays
self.model_renderer.render(self._content_rect)
AugmentedRoadViewSP.update_fade_out_bottom_overlay(self, self._content_rect)
self._hud_renderer.render(self._content_rect)
self.alert_renderer.render(self._content_rect)
self.driver_state_renderer.render(self._content_rect)
+133 -12
View File
@@ -4,27 +4,148 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import requests
import threading
import time
import pyray as rl
from openpilot.common.api import api_get
from openpilot.common.constants import CV
from openpilot.common.params import Params
from openpilot.system.ui.widgets.scroller_tici import Scroller
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.multilang import tr
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.widgets import Widget
class TripsLayout(Widget):
PARAM_KEY = "ApiCache_DriveStats"
UPDATE_INTERVAL = 30 # seconds
def __init__(self):
super().__init__()
self._params = Params()
items = self._initialize_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
self._session = requests.Session()
self._stats = self._get_stats()
def _initialize_items(self):
items = [
self._icon_distance = gui_app.texture("icons/road.png", 100, 100, keep_aspect_ratio=True)
self._icon_drives = gui_app.texture("icons_mici/wheel.png", 80, 80, keep_aspect_ratio=True)
self._icon_hours = gui_app.texture("../../sunnypilot/selfdrive/assets/icons/clock.png", 80, 80, keep_aspect_ratio=True)
]
return items
self._running = True
self._update_thread = threading.Thread(target=self._update_loop, daemon=True)
self._update_thread.start()
def _render(self, rect):
self._scroller.render(rect)
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):
self._scroller.show_event()
def _get_stats(self):
stats = self._params.get(self.PARAM_KEY)
if not stats:
return {}
try:
return stats
except Exception:
cloudlog.exception(f"Failed to decode drive stats: {stats}")
return {}
def _fetch_drive_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.1/devices/{dongle_id}/stats", access_token=identity_token, session=self._session)
if response.status_code == 200:
data = response.json()
self._stats = data
self._params.put(self.PARAM_KEY, data)
except Exception as e:
cloudlog.error(f"Failed to fetch drive stats: {e}")
def _update_loop(self):
while self._running:
if not ui_state.started and device._awake:
self._fetch_drive_stats()
time.sleep(self.UPDATE_INTERVAL)
def _render_stat_group(self, x, y, width, height, title, data, is_metric):
# Card Background
rl.draw_rectangle_rounded(rl.Rectangle(x, y, width, height), 0.05, 10, rl.Color(30, 30, 30, 255))
# Title
title_font = gui_app.font(FontWeight.BOLD)
rl.draw_text_ex(title_font, title, rl.Vector2(x + 60, y + 30), 50 * FONT_SCALE, 0, rl.Color(200, 200, 200, 255))
# Internal content area
# Center the content block (Icon + Value + Unit) vertically
content_y = y + (height / 2) - (140 * FONT_SCALE)
col_width = width / 3
# Values
number_font = gui_app.font(FontWeight.BOLD)
unit_font = gui_app.font(FontWeight.LIGHT)
number_base_size = 92
unit_base_size = 55
number_size = number_base_size * FONT_SCALE
unit_size = unit_base_size * FONT_SCALE
color_unit = rl.Color(160, 160, 160, 255)
routes = int(data.get("routes", 0))
distance = data.get("distance", 0)
distance_str = str(int(distance * CV.MPH_TO_KPH)) if is_metric else str(int(distance))
hours = int(data.get("minutes", 0) / 60)
dist_unit = tr("KM") if is_metric else tr("Miles")
def draw_col(col_idx, icon, value, unit):
col_x = x + (col_width * col_idx)
center_x = col_x + (col_width / 2)
# Icon
icon_x = int(center_x - (icon.width / 2))
icon_y = int(content_y + 60)
rl.draw_texture(icon, icon_x, icon_y, rl.WHITE)
# Value
val_size = measure_text_cached(number_font, value, number_base_size)
rl.draw_text_ex(number_font, value, rl.Vector2(center_x - val_size.x / 1.65, content_y + 145 * FONT_SCALE), number_size, 0, rl.WHITE)
# Unit
unit_size_vec = measure_text_cached(unit_font, unit, unit_base_size)
rl.draw_text_ex(unit_font, unit, rl.Vector2(center_x - unit_size_vec.x / 1.65, content_y + 255 * FONT_SCALE), unit_size, 0, color_unit)
draw_col(0, self._icon_drives, str(routes), tr("Drives"))
draw_col(1, self._icon_distance, distance_str, dist_unit)
draw_col(2, self._icon_hours, str(hours), tr("Hours"))
return y + height
def _render(self, rect: rl.Rectangle):
x = rect.x
y = rect.y
w = rect.width
spacing = 30
available_h = rect.height - 30
card_height = available_h / 2
is_metric = self._params.get_bool("IsMetric")
all_time = self._stats.get("all", {})
week = self._stats.get("week", {})
y = self._render_stat_group(x, y, w, card_height, tr("ALL TIME"), all_time, is_metric)
y += spacing
y = self._render_stat_group(x, y, w, card_height, tr("PAST WEEK"), week, is_metric)
return -1
@@ -5,9 +5,18 @@ This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from openpilot.common.params import Params
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp, multiple_button_item_sp
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.widgets import Widget
CHEVRON_INFO_DESCRIPTION = {
"enabled": tr_noop("Display useful metrics below the chevron that tracks the lead car " +
"only applicable to cars with sunnypilot longitudinal control."),
"disabled": tr_noop("This feature requires sunnypilot longitudinal control to be available.")
}
class VisualsLayout(Widget):
def __init__(self):
@@ -18,13 +27,128 @@ class VisualsLayout(Widget):
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _initialize_items(self):
items = [
self._toggle_defs = {
"BlindSpot": (
lambda: tr("Show Blind Spot Warnings"),
tr("Enabling this will display warnings when a vehicle is detected in your " +
"blind spot as long as your car has BSM supported."),
None,
),
"TorqueBar": (
lambda: tr("Steering Arc"),
tr("Display steering arc on the driving screen when lateral control is enabled."),
None,
),
"RainbowMode": (
lambda: tr("Enable Tesla Rainbow Mode"),
tr("A beautiful rainbow effect on the path the model wants to take. " +
"It does not affect driving in any way."),
None,
),
"StandstillTimer": (
lambda: tr("Enable Standstill Timer"),
tr("Show a timer on the HUD when the car is at a standstill."),
None,
),
"RoadNameToggle": (
lambda: tr("Display Road Name"),
tr("Displays the name of the road the car is traveling on." +
"<br>The OpenStreetMap database of the location must be downloaded from " +
"the OSM panel to fetch the road name."),
None,
),
"GreenLightAlert": (
lambda: tr("Green Traffic Light Alert (Beta)"),
tr("A chime and on-screen alert will play when the traffic light you are waiting for " +
"turns green and you have no vehicle in front of you." +
"<br>Note: This chime is only designed as a notification. " +
"It is the driver's responsibility to observe their environment and make decisions accordingly."),
None,
),
"LeadDepartAlert": (
lambda: tr("Lead Departure Alert (Beta)"),
tr("A chime and on-screen alert will play when you are stopped, and the vehicle in front of you start moving." +
"<br>Note: This chime is only designed as a notification. " +
"It is the driver's responsibility to observe their environment and make decisions accordingly."),
None,
),
"TrueVEgoUI": (
lambda: tr("Speedometer: Always Display True Speed"),
tr("For applicable vehicles, always display the true vehicle current speed from wheel speed sensors."),
None,
),
"HideVEgoUI": (
lambda: tr("Speedometer: Hide from Onroad Screen"),
tr("When enabled, the speedometer on the onroad screen is not displayed."),
None,
),
"ShowTurnSignals": (
lambda: tr("Display Turn Signals"),
tr("When enabled, visual turn indicators are drawn on the HUD."),
None,
),
"RocketFuel": (
lambda: tr("Real-time Acceleration Bar"),
tr("Show an indicator on the left side of the screen to display real-time vehicle acceleration and deceleration. " +
"This displays what the car is currently doing, not what the planner is requesting."),
None,
),
}
self._toggles = {}
for param, (title, desc, callback) in self._toggle_defs.items():
toggle = toggle_item_sp(
title=title,
description=desc,
param=param,
initial_state=ui_state.params.get_bool(param),
callback=callback,
)
self._toggles[param] = toggle
self._chevron_info = multiple_button_item_sp(
title=lambda: tr("Display Metrics Below Chevron"),
description="",
buttons=[lambda: tr("Off"), lambda: tr("Distance"), lambda: tr("Speed"), lambda: tr("Time"), lambda: tr("All")],
param="ChevronInfo",
inline=False
)
self._dev_ui_info = multiple_button_item_sp(
title=lambda: tr("Developer UI"),
description=lambda: tr("Display real-time parameters and metrics from various sources."),
buttons=[lambda: tr("Off"), lambda: tr("Bottom"), lambda: tr("Right"), lambda: tr("Right & Bottom")],
param="DevUIInfo",
button_width=350,
inline=False
)
items = list(self._toggles.values()) + [
self._chevron_info,
self._dev_ui_info,
]
return items
def _update_state(self):
super()._update_state()
for param in self._toggle_defs:
self._toggles[param].action_item.set_state(self._params.get_bool(param))
self._dev_ui_info.action_item.set_selected_button(ui_state.params.get("DevUIInfo", return_default=True))
if ui_state.has_longitudinal_control:
self._chevron_info.set_description(tr(CHEVRON_INFO_DESCRIPTION["enabled"]))
self._chevron_info.action_item.set_selected_button(ui_state.params.get("ChevronInfo", return_default=True))
self._chevron_info.action_item.set_enabled(True)
else:
self._chevron_info.set_description(tr(CHEVRON_INFO_DESCRIPTION["disabled"]))
self._chevron_info.action_item.set_enabled(False)
ui_state.params.put("ChevronInfo", 0)
def _render(self, rect):
self._scroller.render(rect)
def show_event(self):
self._scroller.show_event()
if not ui_state.has_longitudinal_control:
self._chevron_info.set_description(tr(CHEVRON_INFO_DESCRIPTION["disabled"]))
self._chevron_info.show_description(True)
@@ -0,0 +1,122 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from collections.abc import Callable
from cereal import custom
from openpilot.selfdrive.ui.mici.widgets.button import BigButton
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.widgets import NavWidget, Widget
from openpilot.system.ui.widgets.scroller import Scroller
class ModelsLayoutMici(NavWidget):
def __init__(self, back_callback: Callable):
super().__init__()
self.set_back_callback(back_callback)
self.original_back_callback = back_callback
self.focused_widget = None
self.current_model_btn = BigButton(tr("current model"), "", "")
self.current_model_btn.set_click_callback(self._show_folders)
self.cancel_download_btn = BigButton(tr("cancel download"), "", "")
self.cancel_download_btn.set_click_callback(lambda: ui_state.params.remove("ModelManager_DownloadIndex"))
self.main_items: list[Widget] = [self.current_model_btn, self.cancel_download_btn]
self._scroller = Scroller(self.main_items, snap_items=False)
@property
def model_manager(self):
return ui_state.sm["modelManagerSP"]
def _get_grouped_bundles(self):
bundles = self.model_manager.availableBundles
folders = {}
for bundle in bundles:
folder = next((override.value for override in bundle.overrides if override.key == "folder"), "")
folders.setdefault(folder, []).append(bundle)
return folders
def _show_selection_view(self, items: list[Widget], back_callback: Callable):
self._scroller._items = items
for item in items:
item.set_touch_valid_callback(lambda: self._scroller.scroll_panel.is_touch_valid() and self._scroller.enabled)
self._scroller.scroll_panel.set_offset(0)
self.set_back_callback(back_callback)
def _show_folders(self):
self.focused_widget = self.current_model_btn
folders = self._get_grouped_bundles()
folder_buttons = []
default_btn = BigButton(tr("default model"), "", "")
default_btn.set_click_callback(self._select_default)
folder_buttons.append(default_btn)
for folder in sorted(folders.keys(), key=lambda f: max((bundle.index for bundle in folders[f]), default=-1), reverse=True):
if folder.lower() in ["release models", "master models"]:
btn = BigButton(folder.lower(), "", "")
btn.set_click_callback(lambda f=folder: self._select_folder(f))
folder_buttons.append(btn)
self._show_selection_view(folder_buttons, self._reset_main_view)
def _select_model(self, bundle):
ui_state.params.put("ModelManager_DownloadIndex", bundle.index)
self._reset_main_view()
def _select_default(self):
ui_state.params.remove("ModelManager_ActiveBundle")
self._reset_main_view()
def _select_folder(self, folder_name):
folders = self._get_grouped_bundles()
bundles = sorted(folders.get(folder_name, []), key=lambda b: b.index, reverse=True)
btns = []
for bundle in bundles:
txt = bundle.displayName.lower()
btn = BigButton(txt, "", "")
btn.set_click_callback(lambda b=bundle: self._select_model(b))
btns.append(btn)
self._show_selection_view(btns, self._show_folders)
def _reset_main_view(self):
self._scroller._items = self.main_items
self.set_back_callback(self.original_back_callback)
if self.focused_widget and self.focused_widget in self.main_items:
x = self._scroller._pad_start
for item in self.main_items:
if not item.is_visible:
continue
if item == self.focused_widget:
break
x += item.rect.width + self._scroller._spacing
self._scroller.scroll_panel.set_offset(0)
self._scroller.scroll_to(x)
self.focused_widget = None
else:
self._scroller.scroll_panel.set_offset(0)
def _update_state(self):
super()._update_state()
manager = self.model_manager
if manager.selectedBundle and manager.selectedBundle.status == custom.ModelManagerSP.DownloadStatus.downloading:
self.current_model_btn.set_value("downloading...")
self.cancel_download_btn.set_visible(True)
else:
self.current_model_btn.set_value(manager.activeBundle.internalName.lower() if manager.activeBundle else tr("default model"))
self.cancel_download_btn.set_visible(False)
self.current_model_btn.set_enabled(ui_state.is_offroad())
self.current_model_btn.set_text(tr("current model"))
def _render(self, rect):
self._scroller.render(rect)
def show_event(self):
super().show_event()
self._scroller.show_event()
@@ -9,6 +9,7 @@ from enum import IntEnum
from openpilot.selfdrive.ui.mici.layouts.settings import settings as OP
from openpilot.selfdrive.ui.mici.widgets.button import BigButton
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.sunnylink import SunnylinkLayoutMici
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.models import ModelsLayoutMici
ICON_SIZE = 70
@@ -16,6 +17,7 @@ OP.PanelType = IntEnum(
"PanelType",
[es.name for es in OP.PanelType] + [
"SUNNYLINK",
"MODELS",
],
start=0,
)
@@ -27,13 +29,19 @@ class SettingsLayoutSP(OP.SettingsLayout):
sunnylink_btn = BigButton("sunnylink", "", "icons_mici/settings/developer/ssh.png")
sunnylink_btn.set_click_callback(lambda: self._set_current_panel(OP.PanelType.SUNNYLINK))
models_btn = BigButton("models", "", "../../sunnypilot/selfdrive/assets/offroad/icon_models.png")
models_btn.set_click_callback(lambda: self._set_current_panel(OP.PanelType.MODELS))
self._panels.update({
OP.PanelType.SUNNYLINK: OP.PanelInfo("sunnylink", SunnylinkLayoutMici(back_callback=lambda: self._set_current_panel(None))),
OP.PanelType.MODELS: OP.PanelInfo("models", ModelsLayoutMici(back_callback=lambda: self._set_current_panel(None))),
})
items = self._scroller._items.copy()
items.insert(1, sunnylink_btn)
items.insert(2, models_btn)
self._scroller._items.clear()
for item in items:
self._scroller.add_widget(item)
@@ -5,9 +5,27 @@ This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
from openpilot.selfdrive.ui.ui_state import UIStatus
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.selfdrive.ui.ui_state import UIStatus, ui_state
from openpilot.system.ui.lib.application import gui_app
BORDER_COLORS_SP = {
UIStatus.LAT_ONLY: rl.Color(0x00, 0xC8, 0xC8, 0xFF), # Cyan for lateral-only state
UIStatus.LONG_ONLY: rl.Color(0x96, 0x1C, 0xA8, 0xFF), # Purple for longitudinal-only state
}
class AugmentedRoadViewSP:
def __init__(self):
self._fade_texture = gui_app.texture("icons_mici/onroad/onroad_fade.png")
self._fade_alpha_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps)
def update_fade_out_bottom_overlay(self, _content_rect):
# Fade out bottom of overlays for looks (only when engaged)
fade_alpha = self._fade_alpha_filter.update(ui_state.status != UIStatus.DISENGAGED)
if ui_state.torque_bar and fade_alpha > 1e-2:
# Scale the fade texture to the content rect
rl.draw_texture_pro(self._fade_texture,
rl.Rectangle(0, 0, self._fade_texture.width, self._fade_texture.height),
_content_rect, rl.Vector2(0, 0), 0.0,
rl.Color(255, 255, 255, int(255 * fade_alpha)))
@@ -31,6 +31,9 @@ class BlindSpotIndicators:
return self._blind_spot_left_alpha_filter.x > 0.01 or self._blind_spot_right_alpha_filter.x > 0.01
def render(self, rect: rl.Rectangle) -> None:
if not ui_state.blindspot:
return
BLIND_SPOT_MARGIN_X = 20 # Distance from edge of screen
BLIND_SPOT_Y_OFFSET = 100 # Distance from top of screen
@@ -0,0 +1,140 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
from cereal import log
from openpilot.selfdrive.ui import UI_BORDER_SIZE
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer
from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE
from openpilot.system.ui.lib.text_measure import measure_text_cached
class CircularAlertsRenderer:
def __init__(self):
self._green_light_alert_img = gui_app.texture("../../sunnypilot/selfdrive/assets/images/green_light.png", 250, 250)
self._lead_depart_alert_img = gui_app.texture("../../sunnypilot/selfdrive/assets/images/lead_depart.png", 250, 250)
self._e2e_alert_display_timer = 0
self._e2e_alert_frame = 0
self._green_light_alert = False
self._lead_depart_alert = False
self._standstill_elapsed_time = 0.0
self._is_standstill = False
self._alert_text = ""
self._alert_img = None
self._allow_e2e_alerts = False
def update(self) -> None:
sm = ui_state.sm
lp_sp = sm['longitudinalPlanSP']
car_state = sm['carState']
self._green_light_alert = lp_sp.e2eAlerts.greenLightAlert
self._lead_depart_alert = lp_sp.e2eAlerts.leadDepartAlert
self._is_standstill = car_state.standstill
if not ui_state.started:
self._standstill_elapsed_time = 0.0
self._allow_e2e_alerts = sm['selfdriveState'].alertSize == log.SelfdriveState.AlertSize.none and \
sm.recv_frame['driverStateV2'] > ui_state.started_frame
if self._green_light_alert or self._lead_depart_alert:
self._e2e_alert_display_timer = 3 * gui_app.target_fps
# reset onroad sleep timer for e2e alerts
ui_state.reset_onroad_sleep_timer()
if self._e2e_alert_display_timer > 0:
self._e2e_alert_frame += 1
self._e2e_alert_display_timer -= 1
if self._green_light_alert:
self._alert_text = "GREEN\nLIGHT"
self._alert_img = self._green_light_alert_img
elif self._lead_depart_alert:
self._alert_text = "LEAD VEHICLE\nDEPARTING"
self._alert_img = self._lead_depart_alert_img
elif ui_state.standstill_timer and self._is_standstill:
self._alert_img = None
self._standstill_elapsed_time += 1.0 / gui_app.target_fps
minute = int(self._standstill_elapsed_time / 60)
second = int(self._standstill_elapsed_time - (minute * 60))
self._alert_text = f"{minute:01d}:{second:02d}"
self._e2e_alert_frame += 1
else:
self._e2e_alert_frame = 0
if not self._is_standstill:
self._standstill_elapsed_time = 0.0
def render(self, rect: rl.Rectangle) -> None:
if not self._allow_e2e_alerts or (self._e2e_alert_display_timer <= 0 and not (ui_state.standstill_timer and self._is_standstill)):
return
e2e_alert_size = 250
dev_ui_width_adjustment = 180 if ui_state.developer_ui in (DeveloperUiRenderer.DEV_UI_RIGHT, DeveloperUiRenderer.DEV_UI_BOTH) else 100
x = rect.x + rect.width - e2e_alert_size - dev_ui_width_adjustment - (UI_BORDER_SIZE * 3)
y = rect.y + rect.height / 2 + 20
alert_rect = rl.Rectangle(x - e2e_alert_size, y - e2e_alert_size, e2e_alert_size * 2, e2e_alert_size * 2)
center = rl.Vector2(alert_rect.x + alert_rect.width / 2, alert_rect.y + alert_rect.height / 2)
# Pulse logic
is_pulsing = (self._e2e_alert_frame % gui_app.target_fps) < (gui_app.target_fps / 2.5)
# Standstill Timer (STOPPED) should be static white
if self._e2e_alert_display_timer == 0 and ui_state.standstill_timer and self._is_standstill:
frame_color = rl.Color(255, 255, 255, 75)
else:
frame_color = rl.Color(255, 255, 255, 75) if is_pulsing else rl.Color(0, 255, 0, 75)
# Draw Circle
rl.draw_circle_v(center, e2e_alert_size, rl.Color(0, 0, 0, 190))
# Draw Ring (Border)
rl.draw_ring(center, e2e_alert_size - 7.5, e2e_alert_size + 7.5, 0, 360, 0, frame_color)
# Draw Image
if self._alert_img and self._e2e_alert_display_timer > 0:
img_x = int(center.x - self._alert_img.width / 2)
img_y = int(center.y - self._alert_img.height / 2)
rl.draw_texture(self._alert_img, img_x, img_y, rl.WHITE)
# Draw Text
txt_color = rl.Color(255, 255, 255, 255) if is_pulsing else rl.Color(0, 255, 0, 190)
font = gui_app.font(FontWeight.BOLD)
text_size = 48
spacing = 0
lines = self._alert_text.split('\n')
# Position text at bottom of alert circle
bottom_y = (alert_rect.y + alert_rect.height) - (alert_rect.height / 7)
# Draw lines upwards from bottom
current_y = bottom_y - (len(lines) * text_size * FONT_SCALE)
if self._e2e_alert_display_timer == 0 and ui_state.standstill_timer and self._is_standstill:
# Standstill Timer Text
alert_alt_text = "STOPPED"
top_text_size = 80
measure_top = measure_text_cached(font, alert_alt_text, top_text_size, spacing)
top_y = alert_rect.y + alert_rect.height / 3.5
rl.draw_text_ex(font, alert_alt_text, rl.Vector2(center.x - measure_top.x / 2, top_y), top_text_size, spacing, rl.Color(255, 175, 3, 240))
# Timer
timer_text_size = 100
measure_timer = measure_text_cached(font, self._alert_text, timer_text_size, spacing)
timer_y = (alert_rect.y + alert_rect.height) - (alert_rect.height / 5) - measure_timer.y
rl.draw_text_ex(font, self._alert_text, rl.Vector2(center.x - measure_timer.x / 2, timer_y), timer_text_size, spacing, rl.WHITE)
else:
for line in lines:
measure = measure_text_cached(font, line, text_size, spacing)
line_x = center.x - measure.x / 2
rl.draw_text_ex(font, line, rl.Vector2(line_x, current_y), text_size, spacing, txt_color)
current_y += text_size * FONT_SCALE
+24 -3
View File
@@ -6,13 +6,17 @@ See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
from openpilot.selfdrive.ui.mici.onroad.torque_bar import TorqueBar
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.selfdrive.ui.onroad.hud_renderer import HudRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.road_name import RoadNameRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.rocket_fuel import RocketFuel
from openpilot.selfdrive.ui.sunnypilot.onroad.speed_limit import SpeedLimitRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.smart_cruise_control import SmartCruiseControlRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.turn_signal import TurnSignalController
from openpilot.selfdrive.ui.sunnypilot.onroad.circular_alerts import CircularAlertsRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.speed_renderer import SpeedRenderer
class HudRendererSP(HudRenderer):
@@ -22,20 +26,37 @@ class HudRendererSP(HudRenderer):
self.road_name_renderer = RoadNameRenderer()
self.rocket_fuel = RocketFuel()
self.speed_limit_renderer = SpeedLimitRenderer()
self.smart_cruise_control_renderer = SmartCruiseControlRenderer()
self.turn_signal_controller = TurnSignalController()
self.circular_alerts_renderer = CircularAlertsRenderer()
self.speed_renderer = SpeedRenderer()
self._torque_bar = TorqueBar(scale=3.0, always=True)
def _update_state(self) -> None:
super()._update_state()
self.road_name_renderer.update()
self.speed_limit_renderer.update()
self.smart_cruise_control_renderer.update()
self.turn_signal_controller.update()
self.circular_alerts_renderer.update()
self.speed_renderer.update()
def _draw_current_speed(self, rect: rl.Rectangle) -> None:
self.speed_renderer.render(rect)
def _render(self, rect: rl.Rectangle) -> None:
super()._render(rect)
if ui_state.torque_bar and ui_state.sm['controlsState'].lateralControlState.which() != 'angleState':
torque_rect = rect
if ui_state.developer_ui in (DeveloperUiRenderer.DEV_UI_BOTTOM, DeveloperUiRenderer.DEV_UI_BOTH):
torque_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height - DeveloperUiRenderer.BOTTOM_BAR_HEIGHT)
self._torque_bar.render(torque_rect)
self.developer_ui.render(rect)
self.road_name_renderer.render(rect)
self.speed_limit_renderer.render(rect)
self.smart_cruise_control_renderer.render(rect)
self.turn_signal_controller.render(rect)
if ui_state.rocket_fuel:
self.rocket_fuel.render(rect, ui_state.sm)
self.circular_alerts_renderer.render(rect)
self.rocket_fuel.render(rect, ui_state.sm)
+1 -1
View File
@@ -31,7 +31,7 @@ class RoadNameRenderer(Widget):
self.road_name = lmd.roadName
def _render(self, rect: rl.Rectangle):
if not self.road_name:
if not self.road_name or not ui_state.road_name_toggle:
return
text = self.road_name
@@ -6,12 +6,17 @@ See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
from openpilot.selfdrive.ui.ui_state import ui_state
class RocketFuel:
def __init__(self):
self.vc_accel = 0.0
def render(self, rect: rl.Rectangle, sm) -> None:
if not ui_state.rocket_fuel:
return
vc_accel0 = sm['carState'].aEgo
# Smooth the acceleration
@@ -0,0 +1,131 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
from openpilot.selfdrive.ui.onroad.hud_renderer import COLORS
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.widgets import Widget
class SmartCruiseControlRenderer(Widget):
def __init__(self):
super().__init__()
self.vision_enabled = False
self.vision_active = False
self.vision_frame = 0
self.map_enabled = False
self.map_active = False
self.map_frame = 0
self.long_override = False
self.font = gui_app.font(FontWeight.BOLD)
self.scc_tex = rl.load_render_texture(256, 128)
def update(self):
sm = ui_state.sm
if sm.updated["longitudinalPlanSP"]:
lp_sp = sm["longitudinalPlanSP"]
vision = lp_sp.smartCruiseControl.vision
map_ = lp_sp.smartCruiseControl.map
self.vision_enabled = vision.enabled
self.vision_active = vision.active
self.map_enabled = map_.enabled
self.map_active = map_.active
if sm.updated["carControl"]:
self.long_override = sm["carControl"].cruiseControl.override
if self.vision_active:
self.vision_frame += 1
else:
self.vision_frame = 0
if self.map_active:
self.map_frame += 1
else:
self.map_frame = 0
@staticmethod
def _pulse_element(frame):
return not (frame % gui_app.target_fps < (gui_app.target_fps / 2.5))
def _draw_icon(self, rect_center_x, rect_height, x_offset, y_offset, name):
text = name
font_size = 36
padding_v = 5
box_width = 160
sz = measure_text_cached(self.font, text, font_size)
box_height = int(sz.y + padding_v * 2)
texture_width = 256
texture_height = 128
rl.begin_texture_mode(self.scc_tex)
rl.clear_background(rl.Color(0, 0, 0, 0))
if self.long_override:
box_color = COLORS.OVERRIDE
else:
box_color = rl.Color(0, 255, 0, 255)
# Center box in texture
box_x = (texture_width - box_width) // 2
box_y = (texture_height - box_height) // 2
rl.draw_rectangle_rounded(rl.Rectangle(box_x, box_y, box_width, box_height), 0.2, 10, box_color)
# Draw text with custom blend mode to punch hole
rl.rl_set_blend_factors(rl.RL_ZERO, rl.RL_ONE_MINUS_SRC_ALPHA, 0x8006)
rl.rl_set_blend_mode(rl.BLEND_CUSTOM)
text_pos_x = box_x + (box_width - sz.x) / 2
text_pos_y = box_y + (box_height - sz.y) / 2
rl.draw_text_ex(self.font, text, rl.Vector2(text_pos_x, text_pos_y), font_size, 0, rl.WHITE)
rl.rl_set_blend_mode(rl.BLEND_ALPHA) # Reset
rl.end_texture_mode()
screen_y = rect_height / 4 + y_offset
dest_x = rect_center_x + x_offset - texture_width / 2
dest_y = screen_y - texture_height / 2
src_rect = rl.Rectangle(0, 0, texture_width, -texture_height)
dst_rect = rl.Rectangle(dest_x, dest_y, texture_width, texture_height)
rl.draw_texture_pro(self.scc_tex.texture, src_rect, dst_rect, rl.Vector2(0, 0), 0, rl.WHITE)
def _render(self, rect: rl.Rectangle):
x_offset = -260
y1_offset = -40
y2_offset = -100
orders = [y1_offset, y2_offset]
y_scc_v = 0
y_scc_m = 0
idx = 0
if self.vision_enabled:
y_scc_v = orders[idx]
idx += 1
if self.map_enabled:
y_scc_m = orders[idx]
idx += 1
scc_vision_pulse = self._pulse_element(self.vision_frame)
if (self.vision_enabled and not self.vision_active) or (self.vision_active and scc_vision_pulse):
self._draw_icon(rect.x + rect.width / 2, rect.height, x_offset, y_scc_v, "SCC-V")
scc_map_pulse = self._pulse_element(self.map_frame)
if (self.map_enabled and not self.map_active) or (self.map_active and scc_map_pulse):
self._draw_icon(rect.x + rect.width / 2, rect.height, x_offset, y_scc_m, "SCC-M")
@@ -0,0 +1,46 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
from openpilot.common.constants import CV
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.selfdrive.ui.onroad.hud_renderer import FONT_SIZES, COLORS
class SpeedRenderer:
def __init__(self):
self.speed: float = 0.0
self.v_ego_cluster_seen: bool = False
self._font_bold: rl.Font = gui_app.font(FontWeight.BOLD)
self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM)
def update(self) -> None:
car_state = ui_state.sm['carState']
v_ego_cluster = car_state.vEgoCluster
self.v_ego_cluster_seen = self.v_ego_cluster_seen or v_ego_cluster != 0.0
v_ego = v_ego_cluster if self.v_ego_cluster_seen and not ui_state.true_v_ego_ui else car_state.vEgo
speed_conversion = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH
self.speed = max(0.0, v_ego * speed_conversion)
def render(self, rect: rl.Rectangle) -> None:
if ui_state.hide_v_ego_ui:
return
# Draw current speed and unit
speed_text = str(round(self.speed))
speed_text_size = measure_text_cached(self._font_bold, speed_text, FONT_SIZES.current_speed)
speed_pos = rl.Vector2(rect.x + rect.width / 2 - speed_text_size.x / 2, 180 - speed_text_size.y / 2)
rl.draw_text_ex(self._font_bold, speed_text, speed_pos, FONT_SIZES.current_speed, 0, COLORS.WHITE)
unit_text = tr("km/h") if ui_state.is_metric else tr("mph")
unit_text_size = measure_text_cached(self._font_medium, unit_text, FONT_SIZES.speed_unit)
unit_pos = rl.Vector2(rect.x + rect.width / 2 - unit_text_size.x / 2, 290 - unit_text_size.y / 2)
rl.draw_text_ex(self._font_medium, unit_text, unit_pos, FONT_SIZES.speed_unit, 0, COLORS.WHITE_TRANSLUCENT)
@@ -137,6 +137,9 @@ class TurnSignalController:
self._right_signal.deactivate()
def render(self, rect: rl.Rectangle):
if not ui_state.turn_signals:
return
x = rect.x + rect.width / 2
left_x = x - self._config.left_x - self._config.size
+13 -8
View File
@@ -120,18 +120,23 @@ class UIStateSP:
CP_SP_bytes = self.params.get("CarParamsSPPersistent")
if CP_SP_bytes is not None:
self.CP_SP = messaging.log_from_bytes(CP_SP_bytes, custom.CarParamsSP)
self.sunnylink_enabled = self.params.get_bool("SunnylinkEnabled")
self.developer_ui = self.params.get("DevUIInfo")
self.rocket_fuel = self.params.get_bool("RocketFuel")
self.rainbow_path = self.params.get_bool("RainbowMode")
self.chevron_metrics = self.params.get("ChevronInfo")
self.active_bundle = self.params.get("ModelManager_ActiveBundle")
self.blindspot = self.params.get_bool("BlindSpot")
self.chevron_metrics = self.params.get("ChevronInfo")
self.custom_interactive_timeout = self.params.get("InteractivityTimeout", return_default=True)
self.speed_limit_mode = self.params.get("SpeedLimitMode", return_default=True)
# Onroad Screen Brightness
self.developer_ui = self.params.get("DevUIInfo")
self.hide_v_ego_ui = self.params.get_bool("HideVEgoUI")
self.onroad_brightness = int(float(self.params.get("OnroadScreenOffBrightness", return_default=True)))
self.onroad_brightness_timer_param = self.params.get("OnroadScreenOffTimer", return_default=True)
self.rainbow_path = self.params.get_bool("RainbowMode")
self.road_name_toggle = self.params.get_bool("RoadNameToggle")
self.rocket_fuel = self.params.get_bool("RocketFuel")
self.speed_limit_mode = self.params.get("SpeedLimitMode", return_default=True)
self.standstill_timer = self.params.get_bool("StandstillTimer")
self.sunnylink_enabled = self.params.get_bool("SunnylinkEnabled")
self.torque_bar = self.params.get_bool("TorqueBar")
self.true_v_ego_ui = self.params.get_bool("TrueVEgoUI")
self.turn_signals = self.params.get_bool("ShowTurnSignals")
class DeviceSP:
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e095cfc4de71788bd4a99699a2e7ab4098cd426277d672b9e43981c5fab8b40f
size 16407
+22 -18
View File
@@ -90,8 +90,8 @@
"description": ""
},
"BlindSpot": {
"title": "Blind Spot Detection",
"description": ""
"title": "[TIZI/TICI only] Blind Spot Detection",
"description": "Enabling this will display warnings when a vehicle is detected in your blind spot as long as your car has BSM supported."
},
"BlinkerMinLateralControlSpeed": {
"title": "Blinker Min Lateral Control Speed",
@@ -339,8 +339,8 @@
"description": ""
},
"GreenLightAlert": {
"title": "Green Light Alert",
"description": ""
"title": "Green Traffic Light Alert (Beta)",
"description": "A chime and on-screen alert (TIZI/TICI only) will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. <br>Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly."
},
"GsmApn": {
"title": "GSM APN",
@@ -367,8 +367,8 @@
"description": ""
},
"HideVEgoUI": {
"title": "Hide vEgo UI",
"description": ""
"title": "[TIZI/TICI only] Speedometer: Hide from Onroad Screen",
"description": "When enabled, the speedometer on the onroad screen is not displayed."
},
"HyundaiLongitudinalTuning": {
"title": "Hyundai Longitudinal Tuning",
@@ -585,8 +585,8 @@
"description": ""
},
"LeadDepartAlert": {
"title": "Lead Depart Alert",
"description": ""
"title": "Lead Departure Alert (Beta)",
"description": "A chime and on-screen alert (TIZI/TICI only) will play when you are stopped, and the vehicle in front of you start moving. <br>Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly."
},
"LiveDelay": {
"title": "Live Delay",
@@ -1079,12 +1079,12 @@
"description": ""
},
"RoadNameToggle": {
"title": "Display Road Name",
"description": ""
"title": "[TIZI/TICI only] Display Road Name",
"description": "Displays the name of the road the car is traveling on. <br>The OpenStreetMap database of the location must be downloaded to fetch the road name."
},
"RocketFuel": {
"title": "Display Rocket Fuel Bar",
"description": "Show an indicator on the left side of the screen to display real-time vehicle acceleration and deceleration."
"title": "[TIZI/TICI only] Real-time Acceleration Bar",
"description": "Show an indicator on the left side of the screen to display real-time vehicle acceleration and deceleration. This displays what the car is currently doing, not what the planner is requesting."
},
"RouteCount": {
"title": "Route Count",
@@ -1103,8 +1103,8 @@
"description": ""
},
"ShowTurnSignals": {
"title": "Show Turn Signals",
"description": ""
"title": "[TIZI/TICI only] Display Turn Signals",
"description": "When enabled, visual turn indicators are drawn on the HUD."
},
"SmartCruiseControlMap": {
"title": "Smart Cruise Control - Map",
@@ -1196,8 +1196,8 @@
"description": ""
},
"StandstillTimer": {
"title": "Standstill Timer",
"description": ""
"title": "[TIZI/TICI only] Standstill Timer",
"description": "Show a timer on the HUD when the car is at a standstill."
},
"SubaruStopAndGo": {
"title": "Subaru Stop and Go",
@@ -1239,6 +1239,10 @@
"title": "Tesla Coop Steering",
"description": ""
},
"TorqueBar": {
"title": "[TIZI/TICI only] Steering Arc",
"description": "Display steering arc on the driving screen when lateral control is enabled."
},
"TorqueParamsOverrideEnabled": {
"title": "Manual Real-Time Tuning",
"description": ""
@@ -1267,8 +1271,8 @@
"description": ""
},
"TrueVEgoUI": {
"title": "True vEgo UI",
"description": ""
"title": "[TIZI/TICI only] Speedometer: Always Display True Speed",
"description": "For applicable vehicles, always display the true vehicle current speed from wheel speed sensors."
},
"UbloxAvailable": {
"title": "Ublox Available",
+7
View File
@@ -20,6 +20,7 @@ from openpilot.system.athena.registration import register, UNREGISTERED_DONGLE_I
from openpilot.common.swaglog import cloudlog, add_file_handler
from openpilot.system.version import get_build_metadata
from openpilot.system.hardware.hw import Paths
from openpilot.system.hardware import PC
def manager_init() -> None:
@@ -39,6 +40,12 @@ def manager_init() -> None:
if params.get("DeviceBootMode") == 1: # start in Always Offroad mode
params.put_bool("OffroadMode", True)
# quick boot
if params.get_bool("QuickBootToggle") and not PC:
prebuilt_path = "/data/openpilot/prebuilt"
if not os.path.exists(prebuilt_path):
open(prebuilt_path, 'x').close()
if params.get_bool("RecordFrontLock"):
params.put_bool("RecordFront", True)