mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-11-06 18:43:45 +08:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a3c087c402 | |||
| 4e7f4d7656 | |||
| 638359d1e5 | |||
| 230948763f | |||
| 02ef6e4ce0 | |||
| a1a28e467e | |||
| ef9a842c08 | |||
| c94d1efc92 | |||
| 3f34af4948 | |||
| 2d79ea7de4 | |||
| 41cadd46bd | |||
| 57dd5289e6 | |||
| 543866e14d | |||
| dedb23f139 | |||
| 1764bf124c | |||
| b2efa3d16e | |||
| 62d980101d | |||
| 83beadac8d | |||
| 4087a4f369 | |||
| b57b184c73 | |||
| e65f65cb83 | |||
| 6970795f19 | |||
| c98c359dba | |||
| 54edcadcbb | |||
| 9d32254e23 | |||
| 75d1411f4a | |||
| 60bacd0231 | |||
| 856e31765e | |||
| 10bc6355cd | |||
| 594c37a6ec | |||
| 11867d2219 | |||
| 0aaaddddab | |||
| fe03e9b594 |
@@ -46,6 +46,23 @@ jobs:
|
||||
CHECK_OOB: 0
|
||||
GMMU: 0
|
||||
run: tools/op.sh test -v openpilot/sunnypilot/modeld_v2/tests/test_convert_upstream_pkl.py
|
||||
- name: Resolve commaai's tinygrad pin, which their precompiled chestnut model is built and converted with
|
||||
id: upstream-tg
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
read -r sha url < <(gh api repos/commaai/openpilot/contents/tinygrad_repo --jq '.sha + " " + .submodule_git_url')
|
||||
echo "commaai/openpilot master pins $url@$sha, ours is $(git rev-parse HEAD:tinygrad_repo)"
|
||||
dir="$RUNNER_TEMP/upstream_tinygrad"
|
||||
git init -q "$dir" && git -C "$dir" fetch -q --depth 1 "$url" "$sha" && git -C "$dir" checkout -q FETCH_HEAD
|
||||
echo "path=$dir" >> "$GITHUB_OUTPUT"
|
||||
- name: Convert a pickled USB+AMD jit under commaai's tinygrad, as the chestnut model build does
|
||||
env:
|
||||
DEV: MOCKUSB+AMD
|
||||
CHECK_OOB: 0
|
||||
GMMU: 0
|
||||
PYTHONPATH: ${{ steps.upstream-tg.outputs.path }}:${{ github.workspace }}
|
||||
run: tools/op.sh test -v openpilot/sunnypilot/modeld_v2/tests/test_convert_upstream_pkl.py
|
||||
- name: Where tinygrad moved, for the helper or failure point named above
|
||||
if: failure() && github.event_name == 'pull_request'
|
||||
run: |
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
[submodule "opendbc"]
|
||||
path = opendbc_repo
|
||||
url = https://github.com/sunnypilot/opendbc.git
|
||||
branch = toyotabsmhack
|
||||
[submodule "msgq"]
|
||||
path = msgq_repo
|
||||
url = https://github.com/sunnypilot/msgq.git
|
||||
|
||||
+1
-1
Submodule opendbc_repo updated: 53afb97340...b5929417d7
@@ -369,7 +369,6 @@ struct CarParamsSP @0x80ae746ee2596b11 {
|
||||
pcmCruiseSpeed @3 :Bool;
|
||||
intelligentCruiseButtonManagementAvailable @4 :Bool;
|
||||
enableGasInterceptor @5 :Bool;
|
||||
smoothStopDecelRate @6 :Float32;
|
||||
|
||||
neuralNetworkLateralControl @2 :NeuralNetworkLateralControl;
|
||||
|
||||
|
||||
@@ -150,7 +150,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
|
||||
// --- sunnypilot params --- //
|
||||
{"AccelBoostSP", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||
{"SmoothStopSP", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||
{"ApiCache_DriveStats", {PERSISTENT, JSON}},
|
||||
{"AutoLaneChangeBsmDelay", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"AutoLaneChangeTimer", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
@@ -201,7 +200,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"ShowTurnSignals", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"TripSummary", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||
{"TrueVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
// MADS params
|
||||
@@ -247,7 +245,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"TeslaCoopSteering", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"TeslaMadsScreenButton", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
{"ToyotaEnhancedBsm", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"ToyotaEnforceStockLongitudinal", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"ToyotaStopAndGoHack", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"ToyotaTSS2Smooth", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
@@ -57,7 +57,6 @@ class Controls(ControlsExt):
|
||||
self.calibrated_pose: Pose | None = None
|
||||
|
||||
self.LoC = LongControl(self.CP, self.CP_SP)
|
||||
self.LoC = ControlsExt.initialize_longitudinal_control(self, self.LoC)
|
||||
self.VM = VehicleModel(self.CP)
|
||||
self.LaC: LatControl
|
||||
if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
|
||||
|
||||
@@ -10,7 +10,6 @@ from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView
|
||||
from openpilot.selfdrive.ui.ui_state import device, ui_state
|
||||
from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow
|
||||
from openpilot.selfdrive.ui.body.layouts.onroad import BodyLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.trip_summary import TripSummary
|
||||
|
||||
if gui_app.sunnypilot_ui():
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.settings import SettingsLayoutSP as SettingsLayout
|
||||
@@ -32,7 +31,6 @@ class MainLayout(Widget):
|
||||
self._sidebar = Sidebar()
|
||||
self._current_mode = MainState.HOME
|
||||
self._prev_onroad = False
|
||||
self._trip_summary = TripSummary()
|
||||
|
||||
# Initialize layouts
|
||||
self._home_layout = HomeLayout()
|
||||
|
||||
@@ -10,7 +10,6 @@ from openpilot.selfdrive.ui.body.layouts.onroad import BodyLayout
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.scroller import Scroller
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.trip_summary import TripSummary
|
||||
|
||||
if gui_app.sunnypilot_ui():
|
||||
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.settings import SettingsLayoutSP as SettingsLayout
|
||||
@@ -58,7 +57,6 @@ class MiciMainLayout(Scroller):
|
||||
self._setup_callbacks()
|
||||
|
||||
gui_app.add_nav_stack_tick(self._handle_transitions)
|
||||
self._trip_summary = TripSummary()
|
||||
gui_app.push_widget(self)
|
||||
|
||||
# Start onboarding if terms or training not completed, make sure to push after self
|
||||
|
||||
@@ -22,7 +22,6 @@ from openpilot.common.transformations.orientation import rot_from_euler
|
||||
from enum import IntEnum
|
||||
|
||||
if gui_app.sunnypilot_ui():
|
||||
from openpilot.selfdrive.ui.sunnypilot.mici.onroad.augmented_road_view import AugmentedRoadViewSP
|
||||
from openpilot.selfdrive.ui.sunnypilot.mici.onroad.hud_renderer import HudRendererSP as HudRenderer
|
||||
from openpilot.selfdrive.ui.sunnypilot.ui_state import OnroadTimerStatus
|
||||
|
||||
@@ -157,10 +156,9 @@ class BookmarkIcon(Widget):
|
||||
rl.draw_texture_ex(icon, rl.Vector2(icon_x, icon_y), 0.0, 1.0, rl.Color(255, 255, 255, round(255 * alpha)))
|
||||
|
||||
|
||||
class AugmentedRoadView(CameraView, AugmentedRoadViewSP):
|
||||
class AugmentedRoadView(CameraView):
|
||||
def __init__(self, bookmark_callback=None, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_NARROW_ROAD):
|
||||
super().__init__("camerad", stream_type)
|
||||
AugmentedRoadViewSP.__init__(self)
|
||||
self._bookmark_callback = bookmark_callback
|
||||
self._set_placeholder_color(rl.BLACK)
|
||||
|
||||
@@ -277,7 +275,7 @@ class AugmentedRoadView(CameraView, AugmentedRoadViewSP):
|
||||
if show_navigation:
|
||||
self._navigation.render(rl.Rectangle(self._content_rect.x + self._content_rect.width, self.rect.y, PANEL_WIDTH, self.rect.height))
|
||||
else:
|
||||
AugmentedRoadViewSP.render_side_panel(self, self.rect)
|
||||
self._confidence_ball.render(self.rect)
|
||||
|
||||
self._bookmark_icon.render(self.rect)
|
||||
|
||||
|
||||
@@ -63,13 +63,6 @@ class CruiseLayout(Widget):
|
||||
"Eases off in curves and when sunnypilot slows or stops. Never goes past your set speed, and lead-car following still comes first."),
|
||||
param="AccelBoostSP")
|
||||
|
||||
self.smooth_stop_toggle = toggle_item_sp(
|
||||
title=tr("Smooth Stop"),
|
||||
description=tr("Brakes gently as sunnypilot comes to a final stop to reduce the lurch at the end. " +
|
||||
"Applies to all vehicles with sunnypilot longitudinal control."),
|
||||
param="SmoothStopSP",
|
||||
callback=lambda _: ui_state.params.put_bool("OnroadCycleRequested", True))
|
||||
|
||||
self.custom_acc_toggle = toggle_item_sp(
|
||||
title=tr("Custom ACC Speed Increments"),
|
||||
description="",
|
||||
@@ -100,7 +93,6 @@ class CruiseLayout(Widget):
|
||||
self.scc_v_toggle,
|
||||
self.scc_m_toggle,
|
||||
self.accel_boost_toggle,
|
||||
self.smooth_stop_toggle,
|
||||
self.custom_acc_toggle,
|
||||
self.custom_acc_short_increment,
|
||||
self.custom_acc_long_increment,
|
||||
@@ -152,7 +144,6 @@ class CruiseLayout(Widget):
|
||||
self.icbm_toggle.show_description(True)
|
||||
|
||||
self.accel_boost_toggle.action_item.set_enabled(has_long)
|
||||
self.smooth_stop_toggle.action_item.set_enabled(has_long and not ui_state.engaged)
|
||||
|
||||
if has_long or has_icbm:
|
||||
self.custom_acc_toggle.action_item.set_enabled(((has_long and not ui_state.CP.pcmCruise) or has_icbm) and ui_state.is_offroad())
|
||||
@@ -170,7 +161,6 @@ class CruiseLayout(Widget):
|
||||
has_icbm = has_long = False
|
||||
self.icbm_toggle.action_item.set_enabled(False)
|
||||
self.accel_boost_toggle.action_item.set_enabled(False)
|
||||
self.smooth_stop_toggle.action_item.set_enabled(False)
|
||||
self.icbm_toggle.set_description(tr(ONROAD_ONLY_DESCRIPTION))
|
||||
|
||||
show_custom_acc_desc = False
|
||||
|
||||
@@ -12,16 +12,10 @@ from openpilot.system.ui.widgets import DialogResult
|
||||
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
|
||||
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp
|
||||
from opendbc.car.toyota.values import ToyotaFlags
|
||||
from opendbc.sunnypilot.car.toyota.values import ToyotaFlagsSP
|
||||
|
||||
|
||||
ONROAD_ONLY_DESCRIPTION = tr_noop("Start the vehicle to check vehicle compatibility.")
|
||||
SNG_HACK_UNAVAILABLE = tr_noop("sunnypilot Longitudinal Control must be available and enabled for your vehicle to use this feature.")
|
||||
BSM_UNAVAILABLE = tr_noop("Enhanced Blind Spot Monitoring requires a TSS2 Toyota/Lexus without SecOC, " +
|
||||
"or a smartDSU with sunnypilot longitudinal control.")
|
||||
BSM_STOCK_LONG_UNAVAILABLE = tr_noop("Enhanced Blind Spot Monitoring is not available while Enforce Factory Longitudinal Control is enabled.")
|
||||
BSM_REQUIRED = tr_noop("This vehicle does not report blind spot status over the standard CAN signals. " +
|
||||
"Enhanced Blind Spot Monitoring is required for blind spot detection.")
|
||||
PLATFORM_UNAVAILABLE = tr_noop("This feature is currently not available on this platform.")
|
||||
|
||||
DESCRIPTIONS = {
|
||||
@@ -32,10 +26,6 @@ DESCRIPTIONS = {
|
||||
'sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. ' +
|
||||
'This feature is only applicable to certain models that are able to use longitudinal control. This is an alpha feature. Use at your own risk.'
|
||||
),
|
||||
'enhanced_bsm': tr_noop(
|
||||
'sunnypilot will poll the blind spot monitor diagnostics directly from the radar. Use this on vehicles whose blind spot monitors ' +
|
||||
'do not report blind spot status over the standard CAN signals. This is an alpha feature. Use at your own risk.'
|
||||
),
|
||||
'tss2_smooth': tr_noop(
|
||||
'Enable smoother longitudinal control for TSS2 vehicles. ' +
|
||||
'Uses a custom speed-dependent integral gain curve for smoother traffic driving.'
|
||||
@@ -63,14 +53,6 @@ class ToyotaSettings(BrandSettings):
|
||||
enabled=lambda: not ui_state.engaged,
|
||||
)
|
||||
|
||||
self.enhanced_bsm = toggle_item_sp(
|
||||
lambda: tr("Enhanced Blind Spot Monitoring (Alpha)"),
|
||||
description=lambda: tr(DESCRIPTIONS["enhanced_bsm"]),
|
||||
initial_state=ui_state.params.get_bool("ToyotaEnhancedBsm"),
|
||||
callback=self._on_enable_enhanced_bsm,
|
||||
enabled=lambda: not ui_state.engaged,
|
||||
)
|
||||
|
||||
self.tss2_smooth = toggle_item_sp(
|
||||
lambda: tr("TSS2 Smooth Longitudinal (Alpha)"),
|
||||
description=lambda: tr(DESCRIPTIONS["tss2_smooth"]),
|
||||
@@ -82,7 +64,6 @@ class ToyotaSettings(BrandSettings):
|
||||
self.items = [
|
||||
self.enforce_stock_longitudinal,
|
||||
self.stop_and_go_hack,
|
||||
self.enhanced_bsm,
|
||||
self.tss2_smooth,
|
||||
]
|
||||
|
||||
@@ -149,25 +130,6 @@ class ToyotaSettings(BrandSettings):
|
||||
ui_state.params.put_bool("ToyotaStopAndGoHack", False)
|
||||
ui_state.params.put_bool("OnroadCycleRequested", True)
|
||||
|
||||
def _on_enable_enhanced_bsm(self, state: bool):
|
||||
if state:
|
||||
def confirm_callback(result: int):
|
||||
if result == DialogResult.CONFIRM:
|
||||
ui_state.params.put_bool("ToyotaEnhancedBsm", True)
|
||||
ui_state.params.put_bool("OnroadCycleRequested", True)
|
||||
else:
|
||||
self.enhanced_bsm.action_item.set_state(False)
|
||||
|
||||
content = (f"<h1>{self.enhanced_bsm.title}</h1><br>" +
|
||||
f"<p>{self.enhanced_bsm.description}</p>")
|
||||
|
||||
dlg = ConfirmDialog(content, tr("Enable"), rich=True, callback=confirm_callback)
|
||||
gui_app.push_widget(dlg)
|
||||
|
||||
else:
|
||||
ui_state.params.put_bool("ToyotaEnhancedBsm", False)
|
||||
ui_state.params.put_bool("OnroadCycleRequested", True)
|
||||
|
||||
def update_settings(self):
|
||||
if ui_state.CP is not None:
|
||||
longitudinal = ui_state.CP.openpilotLongitudinalControl
|
||||
@@ -188,31 +150,6 @@ class ToyotaSettings(BrandSettings):
|
||||
if show_desc:
|
||||
self.stop_and_go_hack.show_description(True)
|
||||
|
||||
# Enhanced BSM needs the radar diagnostic address, which is only in the TX allowlist for
|
||||
# TSS2 cars without SecOC, or for TSS-P cars whose smartDSU lets sunnypilot control
|
||||
# longitudinal. Enforcing factory longitudinal control rules both of those out.
|
||||
sdsu_bsm_supported = (longitudinal and ui_state.CP_SP is not None and
|
||||
bool(ui_state.CP_SP.flags & ToyotaFlagsSP.SMART_DSU))
|
||||
bsm_supported = (not bool(ui_state.CP.flags & ToyotaFlags.SECOC) and
|
||||
(bool(ui_state.CP.flags & ToyotaFlags.TSS2) or sdsu_bsm_supported))
|
||||
bsm_available = bsm_supported and not enforce_stock
|
||||
self.enhanced_bsm.action_item.set_enabled(bsm_available and not ui_state.engaged)
|
||||
|
||||
bsm_desc = tr(DESCRIPTIONS["enhanced_bsm"])
|
||||
if not bsm_supported:
|
||||
self.enhanced_bsm.action_item.set_state(False)
|
||||
bsm_desc = "<b>" + tr(BSM_UNAVAILABLE) + "</b>\n\n" + bsm_desc
|
||||
elif enforce_stock:
|
||||
self.enhanced_bsm.action_item.set_state(False)
|
||||
bsm_desc = "<b>" + tr(BSM_STOCK_LONG_UNAVAILABLE) + "</b>\n\n" + bsm_desc
|
||||
elif ui_state.CP_SP is not None and ui_state.CP_SP.flags & ToyotaFlagsSP.SP_NEED_DEBUG_BSM:
|
||||
bsm_desc = "<b>" + tr(BSM_REQUIRED) + "</b>\n\n" + bsm_desc
|
||||
|
||||
if self.enhanced_bsm.description != bsm_desc:
|
||||
self.enhanced_bsm.set_description(bsm_desc)
|
||||
if not bsm_available:
|
||||
self.enhanced_bsm.show_description(True)
|
||||
|
||||
tss2 = bool(ui_state.CP.flags & ToyotaFlags.TSS2)
|
||||
if longitudinal and not enforce_stock and tss2:
|
||||
self.tss2_smooth.action_item.set_enabled(not ui_state.engaged)
|
||||
@@ -236,12 +173,6 @@ class ToyotaSettings(BrandSettings):
|
||||
self.stop_and_go_hack.set_description(new_desc)
|
||||
self.stop_and_go_hack.show_description(True)
|
||||
|
||||
self.enhanced_bsm.action_item.set_enabled(False)
|
||||
bsm_desc = "<b>" + tr(ONROAD_ONLY_DESCRIPTION) + "</b>\n\n" + tr(DESCRIPTIONS["enhanced_bsm"])
|
||||
if self.enhanced_bsm.description != bsm_desc:
|
||||
self.enhanced_bsm.set_description(bsm_desc)
|
||||
self.enhanced_bsm.show_description(True)
|
||||
|
||||
self.tss2_smooth.action_item.set_enabled(False)
|
||||
new_desc = "<b>" + tr(ONROAD_ONLY_DESCRIPTION) + "</b>\n\n" + tr(DESCRIPTIONS["tss2_smooth"])
|
||||
if self.tss2_smooth.description != new_desc:
|
||||
|
||||
@@ -72,11 +72,6 @@ class VisualsLayout(Widget):
|
||||
"It is the driver's responsibility to observe their environment and make decisions accordingly."),
|
||||
None,
|
||||
),
|
||||
"TripSummary": (
|
||||
lambda: tr("Trip Summary"),
|
||||
tr("Show a summary of distance and engagement when you shift into park after a drive."),
|
||||
None,
|
||||
),
|
||||
"TrueVEgoUI": (
|
||||
lambda: tr("Speedometer: Always Display True Speed"),
|
||||
tr("For applicable vehicles, always display the true vehicle current speed from wheel speed sensors."),
|
||||
|
||||
@@ -1,199 +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 time
|
||||
import pyray as rl
|
||||
|
||||
from opendbc.car.structs import car
|
||||
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.selfdrive.ui.onroad.augmented_road_view import BORDER_COLORS
|
||||
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.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
|
||||
AUTO_DISMISS = 20.
|
||||
FADE_IN, FADE_OUT = 0.35, 0.3
|
||||
LAT = (UIStatus.ENGAGED, UIStatus.LAT_ONLY)
|
||||
LONG = (UIStatus.ENGAGED, UIStatus.LONG_ONLY)
|
||||
GEAR = car.CarState.GearShifter
|
||||
FORWARD = (GEAR.drive, GEAR.sport, GEAR.low, GEAR.brake, GEAR.eco, GEAR.manumatic)
|
||||
|
||||
|
||||
class TripTracker:
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.segments: list[list] = []
|
||||
self.seconds = 0.
|
||||
self.takeovers = 0
|
||||
self._t = None
|
||||
self._status = None
|
||||
|
||||
def update(self, driving: bool):
|
||||
now = time.monotonic()
|
||||
if not driving:
|
||||
self._t = None
|
||||
self._status = None
|
||||
return
|
||||
dt = 0. if self._t is None else min(now - self._t, 0.5)
|
||||
self._t = now
|
||||
self.seconds += dt
|
||||
if self._status not in (None, UIStatus.DISENGAGED) and ui_state.status == UIStatus.DISENGAGED:
|
||||
self.takeovers += 1
|
||||
self._status = ui_state.status
|
||||
d = max(ui_state.sm["carState"].vEgo, 0.) * dt
|
||||
if d <= 0.:
|
||||
return
|
||||
if self.segments and self.segments[-1][0] == ui_state.status:
|
||||
self.segments[-1][1] += d
|
||||
else:
|
||||
self.segments.append([ui_state.status, d])
|
||||
|
||||
def total(self, statuses=None) -> float:
|
||||
return sum(m for s, m in self.segments if statuses is None or s in statuses)
|
||||
|
||||
|
||||
class TripSummary:
|
||||
def __init__(self):
|
||||
self._trip = TripTracker()
|
||||
self._dialog: TripSummaryDialog | None = None
|
||||
self._gear = None
|
||||
self._drove = False
|
||||
gui_app.add_nav_stack_tick(self._tick)
|
||||
|
||||
def _tick(self):
|
||||
gear = ui_state.sm["carState"].gearShifter if ui_state.started else None
|
||||
self._trip.update(gear not in (None, GEAR.park))
|
||||
self._drove |= gear in FORWARD
|
||||
if gear == self._gear:
|
||||
return
|
||||
self._gear = gear
|
||||
|
||||
if gear not in (None, GEAR.park):
|
||||
self._pop()
|
||||
elif gear == GEAR.park and self._drove and ui_state.trip_summary and not ui_state.is_body and self._trip.total() > 0.:
|
||||
self._dialog = TripSummaryDialog(self._trip, gui_app.get_active_widget(), self._pop)
|
||||
gui_app.push_widget(self._dialog)
|
||||
if gear is None:
|
||||
self._trip.reset()
|
||||
if gear in (None, GEAR.park):
|
||||
self._drove = False
|
||||
|
||||
def _pop(self):
|
||||
if self._dialog is not None and gui_app.get_active_widget() is self._dialog:
|
||||
gui_app.pop_widget()
|
||||
self._dialog = None
|
||||
|
||||
|
||||
def _ease(t: float) -> float:
|
||||
t = min(max(t, 0.), 1.)
|
||||
return 1 - (1 - t) ** 3
|
||||
|
||||
|
||||
def _fade(c, a: float) -> rl.Color:
|
||||
r, g, b, al = c if isinstance(c, tuple) else (c.r, c.g, c.b, c.a)
|
||||
return rl.Color(r, g, b, int(al * a))
|
||||
|
||||
|
||||
class TripSummaryDialog(Widget):
|
||||
def __init__(self, trip: TripTracker, below: Widget | None, on_closed):
|
||||
super().__init__()
|
||||
self._below = below
|
||||
self._on_closed = on_closed
|
||||
self._segments = segments = trip.segments
|
||||
total = sum(m for _, m in segments)
|
||||
|
||||
def pct(statuses):
|
||||
return 100 * sum(m for s, m in segments if s in statuses) / total
|
||||
|
||||
dist = total * (CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH) / 3600
|
||||
dist_fmt = "{:.1f}" if dist < 9.95 else "{:.0f}"
|
||||
self._stats = [
|
||||
(dist, dist_fmt, "km" if ui_state.is_metric else "mi", "driven"),
|
||||
(pct(LAT + LONG), "{:.0f}", "%", "engaged"),
|
||||
(pct(LAT), "{:.0f}", "%", "lateral"),
|
||||
(pct(LONG), "{:.0f}", "%", "longitudinal"),
|
||||
]
|
||||
mins = round(trip.seconds / 60)
|
||||
self._details = [
|
||||
(f"{mins // 60}h {mins % 60}" if mins >= 60 else f"{mins}", "min"),
|
||||
(f"{trip.takeovers}", "takeover" if trip.takeovers == 1 else "takeovers"),
|
||||
]
|
||||
self._bold = gui_app.font(FontWeight.BOLD)
|
||||
self._semi = gui_app.font(FontWeight.SEMI_BOLD)
|
||||
self._normal = gui_app.font(FontWeight.NORMAL)
|
||||
self._big = gui_app.big_ui()
|
||||
self._shown_t = rl.get_time()
|
||||
self._closing_t: float | None = None
|
||||
self.set_click_callback(self.close)
|
||||
|
||||
def close(self):
|
||||
if self._closing_t is None:
|
||||
self._closing_t = rl.get_time()
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
t = rl.get_time() - self._shown_t
|
||||
if self._closing_t is None and t > AUTO_DISMISS:
|
||||
self.close()
|
||||
out = 0. if self._closing_t is None else (rl.get_time() - self._closing_t) / FADE_OUT
|
||||
if out >= 1.:
|
||||
self._on_closed()
|
||||
return
|
||||
bg = 1 - _ease(out)
|
||||
a = _ease(t / FADE_IN) * bg
|
||||
|
||||
if self._big and self._below is not None and bg < 1.:
|
||||
self._below.render(rect)
|
||||
rl.draw_rectangle_rec(rect, _fade(rl.BLACK, bg))
|
||||
|
||||
k = rect.height / 1080 * (1.0 if self._big else 1.22)
|
||||
pad = 120 * k
|
||||
x, w = rect.x + pad, rect.width - 2 * pad
|
||||
y = rect.y + (rect.height - 850 * k) / 2
|
||||
|
||||
rl.draw_text_ex(self._bold, "trip summary", rl.Vector2(x, y), int(150 * k), 0, _fade(rl.WHITE, a))
|
||||
y += 260 * k
|
||||
|
||||
col_w = w / len(self._stats)
|
||||
for i, (target, fmt, unit, label) in enumerate(self._stats):
|
||||
cx = x + col_w * (i + 0.5)
|
||||
v = measure_text_cached(self._bold, fmt.format(target), int(170 * k))
|
||||
u = measure_text_cached(self._semi, unit, int(80 * k))
|
||||
vx = cx - (v.x + 10 * k + u.x) / 2
|
||||
rl.draw_text_ex(self._bold, fmt.format(target), rl.Vector2(vx, y), int(170 * k), 0, _fade(rl.WHITE, a))
|
||||
rl.draw_text_ex(self._semi, unit, rl.Vector2(vx + v.x + 10 * k, y + 75 * k), int(80 * k), 0, _fade(rl.WHITE, a))
|
||||
lb = measure_text_cached(self._normal, label, int(70 * k))
|
||||
rl.draw_text_ex(self._normal, label, rl.Vector2(cx - lb.x / 2, y + 180 * k), int(70 * k), 0, _fade(rl.Color(200, 200, 200, 255), a))
|
||||
y += 330 * k
|
||||
|
||||
bar = rl.Rectangle(x, y, w, 90 * k)
|
||||
total = sum(m for _, m in self._segments)
|
||||
sx = bar.x
|
||||
for status, m in self._segments:
|
||||
sw = bar.width * m / total
|
||||
rl.draw_rectangle_rec(rl.Rectangle(sx, bar.y, min(sw + 1, bar.x + bar.width - sx), bar.height), _fade(BORDER_COLORS[status], a))
|
||||
sx += sw
|
||||
rl.draw_rectangle_rounded_lines_ex(bar, 1.0, 20, 30 * k, _fade(rl.BLACK, a))
|
||||
|
||||
y += (90 + 70) * k
|
||||
|
||||
size = int(90 * k)
|
||||
parts = []
|
||||
for i, (value, unit) in enumerate(self._details):
|
||||
if i:
|
||||
parts.append((" ", self._normal, None))
|
||||
parts += [(value, self._bold, rl.WHITE), (" " + unit, self._normal, rl.Color(170, 170, 170, 255))]
|
||||
widths = [measure_text_cached(f, t, size).x for t, f, _ in parts]
|
||||
lx = x + (w - sum(widths)) / 2
|
||||
for (text, font, color), tw in zip(parts, widths, strict=True):
|
||||
if color is None:
|
||||
rl.draw_circle(int(lx + tw / 2), int(y + size * 0.6), 12 * k, _fade(rl.Color(110, 110, 110, 255), a))
|
||||
else:
|
||||
rl.draw_text_ex(font, text, rl.Vector2(lx, y), size, 0, _fade(color, a))
|
||||
lx += tw
|
||||
@@ -1,95 +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 math
|
||||
import pyray as rl
|
||||
|
||||
from openpilot.cereal import log
|
||||
from openpilot.common.filter_simple import FirstOrderFilter
|
||||
from openpilot.selfdrive.ui.mici.onroad import SIDE_PANEL_WIDTH
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.accel_boost import get_boost_max
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
|
||||
BAR_WIDTH = 26
|
||||
TRACK_TOP_RATIO = 0.08
|
||||
TRACK_BOTTOM_RATIO = 0.92
|
||||
CAP_BLEND_START = 0.45
|
||||
MAX_BOOST_BLEND = 0.55
|
||||
FADE_THRESHOLD = 0.01
|
||||
|
||||
COLOR_TRACK = rl.Color(255, 255, 255, 28)
|
||||
COLOR_FILL = rl.Color(0, 230, 120, 255)
|
||||
|
||||
|
||||
def get_boost_fraction(boost: float, max_boost: float) -> float:
|
||||
if max_boost <= 0.0:
|
||||
return 0.0
|
||||
return min(max(boost / max_boost, 0.0), 1.0)
|
||||
|
||||
|
||||
def lighten(color: rl.Color, amount: float) -> rl.Color:
|
||||
amount = min(max(amount, 0.0), 1.0)
|
||||
return rl.Color(int(color.r + (255 - color.r) * amount),
|
||||
int(color.g + (255 - color.g) * amount),
|
||||
int(color.b + (255 - color.b) * amount), color.a)
|
||||
|
||||
|
||||
def get_boost_color(fraction: float) -> rl.Color:
|
||||
blend = max(0.0, (fraction - CAP_BLEND_START) / (1.0 - CAP_BLEND_START))
|
||||
return lighten(COLOR_FILL, MAX_BOOST_BLEND * blend)
|
||||
|
||||
|
||||
class AccelBoostIndicator(Widget):
|
||||
def __init__(self, panel_width: float = SIDE_PANEL_WIDTH, bar_width: float = BAR_WIDTH):
|
||||
super().__init__()
|
||||
self._panel_width = panel_width
|
||||
self._bar_width = bar_width
|
||||
fps = max(gui_app.target_fps, 1)
|
||||
self._boost_filter = FirstOrderFilter(0.0, 0.15, 1 / fps)
|
||||
self._alpha_filter = FirstOrderFilter(0.0, 0.1, 1 / fps)
|
||||
self._max_boost_filter = FirstOrderFilter(get_boost_max(), 0.25, 1 / fps)
|
||||
|
||||
@property
|
||||
def holding_panel(self) -> bool:
|
||||
return self._alpha_filter.x > FADE_THRESHOLD
|
||||
|
||||
def _update_state(self):
|
||||
sm = ui_state.sm
|
||||
visible = (ui_state.accel_boost and ui_state.has_longitudinal_control and sm.alive['selfdriveState'] and
|
||||
sm.alive['longitudinalPlan'] and sm['selfdriveState'].enabled)
|
||||
plan = sm['longitudinalPlan']
|
||||
applied = visible and plan.longitudinalPlanSource == log.LongitudinalPlan.LongitudinalPlanSource.e2e
|
||||
self._boost_filter.update(float(plan.accelBoost) if applied else 0.0)
|
||||
self._alpha_filter.update(1.0 if visible else 0.0)
|
||||
|
||||
if sm.alive['selfdriveState']:
|
||||
self._max_boost_filter.update(get_boost_max(sm['selfdriveState'].personality))
|
||||
|
||||
def _render(self, _):
|
||||
alpha = self._alpha_filter.x
|
||||
if alpha < FADE_THRESHOLD:
|
||||
return
|
||||
|
||||
panel_x = max(self.rect.x, self.rect.x + self.rect.width - self._panel_width)
|
||||
bar_x = panel_x + (self._panel_width - self._bar_width) / 2
|
||||
track_top = self.rect.y + self.rect.height * TRACK_TOP_RATIO
|
||||
track = rl.Rectangle(bar_x, track_top, self._bar_width,
|
||||
max(1.0, self.rect.height * (TRACK_BOTTOM_RATIO - TRACK_TOP_RATIO)))
|
||||
rl.draw_rectangle_rounded(track, 0.5, 12,
|
||||
rl.Color(COLOR_TRACK.r, COLOR_TRACK.g, COLOR_TRACK.b, int(COLOR_TRACK.a * alpha)))
|
||||
|
||||
fraction = get_boost_fraction(self._boost_filter.x, self._max_boost_filter.x)
|
||||
fill_h = fraction * track.height
|
||||
if fill_h < 1.0:
|
||||
return
|
||||
|
||||
fill_color = get_boost_color(fraction)
|
||||
tip = track.y + track.height - fill_h
|
||||
rl.begin_scissor_mode(int(track.x), int(tip), int(math.ceil(track.width)) + 1, int(math.ceil(fill_h)))
|
||||
rl.draw_rectangle_rounded(track, 0.5, 12, rl.Color(fill_color.r, fill_color.g, fill_color.b, int(235 * alpha)))
|
||||
rl.end_scissor_mode()
|
||||
@@ -1,19 +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 pyray as rl
|
||||
|
||||
from openpilot.selfdrive.ui.sunnypilot.mici.onroad.accel_boost_indicator import AccelBoostIndicator
|
||||
|
||||
|
||||
class AugmentedRoadViewSP:
|
||||
def __init__(self):
|
||||
self._accel_boost_indicator = AccelBoostIndicator()
|
||||
|
||||
def render_side_panel(self, rect: rl.Rectangle) -> None:
|
||||
self._accel_boost_indicator.render(rect)
|
||||
if not self._accel_boost_indicator.holding_panel:
|
||||
self._confidence_ball.render(rect)
|
||||
@@ -1,36 +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.
|
||||
"""
|
||||
from openpilot.cereal import log
|
||||
from openpilot.common.test import OpenpilotTestCase
|
||||
from openpilot.selfdrive.ui.sunnypilot.mici.onroad.accel_boost_indicator import (
|
||||
CAP_BLEND_START, COLOR_FILL, get_boost_color, get_boost_fraction,
|
||||
)
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.accel_boost import ACCEL_BOOST_MAX, get_boost_max
|
||||
|
||||
|
||||
class TestAccelBoostIndicator(OpenpilotTestCase):
|
||||
def test_bar_fills_at_the_active_profile_cap(self):
|
||||
for personality in (log.LongitudinalPersonality.aggressive, log.LongitudinalPersonality.standard,
|
||||
log.LongitudinalPersonality.relaxed):
|
||||
cap = get_boost_max(personality)
|
||||
assert get_boost_fraction(0.0, cap) == 0.0
|
||||
assert get_boost_fraction(cap * 0.5, cap) == 0.5
|
||||
assert get_boost_fraction(cap, cap) == 1.0
|
||||
|
||||
assert get_boost_fraction(max(ACCEL_BOOST_MAX), min(ACCEL_BOOST_MAX)) == 1.0
|
||||
assert get_boost_fraction(0.0, 0.0) == 0.0
|
||||
assert get_boost_fraction(-1.0, get_boost_max()) == 0.0
|
||||
|
||||
def test_fill_lightens_as_boost_builds(self):
|
||||
base = get_boost_color(0.0)
|
||||
assert (base.r, base.g, base.b) == (COLOR_FILL.r, COLOR_FILL.g, COLOR_FILL.b)
|
||||
assert (get_boost_color(CAP_BLEND_START).r, get_boost_color(CAP_BLEND_START).b) == (COLOR_FILL.r, COLOR_FILL.b)
|
||||
|
||||
full = get_boost_color(1.0)
|
||||
assert full.r > base.r
|
||||
assert full.b > base.b
|
||||
assert full.g > full.r and full.g > full.b
|
||||
@@ -44,12 +44,10 @@ class UIStateSP:
|
||||
self.screensaver = ScreenSaverSP(params=self.params)
|
||||
self.screensaver_enabled: bool = False
|
||||
|
||||
self.accel_boost: bool = False
|
||||
self.active_bundle = None
|
||||
self.model_runner_tinygrad: bool = False
|
||||
self.active_runner_tinygrad: bool = False
|
||||
self.blindspot: bool = False
|
||||
self.trip_summary: bool = True
|
||||
self.chevron_metrics = None
|
||||
self.custom_interactive_timeout: int = 0
|
||||
self.developer_ui = None
|
||||
@@ -173,7 +171,6 @@ class UIStateSP:
|
||||
# stock only counts the default big model's compiled pkl. a downloaded big bundle runs on the
|
||||
# chestnut just the same, so ChestnutState has to see it as available too.
|
||||
self.chestnut_compiled = self.chestnut_compiled or self.model_runner_tinygrad
|
||||
self.accel_boost = self.params.get_bool("AccelBoostSP")
|
||||
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)
|
||||
@@ -188,7 +185,6 @@ class UIStateSP:
|
||||
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.trip_summary = self.params.get_bool("TripSummary")
|
||||
self.enforce_torque_control = self.params.get_bool("EnforceTorqueControl")
|
||||
self.custom_torque_params = self.params.get_bool("CustomTorqueParams")
|
||||
self.torque_override_enabled = self.params.get_bool("TorqueParamsOverrideEnabled")
|
||||
|
||||
@@ -66,47 +66,75 @@ def _patched(buf, topo) -> tuple[bytearray, dict]:
|
||||
return blob, rows
|
||||
|
||||
|
||||
def _evaluate(u, env):
|
||||
def _packed_args(program) -> dict[int, int]:
|
||||
from tinygrad.device import TinyELF
|
||||
params = getattr(program.arg, "params", None) or ()
|
||||
return {off: p.arg.slot for (off, _), p in zip(TinyELF.iter_sig(tuple((None, p.addrspace, p.dtype, ()) for p in params)), params, strict=True)}
|
||||
|
||||
|
||||
def _outer_slot(u, packed: dict[int, int]) -> int | None:
|
||||
from tinygrad.uop.ops import Ops
|
||||
if u.op is Ops.PARAM:
|
||||
return None if packed else u.arg.slot
|
||||
if u.op.name == "REFPARAM":
|
||||
return u.arg.slot
|
||||
if packed and u.op is Ops.LOAD and u.src[0].op is Ops.INDEX and _root(u.src[0].src[0]).op is Ops.PARAM:
|
||||
index = _evaluate(u.src[0].src[1], {}, packed)
|
||||
return None if index is None else packed.get(index * u.dtype.itemsize)
|
||||
return None
|
||||
|
||||
|
||||
def _evaluate(u, env, packed: dict[int, int]):
|
||||
from tinygrad.uop.ops import GroupOp, Ops, exec_alu
|
||||
if u.op in (Ops.AFTER, Ops.CAST, Ops.BITCAST):
|
||||
return _evaluate(u.src[0], env)
|
||||
return _evaluate(u.src[0], env, packed)
|
||||
if u.op is Ops.CONST:
|
||||
return u.arg
|
||||
if u.op in (Ops.PARAM, Ops.RANGE):
|
||||
return env.get(u.arg.slot if u.op is Ops.PARAM else u)
|
||||
if u.op is Ops.RANGE:
|
||||
return env.get(u)
|
||||
if (slot := _outer_slot(u, packed)) is not None:
|
||||
return env.get(slot)
|
||||
if u.op is Ops.LOAD:
|
||||
table, index = _root(u.src[0].src[0]), _evaluate(u.src[0].src[1], env)
|
||||
table, index = _root(u.src[0].src[0]), _evaluate(u.src[0].src[1], env, packed)
|
||||
return struct.unpack_from(u.dtype.fmt, table.arg, index * u.dtype.itemsize)[0] if table.op is Ops.BINARY and index is not None else None
|
||||
if u.op in GroupOp.ALU and None not in (values := [_evaluate(x, env) for x in u.src]):
|
||||
if u.op in GroupOp.ALU and None not in (values := [_evaluate(x, env, packed) for x in u.src]):
|
||||
return exec_alu(u.op, u.dtype, values, *([False] if "truncate_output" in inspect.signature(exec_alu).parameters else []))
|
||||
return None
|
||||
|
||||
|
||||
def _input_pokes(submit, kernargs) -> dict[int, tuple[int, int]]:
|
||||
from tinygrad.uop.ops import Ops
|
||||
outer = dict(enumerate(submit.src[1:]))
|
||||
outer, packed = dict(enumerate(submit.src[1:])), _packed_args(submit.src[0])
|
||||
kernargs_params = {i for i, a in outer.items() if a.op is Ops.GETADDR and _root(a.src[0]) is kernargs}
|
||||
inputs = {i: _root(a.src[0]).arg.slot for i, a in outer.items() if a.op is Ops.GETADDR and _root(a.src[0]).op is Ops.PARAM}
|
||||
pokes = {}
|
||||
for call in (u for u in submit.src[0].src[0].toposort() if u.op is Ops.CALL and len(u.src) > 3):
|
||||
_, address, value, *_ = call.src[1:]
|
||||
used = list({x.arg.slot for x in value.toposort() if x.op is Ops.PARAM} & inputs.keys())
|
||||
if len(used) != 1 or not kernargs_params & {x.arg.slot for x in address.toposort() if x.op is Ops.PARAM}:
|
||||
used = list({slot for x in value.toposort() if (slot := _outer_slot(x, packed)) is not None} & inputs.keys())
|
||||
if len(used) != 1 or not kernargs_params & {_outer_slot(x, packed) for x in address.toposort()}:
|
||||
continue
|
||||
ranges = list(dict.fromkeys(x for x in (*address.toposort(), *value.toposort()) if x.op is Ops.RANGE))
|
||||
for trip in itertools.product(*[range(int(r.vmax) + 1) for r in ranges]):
|
||||
env = dict.fromkeys(outer, 0) | dict(zip(ranges, trip, strict=True))
|
||||
if (offset := _evaluate(address, env)) % 8 == 0:
|
||||
pokes[offset] = (inputs[used[0]], _evaluate(value, env))
|
||||
if (offset := _evaluate(address, env, packed)) % 8 == 0:
|
||||
pokes[offset] = (inputs[used[0]], _evaluate(value, env, packed))
|
||||
return pokes
|
||||
|
||||
|
||||
def _elf(image: bytes, rodata: int) -> bytes:
|
||||
names, offset, data, headers = b"\0.text\0.rodata\0.shstrtab\0", 64, b"", []
|
||||
for name, kind, address, content in [(0, 0, 0, b""), (1, 1, ALIGNMENT, image[:rodata]), (7, 1, ALIGNMENT + rodata, image[rodata:]), (15, 3, 0, names)]:
|
||||
headers.append(struct.pack("<IIQQQQIIQQ", name, kind, 2 if kind == 1 else 0, address, offset, len(content), 0, 0, 1, 0))
|
||||
def _elf(image: bytes, rodata: int, entry: int) -> bytes:
|
||||
spans = [(".text", 0, rodata), (".rodata", rodata, len(image))] if entry < rodata else \
|
||||
[(".data", 0, rodata), (".rodata", rodata, entry), (".text", entry, len(image))]
|
||||
sections, names = [(0, 0, 0, 0, b"")], b"\0"
|
||||
for name, start, end in (span for span in spans if span[1] < span[2]):
|
||||
sections.append((len(names), 1, 6 if name == ".text" else 2, ALIGNMENT + start, image[start:end]))
|
||||
names += name.encode() + b"\0"
|
||||
sections.append((len(names), 3, 0, 0, names + b".shstrtab\0"))
|
||||
offset, data, headers = 64, b"", []
|
||||
for name, kind, flags, address, content in sections:
|
||||
headers.append(struct.pack("<IIQQQQIIQQ", name, kind, flags, address, offset, len(content), 0, 0, 1, 0))
|
||||
data, offset = data + content, offset + len(content)
|
||||
return b"\x7fELF\x02\x01\x01" + bytes(9) + struct.pack("<HHIQQQIHHHHHH", 1, 0xe0, 1, 0, 0, offset, 0, 64, 0, 0, 64, 4, 3) + data + b"".join(headers)
|
||||
header = struct.pack("<HHIQQQIHHHHHH", 1, 0xe0, 1, 0, 0, offset, 0, 64, 0, 0, 64, len(sections), len(sections) - 1)
|
||||
return b"\x7fELF\x02\x01\x01" + bytes(9) + header + data + b"".join(headers)
|
||||
|
||||
|
||||
def convert(jit, kwargs: dict, frame: dict, target) -> tuple[dict, memoryview]:
|
||||
@@ -155,7 +183,7 @@ def convert(jit, kwargs: dict, frame: dict, target) -> tuple[dict, memoryview]:
|
||||
if image_alloc not in programs:
|
||||
image = bytes(_patched(image_alloc, topo)[0])
|
||||
rodata = next(r for r in range(0, len(image) - 24, 64) if r + struct.unpack_from("<q", image, r + 16)[0] == entry)
|
||||
programs[image_alloc] = blob.add(_elf(image, rodata))
|
||||
programs[image_alloc] = blob.add(_elf(image, rodata, entry))
|
||||
start, size = _view(arguments.src[0])
|
||||
args = []
|
||||
for slot in range(start, start + unwrap(size), 8):
|
||||
|
||||
@@ -264,8 +264,8 @@ def _program_uop(call: dict, lib: bytes):
|
||||
parameter = UOp.param(call["globals"][compact_slot], _dtype(dtype), math.prod(shape))
|
||||
image = (shape[0], shape[1]) if len(shape) == 3 and shape[2] == 4 else None
|
||||
parameters.append(parameter.replace(arg=_fields(parameter.arg, name=name, image=image)))
|
||||
info = _fields(ProgramInfo, global_size=tuple(call["global_size"]), local_size=tuple(call["local_size"]), globals=tuple(call["globals"]),
|
||||
outs=tuple(call["outs"]), ins=tuple(call["ins"]), target=_fields(Target, **call["target"]))
|
||||
info = _fields(ProgramInfo, global_size=tuple(call["global_size"]), local_size=tuple(call["local_size"]), params=tuple(parameters),
|
||||
globals=tuple(call["globals"]), outs=tuple(call["outs"]), ins=tuple(call["ins"]), target=_fields(Target, **call["target"]))
|
||||
return UOp(Ops.PROGRAM, src=(UOp(Ops.SINK, arg=KernelInfo(name=call["name"])), UOp(Ops.LINEAR, src=tuple(parameters)),
|
||||
UOp(Ops.SOURCE, arg=""), UOp(Ops.BINARY, arg=lib)), arg=info)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
@@ -25,8 +26,8 @@ if sys.platform == "darwin" and os.environ.get("DEV", "").startswith("MOCKUSB"):
|
||||
|
||||
from openpilot.common.test import OpenpilotTestCase
|
||||
from openpilot.sunnypilot.modeld_v2 import convert_upstream_pkl
|
||||
from openpilot.sunnypilot.modeld_v2.convert_upstream_pkl import convert
|
||||
from openpilot.sunnypilot.modeld_v2.helpers import PlainJit, load_model
|
||||
from openpilot.sunnypilot.modeld_v2.convert_upstream_pkl import _elf, convert
|
||||
from openpilot.sunnypilot.modeld_v2.helpers import ALIGNMENT, PlainJit, load_model
|
||||
from openpilot.sunnypilot.modeld_v2.plain_artifact_compiler import capture_frame
|
||||
|
||||
IMG, DESIRE, STATE, OUT = (6, 8, 8), (8,), (4, 16), (1, 32)
|
||||
@@ -119,3 +120,18 @@ class TestConvertUpstreamPkl(OpenpilotTestCase):
|
||||
bundle = load_model(out, cache=False, verify=True)
|
||||
assert bundle["metadata"] == {"name": "tiny"} and bundle["input_specs"] == self.input_specs and bundle["output_specs"] == self.output_specs
|
||||
self.assert_matches_eager(bundle["run"], 6)
|
||||
|
||||
|
||||
class TestKernelElf(OpenpilotTestCase):
|
||||
def test_sections_follow_the_code(self):
|
||||
from tinygrad.runtime.support.elf import elf_loader
|
||||
code = bytes(range(1, 129))
|
||||
for rodata, entry in ((len(code), 0), (0, 256)):
|
||||
image = bytearray(code + bytes(64) if entry < rodata else bytes(256) + code)
|
||||
struct.pack_into("<q", image, rodata + 16, entry - rodata)
|
||||
loaded, sections, _ = elf_loader(_elf(bytes(image), rodata, entry))
|
||||
headers = {section.name: section.header for section in sections}
|
||||
assert bytes(loaded[ALIGNMENT:]) == bytes(image), "the uploaded program differs from the pickle's"
|
||||
assert headers[".rodata"].sh_addr == ALIGNMENT + rodata, "the loader reads the kernel descriptor at .rodata"
|
||||
text = headers[".text"]
|
||||
assert text.sh_addr <= ALIGNMENT + entry < text.sh_addr + text.sh_size, "the amd mock sizes the code from the entry to the end of .text"
|
||||
|
||||
@@ -103,9 +103,7 @@ def setup_interfaces(CI: CarInterfaceBase, params: Params | None = None) -> None
|
||||
|
||||
|
||||
def initialize_params(params) -> list[dict[str, Any]]:
|
||||
keys: list = [
|
||||
"SmoothStopSP",
|
||||
]
|
||||
keys: list = []
|
||||
|
||||
# hyundai
|
||||
keys.extend([
|
||||
@@ -128,7 +126,6 @@ def initialize_params(params) -> list[dict[str, Any]]:
|
||||
keys.extend([
|
||||
"ToyotaEnforceStockLongitudinal",
|
||||
"ToyotaStopAndGoHack",
|
||||
"ToyotaEnhancedBsm",
|
||||
"ToyotaTSS2Smooth",
|
||||
])
|
||||
|
||||
|
||||
@@ -12,13 +12,11 @@ from openpilot.cereal import log, custom
|
||||
from opendbc.car import structs
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.selfdrive.controls.lib.longcontrol import LongControl
|
||||
from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD
|
||||
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
||||
from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.blinker_pause_lateral import BlinkerPauseLateral
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.latcontrol_torque_v0 import LatControlTorque as LatControlTorqueV0
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.longcontrol_sp import LongControlSP
|
||||
|
||||
|
||||
class ControlsExt(ModelStateBase):
|
||||
@@ -36,11 +34,6 @@ class ControlsExt(ModelStateBase):
|
||||
self.sm_services_ext = ['radarState', 'selfdriveStateSP']
|
||||
self.pm_services_ext = ['carControlSP']
|
||||
|
||||
def initialize_longitudinal_control(self, lo_c: LongControl) -> LongControl:
|
||||
if self.CP_SP.smoothStopDecelRate > 0.0:
|
||||
return LongControlSP(self.CP, self.CP_SP)
|
||||
return lo_c
|
||||
|
||||
def initialize_lateral_control(self, lac, CI, dt):
|
||||
enforce_torque_control = self.params.get_bool("EnforceTorqueControl")
|
||||
torque_versions = self.params.get("TorqueControlTune")
|
||||
|
||||
@@ -1,27 +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 numpy as np
|
||||
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.selfdrive.controls.lib.longcontrol import LongControl, LongCtrlState
|
||||
|
||||
|
||||
class LongControlSP(LongControl):
|
||||
def update(self, active, CS, a_target, should_stop, accel_limits):
|
||||
prev_output_accel = self.last_output_accel
|
||||
output_accel = super().update(active, CS, a_target, should_stop, accel_limits)
|
||||
|
||||
if self.long_control_state == LongCtrlState.stopping and self.CP_SP.smoothStopDecelRate > 0.0:
|
||||
output_accel = self.stopping_accel(prev_output_accel, accel_limits)
|
||||
self.last_output_accel = output_accel
|
||||
|
||||
return float(output_accel)
|
||||
|
||||
def stopping_accel(self, accel, accel_limits):
|
||||
if accel > self.CP.stopAccel:
|
||||
accel = min(accel, 0.0) - self.CP_SP.smoothStopDecelRate * DT_CTRL
|
||||
return float(np.clip(accel, *accel_limits))
|
||||
@@ -1,98 +0,0 @@
|
||||
"""
|
||||
Copyright (c) 2021-, 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 types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.car.structs import car
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.cereal import custom
|
||||
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.longcontrol_sp import LongControlSP
|
||||
|
||||
SMOOTH_STOP_DECEL_RATE = 0.3
|
||||
|
||||
LIMITS = (-3.0, 2.0)
|
||||
|
||||
|
||||
def make_control(stop_accel=-2.0, rate=SMOOTH_STOP_DECEL_RATE):
|
||||
CP = car.CarParams.new_message()
|
||||
CP.stopAccel = stop_accel
|
||||
CP.longitudinalTuning.kiBP = [0.0]
|
||||
CP.longitudinalTuning.kiV = [0.0]
|
||||
CP_SP = custom.CarParamsSP.new_message()
|
||||
CP_SP.smoothStopDecelRate = rate
|
||||
return LongControlSP(CP, CP_SP)
|
||||
|
||||
|
||||
def make_cs(v_ego, standstill=False):
|
||||
return SimpleNamespace(vEgo=v_ego, aEgo=0.0, brakePressed=False, standstill=standstill,
|
||||
cruiseState=SimpleNamespace(standstill=standstill))
|
||||
|
||||
|
||||
def start_stopping(loc, a_target=-0.5):
|
||||
loc.update(True, make_cs(0.5), a_target, False, LIMITS)
|
||||
return loc.update(True, make_cs(0.3), a_target, True, LIMITS)
|
||||
|
||||
|
||||
def test_decel_ramps_at_stopping_rate():
|
||||
loc = make_control()
|
||||
out = start_stopping(loc)
|
||||
assert loc.long_control_state == LongCtrlState.stopping
|
||||
for _ in range(100):
|
||||
prev, out = out, loc.update(True, make_cs(0.1), -0.5, True, LIMITS)
|
||||
assert out == pytest.approx(prev - SMOOTH_STOP_DECEL_RATE * DT_CTRL)
|
||||
|
||||
|
||||
def test_no_step_at_handover():
|
||||
loc = make_control()
|
||||
out = loc.update(True, make_cs(0.5), -0.5, False, LIMITS)
|
||||
assert abs(start_stopping(loc) - out) <= SMOOTH_STOP_DECEL_RATE * DT_CTRL + 1e-6
|
||||
|
||||
|
||||
def test_decel_never_fades_to_zero_while_stopping():
|
||||
loc = make_control()
|
||||
outs = [start_stopping(loc)]
|
||||
for v in [0.2, 0.1, 0.03, 0.0] * 50:
|
||||
outs.append(loc.update(True, make_cs(v, standstill=v == 0.0), -0.5, True, LIMITS))
|
||||
assert all(b <= a for a, b in zip(outs, outs[1:], strict=False))
|
||||
|
||||
|
||||
def test_stops_at_stop_accel():
|
||||
loc = make_control(-1.5)
|
||||
start_stopping(loc)
|
||||
for _ in range(int(10.0 / DT_CTRL)):
|
||||
out = loc.update(True, make_cs(0.0, standstill=True), -0.5, True, LIMITS)
|
||||
assert out == pytest.approx(-1.5, abs=0.01)
|
||||
|
||||
|
||||
def test_positive_output_starts_from_zero():
|
||||
loc = make_control()
|
||||
loc.update(True, make_cs(0.5), 0.3, False, LIMITS)
|
||||
assert start_stopping(loc, a_target=0.3) <= 0.0
|
||||
|
||||
|
||||
def test_repeatable():
|
||||
runs = []
|
||||
for _ in range(2):
|
||||
loc = make_control()
|
||||
runs.append([start_stopping(loc)] + [loc.update(True, make_cs(0.1), -0.5, True, LIMITS) for _ in range(300)])
|
||||
assert runs[0] == runs[1]
|
||||
|
||||
|
||||
def test_disabled_uses_stock_stopping_behavior():
|
||||
loc = make_control(rate=0.0)
|
||||
start_stopping(loc)
|
||||
prev = loc.last_output_accel
|
||||
assert loc.update(True, make_cs(0.1), -0.5, True, LIMITS) == pytest.approx(prev - 1.0 * DT_CTRL)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("rate", [0.3, 0.8, 2.0])
|
||||
def test_rate_comes_from_car_params(rate):
|
||||
loc = make_control(rate=rate)
|
||||
out = start_stopping(loc)
|
||||
assert loc.update(True, make_cs(0.1), -0.5, True, LIMITS) == pytest.approx(out - rate * DT_CTRL)
|
||||
@@ -13,7 +13,6 @@ from opendbc.car.subaru.values import CAR as SUBARU_CAR, SubaruFlags
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA_CAR, ToyotaFlags
|
||||
from opendbc.sunnypilot.car.interfaces import has_bsm
|
||||
from opendbc.sunnypilot.car.tesla.values import TeslaFlagsSP
|
||||
from opendbc.sunnypilot.car.toyota.values import ToyotaFlagsSP
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.common.hardware import HARDWARE
|
||||
@@ -45,7 +44,6 @@ CAPABILITY_FIELDS = (
|
||||
"device_type",
|
||||
"subaru_has_sng",
|
||||
"hyundai_alpha_long_available",
|
||||
"toyota_enhanced_bsm_available",
|
||||
"toyota_tss2",
|
||||
"custom_model_active",
|
||||
)
|
||||
@@ -70,7 +68,6 @@ CAPABILITY_LABELS: dict[str, str] = {
|
||||
"device_type": "Device type",
|
||||
"subaru_has_sng": "Subaru Stop-and-Go available",
|
||||
"hyundai_alpha_long_available": "Hyundai Alpha Longitudinal available",
|
||||
"toyota_enhanced_bsm_available": "Toyota Enhanced BSM available",
|
||||
"toyota_tss2": "Toyota TSS2 platform",
|
||||
"custom_model_active": "Non-Default model selected",
|
||||
}
|
||||
@@ -92,7 +89,6 @@ CAR_CAPABILITY_FIELDS = (
|
||||
"subaru_has_sng",
|
||||
"hyundai_alpha_long_available",
|
||||
"toyota_tss2",
|
||||
"toyota_enhanced_bsm_available",
|
||||
)
|
||||
|
||||
# Explicit defaults for non-boolean capability fields
|
||||
@@ -108,7 +104,7 @@ def _bundle_field(bundle: dict | None, key: str) -> str:
|
||||
return bundle.get(key, "") if isinstance(bundle, dict) else ""
|
||||
|
||||
|
||||
def _resolve_brand_capabilities(caps: dict, bundle_platform: str, CP, CP_SP=None) -> None:
|
||||
def _resolve_brand_capabilities(caps: dict, bundle_platform: str, CP) -> None:
|
||||
"""Set brand-specific capabilities from bundle platform or CarParams fallback.
|
||||
|
||||
Bundle (manual car selection) is a pre-fingerprint approximation.
|
||||
@@ -142,18 +138,11 @@ def _resolve_brand_capabilities(caps: dict, bundle_platform: str, CP, CP_SP=None
|
||||
elif brand == "toyota":
|
||||
if bundle_platform:
|
||||
try:
|
||||
flags = TOYOTA_CAR[bundle_platform].config.flags
|
||||
caps["toyota_tss2"] = bool(flags & ToyotaFlags.TSS2)
|
||||
caps["toyota_enhanced_bsm_available"] = bool(flags & ToyotaFlags.TSS2) and not bool(flags & ToyotaFlags.SECOC)
|
||||
caps["toyota_tss2"] = bool(TOYOTA_CAR[bundle_platform].config.flags & ToyotaFlags.TSS2)
|
||||
except KeyError:
|
||||
cloudlog.exception(f"capabilities: unknown toyota platform {bundle_platform!r}")
|
||||
elif CP is not None:
|
||||
caps["toyota_tss2"] = bool(CP.flags & ToyotaFlags.TSS2)
|
||||
# same gate as _initialize_toyota: TSS2 without SecOC, or TSS-P with a smartDSU and openpilot
|
||||
# longitudinal control (the bundle path can't see 0x2FF, so it stays TSS2-only)
|
||||
sdsu_bsm = CP_SP is not None and bool(CP_SP.flags & ToyotaFlagsSP.SMART_DSU) and bool(CP.openpilotLongitudinalControl)
|
||||
caps["toyota_enhanced_bsm_available"] = (not bool(CP.flags & ToyotaFlags.SECOC) and
|
||||
(bool(CP.flags & ToyotaFlags.TSS2) or sdsu_bsm))
|
||||
|
||||
|
||||
def generate_capabilities(params: Params | None = None) -> dict:
|
||||
@@ -228,7 +217,7 @@ def generate_capabilities(params: Params | None = None) -> dict:
|
||||
if CP is not None:
|
||||
caps["has_bsm"] = has_bsm(CP, CP_SP)
|
||||
|
||||
_resolve_brand_capabilities(caps, bundle_platform, CP, CP_SP)
|
||||
_resolve_brand_capabilities(caps, bundle_platform, CP)
|
||||
|
||||
return caps
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ The tables below describe the **compiled** `settings_ui.json` schema — what th
|
||||
|
||||
**Enablement rules**: Grayed out (disabled) when rules fail. Frontend shows a contextual badge explaining why.
|
||||
|
||||
**Capability fields** (referenced in rules): `has_longitudinal_control`, `has_icbm`, `icbm_available`, `torque_allowed`, `brand`, `pcm_cruise`, `alpha_long_available`, `steer_control_type`, `has_bsm`, `is_release`, `is_sp_release`, `is_development`, `tesla_has_vehicle_bus`, `has_stop_and_go`, `stock_longitudinal`, `toyota_enhanced_bsm_available`
|
||||
**Capability fields** (referenced in rules): `has_longitudinal_control`, `has_icbm`, `icbm_available`, `torque_allowed`, `brand`, `pcm_cruise`, `alpha_long_available`, `steer_control_type`, `has_bsm`, `is_release`, `is_sp_release`, `is_development`, `tesla_has_vehicle_bus`, `has_stop_and_go`, `stock_longitudinal`
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -633,23 +633,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "SmoothStopSP",
|
||||
"widget": "toggle",
|
||||
"needs_onroad_cycle": true,
|
||||
"title": "Smooth Stop",
|
||||
"description": "Brakes gently as sunnypilot comes to a final stop to reduce the lurch at the end. Applies to all vehicles with sunnypilot longitudinal control.",
|
||||
"enablement": [
|
||||
{
|
||||
"type": "not_engaged"
|
||||
},
|
||||
{
|
||||
"type": "capability",
|
||||
"field": "has_longitudinal_control",
|
||||
"equals": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "IntelligentCruiseButtonManagement",
|
||||
"widget": "toggle",
|
||||
@@ -1429,12 +1412,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "TripSummary",
|
||||
"widget": "toggle",
|
||||
"title": "Trip Summary",
|
||||
"description": "Show a summary of distance and engagement when you shift into park after a drive."
|
||||
},
|
||||
{
|
||||
"key": "RocketFuel",
|
||||
"widget": "toggle",
|
||||
@@ -2384,28 +2361,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "ToyotaEnhancedBsm",
|
||||
"widget": "toggle",
|
||||
"needs_onroad_cycle": true,
|
||||
"title": "Enhanced Blind Spot Monitoring (Alpha)",
|
||||
"description": "sunnypilot will poll the blind spot monitor diagnostics directly from the radar. Use this on vehicles whose blind spot monitors do not report blind spot status over the standard CAN signals. This is an alpha feature. Use at your own risk.",
|
||||
"enablement": [
|
||||
{
|
||||
"type": "not_engaged"
|
||||
},
|
||||
{
|
||||
"type": "capability",
|
||||
"field": "toyota_enhanced_bsm_available",
|
||||
"equals": true
|
||||
},
|
||||
{
|
||||
"type": "capability",
|
||||
"field": "stock_longitudinal",
|
||||
"equals": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "ToyotaTSS2Smooth",
|
||||
"widget": "toggle",
|
||||
|
||||
@@ -36,16 +36,6 @@ sections:
|
||||
description: "Adds a small push on top of sunnypilot's own acceleration, stronger in Aggressive and gentler in Relaxed. Eases off in curves and when sunnypilot slows or stops. Never goes past your set speed, and lead-car following still comes first."
|
||||
enablement:
|
||||
- $ref: '#/macros/longitudinal'
|
||||
- key: SmoothStopSP
|
||||
widget: toggle
|
||||
needs_onroad_cycle: true
|
||||
title: Smooth Stop
|
||||
description: Brakes gently as sunnypilot comes to a final stop to reduce the lurch at the end. Applies to all vehicles with sunnypilot longitudinal control.
|
||||
enablement:
|
||||
- type: not_engaged
|
||||
- type: capability
|
||||
field: has_longitudinal_control
|
||||
equals: true
|
||||
- key: IntelligentCruiseButtonManagement
|
||||
widget: toggle
|
||||
title: Intelligent Cruise Button Management (ICBM) (Alpha)
|
||||
|
||||
@@ -101,21 +101,6 @@ sections:
|
||||
- type: param
|
||||
key: ToyotaEnforceStockLongitudinal
|
||||
equals: false
|
||||
- key: ToyotaEnhancedBsm
|
||||
widget: toggle
|
||||
needs_onroad_cycle: true
|
||||
title: Enhanced Blind Spot Monitoring (Alpha)
|
||||
description: sunnypilot will poll the blind spot monitor diagnostics directly from the radar. Use this on vehicles whose
|
||||
blind spot monitors do not report blind spot status over the standard CAN signals. This is an alpha feature. Use at
|
||||
your own risk.
|
||||
enablement:
|
||||
- $ref: '#/macros/not_engaged'
|
||||
- type: capability
|
||||
field: toyota_enhanced_bsm_available
|
||||
equals: true
|
||||
- type: capability
|
||||
field: stock_longitudinal
|
||||
equals: false
|
||||
- key: ToyotaTSS2Smooth
|
||||
widget: toggle
|
||||
needs_onroad_cycle: true
|
||||
|
||||
@@ -39,10 +39,6 @@ sections:
|
||||
description: Show a timer on the HUD when the car is at a standstill.
|
||||
visibility:
|
||||
- $ref: '#/macros/hide_on_mici'
|
||||
- key: TripSummary
|
||||
widget: toggle
|
||||
title: Trip Summary
|
||||
description: Show a summary of distance and engagement when you shift into park after a drive.
|
||||
- key: RocketFuel
|
||||
widget: toggle
|
||||
title: Real-time Acceleration Bar
|
||||
|
||||
@@ -70,18 +70,12 @@ class TestOpaquePerBrandFlags(OpenpilotTestCase):
|
||||
def test_hyundai_alpha_long_available_field_present(self):
|
||||
assert "hyundai_alpha_long_available" in CAPABILITY_FIELDS
|
||||
|
||||
def test_toyota_enhanced_bsm_available_field_present(self):
|
||||
assert "toyota_enhanced_bsm_available" in CAPABILITY_FIELDS
|
||||
|
||||
def test_subaru_has_sng_default_false(self, caps):
|
||||
assert caps["subaru_has_sng"] is False
|
||||
|
||||
def test_hyundai_alpha_long_available_default_false(self, caps):
|
||||
assert caps["hyundai_alpha_long_available"] is False
|
||||
|
||||
def test_toyota_enhanced_bsm_available_default_false(self, caps):
|
||||
assert caps["toyota_enhanced_bsm_available"] is False
|
||||
|
||||
def test_toyota_tss2_field_present(self):
|
||||
assert "toyota_tss2" in CAPABILITY_FIELDS
|
||||
|
||||
|
||||
@@ -207,7 +207,6 @@ class TestNotEngagedReplacement(OpenpilotTestCase):
|
||||
"AlphaLongitudinalEnabled",
|
||||
"ToyotaEnforceStockLongitudinal",
|
||||
"ToyotaStopAndGoHack",
|
||||
"ToyotaEnhancedBsm",
|
||||
"ToyotaTSS2Smooth",
|
||||
], names=["key"])
|
||||
def test_offroad_only_replaced_with_not_engaged(self, schema, key):
|
||||
|
||||
@@ -286,7 +286,6 @@ class TestKnownVehicleSettings(OpenpilotTestCase):
|
||||
keys = {i["key"] for i in _brand_items(schema["vehicle_settings"].get("toyota"))}
|
||||
assert "ToyotaEnforceStockLongitudinal" in keys
|
||||
assert "ToyotaStopAndGoHack" in keys
|
||||
assert "ToyotaEnhancedBsm" in keys
|
||||
assert "ToyotaTSS2Smooth" in keys
|
||||
|
||||
def test_tesla_has_coop_steering(self, schema):
|
||||
|
||||
@@ -13,26 +13,41 @@ from pathlib import Path
|
||||
from datetime import datetime, UTC
|
||||
|
||||
|
||||
_VERSION_REGEX = re.compile(r'^(?:\d+[a-zA-Z]+|\d+[vVbB]\d+|[vVbB]\d+|\d{4})$')
|
||||
_DECIMAL_VERSION_REGEX = re.compile(r'^([vVbB]?)(\d+(?:\.\d+)+)$')
|
||||
|
||||
|
||||
def _version(word: str) -> str | None:
|
||||
match = _DECIMAL_VERSION_REGEX.match(word.strip('()[]{}.,;:'))
|
||||
if match:
|
||||
return match.group(1).upper() + match.group(2)
|
||||
stripped = re.sub(r'[^a-zA-Z0-9]', '', word)
|
||||
return stripped.upper() if _VERSION_REGEX.match(stripped) else None
|
||||
|
||||
|
||||
def _truncate(short_name: str) -> str:
|
||||
return short_name[:8].rstrip('.')
|
||||
|
||||
|
||||
def create_short_name(full_name: str) -> str:
|
||||
# Remove parentheses and extract alphanumeric words
|
||||
# Remove parentheses and extract alphanumeric words, keeping decimals for versioning
|
||||
clean_name = re.sub(r'\([^)]*\)', '', full_name)
|
||||
words = [re.sub(r'[^a-zA-Z0-9]', '', word) for word in clean_name.split() if re.sub(r'[^a-zA-Z0-9]', '', word)]
|
||||
words = [re.sub(r'[^a-zA-Z0-9.]', '', word) for word in clean_name.split()]
|
||||
words = [word for word in words if word]
|
||||
|
||||
if len(words) == 1:
|
||||
return words[0][:8].upper()
|
||||
return _truncate((_version(words[0]) or words[0]).upper())
|
||||
|
||||
# Handle special case: Name + Version (e.g., "Word A1" -> "WordA1")
|
||||
if len(words) == 2 and re.match(r'^[A-Za-z]\d+$', words[1]):
|
||||
return (words[0] + words[1])[:8].upper()
|
||||
# Handle special case: Name + Version (e.g., "Word A1" -> "WordA1", "Model v1.1" -> "ModelV1.1")
|
||||
if len(words) == 2 and re.match(r'^[A-Za-z]?\d+$', words[1]):
|
||||
return _truncate((words[0] + (_version(words[1]) or words[1])).upper())
|
||||
|
||||
result = ""
|
||||
for word in words:
|
||||
# Version or number patterns
|
||||
if (re.match(r'^\d+[a-zA-Z]+$', word) or
|
||||
re.match(r'^\d+[vVbB]\d+$', word) or
|
||||
re.match(r'^[vVbB]\d+$', word) or
|
||||
re.match(r'^\d{4}$', word)):
|
||||
result += word.upper()
|
||||
version = _version(word)
|
||||
if version:
|
||||
result += version
|
||||
# All uppercase abbreviations (2-3 letters)
|
||||
elif re.match(r'^[A-Z]{2,3}$', word):
|
||||
result += word
|
||||
@@ -45,7 +60,7 @@ def create_short_name(full_name: str) -> str:
|
||||
result += word
|
||||
else:
|
||||
result += word[0].upper()
|
||||
return result[:8]
|
||||
return _truncate(result)
|
||||
|
||||
|
||||
def create_pkl_name(full_name: str) -> str:
|
||||
|
||||
Reference in New Issue
Block a user