Compare commits

..

33 Commits

Author SHA1 Message Date
royjr a3c087c402 Update opendbc_repo 2026-11-05 23:14:55 -05:00
royjr 4e7f4d7656 Merge branch 'master' into HYUNDAI_SONATA_2024 2026-11-05 23:14:27 -05:00
royjr 638359d1e5 Update opendbc_repo 2026-11-05 18:33:47 -05:00
royjr 230948763f Update opendbc_repo 2026-11-05 16:29:53 -05:00
James Vecellio-Grant 02ef6e4ce0 modeld_v2: artifact parity with upstream tg (#2093)
* modeld_v2: artifact parity with upstream tg

* m

* m

* Update helpers.py

* ew

* Update convert_upstream_pkl.py

* Update test_convert_upstream_pkl.py

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-11-05 16:11:27 -05:00
Nayan a1a28e467e [models] Support major.minor versioning (#2092)
support major.minor versioning, becase why not
2026-11-05 15:35:07 -05:00
royjr ef9a842c08 Update opendbc_repo 2026-11-05 14:14:51 -05:00
royjr c94d1efc92 Update opendbc_repo 2026-11-05 11:12:45 -05:00
royjr 3f34af4948 Update opendbc_repo 2026-11-05 10:33:26 -05:00
royjr 2d79ea7de4 Merge branch 'master' into HYUNDAI_SONATA_2024 2026-11-05 10:19:07 -05:00
royjr 41cadd46bd Update opendbc_repo 2026-11-05 10:08:04 -05:00
royjr 57dd5289e6 Update opendbc_repo 2026-11-05 10:02:55 -05:00
royjr 543866e14d Update opendbc_repo 2026-11-04 00:09:52 -05:00
royjr dedb23f139 Update opendbc_repo 2026-11-03 23:46:38 -05:00
royjr 1764bf124c Merge branch 'master' into HYUNDAI_SONATA_2024 2026-11-03 23:17:46 -05:00
royjr b2efa3d16e Update opendbc_repo 2026-11-03 23:12:23 -05:00
royjr 62d980101d Update opendbc_repo 2026-11-03 22:36:44 -05:00
royjr 83beadac8d Update opendbc_repo 2026-11-03 22:31:06 -05:00
royjr 4087a4f369 mvp 2026-11-03 17:39:26 -05:00
royjr b57b184c73 better 2026-11-02 17:56:21 -05:00
royjr e65f65cb83 Update opendbc_repo 2026-11-02 17:49:49 -05:00
royjr 6970795f19 hurry up 2026-11-02 17:49:46 -05:00
royjr c98c359dba Update opendbc_repo 2026-11-02 14:42:57 -05:00
royjr 54edcadcbb no blend 2026-11-02 11:17:15 -05:00
royjr 9d32254e23 this this 2026-11-02 11:07:43 -05:00
royjr 75d1411f4a Update opendbc_repo 2026-11-02 10:59:38 -05:00
royjr 60bacd0231 clean 2026-11-02 10:06:17 -05:00
royjr 856e31765e more 2026-11-02 09:20:02 -05:00
royjr 10bc6355cd blendful 2026-11-02 08:30:41 -05:00
royjr 594c37a6ec stock 2026-11-02 07:06:37 -05:00
royjr 11867d2219 Update opendbc_repo 2026-11-01 22:18:24 -05:00
royjr 0aaaddddab Update opendbc_repo 2026-11-01 22:14:16 -05:00
royjr fe03e9b594 Update opendbc_repo 2026-10-31 23:55:07 -04:00
34 changed files with 117 additions and 718 deletions
+17
View File
@@ -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: |
-1
View File
@@ -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
View File
@@ -369,7 +369,6 @@ struct CarParamsSP @0x80ae746ee2596b11 {
pcmCruiseSpeed @3 :Bool;
intelligentCruiseButtonManagementAvailable @4 :Bool;
enableGasInterceptor @5 :Bool;
smoothStopDecelRate @6 :Float32;
neuralNetworkLateralControl @2 :NeuralNetworkLateralControl;
-3
View File
@@ -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:
-2
View File
@@ -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):
+2 -2
View File
@@ -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)
+3 -14
View File
@@ -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):
+27 -12
View File
@@ -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: