mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-15 06:33:43 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 89a3a953b2 |
+118
-3
@@ -288,6 +288,7 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
|
||||
sccVision @1;
|
||||
sccMap @2;
|
||||
speedLimitAssist @3;
|
||||
mapd @4;
|
||||
}
|
||||
|
||||
struct E2eAlerts {
|
||||
@@ -475,11 +476,125 @@ struct CustomReserved15 @0xbd443b539493bc68 {
|
||||
struct CustomReserved16 @0xfc6241ed8877b611 {
|
||||
}
|
||||
|
||||
struct CustomReserved17 @0xa30662f84033036c {
|
||||
struct MapdDownloadLocationDetails @0xff889853e7b0987f {
|
||||
location @0 :Text;
|
||||
totalFiles @1 :UInt32;
|
||||
downloadedFiles @2 :UInt32;
|
||||
}
|
||||
|
||||
struct CustomReserved18 @0xc86a3d38d13eb3ef {
|
||||
struct MapdDownloadProgress @0xfaa35dcac85073a2 {
|
||||
active @0 :Bool;
|
||||
cancelled @1 :Bool;
|
||||
totalFiles @2 :UInt32;
|
||||
downloadedFiles @3 :UInt32;
|
||||
locations @4 :List(Text);
|
||||
locationDetails @5 :List(MapdDownloadLocationDetails);
|
||||
}
|
||||
|
||||
struct CustomReserved19 @0xa4f1eb3323f5f582 {
|
||||
struct MapdPathPoint @0xd6f78acca1bc3939 {
|
||||
latitude @0 :Float64;
|
||||
longitude @1 :Float64;
|
||||
curvature @2 :Float32;
|
||||
targetVelocity @3 :Float32;
|
||||
}
|
||||
|
||||
struct MapdExtendedOut @0xa30662f84033036c {
|
||||
downloadProgress @0 :MapdDownloadProgress;
|
||||
settings @1 :Text;
|
||||
path @2 :List(MapdPathPoint);
|
||||
}
|
||||
|
||||
enum MapdInputType {
|
||||
download @0;
|
||||
setTargetLateralAccel @1;
|
||||
setSpeedLimitOffset @2;
|
||||
setSpeedLimitControl @3;
|
||||
setMapCurveSpeedControl @4;
|
||||
setVisionCurveSpeedControl @5;
|
||||
setLogLevel @6;
|
||||
setVisionCurveTargetLatA @7;
|
||||
setVisionCurveMinTargetV @8;
|
||||
reloadSettings @9;
|
||||
saveSettings @10;
|
||||
setEnableSpeed @11;
|
||||
setVisionCurveUseEnableSpeed @12;
|
||||
setMapCurveUseEnableSpeed @13;
|
||||
setSpeedLimitUseEnableSpeed @14;
|
||||
setHoldLastSeenSpeedLimit @15;
|
||||
setTargetSpeedJerk @16;
|
||||
setTargetSpeedAccel @17;
|
||||
setTargetSpeedTimeOffset @18;
|
||||
setDefaultLaneWidth @19;
|
||||
setMapCurveTargetLatA @20;
|
||||
loadDefaultSettings @21;
|
||||
loadRecommendedSettings @22;
|
||||
setSlowDownForNextSpeedLimit @23;
|
||||
setSpeedUpForNextSpeedLimit @24;
|
||||
setHoldSpeedLimitWhileChangingSetSpeed @25;
|
||||
loadPersistentSettings @26;
|
||||
cancelDownload @27;
|
||||
setLogJson @28;
|
||||
setLogSource @29;
|
||||
setExternalSpeedLimitControl @30;
|
||||
setExternalSpeedLimit @31;
|
||||
setSpeedLimitPriority @32;
|
||||
setSpeedLimitChangeRequiresAccept @33;
|
||||
acceptSpeedLimit @34;
|
||||
setPressGasToAcceptSpeedLimit @35;
|
||||
setAdjustSetSpeedToAcceptSpeedLimit @36;
|
||||
setAcceptSpeedLimitTimeout @37;
|
||||
setPressGasToOverrideSpeedLimit @38;
|
||||
}
|
||||
|
||||
enum WaySelectionType {
|
||||
current @0;
|
||||
predicted @1;
|
||||
possible @2;
|
||||
extended @3;
|
||||
fail @4;
|
||||
}
|
||||
|
||||
enum SpeedLimitOffsetType {
|
||||
static @0;
|
||||
percent @1;
|
||||
}
|
||||
|
||||
struct MapdIn @0xc86a3d38d13eb3ef {
|
||||
type @0 :MapdInputType;
|
||||
float @1 :Float32;
|
||||
str @2 :Text;
|
||||
bool @3 :Bool;
|
||||
}
|
||||
|
||||
enum RoadContext {
|
||||
freeway @0;
|
||||
city @1;
|
||||
unknown @2;
|
||||
}
|
||||
|
||||
struct MapdOut @0xa4f1eb3323f5f582 {
|
||||
wayName @0 :Text;
|
||||
wayRef @1 :Text;
|
||||
roadName @2 :Text;
|
||||
speedLimit @3 :Float32;
|
||||
nextSpeedLimit @4 :Float32;
|
||||
nextSpeedLimitDistance @5 :Float32;
|
||||
hazard @6 :Text;
|
||||
nextHazard @7 :Text;
|
||||
nextHazardDistance @8 :Float32;
|
||||
advisorySpeed @9 :Float32;
|
||||
nextAdvisorySpeed @10 :Float32;
|
||||
nextAdvisorySpeedDistance @11 :Float32;
|
||||
oneWay @12 :Bool;
|
||||
lanes @13 :UInt8;
|
||||
tileLoaded @14 :Bool;
|
||||
speedLimitSuggestedSpeed @15 :Float32;
|
||||
suggestedSpeed @16 :Float32;
|
||||
estimatedRoadWidth @17 :Float32;
|
||||
roadContext @18 :RoadContext;
|
||||
distanceFromWayCenter @19 :Float32;
|
||||
visionCurveSpeed @20 :Float32;
|
||||
mapCurveSpeed @21 :Float32;
|
||||
waySelectionType @22 :WaySelectionType;
|
||||
speedLimitAccepted @23 :Bool;
|
||||
}
|
||||
|
||||
+3
-3
@@ -2642,9 +2642,9 @@ struct Event {
|
||||
customReserved14 @140 :Custom.CustomReserved14;
|
||||
customReserved15 @141 :Custom.CustomReserved15;
|
||||
customReserved16 @142 :Custom.CustomReserved16;
|
||||
customReserved17 @143 :Custom.CustomReserved17;
|
||||
customReserved18 @144 :Custom.CustomReserved18;
|
||||
customReserved19 @145 :Custom.CustomReserved19;
|
||||
mapdExtendedOut @143 :Custom.MapdExtendedOut;
|
||||
mapdIn @144 :Custom.MapdIn;
|
||||
mapdOut @145 :Custom.MapdOut;
|
||||
|
||||
# *********** legacy + deprecated ***********
|
||||
model @9 :Legacy.ModelData; # TODO: rename modelV2 and mark this as deprecated
|
||||
|
||||
+1
-1
@@ -98,9 +98,9 @@ _services: dict[str, tuple] = {
|
||||
"carParamsSP": (True, 0.02, 1),
|
||||
"carControlSP": (True, 100., 10),
|
||||
"carStateSP": (True, 100., 10),
|
||||
"liveMapDataSP": (True, 1., 1),
|
||||
"modelDataV2SP": (True, 20., None, QueueSize.BIG),
|
||||
"liveLocationKalman": (True, 20.),
|
||||
"mapdOut": (True, 20., 20, QueueSize.MEDIUM),
|
||||
|
||||
# debug
|
||||
"uiDebug": (True, 0., 1),
|
||||
|
||||
+3
-32
@@ -193,6 +193,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
||||
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
|
||||
|
||||
{"MapdSettings", {PERSISTENT, JSON}},
|
||||
{"Offroad_OSMUpdateRequired", {CLEAR_ON_MANAGER_START, JSON}},
|
||||
|
||||
// Neural Network Lateral Control
|
||||
{"NeuralNetworkLateralControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
@@ -229,38 +232,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"LaneTurnValue", {PERSISTENT | BACKUP, FLOAT, "19.0"}},
|
||||
{"PlanplusControl", {PERSISTENT | BACKUP, FLOAT, "1.0"}},
|
||||
|
||||
// mapd
|
||||
{"MapAdvisorySpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT}},
|
||||
{"MapdVersion", {PERSISTENT, STRING}},
|
||||
{"MapSpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT, "0.0"}},
|
||||
{"NextMapSpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
||||
{"Offroad_OSMUpdateRequired", {CLEAR_ON_MANAGER_START, JSON}},
|
||||
{"OsmDbUpdatesCheck", {CLEAR_ON_MANAGER_START, BOOL}}, // mapd database update happens with device ON, reset on boot
|
||||
{"OSMDownloadBounds", {PERSISTENT, STRING}},
|
||||
{"OsmDownloadedDate", {PERSISTENT, STRING, "0.0"}},
|
||||
{"OSMDownloadLocations", {PERSISTENT, JSON}},
|
||||
{"OSMDownloadProgress", {CLEAR_ON_MANAGER_START, JSON}},
|
||||
{"OsmLocal", {PERSISTENT, BOOL}},
|
||||
{"OsmLocationName", {PERSISTENT, STRING}},
|
||||
{"OsmLocationTitle", {PERSISTENT, STRING}},
|
||||
{"OsmLocationUrl", {PERSISTENT, STRING}},
|
||||
{"OsmStateName", {PERSISTENT, STRING, "All"}},
|
||||
{"OsmStateTitle", {PERSISTENT, STRING}},
|
||||
{"OsmWayTest", {PERSISTENT, STRING}},
|
||||
{"RoadName", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
||||
{"RoadNameToggle", {PERSISTENT, STRING}},
|
||||
|
||||
// Speed Limit
|
||||
{"SpeedLimitMode", {PERSISTENT | BACKUP, INT, "1"}},
|
||||
{"SpeedLimitOffsetType", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
{"SpeedLimitPolicy", {PERSISTENT | BACKUP, INT, "3"}},
|
||||
{"SpeedLimitValueOffset", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
|
||||
// Smart Cruise Control
|
||||
{"MapTargetVelocities", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
||||
{"SmartCruiseControlMap", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"SmartCruiseControlVision", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
// Torque lateral control custom params
|
||||
{"CustomTorqueParams", {PERSISTENT | BACKUP , BOOL}},
|
||||
{"EnforceTorqueControl", {PERSISTENT | BACKUP, BOOL}},
|
||||
|
||||
@@ -27,12 +27,11 @@ def main():
|
||||
longitudinal_planner = LongitudinalPlanner(CP, CP_SP)
|
||||
pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP'])
|
||||
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState',
|
||||
'liveMapDataSP', 'carStateSP', gps_location_service],
|
||||
'carStateSP', 'mapdOut', gps_location_service],
|
||||
poll='carState')
|
||||
|
||||
while True:
|
||||
sm.update()
|
||||
longitudinal_planner.sla.update_car_state(sm['carState'])
|
||||
if sm.updated['modelV2']:
|
||||
longitudinal_planner.update(sm)
|
||||
longitudinal_planner.publish(sm, pm)
|
||||
|
||||
Executable
BIN
Binary file not shown.
@@ -4,6 +4,7 @@ import cereal.messaging as messaging
|
||||
from openpilot.selfdrive.ui.mici.layouts.home import MiciHomeLayout
|
||||
from openpilot.selfdrive.ui.mici.layouts.settings.settings import SettingsLayout
|
||||
from openpilot.selfdrive.ui.mici.layouts.offroad_alerts import MiciOffroadAlerts
|
||||
from openpilot.selfdrive.ui.mici.layouts.mapd_panel import MapdInfoPanel
|
||||
from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView
|
||||
from openpilot.selfdrive.ui.ui_state import device, ui_state
|
||||
from openpilot.selfdrive.ui.mici.layouts.onboarding import OnboardingWindow
|
||||
@@ -40,17 +41,19 @@ class MiciMainLayout(Widget):
|
||||
self._alerts_layout = MiciOffroadAlerts()
|
||||
self._settings_layout = SettingsLayout()
|
||||
self._onroad_layout = AugmentedRoadView(bookmark_callback=self._on_bookmark_clicked)
|
||||
self._mapd_panel = MapdInfoPanel()
|
||||
|
||||
# Initialize widget rects
|
||||
for widget in (self._home_layout, self._settings_layout, self._alerts_layout, self._onroad_layout):
|
||||
for widget in (self._home_layout, self._settings_layout, self._alerts_layout, self._onroad_layout, self._mapd_panel):
|
||||
# TODO: set parent rect and use it if never passed rect from render (like in Scroller)
|
||||
widget.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
|
||||
|
||||
self._scroller = Scroller([
|
||||
self._alerts_layout,
|
||||
self._home_layout,
|
||||
self._onroad_layout,
|
||||
], spacing=0, pad_start=0, pad_end=0)
|
||||
self._alerts_layout,
|
||||
self._home_layout,
|
||||
self._mapd_panel,
|
||||
self._onroad_layout,
|
||||
], spacing=0, pad_start=0, pad_end=0)
|
||||
self._scroller.set_reset_scroll_at_show(False)
|
||||
|
||||
# Disable scrolling when onroad is interacting with bookmark
|
||||
@@ -107,6 +110,14 @@ class MiciMainLayout(Widget):
|
||||
self._layouts[mode].show_event()
|
||||
self._current_mode = mode
|
||||
|
||||
def _is_on_side_panel(self) -> bool:
|
||||
onroad_x = self._onroad_layout.rect.x
|
||||
current_scroll = self._scroller.scroll_panel.get_offset()
|
||||
return abs(current_scroll - onroad_x) > self._rect.width / 2
|
||||
|
||||
def _is_on_mapd_panel(self) -> bool:
|
||||
return self._is_on_side_panel()
|
||||
|
||||
def _handle_transitions(self):
|
||||
if ui_state.started != self._prev_onroad:
|
||||
self._prev_onroad = ui_state.started
|
||||
@@ -123,15 +134,16 @@ class MiciMainLayout(Widget):
|
||||
|
||||
CS = ui_state.sm["carState"]
|
||||
if not CS.standstill and self._prev_standstill:
|
||||
self._set_mode(MainState.MAIN)
|
||||
self._scroll_to(self._onroad_layout)
|
||||
if not self._is_on_mapd_panel():
|
||||
self._set_mode(MainState.MAIN)
|
||||
self._scroll_to(self._onroad_layout)
|
||||
self._prev_standstill = CS.standstill
|
||||
|
||||
def _set_mode_for_started(self, onroad_transition: bool = False):
|
||||
if ui_state.started:
|
||||
CS = ui_state.sm["carState"]
|
||||
# Only go onroad if car starts or is not at a standstill
|
||||
if not CS.standstill or onroad_transition:
|
||||
if (not CS.standstill or onroad_transition) and not self._is_on_mapd_panel():
|
||||
self._set_mode(MainState.MAIN)
|
||||
self._scroll_to(self._onroad_layout)
|
||||
else:
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
"""
|
||||
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 dataclasses import dataclass
|
||||
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.system.ui.widgets import Widget
|
||||
|
||||
METER_TO_KM = 0.001
|
||||
METER_TO_MILE = 0.000621371
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MapdPanelColors:
|
||||
white: rl.Color = rl.WHITE
|
||||
black: rl.Color = rl.BLACK
|
||||
red: rl.Color = rl.Color(255, 0, 0, 255)
|
||||
green: rl.Color = rl.Color(0, 255, 0, 255)
|
||||
grey: rl.Color = rl.Color(145, 155, 149, 241)
|
||||
light_grey: rl.Color = rl.Color(200, 200, 200, 255)
|
||||
dark_grey: rl.Color = rl.Color(100, 100, 100, 255)
|
||||
bg_dark: rl.Color = rl.Color(30, 30, 30, 255)
|
||||
card_bg: rl.Color = rl.Color(50, 50, 50, 200)
|
||||
|
||||
COLORS = MapdPanelColors()
|
||||
|
||||
class MapdInfoPanel(Widget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.speed_limit: float = 0.0
|
||||
self.speed_limit_valid: bool = False
|
||||
self.next_speed_limit: float = 0.0
|
||||
self.next_speed_limit_distance: float = 0.0
|
||||
self.road_name: str = ""
|
||||
self.current_speed: float = 0.0
|
||||
self.lanes: int = 0
|
||||
self.road_context: str = ""
|
||||
self.hazard: str = ""
|
||||
self.vision_curve_speed: float = 0.0
|
||||
self.map_curve_speed: float = 0.0
|
||||
self.tile_loaded: bool = False
|
||||
|
||||
self._font_bold: rl.Font = gui_app.font(FontWeight.BOLD)
|
||||
self._font_semi_bold: rl.Font = gui_app.font(FontWeight.SEMI_BOLD)
|
||||
self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM)
|
||||
|
||||
def _update_state(self) -> None:
|
||||
sm = ui_state.sm
|
||||
speed_conv = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH
|
||||
if sm.valid["mapdOut"]:
|
||||
mapd = sm["mapdOut"]
|
||||
self.speed_limit = mapd.speedLimit * speed_conv
|
||||
self.speed_limit_valid = mapd.speedLimit > 0
|
||||
self.next_speed_limit = mapd.nextSpeedLimit * speed_conv
|
||||
self.next_speed_limit_distance = mapd.nextSpeedLimitDistance
|
||||
self.road_name = mapd.roadName
|
||||
self.lanes = mapd.lanes
|
||||
self.hazard = mapd.hazard
|
||||
self.vision_curve_speed = mapd.visionCurveSpeed * speed_conv
|
||||
self.map_curve_speed = mapd.mapCurveSpeed * speed_conv
|
||||
self.tile_loaded = mapd.tileLoaded
|
||||
|
||||
road_ctx = mapd.roadContext
|
||||
if hasattr(road_ctx, 'raw'):
|
||||
ctx_val = road_ctx.raw
|
||||
else:
|
||||
ctx_val = int(road_ctx)
|
||||
self.road_context = ["Freeway", "City", "Unknown"][ctx_val] if ctx_val < 3 else "Unknown"
|
||||
else:
|
||||
self.speed_limit_valid = False
|
||||
|
||||
if sm.updated["carState"]:
|
||||
self.current_speed = sm["carState"].vEgo * speed_conv
|
||||
|
||||
def _render(self, rect: rl.Rectangle) -> None:
|
||||
self._update_state()
|
||||
|
||||
rl.draw_rectangle(int(rect.x), int(rect.y), int(rect.width), int(rect.height), COLORS.bg_dark)
|
||||
margin = 15
|
||||
left_width = 130
|
||||
right_x = rect.x + left_width + margin + 10
|
||||
right_width = rect.width - left_width - margin * 3
|
||||
sign_x = rect.x + margin + 5
|
||||
sign_y = rect.y + (rect.height - 140) / 2
|
||||
self._draw_speed_limit_sign(sign_x, sign_y)
|
||||
info_y = rect.y + 25
|
||||
|
||||
# Road name
|
||||
self._draw_road_name(right_x, info_y, right_width)
|
||||
info_y += 55
|
||||
|
||||
# Road context (Freeway/City) and lanes
|
||||
self._draw_road_details(right_x, info_y, right_width)
|
||||
info_y += 45
|
||||
|
||||
# Next speed limit
|
||||
self._draw_next_speed_limit(right_x, info_y, right_width)
|
||||
info_y += 45
|
||||
|
||||
# Curve speeds
|
||||
self._draw_curve_speeds(right_x, info_y, right_width)
|
||||
info_y += 45
|
||||
|
||||
# Hazard warning Beta
|
||||
if self.hazard:
|
||||
self._draw_hazard(right_x, info_y, right_width)
|
||||
|
||||
status_text = tr("Map Loaded") if self.tile_loaded else tr("No Map Data")
|
||||
status_color = COLORS.green if self.tile_loaded else COLORS.red
|
||||
status_size = measure_text_cached(self._font_medium, status_text, 18)
|
||||
status_pos = rl.Vector2(rect.x + (rect.width - status_size.x) / 2, rect.y + rect.height - 30)
|
||||
rl.draw_text_ex(self._font_medium, status_text, status_pos, 18, 0, status_color)
|
||||
|
||||
def _draw_speed_limit_sign(self, x: float, y: float) -> None:
|
||||
sign_width = 110 if ui_state.is_metric else 100
|
||||
sign_height = 120 if ui_state.is_metric else 110
|
||||
|
||||
speed_str = str(round(self.speed_limit)) if self.speed_limit_valid else "--"
|
||||
speed_color = COLORS.black if not self.speed_limit_valid or self.current_speed <= self.speed_limit else COLORS.red
|
||||
|
||||
if ui_state.is_metric:
|
||||
self._draw_vienna_sign(x, y, sign_width, sign_height, speed_str, speed_color)
|
||||
else:
|
||||
self._draw_mutcd_sign(x, y, sign_width, sign_height, speed_str, speed_color)
|
||||
|
||||
def _draw_road_name(self, x: float, y: float, width: float) -> None:
|
||||
road_display = self.road_name if self.road_name else "--"
|
||||
road_size = measure_text_cached(self._font_semi_bold, road_display, 34)
|
||||
if road_size.x > width:
|
||||
road_display = road_display[:20] + "..."
|
||||
rl.draw_text_ex(self._font_semi_bold, road_display, rl.Vector2(x, y), 34, 0, COLORS.white)
|
||||
|
||||
def _draw_road_details(self, x: float, y: float, width: float) -> None:
|
||||
details = []
|
||||
if self.road_context and self.road_context != "Unknown":
|
||||
details.append(self.road_context)
|
||||
if self.lanes > 0:
|
||||
details.append(f"{self.lanes} {tr('lanes')}")
|
||||
details_text = " • ".join(details) if details else "--"
|
||||
rl.draw_text_ex(self._font_medium, details_text, rl.Vector2(x, y), 26, 0, COLORS.light_grey)
|
||||
|
||||
def _draw_next_speed_limit(self, x: float, y: float, width: float) -> None:
|
||||
if self.next_speed_limit > 0 and self.next_speed_limit != self.speed_limit:
|
||||
next_text = f"{tr('Next')}: {round(self.next_speed_limit)} ({self._format_distance(self.next_speed_limit_distance)})"
|
||||
else:
|
||||
next_text = f"{tr('Next')}: --"
|
||||
rl.draw_text_ex(self._font_medium, next_text, rl.Vector2(x, y), 26, 0, COLORS.light_grey)
|
||||
|
||||
def _draw_curve_speeds(self, x: float, y: float, width: float) -> None:
|
||||
unit = tr("km/h") if ui_state.is_metric else tr("mph")
|
||||
vision_val = str(round(self.vision_curve_speed)) if self.vision_curve_speed > 0 else "--"
|
||||
map_val = str(round(self.map_curve_speed)) if self.map_curve_speed > 0 else "--"
|
||||
curve_text = f"{tr('Curve')}: V:{vision_val} M:{map_val} {unit}"
|
||||
rl.draw_text_ex(self._font_medium, curve_text, rl.Vector2(x, y), 24, 0, COLORS.light_grey)
|
||||
|
||||
def _draw_hazard(self, x: float, y: float, width: float) -> None:
|
||||
hazard_text = f"⚠ {self.hazard}"
|
||||
rl.draw_text_ex(self._font_semi_bold, hazard_text, rl.Vector2(x, y), 26, 0, COLORS.red)
|
||||
|
||||
def _draw_vienna_sign(self, x: float, y: float, width: float, height: float, speed_str: str, speed_color: rl.Color) -> None:
|
||||
center = rl.Vector2(x + width / 2, y + height / 2)
|
||||
outer_radius = min(width, height) / 2
|
||||
|
||||
rl.draw_circle_v(center, outer_radius, COLORS.white)
|
||||
ring_width = outer_radius * 0.18
|
||||
rl.draw_ring(center, outer_radius - ring_width, outer_radius, 0, 360, 36, COLORS.red)
|
||||
|
||||
font_size = outer_radius * (0.7 if len(speed_str) >= 3 else 0.9)
|
||||
text_size = measure_text_cached(self._font_bold, speed_str, int(font_size))
|
||||
text_pos = rl.Vector2(center.x - text_size.x / 2, center.y - text_size.y / 2)
|
||||
rl.draw_text_ex(self._font_bold, speed_str, text_pos, font_size, 0, speed_color)
|
||||
|
||||
def _draw_mutcd_sign(self, x: float, y: float, width: float, height: float, speed_str: str, speed_color: rl.Color) -> None:
|
||||
sign_rect = rl.Rectangle(x, y, width, height)
|
||||
rl.draw_rectangle_rounded(sign_rect, 0.2, 10, COLORS.white)
|
||||
rl.draw_rectangle_rounded_lines_ex(sign_rect, 0.2, 10, 3, COLORS.black)
|
||||
|
||||
speed_label = tr("SPEED")
|
||||
limit_label = tr("LIMIT")
|
||||
label_size = 16
|
||||
|
||||
speed_text_size = measure_text_cached(self._font_semi_bold, speed_label, label_size)
|
||||
speed_pos = rl.Vector2(x + (width - speed_text_size.x) / 2, y + 8)
|
||||
rl.draw_text_ex(self._font_semi_bold, speed_label, speed_pos, label_size, 0, COLORS.black)
|
||||
|
||||
limit_text_size = measure_text_cached(self._font_semi_bold, limit_label, label_size)
|
||||
limit_pos = rl.Vector2(x + (width - limit_text_size.x) / 2, y + 26)
|
||||
rl.draw_text_ex(self._font_semi_bold, limit_label, limit_pos, label_size, 0, COLORS.black)
|
||||
|
||||
font_size = 40 if len(speed_str) >= 3 else 50
|
||||
num_size = measure_text_cached(self._font_bold, speed_str, font_size)
|
||||
num_pos = rl.Vector2(x + (width - num_size.x) / 2, y + 45)
|
||||
rl.draw_text_ex(self._font_bold, speed_str, num_pos, font_size, 0, speed_color)
|
||||
|
||||
def _format_distance(self, distance: float) -> str:
|
||||
if ui_state.is_metric:
|
||||
if distance < 50:
|
||||
return tr("Near")
|
||||
if distance >= 1000:
|
||||
return f"{distance * METER_TO_KM:.1f}" + tr("km")
|
||||
if distance < 200:
|
||||
rounded = max(10, int(distance / 10) * 10)
|
||||
else:
|
||||
rounded = int(distance / 100) * 100
|
||||
return str(rounded) + tr("m")
|
||||
else:
|
||||
distance_mi = distance * METER_TO_MILE
|
||||
if distance_mi < 0.1:
|
||||
return tr("Near")
|
||||
return f"{distance_mi:.1f}" + tr("mi")
|
||||
@@ -0,0 +1,181 @@
|
||||
"""
|
||||
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 dataclasses import dataclass
|
||||
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.system.ui.widgets import Widget
|
||||
|
||||
METER_TO_KM = 0.001
|
||||
METER_TO_MILE = 0.000621371
|
||||
|
||||
SPEED_LIMIT_WIDTH_METRIC = 200
|
||||
SPEED_LIMIT_WIDTH_IMPERIAL = 172
|
||||
SPEED_LIMIT_HEIGHT = 204
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SpeedLimitColors:
|
||||
white: rl.Color = rl.WHITE
|
||||
black: rl.Color = rl.BLACK
|
||||
red: rl.Color = rl.Color(255, 0, 0, 255)
|
||||
grey: rl.Color = rl.Color(145, 155, 149, 241)
|
||||
green: rl.Color = rl.Color(0, 255, 0, 255)
|
||||
light_grey: rl.Color = rl.Color(200, 200, 200, 255)
|
||||
dark_grey: rl.Color = rl.Color(180, 180, 180, 255)
|
||||
border_grey: rl.Color = rl.Color(77, 77, 77, 255)
|
||||
black_translucent: rl.Color = rl.Color(0, 0, 0, 166)
|
||||
border_translucent: rl.Color = rl.Color(255, 255, 255, 75)
|
||||
|
||||
|
||||
COLORS = SpeedLimitColors()
|
||||
|
||||
|
||||
class SpeedLimitRenderer(Widget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.speed_limit: float = 0.0
|
||||
self.speed_limit_valid: bool = False
|
||||
self.next_speed_limit: float = 0.0
|
||||
self.next_speed_limit_distance: float = 0.0
|
||||
self.road_name: str = ""
|
||||
self.current_speed: float = 0.0
|
||||
|
||||
self._font_semi_bold: rl.Font = gui_app.font(FontWeight.SEMI_BOLD)
|
||||
self._font_bold: rl.Font = gui_app.font(FontWeight.BOLD)
|
||||
self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM)
|
||||
|
||||
def _update_state(self) -> None:
|
||||
sm = ui_state.sm
|
||||
if sm.recv_frame["carState"] < ui_state.started_frame:
|
||||
return
|
||||
|
||||
speed_conv = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH
|
||||
if sm.valid["mapdOut"]:
|
||||
mapd = sm["mapdOut"]
|
||||
self.speed_limit = mapd.speedLimit * speed_conv
|
||||
self.speed_limit_valid = mapd.speedLimit > 0
|
||||
self.next_speed_limit = mapd.nextSpeedLimit * speed_conv
|
||||
self.next_speed_limit_distance = mapd.nextSpeedLimitDistance
|
||||
self.road_name = mapd.roadName
|
||||
else:
|
||||
self.speed_limit_valid = False
|
||||
|
||||
if sm.updated["carState"]:
|
||||
self.current_speed = sm["carState"].vEgo * speed_conv
|
||||
|
||||
def _render(self, rect: rl.Rectangle) -> None:
|
||||
self._update_state()
|
||||
|
||||
sign_width = SPEED_LIMIT_WIDTH_METRIC if ui_state.is_metric else SPEED_LIMIT_WIDTH_IMPERIAL
|
||||
sign_height = SPEED_LIMIT_HEIGHT
|
||||
sign_margin = 12
|
||||
sign_x = rect.x + sign_margin
|
||||
sign_y = rect.y + 45
|
||||
|
||||
sign_rect = rl.Rectangle(sign_x, sign_y, sign_width, sign_height)
|
||||
|
||||
speed_str = str(round(self.speed_limit)) if self.speed_limit_valid else "--"
|
||||
speed_color = COLORS.white if not self.speed_limit_valid or self.current_speed <= self.speed_limit else COLORS.red
|
||||
|
||||
if ui_state.is_metric:
|
||||
self._draw_vienna_sign(sign_rect, speed_str, "", speed_color, self.speed_limit_valid)
|
||||
else:
|
||||
self._draw_mutcd_sign(sign_rect, speed_str, "", speed_color, self.speed_limit_valid)
|
||||
|
||||
if self.next_speed_limit > 0 and self.next_speed_limit != self.speed_limit:
|
||||
self._draw_upcoming_speed_limit(sign_rect)
|
||||
|
||||
def _draw_vienna_sign(self, sign_rect: rl.Rectangle, speed_str: str, sub_text: str, speed_color: rl.Color, has_speed_limit: bool) -> None:
|
||||
"""Draw EU/Vienna"""
|
||||
center = rl.Vector2(sign_rect.x + sign_rect.width / 2, sign_rect.y + sign_rect.height / 2)
|
||||
outer_radius = min(sign_rect.width, sign_rect.height) / 2
|
||||
circle_size = outer_radius * 2
|
||||
|
||||
rl.draw_circle_v(center, outer_radius, COLORS.white)
|
||||
ring_width = outer_radius * 0.18
|
||||
rl.draw_ring(center, outer_radius - ring_width, outer_radius, 0, 360, 36, COLORS.red)
|
||||
|
||||
font_size = circle_size * (0.35 if len(speed_str) >= 3 else 0.45)
|
||||
text_size = measure_text_cached(self._font_bold, speed_str, int(font_size))
|
||||
text_pos = rl.Vector2(center.x - text_size.x / 2, center.y - text_size.y / 2)
|
||||
rl.draw_text_ex(self._font_bold, speed_str, text_pos, font_size, 0, speed_color)
|
||||
|
||||
def _draw_mutcd_sign(self, sign_rect: rl.Rectangle, speed_str: str, sub_text: str, speed_color: rl.Color, has_speed_limit: bool) -> None:
|
||||
"""Draw US/Canada MUTCD sign."""
|
||||
padding = 8
|
||||
inner_rect = rl.Rectangle(sign_rect.x + padding, sign_rect.y + padding, sign_rect.width - (padding * 2), sign_rect.height - (padding * 2))
|
||||
rl.draw_rectangle_rounded(inner_rect, 0.35, 10, COLORS.white)
|
||||
rl.draw_rectangle_rounded_lines_ex(inner_rect, 0.35, 10, 4, COLORS.black)
|
||||
|
||||
border_width = 4
|
||||
border_rect = rl.Rectangle(
|
||||
inner_rect.x + border_width, inner_rect.y + border_width, inner_rect.width - (border_width * 2), inner_rect.height - (border_width * 2)
|
||||
)
|
||||
speed_text = tr("SPEED")
|
||||
limit_text = tr("LIMIT")
|
||||
|
||||
speed_size = measure_text_cached(self._font_semi_bold, speed_text, 40)
|
||||
limit_size = measure_text_cached(self._font_semi_bold, limit_text, 40)
|
||||
|
||||
speed_pos = rl.Vector2(border_rect.x + (border_rect.width - speed_size.x) / 2, sign_rect.y + 27)
|
||||
limit_pos = rl.Vector2(border_rect.x + (border_rect.width - limit_size.x) / 2, sign_rect.y + 65)
|
||||
|
||||
rl.draw_text_ex(self._font_semi_bold, speed_text, speed_pos, 40, 0, COLORS.black)
|
||||
rl.draw_text_ex(self._font_semi_bold, limit_text, limit_pos, 40, 0, COLORS.black)
|
||||
|
||||
font_size = 70 if len(speed_str) >= 3 else 90
|
||||
speed_value_size = measure_text_cached(self._font_bold, speed_str, font_size)
|
||||
speed_value_pos = rl.Vector2(border_rect.x + (border_rect.width - speed_value_size.x) / 2, sign_rect.y + 100)
|
||||
rl.draw_text_ex(self._font_bold, speed_str, speed_value_pos, font_size, 0, speed_color)
|
||||
|
||||
def _draw_upcoming_speed_limit(self, sign_rect: rl.Rectangle) -> None:
|
||||
"""Draw upcoming speed limit indicator."""
|
||||
speed_str = str(round(self.next_speed_limit))
|
||||
distance_str = self._format_distance(self.next_speed_limit_distance)
|
||||
|
||||
ahead_width = 150
|
||||
ahead_height = 100
|
||||
ahead_x = sign_rect.x + (sign_rect.width - ahead_width) / 2
|
||||
ahead_y = sign_rect.y + sign_rect.height + 6
|
||||
ahead_rect = rl.Rectangle(ahead_x, ahead_y, ahead_width, ahead_height)
|
||||
|
||||
rl.draw_rectangle_rounded(ahead_rect, 0.2, 10, rl.Color(0, 0, 0, 180))
|
||||
rl.draw_rectangle_rounded_lines_ex(ahead_rect, 0.2, 10, 3, rl.Color(255, 255, 255, 100))
|
||||
|
||||
ahead_label = tr("AHEAD")
|
||||
ahead_size = measure_text_cached(self._font_semi_bold, ahead_label, 28)
|
||||
ahead_pos = rl.Vector2(ahead_rect.x + (ahead_rect.width - ahead_size.x) / 2, ahead_rect.y + 4)
|
||||
rl.draw_text_ex(self._font_semi_bold, ahead_label, ahead_pos, 28, 0, COLORS.light_grey)
|
||||
|
||||
speed_size = measure_text_cached(self._font_bold, speed_str, 48)
|
||||
speed_pos = rl.Vector2(ahead_rect.x + (ahead_rect.width - speed_size.x) / 2, ahead_rect.y + 26)
|
||||
rl.draw_text_ex(self._font_bold, speed_str, speed_pos, 48, 0, COLORS.white)
|
||||
|
||||
distance_size = measure_text_cached(self._font_medium, distance_str, 28)
|
||||
distance_pos = rl.Vector2(ahead_rect.x + (ahead_rect.width - distance_size.x) / 2, ahead_rect.y + 70)
|
||||
rl.draw_text_ex(self._font_medium, distance_str, distance_pos, 28, 0, COLORS.dark_grey)
|
||||
|
||||
def _format_distance(self, distance: float) -> str:
|
||||
if ui_state.is_metric:
|
||||
if distance < 50:
|
||||
return tr("Near")
|
||||
if distance >= 1000:
|
||||
return f"{distance * METER_TO_KM:.1f}" + tr("km")
|
||||
if distance < 200:
|
||||
rounded = max(10, int(distance / 10) * 10)
|
||||
else:
|
||||
rounded = int(distance / 100) * 100
|
||||
return str(rounded) + tr("m")
|
||||
else:
|
||||
distance_mi = distance * METER_TO_MILE
|
||||
if distance_mi < 0.1:
|
||||
return tr("Near")
|
||||
return f"{distance_mi:.1f}" + tr("mi")
|
||||
@@ -2,6 +2,7 @@ import pyray as rl
|
||||
from dataclasses import dataclass
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.selfdrive.ui.onroad.exp_button import ExpButton
|
||||
from openpilot.selfdrive.ui.onroad.speed_limit_ui import SpeedLimitRenderer
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
@@ -71,6 +72,7 @@ class HudRenderer(Widget):
|
||||
self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM)
|
||||
|
||||
self._exp_button: ExpButton = ExpButton(UI_CONFIG.button_size, UI_CONFIG.wheel_icon_size)
|
||||
self._speed_limit_renderer: SpeedLimitRenderer = SpeedLimitRenderer()
|
||||
|
||||
def _update_state(self) -> None:
|
||||
"""Update HUD state based on car state and controls state."""
|
||||
@@ -112,6 +114,8 @@ class HudRenderer(Widget):
|
||||
COLORS.HEADER_GRADIENT_END,
|
||||
)
|
||||
|
||||
self._speed_limit_renderer.render(rect)
|
||||
|
||||
if self.is_cruise_available:
|
||||
self._draw_set_speed(rect)
|
||||
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
"""
|
||||
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 dataclasses import dataclass
|
||||
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.system.ui.widgets import Widget
|
||||
|
||||
METER_TO_KM = 0.001
|
||||
METER_TO_MILE = 0.000621371
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SpeedLimitColors:
|
||||
white: rl.Color = rl.WHITE
|
||||
black: rl.Color = rl.BLACK
|
||||
red: rl.Color = rl.Color(255, 0, 0, 255)
|
||||
green: rl.Color = rl.Color(0, 255, 0, 255)
|
||||
grey: rl.Color = rl.Color(145, 155, 149, 241)
|
||||
light_grey: rl.Color = rl.Color(200, 200, 200, 255)
|
||||
dark_grey: rl.Color = rl.Color(100, 100, 100, 255)
|
||||
bg_dark: rl.Color = rl.Color(30, 30, 30, 200)
|
||||
card_bg: rl.Color = rl.Color(50, 50, 50, 200)
|
||||
|
||||
|
||||
COLORS = SpeedLimitColors()
|
||||
|
||||
|
||||
class SpeedLimitRenderer(Widget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.speed_limit: float = 0.0
|
||||
self.speed_limit_valid: bool = False
|
||||
self.next_speed_limit: float = 0.0
|
||||
self.next_speed_limit_distance: float = 0.0
|
||||
self.road_name: str = ""
|
||||
self.current_speed: float = 0.0
|
||||
self.lanes: int = 0
|
||||
self.road_context: str = ""
|
||||
self.hazard: str = ""
|
||||
self.vision_curve_speed: float = 0.0
|
||||
self.map_curve_speed: float = 0.0
|
||||
self.tile_loaded: bool = False
|
||||
|
||||
self._font_bold: rl.Font = gui_app.font(FontWeight.BOLD)
|
||||
self._font_semi_bold: rl.Font = gui_app.font(FontWeight.SEMI_BOLD)
|
||||
self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM)
|
||||
|
||||
def _update_state(self) -> None:
|
||||
sm = ui_state.sm
|
||||
if sm.recv_frame["carState"] < ui_state.started_frame:
|
||||
return
|
||||
|
||||
speed_conv = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH
|
||||
if sm.valid["mapdOut"]:
|
||||
mapd = sm["mapdOut"]
|
||||
self.speed_limit = mapd.speedLimit * speed_conv
|
||||
self.speed_limit_valid = mapd.speedLimit > 0
|
||||
self.next_speed_limit = mapd.nextSpeedLimit * speed_conv
|
||||
self.next_speed_limit_distance = mapd.nextSpeedLimitDistance
|
||||
self.road_name = mapd.roadName
|
||||
self.lanes = mapd.lanes
|
||||
self.hazard = mapd.hazard
|
||||
self.vision_curve_speed = mapd.visionCurveSpeed * speed_conv
|
||||
self.map_curve_speed = mapd.mapCurveSpeed * speed_conv
|
||||
self.tile_loaded = mapd.tileLoaded
|
||||
|
||||
road_ctx = mapd.roadContext
|
||||
if hasattr(road_ctx, 'raw'):
|
||||
ctx_val = road_ctx.raw
|
||||
else:
|
||||
ctx_val = int(road_ctx)
|
||||
self.road_context = ["Freeway", "City", "Unknown"][ctx_val] if ctx_val < 3 else "Unknown"
|
||||
else:
|
||||
self.speed_limit_valid = False
|
||||
self.tile_loaded = False
|
||||
|
||||
if sm.updated["carState"]:
|
||||
self.current_speed = sm["carState"].vEgo * speed_conv
|
||||
|
||||
def _render(self, rect: rl.Rectangle) -> None:
|
||||
self._update_state()
|
||||
|
||||
panel_width = 320
|
||||
panel_height = 340
|
||||
panel_x = rect.x + 12
|
||||
panel_y = rect.y + rect.height / 2 - panel_height / 2
|
||||
panel_rect = rl.Rectangle(panel_x, panel_y, panel_width, panel_height)
|
||||
rl.draw_rectangle_rounded(panel_rect, 0.1, 10, COLORS.bg_dark)
|
||||
|
||||
margin = 15
|
||||
sign_width = 110 if ui_state.is_metric else 100
|
||||
sign_height = 120 if ui_state.is_metric else 110
|
||||
sign_x = panel_x + margin
|
||||
sign_y = panel_y + margin
|
||||
|
||||
self._draw_speed_limit_sign(sign_x, sign_y, sign_width, sign_height)
|
||||
info_x = sign_x + sign_width + 15
|
||||
info_width = panel_width - sign_width - margin * 2 - 15
|
||||
info_y = panel_y + 20
|
||||
|
||||
# Road name
|
||||
self._draw_road_name(info_x, info_y, info_width)
|
||||
info_y += 40
|
||||
|
||||
# Road context and lanes
|
||||
self._draw_road_details(info_x, info_y, info_width)
|
||||
info_y += 35
|
||||
|
||||
# Next speed limit
|
||||
self._draw_next_speed_limit(info_x, info_y, info_width)
|
||||
|
||||
info_y = panel_y + sign_height + margin + 25
|
||||
|
||||
# Curve speeds
|
||||
self._draw_curve_speeds(panel_x + margin, info_y, panel_width - margin * 2)
|
||||
info_y += 35
|
||||
|
||||
# Hazard warning beta
|
||||
if self.hazard:
|
||||
self._draw_hazard(panel_x + margin, info_y, panel_width - margin * 2)
|
||||
info_y += 35
|
||||
|
||||
status_text = tr("Map Loaded") if self.tile_loaded else tr("No Map Data")
|
||||
status_color = COLORS.green if self.tile_loaded else COLORS.red
|
||||
status_size = measure_text_cached(self._font_medium, status_text, 18)
|
||||
status_pos = rl.Vector2(panel_x + (panel_width - status_size.x) / 2, panel_y + panel_height - 28)
|
||||
rl.draw_text_ex(self._font_medium, status_text, status_pos, 18, 0, status_color)
|
||||
|
||||
def _draw_speed_limit_sign(self, x: float, y: float, width: float, height: float) -> None:
|
||||
speed_str = str(round(self.speed_limit)) if self.speed_limit_valid else "--"
|
||||
speed_color = COLORS.black if not self.speed_limit_valid or self.current_speed <= self.speed_limit else COLORS.red
|
||||
|
||||
if ui_state.is_metric:
|
||||
self._draw_vienna_sign(x, y, width, height, speed_str, speed_color)
|
||||
else:
|
||||
self._draw_mutcd_sign(x, y, width, height, speed_str, speed_color)
|
||||
|
||||
def _draw_vienna_sign(self, x: float, y: float, width: float, height: float, speed_str: str, speed_color: rl.Color) -> None:
|
||||
center = rl.Vector2(x + width / 2, y + height / 2)
|
||||
outer_radius = min(width, height) / 2
|
||||
|
||||
rl.draw_circle_v(center, outer_radius, COLORS.white)
|
||||
ring_width = outer_radius * 0.18
|
||||
rl.draw_ring(center, outer_radius - ring_width, outer_radius, 0, 360, 36, COLORS.red)
|
||||
|
||||
font_size = outer_radius * (0.7 if len(speed_str) >= 3 else 0.9)
|
||||
text_size = measure_text_cached(self._font_bold, speed_str, int(font_size))
|
||||
text_pos = rl.Vector2(center.x - text_size.x / 2, center.y - text_size.y / 2)
|
||||
rl.draw_text_ex(self._font_bold, speed_str, text_pos, font_size, 0, speed_color)
|
||||
|
||||
def _draw_mutcd_sign(self, x: float, y: float, width: float, height: float, speed_str: str, speed_color: rl.Color) -> None:
|
||||
sign_rect = rl.Rectangle(x, y, width, height)
|
||||
rl.draw_rectangle_rounded(sign_rect, 0.2, 10, COLORS.white)
|
||||
rl.draw_rectangle_rounded_lines_ex(sign_rect, 0.2, 10, 3, COLORS.black)
|
||||
|
||||
speed_label = tr("SPEED")
|
||||
limit_label = tr("LIMIT")
|
||||
label_size = 16
|
||||
|
||||
speed_text_size = measure_text_cached(self._font_semi_bold, speed_label, label_size)
|
||||
speed_pos = rl.Vector2(x + (width - speed_text_size.x) / 2, y + 8)
|
||||
rl.draw_text_ex(self._font_semi_bold, speed_label, speed_pos, label_size, 0, COLORS.black)
|
||||
|
||||
limit_text_size = measure_text_cached(self._font_semi_bold, limit_label, label_size)
|
||||
limit_pos = rl.Vector2(x + (width - limit_text_size.x) / 2, y + 26)
|
||||
rl.draw_text_ex(self._font_semi_bold, limit_label, limit_pos, label_size, 0, COLORS.black)
|
||||
|
||||
font_size = 40 if len(speed_str) >= 3 else 50
|
||||
num_size = measure_text_cached(self._font_bold, speed_str, font_size)
|
||||
num_pos = rl.Vector2(x + (width - num_size.x) / 2, y + 45)
|
||||
rl.draw_text_ex(self._font_bold, speed_str, num_pos, font_size, 0, speed_color)
|
||||
|
||||
def _draw_road_name(self, x: float, y: float, width: float) -> None:
|
||||
road_display = self.road_name if self.road_name else "--"
|
||||
road_size = measure_text_cached(self._font_semi_bold, road_display, 26)
|
||||
if road_size.x > width:
|
||||
road_display = road_display[:15] + "..."
|
||||
rl.draw_text_ex(self._font_semi_bold, road_display, rl.Vector2(x, y), 26, 0, COLORS.white)
|
||||
|
||||
def _draw_road_details(self, x: float, y: float, width: float) -> None:
|
||||
details = []
|
||||
if self.road_context and self.road_context != "Unknown":
|
||||
details.append(self.road_context)
|
||||
if self.lanes > 0:
|
||||
details.append(f"{self.lanes} {tr('lanes')}")
|
||||
details_text = " • ".join(details) if details else "--"
|
||||
rl.draw_text_ex(self._font_medium, details_text, rl.Vector2(x, y), 20, 0, COLORS.light_grey)
|
||||
|
||||
def _draw_next_speed_limit(self, x: float, y: float, width: float) -> None:
|
||||
if self.next_speed_limit > 0 and self.next_speed_limit != self.speed_limit:
|
||||
next_text = f"{tr('Next')}: {round(self.next_speed_limit)} ({self._format_distance(self.next_speed_limit_distance)})"
|
||||
else:
|
||||
next_text = f"{tr('Next')}: --"
|
||||
rl.draw_text_ex(self._font_medium, next_text, rl.Vector2(x, y), 20, 0, COLORS.light_grey)
|
||||
|
||||
def _draw_curve_speeds(self, x: float, y: float, width: float) -> None:
|
||||
unit = tr("km/h") if ui_state.is_metric else tr("mph")
|
||||
vision_val = str(round(self.vision_curve_speed)) if self.vision_curve_speed > 0 else "--"
|
||||
map_val = str(round(self.map_curve_speed)) if self.map_curve_speed > 0 else "--"
|
||||
curve_text = f"{tr('Curve')}: V:{vision_val} M:{map_val} {unit}"
|
||||
rl.draw_text_ex(self._font_medium, curve_text, rl.Vector2(x, y), 20, 0, COLORS.light_grey)
|
||||
|
||||
def _draw_hazard(self, x: float, y: float, width: float) -> None:
|
||||
hazard_text = f"⚠ {self.hazard}"
|
||||
rl.draw_text_ex(self._font_semi_bold, hazard_text, rl.Vector2(x, y), 22, 0, COLORS.red)
|
||||
|
||||
def _format_distance(self, distance: float) -> str:
|
||||
if ui_state.is_metric:
|
||||
if distance < 50:
|
||||
return tr("Near")
|
||||
if distance >= 1000:
|
||||
return f"{distance * METER_TO_KM:.1f}" + tr("km")
|
||||
if distance < 200:
|
||||
rounded = max(10, int(distance / 10) * 10)
|
||||
else:
|
||||
rounded = int(distance / 100) * 100
|
||||
return str(rounded) + tr("m")
|
||||
else:
|
||||
distance_mi = distance * METER_TO_MILE
|
||||
if distance_mi < 0.1:
|
||||
return tr("Near")
|
||||
return f"{distance_mi:.1f}" + tr("mi")
|
||||
@@ -1,232 +0,0 @@
|
||||
"""
|
||||
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
import datetime
|
||||
import os
|
||||
import platform
|
||||
import requests
|
||||
import shutil
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from time import monotonic
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.ui.ui_state import device, ui_state
|
||||
from openpilot.selfdrive.ui.layouts.settings.software import time_ago
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
from openpilot.system.ui.widgets import DialogResult, Widget
|
||||
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
|
||||
from openpilot.system.ui.widgets.list_view import text_item
|
||||
from openpilot.system.ui.widgets.scroller_tici import Scroller
|
||||
|
||||
from openpilot.system.ui.sunnypilot.lib.utils import NoElideButtonAction
|
||||
from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP
|
||||
from openpilot.system.ui.sunnypilot.widgets.tree_dialog import TreeFolder, TreeNode, TreeOptionDialog
|
||||
from openpilot.system.ui.sunnypilot.widgets.progress_bar import progress_item
|
||||
|
||||
MAP_PATH = Path(Paths.mapd_root()) / "offline"
|
||||
|
||||
|
||||
class OSMLayout(Widget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._current_percent = 0
|
||||
self._last_map_size_update = 0
|
||||
self._mem_params = Params("/dev/shm/params") if platform.system() != "Darwin" else ui_state.params
|
||||
self._initialize_items()
|
||||
self._update_map_size()
|
||||
self._progress.set_visible(False)
|
||||
self._state_btn.set_visible(False)
|
||||
self._mapd_version.action_item.set_text(ui_state.params.get("MapdVersion") or "Loading...")
|
||||
self._scroller = Scroller(self.items, line_separator=True, spacing=0)
|
||||
|
||||
def _initialize_items(self):
|
||||
self._mapd_version = text_item(tr("Mapd Version"), lambda: ui_state.params.get("MapdVersion") or "Loading...")
|
||||
self._delete_maps_btn = ListItemSP(tr("Downloaded Maps"), action_item=NoElideButtonAction(tr("DELETE"), enabled=True), callback=self._delete_maps)
|
||||
self._progress = progress_item(tr("Downloading Map"))
|
||||
self._update_btn = ListItemSP(tr("Database Update"), action_item=NoElideButtonAction(tr("CHECK"), enabled=True), callback=self._update_db)
|
||||
self._country_btn = ListItemSP(tr("Country"), action_item=NoElideButtonAction(tr("SELECT"), enabled=True), callback=lambda: self._select_region("Country"))
|
||||
self._state_btn = ListItemSP(tr("State"), action_item=NoElideButtonAction(tr("SELECT"), enabled=True), callback=lambda: self._select_region("State"))
|
||||
|
||||
self.items = [self._mapd_version, self._delete_maps_btn, self._progress, self._update_btn, self._country_btn, self._state_btn]
|
||||
|
||||
def _show_confirm(self, msg, confirm_text, func):
|
||||
gui_app.set_modal_overlay(ConfirmDialog(msg, confirm_text), lambda res: func() if res == DialogResult.CONFIRM else None)
|
||||
|
||||
def calculate_size(self):
|
||||
total_size = 0
|
||||
directories_to_scan = [MAP_PATH] if MAP_PATH.exists() else []
|
||||
while directories_to_scan:
|
||||
try:
|
||||
for entry in os.scandir(directories_to_scan.pop()):
|
||||
if entry.is_file():
|
||||
total_size += entry.stat().st_size
|
||||
elif entry.is_dir():
|
||||
directories_to_scan.append(entry.path)
|
||||
except OSError:
|
||||
pass
|
||||
self._delete_maps_btn.action_item.set_value(f"{total_size / 1024 ** 2:.2f} MB" if total_size < 1024 ** 3 else f"{total_size / 1024 ** 3:.2f} GB")
|
||||
|
||||
def _update_map_size(self):
|
||||
threading.Thread(target=self.calculate_size, daemon=True).start()
|
||||
|
||||
def _do_delete_maps(self):
|
||||
if MAP_PATH.exists():
|
||||
shutil.rmtree(MAP_PATH)
|
||||
|
||||
for param in ("OsmDownloadedDate", "OsmLocal", "OsmLocationName", "OsmLocationTitle", "OsmStateName", "OsmStateTitle"):
|
||||
ui_state.params.remove(param)
|
||||
|
||||
self._delete_maps_btn.action_item.set_enabled(True)
|
||||
self._delete_maps_btn.action_item.set_text(tr("DELETE"))
|
||||
self._update_map_size()
|
||||
|
||||
def _on_confirm_delete_maps(self):
|
||||
self._delete_maps_btn.action_item.set_enabled(False)
|
||||
self._delete_maps_btn.action_item.set_text("DELETING...")
|
||||
threading.Thread(target=self._do_delete_maps).start()
|
||||
|
||||
def _delete_maps(self):
|
||||
self._show_confirm(tr("This will delete ALL downloaded maps\n\nAre you sure you want to delete all maps?"),
|
||||
tr("Yes, delete all maps"), self._on_confirm_delete_maps)
|
||||
|
||||
def _update_db(self):
|
||||
self._show_confirm(tr("This will start the download process and it might take a while to complete."), tr("Start Download"),
|
||||
lambda: ui_state.params.put_bool("OsmDbUpdatesCheck", True))
|
||||
|
||||
def _select_region(self, region_type):
|
||||
is_country = region_type == "Country"
|
||||
btn = self._country_btn if is_country else self._state_btn
|
||||
btn.action_item.set_enabled(False)
|
||||
btn.action_item.set_text(tr("FETCHING..."))
|
||||
threading.Thread(target=self._do_select_region, args=(region_type, btn)).start()
|
||||
|
||||
def _handle_region_selection(self, region_type, locations, key, res, ref):
|
||||
if res != DialogResult.CONFIRM or not ref:
|
||||
if region_type == "State" and res == DialogResult.CANCEL:
|
||||
if ui_state.params.get("OsmLocationName") == "US" and not ui_state.params.get("OsmStateName"):
|
||||
ui_state.params.remove("OsmLocationName")
|
||||
ui_state.params.remove("OsmLocationTitle")
|
||||
ui_state.params.remove("OsmLocal")
|
||||
self._update_labels()
|
||||
return
|
||||
|
||||
if region_type == "Country":
|
||||
ui_state.params.put_bool("OsmLocal", True)
|
||||
ui_state.params.remove("OsmStateName")
|
||||
ui_state.params.remove("OsmStateTitle")
|
||||
|
||||
ui_state.params.put(f"{key}Name", ref)
|
||||
name = next((n.data['display_name'] for n in locations if n.ref == ref), ref)
|
||||
ui_state.params.put(f"{key}Title", name)
|
||||
|
||||
if ref == "US" and region_type == "Country":
|
||||
self._select_region("State")
|
||||
else:
|
||||
self._update_db()
|
||||
|
||||
def _do_select_region(self, region_type, btn):
|
||||
base_url = "https://raw.githubusercontent.com/pfeiferj/openpilot-mapd/main/"
|
||||
url = base_url + ("nation_bounding_boxes.json" if region_type == "Country" else "us_states_bounding_boxes.json")
|
||||
try:
|
||||
data = requests.get(url, timeout=10).json()
|
||||
locations = sorted([TreeNode(ref=k, data={'display_name': v['full_name']}) for k, v in data.items()], key=lambda n: n.data['display_name'])
|
||||
except Exception:
|
||||
locations = []
|
||||
|
||||
if region_type == "State":
|
||||
locations.insert(0, TreeNode(ref="All", data={'display_name': tr("All states (~6.0 GB)")}))
|
||||
|
||||
btn.action_item.set_enabled(True)
|
||||
btn.action_item.set_text(tr("SELECT"))
|
||||
|
||||
key = "OsmLocation" if region_type == "Country" else "OsmState"
|
||||
current = ui_state.params.get(f"{key}Name") or ""
|
||||
|
||||
dialog = TreeOptionDialog(tr(f"Select {region_type}"), [TreeFolder(folder="", nodes=locations)], current_ref=current, search_prompt="Perform a search")
|
||||
dialog.on_exit = lambda res: self._handle_region_selection(region_type, locations, key, res, dialog.selection_ref)
|
||||
gui_app.set_modal_overlay(dialog, callback=lambda res: self._handle_region_selection(region_type, locations, key, res, dialog.selection_ref))
|
||||
|
||||
def _update_labels(self):
|
||||
downloading = bool(self._mem_params.get("OSMDownloadLocations"))
|
||||
self._country_btn.set_enabled(not downloading)
|
||||
self._state_btn.set_enabled(not downloading)
|
||||
self._state_btn.set_visible(ui_state.params.get("OsmLocationName") == "US")
|
||||
self._update_btn.set_visible(bool(ui_state.params.get("OsmLocationName")))
|
||||
|
||||
self._country_btn.action_item.set_value(ui_state.params.get("OsmLocationTitle") or "")
|
||||
self._state_btn.action_item.set_value(ui_state.params.get("OsmStateTitle") or "")
|
||||
|
||||
pending = ui_state.params.get_bool("OsmDbUpdatesCheck")
|
||||
if downloading or pending:
|
||||
if downloading:
|
||||
device._reset_interactive_timeout()
|
||||
self._update_map_size()
|
||||
self._progress.set_visible(True)
|
||||
progress = ui_state.params.get("OSMDownloadProgress")
|
||||
total = progress.get('total_files', 0) if progress else 0
|
||||
done = progress.get('downloaded_files', 0) if progress else 0
|
||||
failed = total > 0 and not downloading and done < total
|
||||
|
||||
if total > 0:
|
||||
progress_perc = max(0.0, min(100.0, (done / total) * 100.0))
|
||||
else:
|
||||
progress_perc = 0.0
|
||||
|
||||
if failed:
|
||||
text = "0% - Downloading Maps"
|
||||
btn_text = tr("Error: Invalid download. Retry.")
|
||||
self._current_percent = 0.0
|
||||
elif total > 0 and downloading:
|
||||
self._current_percent = progress_perc
|
||||
perc_int = int(progress_perc)
|
||||
text = f"{perc_int}% - Downloading Maps"
|
||||
btn_text = f"{done}/{total} ({perc_int}%)"
|
||||
else:
|
||||
self._current_percent = 0.0
|
||||
text = "0% - Downloading Maps"
|
||||
btn_text = tr("Downloading Maps...")
|
||||
|
||||
self._progress.action_item.update(self._current_percent, text, show_progress=total > 0 and downloading and not failed)
|
||||
self._update_btn.action_item.set_enabled(not downloading) # TODO-SP: introduce CANCEL database download with mapd
|
||||
self._update_btn.action_item.set_value(btn_text)
|
||||
self._country_btn.action_item.set_enabled(not downloading)
|
||||
self._state_btn.action_item.set_enabled(not downloading)
|
||||
self._delete_maps_btn.action_item.set_enabled(not downloading)
|
||||
else:
|
||||
self._progress.set_visible(False)
|
||||
self._update_btn.action_item.set_enabled(True)
|
||||
self._country_btn.action_item.set_enabled(True)
|
||||
self._state_btn.action_item.set_enabled(True)
|
||||
self._delete_maps_btn.action_item.set_enabled(True)
|
||||
|
||||
ts = ui_state.params.get("OsmDownloadedDate")
|
||||
dt: datetime.datetime | None = None
|
||||
|
||||
if ts:
|
||||
try:
|
||||
ts_f = float(ts)
|
||||
if ts_f > 0:
|
||||
dt = datetime.datetime.fromtimestamp(ts_f, tz=datetime.UTC)
|
||||
except (ValueError, TypeError):
|
||||
dt = None
|
||||
|
||||
formatted = time_ago(dt)
|
||||
self._update_btn.action_item.set_value(tr("Last checked {}").format(formatted))
|
||||
|
||||
def show_event(self):
|
||||
self._scroller.show_event()
|
||||
|
||||
def _update_state(self):
|
||||
now = monotonic()
|
||||
if now - self._last_map_size_update >= 1.0:
|
||||
self._last_map_size_update = now
|
||||
self._update_labels()
|
||||
|
||||
def _render(self, rect):
|
||||
self._scroller.render(rect)
|
||||
@@ -4,6 +4,7 @@ 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 dataclasses import dataclass
|
||||
from enum import IntEnum
|
||||
|
||||
@@ -17,7 +18,6 @@ from openpilot.selfdrive.ui.sunnypilot.layouts.settings.device import DeviceLayo
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.display import DisplayLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.models import ModelsLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.network import NetworkUISP
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.osm import OSMLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.software import SoftwareLayoutSP
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.steering import SteeringLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.sunnylink import SunnylinkLayout
|
||||
@@ -39,14 +39,14 @@ ICON_SIZE = 70
|
||||
|
||||
OP.PanelType = IntEnum( # type: ignore
|
||||
"PanelType",
|
||||
[es.name for es in OP.PanelType] + [
|
||||
[es.name for es in OP.PanelType]
|
||||
+ [
|
||||
"SUNNYLINK",
|
||||
"MODELS",
|
||||
"STEERING",
|
||||
"CRUISE",
|
||||
"VISUALS",
|
||||
"DISPLAY",
|
||||
"OSM",
|
||||
"NAVIGATION",
|
||||
"TRIPS",
|
||||
"VEHICLE",
|
||||
@@ -75,22 +75,16 @@ class NavButton(Widget):
|
||||
|
||||
# Draw background if selected
|
||||
if is_selected:
|
||||
self.container_rect = rl.Rectangle(
|
||||
content_x - 50, rect.y, OP.SIDEBAR_WIDTH - 50, OP.NAV_BTN_HEIGHT
|
||||
)
|
||||
self.container_rect = rl.Rectangle(content_x - 50, rect.y, OP.SIDEBAR_WIDTH - 50, OP.NAV_BTN_HEIGHT)
|
||||
rl.draw_rectangle_rounded(self.container_rect, 0.2, 5, OP.CLOSE_BTN_COLOR)
|
||||
|
||||
if self.panel_info.icon:
|
||||
icon_texture = gui_app.texture(self.panel_info.icon, ICON_SIZE, ICON_SIZE, keep_aspect_ratio=True)
|
||||
rl.draw_texture(icon_texture, int(content_x), int(rect.y + (OP.NAV_BTN_HEIGHT - icon_texture.height) / 2),
|
||||
rl.WHITE)
|
||||
rl.draw_texture(icon_texture, int(content_x), int(rect.y + (OP.NAV_BTN_HEIGHT - icon_texture.height) / 2), rl.WHITE)
|
||||
content_x += ICON_SIZE + 20
|
||||
|
||||
# Draw button text (right-aligned)
|
||||
text_pos = rl.Vector2(
|
||||
content_x,
|
||||
rect.y + (OP.NAV_BTN_HEIGHT - text_size.y) / 2
|
||||
)
|
||||
text_pos = rl.Vector2(content_x, rect.y + (OP.NAV_BTN_HEIGHT - text_size.y) / 2)
|
||||
rl.draw_text_ex(self.parent._font_medium, self.panel_info.name, text_pos, 55, 0, text_color)
|
||||
|
||||
# Store button rect for click detection
|
||||
@@ -120,7 +114,6 @@ class SettingsLayoutSP(OP.SettingsLayout):
|
||||
OP.PanelType.CRUISE: PanelInfo(tr_noop("Cruise"), CruiseLayout(), icon="icons/speed_limit.png"),
|
||||
OP.PanelType.VISUALS: PanelInfo(tr_noop("Visuals"), VisualsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_visuals.png"),
|
||||
OP.PanelType.DISPLAY: PanelInfo(tr_noop("Display"), DisplayLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_display.png"),
|
||||
OP.PanelType.OSM: PanelInfo(tr_noop("OSM"), OSMLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_map.png"),
|
||||
# OP.PanelType.NAVIGATION: PanelInfo(tr_noop("Navigation"), NavigationLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_map.png"),
|
||||
OP.PanelType.TRIPS: PanelInfo(tr_noop("Trips"), TripsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_trips.png"),
|
||||
OP.PanelType.VEHICLE: PanelInfo(tr_noop("Vehicle"), VehicleLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_vehicle.png"),
|
||||
@@ -132,12 +125,9 @@ class SettingsLayoutSP(OP.SettingsLayout):
|
||||
rl.draw_rectangle_rec(rect, OP.SIDEBAR_COLOR)
|
||||
|
||||
# Close button
|
||||
close_btn_rect = rl.Rectangle(
|
||||
rect.x + style.ITEM_PADDING * 3, rect.y + style.ITEM_PADDING * 2, style.CLOSE_BTN_SIZE, style.CLOSE_BTN_SIZE
|
||||
)
|
||||
close_btn_rect = rl.Rectangle(rect.x + style.ITEM_PADDING * 3, rect.y + style.ITEM_PADDING * 2, style.CLOSE_BTN_SIZE, style.CLOSE_BTN_SIZE)
|
||||
|
||||
pressed = (rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and
|
||||
rl.check_collision_point_rec(rl.get_mouse_position(), close_btn_rect))
|
||||
pressed = rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and rl.check_collision_point_rec(rl.get_mouse_position(), close_btn_rect)
|
||||
close_color = OP.CLOSE_BTN_PRESSED if pressed else OP.CLOSE_BTN_COLOR
|
||||
rl.draw_rectangle_rounded(close_btn_rect, 1.0, 20, close_color)
|
||||
|
||||
@@ -174,7 +164,7 @@ class SettingsLayoutSP(OP.SettingsLayout):
|
||||
rect.x,
|
||||
self._close_btn_rect.height + style.ITEM_PADDING * 4, # Starting Y position for nav items
|
||||
rect.width,
|
||||
rect.height - 300 # Remaining height after close button
|
||||
rect.height - 300, # Remaining height after close button
|
||||
)
|
||||
|
||||
if self._nav_items:
|
||||
|
||||
@@ -17,7 +17,8 @@ class UIStateSP:
|
||||
self.params = Params()
|
||||
self.sm_services_ext = [
|
||||
"modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP", "backupManagerSP",
|
||||
"gpsLocation", "liveTorqueParameters", "carStateSP", "carParamsSP", "liveDelay"
|
||||
"gpsLocation", "liveTorqueParameters", "carStateSP", "carParamsSP", "liveDelay",
|
||||
"mapdOut"
|
||||
]
|
||||
|
||||
self.sunnylink_state = SunnylinkState()
|
||||
|
||||
@@ -7,13 +7,8 @@ See the LICENSE.md file in the root directory for more details.
|
||||
|
||||
from cereal import messaging, custom
|
||||
from opendbc.car import structs
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.selfdrive.car.cruise import V_CRUISE_MAX
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.dec.dec import DynamicExperimentalController
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.e2e_alerts_helper import E2EAlertsHelper
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.smart_cruise_control.smart_cruise_control import SmartCruiseControl
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.speed_limit_assist import SpeedLimitAssist
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.speed_limit_resolver import SpeedLimitResolver
|
||||
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
|
||||
from openpilot.sunnypilot.models.helpers import get_active_bundle
|
||||
|
||||
@@ -24,11 +19,7 @@ LongitudinalPlanSource = custom.LongitudinalPlanSP.LongitudinalPlanSource
|
||||
class LongitudinalPlannerSP:
|
||||
def __init__(self, CP: structs.CarParams, CP_SP: structs.CarParamsSP, mpc):
|
||||
self.events_sp = EventsSP()
|
||||
self.resolver = SpeedLimitResolver()
|
||||
self.dec = DynamicExperimentalController(CP, mpc)
|
||||
self.scc = SmartCruiseControl()
|
||||
self.resolver = SpeedLimitResolver()
|
||||
self.sla = SpeedLimitAssist(CP, CP_SP)
|
||||
self.generation = int(model_bundle.generation) if (model_bundle := get_active_bundle()) else None
|
||||
self.source = LongitudinalPlanSource.cruise
|
||||
self.e2e_alerts_helper = E2EAlertsHelper()
|
||||
@@ -48,33 +39,16 @@ class LongitudinalPlannerSP:
|
||||
return self.dec.mode()
|
||||
|
||||
def update_targets(self, sm: messaging.SubMaster, v_ego: float, a_ego: float, v_cruise: float) -> tuple[float, float]:
|
||||
CS = sm['carState']
|
||||
v_cruise_cluster_kph = min(CS.vCruiseCluster, V_CRUISE_MAX)
|
||||
v_cruise_cluster = v_cruise_cluster_kph * CV.KPH_TO_MS
|
||||
self.source = LongitudinalPlanSource.cruise
|
||||
self.output_v_target = v_cruise
|
||||
self.output_a_target = a_ego
|
||||
|
||||
long_enabled = sm['carControl'].enabled
|
||||
long_override = sm['carControl'].cruiseControl.override
|
||||
if sm.valid['mapdOut']:
|
||||
suggested_speed = sm['mapdOut'].suggestedSpeed
|
||||
if suggested_speed > 0 and suggested_speed < self.output_v_target:
|
||||
self.output_v_target = suggested_speed
|
||||
self.source = LongitudinalPlanSource.mapd
|
||||
|
||||
# Smart Cruise Control
|
||||
self.scc.update(sm, long_enabled, long_override, v_ego, a_ego, v_cruise)
|
||||
|
||||
# Speed Limit Resolver
|
||||
self.resolver.update(v_ego, sm)
|
||||
|
||||
# Speed Limit Assist
|
||||
has_speed_limit = self.resolver.speed_limit_valid or self.resolver.speed_limit_last_valid
|
||||
self.sla.update(long_enabled, long_override, v_ego, a_ego, v_cruise_cluster, self.resolver.speed_limit,
|
||||
self.resolver.speed_limit_final_last, has_speed_limit, self.resolver.distance, self.events_sp)
|
||||
|
||||
targets = {
|
||||
LongitudinalPlanSource.cruise: (v_cruise, a_ego),
|
||||
LongitudinalPlanSource.sccVision: (self.scc.vision.output_v_target, self.scc.vision.output_a_target),
|
||||
LongitudinalPlanSource.sccMap: (self.scc.map.output_v_target, self.scc.map.output_a_target),
|
||||
LongitudinalPlanSource.speedLimitAssist: (self.sla.output_v_target, self.sla.output_a_target),
|
||||
}
|
||||
|
||||
self.source = min(targets, key=lambda k: targets[k][0])
|
||||
self.output_v_target, self.output_a_target = targets[self.source]
|
||||
return self.output_v_target, self.output_a_target
|
||||
|
||||
def update(self, sm: messaging.SubMaster) -> None:
|
||||
@@ -99,44 +73,6 @@ class LongitudinalPlannerSP:
|
||||
dec.enabled = self.dec.enabled()
|
||||
dec.active = self.dec.active()
|
||||
|
||||
# Smart Cruise Control
|
||||
smartCruiseControl = longitudinalPlanSP.smartCruiseControl
|
||||
# Vision Control
|
||||
sccVision = smartCruiseControl.vision
|
||||
sccVision.state = self.scc.vision.state
|
||||
sccVision.vTarget = float(self.scc.vision.output_v_target)
|
||||
sccVision.aTarget = float(self.scc.vision.output_a_target)
|
||||
sccVision.currentLateralAccel = float(self.scc.vision.current_lat_acc)
|
||||
sccVision.maxPredictedLateralAccel = float(self.scc.vision.max_pred_lat_acc)
|
||||
sccVision.enabled = self.scc.vision.is_enabled
|
||||
sccVision.active = self.scc.vision.is_active
|
||||
# Map Control
|
||||
sccMap = smartCruiseControl.map
|
||||
sccMap.state = self.scc.map.state
|
||||
sccMap.vTarget = float(self.scc.map.output_v_target)
|
||||
sccMap.aTarget = float(self.scc.map.output_a_target)
|
||||
sccMap.enabled = self.scc.map.is_enabled
|
||||
sccMap.active = self.scc.map.is_active
|
||||
|
||||
# Speed Limit
|
||||
speedLimit = longitudinalPlanSP.speedLimit
|
||||
resolver = speedLimit.resolver
|
||||
resolver.speedLimit = float(self.resolver.speed_limit)
|
||||
resolver.speedLimitLast = float(self.resolver.speed_limit_last)
|
||||
resolver.speedLimitFinal = float(self.resolver.speed_limit_final)
|
||||
resolver.speedLimitFinalLast = float(self.resolver.speed_limit_final_last)
|
||||
resolver.speedLimitValid = self.resolver.speed_limit_valid
|
||||
resolver.speedLimitLastValid = self.resolver.speed_limit_last_valid
|
||||
resolver.speedLimitOffset = float(self.resolver.speed_limit_offset)
|
||||
resolver.distToSpeedLimit = float(self.resolver.distance)
|
||||
resolver.source = self.resolver.source
|
||||
assist = speedLimit.assist
|
||||
assist.state = self.sla.state
|
||||
assist.enabled = self.sla.is_enabled
|
||||
assist.active = self.sla.is_active
|
||||
assist.vTarget = float(self.sla.output_v_target)
|
||||
assist.aTarget = float(self.sla.output_a_target)
|
||||
|
||||
# E2E Alerts
|
||||
e2eAlerts = longitudinalPlanSP.e2eAlerts
|
||||
e2eAlerts.greenLightAlert = self.e2e_alerts_helper.green_light_alert
|
||||
|
||||
@@ -8,7 +8,6 @@ from openpilot.system.hardware import PC, TICI
|
||||
from openpilot.system.manager.process import PythonProcess, NativeProcess, DaemonProcess
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
|
||||
from openpilot.sunnypilot.mapd.mapd_manager import MAPD_PATH
|
||||
|
||||
from openpilot.sunnypilot.models.helpers import get_active_model_runner
|
||||
from openpilot.sunnypilot.sunnylink.utils import sunnylink_need_register, sunnylink_ready, use_sunnylink_uploader
|
||||
@@ -92,8 +91,6 @@ def is_stock_model(started, params, CP: car.CarParams) -> bool:
|
||||
"""Check if the active model runner is stock."""
|
||||
return bool(get_active_model_runner(params, not started) == custom.ModelManagerSP.Runner.stock)
|
||||
|
||||
def mapd_ready(started: bool, params: Params, CP: car.CarParams) -> bool:
|
||||
return bool(os.path.exists(Paths.mapd_root()))
|
||||
|
||||
def uploader_ready(started: bool, params: Params, CP: car.CarParams) -> bool:
|
||||
if not params.get_bool("OnroadUploads"):
|
||||
@@ -177,11 +174,10 @@ procs += [
|
||||
PythonProcess("backup_manager", "sunnypilot.sunnylink.backups.manager", and_(only_offroad, sunnylink_ready_shim)),
|
||||
|
||||
# mapd
|
||||
NativeProcess("mapd", Paths.mapd_root(), ["bash", "-c", f"{MAPD_PATH} > /dev/null 2>&1"], mapd_ready),
|
||||
PythonProcess("mapd_manager", "sunnypilot.mapd.mapd_manager", always_run),
|
||||
NativeProcess("mapd", "selfdrive", ["./mapd"], always_run),
|
||||
|
||||
# locationd
|
||||
NativeProcess("locationd_llk", "sunnypilot/selfdrive/locationd", ["./locationd"], only_onroad),
|
||||
#NativeProcess("locationd_llk", "sunnypilot/selfdrive/locationd", ["./locationd"], only_onroad),
|
||||
]
|
||||
|
||||
if os.path.exists("./github_runner.sh"):
|
||||
|
||||
Reference in New Issue
Block a user