mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-06-20 09:12:05 +08:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 88dc1ac3bc | |||
| 1179273217 | |||
| c51b41e96a | |||
| 7722d14b89 | |||
| 043eab5620 | |||
| 51d3f0dea2 | |||
| 8be8bbbfdb | |||
| 1f778c8c23 | |||
| 981494a354 | |||
| 254f55ac15 | |||
| 96b58024ab | |||
| a9229e11a0 | |||
| 020f503364 | |||
| c274dba36e | |||
| 35c87a1519 | |||
| 81bd8aa0e2 | |||
| c908189e73 | |||
| d7e5f3cf43 |
@@ -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
@@ -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> |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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Carnival 2022-24">Buy Here</a></sub></details>|||
|
||||
|Kia|Carnival (China only) 2023|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Carnival (China only) 2023">Buy Here</a></sub></details>|||
|
||||
|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Ceed 2019-21">Buy Here</a></sub></details>|||
|
||||
|Kia|Ceed Plug-in Hybrid Non-SCC 2022|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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|[](##)|[](##)|<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>|||
|
||||
|
||||
+1
-1
Submodule opendbc_repo updated: 1abdb9872f...ff2f9686c2
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -2,3 +2,4 @@ SConscript(['common/transformations/SConscript'])
|
||||
SConscript(['modeld/SConscript'])
|
||||
SConscript(['modeld_v2/SConscript'])
|
||||
SConscript(['selfdrive/locationd/SConscript'])
|
||||
SConscript(['modeld_sunny/SConscript'])
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,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));
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)),
|
||||
|
||||
Reference in New Issue
Block a user