mirror of
https://github.com/infiniteCable2/openpilot.git
synced 2026-08-05 00:05:57 +08:00
Merge branch 'master' of https://github.com/sunnypilot/sunnypilot into sync
This commit is contained in:
@@ -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"}},
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user