Compare commits

...

18 Commits

Author SHA1 Message Date
discountchubbs 88dc1ac3bc Merge remote-tracking branch 'origin/master' into sunnymodel 2026-02-10 15:13:58 -08:00
DevTekVE 1179273217 Revert "sunnylink: shorter timeout for websocket reconnect" (#1683)
Revert "sunnylink: shorter timeout for websocket reconnect (#1681)"

This reverts commit c51b41e96a.
2026-02-10 11:28:51 -05:00
Jason Wen c51b41e96a sunnylink: shorter timeout for websocket reconnect (#1681)
* separate

* no need

* shorten

* lint
2026-02-09 23:46:39 -05:00
Jason Wen 7722d14b89 Reapply "[TIZI/TICI] ui: drive stat cache is a json already"
This reverts commit 043eab5620.
2026-02-09 02:55:17 -05:00
Jason Wen 043eab5620 Revert "[TIZI/TICI] ui: drive stat cache is a json already"
This reverts commit 51d3f0dea2.
2026-02-09 02:54:58 -05:00
Jason Wen 51d3f0dea2 [TIZI/TICI] ui: drive stat cache is a json already 2026-02-09 02:47:35 -05:00
Jason Wen 8be8bbbfdb [TIZI/TICI] ui: Trips panel (#1679)
* init

* more

* change

* exist

* better title

* adjust

* more

* seems better

* slightly more

* slightly more

* center it

* final

* move

* no bc ew

* more less
2026-02-09 02:43:28 -05:00
Nayan 1f778c8c23 Device: Retain QuickBoot state after op switch (#1333)
Device: Retain QuickBoot state after SSH Update

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
Co-authored-by: DevTekVE <devtekve@gmail.com>
2026-02-09 00:38:39 -05:00
Nayan 981494a354 [TIZI/TICI] ui: Visuals panel (#1496)
* commaai/openpilot:d05cb31e2e916fba41ba8167030945f427fd811b

* bump opendbc

* bump opendbc

* bump opendbc

* bump opendbc

* bump opendbc

* sunnypilot: remove Qt

* cabana: revert to stock Qt

* commaai/openpilot:5198b1b079c37742c1050f02ce0aa6dd42b038b9

* commaai/openpilot:954b567b9ba0f3d1ae57d6aa7797fa86dd92ec6e

* commaai/openpilot:7534b2a160faa683412c04c1254440e338931c5e

* sum more

* bump opendbc

* not yet

* should've been symlink'ed

* raylib says wut

* quiet mode back

* more fixes

* no more

* too extra red diff on the side

* need to bring this back

* too extra

* let's update docs here

* Revert "let's update docs here"

This reverts commit 51fe03cd51.

* param to control stock vs sp ui

* init styles

* SP Toggles

* Lint

* optimizations

* multi-button

* Lint

* param to control stock vs sp ui

* init styles

* SP Toggles

* Lint

* optimizations

* Panels. With Icons. And Scroller.

* patience, grasshopper

* more patience, grasshopper

* sp raylib preview

* fix callback

* fix ui preview

* add ui previews

* introducing ui_state_sp for py

* param to control stock vs sp ui

* better

* add ui_update callback

* better padding

* init

* revert padding to 20

* new line, who dis

* this

* support for next line multi-button

* use inline=false

* uhh

* disabled colors

* hide em all

* lambdas

* NOT inline

* final touches

* hide HIDE

* ruff.. RUFF.. WHY RUFF

* listitem -> listitemsp

* Revert "add ui_update callback"

This reverts commit 4da32cc009.

* add show_description method

* remove padding from line separator.
like, WHY? 😩😩

* scroller -> scroller_tici

* scroller -> scroller_tici

* remove line separator padding

* ui: `GuiApplicationExt`

* add to readme

* use gui_app.sunnypilot_ui()

* use gui_app.sunnypilot_ui()

* use gui_app.sunnypilot_ui()

* uhhh. nope

* optimizations

* I THINK this is not needed, i don't see it used on the visuals panel...

* unhide for now... Why hidden tho?

* refresh controls

* missing

* blindspot

* standstill timer

* road name toggle

* more descriptions

* more descriptions

* update desc

* param turn signals

* sort

* fix

* always show desc if not available

* should be bool

* rocket fuel

* steering arc

* lint

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
Co-authored-by: DevTekVE <devtekve@gmail.com>
2026-02-09 00:17:34 -05:00
Jason Wen 254f55ac15 [TIZI/TICI] ui: Hide vEgo and True vEgo (#1678) 2026-02-08 20:42:23 -05:00
James Vecellio-Grant 96b58024ab [MICI] ui: driving models selector (#1574)
* ui: models mici

* Update models.py

* Update models.py

* sync

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-02-08 19:54:33 -05:00
Jason Wen a9229e11a0 [TIZI/TICI] ui: standstill timer (#1677)
* standstill timer

* final
2026-02-08 19:40:04 -05:00
Jason Wen 020f503364 [TIZI/TICI] ui: Green Light and Lead Departure elements (#1676)
* init

* big for now

* only adjust for right dev ui for now

* final

* final final
2026-02-08 19:14:19 -05:00
Jason Wen c274dba36e [TIZI/TICI] ui: Smart Cruise Control elements (#1675)
* init

* punch

* lower

* colors
2026-02-08 18:28:36 -05:00
royjr 35c87a1519 [TIZI/TICI] ui: steering arc (#1628)
* init

* lint

* add toggle

* Update params_keys.h

* Update params_metadata.json

* Update params_keys.h

* bool

* decouple

* no

* make it perfect

* fade it

* only with torque bar

* dynamic

* in another PR

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-02-08 17:00:37 -05:00
github-actions[bot] 81bd8aa0e2 [bot] Update Python packages (#1662)
* Update Python packages

* bump

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2026-02-08 01:03:16 -05:00
royjr c908189e73 ui: use correct signals while using PID with Developer UI (#1674)
* only if angleState

* pidState element
2026-02-08 00:32:12 -05:00
discountchubbs d7e5f3cf43 init sunnymodel with wiped locationd and livepose for temporary sim xytz data 2026-02-01 10:12:28 -08:00
44 changed files with 1748 additions and 91 deletions
+4 -1
View File
@@ -115,6 +115,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"SnoozeUpdate", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
{"SshEnabled", {PERSISTENT | BACKUP, BOOL}},
{"TermsVersion", {PERSISTENT, STRING}},
{"TorqueBar", {PERSISTENT | BACKUP, BOOL, "0"}},
{"TrainingVersion", {PERSISTENT, STRING}},
{"UbloxAvailable", {PERSISTENT, BOOL}},
{"UpdateAvailable", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
@@ -228,6 +229,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"LaneTurnDesire", {PERSISTENT | BACKUP, BOOL, "0"}},
{"LaneTurnValue", {PERSISTENT | BACKUP, FLOAT, "19.0"}},
{"PlanplusControl", {PERSISTENT | BACKUP, FLOAT, "1.0"}},
{"ModeldSunny", {PERSISTENT | BACKUP, BOOL, "0"}},
{"AlpamayoDriveFast", {PERSISTENT | BACKUP, BOOL, "0"}},
// mapd
{"MapAdvisorySpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT}},
@@ -248,7 +251,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"OsmStateTitle", {PERSISTENT, STRING}},
{"OsmWayTest", {PERSISTENT, STRING}},
{"RoadName", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
{"RoadNameToggle", {PERSISTENT, STRING}},
{"RoadNameToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
// Speed Limit
{"SpeedLimitMode", {PERSISTENT | BACKUP, INT, "1"}},
+1 -10
View File
@@ -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.
# 344 Supported Cars
# 335 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>&nbsp;|Video|Setup Video|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@@ -23,10 +23,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi S3 2015-17">Buy Here</a></sub></details>|||
|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim, without Super Cruise Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EUV 2022-23">Buy Here</a></sub></details>|<a href="https://youtu.be/xvwzGMUA210" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EV 2022-23">Buy Here</a></sub></details>|||
|Chevrolet|Bolt EV Non-ACC 2017|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EV Non-ACC 2017">Buy Here</a></sub></details>|||
|Chevrolet|Bolt EV Non-ACC 2018-21|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EV Non-ACC 2018-21">Buy Here</a></sub></details>|||
|Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Equinox 2019-22">Buy Here</a></sub></details>|||
|Chevrolet|Malibu Non-ACC 2016-23|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Malibu Non-ACC 2016-23">Buy Here</a></sub></details>|||
|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Silverado 1500 2020-21">Buy Here</a></sub></details>|||
|Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Trailblazer 2021-22">Buy Here</a></sub></details>|||
|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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=Chrysler Pacifica 2017-18">Buy Here</a></sub></details>|||
@@ -91,7 +88,6 @@ A supported vehicle is one that just works when you install a comma device. All
|Honda|Civic Hatchback Hybrid 2025-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda Civic Hatchback Hybrid 2025-26">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda Civic Hatchback Hybrid (Europe only) 2023">Buy Here</a></sub></details>|||
|Honda|Civic Hybrid 2025-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda Civic Hybrid 2025-26">Buy Here</a></sub></details>|||
|Honda|Clarity 2018-21|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector + Honda Clarity Proxy Board<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://shop.retropilot.org/product/honda-clarity-proxy-board-kit">Buy Here</a></sub></details>|||
|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec 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=Honda CR-V 2015-16">Buy Here</a></sub></details>|||
|Honda|CR-V 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|15 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch 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=Honda CR-V 2017-22">Buy Here</a></sub></details>|||
|Honda|CR-V 2023-26|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C 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=Honda CR-V 2023-26">Buy Here</a></sub></details>|||
@@ -122,7 +118,6 @@ A supported vehicle is one that just works when you install a comma device. All
|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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 Elantra 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Hyundai|Elantra GT 2017-20|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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 Elantra GT 2017-20">Buy Here</a></sub></details>|||
|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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 Elantra Hybrid 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Hyundai|Elantra Non-SCC 2022|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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 Elantra Non-SCC 2022">Buy Here</a></sub></details>|||
|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai J 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 Genesis 2015-16">Buy Here</a></sub></details>|||
|Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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 i30 2017-19">Buy Here</a></sub></details>|||
|Hyundai|Ioniq 5 (Southeast Asia and Europe only) 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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=Hyundai Ioniq 5 (Southeast Asia and Europe only) 2022-24">Buy Here</a></sub></details>|||
@@ -140,9 +135,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G 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 Kona Electric 2018-21">Buy Here</a></sub></details>|||
|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O 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 Kona Electric 2022-23">Buy Here</a></sub></details>|||
|Hyundai|Kona Electric (with HDA II, Korea only) 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai R 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 Kona Electric (with HDA II, Korea only) 2023">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=U2fOCmcQ8hw" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Hyundai|Kona Electric Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G 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 Kona Electric Non-SCC 2019">Buy Here</a></sub></details>|||
|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai I 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 Kona Hybrid 2020">Buy Here</a></sub></details>|||
|Hyundai|Kona Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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=Hyundai Kona Non-SCC 2019">Buy Here</a></sub></details>|||
|Hyundai|Nexo 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H 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 Nexo 2021">Buy Here</a></sub></details>|||
|Hyundai|Palisade 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H 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 Palisade 2020-22">Buy Here</a></sub></details>|<a href="https://youtu.be/TAnDqjF4fDY?t=456" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Hyundai|Santa Cruz 2022-24|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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 Santa Cruz 2022-24">Buy Here</a></sub></details>|||
@@ -166,13 +159,11 @@ A supported vehicle is one that just works when you install a comma device. All
|Kia|Carnival 2022-24|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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 Non-SCC 2022|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai I 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 Non-SCC 2022">Buy Here</a></sub></details>|||
|Kia|EV6 (Southeast Asia only) 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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>|||
|Kia|EV6 (with HDA II) 2022-24|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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 (with HDA II) 2022-24">Buy Here</a></sub></details>|||
|Kia|EV6 (without HDA II) 2022-24|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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=Kia EV6 (without HDA II) 2022-24">Buy Here</a></sub></details>|||
|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G 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 Forte 2019-21">Buy Here</a></sub></details>|||
|Kia|Forte 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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 Forte 2022-23">Buy Here</a></sub></details>|||
|Kia|Forte Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G 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 Forte Non-SCC 2019">Buy Here</a></sub></details>|||
|Kia|K5 2021-24|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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 K5 2021-24">Buy Here</a></sub></details>|||
|Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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 K5 Hybrid 2020-22">Buy Here</a></sub></details>|||
|Kia|K8 Hybrid (with HDA II) 2023|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<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 K8 Hybrid (with HDA II) 2023">Buy Here</a></sub></details>|||
+3 -2
View File
@@ -3,14 +3,15 @@ import time
from cereal import car, log, messaging
from openpilot.common.params import Params
from openpilot.system.manager.process_config import managed_processes, is_snpe_model, is_tinygrad_model, is_stock_model
from openpilot.system.manager.process_config import managed_processes, is_snpe_model, is_tinygrad_model, is_stock_model, is_modeld_sunny
from openpilot.system.hardware import HARDWARE
if __name__ == "__main__":
CP = car.CarParams(notCar=True, wheelbase=1, steerRatio=10)
params = Params()
params.put("CarParams", CP.to_bytes())
if is_modeld_sunny:= is_modeld_sunny(False, params, CP):
print("Using sunnypilot custom modeld")
if use_snpe_modeld := is_snpe_model(False, params, CP):
print("Using SNPE modeld")
if use_tinygrad_modeld := is_tinygrad_model(False, params, CP):
+2 -3
View File
@@ -270,7 +270,7 @@ def main():
estimator = LocationEstimator(DEBUG)
filter_initialized = False
critcal_services = ["accelerometer", "gyroscope", "cameraOdometry"]
critcal_services = ["accelerometer", "gyroscope"]
observation_input_invalid = defaultdict(int)
input_invalid_limit = {s: round(INPUT_INVALID_LIMIT * (SERVICE_LIST[s].frequency / 20.)) for s in critcal_services}
@@ -320,8 +320,7 @@ def main():
filter_initialized = sm.all_checks() and sensor_all_checks(acc_msgs, gyro_msgs, sensor_valid, sensor_recv_time, sensor_alive, SIMULATION)
if sm.updated["cameraOdometry"]:
critical_service_inputs_valid = all(observation_input_invalid[s] < input_invalid_threshold[s] for s in critcal_services)
inputs_valid = sm.all_valid() and critical_service_inputs_valid
inputs_valid = True
sensors_valid = sensor_all_checks(acc_msgs, gyro_msgs, sensor_valid, sensor_recv_time, sensor_alive, SIMULATION)
msg = estimator.get_msg(sensors_valid, inputs_valid, filter_initialized)
+3 -3
View File
@@ -91,7 +91,7 @@ class SelfdriveD(CruiseHelper):
ignore = self.sensor_packets + self.gps_packets + ['alertDebug'] + ['modelDataV2SP']
if SIMULATION:
ignore += ['driverCameraState', 'managerState']
ignore += ['driverCameraState', 'managerState', 'livePose', 'liveCalibration', 'liveParameters', 'liveTorqueParameters', 'liveDelay', 'driverAssistance']
if REPLAY:
# no vipc in replay will make them ignored anyways
ignore += ['roadCameraState', 'wideRoadCameraState']
@@ -388,8 +388,8 @@ class SelfdriveD(CruiseHelper):
if not self.CP.notCar:
if not self.sm['livePose'].posenetOK:
self.events.add(EventName.posenetInvalid)
if not self.sm['livePose'].inputsOK:
self.events.add(EventName.locationdTemporaryError)
# if not self.sm['livePose'].inputsOK:
# self.events.add(EventName.locationdTemporaryError)
if not self.sm['liveParameters'].valid and cal_status == log.LiveCalibrationData.Status.calibrated and not TESTING_CLOSET and (not SIMULATION or REPLAY):
self.events.add(EventName.paramsdTemporaryError)
+9 -7
View File
@@ -146,9 +146,11 @@ def arc_bar_pts(cx: float, cy: float,
class TorqueBar(Widget):
def __init__(self, demo: bool = False):
def __init__(self, demo: bool = False, scale: float = 1.0, always: bool = False):
super().__init__()
self._demo = demo
self._scale = scale
self._always = always
self._torque_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps)
self._torque_line_alpha_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps)
@@ -180,8 +182,8 @@ class TorqueBar(Widget):
def _render(self, rect: rl.Rectangle) -> None:
# adjust y pos with torque
torque_line_offset = np.interp(abs(self._torque_filter.x), [0.5, 1], [22, 26])
torque_line_height = np.interp(abs(self._torque_filter.x), [0.5, 1], [14, 56])
torque_line_offset = np.interp(abs(self._torque_filter.x), [0.5, 1], [22 * self._scale, 26 * self._scale])
torque_line_height = np.interp(abs(self._torque_filter.x), [0.5, 1], [14 * self._scale, 56 * self._scale])
# animate alpha and angle span
if not self._demo:
@@ -195,7 +197,7 @@ class TorqueBar(Widget):
torque_line_bg_color = rl.Color(255, 255, 255, int(255 * 0.15 * self._torque_line_alpha_filter.x))
# draw curved line polygon torque bar
torque_line_radius = 1200
torque_line_radius = 1200 * self._scale
top_angle = -90
torque_bg_angle_span = self._torque_line_alpha_filter.x * TORQUE_ANGLE_SPAN
torque_start_angle = top_angle - torque_bg_angle_span / 2
@@ -207,13 +209,13 @@ class TorqueBar(Widget):
cy = rect.y + rect.height + torque_line_radius - torque_line_offset
# draw bg torque indicator line
bg_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, torque_start_angle, torque_end_angle)
bg_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, torque_start_angle, torque_end_angle, cap_radius=7 * self._scale)
draw_polygon(rect, bg_pts, color=torque_line_bg_color)
# draw torque indicator line
a0s = top_angle
a1s = a0s + torque_bg_angle_span / 2 * self._torque_filter.x
sl_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, a0s, a1s)
sl_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, a0s, a1s, cap_radius=7 * self._scale)
# draw beautiful gradient from center to 65% of the bg torque bar width
start_grad_pt = cx / rect.width
@@ -252,5 +254,5 @@ class TorqueBar(Widget):
# draw center torque bar dot
if abs(self._torque_filter.x) < 0.5:
dot_y = self._rect.y + self._rect.height - torque_line_offset - torque_line_height / 2
rl.draw_circle(int(cx), int(dot_y), 10 // 2,
rl.draw_circle(int(cx), int(dot_y), (10 // 2 * self._scale),
rl.Color(182, 182, 182, int(255 * 0.9 * self._torque_line_alpha_filter.x)))
+5 -3
View File
@@ -15,7 +15,7 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCamera
from openpilot.common.transformations.orientation import rot_from_euler
if gui_app.sunnypilot_ui():
from openpilot.selfdrive.ui.sunnypilot.onroad.augmented_road_view import BORDER_COLORS_SP
from openpilot.selfdrive.ui.sunnypilot.onroad.augmented_road_view import BORDER_COLORS_SP, AugmentedRoadViewSP
from openpilot.selfdrive.ui.sunnypilot.onroad.driver_state import DriverStateRendererSP as DriverStateRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.hud_renderer import HudRendererSP as HudRenderer
from openpilot.selfdrive.ui.sunnypilot.ui_state import OnroadTimerStatus
@@ -38,9 +38,10 @@ ROAD_CAM_MIN_SPEED = 15.0 # m/s (34 mph)
INF_POINT = np.array([1000.0, 0.0, 0.0])
class AugmentedRoadView(CameraView):
class AugmentedRoadView(CameraView, AugmentedRoadViewSP):
def __init__(self, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD):
super().__init__("camerad", stream_type)
CameraView.__init__(self, "camerad", stream_type)
AugmentedRoadViewSP.__init__(self)
self._set_placeholder_color(BORDER_COLORS[UIStatus.DISENGAGED])
self.device_camera: DeviceCameraConfig | None = None
@@ -92,6 +93,7 @@ class AugmentedRoadView(CameraView):
# Draw all UI overlays
self.model_renderer.render(self._content_rect)
AugmentedRoadViewSP.update_fade_out_bottom_overlay(self, self._content_rect)
self._hud_renderer.render(self._content_rect)
self.alert_renderer.render(self._content_rect)
self.driver_state_renderer.render(self._content_rect)
+133 -12
View File
@@ -4,27 +4,148 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import requests
import threading
import time
import pyray as rl
from openpilot.common.api import api_get
from openpilot.common.constants import CV
from openpilot.common.params import Params
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.ui.lib.api_helpers import get_token
from openpilot.selfdrive.ui.ui_state import ui_state, device
from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.widgets import Widget
class TripsLayout(Widget):
PARAM_KEY = "ApiCache_DriveStats"
UPDATE_INTERVAL = 30 # seconds
def __init__(self):
super().__init__()
self._params = Params()
items = self._initialize_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
self._session = requests.Session()
self._stats = self._get_stats()
def _initialize_items(self):
items = [
self._icon_distance = gui_app.texture("icons/road.png", 100, 100, keep_aspect_ratio=True)
self._icon_drives = gui_app.texture("icons_mici/wheel.png", 80, 80, keep_aspect_ratio=True)
self._icon_hours = gui_app.texture("../../sunnypilot/selfdrive/assets/icons/clock.png", 80, 80, keep_aspect_ratio=True)
]
return items
self._running = True
self._update_thread = threading.Thread(target=self._update_loop, daemon=True)
self._update_thread.start()
def _render(self, rect):
self._scroller.render(rect)
def __del__(self):
self._running = False
try:
if self._update_thread and self._update_thread.is_alive():
self._update_thread.join(timeout=1.0)
except Exception:
pass
def show_event(self):
self._scroller.show_event()
def _get_stats(self):
stats = self._params.get(self.PARAM_KEY)
if not stats:
return {}
try:
return stats
except Exception:
cloudlog.exception(f"Failed to decode drive stats: {stats}")
return {}
def _fetch_drive_stats(self):
try:
dongle_id = self._params.get("DongleId")
if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID:
return
identity_token = get_token(dongle_id)
response = api_get(f"v1.1/devices/{dongle_id}/stats", access_token=identity_token, session=self._session)
if response.status_code == 200:
data = response.json()
self._stats = data
self._params.put(self.PARAM_KEY, data)
except Exception as e:
cloudlog.error(f"Failed to fetch drive stats: {e}")
def _update_loop(self):
while self._running:
if not ui_state.started and device._awake:
self._fetch_drive_stats()
time.sleep(self.UPDATE_INTERVAL)
def _render_stat_group(self, x, y, width, height, title, data, is_metric):
# Card Background
rl.draw_rectangle_rounded(rl.Rectangle(x, y, width, height), 0.05, 10, rl.Color(30, 30, 30, 255))
# Title
title_font = gui_app.font(FontWeight.BOLD)
rl.draw_text_ex(title_font, title, rl.Vector2(x + 60, y + 30), 50 * FONT_SCALE, 0, rl.Color(200, 200, 200, 255))
# Internal content area
# Center the content block (Icon + Value + Unit) vertically
content_y = y + (height / 2) - (140 * FONT_SCALE)
col_width = width / 3
# Values
number_font = gui_app.font(FontWeight.BOLD)
unit_font = gui_app.font(FontWeight.LIGHT)
number_base_size = 92
unit_base_size = 55
number_size = number_base_size * FONT_SCALE
unit_size = unit_base_size * FONT_SCALE
color_unit = rl.Color(160, 160, 160, 255)
routes = int(data.get("routes", 0))
distance = data.get("distance", 0)
distance_str = str(int(distance * CV.MPH_TO_KPH)) if is_metric else str(int(distance))
hours = int(data.get("minutes", 0) / 60)
dist_unit = tr("KM") if is_metric else tr("Miles")
def draw_col(col_idx, icon, value, unit):
col_x = x + (col_width * col_idx)
center_x = col_x + (col_width / 2)
# Icon
icon_x = int(center_x - (icon.width / 2))
icon_y = int(content_y + 60)
rl.draw_texture(icon, icon_x, icon_y, rl.WHITE)
# Value
val_size = measure_text_cached(number_font, value, number_base_size)
rl.draw_text_ex(number_font, value, rl.Vector2(center_x - val_size.x / 1.65, content_y + 145 * FONT_SCALE), number_size, 0, rl.WHITE)
# Unit
unit_size_vec = measure_text_cached(unit_font, unit, unit_base_size)
rl.draw_text_ex(unit_font, unit, rl.Vector2(center_x - unit_size_vec.x / 1.65, content_y + 255 * FONT_SCALE), unit_size, 0, color_unit)
draw_col(0, self._icon_drives, str(routes), tr("Drives"))
draw_col(1, self._icon_distance, distance_str, dist_unit)
draw_col(2, self._icon_hours, str(hours), tr("Hours"))
return y + height
def _render(self, rect: rl.Rectangle):
x = rect.x
y = rect.y
w = rect.width
spacing = 30
available_h = rect.height - 30
card_height = available_h / 2
is_metric = self._params.get_bool("IsMetric")
all_time = self._stats.get("all", {})
week = self._stats.get("week", {})
y = self._render_stat_group(x, y, w, card_height, tr("ALL TIME"), all_time, is_metric)
y += spacing
y = self._render_stat_group(x, y, w, card_height, tr("PAST WEEK"), week, is_metric)
return -1
@@ -5,9 +5,18 @@ This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from openpilot.common.params import Params
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp, multiple_button_item_sp
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.widgets import Widget
CHEVRON_INFO_DESCRIPTION = {
"enabled": tr_noop("Display useful metrics below the chevron that tracks the lead car " +
"only applicable to cars with sunnypilot longitudinal control."),
"disabled": tr_noop("This feature requires sunnypilot longitudinal control to be available.")
}
class VisualsLayout(Widget):
def __init__(self):
@@ -18,13 +27,128 @@ class VisualsLayout(Widget):
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _initialize_items(self):
items = [
self._toggle_defs = {
"BlindSpot": (
lambda: tr("Show Blind Spot Warnings"),
tr("Enabling this will display warnings when a vehicle is detected in your " +
"blind spot as long as your car has BSM supported."),
None,
),
"TorqueBar": (
lambda: tr("Steering Arc"),
tr("Display steering arc on the driving screen when lateral control is enabled."),
None,
),
"RainbowMode": (
lambda: tr("Enable Tesla Rainbow Mode"),
tr("A beautiful rainbow effect on the path the model wants to take. " +
"It does not affect driving in any way."),
None,
),
"StandstillTimer": (
lambda: tr("Enable Standstill Timer"),
tr("Show a timer on the HUD when the car is at a standstill."),
None,
),
"RoadNameToggle": (
lambda: tr("Display Road Name"),
tr("Displays the name of the road the car is traveling on." +
"<br>The OpenStreetMap database of the location must be downloaded from " +
"the OSM panel to fetch the road name."),
None,
),
"GreenLightAlert": (
lambda: tr("Green Traffic Light Alert (Beta)"),
tr("A chime and on-screen alert will play when the traffic light you are waiting for " +
"turns green and you have no vehicle in front of you." +
"<br>Note: This chime is only designed as a notification. " +
"It is the driver's responsibility to observe their environment and make decisions accordingly."),
None,
),
"LeadDepartAlert": (
lambda: tr("Lead Departure Alert (Beta)"),
tr("A chime and on-screen alert will play when you are stopped, and the vehicle in front of you start moving." +
"<br>Note: This chime is only designed as a notification. " +
"It is the driver's responsibility to observe their environment and make decisions accordingly."),
None,
),
"TrueVEgoUI": (
lambda: tr("Speedometer: Always Display True Speed"),
tr("For applicable vehicles, always display the true vehicle current speed from wheel speed sensors."),
None,
),
"HideVEgoUI": (
lambda: tr("Speedometer: Hide from Onroad Screen"),
tr("When enabled, the speedometer on the onroad screen is not displayed."),
None,
),
"ShowTurnSignals": (
lambda: tr("Display Turn Signals"),
tr("When enabled, visual turn indicators are drawn on the HUD."),
None,
),
"RocketFuel": (
lambda: tr("Real-time Acceleration Bar"),
tr("Show an indicator on the left side of the screen to display real-time vehicle acceleration and deceleration. " +
"This displays what the car is currently doing, not what the planner is requesting."),
None,
),
}
self._toggles = {}
for param, (title, desc, callback) in self._toggle_defs.items():
toggle = toggle_item_sp(
title=title,
description=desc,
param=param,
initial_state=ui_state.params.get_bool(param),
callback=callback,
)
self._toggles[param] = toggle
self._chevron_info = multiple_button_item_sp(
title=lambda: tr("Display Metrics Below Chevron"),
description="",
buttons=[lambda: tr("Off"), lambda: tr("Distance"), lambda: tr("Speed"), lambda: tr("Time"), lambda: tr("All")],
param="ChevronInfo",
inline=False
)
self._dev_ui_info = multiple_button_item_sp(
title=lambda: tr("Developer UI"),
description=lambda: tr("Display real-time parameters and metrics from various sources."),
buttons=[lambda: tr("Off"), lambda: tr("Bottom"), lambda: tr("Right"), lambda: tr("Right & Bottom")],
param="DevUIInfo",
button_width=350,
inline=False
)
items = list(self._toggles.values()) + [
self._chevron_info,
self._dev_ui_info,
]
return items
def _update_state(self):
super()._update_state()
for param in self._toggle_defs:
self._toggles[param].action_item.set_state(self._params.get_bool(param))
self._dev_ui_info.action_item.set_selected_button(ui_state.params.get("DevUIInfo", return_default=True))
if ui_state.has_longitudinal_control:
self._chevron_info.set_description(tr(CHEVRON_INFO_DESCRIPTION["enabled"]))
self._chevron_info.action_item.set_selected_button(ui_state.params.get("ChevronInfo", return_default=True))
self._chevron_info.action_item.set_enabled(True)
else:
self._chevron_info.set_description(tr(CHEVRON_INFO_DESCRIPTION["disabled"]))
self._chevron_info.action_item.set_enabled(False)
ui_state.params.put("ChevronInfo", 0)
def _render(self, rect):
self._scroller.render(rect)
def show_event(self):
self._scroller.show_event()
if not ui_state.has_longitudinal_control:
self._chevron_info.set_description(tr(CHEVRON_INFO_DESCRIPTION["disabled"]))
self._chevron_info.show_description(True)
@@ -0,0 +1,122 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from collections.abc import Callable
from cereal import custom
from openpilot.selfdrive.ui.mici.widgets.button import BigButton
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.widgets import NavWidget, Widget
from openpilot.system.ui.widgets.scroller import Scroller
class ModelsLayoutMici(NavWidget):
def __init__(self, back_callback: Callable):
super().__init__()
self.set_back_callback(back_callback)
self.original_back_callback = back_callback
self.focused_widget = None
self.current_model_btn = BigButton(tr("current model"), "", "")
self.current_model_btn.set_click_callback(self._show_folders)
self.cancel_download_btn = BigButton(tr("cancel download"), "", "")
self.cancel_download_btn.set_click_callback(lambda: ui_state.params.remove("ModelManager_DownloadIndex"))
self.main_items: list[Widget] = [self.current_model_btn, self.cancel_download_btn]
self._scroller = Scroller(self.main_items, snap_items=False)
@property
def model_manager(self):
return ui_state.sm["modelManagerSP"]
def _get_grouped_bundles(self):
bundles = self.model_manager.availableBundles
folders = {}
for bundle in bundles:
folder = next((override.value for override in bundle.overrides if override.key == "folder"), "")
folders.setdefault(folder, []).append(bundle)
return folders
def _show_selection_view(self, items: list[Widget], back_callback: Callable):
self._scroller._items = items
for item in items:
item.set_touch_valid_callback(lambda: self._scroller.scroll_panel.is_touch_valid() and self._scroller.enabled)
self._scroller.scroll_panel.set_offset(0)
self.set_back_callback(back_callback)
def _show_folders(self):
self.focused_widget = self.current_model_btn
folders = self._get_grouped_bundles()
folder_buttons = []
default_btn = BigButton(tr("default model"), "", "")
default_btn.set_click_callback(self._select_default)
folder_buttons.append(default_btn)
for folder in sorted(folders.keys(), key=lambda f: max((bundle.index for bundle in folders[f]), default=-1), reverse=True):
if folder.lower() in ["release models", "master models"]:
btn = BigButton(folder.lower(), "", "")
btn.set_click_callback(lambda f=folder: self._select_folder(f))
folder_buttons.append(btn)
self._show_selection_view(folder_buttons, self._reset_main_view)
def _select_model(self, bundle):
ui_state.params.put("ModelManager_DownloadIndex", bundle.index)
self._reset_main_view()
def _select_default(self):
ui_state.params.remove("ModelManager_ActiveBundle")
self._reset_main_view()
def _select_folder(self, folder_name):
folders = self._get_grouped_bundles()
bundles = sorted(folders.get(folder_name, []), key=lambda b: b.index, reverse=True)
btns = []
for bundle in bundles:
txt = bundle.displayName.lower()
btn = BigButton(txt, "", "")
btn.set_click_callback(lambda b=bundle: self._select_model(b))
btns.append(btn)
self._show_selection_view(btns, self._show_folders)
def _reset_main_view(self):
self._scroller._items = self.main_items
self.set_back_callback(self.original_back_callback)
if self.focused_widget and self.focused_widget in self.main_items:
x = self._scroller._pad_start
for item in self.main_items:
if not item.is_visible:
continue
if item == self.focused_widget:
break
x += item.rect.width + self._scroller._spacing
self._scroller.scroll_panel.set_offset(0)
self._scroller.scroll_to(x)
self.focused_widget = None
else:
self._scroller.scroll_panel.set_offset(0)
def _update_state(self):
super()._update_state()
manager = self.model_manager
if manager.selectedBundle and manager.selectedBundle.status == custom.ModelManagerSP.DownloadStatus.downloading:
self.current_model_btn.set_value("downloading...")
self.cancel_download_btn.set_visible(True)
else:
self.current_model_btn.set_value(manager.activeBundle.internalName.lower() if manager.activeBundle else tr("default model"))
self.cancel_download_btn.set_visible(False)
self.current_model_btn.set_enabled(ui_state.is_offroad())
self.current_model_btn.set_text(tr("current model"))
def _render(self, rect):
self._scroller.render(rect)
def show_event(self):
super().show_event()
self._scroller.show_event()
@@ -9,6 +9,7 @@ from enum import IntEnum
from openpilot.selfdrive.ui.mici.layouts.settings import settings as OP
from openpilot.selfdrive.ui.mici.widgets.button import BigButton
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.sunnylink import SunnylinkLayoutMici
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.models import ModelsLayoutMici
ICON_SIZE = 70
@@ -16,6 +17,7 @@ OP.PanelType = IntEnum(
"PanelType",
[es.name for es in OP.PanelType] + [
"SUNNYLINK",
"MODELS",
],
start=0,
)
@@ -27,13 +29,19 @@ class SettingsLayoutSP(OP.SettingsLayout):
sunnylink_btn = BigButton("sunnylink", "", "icons_mici/settings/developer/ssh.png")
sunnylink_btn.set_click_callback(lambda: self._set_current_panel(OP.PanelType.SUNNYLINK))
models_btn = BigButton("models", "", "../../sunnypilot/selfdrive/assets/offroad/icon_models.png")
models_btn.set_click_callback(lambda: self._set_current_panel(OP.PanelType.MODELS))
self._panels.update({
OP.PanelType.SUNNYLINK: OP.PanelInfo("sunnylink", SunnylinkLayoutMici(back_callback=lambda: self._set_current_panel(None))),
OP.PanelType.MODELS: OP.PanelInfo("models", ModelsLayoutMici(back_callback=lambda: self._set_current_panel(None))),
})
items = self._scroller._items.copy()
items.insert(1, sunnylink_btn)
items.insert(2, models_btn)
self._scroller._items.clear()
for item in items:
self._scroller.add_widget(item)
@@ -5,9 +5,27 @@ This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
from openpilot.selfdrive.ui.ui_state import UIStatus
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.selfdrive.ui.ui_state import UIStatus, ui_state
from openpilot.system.ui.lib.application import gui_app
BORDER_COLORS_SP = {
UIStatus.LAT_ONLY: rl.Color(0x00, 0xC8, 0xC8, 0xFF), # Cyan for lateral-only state
UIStatus.LONG_ONLY: rl.Color(0x96, 0x1C, 0xA8, 0xFF), # Purple for longitudinal-only state
}
class AugmentedRoadViewSP:
def __init__(self):
self._fade_texture = gui_app.texture("icons_mici/onroad/onroad_fade.png")
self._fade_alpha_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps)
def update_fade_out_bottom_overlay(self, _content_rect):
# Fade out bottom of overlays for looks (only when engaged)
fade_alpha = self._fade_alpha_filter.update(ui_state.status != UIStatus.DISENGAGED)
if ui_state.torque_bar and fade_alpha > 1e-2:
# Scale the fade texture to the content rect
rl.draw_texture_pro(self._fade_texture,
rl.Rectangle(0, 0, self._fade_texture.width, self._fade_texture.height),
_content_rect, rl.Vector2(0, 0), 0.0,
rl.Color(255, 255, 255, int(255 * fade_alpha)))
@@ -31,6 +31,9 @@ class BlindSpotIndicators:
return self._blind_spot_left_alpha_filter.x > 0.01 or self._blind_spot_right_alpha_filter.x > 0.01
def render(self, rect: rl.Rectangle) -> None:
if not ui_state.blindspot:
return
BLIND_SPOT_MARGIN_X = 20 # Distance from edge of screen
BLIND_SPOT_Y_OFFSET = 100 # Distance from top of screen
@@ -0,0 +1,140 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
from cereal import log
from openpilot.selfdrive.ui import UI_BORDER_SIZE
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer
from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE
from openpilot.system.ui.lib.text_measure import measure_text_cached
class CircularAlertsRenderer:
def __init__(self):
self._green_light_alert_img = gui_app.texture("../../sunnypilot/selfdrive/assets/images/green_light.png", 250, 250)
self._lead_depart_alert_img = gui_app.texture("../../sunnypilot/selfdrive/assets/images/lead_depart.png", 250, 250)
self._e2e_alert_display_timer = 0
self._e2e_alert_frame = 0
self._green_light_alert = False
self._lead_depart_alert = False
self._standstill_elapsed_time = 0.0
self._is_standstill = False
self._alert_text = ""
self._alert_img = None
self._allow_e2e_alerts = False
def update(self) -> None:
sm = ui_state.sm
lp_sp = sm['longitudinalPlanSP']
car_state = sm['carState']
self._green_light_alert = lp_sp.e2eAlerts.greenLightAlert
self._lead_depart_alert = lp_sp.e2eAlerts.leadDepartAlert
self._is_standstill = car_state.standstill
if not ui_state.started:
self._standstill_elapsed_time = 0.0
self._allow_e2e_alerts = sm['selfdriveState'].alertSize == log.SelfdriveState.AlertSize.none and \
sm.recv_frame['driverStateV2'] > ui_state.started_frame
if self._green_light_alert or self._lead_depart_alert:
self._e2e_alert_display_timer = 3 * gui_app.target_fps
# reset onroad sleep timer for e2e alerts
ui_state.reset_onroad_sleep_timer()
if self._e2e_alert_display_timer > 0:
self._e2e_alert_frame += 1
self._e2e_alert_display_timer -= 1
if self._green_light_alert:
self._alert_text = "GREEN\nLIGHT"
self._alert_img = self._green_light_alert_img
elif self._lead_depart_alert:
self._alert_text = "LEAD VEHICLE\nDEPARTING"
self._alert_img = self._lead_depart_alert_img
elif ui_state.standstill_timer and self._is_standstill:
self._alert_img = None
self._standstill_elapsed_time += 1.0 / gui_app.target_fps
minute = int(self._standstill_elapsed_time / 60)
second = int(self._standstill_elapsed_time - (minute * 60))
self._alert_text = f"{minute:01d}:{second:02d}"
self._e2e_alert_frame += 1
else:
self._e2e_alert_frame = 0
if not self._is_standstill:
self._standstill_elapsed_time = 0.0
def render(self, rect: rl.Rectangle) -> None:
if not self._allow_e2e_alerts or (self._e2e_alert_display_timer <= 0 and not (ui_state.standstill_timer and self._is_standstill)):
return
e2e_alert_size = 250
dev_ui_width_adjustment = 180 if ui_state.developer_ui in (DeveloperUiRenderer.DEV_UI_RIGHT, DeveloperUiRenderer.DEV_UI_BOTH) else 100
x = rect.x + rect.width - e2e_alert_size - dev_ui_width_adjustment - (UI_BORDER_SIZE * 3)
y = rect.y + rect.height / 2 + 20
alert_rect = rl.Rectangle(x - e2e_alert_size, y - e2e_alert_size, e2e_alert_size * 2, e2e_alert_size * 2)
center = rl.Vector2(alert_rect.x + alert_rect.width / 2, alert_rect.y + alert_rect.height / 2)
# Pulse logic
is_pulsing = (self._e2e_alert_frame % gui_app.target_fps) < (gui_app.target_fps / 2.5)
# Standstill Timer (STOPPED) should be static white
if self._e2e_alert_display_timer == 0 and ui_state.standstill_timer and self._is_standstill:
frame_color = rl.Color(255, 255, 255, 75)
else:
frame_color = rl.Color(255, 255, 255, 75) if is_pulsing else rl.Color(0, 255, 0, 75)
# Draw Circle
rl.draw_circle_v(center, e2e_alert_size, rl.Color(0, 0, 0, 190))
# Draw Ring (Border)
rl.draw_ring(center, e2e_alert_size - 7.5, e2e_alert_size + 7.5, 0, 360, 0, frame_color)
# Draw Image
if self._alert_img and self._e2e_alert_display_timer > 0:
img_x = int(center.x - self._alert_img.width / 2)
img_y = int(center.y - self._alert_img.height / 2)
rl.draw_texture(self._alert_img, img_x, img_y, rl.WHITE)
# Draw Text
txt_color = rl.Color(255, 255, 255, 255) if is_pulsing else rl.Color(0, 255, 0, 190)
font = gui_app.font(FontWeight.BOLD)
text_size = 48
spacing = 0
lines = self._alert_text.split('\n')
# Position text at bottom of alert circle
bottom_y = (alert_rect.y + alert_rect.height) - (alert_rect.height / 7)
# Draw lines upwards from bottom
current_y = bottom_y - (len(lines) * text_size * FONT_SCALE)
if self._e2e_alert_display_timer == 0 and ui_state.standstill_timer and self._is_standstill:
# Standstill Timer Text
alert_alt_text = "STOPPED"
top_text_size = 80
measure_top = measure_text_cached(font, alert_alt_text, top_text_size, spacing)
top_y = alert_rect.y + alert_rect.height / 3.5
rl.draw_text_ex(font, alert_alt_text, rl.Vector2(center.x - measure_top.x / 2, top_y), top_text_size, spacing, rl.Color(255, 175, 3, 240))
# Timer
timer_text_size = 100
measure_timer = measure_text_cached(font, self._alert_text, timer_text_size, spacing)
timer_y = (alert_rect.y + alert_rect.height) - (alert_rect.height / 5) - measure_timer.y
rl.draw_text_ex(font, self._alert_text, rl.Vector2(center.x - measure_timer.x / 2, timer_y), timer_text_size, spacing, rl.WHITE)
else:
for line in lines:
measure = measure_text_cached(font, line, text_size, spacing)
line_x = center.x - measure.x / 2
rl.draw_text_ex(font, line, rl.Vector2(line_x, current_y), text_size, spacing, txt_color)
current_y += text_size * FONT_SCALE
@@ -10,7 +10,7 @@ from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui.elements import (
UiElement, RelDistElement, RelSpeedElement, SteeringAngleElement,
DesiredLateralAccelElement, ActualLateralAccelElement, DesiredSteeringAngleElement,
AEgoElement, LeadSpeedElement, FrictionCoefficientElement, LatAccelFactorElement,
SteeringTorqueEpsElement, BearingDegElement, AltitudeElement
SteeringTorqueEpsElement, BearingDegElement, AltitudeElement, DesiredSteeringPIDElement
)
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.text_measure import measure_text_cached
@@ -36,6 +36,7 @@ class DeveloperUiRenderer(Widget):
self.desired_lat_accel_elem = DesiredLateralAccelElement()
self.actual_lat_accel_elem = ActualLateralAccelElement()
self.desired_steer_elem = DesiredSteeringAngleElement()
self.desired_pid_steer_elem = DesiredSteeringPIDElement()
self.a_ego_elem = AEgoElement()
self.lead_speed_elem = LeadSpeedElement()
self.friction_elem = FrictionCoefficientElement()
@@ -85,8 +86,10 @@ class DeveloperUiRenderer(Widget):
]
if controls_state.lateralControlState.which() == 'torqueState':
elements.append(self.desired_lat_accel_elem.update(sm, ui_state.is_metric))
else:
elif controls_state.lateralControlState.which() == 'angleState':
elements.append(self.desired_steer_elem.update(sm, ui_state.is_metric))
elif controls_state.lateralControlState.which() == 'pidState':
elements.append(self.desired_pid_steer_elem.update(sm, ui_state.is_metric))
elements.append(self.actual_lat_accel_elem.update(sm, ui_state.is_metric))
@@ -191,6 +191,31 @@ class DesiredLateralAccelElement(LateralControlElement):
return UiElement(value, "DESIRED L.A.", self.unit, color)
class DesiredSteeringPIDElement(LateralControlElement):
def __init__(self):
self.unit = ""
def update(self, sm, is_metric: bool) -> UiElement:
car_state = sm['carState']
controls_state = sm['controlsState']
lat_active = sm['carControl'].latActive
angle_steers = car_state.steeringAngleDeg
steer_angle_desired = controls_state.lateralControlState.pidState.steeringAngleDesiredDeg
value = f"{steer_angle_desired:.1f}°" if lat_active else "-"
color = rl.WHITE
if lat_active:
if abs(angle_steers) > 180:
color = rl.RED
elif abs(angle_steers) > 90:
color = rl.Color(255, 188, 0, 255)
else:
color = rl.Color(0, 255, 0, 255)
return UiElement(value, "DESIRED STEER", self.unit, color)
class AEgoElement:
def __init__(self):
self.unit = "m/s^2"
+24 -3
View File
@@ -6,13 +6,17 @@ See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
from openpilot.selfdrive.ui.mici.onroad.torque_bar import TorqueBar
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.selfdrive.ui.onroad.hud_renderer import HudRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.road_name import RoadNameRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.rocket_fuel import RocketFuel
from openpilot.selfdrive.ui.sunnypilot.onroad.speed_limit import SpeedLimitRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.smart_cruise_control import SmartCruiseControlRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.turn_signal import TurnSignalController
from openpilot.selfdrive.ui.sunnypilot.onroad.circular_alerts import CircularAlertsRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.speed_renderer import SpeedRenderer
class HudRendererSP(HudRenderer):
@@ -22,20 +26,37 @@ class HudRendererSP(HudRenderer):
self.road_name_renderer = RoadNameRenderer()
self.rocket_fuel = RocketFuel()
self.speed_limit_renderer = SpeedLimitRenderer()
self.smart_cruise_control_renderer = SmartCruiseControlRenderer()
self.turn_signal_controller = TurnSignalController()
self.circular_alerts_renderer = CircularAlertsRenderer()
self.speed_renderer = SpeedRenderer()
self._torque_bar = TorqueBar(scale=3.0, always=True)
def _update_state(self) -> None:
super()._update_state()
self.road_name_renderer.update()
self.speed_limit_renderer.update()
self.smart_cruise_control_renderer.update()
self.turn_signal_controller.update()
self.circular_alerts_renderer.update()
self.speed_renderer.update()
def _draw_current_speed(self, rect: rl.Rectangle) -> None:
self.speed_renderer.render(rect)
def _render(self, rect: rl.Rectangle) -> None:
super()._render(rect)
if ui_state.torque_bar and ui_state.sm['controlsState'].lateralControlState.which() != 'angleState':
torque_rect = rect
if ui_state.developer_ui in (DeveloperUiRenderer.DEV_UI_BOTTOM, DeveloperUiRenderer.DEV_UI_BOTH):
torque_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height - DeveloperUiRenderer.BOTTOM_BAR_HEIGHT)
self._torque_bar.render(torque_rect)
self.developer_ui.render(rect)
self.road_name_renderer.render(rect)
self.speed_limit_renderer.render(rect)
self.smart_cruise_control_renderer.render(rect)
self.turn_signal_controller.render(rect)
if ui_state.rocket_fuel:
self.rocket_fuel.render(rect, ui_state.sm)
self.circular_alerts_renderer.render(rect)
self.rocket_fuel.render(rect, ui_state.sm)
+1 -1
View File
@@ -31,7 +31,7 @@ class RoadNameRenderer(Widget):
self.road_name = lmd.roadName
def _render(self, rect: rl.Rectangle):
if not self.road_name:
if not self.road_name or not ui_state.road_name_toggle:
return
text = self.road_name
@@ -6,12 +6,17 @@ See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
from openpilot.selfdrive.ui.ui_state import ui_state
class RocketFuel:
def __init__(self):
self.vc_accel = 0.0
def render(self, rect: rl.Rectangle, sm) -> None:
if not ui_state.rocket_fuel:
return
vc_accel0 = sm['carState'].aEgo
# Smooth the acceleration
@@ -0,0 +1,131 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
from openpilot.selfdrive.ui.onroad.hud_renderer import COLORS
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.widgets import Widget
class SmartCruiseControlRenderer(Widget):
def __init__(self):
super().__init__()
self.vision_enabled = False
self.vision_active = False
self.vision_frame = 0
self.map_enabled = False
self.map_active = False
self.map_frame = 0
self.long_override = False
self.font = gui_app.font(FontWeight.BOLD)
self.scc_tex = rl.load_render_texture(256, 128)
def update(self):
sm = ui_state.sm
if sm.updated["longitudinalPlanSP"]:
lp_sp = sm["longitudinalPlanSP"]
vision = lp_sp.smartCruiseControl.vision
map_ = lp_sp.smartCruiseControl.map
self.vision_enabled = vision.enabled
self.vision_active = vision.active
self.map_enabled = map_.enabled
self.map_active = map_.active
if sm.updated["carControl"]:
self.long_override = sm["carControl"].cruiseControl.override
if self.vision_active:
self.vision_frame += 1
else:
self.vision_frame = 0
if self.map_active:
self.map_frame += 1
else:
self.map_frame = 0
@staticmethod
def _pulse_element(frame):
return not (frame % gui_app.target_fps < (gui_app.target_fps / 2.5))
def _draw_icon(self, rect_center_x, rect_height, x_offset, y_offset, name):
text = name
font_size = 36
padding_v = 5
box_width = 160
sz = measure_text_cached(self.font, text, font_size)
box_height = int(sz.y + padding_v * 2)
texture_width = 256
texture_height = 128
rl.begin_texture_mode(self.scc_tex)
rl.clear_background(rl.Color(0, 0, 0, 0))
if self.long_override:
box_color = COLORS.OVERRIDE
else:
box_color = rl.Color(0, 255, 0, 255)
# Center box in texture
box_x = (texture_width - box_width) // 2
box_y = (texture_height - box_height) // 2
rl.draw_rectangle_rounded(rl.Rectangle(box_x, box_y, box_width, box_height), 0.2, 10, box_color)
# Draw text with custom blend mode to punch hole
rl.rl_set_blend_factors(rl.RL_ZERO, rl.RL_ONE_MINUS_SRC_ALPHA, 0x8006)
rl.rl_set_blend_mode(rl.BLEND_CUSTOM)
text_pos_x = box_x + (box_width - sz.x) / 2
text_pos_y = box_y + (box_height - sz.y) / 2
rl.draw_text_ex(self.font, text, rl.Vector2(text_pos_x, text_pos_y), font_size, 0, rl.WHITE)
rl.rl_set_blend_mode(rl.BLEND_ALPHA) # Reset
rl.end_texture_mode()
screen_y = rect_height / 4 + y_offset
dest_x = rect_center_x + x_offset - texture_width / 2
dest_y = screen_y - texture_height / 2
src_rect = rl.Rectangle(0, 0, texture_width, -texture_height)
dst_rect = rl.Rectangle(dest_x, dest_y, texture_width, texture_height)
rl.draw_texture_pro(self.scc_tex.texture, src_rect, dst_rect, rl.Vector2(0, 0), 0, rl.WHITE)
def _render(self, rect: rl.Rectangle):
x_offset = -260
y1_offset = -40
y2_offset = -100
orders = [y1_offset, y2_offset]
y_scc_v = 0
y_scc_m = 0
idx = 0
if self.vision_enabled:
y_scc_v = orders[idx]
idx += 1
if self.map_enabled:
y_scc_m = orders[idx]
idx += 1
scc_vision_pulse = self._pulse_element(self.vision_frame)
if (self.vision_enabled and not self.vision_active) or (self.vision_active and scc_vision_pulse):
self._draw_icon(rect.x + rect.width / 2, rect.height, x_offset, y_scc_v, "SCC-V")
scc_map_pulse = self._pulse_element(self.map_frame)
if (self.map_enabled and not self.map_active) or (self.map_active and scc_map_pulse):
self._draw_icon(rect.x + rect.width / 2, rect.height, x_offset, y_scc_m, "SCC-M")
@@ -0,0 +1,46 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
from openpilot.common.constants import CV
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.selfdrive.ui.onroad.hud_renderer import FONT_SIZES, COLORS
class SpeedRenderer:
def __init__(self):
self.speed: float = 0.0
self.v_ego_cluster_seen: bool = False
self._font_bold: rl.Font = gui_app.font(FontWeight.BOLD)
self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM)
def update(self) -> None:
car_state = ui_state.sm['carState']
v_ego_cluster = car_state.vEgoCluster
self.v_ego_cluster_seen = self.v_ego_cluster_seen or v_ego_cluster != 0.0
v_ego = v_ego_cluster if self.v_ego_cluster_seen and not ui_state.true_v_ego_ui else car_state.vEgo
speed_conversion = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH
self.speed = max(0.0, v_ego * speed_conversion)
def render(self, rect: rl.Rectangle) -> None:
if ui_state.hide_v_ego_ui:
return
# Draw current speed and unit
speed_text = str(round(self.speed))
speed_text_size = measure_text_cached(self._font_bold, speed_text, FONT_SIZES.current_speed)
speed_pos = rl.Vector2(rect.x + rect.width / 2 - speed_text_size.x / 2, 180 - speed_text_size.y / 2)
rl.draw_text_ex(self._font_bold, speed_text, speed_pos, FONT_SIZES.current_speed, 0, COLORS.WHITE)
unit_text = tr("km/h") if ui_state.is_metric else tr("mph")
unit_text_size = measure_text_cached(self._font_medium, unit_text, FONT_SIZES.speed_unit)
unit_pos = rl.Vector2(rect.x + rect.width / 2 - unit_text_size.x / 2, 290 - unit_text_size.y / 2)
rl.draw_text_ex(self._font_medium, unit_text, unit_pos, FONT_SIZES.speed_unit, 0, COLORS.WHITE_TRANSLUCENT)
@@ -137,6 +137,9 @@ class TurnSignalController:
self._right_signal.deactivate()
def render(self, rect: rl.Rectangle):
if not ui_state.turn_signals:
return
x = rect.x + rect.width / 2
left_x = x - self._config.left_x - self._config.size
+13 -8
View File
@@ -120,18 +120,23 @@ class UIStateSP:
CP_SP_bytes = self.params.get("CarParamsSPPersistent")
if CP_SP_bytes is not None:
self.CP_SP = messaging.log_from_bytes(CP_SP_bytes, custom.CarParamsSP)
self.sunnylink_enabled = self.params.get_bool("SunnylinkEnabled")
self.developer_ui = self.params.get("DevUIInfo")
self.rocket_fuel = self.params.get_bool("RocketFuel")
self.rainbow_path = self.params.get_bool("RainbowMode")
self.chevron_metrics = self.params.get("ChevronInfo")
self.active_bundle = self.params.get("ModelManager_ActiveBundle")
self.blindspot = self.params.get_bool("BlindSpot")
self.chevron_metrics = self.params.get("ChevronInfo")
self.custom_interactive_timeout = self.params.get("InteractivityTimeout", return_default=True)
self.speed_limit_mode = self.params.get("SpeedLimitMode", return_default=True)
# Onroad Screen Brightness
self.developer_ui = self.params.get("DevUIInfo")
self.hide_v_ego_ui = self.params.get_bool("HideVEgoUI")
self.onroad_brightness = int(float(self.params.get("OnroadScreenOffBrightness", return_default=True)))
self.onroad_brightness_timer_param = self.params.get("OnroadScreenOffTimer", return_default=True)
self.rainbow_path = self.params.get_bool("RainbowMode")
self.road_name_toggle = self.params.get_bool("RoadNameToggle")
self.rocket_fuel = self.params.get_bool("RocketFuel")
self.speed_limit_mode = self.params.get("SpeedLimitMode", return_default=True)
self.standstill_timer = self.params.get_bool("StandstillTimer")
self.sunnylink_enabled = self.params.get_bool("SunnylinkEnabled")
self.torque_bar = self.params.get_bool("TorqueBar")
self.true_v_ego_ui = self.params.get_bool("TrueVEgoUI")
self.turn_signals = self.params.get_bool("ShowTurnSignals")
class DeviceSP:
+1
View File
@@ -2,3 +2,4 @@ SConscript(['common/transformations/SConscript'])
SConscript(['modeld/SConscript'])
SConscript(['modeld_v2/SConscript'])
SConscript(['selfdrive/locationd/SConscript'])
SConscript(['modeld_sunny/SConscript'])
+32
View File
@@ -0,0 +1,32 @@
import os
import glob
Import('env', 'arch')
lenv = env.Clone()
tinygrad_repo = env.Dir("#tinygrad_repo")
tinygrad_files = ["#"+x for x in glob.glob(tinygrad_repo.relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x]
def mayo_compile(flags, model_name):
pythonpath_string = f'PYTHONPATH="${{PYTHONPATH}}:{tinygrad_repo.abspath}"'
onnx_fn = f"distilled_models/{model_name}.onnx"
pkl_fn = f"distilled_models/{model_name}_tinygrad.pkl"
if not os.path.exists("distilled_models"):
try:
os.makedirs("distilled_models")
except OSError:
pass
if os.path.isfile(File(onnx_fn).abspath):
return lenv.Command(
pkl_fn,
[onnx_fn, "compile_split_tinygrad.py"] + tinygrad_files,
f'{pythonpath_string} {flags} python3 {File("compile_split_tinygrad.py").abspath} {File(onnx_fn).abspath} {File(pkl_fn).abspath}'
)
flags = {
'larch64': 'DEV=QCOM',
'Darwin': f'DEV=METAL HOME={os.path.expanduser("~")} IMAGE=0',
}.get(arch, 'DEV=CPU CPU_LLVM=1 IMAGE=0')
for m in ["student_vision", "student_policy"]:
mayo_compile(flags, m)
View File
@@ -0,0 +1,109 @@
"""
The whole point of this module is to mimic compile3.py while adapting it for our buffers to prevent buffer explosion
"""
import os
import sys
import pickle
from tinygrad import Tensor, TinyJit, Context, Device
from tinygrad.device import Buffer
# from tinygrad.nn.onnx import OnnxRunner
from tinygrad.frontend.onnx import OnnxRunner
if "JIT_BATCH_SIZE" not in os.environ:
os.environ["JIT_BATCH_SIZE"] = "0"
if "FLOAT16" not in os.environ:
os.environ["FLOAT16"] = "1"
if "OPT" not in os.environ:
os.environ["OPT"] = "99"
KEEP_BUFFERS = set()
original_reduce = Buffer.__reduce__
def stripped_reduce(self):
if id(self) in KEEP_BUFFERS:
return original_reduce(self)
return (self.__class__, (self.device, self.size, self.dtype))
Buffer.__reduce__ = stripped_reduce
def compile_model(onnx_path, output_path, input_shapes=None, input_types=None):
print(f"Compiling {onnx_path} -> {output_path}")
run_onnx = OnnxRunner(onnx_path)
if input_shapes is None:
input_shapes = {name: spec.shape for name, spec in run_onnx.graph_inputs.items()}
if input_types is None:
input_types = {name: spec.dtype for name, spec in run_onnx.graph_inputs.items()}
Tensor.manual_seed(100)
inputs = {k: Tensor(Tensor.randn(*shp, dtype=input_types[k]).mul(8).realize().numpy(), device='NPY') for k, shp in sorted(input_shapes.items())}
inputs = {k: v.to(Device.DEFAULT).realize() for k, v in inputs.items()}
print(f"Realized all {len(inputs)} inputs on {Device.DEFAULT}")
input_buf_ids = set()
for _, v in inputs.items():
if hasattr(v, '_buffer'):
try:
b = v._buffer()
if b is not None:
input_buf_ids.add(id(b))
except Exception:
pass
if "vision" in onnx_path:
onnx_jit = TinyJit(lambda **kwargs: next(iter(run_onnx({k:v.to(Device.DEFAULT) for k,v in kwargs.items()}).values())).cast('float32'), prune=True)
else:
onnx_jit = TinyJit(lambda **kwargs: [x.cast('float32').contiguous().realize() for x in run_onnx({k:v.to(Device.DEFAULT) for k,v in kwargs.items()}).values()], prune=True)
for i in range(3):
with Context(DEBUG=max(int(os.getenv("DEBUG", 0)), 2 if i == 2 else 1)):
res = onnx_jit(**inputs)
if isinstance(res, list):
for x in res:
x.numpy()
else:
res.numpy()
print(f"Captured {len(onnx_jit.captured.jit_cache)} kernels")
all_read_ids = set()
all_written_ids = set()
for ji in onnx_jit.captured.jit_cache:
if len(ji.bufs) > 0:
if ji.bufs[0] is not None:
all_written_ids.add(id(ji.bufs[0]))
for b in ji.bufs[1:]:
if b is not None:
all_read_ids.add(id(b))
weight_candidates = all_read_ids - all_written_ids
weight_ids = weight_candidates - input_buf_ids
print(f"Identified {len(weight_ids)} weight candidates (Read-Only & Not Input).")
total_weight_size = 0
marked_count = 0
for ji in onnx_jit.captured.jit_cache:
for b in ji.bufs:
if b is not None and id(b) in weight_ids:
if id(b) not in KEEP_BUFFERS:
KEEP_BUFFERS.add(id(b))
total_weight_size += b.size * b.dtype.itemsize
marked_count += 1
print(f"Preserving {marked_count} unique weight buffers.")
print(f"Total Preserved Weight Data Size: {total_weight_size / 1e6:.2f} MB")
with open(output_path, "wb") as f:
pickle.dump(onnx_jit, f)
print(f"Saved {output_path}, pkl size: {os.path.getsize(output_path)/1e6:.2f} MB")
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python compile_split_tinygrad.py <input_onnx> <output_pkl>")
sys.exit(1)
input_onnx = sys.argv[1]
output_pkl = sys.argv[2]
compile_model(input_onnx, output_pkl)
+161
View File
@@ -0,0 +1,161 @@
import numpy as np
from openpilot.common.realtime import DT_MDL
from openpilot.selfdrive.modeld.constants import ModelConstants
from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, get_curvature_from_plan
def interp_vec(t_out, t_in, vec):
# vec shape (N, 3), output (M, 3)
return np.stack([np.interp(t_out, t_in, vec[:, i]) for i in range(3)], axis=1)
def fill_alpamayo_msg(modelV2, net_outputs, frame_id, frame_drop_ratio, timestamp_eof, CP, lat_delay, v_ego):
modelV2.frameId = frame_id
modelV2.frameIdExtra = frame_id
modelV2.timestampEof = timestamp_eof
modelV2.frameDropPerc = frame_drop_ratio * 100.0
modelV2.init('laneLines', 4)
modelV2.init('roadEdges', 2)
modelV2.init('laneLineProbs', 4)
modelV2.init('roadEdgeStds', 2)
for i in range(4):
l = modelV2.laneLines[i]
l.t = [0.0]
l.x = [0.0]
l.y = [0.0]
l.z = [0.0]
modelV2.laneLineProbs[i] = 0.0
for i in range(2):
e = modelV2.roadEdges[i]
e.t = [0.0]
e.x = [0.0]
e.y = [0.0]
e.z = [0.0]
modelV2.roadEdgeStds[i] = 1.0
leads = modelV2.init('leadsV3', 1)
lead = leads[0]
pred_lead = net_outputs['pred_lead'][0]
prob_logit = float(pred_lead[0])
dist_pred = float(pred_lead[1] * 100.0)
dist_sigma = float(np.exp(pred_lead[2]))
v_rel_pred = float(pred_lead[3])
v_sigma = float(np.exp(pred_lead[4]))
a_rel_pred = float(pred_lead[5])
a_sigma = float(np.exp(pred_lead[6]))
prob = float(1.0 / (1.0 + np.exp(-prob_logit)))
lead.prob = prob
lead.probTime = 0.0
# X(t) = X0 + V_rel*t + 0.5*A_rel*t^2
T = ModelConstants.T_IDXS
lead.t = list(T)
lead.x = [float(dist_pred + v_rel_pred * t + 0.5 * a_rel_pred * t**2) for t in T]
lead.v = [float(v_ego + v_rel_pred + a_rel_pred * t) for t in T]
a_ego = net_outputs['acceleration'][0, 0, 0] # T=0 ego accel estimate (x component)
lead.a = [float(a_ego + a_rel_pred)] * len(T)
lead.y = [0.0] * len(T)
lead.xStd = [max(0.5, dist_sigma * 100.0)] * len(T)
lead.yStd = [1.0] * len(T)
lead.vStd = [max(0.1, v_sigma)] * len(T)
lead.aStd = [max(0.1, a_sigma)] * len(T)
modelV2.meta.engagedProb = 1.0
desire_pred = [0.0] * 8
if 'pred_light' in net_outputs:
red_prob = float(1.0 / (1.0 + np.exp(-net_outputs['pred_light'][0, 1] + net_outputs['pred_light'][0, 0])))
desire_pred[4] = red_prob
modelV2.meta.desirePrediction = desire_pred
modelV2.meta.desireState = [0.0] * 8
reasoning_error = net_outputs.get('consistency_error', 0.0)
if reasoning_error < 0.5:
modelV2.confidence = "green"
elif reasoning_error < 1.5:
modelV2.confidence = "yellow"
else:
modelV2.confidence = "red"
ALPAMAYO_T_IDXS = np.arange(1, 65) * 0.1 # 64 steps at .1s intervals
t_idxs = ModelConstants.T_IDXS
t_all = np.concatenate(([0.0], ALPAMAYO_T_IDXS)) # this model starts at t=0.1 so if we prepend 0.0 and interpolate for t=now it should match op
pos_interp = interp_vec(t_idxs, t_all, np.vstack((np.zeros(3), net_outputs['position'][0])))
pos_std_interp = interp_vec(t_idxs, t_all, np.vstack((np.zeros(3), net_outputs.get('position_std', np.ones((64, 3)) * 0.1))))
vel_interp = interp_vec(t_idxs, t_all, np.vstack(([v_ego, 0.0, 0.0], net_outputs['velocity'][0])))
acc_interp = interp_vec(t_idxs, t_all, np.vstack((net_outputs['acceleration'][0][0], net_outputs['acceleration'][0])))
rot_interp = interp_vec(t_idxs, t_all, np.vstack((np.zeros(3), net_outputs['orientation'][0])))
rate_interp = interp_vec(t_idxs, t_all, np.vstack((net_outputs['orientation_rate'][0][0], net_outputs['orientation_rate'][0])))
# https://www.mathworks.com/help/vdynblks/ug/coordinate-systems-in-vehicle-dynamics-blockset.html
# following SAE J670 and ISO 8855, for sunnymayo model: x is forward (f), y is left (lat), z is up/vert
# Openpilot Modelv2 and camerad expects SAE J670: x is forward, y is right, z is down
modelV2.position.t = t_idxs # time, obviously
modelV2.position.x = pos_interp[:, 0].tolist() # f dist
modelV2.position.y = (-pos_interp[:, 1]).tolist() # lat offset (Flip L->R)
modelV2.position.z = (-pos_interp[:, 2]).tolist() # vert offset (Flip U->D) (elevation)
modelV2.position.xStd = pos_std_interp[:, 0].tolist()
modelV2.position.yStd = pos_std_interp[:, 1].tolist()
modelV2.position.zStd = pos_std_interp[:, 2].tolist()
modelV2.velocity.t = t_idxs
modelV2.velocity.x = vel_interp[:, 0].tolist() # f vel (vego)
modelV2.velocity.y = (-vel_interp[:, 1]).tolist() # lat vel (curvature)
modelV2.velocity.z = (-vel_interp[:, 2]).tolist() # vert vel
modelV2.acceleration.t = t_idxs
modelV2.acceleration.x = acc_interp[:, 0].tolist() # f accel (aego)
modelV2.acceleration.y = (-acc_interp[:, 1]).tolist() # lat accel
modelV2.acceleration.z = (-acc_interp[:, 2]).tolist() # vert accel
modelV2.orientation.t = t_idxs
modelV2.orientation.x = rot_interp[:, 0].tolist() # roll (treated as 0)
modelV2.orientation.y = (-rot_interp[:, 1]).tolist() # pitch (from z-slope)
modelV2.orientation.z = (-rot_interp[:, 2]).tolist() # yaw (heading)
modelV2.orientationRate.t = t_idxs
modelV2.orientationRate.x = rate_interp[:, 0].tolist() # roll rate
modelV2.orientationRate.y = (-rate_interp[:, 1]).tolist() # pitch rate (Flip U->D)
modelV2.orientationRate.z = (-rate_interp[:, 2]).tolist() # yaw rate (Flip L->R)
long_action_t = CP.longitudinalActuatorDelay + DT_MDL
desired_accel, should_stop = get_accel_from_plan(vel_interp[:, 0], acc_interp[:, 0], t_idxs, action_t=long_action_t)
modelV2.action.desiredAcceleration = float(desired_accel)
modelV2.action.shouldStop = bool(should_stop)
lat_action_t = lat_delay + DT_MDL
desired_curvature = get_curvature_from_plan(-rot_interp[:, 2], -rate_interp[:, 2], t_idxs, vego=v_ego, action_t=lat_action_t)
modelV2.action.desiredCurvature = float(desired_curvature)
def fill_pose_msg(camera_odometry, net_outputs, frame_id, timestamp_eof):
camera_odometry.frameId = frame_id
camera_odometry.timestampEof = timestamp_eof
trans = net_outputs['velocity'][0, 0].copy()
trans[1] *= -1.0
trans[2] *= -1.0
camera_odometry.trans = trans.tolist()
std_val = float(max(0.01, net_outputs.get('consistency_error', 0.1)))
camera_odometry.transStd = [std_val, std_val, std_val]
rot = net_outputs['orientation_rate'][0, 0].copy()
rot[1] *= -1.0
rot[2] *= -1.0
camera_odometry.rot = rot.tolist()
rot_std = std_val * 0.1
camera_odometry.rotStd = [rot_std, rot_std, rot_std]
@@ -0,0 +1,51 @@
import numpy as np
from dataclasses import dataclass
from openpilot.common.params import Params
@dataclass
class AlpamayoDesire:
DRIVE_SAFELY = 0
TURN_LEFT = 2
TURN_RIGHT = 1
DRIVE_FAST = 3
STOP = 4
class InputIDHelper:
def __init__(self):
self.current_ids = np.zeros((1, 16), dtype=np.int64)
self.desire = AlpamayoDesire.DRIVE_SAFELY
self.params = Params()
self.drive_fast = False
self.msg_count = -1
def update_params(self):
if self.msg_count % 60 == 0:
self.drive_fast = self.params.get_bool("AlpamayoDriveFast")
self.msg_count += 1
def update(self, sm):
self.update_params()
if sm is None:
return self.current_ids
left_blinker = False
right_blinker = False
if sm.seen['carState']:
left_blinker = sm['carState'].leftBlinker
right_blinker = sm['carState'].rightBlinker
# Priority: STOP (TODO) > Turn > Drive Fast > Drive Safely
new_desire = AlpamayoDesire.DRIVE_SAFELY
if left_blinker:
new_desire = AlpamayoDesire.TURN_LEFT
elif right_blinker:
new_desire = AlpamayoDesire.TURN_RIGHT
elif self.drive_fast:
new_desire = AlpamayoDesire.DRIVE_FAST
self.desire = new_desire
self.current_ids.fill(self.desire)
return self.current_ids
@@ -0,0 +1,60 @@
from tinygrad.tensor import Tensor
def action_to_traj(action: Tensor, v0: Tensor, dt: float = 0.1):
"""
This function is a lightweight tinygrad transformation of the unicycle accel physics model based on Nvidia's
unicycle model https://github.com/NVlabs/alpamayo/blob/main/src/alpamayo_r1/action_space/unicycle_accel_curvature.py
Integrate action (accel, kappa) to trajectory (x, y, theta)
Args:
action: (B, T, 2) [accel, kappa]
v0: (B,) Initial velocity
dt: Time step
Returns:
res: Dict containing position, velocity, acceleration, orientation, orientation_rate
"""
B, T, _ = action.shape
ACCEL_MEAN = 0.02902695
ACCEL_STD = 0.68104267
CURV_MEAN = 0.00026922
CURV_STD = 0.02614828
accel = action[..., 0] * ACCEL_STD + ACCEL_MEAN
kappa = action[..., 1] * CURV_STD + CURV_MEAN
# v_{t+1} = v_t + a_t * dt
v_diff = accel * dt
v_seq = v_diff.cumsum(axis=1) + v0.reshape(B, 1) # cumulative sum over T dimension (axis 1)
velocity = v0.reshape(B, 1).cat(v_seq, dim=1)
# theta_{t+1} = theta_t + kappa_t * (v_t * dt + 0.5 * a_t * dt^2)
dt_2_term = 0.5 * (dt**2)
dtheta = kappa * (velocity[:, :-1] * dt + accel * dt_2_term)
theta_seq = dtheta.cumsum(axis=1)
theta = Tensor.zeros(B, 1, device=action.device, dtype=action.dtype).cat(theta_seq, dim=1)
# trapezoidal euler
half_dt = 0.5 * dt
v_cos = velocity * theta.cos()
v_sin = velocity * theta.sin()
dx = (v_cos[:, :-1] + v_cos[:, 1:]) * half_dt
dy = (v_sin[:, :-1] + v_sin[:, 1:]) * half_dt
x = dx.cumsum(axis=1)
y = dy.cumsum(axis=1)
res = {}
res['action'] = accel.stack(kappa, dim=-1) # raw model output
# (x, y, 0)
res['position'] = x.stack(y, Tensor.zeros(B, T, device=action.device, dtype=action.dtype), dim=-1)
# (vx, vy, 0)
res['velocity'] = v_cos[:, 1:].stack(v_sin[:, 1:], Tensor.zeros(B, T, device=action.device, dtype=action.dtype), dim=-1)
# ax = accel * cos(theta), ay = accel * sin(theta), 0
res['acceleration'] = (accel * theta[:, 1:].cos()).stack(accel * theta[:, 1:].sin(), Tensor.zeros(B, T, device=action.device, dtype=action.dtype), dim=-1)
# (0, 0, theta)
res['orientation'] = Tensor.zeros(B, T, device=action.device, dtype=action.dtype).stack(Tensor.zeros(B, T, device=action.device, dtype=action.dtype), theta[:, 1:], dim=-1)
# (0, 0, dtheta/dt)
res['orientation_rate'] = Tensor.zeros(B, T, device=action.device, dtype=action.dtype).stack(Tensor.zeros(B, T, device=action.device, dtype=action.dtype), dtheta / dt, dim=-1)
return res
+19
View File
@@ -0,0 +1,19 @@
import pickle
from pathlib import Path
from openpilot.common.swaglog import cloudlog
def load_compiled_model(model_name: str = "student"):
pkl_path = Path(__file__).parent / "distilled_models" / f"{model_name}_tinygrad.pkl"
if not pkl_path.exists():
cloudlog.error(f"Compiled model not found at {pkl_path}")
return None
try:
with open(pkl_path, "rb") as f:
model_run = pickle.load(f)
return model_run
except Exception as e:
cloudlog.error(f"Failed to load compiled Tinygrad model: {e}")
return None
+344
View File
@@ -0,0 +1,344 @@
import time
import numpy as np
import os
import platform
from setproctitle import setproctitle
from openpilot.system.hardware import TICI
if TICI:
os.environ['DEV'] = 'QCOM'
elif platform.system() == "Darwin":
os.environ['DEV'] = "METAL"
else:
os.environ['DEV'] = 'CPU'
USBGPU = "USBGPU" in os.environ
if USBGPU:
os.environ['DEV'] = 'AMD'
os.environ['AMD_IFACE'] = 'USB'
from tinygrad.tensor import Tensor
from tinygrad.dtype import dtypes
import cereal.messaging as messaging
from cereal import car
from cereal.messaging import PubMaster, SubMaster
from msgq.visionipc import VisionIpcClient, VisionStreamType
from openpilot.common.swaglog import cloudlog
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, DT_MDL
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.transformations.model import bigmodel_frame_from_calib_frame
from openpilot.common.transformations.camera import DEVICE_CAMERAS, view_frame_from_device_frame
from openpilot.common.transformations.orientation import rot_from_euler
from openpilot.common.realtime import Ratekeeper
from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
from openpilot.sunnypilot.modeld_v2.models.commonmodel_pyx import DrivingModelFrame, CLContext
from openpilot.sunnypilot.modeld_sunny.kinematic_model import action_to_traj
from openpilot.sunnypilot.modeld_v2.camera_offset_helper import CameraOffsetHelper
from openpilot.sunnypilot.modeld_sunny.loader import load_compiled_model
from openpilot.sunnypilot.modeld_sunny.input_id_helper import InputIDHelper
from openpilot.sunnypilot.modeld_sunny.fill_model_msg import fill_alpamayo_msg, fill_pose_msg
PROCESS_NAME = "selfdrive.modeld.openpilot.sunnypilot.modeld_sunny"
class FrameMeta:
frame_id: int = 0
timestamp_sof: int = 0
timestamp_eof: int = 0
def __init__(self, vipc=None):
if vipc is not None:
self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof
def safe_exp(x):
return np.exp(np.clip(x, -np.inf, 11))
def softmax(x, axis=-1):
x = x - np.max(x, axis=axis, keepdims=True)
x = safe_exp(x)
return x / np.sum(x, axis=axis, keepdims=True)
class AlpamayoModelD:
def __init__(self, context: CLContext):
self.params = Params()
self.context = context
self.model_vision = load_compiled_model("student_vision")
self.model_policy = load_compiled_model("student_policy")
self.model_loaded = self.model_vision is not None and self.model_policy is not None
self.vision_input_names = ['road', 'wide']
self.vision_input_shapes = {
'road': (1, 3, 512, 1024),
'wide': (1, 3, 512, 1024)
}
self.frames = {name: DrivingModelFrame(context, 1024, 512, buffer_length=4) for name in self.vision_input_names}
self.history_buffer = np.zeros((16, 3), dtype=np.float32)
self.logic_pulse = np.zeros((1, 2048), dtype=np.float32)
def run(self, bufs, transforms, inputs, prepare_only):
if prepare_only:
return None
if not hasattr(self, 'vision_inputs'):
self.vision_inputs = {}
imgs_cl = {n: self.frames[n].prepare(bufs[n], transforms[n].flatten()) for n in self.vision_input_names if bufs.get(n)}
if TICI and not USBGPU:
for k, v in imgs_cl.items():
if k not in self.vision_inputs:
self.vision_inputs[k] = qcom_tensor_from_opencl_address(v.mem_address, self.vision_input_shapes[k], dtype=dtypes.uint8)
else:
for k, v in imgs_cl.items():
self.vision_inputs[k] = Tensor(self.frames[k].buffer_from_cl(v).reshape(self.vision_input_shapes[k]), dtype=dtypes.uint8).realize()
img_t = Tensor.stack([self.vision_inputs['wide'].cast(dtypes.float32) / 255.0,
self.vision_inputs['road'].cast(dtypes.float32) / 255.0], dim=1).unsqueeze(0)
vis_res = self.model_vision(
history=Tensor(inputs["history"]).contiguous().realize(),
img=img_t.contiguous().realize(),
input_ids=Tensor(inputs["input_ids"]).contiguous().realize(),
logic_pulse=Tensor(inputs["logic_pulse"]).contiguous().realize()
)
context = vis_res.contiguous().realize()
x_input = Tensor.zeros(1, 64, 2, device=os.environ.get("DEV"), dtype=dtypes.float32)
v_mu, v_std, pred_pulse, state_mu, state_std, pred_light, pred_lead, hypot_logits = self.model_policy(
context=context,
noisy_action=x_input.contiguous().realize(),
t=Tensor(np.array([[0.0]], dtype=np.float32)).contiguous().realize(), # t=0
traffic=Tensor(inputs["traffic_convention"]).contiguous().realize()
)
weights = softmax(hypot_logits.numpy(), axis=1) # (B, M)
winner_idx = np.argmax(weights[0])
v_winner = v_mu[:, winner_idx]
state_winner = state_mu[:, winner_idx]
state_std_winner = state_std[:, winner_idx]
outputs_tg = action_to_traj(v_winner, Tensor([inputs["v_ego"]], dtype=dtypes.float32), dt=0.1)
outputs = {k: v.numpy() for k, v in outputs_tg.items()}
outputs.update({
"pred_pulse": pred_pulse.numpy(),
"pred_light": pred_light[0:1].numpy(),
"pred_lead": pred_lead[0:1].numpy(),
"weights": weights[0]
})
# Inject world positions for Z/Pitch
pos_world = state_winner[0].numpy()
pos_std = np.exp(state_std_winner[0].numpy())
outputs["position"][0, :, 2] = pos_world[:, 2]
outputs["position_std"] = pos_std # log_sigma -> sigma
d_pos = np.diff(outputs["position"][0], axis=0, prepend=np.zeros((1, 3)))
d_dist = np.maximum(np.linalg.norm(d_pos[:, :2], axis=1), 1e-4)
pitch = np.arctan2(np.diff(pos_world[:, 2], prepend=0.0), d_dist)
outputs["orientation"][0, :, 1] = pitch
outputs["orientation_rate"][0, :, 1] = np.diff(pitch, prepend=0.0) / 0.1
outputs["velocity"][0, :, 2] = np.linalg.norm(outputs["velocity"][0, :, :2], axis=1) * np.tan(pitch)
outputs["consistency_error"] = float(np.mean(np.linalg.norm(outputs["position"][0] - pos_world, axis=1)))
return outputs
def main():
setproctitle(PROCESS_NAME)
config_realtime_process(7, 54)
# Loop runs at 20Hz to match camera acquisition.
# Model inference runs at 10Hz via frame skipping.
rk = Ratekeeper(1.0 / DT_MDL)
cl_context = CLContext()
modeld = AlpamayoModelD(cl_context)
# Load CarParams
cloudlog.warning("Modeld: Waiting for CarParams...")
CP = messaging.log_from_bytes(Params().get("CarParams", block=True), car.CarParams)
cloudlog.warning("Modeld: Got CarParams")
camera_offset_helper = CameraOffsetHelper()
input_id_helper = InputIDHelper()
if modeld.model_loaded:
cloudlog.warning("Modeld: Successfully loaded compiled student model.")
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "liveDelay", "livePose", "driverMonitoringState"])
# VisionIPC Clients
while True:
available_streams = VisionIpcClient.available_streams("camerad", block=False)
if available_streams:
use_extra_client = VisionStreamType.VISION_STREAM_WIDE_ROAD in available_streams and VisionStreamType.VISION_STREAM_ROAD in available_streams
main_wide_camera = VisionStreamType.VISION_STREAM_ROAD not in available_streams
break
time.sleep(.1)
vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_ROAD
vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True, cl_context)
vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False, cl_context)
cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}")
while not vipc_client_main.connect(False):
time.sleep(0.1)
while use_extra_client and not vipc_client_extra.connect(False):
time.sleep(0.1)
cloudlog.warning(f"connected main cam with buffer size: {vipc_client_main.buffer_len} ({vipc_client_main.width} x {vipc_client_main.height})")
if use_extra_client:
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
model_transform_main = np.zeros((3, 3), dtype=np.float32)
model_transform_extra = np.zeros((3, 3), dtype=np.float32)
buf_main, buf_extra = None, None
meta_main = FrameMeta()
meta_extra = FrameMeta()
# filter to track dropped frames
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / 20.0)
last_vipc_frame_id = 0
run_count = 0
lat_delay = 0.0
live_calib_seen = False
while True:
# Keep receiving frames until we are at least 1 frame ahead of previous extra frame
while meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000:
buf_main = vipc_client_main.recv()
meta_main = FrameMeta(vipc_client_main)
if buf_main is None:
break
if buf_main is None:
cloudlog.debug("vipc_client_main no frame")
continue
if use_extra_client:
# Keep receiving extra frames until frame id matches main camera
while True:
buf_extra = vipc_client_extra.recv()
meta_extra = FrameMeta(vipc_client_extra)
if buf_extra is None or meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000:
break
if buf_extra is None:
cloudlog.debug("vipc_client_extra no frame")
continue
if abs(meta_main.timestamp_sof - meta_extra.timestamp_sof) > 10000000:
cloudlog.error(f"frames out of sync! main: {meta_main.frame_id} ({meta_main.timestamp_sof / 1e9:.5f}),\
extra: {meta_extra.frame_id} ({meta_extra.timestamp_sof / 1e9:.5f})")
else:
# Use single camera
buf_extra = buf_main
meta_extra = meta_main
# 10Hz Execution Check (Skip odd frames)
# We use main camera frameId as the clock
if meta_main.frame_id % 2 != 0:
last_vipc_frame_id = meta_main.frame_id
continue
sm.update(0)
v_ego = sm['carState'].vEgo if sm.seen['carState'] else 0.0
yaw_rate = 0.0
if sm.seen['livePose'] and sm['livePose'].angularVelocityDevice.valid:
yaw_rate = sm['livePose'].angularVelocityDevice.z
if sm.frame % 60 == 0:
lat_delay = get_lat_delay(modeld.params, sm["liveDelay"].lateralDelay)
camera_offset_helper.set_offset(modeld.params.get("CameraOffset", return_default=True))
if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']:
device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32)
dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))]
calib_from_bigmodel = np.linalg.inv(bigmodel_frame_from_calib_frame[:, :3])
device_from_calib = rot_from_euler(device_from_calib_euler)
camera_from_calib_main = (dc.ecam.intrinsics if main_wide_camera else dc.fcam.intrinsics) @ view_frame_from_device_frame @ device_from_calib
model_transform_main = camera_from_calib_main @ calib_from_bigmodel
camera_from_calib_extra = dc.ecam.intrinsics @ view_frame_from_device_frame @ device_from_calib
model_transform_extra = camera_from_calib_extra @ calib_from_bigmodel
model_transform_main, model_transform_extra = camera_offset_helper.update(model_transform_main, model_transform_extra, sm, main_wide_camera)
live_calib_seen = True
# Track dropped frames
vipc_dropped_frames = max(0, meta_main.frame_id - last_vipc_frame_id - 1)
frames_dropped = frame_dropped_filter.update(min(vipc_dropped_frames, 10))
if run_count < 10: # let frame drops warm up
frame_dropped_filter.x = 0.
frames_dropped = 0.
run_count = run_count + 1
frame_drop_ratio = frames_dropped / (1 + frames_dropped)
prepare_only = vipc_dropped_frames > 0
if prepare_only:
cloudlog.error(f"skipping model eval. Dropped {vipc_dropped_frames} frames")
bufs = {'road': buf_main, 'wide': buf_extra}
transforms = {'road': model_transform_main, 'wide': model_transform_extra}
dt = 0.1
d_yaw = yaw_rate * dt
d_pos = v_ego * dt * np.array([np.cos(d_yaw/2), np.sin(d_yaw/2)])
rot = np.array([[np.cos(-d_yaw), -np.sin(-d_yaw)], [np.sin(-d_yaw), np.cos(-d_yaw)]])
modeld.history_buffer[:, :2] = (modeld.history_buffer[:, :2] - d_pos) @ rot.T
modeld.history_buffer[:, 2] -= d_yaw
modeld.history_buffer = np.roll(modeld.history_buffer, -1, axis=0)
modeld.history_buffer[-1] = 0.
hist_input = modeld.history_buffer.copy()
hist_input[:, 1] *= -1.0
hist_input[:, 2] *= -1.0
yaws_fixed = hist_input[:, 2]
cos_y, sin_y = np.cos(yaws_fixed), np.sin(yaws_fixed)
zeros, ones = np.zeros_like(cos_y), np.ones_like(cos_y)
rot_flat = np.column_stack([cos_y, -sin_y, zeros, sin_y, cos_y, zeros, zeros, zeros, ones])
inputs = {
'input_ids': input_id_helper.update(sm),
'history': np.column_stack([hist_input[:, :2], zeros, rot_flat])[None, ...].astype(np.float32),
'logic_pulse': modeld.logic_pulse,
'traffic_convention': np.array([[0.0, 1.0]] if sm["driverMonitoringState"].isRHD else [[1.0, 0.0]], dtype=np.float32),
'v_ego': v_ego
}
t0 = time.monotonic()
outputs = modeld.run(bufs, transforms, inputs, prepare_only)
t1 = time.monotonic()
if not prepare_only:
cloudlog.warning(f"Modeld: Inference took {(t1-t0)*1000:.2f} ms")
last_vipc_frame_id = meta_main.frame_id
if outputs is not None:
modeld.logic_pulse[:] = outputs["pred_pulse"]
model_msg = messaging.new_message('modelV2')
drivingdata_msg = messaging.new_message('drivingModelData')
posenet_msg = messaging.new_message('cameraOdometry')
fill_alpamayo_msg(model_msg.modelV2, outputs, meta_main.frame_id, frame_drop_ratio, meta_main.timestamp_eof, CP, lat_delay, v_ego)
model_msg.valid = live_calib_seen and (vipc_dropped_frames < 1)
fill_pose_msg(posenet_msg.cameraOdometry, outputs, meta_main.frame_id, meta_main.timestamp_eof)
posenet_msg.valid = live_calib_seen and (vipc_dropped_frames < 1)
drivingdata_msg.drivingModelData.frameId = meta_main.frame_id
pm.send('drivingModelData', drivingdata_msg)
pm.send('cameraOdometry', posenet_msg)
pm.send('modelV2', model_msg)
rk.keep_time()
if __name__ == "__main__":
main()
@@ -0,0 +1,83 @@
import pytest
import numpy as np
from cereal import car
import cereal.messaging as messaging
from msgq.visionipc import VisionIpcServer, VisionIpcClient, VisionStreamType
from sunnypilot.modeld_sunny.modeld import AlpamayoModelD
from sunnypilot.modeld_v2.models.commonmodel_pyx import CLContext
from sunnypilot.modeld_sunny.fill_model_msg import fill_alpamayo_msg
@pytest.fixture(scope="module")
def cl_context():
return CLContext()
@pytest.fixture(scope="module")
def modeld(cl_context):
print("Initializing AlpamayoModelD...")
return AlpamayoModelD(cl_context)
@pytest.fixture(scope="function")
def vipc_server():
server_name = "camerad_test"
server = VisionIpcServer(server_name)
server.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 1, 1024, 512)
server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 1, 1024, 512)
server.start_listener()
yield server
def test_modeld(cl_context, modeld, vipc_server):
v_ego = 20.0
inputs = {
'input_ids': np.zeros((1, 16), dtype=np.int64),
'history': np.zeros((1, 16, 12), dtype=np.float32),
'logic_pulse': modeld.logic_pulse,
'traffic_convention': np.array([[1.0, 0.0]], dtype=np.float32),
'v_ego': v_ego
}
server_name = "camerad_test"
client_road = VisionIpcClient(server_name, VisionStreamType.VISION_STREAM_ROAD, False, cl_context)
assert client_road.connect(True), "Road client failed to connect"
client_wide = VisionIpcClient(server_name, VisionStreamType.VISION_STREAM_WIDE_ROAD, False, cl_context)
assert client_wide.connect(True), "Wide client failed to connect"
# NV12 size for 1024x512 = 1024*512 * 1.5 = 786432
yuv_data = b'\x00' * 786432
vipc_server.send(VisionStreamType.VISION_STREAM_ROAD, yuv_data)
vipc_server.send(VisionStreamType.VISION_STREAM_WIDE_ROAD, yuv_data)
buf_road = client_road.recv()
buf_wide = client_wide.recv()
assert buf_road is not None
assert buf_wide is not None
bufs = {'road': buf_road, 'wide': buf_wide}
transforms = {'road': np.eye(3, dtype=np.float32), 'wide': np.eye(3, dtype=np.float32)}
outputs = modeld.run(bufs, transforms, inputs, False)
assert outputs is not None
assert outputs["position"].shape == (1, 64, 3)
assert outputs["velocity"].shape == (1, 64, 3)
assert outputs["acceleration"].shape == (1, 64, 3)
assert outputs["orientation"].shape == (1, 64, 3)
assert "pred_pulse" in outputs
assert "pred_light" in outputs
assert "pred_lead" in outputs
assert np.all(np.isfinite(outputs["position"])), "Position contains NaN/Inf"
assert np.all(np.isfinite(outputs["velocity"])), "Velocity contains NaN/Inf"
assert "consistency_error" in outputs
assert outputs["consistency_error"] >= 0.0
model = messaging.new_message('modelV2')
CP = car.CarParams.new_message()
CP.longitudinalActuatorDelay = 0.2
fill_alpamayo_msg(model.modelV2, outputs, 12345, 0.0, 1e9, CP, 0.1, v_ego)
# these just ensure that the model should outputs same action for the same black pixels
assert model.modelV2.action.desiredAcceleration == pytest.approx(-6.75, abs=1e-2)
assert model.modelV2.action.desiredCurvature == pytest.approx(-0.05, abs=1e-2)
assert not model.modelV2.action.shouldStop
assert model.modelV2.frameId == 12345
+1 -1
View File
@@ -66,7 +66,7 @@ class ModelState(ModelStateBase):
self.PLANPLUS_CONTROL: float = 1.0
buffer_length = 5 if self.model_runner.is_20hz else 2
self.frames = {name: DrivingModelFrame(context, buffer_length) for name in self.model_runner.vision_input_names}
self.frames = {name: DrivingModelFrame(context, 512, 256, buffer_length) for name in self.model_runner.vision_input_names}
self.prev_desire = np.zeros(self.constants.DESIRE_LEN, dtype=np.float32)
# img buffers are managed in openCL transform code
+5 -1
View File
@@ -5,7 +5,11 @@
#include "common/clutil.h"
DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context, uint8_t buffer_length) : ModelFrame(device_id, context), buffer_length(buffer_length) {
DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context, int width, int height, uint8_t buffer_length) : ModelFrame(device_id, context), MODEL_WIDTH(width), MODEL_HEIGHT(height), buffer_length(buffer_length) {
MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2;
buf_size = MODEL_FRAME_SIZE * 2;
frame_size_bytes = MODEL_FRAME_SIZE * sizeof(uint8_t);
input_frames = std::make_unique<uint8_t[]>(buf_size);
input_frames_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err));
img_buffer_20hz_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buffer_length*frame_size_bytes, NULL, &err));
+6 -6
View File
@@ -64,15 +64,15 @@ protected:
class DrivingModelFrame : public ModelFrame {
public:
DrivingModelFrame(cl_device_id device_id, cl_context context, uint8_t buffer_length);
DrivingModelFrame(cl_device_id device_id, cl_context context, int width, int height, uint8_t buffer_length);
~DrivingModelFrame();
cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection);
const int MODEL_WIDTH = 512;
const int MODEL_HEIGHT = 256;
const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2;
const int buf_size = MODEL_FRAME_SIZE * 2;
const size_t frame_size_bytes = MODEL_FRAME_SIZE * sizeof(uint8_t);
int MODEL_WIDTH;
int MODEL_HEIGHT;
int MODEL_FRAME_SIZE;
int buf_size;
size_t frame_size_bytes;
const uint8_t buffer_length;
private:
+1 -1
View File
@@ -20,7 +20,7 @@ cdef extern from "sunnypilot/modeld_v2/models/commonmodel.h":
cppclass DrivingModelFrame:
int buf_size
DrivingModelFrame(cl_device_id, cl_context, unsigned char)
DrivingModelFrame(cl_device_id, cl_context, int, int, unsigned char)
cppclass MonitoringModelFrame:
int buf_size
@@ -59,8 +59,8 @@ cdef class ModelFrame:
cdef class DrivingModelFrame(ModelFrame):
cdef cppDrivingModelFrame * _frame
def __cinit__(self, CLContext context, int buffer_length=2):
self._frame = new cppDrivingModelFrame(context.device_id, context.context, buffer_length)
def __cinit__(self, CLContext context, int width, int height, int buffer_length=2):
self._frame = new cppDrivingModelFrame(context.device_id, context.context, width, height, buffer_length)
self.frame = <cppModelFrame*>(self._frame)
self.buf_size = self._frame.buf_size
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e095cfc4de71788bd4a99699a2e7ab4098cd426277d672b9e43981c5fab8b40f
size 16407
+22 -18
View File
@@ -90,8 +90,8 @@
"description": ""
},
"BlindSpot": {
"title": "Blind Spot Detection",
"description": ""
"title": "[TIZI/TICI only] Blind Spot Detection",
"description": "Enabling this will display warnings when a vehicle is detected in your blind spot as long as your car has BSM supported."
},
"BlinkerMinLateralControlSpeed": {
"title": "Blinker Min Lateral Control Speed",
@@ -339,8 +339,8 @@
"description": ""
},
"GreenLightAlert": {
"title": "Green Light Alert",
"description": ""
"title": "Green Traffic Light Alert (Beta)",
"description": "A chime and on-screen alert (TIZI/TICI only) will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. <br>Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly."
},
"GsmApn": {
"title": "GSM APN",
@@ -367,8 +367,8 @@
"description": ""
},
"HideVEgoUI": {
"title": "Hide vEgo UI",
"description": ""
"title": "[TIZI/TICI only] Speedometer: Hide from Onroad Screen",
"description": "When enabled, the speedometer on the onroad screen is not displayed."
},
"HyundaiLongitudinalTuning": {
"title": "Hyundai Longitudinal Tuning",
@@ -585,8 +585,8 @@
"description": ""
},
"LeadDepartAlert": {
"title": "Lead Depart Alert",
"description": ""
"title": "Lead Departure Alert (Beta)",
"description": "A chime and on-screen alert (TIZI/TICI only) will play when you are stopped, and the vehicle in front of you start moving. <br>Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly."
},
"LiveDelay": {
"title": "Live Delay",
@@ -1079,12 +1079,12 @@
"description": ""
},
"RoadNameToggle": {
"title": "Display Road Name",
"description": ""
"title": "[TIZI/TICI only] Display Road Name",
"description": "Displays the name of the road the car is traveling on. <br>The OpenStreetMap database of the location must be downloaded to fetch the road name."
},
"RocketFuel": {
"title": "Display Rocket Fuel Bar",
"description": "Show an indicator on the left side of the screen to display real-time vehicle acceleration and deceleration."
"title": "[TIZI/TICI only] Real-time Acceleration Bar",
"description": "Show an indicator on the left side of the screen to display real-time vehicle acceleration and deceleration. This displays what the car is currently doing, not what the planner is requesting."
},
"RouteCount": {
"title": "Route Count",
@@ -1103,8 +1103,8 @@
"description": ""
},
"ShowTurnSignals": {
"title": "Show Turn Signals",
"description": ""
"title": "[TIZI/TICI only] Display Turn Signals",
"description": "When enabled, visual turn indicators are drawn on the HUD."
},
"SmartCruiseControlMap": {
"title": "Smart Cruise Control - Map",
@@ -1196,8 +1196,8 @@
"description": ""
},
"StandstillTimer": {
"title": "Standstill Timer",
"description": ""
"title": "[TIZI/TICI only] Standstill Timer",
"description": "Show a timer on the HUD when the car is at a standstill."
},
"SubaruStopAndGo": {
"title": "Subaru Stop and Go",
@@ -1239,6 +1239,10 @@
"title": "Tesla Coop Steering",
"description": ""
},
"TorqueBar": {
"title": "[TIZI/TICI only] Steering Arc",
"description": "Display steering arc on the driving screen when lateral control is enabled."
},
"TorqueParamsOverrideEnabled": {
"title": "Manual Real-Time Tuning",
"description": ""
@@ -1267,8 +1271,8 @@
"description": ""
},
"TrueVEgoUI": {
"title": "True vEgo UI",
"description": ""
"title": "[TIZI/TICI only] Speedometer: Always Display True Speed",
"description": "For applicable vehicles, always display the true vehicle current speed from wheel speed sensors."
},
"UbloxAvailable": {
"title": "Ublox Available",
+9 -1
View File
@@ -20,6 +20,7 @@ from openpilot.system.athena.registration import register, UNREGISTERED_DONGLE_I
from openpilot.common.swaglog import cloudlog, add_file_handler
from openpilot.system.version import get_build_metadata
from openpilot.system.hardware.hw import Paths
from openpilot.system.hardware import PC
def manager_init() -> None:
@@ -39,6 +40,12 @@ def manager_init() -> None:
if params.get("DeviceBootMode") == 1: # start in Always Offroad mode
params.put_bool("OffroadMode", True)
# quick boot
if params.get_bool("QuickBootToggle") and not PC:
prebuilt_path = "/data/openpilot/prebuilt"
if not os.path.exists(prebuilt_path):
open(prebuilt_path, 'x').close()
if params.get_bool("RecordFrontLock"):
params.put_bool("RecordFront", True)
@@ -215,7 +222,8 @@ def main() -> None:
if __name__ == "__main__":
unblock_stdout()
if sys.platform != 'darwin':
unblock_stdout()
try:
main()
+8 -3
View File
@@ -80,17 +80,21 @@ def use_sunnylink_uploader_shim(started, params, CP: car.CarParams) -> bool:
"""Shim for use_sunnylink_uploader to match the process manager signature."""
return use_sunnylink_uploader(params)
def is_modeld_sunny(started: bool, params: Params, CP: car.CarParams) -> bool:
"""Check if the active model is modeld_sunny."""
return bool(params.get_bool("ModeldSunny"))
def is_snpe_model(started, params, CP: car.CarParams) -> bool:
"""Check if the active model runner is SNPE."""
return bool(get_active_model_runner(params, not started) == custom.ModelManagerSP.Runner.snpe)
return bool(get_active_model_runner(params, not started) == custom.ModelManagerSP.Runner.snpe and not is_modeld_sunny(started, params, CP))
def is_tinygrad_model(started, params, CP: car.CarParams) -> bool:
"""Check if the active model runner is SNPE."""
return bool(get_active_model_runner(params, not started) == custom.ModelManagerSP.Runner.tinygrad)
return bool(get_active_model_runner(params, not started) == custom.ModelManagerSP.Runner.tinygrad and not is_modeld_sunny(started, params, CP))
def is_stock_model(started, params, CP: car.CarParams) -> bool:
"""Check if the active model runner is stock."""
return bool(get_active_model_runner(params, not started) == custom.ModelManagerSP.Runner.stock)
return bool(get_active_model_runner(params, not started) == custom.ModelManagerSP.Runner.stock and not is_modeld_sunny(started, params, CP))
def mapd_ready(started: bool, params: Params, CP: car.CarParams) -> bool:
return bool(os.path.exists(Paths.mapd_root()))
@@ -172,6 +176,7 @@ procs += [
PythonProcess("models_manager", "sunnypilot.models.manager", only_offroad),
NativeProcess("modeld_snpe", "sunnypilot/modeld", ["./modeld"], and_(only_onroad, is_snpe_model)),
NativeProcess("modeld_tinygrad", "sunnypilot/modeld_v2", ["./modeld"], and_(only_onroad, is_tinygrad_model)),
PythonProcess("modeld_sunny", "sunnypilot.modeld_sunny.modeld", and_(only_onroad, is_modeld_sunny)),
# Backup
PythonProcess("backup_manager", "sunnypilot.sunnylink.backups.manager", and_(only_offroad, sunnylink_ready_shim)),