mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-07-11 00:52:12 +08:00
Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c10956b546 | |||
| 1725f2500b | |||
| b51043770b | |||
| f37fc62ed2 | |||
| 623ef9f592 | |||
| 9d60846b70 | |||
| 8201f3edf4 | |||
| ddfb7420ca | |||
| a6eba52791 | |||
| c4d202c6bb | |||
| ebe56410d3 | |||
| a93e788401 | |||
| 3aa9ddd3c7 | |||
| 6d516a7704 | |||
| 4dbabf4e24 | |||
| 80679b74e6 | |||
| b92d717f2f | |||
| 7565dd2545 | |||
| 52dc4141c5 | |||
| 1e74000f79 | |||
| 3a6491e23a | |||
| 5c9ce2a042 | |||
| 3a4c74b67d | |||
| 4ceb1ecd19 | |||
| 921b51f56f | |||
| 6c1edca806 | |||
| bf6123c4ad | |||
| 8d06444bdd | |||
| 08efc252ec | |||
| 6142a52de7 | |||
| fb934247a2 | |||
| 897a2bcedc | |||
| f4c7b04682 | |||
| e5ac7d5b57 | |||
| e89d65b516 | |||
| ca23bb90cd | |||
| 80f21949a3 | |||
| 68c593db5f | |||
| 179da5d007 | |||
| 6de9526d4d | |||
| f09a3b32d1 | |||
| 4a094ef56f | |||
| 72f09ec9f5 | |||
| ff63d17723 | |||
| 977179f661 | |||
| 21715cdc6d | |||
| dde9c703f3 | |||
| 4bbbe3d2d1 | |||
| 037695af4a | |||
| 33849245d8 | |||
| 73ee0c022f | |||
| e7f7675458 | |||
| f123e7ed75 | |||
| 52669b6ad2 | |||
| 47ed90c6cf | |||
| 2451d70408 | |||
| 380f383e2e | |||
| 0fb4aafa35 | |||
| 36ff474bc8 | |||
| 2e0fa3f827 | |||
| dcca094ad8 | |||
| 433e7268f5 | |||
| 7c16e65347 | |||
| aa1b790708 | |||
| bbf37ae5c7 | |||
| b400312042 | |||
| e64be675e3 | |||
| 2393e0d27d | |||
| 97bad78553 | |||
| 58bc8e3b43 | |||
| 662877c6f3 | |||
| 9622b6f8bd | |||
| ddb19cc074 | |||
| 5c1f28591f | |||
| 3c58da5c84 | |||
| b920e2a998 | |||
| 5a94d818bb | |||
| 71b37cfb94 | |||
| a8b9350103 | |||
| c33e5b3209 | |||
| f66bf4b185 | |||
| b6c6a3ad19 | |||
| 87fae0c6f2 | |||
| f704d18a8b | |||
| 8ee99523f4 | |||
| 8c8b2c4488 | |||
| d90e41f08f | |||
| 600647d5e2 | |||
| 44ab494c41 | |||
| db6832762b | |||
| 3f883ad215 | |||
| df4f2955dc | |||
| a1ec8c6bfe | |||
| e1d2360b8c | |||
| b58552542d |
@@ -16,7 +16,7 @@ simulation:
|
||||
|
||||
ui:
|
||||
- changed-files:
|
||||
- any-glob-to-all-files: 'selfdrive/ui/**'
|
||||
- any-glob-to-all-files: '{selfdrive/ui/**,system/ui/**}'
|
||||
|
||||
tools:
|
||||
- changed-files:
|
||||
|
||||
@@ -12,7 +12,7 @@ inputs:
|
||||
required: true
|
||||
save:
|
||||
description: 'whether to save the cache'
|
||||
default: 'false'
|
||||
default: 'true'
|
||||
required: false
|
||||
outputs:
|
||||
cache-hit:
|
||||
|
||||
@@ -47,10 +47,8 @@ selfdrive/pandad/pandad
|
||||
cereal/services.h
|
||||
cereal/gen
|
||||
cereal/messaging/bridge
|
||||
selfdrive/logcatd/logcatd
|
||||
selfdrive/mapd/default_speeds_by_region.json
|
||||
system/proclogd/proclogd
|
||||
selfdrive/ui/translations/alerts_generated.h
|
||||
selfdrive/ui/translations/tmp
|
||||
selfdrive/test/longitudinal_maneuvers/out
|
||||
selfdrive/car/tests/cars_dump
|
||||
|
||||
@@ -4,6 +4,7 @@ Version 0.9.9 (2025-05-15)
|
||||
* New training architecture supervised by MLSIM
|
||||
* Steering actuator delay is now learned online
|
||||
* Tesla Model 3 and Y support thanks to lukasloetkolben!
|
||||
* Lexus RC 2023 support thanks to nelsonjchen!
|
||||
* Coming soon
|
||||
* New Honda models
|
||||
* Bigger vision model
|
||||
|
||||
+40
-18
@@ -39,20 +39,6 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
|
||||
sha256 @1 :Text;
|
||||
}
|
||||
|
||||
enum Type {
|
||||
drive @0;
|
||||
navigation @1;
|
||||
metadata @2;
|
||||
}
|
||||
|
||||
struct Model {
|
||||
fullName @0 :Text;
|
||||
fileName @1 :Text;
|
||||
downloadUri @2 :DownloadUri;
|
||||
downloadProgress @3 :DownloadProgress;
|
||||
type @4 :Type;
|
||||
}
|
||||
|
||||
enum DownloadStatus {
|
||||
notDownloading @0;
|
||||
downloading @1;
|
||||
@@ -67,6 +53,25 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
|
||||
eta @2 :UInt32;
|
||||
}
|
||||
|
||||
struct Artifact {
|
||||
fileName @0 :Text;
|
||||
downloadUri @1 :DownloadUri;
|
||||
downloadProgress @2 :DownloadProgress;
|
||||
}
|
||||
|
||||
struct Model {
|
||||
type @0 :Type;
|
||||
artifact @1 :Artifact; # Main artifact
|
||||
metadata @2 :Artifact; # Metadata artifact
|
||||
|
||||
enum Type {
|
||||
supercombo @0;
|
||||
navigation @1;
|
||||
vision @2;
|
||||
policy @3;
|
||||
}
|
||||
}
|
||||
|
||||
enum Runner {
|
||||
snpe @0;
|
||||
tinygrad @1;
|
||||
@@ -83,11 +88,14 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
|
||||
environment @6 :Text;
|
||||
runner @7 :Runner;
|
||||
is20hz @8 :Bool;
|
||||
ref @9 :Text; # New field
|
||||
minimumSelectorVersion @10 :UInt32;
|
||||
}
|
||||
}
|
||||
|
||||
struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
|
||||
dec @0 :DynamicExperimentalControl;
|
||||
accelPersonality @1 :AccelerationPersonality;
|
||||
|
||||
struct DynamicExperimentalControl {
|
||||
state @0 :DynamicExperimentalControlState;
|
||||
@@ -99,6 +107,13 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
|
||||
blended @1;
|
||||
}
|
||||
}
|
||||
|
||||
enum AccelerationPersonality {
|
||||
sport @0;
|
||||
normal @1;
|
||||
eco @2;
|
||||
stock @3;
|
||||
}
|
||||
}
|
||||
|
||||
struct OnroadEventSP @0xda96579883444c35 {
|
||||
@@ -137,6 +152,7 @@ struct OnroadEventSP @0xda96579883444c35 {
|
||||
hyundaiRadarTracksConfirmed @13;
|
||||
experimentalModeSwitched @14;
|
||||
wrongCarModeAlertOnly @15;
|
||||
pedalPressedAlertOnly @16;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +175,12 @@ struct CarParamsSP @0x80ae746ee2596b11 {
|
||||
|
||||
struct CarControlSP @0xa5cd762cd951a455 {
|
||||
mads @0 :ModularAssistiveDrivingSystem;
|
||||
params @1 :List(Param);
|
||||
|
||||
struct Param {
|
||||
key @0 :Text;
|
||||
value @1 :Text;
|
||||
}
|
||||
}
|
||||
|
||||
struct BackupManagerSP @0xf98d843bfd7004a3 {
|
||||
@@ -169,14 +191,14 @@ struct BackupManagerSP @0xf98d843bfd7004a3 {
|
||||
lastError @4 :Text;
|
||||
currentBackup @5 :BackupInfo;
|
||||
backupHistory @6 :List(BackupInfo);
|
||||
|
||||
|
||||
enum Status {
|
||||
idle @0;
|
||||
inProgress @1;
|
||||
completed @2;
|
||||
failed @3;
|
||||
}
|
||||
|
||||
|
||||
struct Version {
|
||||
major @0 :UInt16;
|
||||
minor @1 :UInt16;
|
||||
@@ -184,13 +206,13 @@ struct BackupManagerSP @0xf98d843bfd7004a3 {
|
||||
build @3 :UInt16;
|
||||
branch @4 :Text;
|
||||
}
|
||||
|
||||
|
||||
struct MetadataEntry {
|
||||
key @0 :Text;
|
||||
value @1 :Text;
|
||||
tags @2 :List(Text);
|
||||
}
|
||||
|
||||
|
||||
struct BackupInfo {
|
||||
deviceId @0 :Text;
|
||||
version @1 :UInt32;
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define DEFAULT_MODEL "Tomb_Raider_6 (Default)"
|
||||
#define DEFAULT_MODEL "Tomb Raider 7 (Default)"
|
||||
|
||||
@@ -138,7 +138,7 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
|
||||
// MADS params
|
||||
{"Mads", PERSISTENT | BACKUP},
|
||||
{"MadsMainCruiseAllowed", PERSISTENT | BACKUP},
|
||||
{"MadsPauseLateralOnBrake", PERSISTENT | BACKUP},
|
||||
{"MadsSteeringMode", PERSISTENT | BACKUP},
|
||||
{"MadsUnifiedEngagementMode", PERSISTENT | BACKUP},
|
||||
|
||||
// Model Manager params
|
||||
@@ -164,10 +164,12 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
|
||||
{"BackupManager_RestoreVersion", PERSISTENT},
|
||||
|
||||
// sunnypilot car specific params
|
||||
{"HyundaiLongitudinalTuning", PERSISTENT},
|
||||
{"HyundaiRadarTracks", PERSISTENT},
|
||||
{"HyundaiRadarTracksConfirmed", PERSISTENT},
|
||||
{"HyundaiRadarTracksPersistent", PERSISTENT},
|
||||
{"HyundaiRadarTracksToggle", PERSISTENT},
|
||||
|
||||
{"DynamicExperimentalControl", PERSISTENT},
|
||||
{"AccelPersonality", PERSISTENT},
|
||||
};
|
||||
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
"""Utilities for reading real time clocks and keeping soft real time constraints."""
|
||||
import gc
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
from setproctitle import getproctitle
|
||||
@@ -28,13 +29,13 @@ class Priority:
|
||||
|
||||
|
||||
def set_core_affinity(cores: list[int]) -> None:
|
||||
if not PC:
|
||||
if sys.platform == 'linux' and not PC:
|
||||
os.sched_setaffinity(0, cores)
|
||||
|
||||
|
||||
def config_realtime_process(cores: int | list[int], priority: int) -> None:
|
||||
gc.disable()
|
||||
if not PC:
|
||||
if sys.platform == 'linux' and not PC:
|
||||
os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(priority))
|
||||
c = cores if isinstance(cores, list) else [cores, ]
|
||||
set_core_affinity(c)
|
||||
|
||||
+13
-6
@@ -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.
|
||||
|
||||
# 304 Supported Cars
|
||||
# 311 Supported Cars
|
||||
|
||||
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br> |Video|
|
||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
|Acura|ILX 2016-19|AcuraWatch Plus|openpilot|26 mph|25 mph|[](##)|[](##)|<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.html?make=Acura&model=ILX 2016-19">Buy Here</a></sub></details>||
|
||||
|Acura|RDX 2016-18|AcuraWatch Plus|openpilot|26 mph|12 mph|[](##)|[](##)|<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.html?make=Acura&model=RDX 2016-18">Buy Here</a></sub></details>||
|
||||
|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[](##)|[](##)|<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.html?make=Acura&model=ILX 2016-18">Buy Here</a></sub></details>||
|
||||
|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[](##)|[](##)|<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.html?make=Acura&model=ILX 2019">Buy Here</a></sub></details>||
|
||||
|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[](##)|[](##)|<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.html?make=Acura&model=RDX 2016-18">Buy Here</a></sub></details>||
|
||||
|Acura|RDX 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<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.html?make=Acura&model=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|[](##)|[](##)|<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.html?make=Audi&model=A3 2014-19">Buy Here</a></sub></details>||
|
||||
|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<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.html?make=Audi&model=A3 Sportback e-tron 2017-18">Buy Here</a></sub></details>||
|
||||
@@ -32,17 +33,22 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA 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.html?make=Dodge&model=Durango 2020-21">Buy Here</a></sub></details>||
|
||||
|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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.html?make=Ford&model=Bronco Sport 2021-24">Buy Here</a></sub></details>||
|
||||
|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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.html?make=Ford&model=Escape 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<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.html?make=Ford&model=Escape 2023-24">Buy Here</a></sub></details>||
|
||||
|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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.html?make=Ford&model=Escape Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<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.html?make=Ford&model=Escape Hybrid 2023-24">Buy Here</a></sub></details>||
|
||||
|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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.html?make=Ford&model=Escape Plug-in Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<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.html?make=Ford&model=Escape Plug-in Hybrid 2023-24">Buy Here</a></sub></details>||
|
||||
|Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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.html?make=Ford&model=Explorer 2020-24">Buy Here</a></sub></details>||
|
||||
|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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.html?make=Ford&model=Explorer Hybrid 2020-24">Buy Here</a></sub></details>||
|
||||
|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=F-150 2021-23">Buy Here</a></sub></details>||
|
||||
|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=F-150 Hybrid 2021-23">Buy Here</a></sub></details>||
|
||||
|Ford|Focus 2018[<sup>3</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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.html?make=Ford&model=Focus 2018">Buy Here</a></sub></details>||
|
||||
|Ford|Focus Hybrid 2018[<sup>3</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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.html?make=Ford&model=Focus Hybrid 2018">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga 2020-22|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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.html?make=Ford&model=Kuga 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga Hybrid 2020-22|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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.html?make=Ford&model=Kuga Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga Plug-in Hybrid 2020-22|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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.html?make=Ford&model=Kuga Plug-in Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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.html?make=Ford&model=Kuga 2020-23">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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.html?make=Ford&model=Kuga Hybrid 2020-23">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<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.html?make=Ford&model=Kuga Hybrid 2024">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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.html?make=Ford&model=Kuga Plug-in Hybrid 2020-23">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<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.html?make=Ford&model=Kuga Plug-in Hybrid 2024">Buy Here</a></sub></details>||
|
||||
|Ford|Maverick 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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.html?make=Ford&model=Maverick 2022">Buy Here</a></sub></details>||
|
||||
|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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.html?make=Ford&model=Maverick 2023-24">Buy Here</a></sub></details>||
|
||||
|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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.html?make=Ford&model=Maverick Hybrid 2022">Buy Here</a></sub></details>||
|
||||
@@ -186,6 +192,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Lexus|NX Hybrid 2018-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<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.html?make=Lexus&model=NX Hybrid 2018-19">Buy Here</a></sub></details>||
|
||||
|Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|<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.html?make=Lexus&model=NX Hybrid 2020-21">Buy Here</a></sub></details>||
|
||||
|Lexus|RC 2018-20|All|Stock|19 mph|0 mph|[](##)|[](##)|<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.html?make=Lexus&model=RC 2018-20">Buy Here</a></sub></details>||
|
||||
|Lexus|RC 2023|All|openpilot|0 mph|0 mph|[](##)|[](##)|<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.html?make=Lexus&model=RC 2023">Buy Here</a></sub></details>||
|
||||
|Lexus|RX 2016|Lexus Safety System+|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<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.html?make=Lexus&model=RX 2016">Buy Here</a></sub></details>||
|
||||
|Lexus|RX 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<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.html?make=Lexus&model=RX 2017-19">Buy Here</a></sub></details>||
|
||||
|Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<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.html?make=Lexus&model=RX 2020-22">Buy Here</a></sub></details>||
|
||||
|
||||
+2
-2
@@ -25,9 +25,9 @@ ensuring two main safety requirements.
|
||||
by stepping on the brake pedal or by pressing the cancel button.
|
||||
2. The vehicle must not alter its trajectory too quickly for the driver to safely
|
||||
react. This means that while the system is engaged, the actuators are constrained
|
||||
to operate within reasonable limits[^1].
|
||||
to operate within reasonable limits[^1].
|
||||
|
||||
For additional safety implementation details, refer to [panda safety model](https://github.com/commaai/panda#safety-model). For vehicle specific implementation of the safety concept, refer to [panda/board/safety/](https://github.com/commaai/panda/tree/master/board/safety).
|
||||
For additional safety implementation details, refer to [panda safety model](https://github.com/commaai/panda#safety-model). For vehicle specific implementation of the safety concept, refer to [opendbc/safety/safety](https://github.com/commaai/opendbc/tree/master/opendbc/safety/safety).
|
||||
|
||||
**Extra note**: comma.ai strongly discourages the use of openpilot forks with safety code either missing or
|
||||
not fully meeting the above requirements.
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1
|
||||
export VECLIB_MAXIMUM_THREADS=1
|
||||
|
||||
if [ -z "$AGNOS_VERSION" ]; then
|
||||
export AGNOS_VERSION="11.13"
|
||||
export AGNOS_VERSION="12.1"
|
||||
fi
|
||||
|
||||
export STAGING_ROOT="/data/safe_staging"
|
||||
|
||||
+1
-1
Submodule opendbc_repo updated: 10248728cf...52d2957b47
@@ -47,6 +47,7 @@ dependencies = [
|
||||
# logging
|
||||
"pyzmq",
|
||||
"sentry-sdk",
|
||||
"xattr", # used in place of 'os.getxattr' for macos compatibility
|
||||
|
||||
# athena
|
||||
"PyJWT",
|
||||
|
||||
@@ -191,7 +191,7 @@ class CarSpecificEvents:
|
||||
events.add(EventName.accFaulted)
|
||||
if CS.steeringPressed:
|
||||
events.add(EventName.steerOverride)
|
||||
if CS.steeringDisengage:
|
||||
if CS.steeringDisengage and not CS_prev.steeringDisengage:
|
||||
events.add(EventName.steerDisengage)
|
||||
if CS.brakePressed and CS.standstill:
|
||||
events.add(EventName.preEnableStandstill)
|
||||
|
||||
@@ -108,7 +108,7 @@ class Car:
|
||||
fixed_fingerprint = json.loads(self.params.get("CarPlatformBundle", encoding='utf-8') or "{}").get("platform", None)
|
||||
|
||||
self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, num_pandas, cached_params, fixed_fingerprint)
|
||||
sunnypilot_interfaces.setup_interfaces(self.CI.CP, self.CI.CP_SP, self.params)
|
||||
sunnypilot_interfaces.setup_interfaces(self.CI, self.params)
|
||||
self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP, self.CI.CP_SP)
|
||||
self.CP = self.CI.CP
|
||||
self.CP_SP = self.CI.CP_SP
|
||||
|
||||
@@ -55,5 +55,6 @@ def convert_carControlSP(struct: capnp.lib.capnp._DynamicStructReader) -> struct
|
||||
struct_dataclass = structs.CarControlSP(**remove_deprecated({k: v for k, v in struct_dict.items() if not isinstance(k, dict)}))
|
||||
|
||||
struct_dataclass.mads = structs.ModularAssistiveDrivingSystem(**remove_deprecated(struct_dict.get('mads', {})))
|
||||
struct_dataclass.params = [structs.CarControlSP.Param(**remove_deprecated(p)) for p in struct_dict.get('params', [])]
|
||||
|
||||
return struct_dataclass
|
||||
|
||||
@@ -45,9 +45,9 @@ class TestCarInterfaces:
|
||||
alpha_long=args['alpha_long'], docs=False)
|
||||
car_params_sp = CarInterface.get_params_sp(car_params, car_name, args['fingerprints'], args['car_fw'],
|
||||
alpha_long=args['alpha_long'], docs=False)
|
||||
sunnypilot_interfaces.setup_interfaces(car_params, car_params_sp)
|
||||
car_params = car_params.as_reader()
|
||||
car_interface = CarInterface(car_params, car_params_sp)
|
||||
sunnypilot_interfaces.setup_interfaces(car_interface)
|
||||
assert car_params
|
||||
assert car_params_sp
|
||||
assert car_interface
|
||||
|
||||
@@ -4,7 +4,7 @@ from openpilot.common.basedir import BASEDIR
|
||||
from opendbc.car.docs import generate_cars_md, get_all_car_docs
|
||||
from openpilot.selfdrive.debug.dump_car_docs import dump_car_docs
|
||||
from openpilot.selfdrive.debug.print_docs_diff import print_car_docs_diff
|
||||
from openpilot.selfdrive.car.docs import CARS_MD_OUT, CARS_MD_TEMPLATE
|
||||
from openpilot.selfdrive.car.docs import CARS_MD_TEMPLATE
|
||||
|
||||
|
||||
class TestCarDocs:
|
||||
@@ -13,11 +13,7 @@ class TestCarDocs:
|
||||
cls.all_cars = get_all_car_docs()
|
||||
|
||||
def test_generator(self):
|
||||
generated_cars_md = generate_cars_md(self.all_cars, CARS_MD_TEMPLATE)
|
||||
with open(CARS_MD_OUT) as f:
|
||||
current_cars_md = f.read()
|
||||
|
||||
assert generated_cars_md == current_cars_md, "Run selfdrive/car/docs.py to update the compatibility documentation"
|
||||
generate_cars_md(self.all_cars, CARS_MD_TEMPLATE)
|
||||
|
||||
def test_docs_diff(self):
|
||||
dump_path = os.path.join(BASEDIR, "selfdrive", "car", "tests", "cars_dump")
|
||||
|
||||
@@ -338,6 +338,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
prev_panda_gas = self.safety.get_gas_pressed_prev()
|
||||
prev_panda_brake = self.safety.get_brake_pressed_prev()
|
||||
prev_panda_regen_braking = self.safety.get_regen_braking_prev()
|
||||
prev_panda_steering_disengage = self.safety.get_steering_disengage_prev()
|
||||
prev_panda_vehicle_moving = self.safety.get_vehicle_moving()
|
||||
prev_panda_vehicle_speed_min = self.safety.get_vehicle_speed_min()
|
||||
prev_panda_vehicle_speed_max = self.safety.get_vehicle_speed_max()
|
||||
@@ -365,6 +366,9 @@ class TestCarModelBase(unittest.TestCase):
|
||||
if self.safety.get_regen_braking_prev() != prev_panda_regen_braking:
|
||||
self.assertEqual(CS.regenBraking, self.safety.get_regen_braking_prev())
|
||||
|
||||
if self.safety.get_steering_disengage_prev() != prev_panda_steering_disengage:
|
||||
self.assertEqual(CS.steeringDisengage, self.safety.get_steering_disengage_prev())
|
||||
|
||||
if self.safety.get_vehicle_moving() != prev_panda_vehicle_moving:
|
||||
self.assertEqual(not CS.standstill, self.safety.get_vehicle_moving())
|
||||
|
||||
@@ -440,6 +444,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
brake_pressed = False
|
||||
checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev()
|
||||
checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev()
|
||||
checks['steeringDisengage'] += CS.steeringDisengage != self.safety.get_steering_disengage_prev()
|
||||
|
||||
if self.CP.pcmCruise:
|
||||
# On most pcmCruise cars, openpilot's state is always tied to the PCM's cruise state.
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
import math
|
||||
import threading
|
||||
import time
|
||||
from typing import SupportsFloat
|
||||
|
||||
from cereal import car, log, custom
|
||||
from cereal import car, log
|
||||
import cereal.messaging as messaging
|
||||
from openpilot.common.conversions import Conversions as CV
|
||||
from openpilot.common.params import Params
|
||||
@@ -19,6 +21,8 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
|
||||
from openpilot.selfdrive.controls.lib.longcontrol import LongControl
|
||||
from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose
|
||||
|
||||
from openpilot.sunnypilot.selfdrive.controls.controlsd_ext import ControlsExt
|
||||
|
||||
State = log.SelfdriveState.OpenpilotState
|
||||
LaneChangeState = log.LaneChangeState
|
||||
LaneChangeDirection = log.LaneChangeDirection
|
||||
@@ -26,24 +30,23 @@ LaneChangeDirection = log.LaneChangeDirection
|
||||
ACTUATOR_FIELDS = tuple(car.CarControl.Actuators.schema.fields.keys())
|
||||
|
||||
|
||||
class Controls:
|
||||
class Controls(ControlsExt):
|
||||
def __init__(self) -> None:
|
||||
self.params = Params()
|
||||
cloudlog.info("controlsd is waiting for CarParams")
|
||||
self.CP = messaging.log_from_bytes(self.params.get("CarParams", block=True), car.CarParams)
|
||||
cloudlog.info("controlsd got CarParams")
|
||||
|
||||
cloudlog.info("controlsd is waiting for CarParamsSP")
|
||||
self.CP_SP = messaging.log_from_bytes(self.params.get("CarParamsSP", block=True), custom.CarParamsSP)
|
||||
cloudlog.info("controlsd got CarParamsSP")
|
||||
# Initialize sunnypilot controlsd extension
|
||||
ControlsExt.__init__(self, self.CP, self.params)
|
||||
|
||||
self.CI = interfaces[self.CP.carFingerprint](self.CP, self.CP_SP)
|
||||
|
||||
self.sm = messaging.SubMaster(['liveParameters', 'liveTorqueParameters', 'modelV2', 'selfdriveState',
|
||||
'liveCalibration', 'livePose', 'longitudinalPlan', 'carState', 'carOutput',
|
||||
'driverMonitoringState', 'onroadEvents', 'driverAssistance'] + ['selfdriveStateSP'],
|
||||
'driverMonitoringState', 'onroadEvents', 'driverAssistance'] + self.sm_services_ext,
|
||||
poll='selfdriveState')
|
||||
self.pm = messaging.PubMaster(['carControl', 'controlsState'] + ['carControlSP'])
|
||||
self.pm = messaging.PubMaster(['carControl', 'controlsState'] + self.pm_services_ext)
|
||||
|
||||
self.steer_limited_by_controls = False
|
||||
self.curvature = 0.0
|
||||
@@ -100,11 +103,8 @@ class Controls:
|
||||
# Check which actuators can be enabled
|
||||
standstill = abs(CS.vEgo) <= max(self.CP.minSteerSpeed, 0.3) or CS.standstill
|
||||
|
||||
ss_sp = self.sm['selfdriveStateSP']
|
||||
if ss_sp.mads.available:
|
||||
_lat_active = ss_sp.mads.active
|
||||
else:
|
||||
_lat_active = self.sm['selfdriveState'].active
|
||||
# Get which state to use for active lateral control
|
||||
_lat_active = self.get_lat_active(self.sm)
|
||||
|
||||
CC.latActive = _lat_active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \
|
||||
(not standstill or self.CP.steerAtStandstill)
|
||||
@@ -148,12 +148,9 @@ class Controls:
|
||||
cloudlog.error(f"actuators.{p} not finite {actuators.to_dict()}")
|
||||
setattr(actuators, p, 0.0)
|
||||
|
||||
CC_SP = custom.CarControlSP.new_message()
|
||||
CC_SP.mads = ss_sp.mads
|
||||
return CC, lac_log
|
||||
|
||||
return CC, CC_SP, lac_log
|
||||
|
||||
def publish(self, CC, CC_SP, lac_log):
|
||||
def publish(self, CC, lac_log):
|
||||
CS = self.sm['carState']
|
||||
|
||||
# Orientation and angle rates can be useful for carcontroller
|
||||
@@ -227,19 +224,27 @@ class Controls:
|
||||
cc_send.carControl = CC
|
||||
self.pm.send('carControl', cc_send)
|
||||
|
||||
# carControlSP
|
||||
cc_sp_send = messaging.new_message('carControlSP')
|
||||
cc_sp_send.valid = CS.canValid
|
||||
cc_sp_send.carControlSP = CC_SP
|
||||
self.pm.send('carControlSP', cc_sp_send)
|
||||
def params_thread(self, evt):
|
||||
while not evt.is_set():
|
||||
self.get_params_sp()
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
def run(self):
|
||||
rk = Ratekeeper(100, print_delay_threshold=None)
|
||||
while True:
|
||||
self.update()
|
||||
CC, CC_SP, lac_log = self.state_control()
|
||||
self.publish(CC, CC_SP, lac_log)
|
||||
rk.monitor_time()
|
||||
e = threading.Event()
|
||||
t = threading.Thread(target=self.params_thread, args=(e,))
|
||||
try:
|
||||
t.start()
|
||||
while True:
|
||||
self.update()
|
||||
CC, lac_log = self.state_control()
|
||||
self.publish(CC, lac_log)
|
||||
self.run_ext(self.sm, self.pm)
|
||||
rk.monitor_time()
|
||||
finally:
|
||||
e.set()
|
||||
t.join()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -10,6 +10,9 @@ from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.selfdrive.modeld.constants import index_function
|
||||
from openpilot.selfdrive.controls.radard import _LEAD_ACCEL_TAU
|
||||
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.accel_personality.accel_controller import AccelController
|
||||
|
||||
|
||||
if __name__ == '__main__': # generating code
|
||||
from openpilot.third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver
|
||||
else:
|
||||
@@ -228,6 +231,7 @@ class LongitudinalMpc:
|
||||
self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N)
|
||||
self.reset()
|
||||
self.source = SOURCES[2]
|
||||
self.accel_controller = AccelController()
|
||||
|
||||
def reset(self):
|
||||
# self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N)
|
||||
@@ -332,6 +336,8 @@ class LongitudinalMpc:
|
||||
v_ego = self.x0[1]
|
||||
self.status = radarstate.leadOne.status or radarstate.leadTwo.status
|
||||
|
||||
a_cruise_min = self.accel_controller._get_min_accel_for_speed(v_ego)
|
||||
|
||||
lead_xv_0 = self.process_lead(radarstate.leadOne)
|
||||
lead_xv_1 = self.process_lead(radarstate.leadTwo)
|
||||
|
||||
@@ -350,7 +356,7 @@ class LongitudinalMpc:
|
||||
|
||||
# Fake an obstacle for cruise, this ensures smooth acceleration to set speed
|
||||
# when the leads are no factor.
|
||||
v_lower = v_ego + (T_IDXS * CRUISE_MIN_ACCEL * 1.05)
|
||||
v_lower = v_ego + (T_IDXS * a_cruise_min * 1.05)
|
||||
# TODO does this make sense when max_a is negative?
|
||||
v_upper = v_ego + (T_IDXS * CRUISE_MAX_ACCEL * 1.05)
|
||||
v_cruise_clipped = np.clip(v_cruise * np.ones(N+1),
|
||||
|
||||
@@ -93,8 +93,8 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
||||
return x, v, a, j, throttle_prob
|
||||
|
||||
def update(self, sm):
|
||||
LongitudinalPlannerSP.update(self, sm)
|
||||
self.mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc'
|
||||
LongitudinalPlannerSP.update(self, sm)
|
||||
if dec_mpc_mode := self.get_mpc_mode():
|
||||
self.mode = dec_mpc_mode
|
||||
|
||||
@@ -126,6 +126,16 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
||||
else:
|
||||
accel_clip = [ACCEL_MIN, ACCEL_MAX]
|
||||
|
||||
# Override accel using Accel Controller if enabled
|
||||
if self.accel_controller.is_personality_enabled:
|
||||
max_limit = self.accel_controller._get_max_accel_for_speed(v_ego)
|
||||
if self.mpc.mode == 'acc':
|
||||
# Use the accel controller limits directly
|
||||
accel_clip = [ACCEL_MIN, max_limit]
|
||||
# Recalculate limit turn according to the new max limit
|
||||
steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg
|
||||
accel_clip = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_clip, self.CP)
|
||||
|
||||
if reset_state:
|
||||
self.v_desired_filter.x = v_ego
|
||||
# Clip aEgo to cruise limits to prevent large accelerations when becoming active
|
||||
@@ -178,6 +188,10 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
||||
output_a_target = min(output_a_target_mpc, output_a_target_e2e)
|
||||
self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc
|
||||
|
||||
if not self.is_stock:
|
||||
# To support non Tomb Raider models
|
||||
output_a_target, self.output_should_stop = output_a_target_mpc, output_should_stop_mpc
|
||||
|
||||
for idx in range(2):
|
||||
accel_clip[idx] = np.clip(accel_clip[idx], self.prev_accel_clip[idx] - 0.05, self.prev_accel_clip[idx] + 0.05)
|
||||
self.output_a_target = np.clip(output_a_target, accel_clip[0], accel_clip[1])
|
||||
|
||||
@@ -23,7 +23,7 @@ class TestLatControl:
|
||||
CP = CarInterface.get_non_essential_params(car_name)
|
||||
CP_SP = CarInterface.get_non_essential_params_sp(CP, car_name)
|
||||
CI = CarInterface(CP, CP_SP)
|
||||
sunnypilot_interfaces.setup_interfaces(CP, CP_SP)
|
||||
sunnypilot_interfaces.setup_interfaces(CI)
|
||||
CP_SP = convert_to_capnp(CP_SP)
|
||||
VM = VehicleModel(CP)
|
||||
|
||||
|
||||
@@ -71,6 +71,13 @@ SUPPORTED_FW_VERSIONS = {
|
||||
b"DLhe SCC FHCUP 1.00 1.02 99110-L7000 \x01 \x102 ": ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
# 2022 Niro EV
|
||||
b"DEev SCC F-CUP 1.00 1.00 99110-Q4600\x01\x42 ": ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
b"DEev SCC F-CUP 1.00 1.00 99110-Q4600 \x07\x03\t% ": ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -90,18 +90,11 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
|
||||
fill_xyzt(modelV2.orientationRate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T)
|
||||
|
||||
# temporal pose
|
||||
temporal_pose = modelV2.temporalPose
|
||||
if 'sim_pose' in net_output_data:
|
||||
temporal_pose.trans = net_output_data['sim_pose'][0,:ModelConstants.POSE_WIDTH//2].tolist()
|
||||
temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:ModelConstants.POSE_WIDTH//2].tolist()
|
||||
temporal_pose.rot = net_output_data['sim_pose'][0,ModelConstants.POSE_WIDTH//2:].tolist()
|
||||
temporal_pose.rotStd = net_output_data['sim_pose_stds'][0,ModelConstants.POSE_WIDTH//2:].tolist()
|
||||
else:
|
||||
temporal_pose.trans = net_output_data['plan'][0,0,Plan.VELOCITY].tolist()
|
||||
temporal_pose.transStd = net_output_data['plan_stds'][0,0,Plan.VELOCITY].tolist()
|
||||
temporal_pose.rot = net_output_data['plan'][0,0,Plan.ORIENTATION_RATE].tolist()
|
||||
temporal_pose.rotStd = net_output_data['plan_stds'][0,0,Plan.ORIENTATION_RATE].tolist()
|
||||
|
||||
#temporal_pose = modelV2.temporalPose
|
||||
#temporal_pose.trans = net_output_data['sim_pose'][0,:ModelConstants.POSE_WIDTH//2].tolist()
|
||||
#temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:ModelConstants.POSE_WIDTH//2].tolist()
|
||||
#temporal_pose.rot = net_output_data['sim_pose'][0,ModelConstants.POSE_WIDTH//2:].tolist()
|
||||
#temporal_pose.rotStd = net_output_data['sim_pose_stds'][0,ModelConstants.POSE_WIDTH//2:].tolist()
|
||||
|
||||
# poly path
|
||||
fill_xyz_poly(driving_model_data.path, ModelConstants.POLY_PATH_DEGREE, *net_output_data['plan'][0,:,Plan.POSITION].T)
|
||||
|
||||
@@ -41,7 +41,7 @@ POLICY_PKL_PATH = Path(__file__).parent / 'models/driving_policy_tinygrad.pkl'
|
||||
VISION_METADATA_PATH = Path(__file__).parent / 'models/driving_vision_metadata.pkl'
|
||||
POLICY_METADATA_PATH = Path(__file__).parent / 'models/driving_policy_metadata.pkl'
|
||||
|
||||
LAT_SMOOTH_SECONDS = 0.3
|
||||
LAT_SMOOTH_SECONDS = 0.1
|
||||
LONG_SMOOTH_SECONDS = 0.3
|
||||
MIN_LAT_CONTROL_SPEED = 0.3
|
||||
|
||||
@@ -54,9 +54,12 @@ def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.
|
||||
ModelConstants.T_IDXS,
|
||||
action_t=long_action_t)
|
||||
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, LONG_SMOOTH_SECONDS)
|
||||
desired_curvature = get_curvature_from_plan(plan[:, Plan.T_FROM_CURRENT_EULER][:, 2],
|
||||
plan[:, Plan.ORIENTATION_RATE][:, 2],
|
||||
ModelConstants.T_IDXS, v_ego, lat_action_t)
|
||||
|
||||
desired_curvature = get_curvature_from_plan(plan[:,Plan.T_FROM_CURRENT_EULER][:,2],
|
||||
plan[:,Plan.ORIENTATION_RATE][:,2],
|
||||
ModelConstants.T_IDXS,
|
||||
v_ego,
|
||||
lat_action_t)
|
||||
if v_ego > MIN_LAT_CONTROL_SPEED:
|
||||
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, LAT_SMOOTH_SECONDS)
|
||||
else:
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8a478376723ba79e2393ca95e2d3e497571ba6fed113e5f13a36f0e4b4d4a7c5
|
||||
oid sha256:19e30484236efff72d519938c3e26461dbeb89c11d81fa7ecbff8e0263333c18
|
||||
size 15588463
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
#define CUTOFF_IL 400
|
||||
#define SATURATE_IL 1000
|
||||
|
||||
#define ALT_EXP_DISENGAGE_LATERAL_ON_BRAKE 2048
|
||||
#define ALT_EXP_MADS_DISENGAGE_LATERAL_ON_BRAKE 2048
|
||||
|
||||
ExitHandler do_exit;
|
||||
|
||||
@@ -57,7 +57,7 @@ bool check_all_connected(const std::vector<Panda *> &pandas) {
|
||||
|
||||
bool process_mads_heartbeat(SubMaster *sm) {
|
||||
const int &alt_exp = (*sm)["carParams"].getCarParams().getAlternativeExperience();
|
||||
const bool disengage_lateral_on_brake = (alt_exp & ALT_EXP_DISENGAGE_LATERAL_ON_BRAKE) != 0;
|
||||
const bool disengage_lateral_on_brake = (alt_exp & ALT_EXP_MADS_DISENGAGE_LATERAL_ON_BRAKE) != 0;
|
||||
|
||||
const auto &mads = (*sm)["selfdriveStateSP"].getSelfdriveStateSP().getMads();
|
||||
const bool heartbeat_type = disengage_lateral_on_brake ? mads.getActive() : mads.getEnabled();
|
||||
@@ -474,7 +474,12 @@ void pandad_run(std::vector<Panda *> &pandas) {
|
||||
for (auto *panda : pandas) {
|
||||
std::string log = panda->serial_read();
|
||||
if (!log.empty()) {
|
||||
LOGD("%s", log.c_str());
|
||||
if (log.find("Register 0x") != std::string::npos) {
|
||||
// Log register divergent faults as errors
|
||||
LOGE("%s", log.c_str());
|
||||
} else {
|
||||
LOGD("%s", log.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,16 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
|
||||
"../assets/offroad/icon_speed_limit.png",
|
||||
longi_button_texts);
|
||||
|
||||
// accel controller
|
||||
std::vector<QString> accel_personality_texts{tr("Sport"), tr("Normal"), tr("Eco"), tr("Stock")};
|
||||
accel_personality_setting = new ButtonParamControlSP("AccelPersonality", tr("Acceleration Personality"),
|
||||
tr("Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. "
|
||||
"In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these "
|
||||
"acceleration personality within Onroad Settings on the driving screen."),
|
||||
"",
|
||||
accel_personality_texts);
|
||||
accel_personality_setting->showDescription();
|
||||
|
||||
// set up uiState update for personality setting
|
||||
QObject::connect(uiState(), &UIState::uiUpdate, this, &TogglesPanel::updateState);
|
||||
|
||||
@@ -93,6 +103,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
|
||||
// insert longitudinal personality after NDOG toggle
|
||||
if (param == "DisengageOnAccelerator") {
|
||||
addItem(long_personality_setting);
|
||||
addItem(accel_personality_setting);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +124,13 @@ void TogglesPanel::updateState(const UIState &s) {
|
||||
}
|
||||
uiState()->scene.personality = personality;
|
||||
}
|
||||
if (sm.updated("longitudinalPlanSP")) {
|
||||
auto accel_personality = sm["longitudinalPlanSP"].getLongitudinalPlanSP().getAccelPersonality();
|
||||
if (accel_personality != s.scene.accel_personality && s.scene.started && isVisible()) {
|
||||
accel_personality_setting->setCheckedButton(static_cast<int>(accel_personality));
|
||||
}
|
||||
uiState()->scene.accel_personality = accel_personality;
|
||||
}
|
||||
}
|
||||
|
||||
void TogglesPanel::expandToggleDescription(const QString ¶m) {
|
||||
@@ -150,10 +168,12 @@ void TogglesPanel::updateToggles() {
|
||||
experimental_mode_toggle->setEnabled(true);
|
||||
experimental_mode_toggle->setDescription(e2e_description);
|
||||
long_personality_setting->setEnabled(true);
|
||||
accel_personality_setting->setEnabled(true);
|
||||
} else {
|
||||
// no long for now
|
||||
experimental_mode_toggle->setEnabled(false);
|
||||
long_personality_setting->setEnabled(false);
|
||||
accel_personality_setting->setEnabled(true);
|
||||
params.remove("ExperimentalMode");
|
||||
|
||||
const QString unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.");
|
||||
|
||||
@@ -85,6 +85,7 @@ protected:
|
||||
Params params;
|
||||
std::map<std::string, ParamControl*> toggles;
|
||||
ButtonParamControl *long_personality_setting;
|
||||
ButtonParamControl *accel_personality_setting;
|
||||
|
||||
virtual void updateToggles();
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ widgets_src = [
|
||||
"sunnypilot/qt/widgets/toggle.cc",
|
||||
"sunnypilot/qt/widgets/controls.cc",
|
||||
"sunnypilot/qt/widgets/drive_stats.cc",
|
||||
"sunnypilot/qt/widgets/expandable_row.cc",
|
||||
"sunnypilot/qt/widgets/prime.cc",
|
||||
"sunnypilot/qt/widgets/scrollview.cc",
|
||||
"sunnypilot/qt/network/networking.cc",
|
||||
@@ -21,6 +22,7 @@ qt_src = [
|
||||
"sunnypilot/qt/offroad/offroad_home.cc",
|
||||
"sunnypilot/qt/offroad/settings/device_panel.cc",
|
||||
"sunnypilot/qt/offroad/settings/lateral_panel.cc",
|
||||
"sunnypilot/qt/offroad/settings/longitudinal_panel.cc",
|
||||
"sunnypilot/qt/offroad/settings/max_time_offroad.cc",
|
||||
"sunnypilot/qt/offroad/settings/settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/software_panel.cc",
|
||||
@@ -49,11 +51,28 @@ network_src = [
|
||||
]
|
||||
|
||||
vehicle_panel_qt_src = [
|
||||
"sunnypilot/qt/offroad/settings/vehicle/brand_settings_factory.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/platform_selector.cc",
|
||||
]
|
||||
|
||||
brand_settings_qt_src = [
|
||||
"sunnypilot/qt/offroad/settings/vehicle/chrysler_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/ford_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/gm_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/honda_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/mazda_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/nissan_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/rivian_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/subaru_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/tesla_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/toyota_settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle/volkswagen_settings.cc",
|
||||
]
|
||||
|
||||
sp_widgets_src = widgets_src + network_src
|
||||
sp_qt_src = qt_src + lateral_panel_qt_src + vehicle_panel_qt_src
|
||||
sp_qt_src = qt_src + lateral_panel_qt_src + vehicle_panel_qt_src + brand_settings_qt_src
|
||||
sp_qt_util = qt_util
|
||||
|
||||
Export('sp_widgets_src', 'sp_qt_src', "sp_qt_util")
|
||||
|
||||
@@ -112,9 +112,18 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) {
|
||||
|
||||
addItem(power_group_layout);
|
||||
|
||||
std::vector always_enabled_btns = {
|
||||
rebootBtn,
|
||||
poweroffBtn,
|
||||
offroadBtn,
|
||||
buttons["quietModeBtn"],
|
||||
};
|
||||
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) {
|
||||
for (auto btn : findChildren<PushButtonSP*>()) {
|
||||
if (btn != rebootBtn && btn != poweroffBtn && btn != offroadBtn) {
|
||||
bool always_enabled = std::find(always_enabled_btns.begin(), always_enabled_btns.end(), btn) != always_enabled_btns.end();
|
||||
|
||||
if (!always_enabled) {
|
||||
btn->setEnabled(offroad);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,35 +21,19 @@ MadsSettings::MadsSettings(QWidget *parent) : QWidget(parent) {
|
||||
|
||||
ListWidget *list = new ListWidget(this, false);
|
||||
// Main cruise
|
||||
madsMainCruiseToggle = new ParamControl(
|
||||
"MadsMainCruiseAllowed",
|
||||
tr("Toggle with Main Cruise"),
|
||||
tr("Note: For vehicles without LFA/LKAS button, disabling this will prevent lateral control engagement."),
|
||||
"");
|
||||
madsMainCruiseToggle = new ParamControl("MadsMainCruiseAllowed", tr("Toggle with Main Cruise"), "", "");
|
||||
list->addItem(madsMainCruiseToggle);
|
||||
|
||||
// Unified Engagement Mode
|
||||
madsUnifiedEngagementModeToggle = new ParamControl(
|
||||
"MadsUnifiedEngagementMode",
|
||||
tr("Unified Engagement Mode (UEM)"),
|
||||
QString("%1<br>"
|
||||
"<h4>%2</h4>")
|
||||
.arg(tr("Engage lateral and longitudinal control with cruise control engagement."))
|
||||
.arg(tr("Note: Once lateral control is engaged via UEM, it will remain engaged until it is manually disabled via the MADS button or car shut off.")),
|
||||
"");
|
||||
madsUnifiedEngagementModeToggle = new ParamControl("MadsUnifiedEngagementMode", tr("Unified Engagement Mode (UEM)"), "", "");
|
||||
list->addItem(madsUnifiedEngagementModeToggle);
|
||||
|
||||
// Pause Lateral On Brake
|
||||
std::vector<QString> lateral_on_brake_texts{tr("Remain Active"), tr("Pause Steering")};
|
||||
madsPauseLateralOnBrake = new ButtonParamControl(
|
||||
"MadsPauseLateralOnBrake",
|
||||
tr("Steering Mode After Braking"),
|
||||
tr("Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot.\n\n"
|
||||
"Remain Active: ALC will remain active even after the brake pedal is pressed.\nPause Steering: ALC will be paused after the brake pedal is manually pressed."),
|
||||
"",
|
||||
lateral_on_brake_texts,
|
||||
500);
|
||||
list->addItem(madsPauseLateralOnBrake);
|
||||
// Steering Mode On Brake
|
||||
madsSteeringMode = new ButtonParamControl("MadsSteeringMode", tr("Steering Mode on Brake Pedal"), "", "", madsSteeringModeTexts(), 500);
|
||||
QObject::connect(madsSteeringMode, &ButtonParamControl::buttonToggled, [=] {
|
||||
updateToggles(offroad);
|
||||
});
|
||||
list->addItem(madsSteeringMode);
|
||||
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, this, &MadsSettings::updateToggles);
|
||||
|
||||
@@ -61,7 +45,58 @@ void MadsSettings::showEvent(QShowEvent *event) {
|
||||
}
|
||||
|
||||
void MadsSettings::updateToggles(bool _offroad) {
|
||||
madsPauseLateralOnBrake->setEnabled(_offroad);
|
||||
auto mads_steering_mode_param = std::atoi(params.get("MadsSteeringMode").c_str());
|
||||
|
||||
auto steering_mode = static_cast<MadsSteeringMode>(
|
||||
std::clamp(mads_steering_mode_param, static_cast<int>(MadsSteeringMode::REMAIN_ACTIVE), static_cast<int>(MadsSteeringMode::DISENGAGE))
|
||||
);
|
||||
|
||||
auto cp_bytes = params.get("CarParamsPersistent");
|
||||
if (!cp_bytes.empty()) {
|
||||
AlignedBuffer aligned_buf;
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
||||
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||
|
||||
if (isBrandInList(CP.getBrand(), mads_limited_settings_brands)) {
|
||||
params.remove("MadsMainCruiseAllowed");
|
||||
params.putBool("MadsUnifiedEngagementMode", true);
|
||||
params.put("MadsSteeringMode", std::to_string(static_cast<int>(MadsSteeringMode::DISENGAGE)));
|
||||
|
||||
madsMainCruiseToggle->setEnabled(false);
|
||||
madsMainCruiseToggle->setDescription(madsDescriptionBuilder(DEFAULT_TO_OFF, MADS_MAIN_CRUISE_BASE_DESC));
|
||||
madsMainCruiseToggle->showDescription();
|
||||
|
||||
madsUnifiedEngagementModeToggle->setEnabled(false);
|
||||
madsUnifiedEngagementModeToggle->setDescription(madsDescriptionBuilder(DEFAULT_TO_ON, MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC));
|
||||
madsUnifiedEngagementModeToggle->showDescription();
|
||||
|
||||
madsSteeringModeValues = convertMadsSteeringModeValues({MadsSteeringMode::DISENGAGE});
|
||||
madsSteeringMode->setDescription(madsDescriptionBuilder(STATUS_DISENGAGE_ONLY, madsSteeringModeDescription(steering_mode)));
|
||||
} else {
|
||||
madsMainCruiseToggle->setEnabled(true);
|
||||
madsMainCruiseToggle->setDescription(MADS_MAIN_CRUISE_BASE_DESC);
|
||||
|
||||
madsUnifiedEngagementModeToggle->setEnabled(true);
|
||||
madsUnifiedEngagementModeToggle->setDescription(MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC);
|
||||
|
||||
madsSteeringModeValues = convertMadsSteeringModeValues(getMadsSteeringModeValues());
|
||||
madsSteeringMode->setDescription(madsSteeringModeDescription(steering_mode));
|
||||
}
|
||||
} else {
|
||||
madsMainCruiseToggle->setEnabled(false);
|
||||
madsMainCruiseToggle->setDescription(madsDescriptionBuilder(STATUS_CHECK_COMPATIBILITY, MADS_MAIN_CRUISE_BASE_DESC));
|
||||
madsMainCruiseToggle->showDescription();
|
||||
|
||||
madsUnifiedEngagementModeToggle->setEnabled(false);
|
||||
madsUnifiedEngagementModeToggle->setDescription(madsDescriptionBuilder(STATUS_CHECK_COMPATIBILITY, MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC));
|
||||
madsUnifiedEngagementModeToggle->showDescription();
|
||||
|
||||
madsSteeringModeValues = {};
|
||||
madsSteeringMode->setDescription(madsDescriptionBuilder(STATUS_CHECK_COMPATIBILITY, madsSteeringModeDescription(steering_mode)));
|
||||
}
|
||||
|
||||
madsSteeringMode->setEnableSelectedButtons(_offroad, madsSteeringModeValues);
|
||||
madsSteeringMode->showDescription();
|
||||
|
||||
offroad = _offroad;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,20 @@
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
const std::vector<std::string> mads_limited_settings_brands = {"rivian", "tesla"};
|
||||
|
||||
enum class MadsSteeringMode {
|
||||
REMAIN_ACTIVE = 0,
|
||||
PAUSE = 1,
|
||||
DISENGAGE = 2,
|
||||
};
|
||||
|
||||
struct MadsSteeringModeOption {
|
||||
MadsSteeringMode mode;
|
||||
QString display_text;
|
||||
QString description;
|
||||
};
|
||||
|
||||
class MadsSettings : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -32,5 +46,70 @@ private:
|
||||
|
||||
ParamControl *madsMainCruiseToggle;
|
||||
ParamControl *madsUnifiedEngagementModeToggle;
|
||||
ButtonParamControl *madsPauseLateralOnBrake;
|
||||
ButtonParamControl *madsSteeringMode;
|
||||
|
||||
std::vector<int> madsSteeringModeValues = {};
|
||||
|
||||
const QString MADS_MAIN_CRUISE_BASE_DESC = tr("Note: For vehicles without LFA/LKAS button, disabling this will prevent lateral control engagement.");
|
||||
const QString MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC = QString("%1<br>"
|
||||
"<h4>%2</h4>")
|
||||
.arg(tr("Engage lateral and longitudinal control with cruise control engagement."))
|
||||
.arg(tr("Note: Once lateral control is engaged via UEM, it will remain engaged until it is manually disabled via the MADS button or car shut off."));
|
||||
|
||||
const QString STATUS_CHECK_COMPATIBILITY = tr("Start the vehicle to check vehicle compatibility.");
|
||||
const QString DEFAULT_TO_OFF = tr("This feature defaults to OFF, and does not allow selection due to vehicle limitations.");
|
||||
const QString DEFAULT_TO_ON = tr("This feature defaults to ON, and does not allow selection due to vehicle limitations.");
|
||||
const QString STATUS_DISENGAGE_ONLY = tr("This platform only supports Disengage mode due to vehicle limitations.");
|
||||
|
||||
static const std::vector<MadsSteeringModeOption> &madsSteeringModeOptions() {
|
||||
static const std::vector<MadsSteeringModeOption> options = {
|
||||
{MadsSteeringMode::REMAIN_ACTIVE, tr("Remain Active"), tr("Remain Active: ALC will remain active when the brake pedal is pressed.")},
|
||||
{MadsSteeringMode::PAUSE, tr("Pause"), tr("Pause: ALC will pause when the brake pedal is pressed.")},
|
||||
{MadsSteeringMode::DISENGAGE, tr("Disengage"), tr("Disengage: ALC will disengage when the brake pedal is pressed.")},
|
||||
};
|
||||
return options;
|
||||
}
|
||||
|
||||
static std::vector<MadsSteeringMode> getMadsSteeringModeValues() {
|
||||
std::vector<MadsSteeringMode> values;
|
||||
for (const auto& option : madsSteeringModeOptions()) {
|
||||
values.push_back(option.mode);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
static std::vector<int> convertMadsSteeringModeValues(const std::vector<MadsSteeringMode> &modes) {
|
||||
std::vector<int> values;
|
||||
for (const auto& mode : modes) {
|
||||
values.push_back(static_cast<int>(mode));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
static std::vector<QString> madsSteeringModeTexts() {
|
||||
std::vector<QString> texts;
|
||||
for (const auto& option : madsSteeringModeOptions()) {
|
||||
texts.push_back(option.display_text);
|
||||
}
|
||||
return texts;
|
||||
}
|
||||
|
||||
static QString madsSteeringModeDescription(const MadsSteeringMode mode = MadsSteeringMode::REMAIN_ACTIVE) {
|
||||
QString base_desc = tr("Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot.");
|
||||
QString result = base_desc + "<br><br>";
|
||||
|
||||
for (const auto& option : madsSteeringModeOptions()) {
|
||||
QString desc = option.description;
|
||||
if (option.mode == mode) {
|
||||
desc = "<font color='white'><b>" + desc + "</b></font>";
|
||||
}
|
||||
result += desc + "<br>";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static QString madsDescriptionBuilder(const QString &custom_description, const QString &base_description) {
|
||||
return "<font color='white'><b>" + custom_description + "</b></font><br><br>" + base_description;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -122,6 +122,21 @@ void LateralPanel::updateToggles(bool _offroad) {
|
||||
toggle->setEnabled(_offroad);
|
||||
}
|
||||
|
||||
auto cp_bytes = params.get("CarParamsPersistent");
|
||||
if (!cp_bytes.empty()) {
|
||||
AlignedBuffer aligned_buf;
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
||||
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||
|
||||
if (isBrandInList(CP.getBrand(), mads_limited_settings_brands)) {
|
||||
madsToggle->setDescription(descriptionBuilder(STATUS_MADS_SETTINGS_LIMITED_COMPATIBILITY, MADS_BASE_DESC));
|
||||
} else {
|
||||
madsToggle->setDescription(descriptionBuilder(STATUS_MADS_SETTINGS_FULL_COMPATIBILITY, MADS_BASE_DESC));
|
||||
}
|
||||
} else {
|
||||
madsToggle->setDescription(descriptionBuilder(STATUS_MADS_CHECK_COMPATIBILITY, MADS_BASE_DESC));
|
||||
}
|
||||
|
||||
madsSettingsButton->setEnabled(madsToggle->isToggled());
|
||||
|
||||
offroad = _offroad;
|
||||
|
||||
@@ -30,6 +30,7 @@ public slots:
|
||||
void updateToggles(bool _offroad);
|
||||
|
||||
private:
|
||||
Params params;
|
||||
QStackedLayout* main_layout = nullptr;
|
||||
QWidget* sunnypilotScreen = nullptr;
|
||||
ScrollViewSP *sunnypilotScroller = nullptr;
|
||||
@@ -42,4 +43,14 @@ private:
|
||||
PushButtonSP *laneChangeSettingsButton;
|
||||
LaneChangeSettings *laneChangeWidget = nullptr;
|
||||
NeuralNetworkLateralControl *nnlcToggle = nullptr;
|
||||
|
||||
const QString MADS_BASE_DESC = tr("Enables independent engagements of Automatic Lane Centering (ALC) and Adaptive Cruise Control (ACC).");
|
||||
|
||||
const QString STATUS_MADS_CHECK_COMPATIBILITY = tr("Start the vehicle to check vehicle compatibility.");
|
||||
const QString STATUS_MADS_SETTINGS_FULL_COMPATIBILITY = tr("This platform supports all MADS settings.");
|
||||
const QString STATUS_MADS_SETTINGS_LIMITED_COMPATIBILITY = tr("This platform supports limited MADS settings.");
|
||||
|
||||
static QString descriptionBuilder(const QString &custom_description, const QString &base_description) {
|
||||
return "<font color='white'><b>" + custom_description + "</b></font><br><br>" + base_description;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 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_panel.h"
|
||||
|
||||
LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 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/qt/offroad/settings/settings.h"
|
||||
|
||||
class LongitudinalPanel : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LongitudinalPanel(QWidget *parent = nullptr);
|
||||
};
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/trips_panel.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_panel.h"
|
||||
|
||||
@@ -79,6 +80,7 @@ SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) {
|
||||
PanelInfo(" " + tr("Toggles"), toggles, "../../sunnypilot/selfdrive/assets/offroad/icon_toggle.png"),
|
||||
PanelInfo(" " + tr("Software"), new SoftwarePanelSP(this), "../../sunnypilot/selfdrive/assets/offroad/icon_software.png"),
|
||||
PanelInfo(" " + tr("Steering"), new LateralPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_lateral.png"),
|
||||
PanelInfo(" " + tr("Cruise"), new LongitudinalPanel(this), "../assets/offroad/icon_speed_limit.png"),
|
||||
PanelInfo(" " + tr("Trips"), new TripsPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_trips.png"),
|
||||
PanelInfo(" " + tr("Vehicle"), new VehiclePanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_vehicle.png"),
|
||||
PanelInfo(" " + tr("Firehose"), new FirehosePanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_firehose.svg"),
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
|
||||
#include "selfdrive/ui/qt/offroad/settings.h"
|
||||
|
||||
inline bool isBrandInList(const std::string &brand, const std::vector<std::string> &list) {
|
||||
return std::find(list.begin(), list.end(), brand) != list.end();
|
||||
}
|
||||
|
||||
class SettingsWindowSP : public SettingsWindow {
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
@@ -47,24 +47,24 @@ void SoftwarePanelSP::handleBundleDownloadProgress() {
|
||||
// Get status for each model type in order
|
||||
for (const auto &model: models) {
|
||||
QString typeName;
|
||||
QString modelName;
|
||||
QString modelName = QString::fromStdString(bundle.getDisplayName());
|
||||
|
||||
switch (model.getType()) {
|
||||
case cereal::ModelManagerSP::Type::DRIVE:
|
||||
case cereal::ModelManagerSP::Model::Type::SUPERCOMBO:
|
||||
typeName = tr("Driving");
|
||||
modelName = QString::fromStdString(bundle.getDisplayName());
|
||||
break;
|
||||
case cereal::ModelManagerSP::Type::NAVIGATION:
|
||||
case cereal::ModelManagerSP::Model::Type::NAVIGATION:
|
||||
typeName = tr("Navigation");
|
||||
modelName = QString::fromStdString(model.getFullName());
|
||||
break;
|
||||
case cereal::ModelManagerSP::Type::METADATA:
|
||||
typeName = tr("Metadata");
|
||||
modelName = QString::fromStdString(model.getFullName());
|
||||
case cereal::ModelManagerSP::Model::Type::VISION:
|
||||
typeName = tr("Vision");
|
||||
break;
|
||||
case cereal::ModelManagerSP::Model::Type::POLICY:
|
||||
typeName = tr("Policy");
|
||||
break;
|
||||
}
|
||||
|
||||
const auto &progress = model.getDownloadProgress();
|
||||
const auto &progress = model.getArtifact().getDownloadProgress();
|
||||
QString line;
|
||||
|
||||
if (progress.getStatus() == cereal::ModelManagerSP::DownloadStatus::DOWNLOADING) {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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/vehicle/brand_settings_factory.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brands.h"
|
||||
|
||||
static const QStringList supportedBrands = {
|
||||
"chrysler",
|
||||
"ford",
|
||||
"gm",
|
||||
"honda",
|
||||
"hyundai",
|
||||
"mazda",
|
||||
"nissan",
|
||||
"rivian",
|
||||
"subaru",
|
||||
"tesla",
|
||||
"toyota",
|
||||
"volkswagen",
|
||||
};
|
||||
|
||||
BrandSettingsInterface* BrandSettingsFactory::createBrandSettings(const QString& brand, QWidget* parent) {
|
||||
if (brand == "chrysler")
|
||||
return new ChryslerSettings(parent);
|
||||
if (brand == "ford")
|
||||
return new FordSettings(parent);
|
||||
if (brand == "gm")
|
||||
return new GMSettings(parent);
|
||||
if (brand == "honda")
|
||||
return new HondaSettings(parent);
|
||||
if (brand == "hyundai")
|
||||
return new HyundaiSettings(parent);
|
||||
if (brand == "mazda")
|
||||
return new MazdaSettings(parent);
|
||||
if (brand == "nissan")
|
||||
return new NissanSettings(parent);
|
||||
if (brand == "rivian")
|
||||
return new RivianSettings(parent);
|
||||
if (brand == "subaru")
|
||||
return new SubaruSettings(parent);
|
||||
if (brand == "tesla")
|
||||
return new TeslaSettings(parent);
|
||||
if (brand == "toyota")
|
||||
return new ToyotaSettings(parent);
|
||||
if (brand == "volkswagen")
|
||||
return new VolkswagenSettings(parent);
|
||||
|
||||
// Default empty settings if brand not supported
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool BrandSettingsFactory::isBrandSupported(const QString& brand) {
|
||||
return supportedBrands.contains(brand);
|
||||
}
|
||||
|
||||
QStringList BrandSettingsFactory::getSupportedBrands() {
|
||||
return supportedBrands;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 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/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
class BrandSettingsFactory {
|
||||
|
||||
public:
|
||||
static BrandSettingsInterface* createBrandSettings(const QString &brand, QWidget *parent = nullptr);
|
||||
static bool isBrandSupported(const QString& brand);
|
||||
static QStringList getSupportedBrands();
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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/vehicle/brand_settings_interface.h"
|
||||
|
||||
BrandSettingsInterface::BrandSettingsInterface(QWidget *parent) : QWidget(parent) {
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
list = new ListWidget(this, false);
|
||||
main_layout->addWidget(list);
|
||||
}
|
||||
|
||||
void BrandSettingsInterface::updatePanel(bool _offroad) {
|
||||
offroad = _offroad;
|
||||
updateSettings();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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"
|
||||
|
||||
class BrandSettingsInterface : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BrandSettingsInterface(QWidget *parent = nullptr);
|
||||
virtual ~BrandSettingsInterface() = default;
|
||||
|
||||
void updatePanel(bool _offroad);
|
||||
virtual void updateSettings() = 0;
|
||||
|
||||
protected:
|
||||
ListWidget *list = nullptr;
|
||||
Params params;
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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/qt/offroad/settings/vehicle/chrysler_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/ford_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/gm_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/honda_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/mazda_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/nissan_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/rivian_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/toyota_settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/volkswagen_settings.h"
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/chrysler_settings.h"
|
||||
|
||||
ChryslerSettings::ChryslerSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void ChryslerSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class ChryslerSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ChryslerSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/ford_settings.h"
|
||||
|
||||
FordSettings::FordSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void FordSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class FordSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FordSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/gm_settings.h"
|
||||
|
||||
GMSettings::GMSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void GMSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class GMSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GMSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/honda_settings.h"
|
||||
|
||||
HondaSettings::HondaSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void HondaSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class HondaSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit HondaSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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/vehicle/hyundai_settings.h"
|
||||
|
||||
HyundaiSettings::HyundaiSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
std::vector<QString> tuning_texts{ tr("Off"), tr("Dynamic"), tr("Predictive") };
|
||||
longitudinalTuningToggle = new ButtonParamControl(
|
||||
"HyundaiLongitudinalTuning",
|
||||
tr("Custom Longitudinal Tuning"),
|
||||
"",
|
||||
"",
|
||||
tuning_texts,
|
||||
500
|
||||
);
|
||||
QObject::connect(longitudinalTuningToggle, &ButtonParamControlSP::buttonToggled, this, &HyundaiSettings::updateSettings);
|
||||
list->addItem(longitudinalTuningToggle);
|
||||
longitudinalTuningToggle->showDescription();
|
||||
}
|
||||
|
||||
void HyundaiSettings::updateSettings() {
|
||||
auto longitudinal_tuning_param = std::atoi(params.get("HyundaiLongitudinalTuning").c_str());
|
||||
|
||||
auto cp_bytes = params.get("CarParamsPersistent");
|
||||
if (!cp_bytes.empty()) {
|
||||
AlignedBuffer aligned_buf;
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
||||
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||
|
||||
has_longitudinal_control = hasLongitudinalControl(CP);
|
||||
} else {
|
||||
has_longitudinal_control = false;
|
||||
}
|
||||
|
||||
LongitudinalTuningOption longitudinal_tuning_option;
|
||||
if (longitudinal_tuning_param == int(LongitudinalTuningOption::PREDICTIVE)) {
|
||||
longitudinal_tuning_option = LongitudinalTuningOption::PREDICTIVE;
|
||||
} else if (longitudinal_tuning_param == int(LongitudinalTuningOption::DYNAMIC)) {
|
||||
longitudinal_tuning_option = LongitudinalTuningOption::DYNAMIC;
|
||||
} else {
|
||||
longitudinal_tuning_option = LongitudinalTuningOption::OFF;
|
||||
}
|
||||
|
||||
bool longitudinal_tuning_disabled = !offroad || !has_longitudinal_control;
|
||||
QString longitudinal_tuning_description = longitudinalTuningDescription(longitudinal_tuning_option);
|
||||
if (longitudinal_tuning_disabled) {
|
||||
longitudinal_tuning_description = toggleDisableMsg(offroad, has_longitudinal_control);
|
||||
}
|
||||
|
||||
longitudinalTuningToggle->setEnabled(!longitudinal_tuning_disabled);
|
||||
longitudinalTuningToggle->setDescription(longitudinal_tuning_description);
|
||||
longitudinalTuningToggle->showDescription();
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
enum class LongitudinalTuningOption {
|
||||
OFF,
|
||||
DYNAMIC,
|
||||
PREDICTIVE,
|
||||
};
|
||||
|
||||
class HyundaiSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit HyundaiSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool has_longitudinal_control = false;
|
||||
ButtonParamControl *longitudinalTuningToggle = nullptr;
|
||||
|
||||
static QString toggleDisableMsg(bool _offroad, bool _has_longitudinal_control) {
|
||||
if (!_has_longitudinal_control) {
|
||||
return tr("This feature can only be used with openpilot longitudinal control enabled.");
|
||||
}
|
||||
|
||||
if (!_offroad) {
|
||||
return tr("Enable \"Always Offroad\" in Device panel, or turn vehicle off to select an option.");
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
static QString longitudinalTuningDescription(LongitudinalTuningOption option = LongitudinalTuningOption::OFF) {
|
||||
QString off_str = tr("Off: Uses default tuning");
|
||||
QString dynamic_str = tr("Dynamic: Adjusts acceleration limits based on current speed");
|
||||
QString predictive_str = tr("Predictive: Uses future trajectory data to anticipate needed adjustments");
|
||||
|
||||
if (option == LongitudinalTuningOption::PREDICTIVE) {
|
||||
predictive_str = "<font color='white'><b>" + predictive_str + "</b></font>";
|
||||
} else if (option == LongitudinalTuningOption::DYNAMIC) {
|
||||
dynamic_str = "<font color='white'><b>" + dynamic_str + "</b></font>";
|
||||
} else {
|
||||
off_str = "<font color='white'><b>" + off_str + "</b></font>";
|
||||
}
|
||||
|
||||
return QString("%1<br><br>%2<br>%3<br>%4<br>")
|
||||
.arg(tr("Fine-tune your driving experience by adjusting acceleration smoothness with openpilot longitudinal control."))
|
||||
.arg(off_str)
|
||||
.arg(dynamic_str)
|
||||
.arg(predictive_str);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/mazda_settings.h"
|
||||
|
||||
MazdaSettings::MazdaSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void MazdaSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class MazdaSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MazdaSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/nissan_settings.h"
|
||||
|
||||
NissanSettings::NissanSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void NissanSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class NissanSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NissanSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -43,36 +43,73 @@ PlatformSelector::PlatformSelector() : ButtonControl(tr("Vehicle"), "", "") {
|
||||
}
|
||||
});
|
||||
|
||||
main_layout->addStretch(0);
|
||||
refresh(offroad);
|
||||
}
|
||||
|
||||
void PlatformSelector::refresh(bool _offroad) {
|
||||
QString name = getPlatformBundle("name").toString();
|
||||
platform = unrecognized_str;
|
||||
QString platform_color = YELLOW_PLATFORM;
|
||||
|
||||
if (!name.isEmpty()) {
|
||||
setValue(name);
|
||||
platform = name;
|
||||
platform_color = BLUE_PLATFORM;
|
||||
brand = getPlatformBundle("brand").toString();
|
||||
setText(tr("REMOVE"));
|
||||
} else {
|
||||
setText(tr("SEARCH"));
|
||||
|
||||
platform = unrecognized_str;
|
||||
brand = "";
|
||||
auto cp_bytes = params.get("CarParamsPersistent");
|
||||
if (!cp_bytes.empty()) {
|
||||
AlignedBuffer aligned_buf;
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
||||
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||
setValue(QString::fromStdString(CP.getCarFingerprint().cStr()));
|
||||
|
||||
platform = QString::fromStdString(CP.getCarFingerprint().cStr());
|
||||
|
||||
for (auto it = platforms.constBegin(); it != platforms.constEnd(); ++it) {
|
||||
if (it.value()["platform"].toString() == platform) {
|
||||
brand = it.value()["brand"].toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (platform == "MOCK") {
|
||||
platform = unrecognized_str;
|
||||
} else {
|
||||
platform_color = GREEN_PLATFORM;
|
||||
}
|
||||
}
|
||||
}
|
||||
setValue(platform, platform_color);
|
||||
setEnabled(true);
|
||||
emit refreshPanel();
|
||||
|
||||
offroad = _offroad;
|
||||
|
||||
FingerprintStatus cur_status;
|
||||
if (platform_color == GREEN_PLATFORM) {
|
||||
cur_status = FingerprintStatus::AUTO_FINGERPRINT;
|
||||
} else if (platform_color == BLUE_PLATFORM) {
|
||||
cur_status = FingerprintStatus::MANUAL_FINGERPRINT;
|
||||
} else {
|
||||
cur_status = FingerprintStatus::UNRECOGNIZED;
|
||||
}
|
||||
|
||||
setDescription(platformDescription(cur_status));
|
||||
showDescription();
|
||||
}
|
||||
|
||||
void PlatformSelector::setPlatform(const QString &platform) {
|
||||
QVariantMap platform_data = platforms[platform];
|
||||
void PlatformSelector::setPlatform(const QString &_platform) {
|
||||
QVariantMap platform_data = platforms[_platform];
|
||||
|
||||
const QString offroad_msg = offroad ? tr("This setting will take effect immediately.") :
|
||||
tr("This setting will take effect once the device enters offroad state.");
|
||||
const QString msg = QString("<b>%1</b><br><br>%2")
|
||||
.arg(platform, offroad_msg);
|
||||
.arg(_platform, offroad_msg);
|
||||
|
||||
QString content("<body><h2 style=\"text-align: center;\">" + tr("Vehicle Selector") + "</h2><br>"
|
||||
"<p style=\"text-align: center; margin: 0 128px; font-size: 50px;\">" + msg + "</p></body>");
|
||||
@@ -80,7 +117,7 @@ void PlatformSelector::setPlatform(const QString &platform) {
|
||||
if (ConfirmationDialog(content, tr("Confirm"), tr("Cancel"), true, this).exec()) {
|
||||
QJsonObject json_bundle;
|
||||
json_bundle["platform"] = platform_data["platform"].toString();
|
||||
json_bundle["name"] = platform;
|
||||
json_bundle["name"] = _platform;
|
||||
json_bundle["make"] = platform_data["make"].toString();
|
||||
json_bundle["brand"] = platform_data["brand"].toString();
|
||||
json_bundle["model"] = platform_data["model"].toString();
|
||||
|
||||
@@ -9,6 +9,16 @@
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
|
||||
static const QString GREEN_PLATFORM = "#00F100";
|
||||
static const QString BLUE_PLATFORM = "#0086E9";
|
||||
static const QString YELLOW_PLATFORM = "#FFD500";
|
||||
|
||||
enum class FingerprintStatus {
|
||||
AUTO_FINGERPRINT,
|
||||
MANUAL_FINGERPRINT,
|
||||
UNRECOGNIZED,
|
||||
};
|
||||
|
||||
class PlatformSelector : public ButtonControl {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -16,9 +26,15 @@ public:
|
||||
PlatformSelector();
|
||||
QVariant getPlatformBundle(const QString &key);
|
||||
|
||||
QString platform;
|
||||
QString brand;
|
||||
|
||||
public slots:
|
||||
void refresh(bool _offroad);
|
||||
|
||||
signals:
|
||||
void refreshPanel();
|
||||
|
||||
private:
|
||||
void searchPlatforms(const QString &query);
|
||||
void setPlatform(const QString &platform = "");
|
||||
@@ -26,4 +42,27 @@ private:
|
||||
|
||||
Params params;
|
||||
bool offroad;
|
||||
|
||||
QString unrecognized_str = tr("Unrecognized Vehicle");
|
||||
|
||||
static QString platformDescription(FingerprintStatus status = FingerprintStatus::UNRECOGNIZED) {
|
||||
QString auto_str = "🟢 - " + tr("Fingerprinted automatically");
|
||||
QString manual_str = "🔵 - " + tr("Manually selected");
|
||||
QString unrecognized_str = "🟡 - " + tr("Not fingerprinted or manually selected");
|
||||
|
||||
if (status == FingerprintStatus::AUTO_FINGERPRINT) {
|
||||
auto_str = "<font color='white'><b>" + auto_str + "</b></font>";
|
||||
} else if (status == FingerprintStatus::MANUAL_FINGERPRINT) {
|
||||
manual_str = "<font color='white'><b>" + manual_str + "</b></font>";
|
||||
} else {
|
||||
unrecognized_str = "<font color='white'><b>" + unrecognized_str + "</b></font>";
|
||||
}
|
||||
|
||||
return QString("%1<br>%2<br><br>%3<br>%4<br>%5")
|
||||
.arg(tr("Select vehicle to force fingerprint manually."))
|
||||
.arg(tr("Colors represent fingerprint status:"))
|
||||
.arg(auto_str)
|
||||
.arg(manual_str)
|
||||
.arg(unrecognized_str);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/rivian_settings.h"
|
||||
|
||||
RivianSettings::RivianSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void RivianSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class RivianSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RivianSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/subaru_settings.h"
|
||||
|
||||
SubaruSettings::SubaruSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void SubaruSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class SubaruSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SubaruSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/tesla_settings.h"
|
||||
|
||||
TeslaSettings::TeslaSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void TeslaSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class TeslaSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TeslaSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/toyota_settings.h"
|
||||
|
||||
ToyotaSettings::ToyotaSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void ToyotaSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class ToyotaSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ToyotaSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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/vehicle/volkswagen_settings.h"
|
||||
|
||||
VolkswagenSettings::VolkswagenSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
}
|
||||
|
||||
void VolkswagenSettings::updateSettings() {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
class VolkswagenSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit VolkswagenSettings(QWidget *parent = nullptr);
|
||||
void updateSettings() override;
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
};
|
||||
@@ -7,26 +7,32 @@
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_panel.h"
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_factory.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brands.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
|
||||
|
||||
VehiclePanel::VehiclePanel(QWidget *parent) : QFrame(parent) {
|
||||
main_layout = new QStackedLayout(this);
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->setContentsMargins(50, 20, 50, 20);
|
||||
|
||||
ListWidget *list = new ListWidget(this);
|
||||
|
||||
vehicleScreen = new QWidget(this);
|
||||
QVBoxLayout *vlayout = new QVBoxLayout(vehicleScreen);
|
||||
vlayout->setContentsMargins(50, 20, 50, 20);
|
||||
|
||||
platformSelector = new PlatformSelector();
|
||||
QObject::connect(platformSelector, &PlatformSelector::refreshPanel, this, &VehiclePanel::updateBrandSettings);
|
||||
list->addItem(platformSelector);
|
||||
|
||||
brandSettingsContainer = new QWidget(this);
|
||||
brandSettingsContainerLayout = new QVBoxLayout(brandSettingsContainer);
|
||||
brandSettingsContainerLayout->setContentsMargins(0, 0, 0, 0);
|
||||
brandSettingsContainerLayout->setSpacing(0);
|
||||
list->addItem(brandSettingsContainer);
|
||||
|
||||
ScrollViewSP *scroller = new ScrollViewSP(list, this);
|
||||
vlayout->addWidget(scroller);
|
||||
main_layout->addWidget(scroller);
|
||||
|
||||
currentBrandSettings = nullptr;
|
||||
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, this, &VehiclePanel::updatePanel);
|
||||
|
||||
main_layout->addWidget(vehicleScreen);
|
||||
main_layout->setCurrentWidget(vehicleScreen);
|
||||
}
|
||||
|
||||
void VehiclePanel::showEvent(QShowEvent *event) {
|
||||
@@ -34,7 +40,28 @@ void VehiclePanel::showEvent(QShowEvent *event) {
|
||||
}
|
||||
|
||||
void VehiclePanel::updatePanel(bool _offroad) {
|
||||
platformSelector->refresh(_offroad);
|
||||
|
||||
offroad = _offroad;
|
||||
platformSelector->refresh(_offroad);
|
||||
updateBrandSettings();
|
||||
}
|
||||
|
||||
void VehiclePanel::updateBrandSettings() {
|
||||
if (!isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentBrandSettings) {
|
||||
brandSettingsContainerLayout->removeWidget(currentBrandSettings);
|
||||
delete currentBrandSettings;
|
||||
currentBrandSettings = nullptr;
|
||||
}
|
||||
|
||||
if (BrandSettingsFactory::isBrandSupported(platformSelector->brand)) {
|
||||
currentBrandSettings = BrandSettingsFactory::createBrandSettings(platformSelector->brand, this);
|
||||
if (currentBrandSettings) {
|
||||
currentBrandSettings->setContentsMargins(0, 0, 0, 0);
|
||||
brandSettingsContainerLayout->addWidget(currentBrandSettings);
|
||||
currentBrandSettings->updatePanel(offroad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/brand_settings_interface.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/platform_selector.h"
|
||||
|
||||
class VehiclePanel : public QFrame {
|
||||
@@ -22,8 +23,12 @@ public slots:
|
||||
void updatePanel(bool _offroad);
|
||||
|
||||
private:
|
||||
QStackedLayout* main_layout = nullptr;
|
||||
QWidget* vehicleScreen = nullptr;
|
||||
PlatformSelector *platformSelector = nullptr;
|
||||
bool offroad;
|
||||
PlatformSelector* platformSelector = nullptr;
|
||||
BrandSettingsInterface* currentBrandSettings = nullptr;
|
||||
QWidget* brandSettingsContainer = nullptr;
|
||||
QVBoxLayout* brandSettingsContainerLayout = nullptr;
|
||||
bool offroad = false;
|
||||
|
||||
private slots:
|
||||
void updateBrandSettings();
|
||||
};
|
||||
|
||||
@@ -118,7 +118,7 @@ AbstractControlSP_SELECTOR::AbstractControlSP_SELECTOR(const QString &title, con
|
||||
if (!title.isEmpty()) {
|
||||
title_label = new QPushButton(title);
|
||||
title_label->setFixedHeight(120);
|
||||
title_label->setStyleSheet("font-size: 50px; font-weight: 450; text-align: left; border: none; padding: 20 0 0 0");
|
||||
title_label->setStyleSheet("font-size: 50px; font-weight: 450; text-align: left; border: none; padding: 0 0 0 0");
|
||||
main_layout->addWidget(title_label, 1);
|
||||
|
||||
connect(title_label, &QPushButton::clicked, [=]() {
|
||||
@@ -148,7 +148,7 @@ AbstractControlSP_SELECTOR::AbstractControlSP_SELECTOR(const QString &title, con
|
||||
|
||||
// description
|
||||
description = new QLabel(desc);
|
||||
description->setContentsMargins(0, 20, 40, 20);
|
||||
description->setContentsMargins(40, 20, 40, 20);
|
||||
description->setStyleSheet("font-size: 40px; color: grey");
|
||||
description->setWordWrap(true);
|
||||
description->setVisible(false);
|
||||
|
||||
@@ -219,7 +219,7 @@ class ButtonParamControlSP : public AbstractControlSP_SELECTOR {
|
||||
|
||||
public:
|
||||
ButtonParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
|
||||
const std::vector<QString> &button_texts, const int minimum_button_width = 300) : AbstractControlSP_SELECTOR(title, desc, icon), button_texts(button_texts) {
|
||||
const std::vector<QString> &button_texts, const int minimum_button_width = 300, const bool inline_layout = false) : AbstractControlSP_SELECTOR(title, desc, icon), button_texts(button_texts), is_inline_layout(inline_layout) {
|
||||
const QString style = R"(
|
||||
QPushButton {
|
||||
border-radius: 20px;
|
||||
@@ -246,6 +246,19 @@ public:
|
||||
key = param.toStdString();
|
||||
int value = atoi(params.get(key).c_str());
|
||||
|
||||
if (inline_layout) {
|
||||
button_param_layout->setMargin(0);
|
||||
button_param_layout->setSpacing(0);
|
||||
if (!title.isEmpty()) {
|
||||
main_layout->removeWidget(title_label);
|
||||
hlayout->addWidget(title_label, 1);
|
||||
}
|
||||
if (spacingItem != nullptr && main_layout->indexOf(spacingItem) != -1) {
|
||||
main_layout->removeItem(spacingItem);
|
||||
spacingItem = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
button_group = new QButtonGroup(this);
|
||||
button_group->setExclusive(true);
|
||||
for (int i = 0; i < button_texts.size(); i++) {
|
||||
@@ -254,12 +267,18 @@ public:
|
||||
button->setChecked(i == value);
|
||||
button->setStyleSheet(style);
|
||||
button->setMinimumWidth(minimum_button_width);
|
||||
if (i == 0) hlayout->addSpacing(2);
|
||||
hlayout->addWidget(button);
|
||||
if (i == 0) button_param_layout->addSpacing(2);
|
||||
button_param_layout->addWidget(button);
|
||||
button_group->addButton(button, i);
|
||||
}
|
||||
|
||||
hlayout->setAlignment(Qt::AlignLeft);
|
||||
button_param_layout->setAlignment(Qt::AlignLeft);
|
||||
if (is_inline_layout) {
|
||||
QFrame *container = new QFrame;
|
||||
container->setLayout(button_param_layout);
|
||||
container->setStyleSheet("background-color: #393939; border-radius: 20px;");
|
||||
hlayout->addWidget(container);
|
||||
}
|
||||
|
||||
QObject::connect(button_group, QOverload<int>::of(&QButtonGroup::buttonClicked), [=](int id) {
|
||||
params.put(key, std::to_string(id));
|
||||
@@ -305,15 +324,20 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void setDisabledSelectedButton(std::string val) {
|
||||
int value = atoi(val.c_str());
|
||||
void setEnableSelectedButtons(bool enable, const std::vector<int>& enabled_btns = {}) const {
|
||||
for (int i = 0; i < button_group->buttons().size(); i++) {
|
||||
button_group->buttons()[i]->setEnabled(i != value);
|
||||
// Enable the button if its index is in the enabled list
|
||||
bool should_enable = std::find(enabled_btns.begin(), enabled_btns.end(), i) != enabled_btns.end();
|
||||
button_group->buttons()[i]->setEnabled(enable && should_enable);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override {
|
||||
if (is_inline_layout) {
|
||||
return;
|
||||
}
|
||||
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
@@ -348,6 +372,8 @@ private:
|
||||
std::vector<QString> button_texts;
|
||||
|
||||
bool button_group_enabled = true;
|
||||
bool is_inline_layout;
|
||||
QHBoxLayout *button_param_layout = is_inline_layout ? new QHBoxLayout() : hlayout;
|
||||
};
|
||||
|
||||
class ListWidgetSP : public QWidget {
|
||||
@@ -360,7 +386,7 @@ public:
|
||||
outer_layout.addLayout(&inner_layout);
|
||||
inner_layout.setMargin(0);
|
||||
inner_layout.setSpacing(25); // default spacing is 25
|
||||
outer_layout.addStretch();
|
||||
outer_layout.addStretch(1);
|
||||
}
|
||||
inline void addItem(QWidget *w) { inner_layout.addWidget(w); }
|
||||
inline void addItem(QLayout *layout) { inner_layout.addLayout(layout); }
|
||||
@@ -415,15 +441,16 @@ public:
|
||||
class OptionControlSP : public AbstractControlSP_SELECTOR {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
bool isInlineLayout;
|
||||
QHBoxLayout *optionSelectorLayout = isInlineLayout ? new QHBoxLayout() : hlayout;
|
||||
|
||||
protected:
|
||||
struct MinMaxValue {
|
||||
int min_value;
|
||||
int max_value;
|
||||
};
|
||||
|
||||
private:
|
||||
bool is_inline_layout;
|
||||
QHBoxLayout *optionSelectorLayout = is_inline_layout ? new QHBoxLayout() : hlayout;
|
||||
|
||||
int getParamValue() {
|
||||
const auto param_value = QString::fromStdString(params.get(key));
|
||||
const auto result = valueMap != nullptr ? valueMap->key(param_value) : param_value;
|
||||
@@ -438,7 +465,7 @@ private:
|
||||
|
||||
public:
|
||||
OptionControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
|
||||
const MinMaxValue &range, const int per_value_change = 1, const bool inline_layout = false, const QMap<QString, QString> *valMap = nullptr) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr), _title(title), valueMap(valMap), isInlineLayout(inline_layout) {
|
||||
const MinMaxValue &range, const int per_value_change = 1, const bool inline_layout = false, const QMap<QString, QString> *valMap = nullptr) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr), _title(title), valueMap(valMap), is_inline_layout(inline_layout) {
|
||||
const QString style = R"(
|
||||
QPushButton {
|
||||
border-radius: 20px;
|
||||
@@ -513,7 +540,7 @@ public:
|
||||
}
|
||||
|
||||
optionSelectorLayout->setAlignment(Qt::AlignLeft);
|
||||
if (isInlineLayout) {
|
||||
if (is_inline_layout) {
|
||||
QFrame *container = new QFrame;
|
||||
container->setLayout(optionSelectorLayout);
|
||||
container->setStyleSheet("background-color: #393939; border-radius: 20px;");
|
||||
@@ -525,6 +552,10 @@ public:
|
||||
request_update = _update;
|
||||
}
|
||||
|
||||
void setFixedWidth(int width) {
|
||||
label.setFixedWidth(width);
|
||||
}
|
||||
|
||||
inline void setLabel(const QString &text) { label.setText(text); }
|
||||
|
||||
void setEnabled(bool enabled) {
|
||||
@@ -540,7 +571,7 @@ public:
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override {
|
||||
if (isInlineLayout) {
|
||||
if (is_inline_layout) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -633,16 +664,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
// Override mouse release event to handle style updates smoothly
|
||||
void mouseReleaseEvent(QMouseEvent *event) override {
|
||||
if (!key.empty()) {
|
||||
bool next_state = !params.getBool(key);
|
||||
updateStyle(next_state);
|
||||
}
|
||||
QPushButton::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string key = "";
|
||||
Params params;
|
||||
@@ -651,13 +672,14 @@ private:
|
||||
|
||||
QString btn_enabled_off_style = "QPushButton:enabled { background-color: #393939; }";
|
||||
QString btn_enabled_on_style = "QPushButton:enabled { background-color: #1e79e8; }";
|
||||
QString btn_pressed_style = "QPushButton:pressed { background-color: #4A4A4A; }";
|
||||
QString btn_disabled_stype = "QPushButton:disabled { background-color: #121212; color: #5C5C5C; }";
|
||||
QString btn_off_pressed_style = "QPushButton:pressed { background-color: #4A4A4A; }";
|
||||
QString btn_on_pressed_style = "QPushButton:pressed { background-color: #1E8FFF; }";
|
||||
QString btn_disabled_style = "QPushButton:disabled { background-color: #121212; color: #5C5C5C; }";
|
||||
|
||||
void updateStyle(bool enabled) {
|
||||
QString enabled_style = enabled ? btn_enabled_on_style : btn_enabled_off_style;
|
||||
|
||||
setStyleSheet(buttonStyle + enabled_style + btn_pressed_style + btn_disabled_stype);
|
||||
QString pressed_style = enabled ? btn_on_pressed_style : btn_off_pressed_style;
|
||||
setStyleSheet(buttonStyle + enabled_style + pressed_style + btn_disabled_style);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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/expandable_row.h"
|
||||
|
||||
ExpandableToggleRow::ExpandableToggleRow(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent)
|
||||
: ToggleControlSP(title, desc, icon, false, parent) {
|
||||
|
||||
key = param.toStdString();
|
||||
QObject::connect(this, &ExpandableToggleRow::toggleFlipped, this, &ExpandableToggleRow::toggleClicked);
|
||||
|
||||
collapsibleWidget = new QFrame(this);
|
||||
collapsibleWidget->setContentsMargins(0, 0, 0, 0);
|
||||
collapsibleWidget->setVisible(false);
|
||||
QVBoxLayout *collapsible_layout = new QVBoxLayout();
|
||||
collapsibleWidget->setLayout(collapsible_layout);
|
||||
|
||||
list = new ListWidgetSP(this, false);
|
||||
|
||||
main_layout->addWidget(collapsibleWidget);
|
||||
collapsible_layout->addWidget(list);
|
||||
}
|
||||
|
||||
void ExpandableToggleRow::toggleClicked(bool state) {
|
||||
params.putBool(key, state);
|
||||
collapsibleWidget->setVisible(state);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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/qt/widgets/controls.h"
|
||||
|
||||
class ExpandableToggleRow : public ToggleControlSP {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ExpandableToggleRow(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr);
|
||||
|
||||
void addItem(QWidget *widget) {
|
||||
list->addItem(widget);
|
||||
}
|
||||
|
||||
ListWidgetSP *innerList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
bool state = params.getBool(key);
|
||||
if (state != toggle.on) {
|
||||
toggle.togglePosition();
|
||||
}
|
||||
collapsibleWidget->setVisible(state);
|
||||
}
|
||||
|
||||
bool isToggled() {
|
||||
return params.getBool(key);
|
||||
}
|
||||
|
||||
void showEvent(QShowEvent *event) override {
|
||||
refresh();
|
||||
}
|
||||
|
||||
private:
|
||||
void toggleClicked(bool state);
|
||||
|
||||
std::string key;
|
||||
Params params;
|
||||
|
||||
ListWidgetSP *list;
|
||||
QFrame *collapsibleWidget = nullptr;
|
||||
};
|
||||
@@ -37,7 +37,7 @@ DATA: dict[str, capnp.lib.capnp._DynamicStructBuilder] = dict.fromkeys(
|
||||
"driverStateV2", "roadCameraState", "wideRoadCameraState", "driverCameraState"], None)
|
||||
|
||||
def setup_homescreen(click, pm: PubMaster, scroll=None):
|
||||
pass
|
||||
time.sleep(UI_DELAY)
|
||||
|
||||
def setup_settings_device(click, pm: PubMaster, scroll=None):
|
||||
click(100, 100)
|
||||
@@ -61,6 +61,7 @@ def setup_settings_software(click, pm: PubMaster, scroll=None):
|
||||
time.sleep(UI_DELAY)
|
||||
|
||||
def setup_settings_firehose(click, pm: PubMaster, scroll=None):
|
||||
setup_settings_device(click, pm)
|
||||
scroll(-400, 278, 962)
|
||||
click(278, 862)
|
||||
|
||||
@@ -151,7 +152,7 @@ def setup_keyboard_uppercase(click, pm: PubMaster, scroll=None):
|
||||
|
||||
def setup_driver_camera(click, pm: PubMaster, scroll=None):
|
||||
setup_settings_device(click, pm)
|
||||
click(1720, 620)
|
||||
click(1720, 825)
|
||||
DATA['deviceState'].deviceState.started = False
|
||||
setup_onroad(click, pm)
|
||||
DATA['deviceState'].deviceState.started = True
|
||||
@@ -239,11 +240,17 @@ def setup_settings_steering_alc(click, pm: PubMaster, scroll=None):
|
||||
click(970, 534)
|
||||
time.sleep(UI_DELAY)
|
||||
|
||||
def setup_settings_trips(click, pm: PubMaster, scroll=None):
|
||||
def setup_settings_driving(click, pm: PubMaster, scroll=None):
|
||||
setup_settings_device(click, pm)
|
||||
click(278, 962)
|
||||
time.sleep(UI_DELAY)
|
||||
|
||||
def setup_settings_trips(click, pm: PubMaster, scroll=None):
|
||||
setup_settings_device(click, pm)
|
||||
scroll(-400, 278, 962)
|
||||
click(278, 646)
|
||||
time.sleep(UI_DELAY)
|
||||
|
||||
def setup_settings_vehicle(click, pm: PubMaster, scroll=None):
|
||||
Params().put("CarPlatformBundle", json.dumps(
|
||||
{
|
||||
@@ -291,6 +298,7 @@ CASES.update({
|
||||
"settings_steering": setup_settings_steering,
|
||||
"settings_steering_mads": setup_settings_steering_mads,
|
||||
"settings_steering_alc": setup_settings_steering_alc,
|
||||
"settings_driving": setup_settings_driving,
|
||||
"settings_trips": setup_settings_trips,
|
||||
"settings_vehicle": setup_settings_vehicle,
|
||||
})
|
||||
|
||||
@@ -60,6 +60,7 @@ typedef struct UIScene {
|
||||
cereal::PandaState::PandaType pandaType;
|
||||
|
||||
cereal::LongitudinalPersonality personality;
|
||||
cereal::LongitudinalPlanSP::AccelerationPersonality accel_personality;
|
||||
|
||||
float light_sensor = -1;
|
||||
bool started, ignition, is_metric;
|
||||
|
||||
+34
-12
@@ -11,15 +11,37 @@ from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
from opendbc.sunnypilot.car.hyundai.values import HyundaiFlagsSP, HyundaiSafetyFlagsSP
|
||||
|
||||
|
||||
class MadsSteeringModeOnBrake:
|
||||
REMAIN_ACTIVE = 0
|
||||
PAUSE = 1
|
||||
DISENGAGE = 2
|
||||
|
||||
|
||||
def get_mads_limited_brands(CP: structs.CarParams) -> bool:
|
||||
return CP.brand in ("rivian", "tesla")
|
||||
|
||||
|
||||
def read_steering_mode_param(CP: structs.CarParams, params: Params):
|
||||
if get_mads_limited_brands(CP):
|
||||
return MadsSteeringModeOnBrake.DISENGAGE
|
||||
|
||||
try:
|
||||
return int(params.get("MadsSteeringMode"))
|
||||
except (ValueError, TypeError):
|
||||
return MadsSteeringModeOnBrake.REMAIN_ACTIVE
|
||||
|
||||
|
||||
def set_alternative_experience(CP: structs.CarParams, params: Params):
|
||||
enabled = params.get_bool("Mads")
|
||||
pause_lateral_on_brake = params.get_bool("MadsPauseLateralOnBrake")
|
||||
steering_mode = read_steering_mode_param(CP, params)
|
||||
|
||||
if enabled:
|
||||
CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ENABLE_MADS
|
||||
|
||||
if pause_lateral_on_brake:
|
||||
CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISENGAGE_LATERAL_ON_BRAKE
|
||||
if steering_mode == MadsSteeringModeOnBrake.DISENGAGE:
|
||||
CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.MADS_DISENGAGE_LATERAL_ON_BRAKE
|
||||
elif steering_mode == MadsSteeringModeOnBrake.PAUSE:
|
||||
CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.MADS_PAUSE_LATERAL_ON_BRAKE
|
||||
|
||||
|
||||
def set_car_specific_params(CP: structs.CarParams, CP_SP: structs.CarParamsSP, params: Params):
|
||||
@@ -31,12 +53,12 @@ def set_car_specific_params(CP: structs.CarParams, CP_SP: structs.CarParamsSP, p
|
||||
CP_SP.flags |= HyundaiFlagsSP.LONGITUDINAL_MAIN_CRUISE_TOGGLEABLE.value
|
||||
CP_SP.safetyParam |= HyundaiSafetyFlagsSP.LONG_MAIN_CRUISE_TOGGLEABLE
|
||||
|
||||
# MADS is currently not supported in Tesla due to lack of consistent states to engage controls
|
||||
# TODO-SP: To enable MADS for Tesla, identify consistent signals for MADS toggling
|
||||
if CP.brand == "tesla":
|
||||
params.remove("Mads")
|
||||
|
||||
# MADS is currently not supported in Rivian due to lack of consistent states to engage controls
|
||||
# TODO-SP: To enable MADS for Rivian, identify consistent signals for MADS toggling
|
||||
if CP.brand == "rivian":
|
||||
params.remove("Mads")
|
||||
# MADS Partial Support
|
||||
# MADS is currently partially supported for these platforms due to lack of consistent states to engage controls
|
||||
# Only MadsSteeringModeOnBrake.DISENGAGE is supported for these platforms
|
||||
# TODO-SP: To enable MADS full support for Rivian/Tesla, identify consistent signals for MADS toggling
|
||||
mads_partial_support = get_mads_limited_brands(CP)
|
||||
if mads_partial_support:
|
||||
params.put("MadsSteeringMode", "2")
|
||||
params.put_bool("MadsUnifiedEngagementMode", True)
|
||||
params.remove("MadsMainCruiseAllowed")
|
||||
|
||||
+99
-47
@@ -9,6 +9,8 @@ from cereal import log, custom
|
||||
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.hyundai.values import HyundaiFlags
|
||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
from openpilot.sunnypilot.mads.helpers import MadsSteeringModeOnBrake, read_steering_mode_param, get_mads_limited_brands
|
||||
from openpilot.sunnypilot.mads.state import StateMachine, GEARS_ALLOW_PAUSED_SILENT
|
||||
|
||||
State = custom.ModularAssistiveDrivingSystem.ModularAssistiveDrivingSystemState
|
||||
@@ -24,75 +26,105 @@ IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
|
||||
|
||||
class ModularAssistiveDrivingSystem:
|
||||
def __init__(self, selfdrive):
|
||||
self.CP = selfdrive.CP
|
||||
self.params = selfdrive.params
|
||||
|
||||
self.enabled = False
|
||||
self.active = False
|
||||
self.available = False
|
||||
self.allow_always = False
|
||||
self.no_main_cruise = False
|
||||
self.selfdrive = selfdrive
|
||||
self.selfdrive.enabled_prev = False
|
||||
self.state_machine = StateMachine(self)
|
||||
self.events = self.selfdrive.events
|
||||
self.events_sp = self.selfdrive.events_sp
|
||||
self.disengage_on_accelerator = not self.CP.alternativeExperience & ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
|
||||
|
||||
if self.selfdrive.CP.brand == "hyundai":
|
||||
if self.selfdrive.CP.flags & (HyundaiFlags.HAS_LDA_BUTTON | HyundaiFlags.CANFD):
|
||||
if self.CP.brand == "hyundai":
|
||||
if self.CP.flags & (HyundaiFlags.HAS_LDA_BUTTON | HyundaiFlags.CANFD):
|
||||
self.allow_always = True
|
||||
|
||||
if get_mads_limited_brands(self.CP):
|
||||
self.no_main_cruise = True
|
||||
|
||||
# read params on init
|
||||
self.enabled_toggle = self.params.get_bool("Mads")
|
||||
self.main_enabled_toggle = self.params.get_bool("MadsMainCruiseAllowed")
|
||||
self.pause_lateral_on_brake_toggle = self.params.get_bool("MadsPauseLateralOnBrake")
|
||||
self.steering_mode_on_brake = read_steering_mode_param(self.CP, self.params)
|
||||
self.unified_engagement_mode = self.params.get_bool("MadsUnifiedEngagementMode")
|
||||
|
||||
def read_params(self):
|
||||
self.main_enabled_toggle = self.params.get_bool("MadsMainCruiseAllowed")
|
||||
self.unified_engagement_mode = self.params.get_bool("MadsUnifiedEngagementMode")
|
||||
|
||||
def pedal_pressed_non_gas_pressed(self, CS: structs.CarState) -> bool:
|
||||
if self.events.has(EventName.pedalPressed) and not (CS.gasPressed and not self.selfdrive.CS_prev.gasPressed and self.disengage_on_accelerator):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def should_silent_lkas_enable(self, CS: structs.CarState) -> bool:
|
||||
if self.steering_mode_on_brake == MadsSteeringModeOnBrake.PAUSE and self.pedal_pressed_non_gas_pressed(CS):
|
||||
return False
|
||||
|
||||
if self.events_sp.contains_in_list(GEARS_ALLOW_PAUSED_SILENT):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def block_unified_engagement_mode(self) -> bool:
|
||||
# UEM disabled
|
||||
if not self.unified_engagement_mode:
|
||||
return True
|
||||
|
||||
if self.enabled:
|
||||
return True
|
||||
|
||||
if self.selfdrive.enabled and self.selfdrive.enabled_prev:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_wrong_car_mode(self, alert_only: bool) -> None:
|
||||
if alert_only:
|
||||
if self.events.has(EventName.wrongCarMode):
|
||||
self.replace_event(EventName.wrongCarMode, EventNameSP.wrongCarModeAlertOnly)
|
||||
else:
|
||||
self.events.remove(EventName.wrongCarMode)
|
||||
|
||||
def transition_paused_state(self):
|
||||
if self.state_machine.state != State.paused:
|
||||
self.events_sp.add(EventNameSP.silentLkasDisable)
|
||||
|
||||
def replace_event(self, old_event: int, new_event: int):
|
||||
self.events.remove(old_event)
|
||||
self.events_sp.add(new_event)
|
||||
|
||||
def update_events(self, CS: structs.CarState):
|
||||
def update_unified_engagement_mode():
|
||||
uem_blocked = self.enabled or (self.selfdrive.enabled and self.selfdrive.enabled_prev)
|
||||
if (self.unified_engagement_mode and uem_blocked) or not self.unified_engagement_mode:
|
||||
self.events.remove(EventName.pcmEnable)
|
||||
self.events.remove(EventName.buttonEnable)
|
||||
|
||||
def transition_paused_state():
|
||||
if self.state_machine.state != State.paused:
|
||||
self.events_sp.add(EventNameSP.silentLkasDisable)
|
||||
|
||||
def replace_event(old_event: int, new_event: int):
|
||||
self.events.remove(old_event)
|
||||
self.events_sp.add(new_event)
|
||||
|
||||
if not self.selfdrive.enabled and self.enabled:
|
||||
if self.events.has(EventName.doorOpen):
|
||||
replace_event(EventName.doorOpen, EventNameSP.silentDoorOpen)
|
||||
transition_paused_state()
|
||||
self.replace_event(EventName.doorOpen, EventNameSP.silentDoorOpen)
|
||||
self.transition_paused_state()
|
||||
if self.events.has(EventName.seatbeltNotLatched):
|
||||
replace_event(EventName.seatbeltNotLatched, EventNameSP.silentSeatbeltNotLatched)
|
||||
transition_paused_state()
|
||||
if self.events.has(EventName.wrongGear) and (CS.standstill or CS.gearShifter == GearShifter.reverse):
|
||||
replace_event(EventName.wrongGear, EventNameSP.silentWrongGear)
|
||||
transition_paused_state()
|
||||
self.replace_event(EventName.seatbeltNotLatched, EventNameSP.silentSeatbeltNotLatched)
|
||||
self.transition_paused_state()
|
||||
if self.events.has(EventName.wrongGear) and (CS.vEgo < 2.5 or CS.gearShifter == GearShifter.reverse):
|
||||
self.replace_event(EventName.wrongGear, EventNameSP.silentWrongGear)
|
||||
self.transition_paused_state()
|
||||
if self.events.has(EventName.reverseGear):
|
||||
replace_event(EventName.reverseGear, EventNameSP.silentReverseGear)
|
||||
transition_paused_state()
|
||||
self.replace_event(EventName.reverseGear, EventNameSP.silentReverseGear)
|
||||
self.transition_paused_state()
|
||||
if self.events.has(EventName.brakeHold):
|
||||
replace_event(EventName.brakeHold, EventNameSP.silentBrakeHold)
|
||||
transition_paused_state()
|
||||
self.replace_event(EventName.brakeHold, EventNameSP.silentBrakeHold)
|
||||
self.transition_paused_state()
|
||||
if self.events.has(EventName.parkBrake):
|
||||
replace_event(EventName.parkBrake, EventNameSP.silentParkBrake)
|
||||
transition_paused_state()
|
||||
self.replace_event(EventName.parkBrake, EventNameSP.silentParkBrake)
|
||||
self.transition_paused_state()
|
||||
|
||||
if self.pause_lateral_on_brake_toggle:
|
||||
if CS.brakePressed:
|
||||
transition_paused_state()
|
||||
|
||||
if not (self.pause_lateral_on_brake_toggle and CS.brakePressed) and \
|
||||
not self.events_sp.contains_in_list(GEARS_ALLOW_PAUSED_SILENT):
|
||||
if self.state_machine.state == State.paused:
|
||||
self.events_sp.add(EventNameSP.silentLkasEnable)
|
||||
if self.steering_mode_on_brake == MadsSteeringModeOnBrake.PAUSE:
|
||||
if self.pedal_pressed_non_gas_pressed(CS):
|
||||
self.transition_paused_state()
|
||||
|
||||
self.events.remove(EventName.preEnableStandstill)
|
||||
self.events.remove(EventName.belowEngageSpeed)
|
||||
@@ -100,8 +132,19 @@ class ModularAssistiveDrivingSystem:
|
||||
self.events.remove(EventName.cruiseDisabled)
|
||||
self.events.remove(EventName.manualRestart)
|
||||
|
||||
if self.events.has(EventName.pcmEnable) or self.events.has(EventName.buttonEnable):
|
||||
update_unified_engagement_mode()
|
||||
selfdrive_enable_events = self.events.has(EventName.pcmEnable) or self.events.has(EventName.buttonEnable)
|
||||
set_speed_btns_enable = any(be.type in SET_SPEED_BUTTONS for be in CS.buttonEvents)
|
||||
|
||||
# wrongCarMode alert only or actively block control
|
||||
self.get_wrong_car_mode(selfdrive_enable_events or set_speed_btns_enable)
|
||||
|
||||
if selfdrive_enable_events:
|
||||
if self.pedal_pressed_non_gas_pressed(CS):
|
||||
self.events_sp.add(EventNameSP.pedalPressedAlertOnly)
|
||||
|
||||
if self.block_unified_engagement_mode():
|
||||
self.events.remove(EventName.pcmEnable)
|
||||
self.events.remove(EventName.buttonEnable)
|
||||
else:
|
||||
if self.main_enabled_toggle:
|
||||
if CS.cruiseState.available and not self.selfdrive.CS_prev.cruiseState.available:
|
||||
@@ -120,20 +163,29 @@ class ModularAssistiveDrivingSystem:
|
||||
else:
|
||||
self.events_sp.add(EventNameSP.lkasEnable)
|
||||
|
||||
if not CS.cruiseState.available:
|
||||
if not CS.cruiseState.available and not self.no_main_cruise:
|
||||
self.events.remove(EventName.buttonEnable)
|
||||
if self.selfdrive.CS_prev.cruiseState.available:
|
||||
self.events_sp.add(EventNameSP.lkasDisable)
|
||||
|
||||
if self.steering_mode_on_brake == MadsSteeringModeOnBrake.DISENGAGE:
|
||||
if self.pedal_pressed_non_gas_pressed(CS):
|
||||
if self.enabled:
|
||||
self.events_sp.add(EventNameSP.lkasDisable)
|
||||
else:
|
||||
# block lkasEnable if being sent, then send pedalPressedAlertOnly event
|
||||
if self.events_sp.contains(EventNameSP.lkasEnable):
|
||||
self.events_sp.remove(EventNameSP.lkasEnable)
|
||||
self.events_sp.add(EventNameSP.pedalPressedAlertOnly)
|
||||
|
||||
if self.should_silent_lkas_enable(CS):
|
||||
if self.state_machine.state == State.paused:
|
||||
self.events_sp.add(EventNameSP.silentLkasEnable)
|
||||
|
||||
self.events.remove(EventName.pcmDisable)
|
||||
self.events.remove(EventName.buttonCancel)
|
||||
self.events.remove(EventName.pedalPressed)
|
||||
self.events.remove(EventName.wrongCruiseMode)
|
||||
if any(be.type in SET_SPEED_BUTTONS for be in CS.buttonEvents):
|
||||
if self.events.has(EventName.wrongCarMode):
|
||||
replace_event(EventName.wrongCarMode, EventNameSP.wrongCarModeAlertOnly)
|
||||
else:
|
||||
self.events.remove(EventName.wrongCarMode)
|
||||
|
||||
def update(self, CS: structs.CarState):
|
||||
if not self.enabled_toggle:
|
||||
@@ -141,7 +193,7 @@ class ModularAssistiveDrivingSystem:
|
||||
|
||||
self.update_events(CS)
|
||||
|
||||
if not self.selfdrive.CP.passive and self.selfdrive.initialized:
|
||||
if not self.CP.passive and self.selfdrive.initialized:
|
||||
self.enabled, self.active = self.state_machine.update()
|
||||
|
||||
# Copy of previous SelfdriveD states for MADS events handling
|
||||
|
||||
@@ -51,7 +51,7 @@ class StateMachine:
|
||||
if self.state != State.disabled:
|
||||
# user and immediate disable always have priority in a non-disabled state
|
||||
if self.check_contains(ET.USER_DISABLE):
|
||||
if self._events_sp.has(EventNameSP.silentLkasDisable) or self._events_sp.has(EventNameSP.silentBrakeHold):
|
||||
if self._events_sp.has(EventNameSP.silentLkasDisable):
|
||||
self.state = State.paused
|
||||
else:
|
||||
self.state = State.disabled
|
||||
|
||||
@@ -73,7 +73,7 @@ class TestMADSStateMachine:
|
||||
self.clear_events()
|
||||
|
||||
def test_user_disable_to_paused(self):
|
||||
paused_events = (EventNameSP.silentLkasDisable, EventNameSP.silentBrakeHold)
|
||||
paused_events = (EventNameSP.silentLkasDisable, )
|
||||
for state in ALL_STATES:
|
||||
for et in MAINTAIN_STATES[state]:
|
||||
self.events_sp.add(make_event([et, ET.USER_DISABLE]))
|
||||
|
||||
@@ -52,3 +52,8 @@ commonmodel_lib = lenv.Library('commonmodel', common_src)
|
||||
lenvCython.Program('runners/runmodel_pyx.so', 'runners/runmodel_pyx.pyx', LIBS=cython_libs, FRAMEWORKS=frameworks)
|
||||
lenvCython.Program('runners/snpemodel_pyx.so', 'runners/snpemodel_pyx.pyx', LIBS=[snpemodel_lib, snpe_lib, *cython_libs], FRAMEWORKS=frameworks, RPATH=snpe_rpath)
|
||||
lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks)
|
||||
|
||||
if arch == "larch64":
|
||||
thneed_lib = env.SharedLibrary('thneed', thneed_src, LIBS=[gpucommon, common, 'OpenCL', 'dl'])
|
||||
thneedmodel_lib = env.Library('thneedmodel', ['runners/thneedmodel.cc'])
|
||||
lenvCython.Program('runners/thneedmodel_pyx.so', 'runners/thneedmodel_pyx.pyx', LIBS=envCython["LIBS"]+[thneedmodel_lib, thneed_lib, gpucommon, common, 'dl', 'OpenCL'])
|
||||
@@ -23,7 +23,7 @@ from openpilot.sunnypilot.modeld.parse_model_outputs import Parser
|
||||
from openpilot.sunnypilot.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState
|
||||
from openpilot.sunnypilot.modeld.constants import ModelConstants
|
||||
from openpilot.sunnypilot.modeld.models.commonmodel_pyx import ModelFrame, CLContext
|
||||
from openpilot.sunnypilot.modeld.runners.run_helpers import get_model_path, load_metadata, prepare_inputs, load_meta_constants
|
||||
from openpilot.sunnypilot.models.helpers import get_model_path, load_metadata, prepare_inputs, load_meta_constants
|
||||
|
||||
PROCESS_NAME = "selfdrive.modeld.modeld_snpe"
|
||||
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
"""
|
||||
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pickle
|
||||
import numpy as np
|
||||
|
||||
from cereal import custom
|
||||
from openpilot.sunnypilot.modeld.constants import Meta, MetaTombRaider, MetaSimPose
|
||||
from openpilot.sunnypilot.modeld.runners import ModelRunner
|
||||
from openpilot.sunnypilot.models.helpers import get_active_bundle
|
||||
from openpilot.system.hardware import PC
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
from pathlib import Path
|
||||
|
||||
USE_ONNX = os.getenv('USE_ONNX', PC)
|
||||
|
||||
CUSTOM_MODEL_PATH = Paths.model_root()
|
||||
METADATA_PATH = Path(__file__).parent / '../models/supercombo_metadata.pkl'
|
||||
|
||||
ModelManager = custom.ModelManagerSP
|
||||
|
||||
|
||||
def get_model_path():
|
||||
if USE_ONNX:
|
||||
return {ModelRunner.ONNX: Path(__file__).parent / '../models/supercombo.onnx'}
|
||||
|
||||
if bundle := get_active_bundle():
|
||||
drive_model = next(model for model in bundle.models if model.type == ModelManager.Type.drive)
|
||||
return {ModelRunner.THNEED: f"{CUSTOM_MODEL_PATH}/{drive_model.fileName}"}
|
||||
|
||||
return {ModelRunner.THNEED: Path(__file__).parent / '../models/supercombo.thneed'}
|
||||
|
||||
|
||||
def load_metadata():
|
||||
metadata_path = METADATA_PATH
|
||||
|
||||
if bundle := get_active_bundle():
|
||||
metadata_model = next(model for model in bundle.models if model.type == ModelManager.Type.metadata)
|
||||
metadata_path = f"{CUSTOM_MODEL_PATH}/{metadata_model.fileName}"
|
||||
|
||||
with open(metadata_path, 'rb') as f:
|
||||
return pickle.load(f)
|
||||
|
||||
|
||||
def prepare_inputs(model_metadata) -> dict[str, np.ndarray]:
|
||||
# img buffers are managed in openCL transform code so we don't pass them as inputs
|
||||
inputs = {
|
||||
k: np.zeros(v, dtype=np.float32).flatten()
|
||||
for k, v in model_metadata['input_shapes'].items()
|
||||
if 'img' not in k
|
||||
}
|
||||
|
||||
return inputs
|
||||
|
||||
|
||||
def load_meta_constants(model_metadata):
|
||||
"""
|
||||
Determines and loads the appropriate meta model class based on the metadata provided. The function checks
|
||||
specific keys and conditions within the provided metadata dictionary to identify the corresponding meta
|
||||
model class to return.
|
||||
|
||||
:param model_metadata: Dictionary containing metadata about the model. It includes
|
||||
details such as input shapes, output slices, and other configurations for identifying
|
||||
metadata-dependent meta model classes.
|
||||
:type model_metadata: dict
|
||||
:return: The appropriate meta model class (Meta, MetaSimPose, or MetaTombRaider)
|
||||
based on the conditions and metadata provided.
|
||||
:rtype: type
|
||||
"""
|
||||
meta = Meta # Default Meta
|
||||
|
||||
if 'sim_pose' in model_metadata['input_shapes'].keys():
|
||||
# Meta for models with sim_pose input
|
||||
meta = MetaSimPose
|
||||
else:
|
||||
# Meta for Tomb Raider, it does not include sim_pose input but has the same meta slice as previous models
|
||||
meta_slice = model_metadata['output_slices']['meta']
|
||||
meta_tf_slice = slice(5868, 5921, None)
|
||||
|
||||
if (
|
||||
meta_slice.start == meta_tf_slice.start and
|
||||
meta_slice.stop == meta_tf_slice.stop and
|
||||
meta_slice.step == meta_tf_slice.step
|
||||
):
|
||||
meta = MetaTombRaider
|
||||
|
||||
return meta
|
||||
@@ -8,6 +8,7 @@ from openpilot.sunnypilot.modeld_v2 import MODEL_PATH, MODEL_PKL_PATH, METADATA_
|
||||
from openpilot.sunnypilot.modeld_v2.models.commonmodel_pyx import DrivingModelFrame, CLMem
|
||||
from openpilot.sunnypilot.modeld_v2.runners.ort_helpers import make_onnx_cpu_runner, ORT_TYPES_TO_NP_TYPES
|
||||
from openpilot.sunnypilot.modeld_v2.runners.tinygrad_helpers import qcom_tensor_from_opencl_address
|
||||
from openpilot.sunnypilot.modeld_v2.parse_model_outputs import Parser
|
||||
from openpilot.system.hardware import TICI
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
|
||||
@@ -34,8 +35,8 @@ class ModelRunner(ABC):
|
||||
|
||||
if bundle := get_active_bundle():
|
||||
bundle_models = {model.type.raw: model for model in bundle.models}
|
||||
self._drive_model = bundle_models.get(ModelManager.Type.drive)
|
||||
self._metadata_model = bundle_models.get(ModelManager.Type.metadata)
|
||||
self._drive_model = bundle_models.get(ModelManager.Model.Type.supercombo)
|
||||
self._metadata_model = self._drive_model.metadata
|
||||
self.is_20hz = bundle.is20hz
|
||||
|
||||
# Override the metadata path if a metadata model is found in the active bundle
|
||||
@@ -48,6 +49,7 @@ class ModelRunner(ABC):
|
||||
self.input_shapes = self.model_metadata['input_shapes']
|
||||
self.output_slices = self.model_metadata['output_slices']
|
||||
self.inputs: dict = {}
|
||||
self.parser = Parser()
|
||||
|
||||
@abstractmethod
|
||||
def prepare_inputs(self, imgs_cl: dict[str, CLMem], numpy_inputs: dict[str, np.ndarray], frames: dict[str, DrivingModelFrame]) -> dict:
|
||||
@@ -55,16 +57,22 @@ class ModelRunner(ABC):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def run_model(self):
|
||||
def _run_model(self):
|
||||
"""Run model inference with prepared inputs."""
|
||||
raise NotImplementedError("This method should be implemented in subclasses.")
|
||||
|
||||
def slice_outputs(self, model_outputs: np.ndarray) -> dict:
|
||||
def _slice_outputs(self, model_outputs: np.ndarray) -> dict:
|
||||
"""Slice model outputs according to metadata configuration."""
|
||||
parsed_outputs = {k: model_outputs[np.newaxis, v] for k, v in self.output_slices.items()}
|
||||
if SEND_RAW_PRED:
|
||||
parsed_outputs['raw_pred'] = model_outputs.copy()
|
||||
return parsed_outputs
|
||||
|
||||
def run_model(self) -> dict[str, np.ndarray]:
|
||||
"""Run model inference with prepared inputs and parse outputs."""
|
||||
result: dict[str, np.ndarray] = self.parser.parse_outputs(self._slice_outputs(self._run_model()))
|
||||
return result
|
||||
|
||||
|
||||
class TinygradRunner(ModelRunner):
|
||||
"""Tinygrad implementation of model runner for TICI hardware."""
|
||||
@@ -74,7 +82,7 @@ class TinygradRunner(ModelRunner):
|
||||
|
||||
model_pkl_path = MODEL_PKL_PATH
|
||||
if self._drive_model:
|
||||
model_pkl_path = f"{CUSTOM_MODEL_PATH}/{self._drive_model.fileName}"
|
||||
model_pkl_path = f"{CUSTOM_MODEL_PATH}/{self._drive_model.artifact.fileName}"
|
||||
assert model_pkl_path.endswith('_tinygrad.pkl'), f"Invalid model file: {model_pkl_path} for TinygradRunner"
|
||||
|
||||
# Load Tinygrad model
|
||||
@@ -108,7 +116,7 @@ class TinygradRunner(ModelRunner):
|
||||
|
||||
return self.inputs
|
||||
|
||||
def run_model(self):
|
||||
def _run_model(self):
|
||||
return self.model_run(**self.inputs).numpy().flatten()
|
||||
|
||||
|
||||
@@ -130,5 +138,5 @@ class ONNXRunner(ModelRunner):
|
||||
self.inputs[key] = frames[key].buffer_from_cl(imgs_cl[key]).reshape(self.input_shapes[key]).astype(dtype=self.input_to_nptype[key])
|
||||
return self.inputs
|
||||
|
||||
def run_model(self):
|
||||
def _run_model(self):
|
||||
return self.runner.run(None, self.inputs)[0].flatten()
|
||||
|
||||
@@ -18,7 +18,6 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS
|
||||
from openpilot.common.transformations.model import get_warp_matrix
|
||||
from openpilot.system import sentry
|
||||
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
|
||||
from openpilot.sunnypilot.modeld_v2.parse_model_outputs import Parser
|
||||
from openpilot.sunnypilot.modeld_v2.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState
|
||||
from openpilot.sunnypilot.modeld_v2.constants import ModelConstants
|
||||
from openpilot.sunnypilot.modeld_v2.models.commonmodel_pyx import DrivingModelFrame, CLContext
|
||||
@@ -27,6 +26,7 @@ from openpilot.sunnypilot.modeld_v2.meta_helper import load_meta_constants
|
||||
from openpilot.sunnypilot.modeld_v2.model_runner import ONNXRunner, TinygradRunner
|
||||
|
||||
PROCESS_NAME = "selfdrive.modeld.modeld"
|
||||
LAT_SMOOTH_SECONDS = 0.0
|
||||
|
||||
|
||||
class FrameMeta:
|
||||
@@ -41,7 +41,6 @@ class FrameMeta:
|
||||
class ModelState:
|
||||
frames: dict[str, DrivingModelFrame]
|
||||
inputs: dict[str, np.ndarray]
|
||||
output: np.ndarray
|
||||
prev_desire: np.ndarray # for tracking the rising edge of the pulse
|
||||
|
||||
def __init__(self, context: CLContext):
|
||||
@@ -54,8 +53,8 @@ class ModelState:
|
||||
self.frames = {'input_imgs': DrivingModelFrame(context, buffer_length), 'big_input_imgs': DrivingModelFrame(context, buffer_length)}
|
||||
self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
|
||||
if self.model_runner.is_20hz:
|
||||
self.full_features_20Hz = np.zeros((ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32)
|
||||
self.desire_20Hz = np.zeros((ModelConstants.FULL_HISTORY_BUFFER_LEN + 1, ModelConstants.DESIRE_LEN), dtype=np.float32)
|
||||
self.full_features_buffer = np.zeros((ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32)
|
||||
self.full_desire = np.zeros((ModelConstants.FULL_HISTORY_BUFFER_LEN + 1, ModelConstants.DESIRE_LEN), dtype=np.float32)
|
||||
|
||||
# img buffers are managed in openCL transform code
|
||||
self.numpy_inputs = {}
|
||||
@@ -64,15 +63,10 @@ class ModelState:
|
||||
if key not in self.frames: # Managed by opencl
|
||||
self.numpy_inputs[key] = np.zeros(shape, dtype=np.float32)
|
||||
|
||||
self.parser = Parser()
|
||||
|
||||
if self.model_runner.is_20hz:
|
||||
net_output_size = self.model_runner.model_metadata['output_shapes']['outputs'][1]
|
||||
self.output = np.zeros(net_output_size, dtype=np.float32)
|
||||
|
||||
num_elements = self.numpy_inputs['features_buffer'].shape[1]
|
||||
step_size = int(-100 / num_elements)
|
||||
self.full_features_20Hz_idxs = np.arange(step_size, step_size * (num_elements + 1), step_size)[::-1]
|
||||
self.full_features_buffer_idxs = np.arange(step_size, step_size * (num_elements + 1), step_size)[::-1]
|
||||
self.desire_reshape_dims = (self.numpy_inputs['desire'].shape[0], self.numpy_inputs['desire'].shape[1], -1, self.numpy_inputs['desire'].shape[2])
|
||||
|
||||
def run(self, buf: VisionBuf, wbuf: VisionBuf, transform: np.ndarray, transform_wide: np.ndarray,
|
||||
@@ -83,9 +77,9 @@ class ModelState:
|
||||
self.prev_desire[:] = inputs['desire']
|
||||
|
||||
if self.model_runner.is_20hz:
|
||||
self.desire_20Hz[:-1] = self.desire_20Hz[1:]
|
||||
self.desire_20Hz[-1] = new_desire
|
||||
self.numpy_inputs['desire'][:] = self.desire_20Hz.reshape(self.desire_reshape_dims).max(axis=2)
|
||||
self.full_desire[:-1] = self.full_desire[1:]
|
||||
self.full_desire[-1] = new_desire
|
||||
self.numpy_inputs['desire'][:] = self.full_desire.reshape(self.desire_reshape_dims).max(axis=2)
|
||||
else:
|
||||
length = inputs['desire'].shape[0]
|
||||
self.numpy_inputs['desire'][0, :-1] = self.numpy_inputs['desire'][0, 1:]
|
||||
@@ -105,13 +99,12 @@ class ModelState:
|
||||
return None
|
||||
|
||||
# Run model inference
|
||||
self.output = self.model_runner.run_model()
|
||||
outputs = self.parser.parse_outputs(self.model_runner.slice_outputs(self.output))
|
||||
outputs = self.model_runner.run_model()
|
||||
|
||||
if self.model_runner.is_20hz:
|
||||
self.full_features_20Hz[:-1] = self.full_features_20Hz[1:]
|
||||
self.full_features_20Hz[-1] = outputs['hidden_state'][0, :]
|
||||
self.numpy_inputs['features_buffer'][:] = self.full_features_20Hz[self.full_features_20Hz_idxs]
|
||||
self.full_features_buffer[:-1] = self.full_features_buffer[1:]
|
||||
self.full_features_buffer[-1] = outputs['hidden_state'][0, :]
|
||||
self.numpy_inputs['features_buffer'][:] = self.full_features_buffer[self.full_features_buffer_idxs]
|
||||
else:
|
||||
feature_len = outputs['hidden_state'].shape[1]
|
||||
self.numpy_inputs['features_buffer'][0, :-1] = self.numpy_inputs['features_buffer'][0, 1:]
|
||||
@@ -171,7 +164,7 @@ def main(demo=False):
|
||||
|
||||
# messaging
|
||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
|
||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl"])
|
||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
||||
|
||||
publish_state = PublishState()
|
||||
params = Params()
|
||||
@@ -196,8 +189,8 @@ def main(demo=False):
|
||||
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
|
||||
cloudlog.info("modeld got CarParams: %s", CP.brand)
|
||||
|
||||
# TODO this needs more thought, use .2s extra for now to estimate other delays
|
||||
steer_delay = CP.steerActuatorDelay + .2
|
||||
# Enable lagd support for modeld_v2
|
||||
steer_delay = sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS
|
||||
|
||||
DH = DesireHelper()
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import time
|
||||
import requests
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from sunnypilot.models.helpers import is_bundle_version_compatible
|
||||
|
||||
from cereal import custom
|
||||
|
||||
@@ -19,68 +20,49 @@ class ModelParser:
|
||||
"""Handles parsing of model data into cereal objects"""
|
||||
|
||||
@staticmethod
|
||||
def _parse_model(full_name: str, file_name: str, uri_data: dict,
|
||||
model_type: custom.ModelManagerSP.Type) -> custom.ModelManagerSP.Model:
|
||||
model = custom.ModelManagerSP.Model()
|
||||
def _parse_download_uri(download_uri_data) -> custom.ModelManagerSP.DownloadUri:
|
||||
download_uri = custom.ModelManagerSP.DownloadUri()
|
||||
download_uri.uri = download_uri_data.get("url")
|
||||
download_uri.sha256 = download_uri_data.get("sha256")
|
||||
return download_uri
|
||||
|
||||
download_uri.uri = uri_data["url"]
|
||||
download_uri.sha256 = uri_data["sha256"]
|
||||
@staticmethod
|
||||
def _parse_artifact(artifact_data) -> custom.ModelManagerSP.Artifact:
|
||||
artifact = custom.ModelManagerSP.Artifact()
|
||||
artifact.fileName = artifact_data.get("file_name")
|
||||
artifact.downloadUri = ModelParser._parse_download_uri(artifact_data.get("download_uri", {}))
|
||||
return artifact
|
||||
|
||||
model.fullName = full_name
|
||||
model.fileName = file_name
|
||||
model.downloadUri = download_uri
|
||||
model.type = model_type
|
||||
@staticmethod
|
||||
def _parse_model(model_data) -> custom.ModelManagerSP.Model:
|
||||
model = custom.ModelManagerSP.Model()
|
||||
|
||||
model.type = model_data.get("type")
|
||||
model.artifact = ModelParser._parse_artifact(model_data.get("artifact", {}))
|
||||
if metadata := model_data.get("metadata"):
|
||||
model.metadata = ModelParser._parse_artifact(metadata)
|
||||
return model
|
||||
|
||||
@staticmethod
|
||||
def _parse_bundle(key: str, value: dict) -> custom.ModelManagerSP.ModelBundle:
|
||||
def _parse_bundle(bundle) -> custom.ModelManagerSP.ModelBundle:
|
||||
model_bundle = custom.ModelManagerSP.ModelBundle()
|
||||
|
||||
# Parse main driving model
|
||||
models = [
|
||||
ModelParser._parse_model(
|
||||
value["full_name"],
|
||||
value["file_name"],
|
||||
value["download_uri"],
|
||||
custom.ModelManagerSP.Type.drive
|
||||
)
|
||||
]
|
||||
|
||||
# Parse navigation model if exists
|
||||
if value.get("download_uri_nav"):
|
||||
models.append(ModelParser._parse_model(
|
||||
value["full_name_nav"],
|
||||
value["file_name_nav"],
|
||||
value["download_uri_nav"],
|
||||
custom.ModelManagerSP.Type.navigation
|
||||
))
|
||||
|
||||
# Parse metadata model if exists
|
||||
if value.get("download_uri_metadata"):
|
||||
models.append(ModelParser._parse_model(
|
||||
value["full_name_metadata"],
|
||||
value["file_name_metadata"],
|
||||
value["download_uri_metadata"],
|
||||
custom.ModelManagerSP.Type.metadata
|
||||
))
|
||||
|
||||
model_bundle.index = int(value["index"])
|
||||
model_bundle.internalName = key
|
||||
model_bundle.displayName = value["display_name"]
|
||||
model_bundle.models = models
|
||||
model_bundle.index = int(bundle["index"])
|
||||
model_bundle.internalName = bundle["short_name"]
|
||||
model_bundle.displayName = bundle["display_name"]
|
||||
model_bundle.models = [ModelParser._parse_model(model) for model in bundle.get("models",[])]
|
||||
model_bundle.status = 0
|
||||
model_bundle.generation = int(value["generation"])
|
||||
model_bundle.environment = value["environment"]
|
||||
model_bundle.runner = value.get("runner", custom.ModelManagerSP.Runner.snpe)
|
||||
model_bundle.is20hz = value.get("is_20hz", False)
|
||||
model_bundle.generation = int(bundle["generation"])
|
||||
model_bundle.environment = bundle["environment"]
|
||||
model_bundle.runner = bundle.get("runner", custom.ModelManagerSP.Runner.snpe)
|
||||
model_bundle.is20hz = bundle.get("is_20hz", False)
|
||||
model_bundle.minimumSelectorVersion = int(bundle["minimum_selector_version"])
|
||||
|
||||
return model_bundle
|
||||
|
||||
@staticmethod
|
||||
def parse_models(json_data: dict) -> list[custom.ModelManagerSP.ModelBundle]:
|
||||
return [ModelParser._parse_bundle(key, value) for key, value in json_data.items()]
|
||||
found_bundles = [ModelParser._parse_bundle(bundle) for bundle in json_data.get("bundles", [])]
|
||||
return [bundle for bundle in found_bundles if is_bundle_version_compatible(bundle.to_dict())]
|
||||
|
||||
|
||||
class ModelCache:
|
||||
@@ -122,7 +104,7 @@ class ModelCache:
|
||||
|
||||
class ModelFetcher:
|
||||
"""Handles fetching and caching of model data from remote source"""
|
||||
MODEL_URL = "https://docs.sunnypilot.ai/driving_models_v2.json"
|
||||
MODEL_URL = "https://docs.sunnypilot.ai/driving_models_v3.json"
|
||||
|
||||
def __init__(self, params: Params):
|
||||
self.params = params
|
||||
@@ -143,7 +125,7 @@ class ModelFetcher:
|
||||
cloudlog.exception("Error fetching models")
|
||||
raise
|
||||
|
||||
def get_available_models(self) -> list[custom.ModelManagerSP.ModelBundle]:
|
||||
def get_available_bundles(self) -> list[custom.ModelManagerSP.ModelBundle]:
|
||||
"""Gets the list of available models, with smart cache handling"""
|
||||
cached_data, is_expired = self.model_cache.get()
|
||||
|
||||
@@ -160,3 +142,16 @@ class ModelFetcher:
|
||||
|
||||
cloudlog.warning("Failed to fetch fresh data. Using expired cache as fallback")
|
||||
return self.model_parser.parse_models(cached_data)
|
||||
|
||||
if __name__ == "__main__":
|
||||
params = Params()
|
||||
model_fetcher = ModelFetcher(params)
|
||||
bundles = model_fetcher.get_available_bundles()
|
||||
for bundle in bundles:
|
||||
for model in bundle.models:
|
||||
# Print model details
|
||||
print(f"Bundle: {bundle.internalName}, Type: {model.type}, Status: {bundle.status}")
|
||||
# Print artifact details
|
||||
print(f"Artifact: {model.artifact.fileName}, Download URI: {model.artifact.downloadUri.uri}")
|
||||
# Print metadata details
|
||||
print(f"Metadata: {model.metadata.fileName}, Download URI: {model.metadata.downloadUri.uri}")
|
||||
|
||||
@@ -7,8 +7,27 @@ See the LICENSE.md file in the root directory for more details.
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import pickle
|
||||
import numpy as np
|
||||
import json
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from cereal import custom, messaging
|
||||
from cereal import custom
|
||||
from openpilot.sunnypilot.modeld.constants import Meta, MetaTombRaider, MetaSimPose
|
||||
from openpilot.sunnypilot.modeld.runners import ModelRunner
|
||||
from openpilot.system.hardware import PC
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
from pathlib import Path
|
||||
|
||||
CURRENT_SELECTOR_VERSION = 3
|
||||
REQUIRED_MIN_SELECTOR_VERSION = 2
|
||||
|
||||
USE_ONNX = os.getenv('USE_ONNX', PC)
|
||||
|
||||
CUSTOM_MODEL_PATH = Paths.model_root()
|
||||
METADATA_PATH = Path(__file__).parent / '../models/supercombo_metadata.pkl'
|
||||
|
||||
ModelManager = custom.ModelManagerSP
|
||||
|
||||
|
||||
async def verify_file(file_path: str, expected_hash: str) -> bool:
|
||||
@@ -24,13 +43,36 @@ async def verify_file(file_path: str, expected_hash: str) -> bool:
|
||||
return sha256_hash.hexdigest().lower() == expected_hash.lower()
|
||||
|
||||
|
||||
def is_bundle_version_compatible(bundle: dict) -> bool:
|
||||
"""
|
||||
Checks whether the model bundle is compatible with the current selector version constraints.
|
||||
|
||||
The bundle specifies a `minimum_selector_version`, which defines the minimum selector version
|
||||
required to load the model. This function ensures that:
|
||||
|
||||
1. The model is not too old: the bundle must require at least `REQUIRED_MIN_SELECTOR_VERSION`.
|
||||
2. The model is not too new: it must support the current selector version (`CURRENT_SELECTOR_VERSION`).
|
||||
|
||||
This allows the selector to enforce both a minimum and maximum range of supported models,
|
||||
even if a model would otherwise be compatible.
|
||||
|
||||
:param bundle: Dictionary containing `minimum_selector_version`, as defined by the model bundle.
|
||||
:type bundle: Dict
|
||||
:return: True if the selector version is within the accepted range for the bundle; otherwise False.
|
||||
:rtype: Bool
|
||||
"""
|
||||
return bool(REQUIRED_MIN_SELECTOR_VERSION <= bundle.get("minimumSelectorVersion", 0) <= CURRENT_SELECTOR_VERSION)
|
||||
|
||||
def get_active_bundle(params: Params = None) -> custom.ModelManagerSP.ModelBundle:
|
||||
"""Gets the active model bundle from cache"""
|
||||
if params is None:
|
||||
params = Params()
|
||||
|
||||
if active_bundle := params.get("ModelManager_ActiveBundle"):
|
||||
return messaging.log_from_bytes(active_bundle, custom.ModelManagerSP.ModelBundle)
|
||||
try:
|
||||
if (active_bundle := json.loads(params.get("ModelManager_ActiveBundle") or "{}")) and is_bundle_version_compatible(active_bundle):
|
||||
return custom.ModelManagerSP.ModelBundle(**active_bundle)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
@@ -71,3 +113,74 @@ def get_active_model_runner(params: Params = None, force_check=False) -> custom.
|
||||
params.put("ModelRunnerTypeCache", str(int(runner_type)))
|
||||
|
||||
return runner_type
|
||||
|
||||
def _get_model():
|
||||
if bundle := get_active_bundle():
|
||||
drive_model = next(model for model in bundle.models if model.type == ModelManager.Model.Type.supercombo)
|
||||
return drive_model
|
||||
|
||||
return None
|
||||
|
||||
def get_model_path():
|
||||
if USE_ONNX:
|
||||
return {ModelRunner.ONNX: Path(__file__).parent / '../models/supercombo.onnx'}
|
||||
|
||||
if model := _get_model():
|
||||
return {ModelRunner.THNEED: f"{CUSTOM_MODEL_PATH}/{model.artifact.fileName}"}
|
||||
|
||||
return {ModelRunner.THNEED: Path(__file__).parent / '../models/supercombo.thneed'}
|
||||
|
||||
|
||||
def load_metadata():
|
||||
metadata_path = METADATA_PATH
|
||||
|
||||
if model := _get_model():
|
||||
metadata_path = f"{CUSTOM_MODEL_PATH}/{model.metadata.fileName}"
|
||||
|
||||
with open(metadata_path, 'rb') as f:
|
||||
return pickle.load(f)
|
||||
|
||||
|
||||
def prepare_inputs(model_metadata) -> dict[str, np.ndarray]:
|
||||
# img buffers are managed in openCL transform code so we don't pass them as inputs
|
||||
inputs = {
|
||||
k: np.zeros(v, dtype=np.float32).flatten()
|
||||
for k, v in model_metadata['input_shapes'].items()
|
||||
if 'img' not in k
|
||||
}
|
||||
|
||||
return inputs
|
||||
|
||||
|
||||
def load_meta_constants(model_metadata):
|
||||
"""
|
||||
Determines and loads the appropriate meta model class based on the metadata provided. The function checks
|
||||
specific keys and conditions within the provided metadata dictionary to identify the corresponding meta
|
||||
model class to return.
|
||||
|
||||
:param model_metadata: Dictionary containing metadata about the model. It includes
|
||||
details such as input shapes, output slices, and other configurations for identifying
|
||||
metadata-dependent meta model classes.
|
||||
:type model_metadata: dict
|
||||
:return: The appropriate meta model class (Meta, MetaSimPose, or MetaTombRaider)
|
||||
based on the conditions and metadata provided.
|
||||
:rtype: type
|
||||
"""
|
||||
meta = Meta # Default Meta
|
||||
|
||||
if 'sim_pose' in model_metadata['input_shapes'].keys():
|
||||
# Meta for models with sim_pose input
|
||||
meta = MetaSimPose
|
||||
else:
|
||||
# Meta for Tomb Raider, it does not include sim_pose input but has the same meta slice as previous models
|
||||
meta_slice = model_metadata['output_slices']['meta']
|
||||
meta_tf_slice = slice(5868, 5921, None)
|
||||
|
||||
if (
|
||||
meta_slice.start == meta_tf_slice.start and
|
||||
meta_slice.stop == meta_tf_slice.stop and
|
||||
meta_slice.step == meta_tf_slice.step
|
||||
):
|
||||
meta = MetaTombRaider
|
||||
|
||||
return meta
|
||||
|
||||
@@ -8,6 +8,7 @@ See the LICENSE.md file in the root directory for more details.
|
||||
import asyncio
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
|
||||
import aiohttp
|
||||
from openpilot.common.params import Params
|
||||
@@ -73,43 +74,54 @@ class ModelManagerSP:
|
||||
# Clean up start time after download completes
|
||||
del self._download_start_times[model.fileName]
|
||||
|
||||
async def _process_model(self, model, destination_path: str) -> None:
|
||||
async def _process_artifact(self, artifact, destination_path: str) -> None:
|
||||
"""Processes a single model download including verification"""
|
||||
url = model.downloadUri.uri
|
||||
expected_hash = model.downloadUri.sha256
|
||||
filename = model.fileName
|
||||
if not artifact.downloadUri.uri:
|
||||
return None
|
||||
|
||||
url = artifact.downloadUri.uri
|
||||
expected_hash = artifact.downloadUri.sha256
|
||||
filename = artifact.fileName
|
||||
full_path = os.path.join(destination_path, filename)
|
||||
|
||||
try:
|
||||
# Check existing file
|
||||
if os.path.exists(full_path) and await verify_file(full_path, expected_hash):
|
||||
model.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.cached
|
||||
model.downloadProgress.progress = 100
|
||||
model.downloadProgress.eta = 0
|
||||
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.cached
|
||||
artifact.downloadProgress.progress = 100
|
||||
artifact.downloadProgress.eta = 0
|
||||
self._report_status()
|
||||
return
|
||||
|
||||
# Download and verify
|
||||
await self._download_file(url, full_path, model)
|
||||
await self._download_file(url, full_path, artifact)
|
||||
if not await verify_file(full_path, expected_hash):
|
||||
raise ValueError(f"Hash validation failed for {filename}")
|
||||
|
||||
model.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.downloaded
|
||||
model.downloadProgress.eta = 0
|
||||
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.downloaded
|
||||
artifact.downloadProgress.eta = 0
|
||||
self._report_status()
|
||||
|
||||
except Exception as e:
|
||||
cloudlog.error(f"Error downloading {filename}: {str(e)}")
|
||||
if os.path.exists(full_path):
|
||||
os.remove(full_path)
|
||||
model.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.failed
|
||||
model.downloadProgress.eta = 0
|
||||
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.failed
|
||||
artifact.downloadProgress.eta = 0
|
||||
self.selected_bundle.status = custom.ModelManagerSP.DownloadStatus.failed
|
||||
self._report_status()
|
||||
# Clean up start time if it exists
|
||||
self._download_start_times.pop(model.fileName, None)
|
||||
self._download_start_times.pop(artifact.fileName, None)
|
||||
raise
|
||||
|
||||
async def _process_model(self, model, destination_path: str) -> None:
|
||||
"""Processes a single model download including verification"""
|
||||
model_artifact = model.artifact
|
||||
metadata_artifact = model.metadata
|
||||
|
||||
await self._process_artifact(metadata_artifact, destination_path)
|
||||
await self._process_artifact(model_artifact, destination_path)
|
||||
|
||||
def _report_status(self) -> None:
|
||||
"""Reports current status through messaging system"""
|
||||
msg = messaging.new_message('modelManagerSP', valid=True)
|
||||
@@ -134,7 +146,7 @@ class ModelManagerSP:
|
||||
await asyncio.gather(*tasks)
|
||||
self.active_bundle = self.selected_bundle
|
||||
self.active_bundle.status = custom.ModelManagerSP.DownloadStatus.downloaded
|
||||
self.params.put("ModelManager_ActiveBundle", self.active_bundle.to_bytes())
|
||||
self.params.put("ModelManager_ActiveBundle", json.dumps(self.active_bundle.to_dict()))
|
||||
self.selected_bundle = None
|
||||
|
||||
except Exception:
|
||||
@@ -154,7 +166,7 @@ class ModelManagerSP:
|
||||
|
||||
while True:
|
||||
try:
|
||||
self.available_models = self.model_fetcher.get_available_models()
|
||||
self.available_models = self.model_fetcher.get_available_bundles()
|
||||
self.active_bundle = get_active_bundle(self.params)
|
||||
|
||||
if index_to_download := self.params.get("ModelManager_DownloadIndex", block=False, encoding="utf-8"):
|
||||
|
||||
@@ -1 +1 @@
|
||||
82ead1a4ecffbde961382329a6cf71bc97a6b112fbfb1f175a51c28ee2472bec
|
||||
8ef2dbcae743eb132167074a374f0a834308be31cffd532598bb13c3d7144a57
|
||||
@@ -11,6 +11,7 @@ from opendbc.car.car_helpers import can_fingerprint
|
||||
from opendbc.car.interfaces import CarInterfaceBase
|
||||
from opendbc.car.hyundai.radar_interface import RADAR_START_ADDR
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, DBC as HYUNDAI_DBC
|
||||
from opendbc.sunnypilot.car.hyundai.longitudinal.helpers import LongitudinalTuningType
|
||||
from opendbc.sunnypilot.car.hyundai.values import HyundaiFlagsSP
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
@@ -25,9 +26,24 @@ def log_fingerprint(CP: structs.CarParams) -> None:
|
||||
else:
|
||||
sentry.capture_fingerprint(CP.carFingerprint, CP.brand)
|
||||
|
||||
def _initialize_custom_longitudinal_tuning(CI: CarInterfaceBase, CP: structs.CarParams, CP_SP: structs.CarParamsSP,
|
||||
params: Params = None) -> None:
|
||||
if params is None:
|
||||
params = Params()
|
||||
|
||||
def _initialize_neural_network_lateral_control(CP: structs.CarParams, CP_SP: structs.CarParamsSP, params: Params = None,
|
||||
enabled: bool = False) -> None:
|
||||
# Hyundai Custom Longitudinal Tuning
|
||||
if CP.brand == 'hyundai':
|
||||
hyundai_longitudinal_tuning = int(params.get("HyundaiLongitudinalTuning", encoding="utf8") or 0)
|
||||
if hyundai_longitudinal_tuning == LongitudinalTuningType.DYNAMIC:
|
||||
CP_SP.flags |= HyundaiFlagsSP.LONG_TUNING_DYNAMIC.value
|
||||
if hyundai_longitudinal_tuning == LongitudinalTuningType.PREDICTIVE:
|
||||
CP_SP.flags |= HyundaiFlagsSP.LONG_TUNING_PREDICTIVE.value
|
||||
|
||||
CP_SP = CI.get_longitudinal_tuning_sp(CP, CP_SP)
|
||||
|
||||
|
||||
def _initialize_neural_network_lateral_control(CI: CarInterfaceBase, CP: structs.CarParams, CP_SP: structs.CarParamsSP,
|
||||
params: Params = None, enabled: bool = False) -> None:
|
||||
if params is None:
|
||||
params = Params()
|
||||
|
||||
@@ -40,7 +56,7 @@ def _initialize_neural_network_lateral_control(CP: structs.CarParams, CP_SP: str
|
||||
enabled = params.get_bool("NeuralNetworkLateralControl")
|
||||
|
||||
if enabled:
|
||||
CarInterfaceBase.configure_torque_tune(CP.carFingerprint, CP.lateralTuning)
|
||||
CI.configure_torque_tune(CP.carFingerprint, CP.lateralTuning)
|
||||
|
||||
CP_SP.neuralNetworkLateralControl.model.path = nnlc_model_path
|
||||
CP_SP.neuralNetworkLateralControl.model.name = nnlc_model_name
|
||||
@@ -61,8 +77,12 @@ def _initialize_radar_tracks(CP: structs.CarParams, CP_SP: structs.CarParamsSP,
|
||||
CP.radarUnavailable = False
|
||||
|
||||
|
||||
def setup_interfaces(CP: structs.CarParams, CP_SP: structs.CarParamsSP, params: Params = None):
|
||||
_initialize_neural_network_lateral_control(CP, CP_SP, params)
|
||||
def setup_interfaces(CI: CarInterfaceBase, params: Params = None) -> None:
|
||||
CP = CI.CP
|
||||
CP_SP = CI.CP_SP
|
||||
|
||||
_initialize_custom_longitudinal_tuning(CI, CP, CP_SP, params)
|
||||
_initialize_neural_network_lateral_control(CI, CP, CP_SP, params)
|
||||
_initialize_radar_tracks(CP, CP_SP, params)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
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 cereal.messaging as messaging
|
||||
from cereal import custom
|
||||
|
||||
from opendbc.car import structs
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.param_store import ParamStore
|
||||
|
||||
|
||||
class ControlsExt:
|
||||
def __init__(self, CP: structs.CarParams, params: Params):
|
||||
self.CP = CP
|
||||
self.params = params
|
||||
self.param_store = ParamStore(self.CP)
|
||||
self.get_params_sp()
|
||||
|
||||
cloudlog.info("controlsd_ext is waiting for CarParamsSP")
|
||||
self.CP_SP = messaging.log_from_bytes(params.get("CarParamsSP", block=True), custom.CarParamsSP)
|
||||
cloudlog.info("controlsd_ext got CarParamsSP")
|
||||
|
||||
self.sm_services_ext = ['selfdriveStateSP']
|
||||
self.pm_services_ext = ['carControlSP']
|
||||
|
||||
def get_params_sp(self) -> None:
|
||||
self.param_store.update(self.params)
|
||||
|
||||
@staticmethod
|
||||
def get_lat_active(sm: messaging.SubMaster) -> bool:
|
||||
ss_sp = sm['selfdriveStateSP']
|
||||
|
||||
if ss_sp.mads.available:
|
||||
return bool(ss_sp.mads.active)
|
||||
|
||||
# MADS not available, use stock state to engage
|
||||
return bool(sm['selfdriveState'].active)
|
||||
|
||||
def state_control_ext(self, sm: messaging.SubMaster) -> custom.CarControlSP:
|
||||
CC_SP = custom.CarControlSP.new_message()
|
||||
|
||||
# MADS state
|
||||
CC_SP.mads = sm['selfdriveStateSP'].mads
|
||||
|
||||
CC_SP.params = self.param_store.publish()
|
||||
|
||||
return CC_SP
|
||||
|
||||
@staticmethod
|
||||
def publish_ext(CC_SP: custom.CarControlSP, sm: messaging.SubMaster, pm: messaging.PubMaster) -> None:
|
||||
cc_sp_send = messaging.new_message('carControlSP')
|
||||
cc_sp_send.valid = sm['carState'].canValid
|
||||
cc_sp_send.carControlSP = CC_SP
|
||||
|
||||
pm.send('carControlSP', cc_sp_send)
|
||||
|
||||
def run_ext(self, sm: messaging.SubMaster, pm: messaging.PubMaster) -> None:
|
||||
CC_SP = self.state_control_ext(sm)
|
||||
self.publish_ext(CC_SP, sm, pm)
|
||||
@@ -0,0 +1,75 @@
|
||||
"""
|
||||
Copyright (c) 2021-, rav4kumar, 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.common.realtime import DT_MDL
|
||||
from openpilot.common.params import Params
|
||||
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.accel_personality.accel_profiles import (
|
||||
get_max_accel_hermite,
|
||||
get_min_accel_hermite
|
||||
)
|
||||
|
||||
|
||||
AccelPersonality = custom.LongitudinalPlanSP.AccelerationPersonality
|
||||
|
||||
class AccelController:
|
||||
def __init__(self):
|
||||
self.params = Params()
|
||||
self.personality = AccelPersonality.stock
|
||||
self.frame = 0
|
||||
|
||||
def _update_personality_from_param(self):
|
||||
if self.frame % int(1. / DT_MDL) == 0:
|
||||
personality_str = self.params.get("AccelPersonality", encoding='utf-8')
|
||||
if personality_str is not None:
|
||||
personality_int = int(personality_str)
|
||||
if personality_int in [AccelPersonality.stock, AccelPersonality.normal, AccelPersonality.eco, AccelPersonality.sport]:
|
||||
self.personality = personality_int
|
||||
|
||||
def _get_max_accel_for_speed(self, v_ego: float) -> float:
|
||||
self._update_personality_from_param()
|
||||
|
||||
if self.personality == AccelPersonality.eco:
|
||||
mode = "eco"
|
||||
elif self.personality == AccelPersonality.sport:
|
||||
mode = "sport"
|
||||
else:
|
||||
mode = "normal"
|
||||
|
||||
return get_max_accel_hermite(v_ego, mode)
|
||||
|
||||
def _get_min_accel_for_speed(self, v_ego: float) -> float:
|
||||
self._update_personality_from_param()
|
||||
|
||||
if self.personality == AccelPersonality.eco:
|
||||
mode = "eco"
|
||||
elif self.personality == AccelPersonality.sport:
|
||||
mode = "sport"
|
||||
elif self.personality == AccelPersonality.normal:
|
||||
mode = "normal"
|
||||
else:
|
||||
mode = "stock"
|
||||
|
||||
return get_min_accel_hermite(v_ego, mode)
|
||||
|
||||
def get_accel_limits(self, v_ego: float, accel_limits: list[float]) -> tuple[float, float]:
|
||||
self._update_personality_from_param()
|
||||
|
||||
if self.personality == AccelPersonality.stock:
|
||||
return (accel_limits[0], accel_limits[1])
|
||||
else:
|
||||
max_accel = self._get_max_accel_for_speed(v_ego)
|
||||
min_accel = self._get_min_accel_for_speed(v_ego)
|
||||
return (min_accel, max_accel)
|
||||
|
||||
def is_personality_enabled(self, accel_personality: int = AccelPersonality.stock) -> bool:
|
||||
self.personality = accel_personality
|
||||
self._update_personality_from_param()
|
||||
return bool(self.personality != AccelPersonality.stock)
|
||||
|
||||
def update(self):
|
||||
self.frame += 1
|
||||
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
Copyright (c) 2021-, rav4kumar, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
# Profiles
|
||||
MAX_ACCEL_PROFILES = {
|
||||
"eco": [2.00, 2.00, 1.98, 1.54, 0.83, .572, .455, .365, .32, .10],
|
||||
"normal": [2.00, 2.00, 1.99, 1.66, 1.06, .66, .58, .49, .37, .15],
|
||||
"sport": [2.00, 2.00, 2.00, 1.95, 1.25, .88, .70, .60, .45, .20],
|
||||
}
|
||||
MAX_ACCEL_BREAKPOINTS = [0., 1., 6., 8., 11., 16., 20., 25., 30., 55.]
|
||||
|
||||
MIN_ACCEL_PROFILES = {
|
||||
"eco": [-.06, -.06, -.11, -.11, -.071, -.071, -.072, -.65, -.65],
|
||||
"normal": [-.07, -.07, -.12, -.12, -.072, -.072, -.073, -.70, -.70],
|
||||
"sport": [-1.2, -1.2, -1.2, -1.2, -1.2, -1.2, -1.2, -1.2, -1.2],
|
||||
"stock": [-1.2, -1.2, -1.2, -1.2, -1.2, -1.2, -1.2, -1.2, -1.2],
|
||||
}
|
||||
MIN_ACCEL_BREAKPOINTS = [0., 0.5, 0.6, 1.2, 8., 12., 14., 25., 40.]
|
||||
|
||||
# Precompute slopes for Cubic Bézier curves
|
||||
def compute_symmetric_slopes(x, y):
|
||||
n = len(x)
|
||||
if n < 2:
|
||||
raise ValueError("At least two points are required to compute slopes.")
|
||||
m = np.zeros(n)
|
||||
for i in range(n):
|
||||
if i == 0:
|
||||
m[i] = (y[i+1] - y[i]) / (x[i+1] - x[i])
|
||||
elif i == n-1:
|
||||
m[i] = (y[i] - y[i-1]) / (x[i] - x[i-1])
|
||||
else:
|
||||
m[i] = ((y[i+1] - y[i]) / (x[i+1] - x[i]) + (y[i] - y[i-1]) / (x[i] - x[i-1])) / 2
|
||||
return m
|
||||
|
||||
|
||||
MAX_ACCEL_SLOPES = {
|
||||
mode: compute_symmetric_slopes(MAX_ACCEL_BREAKPOINTS, values)
|
||||
for mode, values in MAX_ACCEL_PROFILES.items()
|
||||
}
|
||||
|
||||
MIN_ACCEL_SLOPES = {
|
||||
mode: compute_symmetric_slopes(MIN_ACCEL_BREAKPOINTS, values)
|
||||
for mode, values in MIN_ACCEL_PROFILES.items()
|
||||
}
|
||||
|
||||
# Hermite interpolation function
|
||||
def hermite_interpolate(x, xp, yp, slopes, mode):
|
||||
# Clip x inside the domain
|
||||
x = np.clip(x, xp[0], xp[-1])
|
||||
|
||||
# Find segment
|
||||
idx = np.searchsorted(xp, x) - 1
|
||||
idx = np.clip(idx, 0, len(slopes[mode]) - 1)
|
||||
|
||||
x0, x1 = xp[idx], xp[idx+1]
|
||||
y0, y1 = yp[idx], yp[idx+1]
|
||||
m0, m1 = slopes[mode][idx], slopes[mode][idx+1]
|
||||
|
||||
t = (x - x0) / (x1 - x0)
|
||||
h00 = 2*t**3 - 3*t**2 + 1
|
||||
h10 = t**3 - 2*t**2 + t
|
||||
h01 = -2*t**3 + 3*t**2
|
||||
h11 = t**3 - t**2
|
||||
|
||||
interpolated = (h00 * y0) + (h10 * (x1 - x0) * m0) + (h01 * y1) + (h11 * (x1 - x0) * m1)
|
||||
return interpolated
|
||||
|
||||
# Final functions to call
|
||||
def get_max_accel_hermite(v_ego: float, mode: str = "normal") -> float:
|
||||
return float(hermite_interpolate(v_ego, MAX_ACCEL_BREAKPOINTS, MAX_ACCEL_PROFILES[mode], MAX_ACCEL_SLOPES, mode))
|
||||
|
||||
def get_min_accel_hermite(v_ego: float, mode: str = "normal") -> float:
|
||||
return float(hermite_interpolate(v_ego, MIN_ACCEL_BREAKPOINTS, MIN_ACCEL_PROFILES[mode], MIN_ACCEL_SLOPES, mode))
|
||||
@@ -7,14 +7,17 @@ See the LICENSE.md file in the root directory for more details.
|
||||
|
||||
from cereal import messaging, custom
|
||||
from opendbc.car import structs
|
||||
from openpilot.sunnypilot.models.helpers import get_active_model_runner
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.dec.dec import DynamicExperimentalController
|
||||
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.accel_personality.accel_controller import AccelController
|
||||
DecState = custom.LongitudinalPlanSP.DynamicExperimentalControl.DynamicExperimentalControlState
|
||||
|
||||
|
||||
class LongitudinalPlannerSP:
|
||||
def __init__(self, CP: structs.CarParams, mpc):
|
||||
self.dec = DynamicExperimentalController(CP, mpc)
|
||||
self.accel_controller = AccelController()
|
||||
self.is_stock = get_active_model_runner() == custom.ModelManagerSP.Runner.stock
|
||||
|
||||
def get_mpc_mode(self) -> str | None:
|
||||
if not self.dec.active():
|
||||
@@ -24,6 +27,7 @@ class LongitudinalPlannerSP:
|
||||
|
||||
def update(self, sm: messaging.SubMaster) -> None:
|
||||
self.dec.update(sm)
|
||||
self.accel_controller.update()
|
||||
|
||||
def publish_longitudinal_plan_sp(self, sm: messaging.SubMaster, pm: messaging.PubMaster) -> None:
|
||||
plan_sp_send = messaging.new_message('longitudinalPlanSP')
|
||||
|
||||
@@ -24,7 +24,7 @@ class TestNNLCFingerprintBase:
|
||||
CP_SP = CarInterface.get_non_essential_params_sp(CP, car_name)
|
||||
CI = CarInterface(CP, CP_SP)
|
||||
|
||||
sunnypilot_interfaces.setup_interfaces(CP, CP_SP, Params())
|
||||
sunnypilot_interfaces.setup_interfaces(CI, Params())
|
||||
|
||||
return CI
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user