Compare commits

..

20 Commits

Author SHA1 Message Date
James Vecellio-Grant
874f04ac1a Merge branch 'master' into blend-exp 2025-08-29 16:20:17 -05:00
Jason Wen
ba1da60c25 NNLC: compute error in torque space (#1185)
* NNLC: compute error in torque space

* bump

* sp happy too

* bump

* lint

* update path

* oops

* test entire loop

* bump

* test gm

* bump

* bump
2025-08-29 22:39:25 +02:00
DevTekVE
54174d1ef0 agnos: split launch for c3 and c3x to support custom agnos (#1186)
* refactor: skip AGNOS update for tici models in launch script

* back to stock on chffrplus

* feat: enhance launch script for Tici model with error handling and fallback

* empty new line pls
2025-08-29 22:23:58 +02:00
James Vecellio-Grant
d5eea30ad4 Merge branch 'master' into blend-exp 2025-08-26 16:06:56 -07:00
royjr
342ff24510 feature: external storage (#979)
* external storage

* fix mountStorage

* fix perms

* works for now

* better

* lagless

* move to sp qt

* orderish

* fix ui crash

* cleanup

* fix format

* offroad only

* debug external storage

* dont care about delete

* just use cloudlog

* show logs if using external storage

* better text

* wipe entire drive

* allow partitionless drive to be formatted

* label while formatting

* this works

* better

* cleaner

* cleaner logs

* keep upstream happy

---------

Co-authored-by: DevTekVE <devtekve@gmail.com>
2025-08-26 11:49:55 -04:00
github-actions[bot]
6bbf42c16a [bot] Update translations (#1183)
Update translations

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-08-25 23:50:58 -04:00
github-actions[bot]
73e66c4a0b [bot] Update Python packages (#1178)
Update Python packages

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-08-25 21:22:20 -04:00
Nayan
9579d331fc ui: sunnylink panel title & message (#1181)
add title & message to clarify sponsorship isn't required for basic functions
2025-08-25 19:49:49 +02:00
discountchubbs
fd790e684b longitudinal_planner: fix mode transition argument 2025-08-24 21:14:51 -05:00
James Vecellio-Grant
a7d6ac1a03 Merge branch 'master' into blend-exp 2025-08-24 19:11:55 -07:00
James Vecellio-Grant
2b2d0fe941 Merge branch 'master' into blend-exp 2025-08-21 06:42:21 -07:00
James Vecellio-Grant
2eb60e2b97 Merge branch 'master' into blend-exp 2025-08-18 12:17:04 -07:00
discountchubbs
b609bb1391 update 2025-08-18 12:15:37 -07:00
James Vecellio-Grant
8987ee1f9d Merge branch 'master' into blend-exp 2025-08-11 07:13:21 -07:00
Kumar
a39c9d8a2a Merge branch 'master' into blend-exp 2025-08-10 09:36:39 -07:00
discountchubbs
04189ae030 link to dec.active boolean 2025-08-07 09:48:15 -07:00
discountchubbs
9353e3c277 Attach to dynamic experimental controller 2025-08-01 15:00:34 -07:00
James Vecellio-Grant
1ce97e6033 Merge branch 'master' into blend-exp 2025-08-01 07:58:59 -07:00
discountchubbs
29c1f1d06b move to sunny long planner 2025-07-31 11:50:34 -07:00
discountchubbs
9cbce2af33 longitudinal_planner: acc to e2e transition
Use a k=2 sigmoid normalized at 0.5

longitudinal_planner: simple transition

Allow a toggleable param in the cruise panel
2025-07-31 11:26:45 -07:00
61 changed files with 807 additions and 1437 deletions

View File

@@ -123,9 +123,6 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
dec @0 :DynamicExperimentalControl;
events @1 :List(OnroadEventSP.Event);
slc @2 :SpeedLimitControl;
struct DynamicExperimentalControl {
state @0 :DynamicExperimentalControlState;
enabled @1 :Bool;
@@ -136,23 +133,6 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
blended @1;
}
}
struct SpeedLimitControl {
state @0 :SpeedLimitControlState;
enabled @1 :Bool;
active @2 :Bool;
speedLimit @3 :Float32;
speedLimitOffset @4 :Float32;
distToSpeedLimit @5 :Float32;
}
enum SpeedLimitControlState {
inactive @0; # No speed limit set or not enabled by parameter.
tempInactive @1; # User wants to ignore speed limit until it changes.
preActive @2;
adapting @3; # Reducing speed to match new speed limit.
active @4; # Cruising at speed limit.
}
}
struct OnroadEventSP @0xda96579883444c35 {
@@ -192,10 +172,6 @@ struct OnroadEventSP @0xda96579883444c35 {
experimentalModeSwitched @14;
wrongCarModeAlertOnly @15;
pedalPressedAlertOnly @16;
speedLimitPreActive @17;
speedLimitActive @18;
speedLimitConfirmed @19;
speedLimitValueChange @20;
}
}
@@ -271,7 +247,6 @@ struct BackupManagerSP @0xf98d843bfd7004a3 {
}
struct CarStateSP @0xb86e6369214c01c8 {
speedLimit @0 :Float32; # m/s
}
struct LiveMapDataSP @0xf416ec09499d9d19 {

View File

@@ -216,14 +216,4 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"OsmStateTitle", {PERSISTENT, STRING}},
{"OsmWayTest", {PERSISTENT, STRING}},
{"RoadName", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
// Speed Limit Control
{"SpeedLimitControl", {PERSISTENT | BACKUP, BOOL, "0"}},
{"SpeedLimitControlPolicy", {PERSISTENT | BACKUP, INT, "3"}},
{"SpeedLimitEngageType", {PERSISTENT | BACKUP, INT, "0"}},
{"SpeedLimitOffsetType", {PERSISTENT | BACKUP, INT, "0"}},
{"SpeedLimitValueOffset", {PERSISTENT | BACKUP, INT, "0"}},
{"SpeedLimitWarningType", {PERSISTENT | BACKUP, INT, "0"}},
{"SpeedLimitWarningOffsetType", {PERSISTENT | BACKUP, INT, "0"}},
{"SpeedLimitWarningValueOffset", {PERSISTENT | BACKUP, INT, "0"}},
};

View File

@@ -4,12 +4,13 @@
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.
# 326 Supported Cars
# 334 Supported Cars
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br>&nbsp;|Video|Setup Video|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura ILX 2016-18">Buy Here</a></sub></details>|||
|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura ILX 2019">Buy Here</a></sub></details>|||
|Acura|MDX 2025|All except Type S|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2025">Buy Here</a></sub></details>|||
|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2016-18">Buy Here</a></sub></details>|||
|Acura|RDX 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2019-21">Buy Here</a></sub></details>|||
|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 2014-19">Buy Here</a></sub></details>|||
@@ -72,12 +73,15 @@ A supported vehicle is one that just works when you install a comma device. All
|Genesis|GV80 2023[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis GV80 2023">Buy Here</a></sub></details>|||
|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=GMC Sierra 1500 2020-21">Buy Here</a></sub></details>|<a href="https://youtu.be/5HbNoBLzRwE" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Accord 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2018-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=mrUwlj3Mi58" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Accord 2023|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2023">Buy Here</a></sub></details>|||
|Honda|Accord 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2023-25">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2018-22">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2023-25">Buy Here</a></sub></details>|||
|Honda|City (Brazil only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda City (Brazil only) 2023">Buy Here</a></sub></details>|||
|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2016-18">Buy Here</a></sub></details>|<a href="https://youtu.be/-IkImTe1NYE" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|2 mph[<sup>5</sup>](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=4Iz1Mz5LGF8" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2017-21">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2017-18">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2019-21">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic Hatchback Hybrid 2025|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid 2025">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid (Europe only) 2023">Buy Here</a></sub></details>|||
@@ -85,7 +89,9 @@ A supported vehicle is one that just works when you install a comma device. All
|Honda|Clarity 2018-21|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector + Honda Clarity Proxy Board<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://shop.retropilot.org/product/honda-clarity-proxy-board-kit">Buy Here</a></sub></details>|||
|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2015-16">Buy Here</a></sub></details>|||
|Honda|CR-V 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|15 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2017-22">Buy Here</a></sub></details>|||
|Honda|CR-V 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2023-25">Buy Here</a></sub></details>|||
|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2017-22">Buy Here</a></sub></details>|||
|Honda|CR-V Hybrid 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2023-25">Buy Here</a></sub></details>|||
|Honda|e 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda e 2020">Buy Here</a></sub></details>|||
|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Fit 2018-20">Buy Here</a></sub></details>|||
|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Freed 2020">Buy Here</a></sub></details>|||
@@ -96,6 +102,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2018-20">Buy Here</a></sub></details>|||
|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Passport 2019-25">Buy Here</a></sub></details>|||
|Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2016-22">Buy Here</a></sub></details>|||
|Honda|Pilot 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2023-25">Buy Here</a></sub></details>|||
|Honda|Ridgeline 2017-25|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Ridgeline 2017-25">Buy Here</a></sub></details>|||
|Hyundai|Azera 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera 2022">Buy Here</a></sub></details>|||
|Hyundai|Azera Hybrid 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera Hybrid 2019">Buy Here</a></sub></details>|||
@@ -120,7 +127,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Plug-in Hybrid 2019">Buy Here</a></sub></details>|||
|Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Plug-in Hybrid 2020-22">Buy Here</a></sub></details>|||
|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona 2020">Buy Here</a></sub></details>|||
|Hyundai|Kona 2022|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona 2022">Buy Here</a></sub></details>|||
|Hyundai|Kona 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona 2022-23">Buy Here</a></sub></details>|||
|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric 2018-21">Buy Here</a></sub></details>|||
|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai R connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<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>||
@@ -298,6 +305,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Toyota|RAV4 Hybrid 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2022">Buy Here</a></sub></details>|<a href="https://youtu.be/U0nH9cnrFB0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2023-25">Buy Here</a></sub></details>|<a href="https://youtu.be/4eIsEq4L4Ng" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Toyota|Sienna 2018-20|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Sienna 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=q1UPOo4Sh68" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Toyota|Wildlander PHEV 2021|All|openpilot|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Wildlander PHEV 2021">Buy Here</a></sub></details>|||
|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon eHybrid 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon R 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||

View File

@@ -1,3 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
# On any failure, run the fallback launcher
trap 'exec ./launch_chffrplus.sh' ERR
C3_LAUNCH_SH="./sunnypilot/system/hardware/c3/launch_chffrplus.sh"
MODEL="$(tr -d '\0' < "/sys/firmware/devicetree/base/model")"
export MODEL
if [ "$MODEL" = "comma tici" ]; then
# Force a failure if the launcher doesn't exist
[ -x "$C3_LAUNCH_SH" ] || false
# If it exists, run it
exec "$C3_LAUNCH_SH"
fi
exec ./launch_chffrplus.sh

View File

@@ -95,6 +95,8 @@ class Controls(ControlsExt, ModelStateBase):
self.LaC.update_live_torque_params(torque_params.latAccelFactorFiltered, torque_params.latAccelOffsetFiltered,
torque_params.frictionCoefficientFiltered)
self.LaC.extension.update_limits()
self.LaC.extension.update_model_v2(self.sm['modelV2'])
self.lat_delay = get_lat_delay(self.params, self.sm["liveDelay"].lateralDelay)

View File

@@ -35,7 +35,7 @@ class LatControlTorque(LatControl):
self.update_limits()
self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg
self.extension = LatControlTorqueExt(self, CP, CP_SP)
self.extension = LatControlTorqueExt(self, CP, CP_SP, CI)
def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction):
self.torque_params.latAccelFactor = latAccelFactor
@@ -73,12 +73,6 @@ class LatControlTorque(LatControl):
ff = gravity_adjusted_lateral_accel
ff += get_friction(desired_lateral_accel - actual_lateral_accel, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params)
# Lateral acceleration torque controller extension updates
# Overrides stock ff and pid_log.error
ff, pid_log = self.extension.update(CS, VM, params, ff, pid_log, setpoint, measurement, calibrated_pose, roll_compensation,
desired_lateral_accel, actual_lateral_accel, lateral_accel_deadzone, gravity_adjusted_lateral_accel,
desired_curvature, actual_curvature)
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
output_lataccel = self.pid.update(pid_log.error,
feedforward=ff,
@@ -86,6 +80,12 @@ class LatControlTorque(LatControl):
freeze_integrator=freeze_integrator)
output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params)
# Lateral acceleration torque controller extension updates
# Overrides pid_log.error and output_torque
pid_log, output_torque = self.extension.update(CS, VM, self.pid, params, ff, pid_log, setpoint, measurement, calibrated_pose, roll_compensation,
desired_lateral_accel, actual_lateral_accel, lateral_accel_deadzone, gravity_adjusted_lateral_accel,
desired_curvature, actual_curvature, steer_limited_by_safety, output_torque)
pid_log.active = True
pid_log.p = float(self.pid.p)
pid_log.i = float(self.pid.i)

View File

@@ -102,6 +102,8 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
if not self.mlsim:
self.mpc.mode = dec_mpc_mode
self.handle_mode_transition(mode)
if len(sm['carControl'].orientationNED) == 3:
accel_coast = get_coast_accel(sm['carControl'].orientationNED[1])
else:
@@ -146,9 +148,6 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
clipped_accel_coast_interp = np.interp(v_ego, [MIN_ALLOW_THROTTLE_SPEED, MIN_ALLOW_THROTTLE_SPEED*2], [accel_clip[1], clipped_accel_coast])
accel_clip[1] = min(accel_clip[1], clipped_accel_coast_interp)
# Get new v_cruise from Speed Limit Control
v_cruise = LongitudinalPlannerSP.update_v_cruise(self, sm, self.v_desired_filter.x, self.a_desired, v_cruise)
if force_slow_decel:
v_cruise = 0.0
@@ -180,7 +179,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
output_a_target = output_a_target_mpc
self.output_should_stop = output_should_stop_mpc
else:
output_a_target = min(output_a_target_mpc, output_a_target_e2e)
output_a_target = self.blend_accel_transition(output_a_target_mpc, output_a_target_e2e, v_ego)
self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc
for idx in range(2):

View File

@@ -1,6 +1,5 @@
#!/usr/bin/env python3
from cereal import car
from openpilot.common.gps import get_gps_location_service
from openpilot.common.params import Params
from openpilot.common.realtime import Priority, config_realtime_process
from openpilot.common.swaglog import cloudlog
@@ -17,13 +16,10 @@ def main():
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
cloudlog.info("plannerd got CarParams: %s", CP.brand)
gps_location_service = get_gps_location_service(params)
ldw = LaneDepartureWarning()
longitudinal_planner = LongitudinalPlanner(CP)
pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP'])
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState',
'liveMapDataSP', 'carStateSP', gps_location_service],
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState'],
poll='modelV2')
while True:

View File

@@ -96,7 +96,7 @@ class SelfdriveD(CruiseHelper):
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay',
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback'] + \
self.camera_packets + self.sensor_packets + self.gps_packets + ["longitudinalPlanSP"],
self.camera_packets + self.sensor_packets + self.gps_packets,
ignore_alive=ignore, ignore_avg_freq=ignore,
ignore_valid=ignore, frequency=int(1/DT_CTRL))
@@ -209,7 +209,6 @@ class SelfdriveD(CruiseHelper):
if not self.CP.notCar:
self.events.add_from_msg(self.sm['driverMonitoringState'].events)
self.events_sp.add_from_msg(self.sm['longitudinalPlanSP'].events)
# Add car events, ignore if CAN isn't valid
if CS.canValid:

View File

@@ -67,9 +67,6 @@ class Plant:
lp = messaging.new_message('liveParameters')
car_control = messaging.new_message('carControl')
model = messaging.new_message('modelV2')
car_state_sp = messaging.new_message('carStateSP')
live_map_data_sp = messaging.new_message('liveMapDataSP')
gps_data = messaging.new_message('gpsLocation')
a_lead = (v_lead - self.v_lead_prev)/self.ts
self.v_lead_prev = v_lead
@@ -136,10 +133,7 @@ class Plant:
'controlsState': control.controlsState,
'selfdriveState': ss.selfdriveState,
'liveParameters': lp.liveParameters,
'modelV2': model.modelV2,
'carStateSP': car_state_sp.carStateSP,
'liveMapDataSP': live_map_data_sp.liveMapDataSP,
'gpsLocation': gps_data.gpsLocation}
'modelV2': model.modelV2}
self.planner.update(sm)
self.acceleration = self.planner.output_a_target
self.speed = self.speed + self.acceleration * self.ts

