mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-11-13 06:53:43 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 43744ac5b8 | |||
| 812b8b27a8 | |||
| 3cfe730f8c | |||
| 6d34938afe | |||
| 61af0a0512 | |||
| 066c0e2957 | |||
| 26b8501e7b |
@@ -42,7 +42,7 @@ jobs:
|
||||
WARP_PKL="openpilot/sunnypilot/modeld_v2/models/driving_warp_${res}_tinygrad.pkl"
|
||||
echo "Compiling $WARP_PKL on QCOM"
|
||||
taskset -c 7 env DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1 python3 openpilot/sunnypilot/modeld_v2/compile_warp.py \
|
||||
--frame ${FRAME_PARAM} --warp-to ${MODEL_SIZE} --output "${WARP_PKL}"
|
||||
--frame ${FRAME_PARAM} --warp-to ${MODEL_SIZE} --split-frames --output "${WARP_PKL}"
|
||||
|
||||
BIG_WARP_PKL="openpilot/sunnypilot/modeld_v2/models/big_driving_warp_${res}_tinygrad.pkl"
|
||||
echo "Compiling $BIG_WARP_PKL on AMD"
|
||||
|
||||
+4
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
|
||||
|
||||
# 351 Supported Cars
|
||||
# 354 Supported Cars
|
||||
|
||||
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br> |Video|Setup Video|
|
||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
@@ -153,18 +153,21 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Sonata 2018-19">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Sonata 2020-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Sonata 2020-23">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=ix63r9kE3Fw" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||
|Hyundai|Sonata Hybrid 2020-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Sonata Hybrid 2020-23">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Sonata Hybrid (without HDA II) 2024-26|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Sonata Hybrid (without HDA II) 2024-26">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Staria 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Staria 2023">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Tucson 2021">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Tucson 2022|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Tucson 2022">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Tucson 2023-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Tucson 2023-24">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Tucson Diesel 2019">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Tucson Hybrid 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Tucson Hybrid 2022-24">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Tucson Hybrid (without HDA II) 2025-26|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Tucson Hybrid (without HDA II) 2025-26">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Tucson Plug-in Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Tucson Plug-in Hybrid 2024">Buy Here</a></sub></details>|||
|
||||
|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Veloster 2019-20">Buy Here</a></sub></details>|||
|
||||
|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Jeep Grand Cherokee 2016-18">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=eLR9o2JkuRk" target="_blank"><img height="18px" src="assets/icon-youtube.svg" /></a>||
|
||||
|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Jeep Grand Cherokee 2019-21">Buy Here</a></sub></details>|||
|
||||
|Kia|Carnival 2022-24|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Carnival 2022-24">Buy Here</a></sub></details>|||
|
||||
|Kia|Carnival (China only) 2023|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Carnival (China only) 2023">Buy Here</a></sub></details>|||
|
||||
|Kia|Carnival Hybrid (with HDA II) 2025-26|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Carnival Hybrid (with HDA II) 2025-26">Buy Here</a></sub></details>|||
|
||||
|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Ceed 2019-21">Buy Here</a></sub></details>|||
|
||||
|Kia|Ceed Plug-in Hybrid 2021|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Ceed Plug-in Hybrid 2021">Buy Here</a></sub></details>|||
|
||||
|Kia|EV6 (Southeast Asia only) 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia EV6 (Southeast Asia only) 2022-24">Buy Here</a></sub></details>|||
|
||||
|
||||
+1
-1
Submodule opendbc_repo updated: 48611ae9d6...f087f898a1
@@ -200,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -48,7 +48,6 @@ class UIStateSP:
|
||||
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
|
||||
@@ -186,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")
|
||||
|
||||
@@ -17,16 +17,24 @@ def main():
|
||||
parser.add_argument("--frame", required=True, help="width,height,stride,y_height,uv_height,buffer_size")
|
||||
parser.add_argument("--warp-to", required=True, help="model input WxH")
|
||||
parser.add_argument("--output", required=True)
|
||||
parser.add_argument("--split-frames", action="store_true", help="one input per camera (frame, big_frame), bound to the camera buffers by pointer")
|
||||
args = parser.parse_args()
|
||||
|
||||
from examples.openpilot.compile_warp import make_frame_prepare, parse_frame, parse_size
|
||||
from tinygrad import Device, Tensor, TinyJit
|
||||
frame = parse_frame(args.frame)
|
||||
warp = make_frame_prepare(frame, *parse_size(args.warp_to))
|
||||
run = TinyJit(lambda input_frame, M_inv: Tensor.stack(*(warp(input_frame[i], M_inv[i]) for i in range(2))), prune=True)
|
||||
rng = np.random.default_rng(42)
|
||||
inputs = {"input_frame": Tensor(rng.integers(0, 256, (2, frame.size), dtype=np.uint8), device=Device.DEFAULT).realize(),
|
||||
"M_inv": Tensor((rng.standard_normal((2, 3, 3)) * 8).astype(np.float32), device=Device.DEFAULT).realize()}
|
||||
frames = rng.integers(0, 256, (2, frame.size), dtype=np.uint8)
|
||||
m_inv = Tensor((rng.standard_normal((2, 3, 3)) * 8).astype(np.float32), device=Device.DEFAULT).realize()
|
||||
if args.split_frames:
|
||||
run = TinyJit(lambda frame, big_frame, M_inv: Tensor.stack(warp(frame, M_inv[0]), warp(big_frame, M_inv[1])), prune=True)
|
||||
inputs = {"frame": Tensor(frames[0], device=Device.DEFAULT).realize(), "big_frame": Tensor(frames[1], device=Device.DEFAULT).realize(), "M_inv": m_inv}
|
||||
specs = dict.fromkeys(("frame", "big_frame"), ((frame.size,), "|u1", Device.DEFAULT))
|
||||
else:
|
||||
run = TinyJit(lambda input_frame, M_inv: Tensor.stack(*(warp(input_frame[i], M_inv[i]) for i in range(2))), prune=True)
|
||||
inputs = {"input_frame": Tensor(frames, device=Device.DEFAULT).realize(), "M_inv": m_inv}
|
||||
specs = {"input_frame": ((2, frame.size), "|u1", Device.DEFAULT)}
|
||||
with record_kernel_linear() as recorded:
|
||||
run(**inputs)
|
||||
run(**inputs)
|
||||
@@ -34,7 +42,7 @@ def main():
|
||||
assert len(recorded) == 1, f"expected one captured kernel list, got {len(recorded)}"
|
||||
part = serialize_jit(run, recorded[0], check)
|
||||
PlainJit(*part)
|
||||
specs = {"input_frame": ((2, frame.size), "|u1", Device.DEFAULT), "M_inv": ((2, 3, 3), "<f4", Device.DEFAULT)}
|
||||
specs |= {"M_inv": ((2, 3, 3), "<f4", Device.DEFAULT)}
|
||||
write_bundle(args.output, {"run": part}, {"input_specs": specs, "metadata": {}})
|
||||
print(f"Saved warp to {args.output} ({len(part[0]['calls'])} kernels)")
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ from functools import partial
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.sunnypilot.modeld_v2.helpers import _dtype, read_parts
|
||||
|
||||
SPLIT_FRAMES = ("big_frame", "frame")
|
||||
|
||||
|
||||
def pack_layout(shapes: dict[str, tuple], frame_size: int) -> tuple[dict[str, int], int]:
|
||||
offsets, offset = {}, 0
|
||||
@@ -39,29 +41,31 @@ def frame_part(warp_path: str, parts: dict, extras: dict) -> dict:
|
||||
(model, model_blob), (warp, warp_blob) = parts["run"], read_parts(warp_path)[0]["run"]
|
||||
states = sorted(name for name in extras["input_specs"] if f"next_{name}" in extras["metadata"]["output_shapes"])
|
||||
host = {name: shape for name, (shape, _, _) in extras["input_specs"].items() if name not in states and name != "new_img"}
|
||||
frame_size = next(s["numel"] for s in warp["inputs"] if s["name"] == "input_frame") // 2
|
||||
offsets, packed = pack_layout(host, frame_size)
|
||||
frame_specs = {s["name"]: s for s in warp["inputs"] if s["name"] in SPLIT_FRAMES}
|
||||
frame_size = next(s["numel"] for s in warp["inputs"] if s["name"] in ("input_frame", "frame")) // (1 if frame_specs else 2)
|
||||
offsets, packed = pack_layout(host, 0 if frame_specs else frame_size)
|
||||
shift, upload, u8 = len(model_blob), len(model["buffers"]) + len(warp["buffers"]), repr(_dtype("dtypes.uint8"))
|
||||
|
||||
def view(name: str, nbytes: int, dtype: str) -> dict:
|
||||
return {"buffer": upload, "offset": offsets[name], "nbytes": nbytes, "dtype": dtype}
|
||||
outputs = sorted(s["slot"] for s in model["inputs"] if s["name"] is None)
|
||||
names = sorted(["host", *states])
|
||||
names = sorted(["host", *states, *frame_specs])
|
||||
slots = {s["slot"]: names.index(s["name"]) for s in model["inputs"] if s["name"] in states} | \
|
||||
{slot: len(names) + i for i, slot in enumerate(outputs)}
|
||||
returned = warp["returns"]["tensors"][0]
|
||||
model_inputs = {s["slot"]: {"input": slots[s["slot"]]} if s["slot"] in slots else
|
||||
{k: v for k, v in returned.items() if k != "shape"} | {"buffer": returned["buffer"] + len(model["buffers"])} if s["name"] == "new_img" else
|
||||
view(s["name"], s["numel"] * _dtype(s["dtype"]).itemsize, s["dtype"]) for s in model["inputs"]}
|
||||
warp_inputs = {s["slot"]: view("frames" if s["name"] == "input_frame" else "tfm", s["numel"] * _dtype(s["dtype"]).itemsize, s["dtype"])
|
||||
for s in warp["inputs"]}
|
||||
warp_inputs = {s["slot"]: {"input": names.index(s["name"])} if s["name"] in frame_specs else
|
||||
view("frames" if s["name"] == "input_frame" else "tfm", s["numel"] * _dtype(s["dtype"]).itemsize, s["dtype"]) for s in warp["inputs"]}
|
||||
|
||||
def remap(calls: list, inputs: dict, buffers: int, lib: int) -> list:
|
||||
return [call | ({"lib": [call["lib"][0] + lib, call["lib"][1] + lib]} if "lib" in call else {}) |
|
||||
{"args": [dict(inputs[a["input"]]) if "input" in a else a | {"buffer": a["buffer"] + buffers} for a in call["args"]]} for call in calls]
|
||||
header = {"input_names": names, "returns": {"kind": "NoneType", "tensors": []}, "check": None,
|
||||
"inputs": [{"slot": names.index("host"), "name": "host", "dtype": u8, "numel": packed, "device": "NPY"}] +
|
||||
[s | {"slot": slots[s["slot"]]} for s in model["inputs"] if s["slot"] in slots],
|
||||
[s | {"slot": slots[s["slot"]]} for s in model["inputs"] if s["slot"] in slots] +
|
||||
[s | {"slot": names.index(name)} for name, s in frame_specs.items()],
|
||||
"buffers": model["buffers"] + [b | ({"init": [b["init"][0] + shift, b["init"][1] + shift]} if "init" in b else {}) for b in warp["buffers"]] +
|
||||
[{"nbytes": packed, "device": model["inputs"][0]["device"], "dtype": u8}],
|
||||
"calls": [{"kind": "copy", "args": [{"buffer": upload, "offset": 0, "nbytes": packed, "dtype": u8}, {"input": names.index("host")}]}] +
|
||||
|
||||
@@ -270,16 +270,42 @@ def _program_uop(call: dict, lib: bytes):
|
||||
UOp(Ops.SOURCE, arg=""), UOp(Ops.BINARY, arg=lib)), arg=info)
|
||||
|
||||
|
||||
def _input_key(args: tuple, kwargs: dict) -> tuple | None:
|
||||
from tinygrad import Tensor
|
||||
if args:
|
||||
return None
|
||||
key = []
|
||||
for name, value in sorted(kwargs.items()):
|
||||
if not isinstance(value, (Tensor, dict)):
|
||||
return None
|
||||
for sub, tensor in [(None, value)] if isinstance(value, Tensor) else sorted(value.items()):
|
||||
if not isinstance(tensor, Tensor) or not tensor.uop.is_realized:
|
||||
return None
|
||||
key.append((name, sub, tensor.uop))
|
||||
return tuple(key)
|
||||
|
||||
|
||||
class _Jit:
|
||||
def __init__(self, captured, input_names: list, buffers: list):
|
||||
self.captured, self.input_names, self.buffers = captured, input_names, buffers
|
||||
|
||||
def __getstate__(self):
|
||||
return {k: v for k, v in self.__dict__.items() if k != "_bound"}
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
import tinygrad.engine.jit as jit
|
||||
input_uops, var_vals, names, _ = jit._prepare_jit_inputs(args, kwargs)
|
||||
if names != self.input_names:
|
||||
raise ValueError(f"expected inputs {self.input_names}, got {names}")
|
||||
return self.captured(input_uops, var_vals)
|
||||
# modeld binds the same buffers every frame (the camera buffers cycle through a few): bind each set once, not per call
|
||||
bound, key = self.__dict__.setdefault("_bound", {}), _input_key(args, kwargs)
|
||||
if key is None or (hit := bound.get(key)) is None:
|
||||
input_uops, var_vals, names, _ = jit._prepare_jit_inputs(args, kwargs)
|
||||
if names != self.input_names:
|
||||
raise ValueError(f"expected inputs {self.input_names}, got {names}")
|
||||
hit = (input_uops, var_vals)
|
||||
if key is not None:
|
||||
if len(bound) >= 16:
|
||||
bound.clear()
|
||||
bound[key] = hit
|
||||
return self.captured(*hit)
|
||||
|
||||
|
||||
class PlainJit(_Jit):
|
||||
@@ -311,7 +337,7 @@ class PlainJit(_Jit):
|
||||
calls.append(_program_uop(call, bytes(blob[slice(*call["lib"])])).call(*arguments))
|
||||
else:
|
||||
calls.append(arguments[0].store_call(arguments[1]))
|
||||
# stand-ins for JIT inputs: copy staging to GPU is decided from the input buffers, the real ones bind per call
|
||||
# allocate gpu buffers
|
||||
stand_ins = {slot: UOp.from_buffer(Buffer(_device(spec["device"], device_map), spec["numel"] * _dtype(spec["dtype"]).itemsize).ensure_allocated(),
|
||||
_dtype(spec["dtype"])) for slot, spec in inputs.items()}
|
||||
input_uops = tuple(stand_ins.get(slot, next(iter(stand_ins.values()), None)) for slot in range(max(stand_ins, default=-1) + 1))
|
||||
|
||||
@@ -11,7 +11,7 @@ import pickle
|
||||
import numpy as np
|
||||
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.sunnypilot.modeld_v2.frame_fusion import pack_layout
|
||||
from openpilot.sunnypilot.modeld_v2.frame_fusion import pack_layout, SPLIT_FRAMES
|
||||
from openpilot.sunnypilot.modeld_v2.helpers import load_model
|
||||
from openpilot.sunnypilot.modeld_v2.compile_modeld import (POLICY_INPUTS, derive_frame_skip,
|
||||
make_split_input_queues, make_supercombo_input_queues)
|
||||
@@ -169,16 +169,18 @@ class NativeTinygradAdapter(BaseModelAdapter):
|
||||
self.warp_frame_size = self.frame_copy_size
|
||||
self._init_common()
|
||||
self.run_model = self.jits['run']
|
||||
self.split = [name for name in SPLIT_FRAMES if name in self.run_model.input_names] # the warp reads the camera buffers in place
|
||||
self.frame_inputs: dict[str, Tensor] = {}
|
||||
|
||||
self.input_queues = {name: Tensor(np.zeros(shape, dtype=dtype), device=self.model_device).realize()
|
||||
for name, (shape, dtype) in self.input_shapes.items() if name in self.state_pairs}
|
||||
shapes = {name: shape for name, (shape, _) in self.input_shapes.items() if name not in self.state_pairs and name != 'new_img'}
|
||||
offsets, packed = pack_layout(shapes, self.warp_frame_size)
|
||||
offsets, packed = pack_layout(shapes, 0 if self.split else self.warp_frame_size)
|
||||
self.packed_input = np.zeros(packed, dtype=np.uint8)
|
||||
self.host = Tensor(self.packed_input, device='NPY')
|
||||
self.numpy_inputs = {name: np.ndarray(shape, dtype=np.float32, buffer=self.packed_input, offset=offsets[name])
|
||||
for name, shape in ({'tfm': (2, 3, 3)} | shapes).items()}
|
||||
self.frames = self.packed_input[offsets['frames']:].reshape(2, self.warp_frame_size)
|
||||
self.frames = None if self.split else self.packed_input[offsets['frames']:].reshape(2, self.warp_frame_size)
|
||||
|
||||
self.outputs = {name: Tensor(np.zeros(shape, dtype=dtype), device=device).realize() for name, (shape, dtype, device) in self.jits['output_specs'].items()}
|
||||
for name, next_name in self.state_pairs.items():
|
||||
@@ -187,17 +189,30 @@ class NativeTinygradAdapter(BaseModelAdapter):
|
||||
|
||||
def copy_frames(self, bufs):
|
||||
for i, key in enumerate(self._vision_input_names):
|
||||
if key in bufs:
|
||||
if key not in bufs:
|
||||
continue
|
||||
if not self.split:
|
||||
data = bufs[key].data if hasattr(bufs[key], 'data') else bufs[key]
|
||||
np.copyto(self.frames[i], np.frombuffer(data, dtype=np.uint8, count=self.warp_frame_size))
|
||||
elif (name := ('frame', 'big_frame')[i]) in self.split:
|
||||
if not hasattr(bufs[key], 'data'): # a numpy frame (warmup, tests): cached memory the GPU must not read in place, so upload it
|
||||
self.frame_inputs[name] = Tensor(np.ascontiguousarray(bufs[key][:self.warp_frame_size]), device=self.model_device).realize()
|
||||
continue
|
||||
ptr = np.frombuffer(bufs[key].data, dtype=np.uint8).ctypes.data # visionipc buffers are coherent: map once per buffer, no copy
|
||||
if (key, ptr) not in self._blob_cache:
|
||||
self._blob_cache[(key, ptr)] = Tensor.from_blob(ptr, (self.warp_frame_size,), dtype='uint8', device=self.model_device)
|
||||
self.frame_inputs[name] = self._blob_cache[(key, ptr)]
|
||||
|
||||
def reset_warmup_buffers(self) -> None:
|
||||
self.packed_input[:] = 0
|
||||
self.frame_inputs.clear()
|
||||
self._blob_cache.clear()
|
||||
self.run_model.__dict__.pop('_bound', None) # warmup's uploaded frames: drop their bindings with them
|
||||
for key in self.state_pairs:
|
||||
self.input_queues[key].assign(0).realize()
|
||||
|
||||
def run(self):
|
||||
self.run_model(host=self.host, **self.input_queues, output_buffers=self.outputs)
|
||||
self.run_model(host=self.host, **self.input_queues, **self.frame_inputs, output_buffers=self.outputs)
|
||||
return self.outputs['outputs']
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4b22fd6bcb3a3e8a642e6866449dd9c30c12e694ff0741cad9e34e4b00d8c5fa
|
||||
size 3698799
|
||||
oid sha256:d776c72125156b5dd3eb59768409401e28c8a52a3a619cd3b9f02c10a156fda6
|
||||
size 3699072
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9a17fd6acece4fab23784ef0f3f8bba28a049e9599e5e9e5ee7b36e4b1d24a6f
|
||||
size 7924847
|
||||
oid sha256:a3f13ed12e087817a86afd48b2496c66c4f61a1925065b676b2945e81fabaacd
|
||||
size 7924864
|
||||
|
||||
@@ -144,6 +144,21 @@ class TestPlainArtifact(OpenpilotTestCase):
|
||||
for name in ("outputs", "next_state_feat_q"):
|
||||
assert np.array_equal(inputs["output_buffers"][name].numpy(), expected["output_buffers"][name].numpy()), name
|
||||
|
||||
split_path = os.path.join(self.tmp.name, "warp_split_tinygrad.pkl")
|
||||
with mock.patch.object(sys, "argv", ["compile_warp", "--frame", FRAME, "--warp-to", f"{MODEL_W}x{MODEL_H}", "--output", split_path, "--split-frames"]):
|
||||
compile_warp.main()
|
||||
split = load_model(path, cache=False, extend=functools.partial(frame_part, split_path))
|
||||
assert split["run"].input_names == ["big_frame", "frame", "host", "state_feat_q"]
|
||||
offsets, packed = pack_layout(HOST, 0)
|
||||
host = np.zeros(packed, np.uint8)
|
||||
for name, shape in ({"tfm": (2, 3, 3)} | HOST).items():
|
||||
np.ndarray(shape, np.float32, host, offsets[name])[...] = tfm.numpy() if name == "tfm" else inputs[name].numpy()
|
||||
out = {"outputs": Tensor.zeros(OUT).contiguous().realize(), "next_state_feat_q": Tensor.zeros(STATE).contiguous().realize()}
|
||||
split["run"](host=Tensor(host, device="NPY"), frame=Tensor(frames.numpy()[0]).realize(), big_frame=Tensor(frames.numpy()[1]).realize(),
|
||||
state_feat_q=expected["state_feat_q"], output_buffers=out)
|
||||
for name in ("outputs", "next_state_feat_q"): # the camera buffers bound in place give the packed upload's result exactly
|
||||
assert np.array_equal(out[name].numpy(), expected["output_buffers"][name].numpy()), name
|
||||
|
||||
def test_tinygrad_api_the_loader_reads(self):
|
||||
import tinygrad.engine.jit as jit
|
||||
from tinygrad.device import Buffer, TinyELF
|
||||
|
||||
@@ -1412,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",
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user