View File

@@ -4,6 +4,7 @@ widgets_src = [
"sunnypilot/qt/widgets/controls.cc",
"sunnypilot/qt/widgets/drive_stats.cc",
"sunnypilot/qt/widgets/expandable_row.cc",
"sunnypilot/qt/widgets/external_storage.cc",
"sunnypilot/qt/widgets/prime.cc",
"sunnypilot/qt/widgets/scrollview.cc",
"sunnypilot/qt/network/networking.cc",
@@ -52,10 +53,6 @@ lateral_panel_qt_src = [
longitudinal_panel_qt_src = [
"sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.cc",
"sunnypilot/qt/offroad/settings/longitudinal/slc/speed_limit_control.cc",
"sunnypilot/qt/offroad/settings/longitudinal/slc/speed_limit_control_policy.cc",
"sunnypilot/qt/offroad/settings/longitudinal/slc/speed_limit_control_subpanel.cc",
"sunnypilot/qt/offroad/settings/longitudinal/slc/speed_limit_control_warning.cc",
]
network_src = [

View File

@@ -5,9 +5,14 @@
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/external_storage.h"
DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(parent) {
#ifndef __APPLE__
addItem(new ExternalStorageControl());
#endif
// Advanced Controls Toggle
showAdvancedControls = new ParamControlSP("ShowAdvancedControls", tr("Show Advanced Controls"), tr("Toggle visibility of advanced sunnypilot controls.\nThis only toggles the visibility of the controls; it does not toggle the actual control enabled/disabled state."), "");
addItem(showAdvancedControls);

View File

@@ -1,25 +0,0 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/slc/speed_limit_control.h"
SpeedLimitControl::SpeedLimitControl(const QString &param, const QString &title, const QString &desc, const QString &icon, QWidget *parent)
: ExpandableToggleRow(param, title, desc, icon, parent) {
auto *slcFrame = new QFrame(this);
auto *slcFrameLayout = new QVBoxLayout();
slcFrame->setLayout(slcFrameLayout);
slcFrameLayout->setSpacing(0);
slcFrameLayout->setContentsMargins(0, 0, 0, 0);
slcSettings = new PushButtonSP(tr("Customize SLC"));
slcFrameLayout->addWidget(slcSettings);
connect(slcSettings, &QPushButton::clicked, [&]() {
emit slcSettingsButtonClicked();
});
addItem(slcFrame);
}

View File

@@ -1,77 +0,0 @@
/*
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/expandable_row.h"
enum class SLCEngageType {
AUTO,
USER_CONFIRM,
};
inline const char *SLCEngageTypeText[]{
QT_TR_NOOP("Auto"),
QT_TR_NOOP("User Confirm")
};
enum class SLCOffsetType {
NONE,
FIXED,
PERCENT,
};
inline const char *SLCOffsetTypeText[]{
QT_TR_NOOP("None"),
QT_TR_NOOP("Fixed"),
QT_TR_NOOP("Percent"),
};
enum class SLCWarningType {
OFF,
DISPLAY,
CHIME,
};
inline const char *SLCWarningTypeText[]{
QT_TR_NOOP("Off"),
QT_TR_NOOP("Display"),
QT_TR_NOOP("Chime")
};
enum class SLCSourcePolicy {
CAR_ONLY,
MAP_ONLY,
CAR_FIRST,
MAP_FIRST,
COMBINED
};
inline const char *SLCSourcePolicyText[]{
QT_TR_NOOP("Car\nOnly"),
QT_TR_NOOP("Map\nOnly"),
QT_TR_NOOP("Car\nFirst"),
QT_TR_NOOP("Map\nFirst"),
QT_TR_NOOP("Combined\nData")
};
class SpeedLimitControl : public ExpandableToggleRow {
Q_OBJECT
public:
SpeedLimitControl(const QString &param, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr);
signals:
void slcSettingsButtonClicked();
private:
Params params;
PushButtonSP *slcSettings;
};

View File

@@ -1,53 +0,0 @@
/*
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/slc/speed_limit_control_policy.h"
SpeedLimitControlPolicy::SpeedLimitControlPolicy(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0);
main_layout->setSpacing(0);
// Back button
PanelBackButton *back = new PanelBackButton(tr("Back"));
connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
main_layout->addWidget(back, 0, Qt::AlignLeft);
main_layout->addSpacing(10);
ListWidgetSP *list = new ListWidgetSP(this, true);
std::vector<QString> slc_policy_texts{
SLCSourcePolicyText[static_cast<int>(SLCSourcePolicy::CAR_ONLY)],
SLCSourcePolicyText[static_cast<int>(SLCSourcePolicy::MAP_ONLY)],
SLCSourcePolicyText[static_cast<int>(SLCSourcePolicy::CAR_FIRST)],
SLCSourcePolicyText[static_cast<int>(SLCSourcePolicy::MAP_FIRST)],
SLCSourcePolicyText[static_cast<int>(SLCSourcePolicy::COMBINED)]
};
slc_policy = new ButtonParamControlSP(
"SpeedLimitControlPolicy",
tr("Speed Limit Source"),
"",
"",
slc_policy_texts,
250);
list->addItem(slc_policy);
connect(slc_policy, &ButtonParamControlSP::buttonClicked, this, &SpeedLimitControlPolicy::refresh);
refresh();
main_layout->addWidget(list);
};
void SpeedLimitControlPolicy::refresh() {
SLCSourcePolicy policy_param = static_cast<SLCSourcePolicy>(std::atoi(params.get("SpeedLimitControlPolicy").c_str()));
slc_policy->setDescription(sourceDescription(policy_param));
}
void SpeedLimitControlPolicy::showEvent(QShowEvent *event) {
refresh();
slc_policy->showDescription();
}

View File

@@ -1,56 +0,0 @@
/*
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/slc/speed_limit_control.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
class SpeedLimitControlPolicy : public QWidget {
Q_OBJECT
public:
explicit SpeedLimitControlPolicy(QWidget *parent = nullptr);
void refresh();
void showEvent(QShowEvent *event) override;
signals:
void backPress();
private:
Params params;
ButtonParamControlSP *slc_policy;
static QString sourceDescription(SLCSourcePolicy type = SLCSourcePolicy::CAR_ONLY) {
QString car_only = tr("⦿ Car Only: Use Speed Limit data only from Car");
QString map_only = tr("⦿ Map Only: Use Speed Limit data only from OpenStreetMaps");
QString car_first = tr("⦿ Car First: Use Speed Limit data from Car if available, else use from OpenStreetMaps");
QString map_first = tr("⦿ Map First: Use Speed Limit data from OpenStreetMaps if available, else use from Car");
QString combined = tr("⦿ Combined: Use combined Speed Limit data from Car & OpenStreetMaps");
if (type == SLCSourcePolicy::CAR_ONLY) {
car_only = "<font color='white'><b>" + car_only + "</b></font>";
} else if (type == SLCSourcePolicy::MAP_ONLY) {
map_only = "<font color='white'><b>" + map_only + "</b></font>";
} else if (type == SLCSourcePolicy::CAR_FIRST) {
car_first = "<font color='white'><b>" + car_first + "</b></font>";
} else if (type == SLCSourcePolicy::MAP_FIRST) {
map_first = "<font color='white'><b>" + map_first + "</b></font>";
} else {
combined = "<font color='white'><b>" + combined + "</b></font>";
}
return QString("%1<br>%2<br>%3<br>%4<br>%5")
.arg(car_only)
.arg(map_only)
.arg(car_first)
.arg(map_first)
.arg(combined);
}
};

View File

@@ -1,142 +0,0 @@
/*
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/slc/speed_limit_control_subpanel.h"
SpeedLimitControlSubpanel::SpeedLimitControlSubpanel(QWidget *parent) : QStackedWidget(parent) {
subPanelFrame = new QFrame();
QVBoxLayout *subPanelLayout = new QVBoxLayout(subPanelFrame);
subPanelLayout->setContentsMargins(0, 0, 0, 0);
subPanelLayout->setSpacing(0);
// Back button
PanelBackButton *back = new PanelBackButton(tr("Back"));
connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
subPanelLayout->addWidget(back, 0, Qt::AlignLeft);
subPanelLayout->addSpacing(20);
ListWidgetSP *list = new ListWidgetSP(this, true);
auto *slcBtnFrame = new QFrame(this);
auto *slcBtnFrameLayout = new QGridLayout();
slcBtnFrame->setLayout(slcBtnFrameLayout);
slcBtnFrameLayout->setContentsMargins(0, 40, 0, 40);
slcBtnFrameLayout->setSpacing(0);
slcWarningScreen = new SpeedLimitControlWarning(this);
slcPolicyScreen = new SpeedLimitControlPolicy(this);
slcWarningControl = new PushButtonSP(tr("Customize Warning"));
connect(slcWarningControl, &QPushButton::clicked, [&]() {
setCurrentWidget(slcWarningScreen);
slcWarningScreen->refresh();
});
connect(slcWarningScreen, &SpeedLimitControlWarning::backPress, [&]() {
setCurrentWidget(subPanelFrame);
showEvent(new QShowEvent());
});
slcSourceControl = new PushButtonSP(tr("Customize Source"));
connect(slcSourceControl, &QPushButton::clicked, [&]() {
setCurrentWidget(slcPolicyScreen);
slcPolicyScreen->refresh();
});
connect(slcPolicyScreen, &SpeedLimitControlPolicy::backPress, [&]() {
setCurrentWidget(subPanelFrame);
showEvent(new QShowEvent());
});
slcWarningControl->setFixedWidth(720);
slcSourceControl->setFixedWidth(720);
slcBtnFrameLayout->addWidget(slcWarningControl, 0, 0, Qt::AlignLeft);
slcBtnFrameLayout->addWidget(slcSourceControl, 0, 1, Qt::AlignRight);
list->addItem(slcBtnFrame);
std::vector<QString> slc_engage_texts{
SLCEngageTypeText[static_cast<int>(SLCEngageType::AUTO)],
SLCEngageTypeText[static_cast<int>(SLCEngageType::USER_CONFIRM)]
};
slc_engage_setting = new ButtonParamControlSP(
"SpeedLimitEngageType",
tr("Engage Mode"),
"",
"",
slc_engage_texts,
500);
slc_engage_setting->showDescription();
list->addItem(slc_engage_setting);
QFrame *offsetFrame = new QFrame(this);
QVBoxLayout *offsetLayout = new QVBoxLayout(offsetFrame);
std::vector<QString> slc_offset_texts{
SLCOffsetTypeText[static_cast<int>(SLCOffsetType::NONE)],
SLCOffsetTypeText[static_cast<int>(SLCOffsetType::FIXED)],
SLCOffsetTypeText[static_cast<int>(SLCOffsetType::PERCENT)]
};
slc_offset_setting = new ButtonParamControlSP(
"SpeedLimitOffsetType",
tr("Speed Limit Offset"),
"",
"",
slc_offset_texts,
500);
offsetLayout->addWidget(slc_offset_setting);
slc_offset = new OptionControlSP(
"SpeedLimitValueOffset",
"",
"",
"",
{-30, 30}
);
offsetLayout->addWidget(slc_offset);
list->addItem(offsetFrame);
connect(slc_offset, &OptionControlSP::updateLabels, this, &SpeedLimitControlSubpanel::refresh);
connect(slc_offset_setting, &ButtonParamControlSP::showDescriptionEvent, slc_offset, &OptionControlSP::showDescription);
connect(slc_engage_setting, &ButtonParamControlSP::buttonClicked, this, &SpeedLimitControlSubpanel::refresh);
connect(slc_offset_setting, &ButtonParamControlSP::buttonClicked, this, &SpeedLimitControlSubpanel::refresh);
refresh();
subPanelLayout->addWidget(list);
addWidget(subPanelFrame);
addWidget(slcWarningScreen);
addWidget(slcPolicyScreen);
setCurrentWidget(subPanelFrame);
};
void SpeedLimitControlSubpanel::refresh() {
SLCEngageType engage_type_param = static_cast<SLCEngageType>(std::atoi(params.get("SpeedLimitEngageType").c_str()));
SLCOffsetType offset_type_param = static_cast<SLCOffsetType>(std::atoi(params.get("SpeedLimitOffsetType").c_str()));
QString offsetLabel = QString::fromStdString(params.get("SpeedLimitValueOffset"));
slc_engage_setting->setDescription(engageModeDescription(engage_type_param));
slc_offset->setDescription(offsetDescription(offset_type_param));
if (offset_type_param == SLCOffsetType::PERCENT) {
offsetLabel += "%";
}
if (offset_type_param == SLCOffsetType::NONE) {
slc_offset->setVisible(false);
} else {
slc_offset->setVisible(true);
slc_offset->setLabel(offsetLabel);
slc_offset->showDescription();
}
}
void SpeedLimitControlSubpanel::showEvent(QShowEvent *event) {
refresh();
slc_engage_setting->showDescription();
slc_offset->showDescription();
}

View File

@@ -1,72 +0,0 @@
/*
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/slc/speed_limit_control.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/slc/speed_limit_control_policy.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/slc/speed_limit_control_warning.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
class SpeedLimitControlSubpanel : public QStackedWidget {
Q_OBJECT
public:
SpeedLimitControlSubpanel(QWidget *parent = nullptr);
void refresh();
void showEvent(QShowEvent *event) override;
signals:
void backPress();
private:
Params params;
QFrame *subPanelFrame;
PushButtonSP *slcWarningControl;
PushButtonSP *slcSourceControl;
ButtonParamControlSP *slc_offset_setting;
OptionControlSP *slc_offset;
ButtonParamControlSP *slc_engage_setting;
SpeedLimitControlWarning *slcWarningScreen;
SpeedLimitControlPolicy *slcPolicyScreen;
static QString engageModeDescription(SLCEngageType type = SLCEngageType::AUTO) {
QString auto_str = tr("⦿ Auto: Automatic speed adjustment based on speed limit data");
QString user_str = tr("⦿ User Confirm: Asks driver to confirm speed adjustment based on speed limit data");
if (type == SLCEngageType::USER_CONFIRM) {
user_str = "<font color='white'><b>" + user_str + "</b></font>";
} else {
auto_str = "<font color='white'><b>" + auto_str + "</b></font>";
}
return QString("%1<br>%2")
.arg(auto_str)
.arg(user_str);
}
static QString offsetDescription(SLCOffsetType type = SLCOffsetType::NONE) {
QString none_str = tr("⦿ None: No Offset");
QString fixed_str = tr("⦿ Fixed: Adds a fixed offset [Speed Limit + Offset]");
QString percent_str = tr("⦿ Percent: Adds a percent offset [Speed Limit + (Offset % Speed Limit)]");
if (type == SLCOffsetType::FIXED) {
fixed_str = "<font color='white'><b>" + fixed_str + "</b></font>";
} else if (type == SLCOffsetType::PERCENT) {
percent_str = "<font color='white'><b>" + percent_str + "</b></font>";
} else {
none_str = "<font color='white'><b>" + none_str + "</b></font>";
}
return QString("%1<br>%2<br>%3")
.arg(none_str)
.arg(fixed_str)
.arg(percent_str);
}
};

View File

@@ -1,100 +0,0 @@
/*
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/slc/speed_limit_control_warning.h"
SpeedLimitControlWarning::SpeedLimitControlWarning(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0);
main_layout->setSpacing(0);
// Back button
PanelBackButton *back = new PanelBackButton(tr("Back"));
connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
main_layout->addWidget(back, 0, Qt::AlignLeft);
main_layout->addSpacing(10);
ListWidgetSP *list = new ListWidgetSP(this, true);
std::vector<QString> slc_warning_texts{
SLCWarningTypeText[static_cast<int>(SLCWarningType::OFF)],
SLCWarningTypeText[static_cast<int>(SLCWarningType::DISPLAY)],
SLCWarningTypeText[static_cast<int>(SLCWarningType::CHIME)]
};
slc_warning_settings = new ButtonParamControlSP(
"SpeedLimitWarningType", tr("Speed Limit Warning"),
"",
"",
slc_warning_texts,
300);
list->addItem(slc_warning_settings);
QFrame *offsetFrame = new QFrame(this);
QVBoxLayout *offsetLayout = new QVBoxLayout(offsetFrame);
std::vector<QString> slc_warning_offset_texts{
SLCOffsetTypeText[static_cast<int>(SLCOffsetType::NONE)],
SLCOffsetTypeText[static_cast<int>(SLCOffsetType::FIXED)],
SLCOffsetTypeText[static_cast<int>(SLCOffsetType::PERCENT)]
};
slc_warning_offset_settings = new ButtonParamControlSP(
"SpeedLimitWarningOffsetType",
tr("Warning Offset"),
"",
"",
slc_warning_offset_texts,
300);
offsetLayout->addWidget(slc_warning_offset_settings);
slc_warning_offset = new OptionControlSP(
"SpeedLimitWarningValueOffset",
"",
"",
"",
{-30, 30}
);
slc_warning_offset->setFixedWidth(100);
offsetLayout->addWidget(slc_warning_offset);
list->addItem(offsetFrame);
connect(slc_warning_offset, &OptionControlSP::updateLabels, this, &SpeedLimitControlWarning::refresh);
connect(slc_warning_offset_settings, &ButtonParamControlSP::showDescriptionEvent, slc_warning_offset, &OptionControlSP::showDescription);
connect(slc_warning_settings, &ButtonParamControlSP::buttonClicked, this, &SpeedLimitControlWarning::refresh);
connect(slc_warning_offset_settings, &ButtonParamControlSP::buttonClicked, this, &SpeedLimitControlWarning::refresh);
refresh();
main_layout->addWidget(list);
};
void SpeedLimitControlWarning::refresh() {
SLCWarningType warning_type_param = static_cast<SLCWarningType>(std::atoi(params.get("SpeedLimitWarningType").c_str()));
SLCOffsetType offset_type_param = static_cast<SLCOffsetType>(std::atoi(params.get("SpeedLimitWarningOffsetType").c_str()));
QString offsetLabel = QString::fromStdString(params.get("SpeedLimitWarningValueOffset"));
slc_warning_settings->setDescription(warningDescription(warning_type_param));
slc_warning_offset->setDescription(offsetDescription(offset_type_param));
if (offset_type_param == SLCOffsetType::PERCENT) {
offsetLabel += "%";
}
if (offset_type_param == SLCOffsetType::NONE) {
slc_warning_offset->setDisabled(true);
slc_warning_offset->setLabel(tr("N/A"));
} else {
slc_warning_offset->setDisabled(false);
slc_warning_offset->setLabel(offsetLabel);
}
}
void SpeedLimitControlWarning::showEvent(QShowEvent *event) {
refresh();
slc_warning_settings->showDescription();
slc_warning_offset->showDescription();
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/slc/speed_limit_control.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
class SpeedLimitControlWarning : public QWidget {
Q_OBJECT
public:
SpeedLimitControlWarning(QWidget *parent = nullptr);
void refresh();
void showEvent(QShowEvent *event) override;
signals:
void backPress();
private:
Params params;
ButtonParamControlSP *slc_warning_settings;
ButtonParamControlSP *slc_warning_offset_settings;
OptionControlSP *slc_warning_offset;
static QString warningDescription(SLCWarningType type = SLCWarningType::OFF) {
QString off_str = tr("⦿ Off: No Warning");
QString display_str = tr("⦿ Display: Speed Limit Sign will visually alert");
QString chime_str = tr("⦿ Chime: Speed Limit Sign will visually alert along with an audible chime");
if (type == SLCWarningType::DISPLAY) {
display_str = "<font color='white'><b>" + display_str + "</b></font>";
} else if (type == SLCWarningType::CHIME) {
chime_str = "<font color='white'><b>" + chime_str + "</b></font>";
} else {
off_str = "<font color='white'><b>" + off_str + "</b></font>";
}
return QString("%1<br>%2<br>%3")
.arg(off_str)
.arg(display_str)
.arg(chime_str);
}
static QString offsetDescription(SLCOffsetType type = SLCOffsetType::NONE) {
QString none_str = tr("⦿ None: No Offset");
QString fixed_str = tr("⦿ Fixed: Adds a fixed offset [Speed Limit + Offset]");
QString percent_str = tr("⦿ Percent: Adds a percent offset [Speed Limit + (Offset % Speed Limit)]");
if (type == SLCOffsetType::FIXED) {
fixed_str = "<font color='white'><b>" + fixed_str + "</b></font>";
} else if (type == SLCOffsetType::PERCENT) {
percent_str = "<font color='white'><b>" + percent_str + "</b></font>";
} else {
none_str = "<font color='white'><b>" + none_str + "</b></font>";
}
return QString("%1<br>%2<br>%3")
.arg(none_str)
.arg(fixed_str)
.arg(percent_str);
}
};

View File

@@ -8,21 +8,6 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h"
LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
setStyleSheet(R"(
#back_btn {
font-size: 50px;
margin: 0px;
padding: 15px;
border-width: 0;
border-radius: 30px;
color: #dddddd;
background-color: #393939;
}
#back_btn:pressed {
background-color: #4a4a4a;
}
)");
main_layout = new QStackedLayout(this);
ListWidget *list = new ListWidget(this, false);
@@ -38,28 +23,7 @@ LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
QObject::connect(uiState(), &UIState::offroadTransition, this, &LongitudinalPanel::refresh);
slcControl = new SpeedLimitControl(
"SpeedLimitControl",
tr("Speed Limit Control (SLC)"),
tr("When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. "
"The maximum cruising speed will always be the MAX set speed."),
"",
this);
list->addItem(slcControl);
connect(slcControl, &SpeedLimitControl::slcSettingsButtonClicked, [=]() {
cruisePanelScroller->setLastScrollPosition();
main_layout->setCurrentWidget(slcScreen);
});
slcScreen = new SpeedLimitControlSubpanel(this);
connect(slcScreen, &SpeedLimitControlSubpanel::backPress, [=]() {
cruisePanelScroller->restoreScrollPosition();
main_layout->setCurrentWidget(cruisePanelScreen);
});
main_layout->addWidget(cruisePanelScreen);
main_layout->addWidget(slcScreen);
main_layout->setCurrentWidget(cruisePanelScreen);
refresh(offroad);
}

View File

@@ -8,11 +8,8 @@
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/slc/speed_limit_control.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/slc/speed_limit_control_subpanel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
#include "selfdrive/ui/sunnypilot/ui.h"
class LongitudinalPanel : public QWidget {
Q_OBJECT
@@ -32,6 +29,4 @@ private:
ScrollViewSP *cruisePanelScroller = nullptr;
QWidget *cruisePanelScreen = nullptr;
CustomAccIncrement *customAccIncrement = nullptr;
SpeedLimitControlSubpanel *slcScreen;
SpeedLimitControl *slcControl;
};

View File

@@ -34,6 +34,27 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) {
vlayout->setContentsMargins(50, 20, 50, 20);
auto *list = new ListWidget(this, false);
QVBoxLayout *titleLayout = new QVBoxLayout;
QLabel *title = new QLabel(tr("🚀 sunnylink 🚀"));
title->setStyleSheet("font-size: 90px; font-weight: 500; font-family: 'Noto Color Emoji';");
titleLayout->addWidget(title, 0, Qt::AlignCenter);
QLabel *sunnylinkDesc = new QLabel("<div align='center'><font color='green'>"+
tr("For secure backup, restore, and remote configuration")+ "</font></div>");
QLabel *sponsorMsg = new QLabel("<div align='center'><font color='orange'>"+
tr("Sponsorship isn't required for basic backup/restore") + "<br>" +
tr("Click the sponsor button for more details")+ "</font></div>");
sunnylinkDesc->setStyleSheet("font-size: 40px; font-weight: 100; font-family: 'Noto';");
sponsorMsg->setStyleSheet("font-size: 35px; font-weight: 100; font-family: 'Noto';");
titleLayout->addWidget(sunnylinkDesc, 0, Qt::AlignCenter);
titleLayout->addWidget(sponsorMsg, 0, Qt::AlignCenter);
list->addItem(titleLayout);
QString sunnylinkEnabledBtnDesc = tr("This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that.");
sunnylinkEnabledBtn = new ParamControl(
"SunnylinkEnabled",

View File

@@ -18,7 +18,6 @@ ExpandableToggleRow::ExpandableToggleRow(const QString &param, const QString &ti
collapsibleWidget->setVisible(false);
QVBoxLayout *collapsible_layout = new QVBoxLayout();
collapsibleWidget->setLayout(collapsible_layout);
collapsible_layout->setContentsMargins(0, 20, 0, 20);
list = new ListWidgetSP(this, false);

View File

@@ -0,0 +1,170 @@
/**
* 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.
*/
#include "selfdrive/ui/sunnypilot/qt/widgets/external_storage.h"
#include <QProcess>
#include <QCoreApplication>
#include <QShowEvent>
#include <QTimer>
#include <QtConcurrent>
#include "common/params.h"
#include "selfdrive/ui/qt/api.h"
#include "selfdrive/ui/qt/widgets/input.h"
#include "selfdrive/ui/sunnypilot/ui.h"
ExternalStorageControl::ExternalStorageControl() :
ButtonControl(tr("External Storage"), "", tr("Extend your comma device's storage by inserting a USB drive into the aux port.")) {
QObject::connect(this, &ButtonControl::clicked, [=]() {
if (text() == tr("CHECK") || text() == tr("MOUNT")) {
mountStorage();
} else if (text() == tr("UNMOUNT")) {
unmountStorage();
} else if (text() == tr("FORMAT")) {
if (ConfirmationDialog::confirm(tr("Are you sure you want to format this drive? This will erase all data."), tr("Format"), this)) {
formatStorage();
}
}
});
QObject::connect(uiState(), &UIState::offroadTransition, this, &ExternalStorageControl::updateState);
updateState(!uiState()->scene.started);
refresh();
}
void ExternalStorageControl::updateState(bool offroad) {
setEnabled(offroad);
}
void ExternalStorageControl::debouncedRefresh() {
if (refreshPending) return;
refreshPending = true;
QTimer::singleShot(250, this, [=]() {
refreshPending = false;
refresh();
});
}
void ExternalStorageControl::refresh() {
QtConcurrent::run([=]() {
auto run = [](const QString &cmd) {
QProcess p;
p.start("sh", QStringList() << "-c" << cmd);
p.waitForFinished();
return p.exitCode() == 0;
};
bool isMounted = run("findmnt -n /mnt/external_realdata");
bool hasDrive = run("lsblk -f /dev/sdg");
bool hasFs = run("lsblk -f /dev/sdg1 | grep -q ext4");
bool hasLabel = run("sudo blkid /dev/sdg1 | grep -q 'LABEL=\"openpilot\"'");
QString info;
if (isMounted && hasLabel) {
QProcess df;
df.start("sh", QStringList() << "-c" << "df -h /mnt/external_realdata | awk 'NR==2 {print $3 \"/\" $2}'");
df.waitForFinished();
info = df.readAllStandardOutput().trimmed();
}
QMetaObject::invokeMethod(this, [=]() {
if (formatting) {
setValue(tr("formatting"));
setText(tr("FORMAT"));
setEnabled(false);
} else {
if (!hasDrive) {
setValue(tr("insert drive"));
setText(tr("CHECK"));
} else if (!hasFs || !hasLabel) {
setValue(tr("needs format"));
setText(tr("FORMAT"));
} else if (isMounted) {
setValue(info);
setText(tr("UNMOUNT"));
} else {
setValue("drive detected");
setText(tr("MOUNT"));
}
updateState(!uiState()->scene.started);
}
}, Qt::QueuedConnection);
});
}
void ExternalStorageControl::mountStorage() {
setValue(tr("mounting"));
setEnabled(false);
QtConcurrent::run([=]() {
QProcess process;
process.start("sh", QStringList() << "-c" <<
"sudo mount -o remount,rw / && "
"sudo mkdir -p /mnt/external_realdata && "
"grep -q '/dev/sdg1 /mnt/external_realdata' /etc/fstab || "
"echo '/dev/sdg1 /mnt/external_realdata ext4 defaults,nofail 0 2' | sudo tee -a /etc/fstab && "
"sudo systemctl daemon-reexec && "
"sudo mount /mnt/external_realdata && "
"sudo chown -R comma:comma /mnt/external_realdata && "
"sudo chmod -R 775 /mnt/external_realdata && "
"sudo mount -o remount,ro /");
process.waitForFinished();
QMetaObject::invokeMethod(this, [=]() {
debouncedRefresh();
}, Qt::QueuedConnection);
});
}
void ExternalStorageControl::unmountStorage() {
setValue(tr("unmounting"));
setEnabled(false);
QtConcurrent::run([=]() {
QProcess process;
process.start("sh", QStringList() << "-c" << "sudo umount /mnt/external_realdata");
process.waitForFinished();
QMetaObject::invokeMethod(this, [=]() {
debouncedRefresh();
}, Qt::QueuedConnection);
});
}
void ExternalStorageControl::formatStorage() {
unmountStorage();
formatting = true;
setValue(tr("formatting"));
setEnabled(false);
QProcess *process = new QProcess(this);
connect(process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
this, [=](int exitCode, QProcess::ExitStatus status) {
process->deleteLater();
formatting = false;
if (exitCode == 0 && status == QProcess::NormalExit) {
mountStorage();
} else {
setValue(tr("needs format"));
updateState(!uiState()->scene.started);
}
});
process->start("sh", QStringList() << "-c" <<
"sudo wipefs -a /dev/sdg && "
"sudo parted -s /dev/sdg mklabel gpt mkpart primary ext4 0% 100% && "
"sudo mkfs.ext4 -F -L openpilot /dev/sdg1"
);
}
void ExternalStorageControl::showEvent(QShowEvent *event) {
ButtonControl::showEvent(event);
QTimer::singleShot(100, this, &ExternalStorageControl::debouncedRefresh);
}

View File

@@ -0,0 +1,34 @@
/**
* 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.
*/
#pragma once
#include "system/hardware/hw.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#define ButtonControl ButtonControlSP
class ExternalStorageControl : public ButtonControl {
Q_OBJECT
public:
ExternalStorageControl();
protected:
void showEvent(QShowEvent *event) override;
private:
Params params;
bool refreshPending = false;
bool formatting = false;
void updateState(bool offroad);
void refresh();
void debouncedRefresh();
void mountStorage();
void unmountStorage();
void formatStorage();
};

View File

@@ -2103,6 +2103,22 @@ Warning: You are on a metered connection!</source>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>

View File

@@ -2085,6 +2085,22 @@ Warning: You are on a metered connection!</source>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>

View File

@@ -2087,6 +2087,22 @@ Warning: You are on a metered connection!</source>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>

View File

@@ -2083,6 +2083,22 @@ Warning: You are on a metered connection!</source>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>

View File

@@ -2082,6 +2082,22 @@ Warning: You are on a metered connection!</source>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>

View File

@@ -2096,6 +2096,22 @@ Warning: You are on a metered connection!</source>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>

View File

@@ -2087,6 +2087,22 @@ Warning: You are on a metered connection!</source>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>

View File

@@ -2078,6 +2078,22 @@ Warning: You are on a metered connection!</source>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>

View File

@@ -2077,6 +2077,22 @@ Warning: You are on a metered connection!</source>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>

View File

@@ -2082,6 +2082,22 @@ Warning: You are on a metered connection!</source>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>

View File

@@ -2082,6 +2082,22 @@ Warning: You are on a metered connection!</source>
<source>[Don&apos;t use] Enable sunnylink uploader</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🚀 sunnylink 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>For secure backup, restore, and remote configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sponsorship isn&apos;t required for basic backup/restore</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Click the sponsor button for more details</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnylinkSponsorPopup</name>

View File

@@ -9,23 +9,28 @@ from openpilot.sunnypilot.selfdrive.controls.lib.nnlc.nnlc import NeuralNetworkL
class LatControlTorqueExt(NeuralNetworkLateralControl):
def __init__(self, lac_torque, CP, CP_SP):
super().__init__(lac_torque, CP, CP_SP)
def __init__(self, lac_torque, CP, CP_SP, CI):
super().__init__(lac_torque, CP, CP_SP, CI)
def update(self, CS, VM, params, ff, pid_log, setpoint, measurement, calibrated_pose, roll_compensation,
def update(self, CS, VM, pid, params, ff, pid_log, setpoint, measurement, calibrated_pose, roll_compensation,
desired_lateral_accel, actual_lateral_accel, lateral_accel_deadzone, gravity_adjusted_lateral_accel,
desired_curvature, actual_curvature):
desired_curvature, actual_curvature, steer_limited_by_safety, output_torque):
self._ff = ff
self._pid = pid
self._pid_log = pid_log
self._setpoint = setpoint
self._measurement = measurement
self._roll_compensation = roll_compensation
self._lateral_accel_deadzone = lateral_accel_deadzone
self._desired_lateral_accel = desired_lateral_accel
self._actual_lateral_accel = actual_lateral_accel
self._desired_curvature = desired_curvature
self._actual_curvature = actual_curvature
self._gravity_adjusted_lateral_accel = gravity_adjusted_lateral_accel
self._steer_limited_by_safety = steer_limited_by_safety
self._output_torque = output_torque
self.update_calculations(CS, VM, desired_lateral_accel)
self.update_neural_network_feedforward(CS, params, calibrated_pose)
return self._ff, self._pid_log
return self._pid_log, self._output_torque

View File

@@ -7,6 +7,7 @@ See the LICENSE.md file in the root directory for more details.
import math
import numpy as np
from openpilot.common.pid import PIDController
from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N
from openpilot.selfdrive.modeld.constants import ModelConstants
@@ -43,9 +44,10 @@ def get_lookahead_value(future_vals, current_val):
class LatControlTorqueExtBase:
def __init__(self, lac_torque, CP, CP_SP):
def __init__(self, lac_torque, CP, CP_SP, CI):
self.model_v2 = None
self.model_valid = False
self.lac_torque = lac_torque
self.torque_params = lac_torque.torque_params
self.actual_lateral_jerk: float = 0.0
@@ -53,17 +55,22 @@ class LatControlTorqueExtBase:
self.lateral_jerk_measurement: float = 0.0
self.lookahead_lateral_jerk: float = 0.0
self.torque_from_lateral_accel = lac_torque.torque_from_lateral_accel
self.torque_from_lateral_accel_in_torque_space = CI.torque_from_lateral_accel_in_torque_space()
self._ff = 0.0
self._pid = PIDController(0.0, 0.0, k_f=0.0)
self._pid_log = None
self._setpoint = 0.0
self._measurement = 0.0
self._roll_compensation = 0.0
self._lateral_accel_deadzone = 0.0
self._desired_lateral_accel = 0.0
self._actual_lateral_accel = 0.0
self._desired_curvature = 0.0
self._actual_curvature = 0.0
self._gravity_adjusted_lateral_accel = 0.0
self._steer_limited_by_safety = False
self._output_torque = 0.0
# twilsonco's Lateral Neural Network Feedforward
# Instantaneous lateral jerk changes very rapidly, making it not useful on its own,

View File

@@ -4,13 +4,12 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import math
from cereal import messaging, custom
from opendbc.car import structs
from openpilot.selfdrive.car.cruise import V_CRUISE_UNSET
from opendbc.car.interfaces import ACCEL_MIN
from openpilot.sunnypilot.selfdrive.controls.lib.dec.dec import DynamicExperimentalController
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit_controller.speed_limit_controller import SpeedLimitController
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
from openpilot.sunnypilot.models.helpers import get_active_bundle
DecState = custom.LongitudinalPlanSP.DynamicExperimentalControl.DynamicExperimentalControlState
@@ -18,11 +17,9 @@ DecState = custom.LongitudinalPlanSP.DynamicExperimentalControl.DynamicExperimen
class LongitudinalPlannerSP:
def __init__(self, CP: structs.CarParams, mpc):
self.events_sp = EventsSP()
self.dec = DynamicExperimentalController(CP, mpc)
self.transition_init()
self.generation = int(model_bundle.generation) if (model_bundle := get_active_bundle()) else None
self.slc = SpeedLimitController(CP)
@property
def mlsim(self) -> bool:
@@ -35,16 +32,31 @@ class LongitudinalPlannerSP:
return self.dec.mode()
def update_v_cruise(self, sm: messaging.SubMaster, v_ego: float, a_ego: float, v_cruise: float) -> float:
self.events_sp.clear()
def transition_init(self) -> None:
self._transition_counter = 0
self._transition_steps = 20
self._last_mode = 'acc'
self.slc.update(sm, v_ego, a_ego, v_cruise, self.events_sp)
def handle_mode_transition(self, mode: str) -> None:
if self._last_mode != mode:
if mode == 'blended':
self._transition_counter = 0
self._last_mode = mode
v_cruise_slc = self.slc.speed_limit_offseted if self.slc.is_active else V_CRUISE_UNSET
v_cruise_final = min(v_cruise, v_cruise_slc)
return v_cruise_final
def blend_accel_transition(self, mpc_accel: float, e2e_accel: float, v_ego: float) -> float:
if self.dec.enabled():
if self._transition_counter < self._transition_steps:
self._transition_counter += 1
progress = self._transition_counter / self._transition_steps
if v_ego > 5.0 and e2e_accel < 0.0:
if mpc_accel < 0.0 and e2e_accel > mpc_accel:
return mpc_accel
# use k3.0 and normalize midpoint at 0.5
sigmoid = 1.0 / (1.0 + math.exp(-3.0 * (abs(e2e_accel / ACCEL_MIN) - 0.5)))
blend_factor = 1.0 - (1.0 - progress) * (1.0 - sigmoid)
blended = mpc_accel + (e2e_accel - mpc_accel) * blend_factor
return blended
return min(mpc_accel, e2e_accel)
def update(self, sm: messaging.SubMaster) -> None:
self.dec.update(sm)
@@ -55,7 +67,6 @@ class LongitudinalPlannerSP:
plan_sp_send.valid = sm.all_checks(service_list=['carState', 'controlsState'])
longitudinalPlanSP = plan_sp_send.longitudinalPlanSP
longitudinalPlanSP.events = self.events_sp.to_msg()
# Dynamic Experimental Control
dec = longitudinalPlanSP.dec
@@ -63,13 +74,4 @@ class LongitudinalPlannerSP:
dec.enabled = self.dec.enabled()
dec.active = self.dec.active()
# Speed Limit Control
slc = longitudinalPlanSP.slc
slc.state = self.slc.state
slc.enabled = self.slc.is_enabled
slc.active = self.slc.is_active
slc.speedLimit = float(self.slc.speed_limit)
slc.speedLimitOffset = float(self.slc.speed_limit_offset)
slc.distToSpeedLimit = float(self.slc.distance)
pm.send('longitudinalPlanSP', plan_sp_send)

View File

@@ -9,6 +9,8 @@ import math
import numpy as np
from opendbc.car.lateral import FRICTION_THRESHOLD, get_friction
from opendbc.sunnypilot.car.interfaces import LatControlInputs
from opendbc.sunnypilot.car.lateral_ext import get_friction as get_friction_in_torque_space
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.params import Params
from openpilot.selfdrive.modeld.constants import ModelConstants
@@ -30,8 +32,8 @@ def roll_pitch_adjust(roll, pitch):
class NeuralNetworkLateralControl(LatControlTorqueExtBase):
def __init__(self, lac_torque, CP, CP_SP):
super().__init__(lac_torque, CP, CP_SP)
def __init__(self, lac_torque, CP, CP_SP, CI):
super().__init__(lac_torque, CP, CP_SP, CI)
self.params = Params()
self.enabled = self.params.get_bool("NeuralNetworkLateralControl")
self.has_nn_model = CP_SP.neuralNetworkLateralControl.model.path != MOCK_MODEL_PATH
@@ -57,14 +59,44 @@ class NeuralNetworkLateralControl(LatControlTorqueExtBase):
self.error_deque = deque(maxlen=history_check_frames[0])
self.past_future_len = len(self.past_times) + len(self.nn_future_times)
@property
def _nnlc_enabled(self):
return self.enabled and self.model_valid and self.has_nn_model
def update_limits(self):
if not self._nnlc_enabled:
return
self._pid.set_limits(self.lac_torque.steer_max, -self.lac_torque.steer_max)
def update_lateral_lag(self, lag):
super().update_lateral_lag(lag)
self.nn_future_times = [t + self.desired_lat_jerk_time for t in self.future_times]
def update_feedforward_torque_space(self, CS):
torque_from_setpoint = self.torque_from_lateral_accel_in_torque_space(LatControlInputs(self._setpoint, self._roll_compensation, CS.vEgo, CS.aEgo),
self.torque_params, gravity_adjusted=False)
torque_from_measurement = self.torque_from_lateral_accel_in_torque_space(LatControlInputs(self._measurement, self._roll_compensation, CS.vEgo, CS.aEgo),
self.torque_params, gravity_adjusted=False)
self._pid_log.error = float(torque_from_setpoint - torque_from_measurement)
self._ff = self.torque_from_lateral_accel_in_torque_space(LatControlInputs(self._gravity_adjusted_lateral_accel, self._roll_compensation,
CS.vEgo, CS.aEgo), self.torque_params, gravity_adjusted=True)
self._ff += get_friction_in_torque_space(self._desired_lateral_accel - self._actual_lateral_accel, self._lateral_accel_deadzone,
FRICTION_THRESHOLD, self.torque_params)
def update_output_torque(self, CS):
freeze_integrator = self._steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
self._output_torque = self._pid.update(self._pid_log.error,
feedforward=self._ff,
speed=CS.vEgo,
freeze_integrator=freeze_integrator)
def update_neural_network_feedforward(self, CS, params, calibrated_pose) -> None:
if not self.enabled or not self.model_valid or not self.has_nn_model:
if not self._nnlc_enabled:
return
self.update_feedforward_torque_space(CS)
low_speed_factor = float(np.interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y)) ** 2
self._setpoint = self._desired_lateral_accel + low_speed_factor * self._desired_curvature
self._measurement = self._actual_lateral_accel + low_speed_factor * self._actual_curvature
@@ -128,3 +160,5 @@ class NeuralNetworkLateralControl(LatControlTorqueExtBase):
# apply friction override for cars with low NN friction response
if self.model.friction_override:
self._pid_log.error += get_friction(friction_input, self._lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params)
self.update_output_torque(CS)

View File

@@ -3,6 +3,7 @@ from parameterized import parameterized
from cereal import car, log, messaging
from opendbc.car.car_helpers import interfaces
from opendbc.car.gm.values import CAR as GM
from opendbc.car.honda.values import CAR as HONDA
from opendbc.car.hyundai.values import CAR as HYUNDAI
from opendbc.car.toyota.values import CAR as TOYOTA
@@ -41,7 +42,7 @@ def generate_modelV2():
class TestNeuralNetworkLateralControl:
@parameterized.expand([HONDA.HONDA_CIVIC, TOYOTA.TOYOTA_RAV4, HYUNDAI.HYUNDAI_SANTA_CRUZ_1ST_GEN])
@parameterized.expand([HONDA.HONDA_CIVIC, TOYOTA.TOYOTA_RAV4, HYUNDAI.HYUNDAI_SANTA_CRUZ_1ST_GEN, GM.CHEVROLET_BOLT_EUV])
def test_saturation(self, car_name):
params = Params()
params.put_bool("NeuralNetworkLateralControl", True)
@@ -57,6 +58,7 @@ class TestNeuralNetworkLateralControl:
VM = VehicleModel(CP)
controller = LatControlTorque(CP.as_reader(), CP_SP.as_reader(), CI)
torque_params = CP.lateralTuning.torque
CS = car.CarState.new_message()
CS.vEgo = 30
@@ -77,17 +79,23 @@ class TestNeuralNetworkLateralControl:
for _ in range(1000):
controller.extension.update_model_v2(model_v2)
controller.extension.update_lateral_lag(test_lag)
controller.update_live_torque_params(torque_params.latAccelFactor, torque_params.latAccelOffset, torque_params.friction)
controller.extension.update_limits()
_, _, lac_log = controller.update(True, CS, VM, params, False, 0, pose, True)
assert lac_log.saturated
for _ in range(1000):
controller.extension.update_model_v2(model_v2)
controller.extension.update_lateral_lag(test_lag)
controller.update_live_torque_params(torque_params.latAccelFactor, torque_params.latAccelOffset, torque_params.friction)
controller.extension.update_limits()
_, _, lac_log = controller.update(True, CS, VM, params, False, 0, pose, False)
assert not lac_log.saturated
for _ in range(1000):
controller.extension.update_model_v2(model_v2)
controller.extension.update_lateral_lag(test_lag)
controller.update_live_torque_params(torque_params.latAccelFactor, torque_params.latAccelOffset, torque_params.friction)
controller.extension.update_limits()
_, _, lac_log = controller.update(True, CS, VM, params, False, 1, pose, False)
assert lac_log.saturated

View File

@@ -1,19 +0,0 @@
from cereal import custom
DEBUG = True
PARAMS_UPDATE_PERIOD = 2. # secs. Time between parameter updates.
TEMP_INACTIVE_GUARD_PERIOD = 1. # secs. Time to wait after activation before considering temp deactivation signal.
# Lookup table for speed limit percent offset depending on speed.
LIMIT_PERC_OFFSET_V = [0.1, 0.05, 0.038] # 55, 105, 135 km/h
LIMIT_PERC_OFFSET_BP = [13.9, 27.8, 36.1] # 50, 100, 130 km/h
# Constants for Limit controllers.
LIMIT_ADAPT_ACC = -1. # m/s^2 Ideal acceleration for the adapting (braking) phase when approaching speed limits.
LIMIT_MIN_ACC = -1.5 # m/s^2 Maximum deceleration allowed for limit controllers to provide.
LIMIT_MAX_ACC = 1.0 # m/s^2 Maximum acceleration allowed for limit controllers to provide while active.
LIMIT_MIN_SPEED = 8.33 # m/s, Minimum speed limit to provide as solution on limit controllers.
LIMIT_SPEED_OFFSET_TH = -1. # m/s Maximum offset between speed limit and current speed for adapting state.
LIMIT_MAX_MAP_DATA_AGE = 10. # s Maximum time to hold to map data, then consider it invalid inside limits controllers.
SpeedLimitControlState = custom.LongitudinalPlanSP.SpeedLimitControlState

View File

@@ -1,25 +0,0 @@
from enum import IntEnum
class Source(IntEnum):
none = 0
car_state = 1
map_data = 2
class Policy(IntEnum):
map_data_only = 0
car_state_only = 1
map_data_priority = 2
car_state_priority = 3
combined = 4
class Engage(IntEnum):
auto = 0
class OffsetType(IntEnum):
default = 0
fixed = 1
percentage = 2

View File

@@ -1,19 +0,0 @@
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit_controller import DEBUG, SpeedLimitControlState
from openpilot.common.swaglog import cloudlog
def debug(msg):
if not DEBUG:
return
cloudlog.debug(msg)
def description_for_state(speed_limit_control_state):
if speed_limit_control_state == SpeedLimitControlState.inactive:
return 'INACTIVE'
if speed_limit_control_state == SpeedLimitControlState.tempInactive:
return 'TEMP_INACTIVE'
if speed_limit_control_state == SpeedLimitControlState.adapting:
return 'ADAPTING'
if speed_limit_control_state == SpeedLimitControlState.active:
return 'ACTIVE'

View File

@@ -1,284 +0,0 @@
import numpy as np
import time
from cereal import messaging, custom
from openpilot.common.constants import CV
from openpilot.common.params import Params
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit_controller import LIMIT_PERC_OFFSET_BP, LIMIT_PERC_OFFSET_V, \
PARAMS_UPDATE_PERIOD, TEMP_INACTIVE_GUARD_PERIOD, LIMIT_SPEED_OFFSET_TH, SpeedLimitControlState
from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit_controller.common import Source, Policy, Engage, OffsetType
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit_controller.helpers import description_for_state, debug
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit_controller.speed_limit_resolver import SpeedLimitResolver
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
from openpilot.selfdrive.modeld.constants import ModelConstants
EventNameSP = custom.OnroadEventSP.EventName
ACTIVE_STATES = (SpeedLimitControlState.active, SpeedLimitControlState.adapting)
ENABLED_STATES = (SpeedLimitControlState.preActive, SpeedLimitControlState.tempInactive, *ACTIVE_STATES)
class SpeedLimitController:
_speed_limit: float
_distance: float
_source: Source
_v_ego: float
_a_ego: float
_v_offset: float
def __init__(self, CP):
self.params = Params()
self._CP = CP
self._policy = self.params.get("SpeedLimitControlPolicy", return_default=True)
self._resolver = SpeedLimitResolver(self._policy)
self._last_params_update = 0.0
self._last_op_engaged_time = 0.0
self._is_metric = self.params.get_bool("IsMetric")
self._enabled = self.params.get_bool("SpeedLimitControl")
self._op_engaged = False
self._op_engaged_prev = False
self._v_ego = 0.
self._a_ego = 0.
self._v_offset = 0.
self._v_cruise_setpoint = 0.
self._v_cruise_setpoint_prev = 0.
self._v_cruise_setpoint_changed = False
self._speed_limit = 0.
self._speed_limit_prev = 0.
self._speed_limit_changed = False
self._distance = 0.
self._source = Source.none
self._state = SpeedLimitControlState.inactive
self._state_prev = SpeedLimitControlState.inactive
self._gas_pressed = False
self._pcm_cruise_op_long = CP.openpilotLongitudinalControl and CP.pcmCruise
self._offset_type = OffsetType(self.params.get("SpeedLimitWarningValueOffset", return_default=True))
self._offset_value = self.params.get("SpeedLimitValueOffset", return_default=True)
self._warning_type = self.params.get("SpeedLimitWarningType", return_default=True)
self._warning_offset_type = OffsetType(self.params.get("SpeedLimitWarningOffsetType", return_default=True))
self._warning_offset_value = self.params.get("SpeedLimitWarningValueOffset", return_default=True)
self._engage_type = self._read_engage_type_param()
self._current_time = 0.
self._v_cruise_rounded = 0.
self._v_cruise_prev_rounded = 0.
self._speed_limit_offsetted_rounded = 0.
self._speed_limit_warning_offsetted_rounded = 0.
self._speed_factor = CV.MS_TO_KPH if self._is_metric else CV.MS_TO_MPH
# Mapping functions to state transitions
self.state_transition_strategy = {
# Transition functions for each state
SpeedLimitControlState.inactive: self.transition_state_from_inactive,
SpeedLimitControlState.tempInactive: self.transition_state_from_temp_inactive,
SpeedLimitControlState.adapting: self.transition_state_from_adapting,
SpeedLimitControlState.active: self.transition_state_from_active,
SpeedLimitControlState.preActive: self.transition_state_from_pre_active,
}
# FIXME-SP: unused?
# Solution functions mapped to respective states
self.acceleration_solutions = {
# Solution functions for each state
SpeedLimitControlState.tempInactive: self.get_current_acceleration_as_target,
SpeedLimitControlState.inactive: self.get_current_acceleration_as_target,
SpeedLimitControlState.adapting: self.get_adapting_state_target_acceleration,
SpeedLimitControlState.active: self.get_active_state_target_acceleration,
SpeedLimitControlState.preActive: self.get_current_acceleration_as_target,
}
@property
def state(self) -> SpeedLimitControlState:
return self._state
@state.setter
def state(self, value) -> None:
if value != self._state:
debug(f'Speed Limit Controller state: {description_for_state(value)}')
if value == SpeedLimitControlState.tempInactive:
# Reset previous speed limit to current value as to prevent going out of tempInactive in
# a single cycle when the speed limit changes at the same time the user has temporarily deactivated it.
self._speed_limit_prev = self._speed_limit
self._state = value
@property
def is_enabled(self) -> bool:
return self.state in ENABLED_STATES and self._enabled
@property
def is_active(self) -> bool:
return self.state in ACTIVE_STATES and self._enabled
@property
def speed_limit_offseted(self) -> float:
return self._speed_limit + self.speed_limit_offset
@property
def speed_limit_offset(self) -> float:
return self._get_offset(self._offset_type, self._offset_value)
@property
def speed_limit_warning_offset(self) -> float:
return self._get_offset(self._warning_offset_type, self._warning_offset_value)
@property
def speed_limit(self) -> float:
return self._speed_limit
@property
def distance(self) -> float:
return self._distance
@property
def source(self) -> Source:
return self._source
def _get_offset(self, offset_type: OffsetType, offset_value: int) -> float:
if offset_type == OffsetType.default:
return float(np.interp(self._speed_limit, LIMIT_PERC_OFFSET_BP, LIMIT_PERC_OFFSET_V) * self._speed_limit)
elif offset_type == OffsetType.fixed:
return offset_value * (CV.KPH_TO_MS if self._is_metric else CV.MPH_TO_MS)
elif offset_type == OffsetType.percentage:
return offset_value * 0.01 * self._speed_limit
else:
raise NotImplementedError("Offset not supported")
def _update_v_cruise_setpoint_prev(self) -> None:
self._v_cruise_setpoint_prev = self._v_cruise_setpoint
def _update_params(self) -> None:
if self._current_time > self._last_params_update + PARAMS_UPDATE_PERIOD:
self._enabled = self.params.get_bool("SpeedLimitControl")
self._offset_type = OffsetType(self.params.get("SpeedLimitWarningValueOffset", return_default=True))
self._offset_value = self.params.get("SpeedLimitValueOffset", return_default=True)
self._warning_type = self.params.get("SpeedLimitWarningType", return_default=True)
self._warning_offset_type = OffsetType(self.params.get("SpeedLimitWarningOffsetType", return_default=True))
self._warning_offset_value = self.params.get("SpeedLimitWarningValueOffset", return_default=True)
self._policy = Policy(self.params.get("SpeedLimitControlPolicy", return_default=True))
self._is_metric = self.params.get_bool("IsMetric")
self._speed_factor = CV.MS_TO_KPH if self._is_metric else CV.MS_TO_MPH
self._resolver.change_policy(self._policy)
self._engage_type = self._read_engage_type_param()
self._last_params_update = self._current_time
def _read_engage_type_param(self) -> Engage:
if self._pcm_cruise_op_long:
return Engage.auto
return Engage.auto
def _update_calculations(self) -> None:
# Update current velocity offset (error)
self._v_offset = self.speed_limit_offseted - self._v_ego
# Track the time op becomes active to prevent going to tempInactive right away after
# op enabling since controlsd will change the cruise speed every time on enabling and this will
# cause a temp inactive transition if the controller is updated before controlsd sets actual cruise
# speed.
if not self._op_engaged_prev and self._op_engaged:
self._last_op_engaged_time = self._current_time
# Update change tracking variables
self._speed_limit_changed = self._speed_limit != self._speed_limit_prev
self._v_cruise_setpoint_changed = self._v_cruise_setpoint != self._v_cruise_setpoint_prev
self._speed_limit_prev = self._speed_limit
self._update_v_cruise_setpoint_prev() # always for Engage.auto
self._op_engaged_prev = self._op_engaged
self._v_cruise_rounded = int(round(self._v_cruise_setpoint * self._speed_factor))
self._v_cruise_prev_rounded = int(round(self._v_cruise_setpoint_prev * self._speed_factor))
self._speed_limit_offsetted_rounded = 0 if self._speed_limit == 0 else int(round((self._speed_limit + self.speed_limit_offset) * self._speed_factor))
self._speed_limit_warning_offsetted_rounded = 0 if self._speed_limit == 0 else \
int(round((self._speed_limit + self.speed_limit_warning_offset) * self._speed_factor))
def transition_state_from_inactive(self) -> None:
""" Make state transition from inactive state """
if self._engage_type == Engage.auto:
if self._v_offset < LIMIT_SPEED_OFFSET_TH:
self.state = SpeedLimitControlState.adapting
else:
self.state = SpeedLimitControlState.active
def transition_state_from_temp_inactive(self) -> None:
""" Make state transition from temporary inactive state """
if self._speed_limit_changed:
if self._engage_type == Engage.auto:
self.state = SpeedLimitControlState.inactive
def transition_state_from_pre_active(self) -> None:
""" Make state transition from preActive state """
def transition_state_from_adapting(self) -> None:
""" Make state transition from adapting state """
if self._v_offset >= LIMIT_SPEED_OFFSET_TH:
self.state = SpeedLimitControlState.active
def transition_state_from_active(self) -> None:
""" Make state transition from active state """
if self._engage_type == Engage.auto:
if self._v_offset < LIMIT_SPEED_OFFSET_TH:
self.state = SpeedLimitControlState.adapting
def _state_transition(self) -> None:
self._state_prev = self._state
# In any case, if op is disabled, or speed limit control is disabled or no valid speed limit
# or gas is pressed, deactivate.
if not self._op_engaged or not self._enabled or self._speed_limit == 0:
self.state = SpeedLimitControlState.inactive
return
# In any case, we deactivate the speed limit controller temporarily if the user changes the cruise speed.
# Ignore if a minimum amount of time has not passed since activation. This is to prevent temp inactivations
# due to controlsd logic changing cruise setpoint when going active.
if self._engage_type == Engage.auto and self._v_cruise_setpoint_changed and \
self._current_time > (self._last_op_engaged_time + TEMP_INACTIVE_GUARD_PERIOD):
self.state = SpeedLimitControlState.tempInactive
return
self.state_transition_strategy[self.state]()
self._update_v_cruise_setpoint_prev() # always for Engage.auto
def get_current_acceleration_as_target(self) -> float:
""" When state is inactive or tempInactive, preserve current acceleration """
return self._a_ego
def get_adapting_state_target_acceleration(self) -> float:
""" In adapting state, calculate target acceleration based on speed limit and current velocity """
if self.distance > 0:
return (self.speed_limit_offseted ** 2 - self._v_ego ** 2) / (2. * self.distance)
return self._v_offset / float(ModelConstants.T_IDXS[CONTROL_N])
def get_active_state_target_acceleration(self) -> float:
""" In active state, aim to keep speed constant around control time horizon """
return self._v_offset / float(ModelConstants.T_IDXS[CONTROL_N])
def _update_events(self, events_sp: EventsSP) -> None:
if self.is_active:
if self._engage_type == Engage.auto:
if self._state_prev not in ACTIVE_STATES:
events_sp.add(EventNameSP.speedLimitActive)
elif self._speed_limit_changed != 0:
events_sp.add(EventNameSP.speedLimitValueChange)
def update(self, sm: messaging.SubMaster, v_ego: float, a_ego: float, v_cruise_setpoint: float, events_sp: EventsSP) -> None:
_car_state = sm['carState']
self._op_engaged = sm['carControl'].longActive
self._v_ego = v_ego
self._a_ego = a_ego
self._v_cruise_setpoint = v_cruise_setpoint if not np.isnan(v_cruise_setpoint) else 0.0
self._gas_pressed = _car_state.gasPressed
self._current_time = time.monotonic()
self._speed_limit, self._distance, self._source = self._resolver.resolve(v_ego, self.speed_limit, sm)
self._update_params()
self._update_calculations()
self._state_transition()
self._update_events(events_sp)

View File

@@ -1,119 +0,0 @@
import time
import numpy as np
from cereal import messaging
from openpilot.common.gps import get_gps_location_service
from openpilot.common.params import Params
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit_controller import LIMIT_MAX_MAP_DATA_AGE, LIMIT_ADAPT_ACC
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit_controller.common import Source, Policy
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit_controller.helpers import debug
class SpeedLimitResolver:
_limit_solutions: dict[Source, float] # Store for speed limit solutions from different sources
_distance_solutions: dict[Source, float] # Store for distance to current speed limit start for different sources
_v_ego: float
_current_speed_limit: float
def __init__(self, policy: Policy):
self._gps_location_service = get_gps_location_service(Params())
self._limit_solutions = {}
self._distance_solutions = {}
self._policy = policy
self._policy_to_sources_map = {
Policy.car_state_only: [Source.car_state],
Policy.car_state_priority: [Source.car_state, Source.map_data],
Policy.map_data_priority: [Source.map_data, Source.car_state],
Policy.map_data_only: [Source.map_data],
Policy.combined: [Source.car_state, Source.map_data],
}
for source in Source:
self._reset_limit_sources(source)
def change_policy(self, policy: Policy) -> None:
self._policy = policy
def _reset_limit_sources(self, source: Source) -> None:
self._limit_solutions[source] = 0.
self._distance_solutions[source] = 0.
def resolve(self, v_ego: float, current_speed_limit: float, sm: messaging.SubMaster) -> tuple[float, float, Source]:
self._v_ego = v_ego
self._current_speed_limit = current_speed_limit
self._resolve_limit_sources(sm)
return self._consolidate()
def _resolve_limit_sources(self, sm: messaging.SubMaster) -> None:
"""Get limit solutions from each data source"""
self._get_from_car_state(sm)
self._get_from_map_data(sm)
def _get_from_car_state(self, sm: messaging.SubMaster) -> None:
self._reset_limit_sources(Source.car_state)
self._limit_solutions[Source.car_state] = sm['carStateSP'].speedLimit
self._distance_solutions[Source.car_state] = 0.
def _get_from_map_data(self, sm: messaging.SubMaster) -> None:
self._reset_limit_sources(Source.map_data)
self._process_map_data(sm)
def _process_map_data(self, sm: messaging.SubMaster) -> None:
gps_data = sm[self._gps_location_service]
map_data = sm['liveMapDataSP']
gps_fix_age = time.monotonic() - gps_data.unixTimestampMillis * 1e-3
if gps_fix_age > LIMIT_MAX_MAP_DATA_AGE:
debug(f'SL: Ignoring map data as is too old. Age: {gps_fix_age}')
return
speed_limit = map_data.speedLimit if map_data.speedLimitValid else 0.
next_speed_limit = map_data.speedLimitAhead if map_data.speedLimitAheadValid else 0.
self._calculate_map_data_limits(sm, speed_limit, next_speed_limit)
def _calculate_map_data_limits(self, sm: messaging.SubMaster, speed_limit: float, next_speed_limit: float) -> None:
gps_data = sm[self._gps_location_service]
map_data = sm['liveMapDataSP']
distance_since_fix = self._v_ego * (time.monotonic() - gps_data.unixTimestampMillis * 1e-3)
distance_to_speed_limit_ahead = max(0., map_data.speedLimitAheadDistance - distance_since_fix)
self._limit_solutions[Source.map_data] = speed_limit
self._distance_solutions[Source.map_data] = 0.
if 0. < next_speed_limit < self._v_ego:
adapt_time = (next_speed_limit - self._v_ego) / LIMIT_ADAPT_ACC
adapt_distance = self._v_ego * adapt_time + 0.5 * LIMIT_ADAPT_ACC * adapt_time ** 2
if distance_to_speed_limit_ahead <= adapt_distance:
self._limit_solutions[Source.map_data] = next_speed_limit
self._distance_solutions[Source.map_data] = distance_to_speed_limit_ahead
def _consolidate(self) -> tuple[float, float, Source]:
source = self._get_source_solution_according_to_policy()
self.speed_limit = self._limit_solutions[source] if source else 0.
self.distance = self._distance_solutions[source] if source else 0.
self.source = source or Source.none
debug(f'SL: *** Speed Limit set: {self.speed_limit}, distance: {self.distance}, source: {self.source}')
return self.speed_limit, self.distance, self.source
def _get_source_solution_according_to_policy(self) -> Source | None:
sources_for_policy = self._policy_to_sources_map[self._policy]
if self._policy != Policy.combined:
# They are ordered in the order of preference, so we pick the first that's non zero
for source in sources_for_policy:
if self._limit_solutions[source] > 0.:
return Source(source)
limits = np.array([self._limit_solutions[source] for source in sources_for_policy], dtype=float)
sources = np.array([source.value for source in sources_for_policy], dtype=int)
if len(limits) > 0:
min_idx = np.argmin(limits)
return Source(sources[min_idx])
return None

View File

@@ -1,58 +0,0 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from cereal import custom
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
EventNameSP = custom.OnroadEventSP.EventName
State = custom.LongitudinalPlanSP.SpeedLimitControlState
ACTIVE_STATES = (State.active, State.adapting)
ENABLED_STATES = (State.preActive, State.tempInactive, *ACTIVE_STATES)
class StateMachine:
def __init__(self):
self.state = State.inactive
def update(self, events_sp: EventsSP) -> tuple[bool, bool]:
# INACTIVE
if self.state == State.inactive:
if events_sp.has(EventNameSP.speedLimitAdapting):
self.state = State.adapting
elif events_sp.has(EventNameSP.speedLimitActive):
self.state = State.active
# ACTIVE
elif self.state == State.active:
if events_sp.has(EventNameSP.speedLimitDisable):
self.state = State.inactive
elif events_sp.has(EventNameSP.speedLimitUserCancel):
self.state = State.tempInactive
elif events_sp.has(EventNameSP.speedLimitAdapting):
self.state = State.adapting
# ADAPTING
elif self.state == State.adapting:
if events_sp.has(EventNameSP.speedLimitDisable):
self.state = State.inactive
elif events_sp.has(EventNameSP.speedLimitUserCancel):
self.state = State.tempInactive
elif events_sp.has(EventNameSP.speedLimitReached):
self.state = State.active
# TEMP INACTIVE
elif self.state == State.tempInactive:
if events_sp.has(EventNameSP.speedLimitDisable):
self.state = State.inactive
elif events_sp.has(EventNameSP.speedLimitValueChange):
# When speed limit changes, reactivate
self.state = State.inactive
enabled = self.state in ENABLED_STATES
active = self.state in ACTIVE_STATES
return enabled, active

View File

@@ -1,130 +0,0 @@
import random
import time
import pytest
from pytest_mock import MockerFixture
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit_controller import LIMIT_MAX_MAP_DATA_AGE
# from selfdrive.controls.lib.speed_limit_controller_tbd import SpeedLimitResolver as OriginalSpeedLimitResolver
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit_controller.speed_limit_resolver import SpeedLimitResolver as RefactoredSpeedLimitResolver
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit_controller.common import Source, Policy
def create_mock(properties, mocker: MockerFixture):
mock = mocker.MagicMock()
for _property, value in properties.items():
setattr(mock, _property, value)
return mock
def setup_sm_mock(mocker: MockerFixture):
cruise_speed_limit = random.uniform(0, 120)
live_map_data_limit = random.uniform(0, 120)
car_state = create_mock({
'gasPressed': False,
'brakePressed': False,
'standstill': False,
}, mocker)
car_state_sp = create_mock({
'speedLimit': cruise_speed_limit,
}, mocker)
live_map_data = create_mock({
'speedLimit': live_map_data_limit,
'speedLimitValid': True,
'speedLimitAhead': 0.,
'speedLimitAheadValid': 0.,
'speedLimitAheadDistance': 0.,
}, mocker)
gps_data = create_mock({
'unixTimestampMillis': time.monotonic() * 1e3,
}, mocker)
sm_mock = mocker.MagicMock()
sm_mock.__getitem__.side_effect = lambda key: {
'carState': car_state,
'liveMapDataSP': live_map_data,
'carStateSP': car_state_sp,
'gpsLocation': gps_data,
}[key]
return sm_mock
parametrized_policies = pytest.mark.parametrize(
"policy, sm_key, function_key", [
(Policy.car_state_only, 'carStateSP', 'car_state'),
(Policy.car_state_priority, 'carStateSP', 'car_state'),
(Policy.map_data_only, 'liveMapDataSP', 'map_data'),
(Policy.map_data_priority, 'liveMapDataSP', 'map_data'),
],
ids=lambda val: val.name if hasattr(val, 'name') else str(val)
)
@pytest.mark.parametrize("resolver_class", [RefactoredSpeedLimitResolver], ids=["Refactored"])
class TestSpeedLimitResolverValidation:
@pytest.mark.parametrize("policy", list(Policy), ids=lambda policy: policy.name)
def test_initial_state(self, resolver_class, policy):
resolver = resolver_class(policy)
for source in Source:
if source in resolver._limit_solutions:
assert resolver._limit_solutions[source] == 0.
assert resolver._distance_solutions[source] == 0.
@parametrized_policies
def test_resolver(self, resolver_class, policy, sm_key, function_key, mocker: MockerFixture):
resolver = resolver_class(policy)
sm_mock = setup_sm_mock(mocker)
source_speed_limit = sm_mock[sm_key].speedLimit
# Assert the resolver
speed_limit, _, source = resolver.resolve(source_speed_limit, 0, sm_mock)
assert speed_limit == source_speed_limit
assert source == Source[function_key]
def test_resolver_combined(self, resolver_class, mocker: MockerFixture):
resolver = resolver_class(Policy.combined)
sm_mock = setup_sm_mock(mocker)
socket_to_source = {'carStateSP': Source.car_state, 'liveMapDataSP': Source.map_data}
minimum_key, minimum_speed_limit = min(
((key, sm_mock[key].speedLimit) for key in
socket_to_source.keys()), key=lambda x: x[1])
# Assert the resolver
speed_limit, _, source = resolver.resolve(minimum_speed_limit, 0, sm_mock)
assert speed_limit == minimum_speed_limit
assert source == socket_to_source[minimum_key]
@parametrized_policies
def test_parser(self, resolver_class, policy, sm_key, function_key, mocker: MockerFixture):
resolver = resolver_class(policy)
sm_mock = setup_sm_mock(mocker)
source_speed_limit = sm_mock[sm_key].speedLimit
# Assert the parsing
speed_limit, _, source = resolver.resolve(source_speed_limit, 0, sm_mock)
assert resolver._limit_solutions[Source[function_key]] == source_speed_limit
assert resolver._distance_solutions[Source[function_key]] == 0.
@pytest.mark.parametrize("policy", list(Policy), ids=lambda policy: policy.name)
def test_resolve_interaction_in_update(self, resolver_class, policy, mocker: MockerFixture):
v_ego = 50
resolver = resolver_class(policy)
sm_mock = setup_sm_mock(mocker)
_speed_limit, _distance, _source = resolver.resolve(v_ego, 0, sm_mock)
# After resolution
assert _speed_limit is not None
assert _distance is not None
assert _source is not None
@pytest.mark.parametrize("policy", list(Policy), ids=lambda policy: policy.name)
def test_old_map_data_ignored(self, resolver_class, policy, mocker: MockerFixture):
resolver = resolver_class(policy)
sm_mock = mocker.MagicMock()
sm_mock['gpsLocation'].unixTimestampMillis = (time.monotonic() - 2 * LIMIT_MAX_MAP_DATA_AGE) * 1e3
resolver._get_from_map_data(sm_mock)
assert resolver._limit_solutions[Source.map_data] == 0.
assert resolver._distance_solutions[Source.map_data] == 0.

View File

@@ -1,6 +1,4 @@
import cereal.messaging as messaging
from cereal import log, car, custom
from openpilot.common.constants import CV
from openpilot.sunnypilot.selfdrive.selfdrived.events_base import EventsBase, Priority, ET, Alert, \
NoEntryAlert, ImmediateDisableAlert, EngagementAlert, NormalPermanentAlert, AlertCallbackType, wrong_car_mode_alert
@@ -16,17 +14,6 @@ EventNameSP = custom.OnroadEventSP.EventName
EVENT_NAME_SP = {v: k for k, v in EventNameSP.schema.enumerants.items()}
def speed_limit_adjust_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
speedLimit = sm['longitudinalPlanSP'].slc.speedLimit
speed = round(speedLimit * (CV.MS_TO_KPH if metric else CV.MS_TO_MPH))
message = f'Adjusting to {speed} {"km/h" if metric else "mph"} speed limit'
return Alert(
message,
"",
AlertStatus.normal, AlertSize.small,
Priority.LOW, VisualAlert.none, AudibleAlert.none, 4.)
class EventsSP(EventsBase):
def __init__(self):
super().__init__()
@@ -145,18 +132,6 @@ EVENTS_SP: dict[int, dict[str, Alert | AlertCallbackType]] = {
EventNameSP.pedalPressedAlertOnly: {
ET.WARNING: NoEntryAlert("Pedal Pressed")
},
EventNameSP.speedLimitActive: {
ET.WARNING: Alert(
"Set speed changed to match posted speed limit",
"",
AlertStatus.normal, AlertSize.small,
Priority.LOW, VisualAlert.none, AudibleAlert.none, 3.),
},
EventNameSP.speedLimitValueChange: {
ET.WARNING: speed_limit_adjust_alert,
},
}
}

View File

@@ -0,0 +1,3 @@
# C3 specific hardware code
`c3` is known as `tici` and comma three by comma. Not to confuse it with `c3x` which is known as `tizi`.

View File

@@ -0,0 +1,84 @@
[
{
"name": "xbl",
"url": "https://commadist.azureedge.net/agnosupdate/xbl-effa23294138e2297b85a5b482a885184c437b5ab25d74f2a62d4fce4e68f63b.img.xz",
"hash": "effa23294138e2297b85a5b482a885184c437b5ab25d74f2a62d4fce4e68f63b",
"hash_raw": "effa23294138e2297b85a5b482a885184c437b5ab25d74f2a62d4fce4e68f63b",
"size": 3282256,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "ed61a650bea0c56652dd0fc68465d8fc722a4e6489dc8f257630c42c6adcdc89"
},
{
"name": "xbl_config",
"url": "https://commadist.azureedge.net/agnosupdate/xbl_config-63d019efed684601f145ef37628e62c8da73f5053a8e51d7de09e72b8b11f97c.img.xz",
"hash": "63d019efed684601f145ef37628e62c8da73f5053a8e51d7de09e72b8b11f97c",
"hash_raw": "63d019efed684601f145ef37628e62c8da73f5053a8e51d7de09e72b8b11f97c",
"size": 98124,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "b12801ffaa81e58e3cef914488d3b447e35483ba549b28c6cd9deb4814c3265f"
},
{
"name": "abl",
"url": "https://commadist.azureedge.net/agnosupdate/abl-32a2174b5f764e95dfc54cf358ba01752943b1b3b90e626149c3da7d5f1830b6.img.xz",
"hash": "32a2174b5f764e95dfc54cf358ba01752943b1b3b90e626149c3da7d5f1830b6",
"hash_raw": "32a2174b5f764e95dfc54cf358ba01752943b1b3b90e626149c3da7d5f1830b6",
"size": 274432,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "32a2174b5f764e95dfc54cf358ba01752943b1b3b90e626149c3da7d5f1830b6"
},
{
"name": "aop",
"url": "https://commadist.azureedge.net/agnosupdate/aop-21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9.img.xz",
"hash": "21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9",
"hash_raw": "21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9",
"size": 184364,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "c1be2f4aac5b3af49b904b027faec418d05efd7bd5144eb4fdfcba602bcf2180"
},
{
"name": "devcfg",
"url": "https://commadist.azureedge.net/agnosupdate/devcfg-d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620.img.xz",
"hash": "d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620",
"hash_raw": "d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620",
"size": 40336,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "17b229668b20305ff8fa3cd5f94716a3aaa1e5bf9d1c24117eff7f2f81ae719f"
},
{
"name": "boot",
"url": "https://commadist.azureedge.net/agnosupdate/boot-0191529aa97d90d1fa04b472d80230b777606459e1e1e9e2323c9519839827b4.img.xz",
"hash": "0191529aa97d90d1fa04b472d80230b777606459e1e1e9e2323c9519839827b4",
"hash_raw": "0191529aa97d90d1fa04b472d80230b777606459e1e1e9e2323c9519839827b4",
"size": 18515968,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "492ae27f569e8db457c79d0e358a7a6297d1a1c685c2b1ae6deba7315d3a6cb0"
},
{
"name": "system",
"url": "https://commadist.azureedge.net/agnosupdate/system-e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087.img.xz",
"hash": "1468d50b7ad0fda0f04074755d21e786e3b1b6ca5dd5b17eb2608202025e6126",
"hash_raw": "e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087",
"size": 5368709120,
"sparse": true,
"full_check": false,
"has_ab": true,
"ondevice_hash": "242aa5adad1c04e1398e00e2440d1babf962022eb12b89adf2e60ee3068946e7",
"alt": {
"hash": "e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087",
"url": "https://commadist.azureedge.net/agnosupdate/system-e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087.img",
"size": 5368709120
}
}
]

View File

@@ -0,0 +1,92 @@
#!/usr/bin/env bash
SP_C3_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
DIR="$( cd "$SP_C3_DIR/../../../.." >/dev/null 2>&1 && pwd )"
source "$SP_C3_DIR/launch_env.sh"
function agnos_init {
# TODO: move this to agnos
sudo rm -f /data/etc/NetworkManager/system-connections/*.nmmeta
# set success flag for current boot slot
sudo abctl --set_success
# TODO: do this without udev in AGNOS
# udev does this, but sometimes we startup faster
sudo chgrp gpu /dev/adsprpc-smd /dev/ion /dev/kgsl-3d0
sudo chmod 660 /dev/adsprpc-smd /dev/ion /dev/kgsl-3d0
if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then
AGNOS_PY="$DIR/system/hardware/tici/agnos.py"
MANIFEST="$SP_C3_DIR/agnos.json"
if $AGNOS_PY --verify $MANIFEST; then
sudo reboot
fi
$DIR/system/hardware/tici/updater $AGNOS_PY $MANIFEST
fi
}
function launch {
# Remove orphaned git lock if it exists on boot
[ -f "$DIR/.git/index.lock" ] && rm -f $DIR/.git/index.lock
# Check to see if there's a valid overlay-based update available. Conditions
# are as follows:
#
# 1. The DIR init file has to exist, with a newer modtime than anything in
# the DIR Git repo. This checks for local development work or the user
# switching branches/forks, which should not be overwritten.
# 2. The FINALIZED consistent file has to exist, indicating there's an update
# that completed successfully and synced to disk.
if [ -f "${DIR}/.overlay_init" ]; then
find ${DIR}/.git -newer ${DIR}/.overlay_init | grep -q '.' 2> /dev/null
if [ $? -eq 0 ]; then
echo "${DIR} has been modified, skipping overlay update installation"
else
if [ -f "${STAGING_ROOT}/finalized/.overlay_consistent" ]; then
if [ ! -d /data/safe_staging/old_openpilot ]; then
echo "Valid overlay update found, installing"
LAUNCHER_LOCATION="${BASH_SOURCE[0]}"
mv $DIR /data/safe_staging/old_openpilot
mv "${STAGING_ROOT}/finalized" $DIR
cd $DIR
echo "Restarting launch script ${LAUNCHER_LOCATION}"
unset AGNOS_VERSION
exec "${LAUNCHER_LOCATION}"
else
echo "openpilot backup found, not updating"
# TODO: restore backup? This means the updater didn't start after swapping
fi
fi
fi
fi
# handle pythonpath
ln -sfn $(pwd) /data/pythonpath
export PYTHONPATH="$PWD"
# hardware specific init
if [ -f /AGNOS ]; then
agnos_init
fi
# write tmux scrollback to a file
tmux capture-pane -pq -S-1000 > /tmp/launch_log
# start manager
cd $DIR/system/manager
if [ ! -f $DIR/prebuilt ]; then
./build.py
fi
./manager.py
# if broken, keep on screen error
while true; do sleep 1; done
}
launch

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
export OMP_NUM_THREADS=1
export MKL_NUM_THREADS=1
export NUMEXPR_NUM_THREADS=1
export OPENBLAS_NUM_THREADS=1
export VECLIB_MAXIMUM_THREADS=1
if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="12.8"
fi

View File

@@ -381,20 +381,22 @@ def setNavDestination(latitude: int = 0, longitude: int = 0, place_name: str = N
return {"success": 1}
def scan_dir(path: str, prefix: str) -> list[str]:
def scan_dir(path: str, prefix: str, base: str | None = None) -> list[str]:
if base is None:
base = path
files = []
# only walk directories that match the prefix
# (glob and friends traverse entire dir tree)
with os.scandir(path) as i:
for e in i:
rel_path = os.path.relpath(e.path, Paths.log_root())
rel_path = os.path.relpath(e.path, base)
if e.is_dir(follow_symlinks=False):
# add trailing slash
rel_path = os.path.join(rel_path, '')
# if prefix is a partial dir name, current dir will start with prefix
# if prefix is a partial file name, prefix with start with dir name
if rel_path.startswith(prefix) or prefix.startswith(rel_path):
files.extend(scan_dir(e.path, prefix))
files.extend(scan_dir(e.path, prefix, base))
else:
if rel_path.startswith(prefix):
files.append(rel_path)
@@ -402,7 +404,12 @@ def scan_dir(path: str, prefix: str) -> list[str]:
@dispatcher.add_method
def listDataDirectory(prefix='') -> list[str]:
return scan_dir(Paths.log_root(), prefix)
internal_files = scan_dir(Paths.log_root(), prefix, Paths.log_root())
try:
external_files = scan_dir(Paths.log_root_external(), prefix, Paths.log_root_external())
except FileNotFoundError:
external_files = []
return sorted(set(internal_files + external_files))
@dispatcher.add_method
@@ -427,8 +434,13 @@ def uploadFilesToUrls(files_data: list[UploadFileDict]) -> UploadFilesToUrlRespo
failed.append(file.fn)
continue
path = os.path.join(Paths.log_root(), file.fn)
if not os.path.exists(path) and not os.path.exists(strip_zst_extension(path)):
path_internal = os.path.join(Paths.log_root(), file.fn)
path_external = os.path.join(Paths.log_root_external(), file.fn)
if os.path.exists(path_internal) or os.path.exists(strip_zst_extension(path_internal)):
path = path_internal
elif os.path.exists(path_external) or os.path.exists(strip_zst_extension(path_external)):
path = path_external
else:
failed.append(file.fn)
continue

View File

@@ -20,6 +20,10 @@ class Paths:
else:
return '/data/media/0/realdata/'
@staticmethod
def log_root_external() -> str:
return '/mnt/external_realdata/'
@staticmethod
def swaglog_root() -> str:
if PC:

View File

@@ -9,21 +9,26 @@ STATS_DIR_FILE_LIMIT = 10000
STATS_SOCKET = "ipc:///tmp/stats"
STATS_FLUSH_TIME_S = 60
def get_available_percent(default: float) -> float:
PATH_DICT = {
"internal": Paths.log_root(),
"external": Paths.log_root_external()
}
def get_available_percent(default: float, path_type="internal") -> float:
try:
statvfs = os.statvfs(Paths.log_root())
statvfs = os.statvfs(PATH_DICT[path_type])
available_percent = 100.0 * statvfs.f_bavail / statvfs.f_blocks
except OSError:
except (OSError, KeyError):
available_percent = default
return available_percent
def get_available_bytes(default: int) -> int:
def get_available_bytes(default: int, path_type="internal") -> int:
try:
statvfs = os.statvfs(Paths.log_root())
statvfs = os.statvfs(PATH_DICT[path_type])
available_bytes = statvfs.f_bavail * statvfs.f_frsize
except OSError:
except (OSError, KeyError):
available_bytes = default
return available_bytes

View File

@@ -1,7 +1,9 @@
#!/usr/bin/env python3
import os
import time
import shutil
import threading
from pathlib import Path
from openpilot.system.hardware.hw import Paths
from openpilot.common.swaglog import cloudlog
from openpilot.system.loggerd.config import get_available_bytes, get_available_percent
@@ -61,6 +63,41 @@ def deleter_thread(exit_event: threading.Event):
if any(name.endswith(".lock") for name in os.listdir(delete_path)):
continue
if Path(Paths.log_root_external()).is_mount():
out_of_bytes_external = get_available_bytes(default=MIN_BYTES + 1, path_type="external") < MIN_BYTES
out_of_percent_external = get_available_percent(default=MIN_PERCENT + 1, path_type="external") < MIN_PERCENT
if out_of_percent_external or out_of_bytes_external:
dirs_external = listdir_by_creation(Paths.log_root_external())
# remove the earliest external directory we can
for delete_dir_external in sorted(dirs_external):
delete_path_external = os.path.join(Paths.log_root_external(), delete_dir_external)
try:
cloudlog.warning(f"deleting {delete_path_external}")
shutil.rmtree(delete_path_external)
break
except OSError:
cloudlog.exception(f"issue deleting {delete_path_external}")
# move directory from internal to external
path_external = os.path.join(Paths.log_root_external(), delete_dir)
try:
cloudlog.warning(f"moving {delete_path} to {path_external}")
start = time.monotonic()
shutil.move(delete_path, path_external)
cloudlog.warning(f"moved {delete_path} to {path_external} in {time.monotonic() - start:.2f}s")
break
except Exception:
cloudlog.error(f"issue moving {delete_path} to {path_external}")
try:
cloudlog.warning(f"deleting {delete_path}")
shutil.rmtree(delete_path)
break
except OSError:
cloudlog.exception(f"issue deleting {delete_path}")
continue
try:
cloudlog.info(f"deleting {delete_path}")
shutil.rmtree(delete_path)

6
uv.lock generated
View File

@@ -4832,11 +4832,11 @@ wheels = [
[[package]]
name = "typing-extensions"
version = "4.14.1"
version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" }
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" },
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]