Compare commits

...

161 Commits

Author SHA1 Message Date
DevTekVE
a77d1f7eb0 Add ButtonHoldTracker for button hold logic and tests
Introduce a new `ButtonHoldTracker` class to manage button hold durations, replacing manual timer handling in `ExperimentalSwitcher`. Updated `ExperimentalSwitcher` to leverage this implementation for cleaner and more modular code. Added comprehensive unit tests for both `ButtonHoldTracker` and `ExperimentalSwitcher` to ensure functionality and edge case coverage.
2025-01-19 12:58:29 +01:00
Jason Wen
6eb6af5785 Merge branch 'dec-redo' into master-dev-c3-new 2025-01-19 03:39:30 -05:00
Jason Wen
7471bd4029 init 2025-01-19 03:18:05 -05:00
Jason Wen
767f057112 Merge branch 'master-new' into dec-redo 2025-01-19 02:55:46 -05:00
Jason Wen
6ef47dafcf ui: display DEC status via mode button (#592) 2025-01-19 02:54:58 -05:00
Jason Wen
3f6ac7e77a Merge branch 'ui-model-selector-rich' into master-dev-c3-new
# Conflicts:
#	opendbc_repo
#	panda
2025-01-19 02:51:06 -05:00
Jason Wen
d8040eea3f Merge branch 'master-new' into ui-model-selector-rich 2025-01-19 02:50:22 -05:00
Jason Wen
d223135ee3 Merge branch 'master-new' into ui-model-selector-rich 2025-01-19 02:49:13 -05:00
Jason Wen
8f29ddba45 ui: Model Selector: display prompt with rich text widget 2025-01-19 02:48:32 -05:00
Jason Wen
b7c3072a4c Merge branch 'mads-honda-states' into master-dev-c3-new 2025-01-18 23:57:32 -05:00
Jason Wen
3cba24f93a MADS: Honda: Allow steering 2025-01-18 23:57:05 -05:00
Jason Wen
f2edd589ba fix 2025-01-18 22:04:30 -05:00
Jason Wen
bf0a7b4494 migrate to sp only 2025-01-18 22:02:40 -05:00
Jason Wen
19b96003dc use another method for drawing 2025-01-18 21:51:49 -05:00
Kumar
10ee25d199 Update constants.py 2025-01-18 19:48:02 -07:00
Kumar
cd6fb642bd Update dec.py 2025-01-18 19:46:19 -07:00
Jason Wen
3d35164287 even dimmer 2025-01-18 21:43:14 -05:00
Jason Wen
a9ca9bbdf8 should be active and dimmer 2025-01-18 21:35:27 -05:00
Jason Wen
0737f311b8 add opacity 2025-01-18 21:31:01 -05:00
Jason Wen
dc45d3043c try this 2025-01-18 21:22:55 -05:00
Jason Wen
2eefd0aaf5 visuals for DEC 2025-01-18 21:12:19 -05:00
Jason Wen
cb9d92dc3c Merge branch 'master-new' into dec-redo 2025-01-18 20:55:35 -05:00
Jason Wen
2525fdc350 Merge branch 'mads-honda-states' into master-dev-c3-new 2025-01-18 20:41:18 -05:00
Jason Wen
9eb36a0b7e fix icons 2025-01-18 20:41:01 -05:00
Jason Wen
b5a5d94a2c bring them back 2025-01-18 20:39:05 -05:00
Jason Wen
a93dc0047d Merge branch 'mads-honda-states' into master-dev-c3-new 2025-01-18 16:35:34 -05:00
Jason Wen
25d195df72 try this 2025-01-18 16:35:27 -05:00
Jason Wen
3fc2f70eb8 nuke them all to test 2025-01-18 16:34:32 -05:00
Jason Wen
64058c4b7c Merge branch 'mads-honda-states' into master-dev-c3-new 2025-01-18 15:48:12 -05:00
Jason Wen
3044aa6133 bump opendbc 2025-01-18 15:48:01 -05:00
Jason Wen
f39f76182a Merge branch 'mads-honda-states' into master-dev-c3-new 2025-01-18 15:45:07 -05:00
Jason Wen
15567bf605 Merge branch 'master-new' into master-dev-c3-new 2025-01-18 15:45:02 -05:00
Jason Wen
3b090e0b01 Merge branch 'master-new' into mads-honda-states 2025-01-18 15:44:51 -05:00
Jason Wen
df3e848965 MADS: Honda: Cluster icons and events updates 2025-01-18 15:43:46 -05:00
DevTekVE
b3e3d1b384 Fix lead detection fallback for weighted average check.
Add a fallback value of -1 when computing the weighted average to prevent errors caused by invalid or None values. This ensures robust lead detection and avoids potential crashes or undefined behavior.
2025-01-18 20:48:06 +01:00
DevTekVE
b13356d267 Merge branch 'master-new' into dec-redo 2025-01-18 20:41:04 +01:00
rav4kumar
0089096d12 Update slow-down logic and constants for improved behavior
Adjust the slowdown scaling factor and anomaly handling to refine behavior without abrupt resets. Modify constants to increase window size and adjust probabilities and distances for smoother adaptation. Update version to reflect the new changes.
2025-01-18 11:26:02 -07:00
rav4kumar
3d135f9831 Revert "dec: how good is FirstOrderFilter?"
This reverts commit 01e06df542.
2025-01-18 11:12:10 -07:00
rav4kumar
a219046981 Revert "Update dec.py"
This reverts commit 3f29ccbd99.
2025-01-18 11:12:03 -07:00
rav4kumar
2c9ffdbf03 Revert "dec: faster ?"
This reverts commit 40259cd22a.
2025-01-18 11:11:57 -07:00
Jason Wen
5014b42650 Merge branch 'ui-regulatory-hide' into master-dev-c3-new 2025-01-17 21:48:20 -05:00
Jason Wen
c1f897168a ui: Hide regulatory button on non-comma devices 2025-01-17 21:45:51 -05:00
Jason Wen
301174b19a Merge branch 'ui-device-offroad' into master-dev-c3-new 2025-01-17 21:29:58 -05:00
Jason Wen
1b139c4ca1 ui: Transition offroad state for Device panel buttons 2025-01-17 21:29:46 -05:00
Jason Wen
745e7b0b28 Merge branch 'master-new' into master-dev-c3-new 2025-01-17 21:23:34 -05:00
Jason Wen
1f328c9fda Revert "ui: driver view window should use uiStateSP"
This reverts commit c08b18936d.
2025-01-17 15:02:49 -05:00
Jason Wen
eab69c0f3d Revert "try this"
This reverts commit bd8a93af8b.
2025-01-17 15:02:47 -05:00
Jason Wen
97fda2f918 Revert "more"
This reverts commit 2f5f708c2b.
2025-01-17 15:02:44 -05:00
Jason Wen
03e10db82e Merge branch 'ui-dm-cam-freeze' into master-dev-c3-new 2025-01-17 14:59:14 -05:00
Jason Wen
2f5f708c2b more 2025-01-17 14:59:06 -05:00
Jason Wen
413fc15cf8 Merge branch 'ui-dm-cam-freeze' into master-dev-c3-new 2025-01-17 14:52:54 -05:00
Jason Wen
bd8a93af8b try this 2025-01-17 14:52:42 -05:00
Jason Wen
928bec2acc Merge branch 'ui-dm-cam-freeze' into master-dev-c3-new 2025-01-17 14:37:39 -05:00
Jason Wen
c08b18936d ui: driver view window should use uiStateSP 2025-01-17 14:37:23 -05:00
Jason Wen
5f94110b7a no need 2025-01-17 12:12:51 -05:00
Jason Wen
bca0e7862f Merge branch 'sentry-sp' into master-dev-c3-new 2025-01-17 12:03:17 -05:00
Jason Wen
45c47d0c48 new endpoint 2025-01-17 12:01:11 -05:00
Jason Wen
ab8dd45cda do this instead 2025-01-17 10:57:06 -05:00
Jason Wen
66eb3a2532 wrap them around 2025-01-17 10:47:25 -05:00
Jason Wen
59b68e42f6 don't use deprecated method 2025-01-17 10:29:17 -05:00
Jason Wen
976c9ac9bf typo 2025-01-17 02:40:27 -05:00
Jason Wen
c65c8a535c no duplicate 2025-01-17 02:39:51 -05:00
Jason Wen
5e79eb774b Merge branch 'sentry-sp' into master-dev-c3-new 2025-01-17 02:06:22 -05:00
Jason Wen
0735fd5553 Merge branch 'dec-redo' into master-dev-c3-new 2025-01-17 02:06:17 -05:00
Jason Wen
5ad8b46158 cap 2025-01-17 02:04:59 -05:00
Jason Wen
3f4e372c34 remove 2025-01-17 01:55:15 -05:00
Jason Wen
7fde68ecf9 have to do this 2025-01-17 01:48:43 -05:00
Jason Wen
df1ef23280 use deprecated 2025-01-17 01:41:54 -05:00
Jason Wen
378eb8a4d2 remove unused imports 2025-01-17 01:39:15 -05:00
rav4kumar
40259cd22a dec: faster ? 2025-01-16 23:25:55 -07:00
Kumar
3f29ccbd99 Update dec.py 2025-01-16 23:25:45 -07:00
Jason Wen
189e883116 no point 2025-01-17 01:23:04 -05:00
rav4kumar
01e06df542 dec: how good is FirstOrderFilter? 2025-01-16 23:16:54 -07:00
rav4kumar
b1e8824187 revert smooht lead for now 2025-01-16 23:16:00 -07:00
Jason Wen
cb1240846b sentry: log fingerprints and save exceptions 2025-01-17 01:15:22 -05:00
Jason Wen
fa6ee806f5 Merge branch 'long-distance-btn-exp-mode' into master-dev-c3-new 2025-01-16 18:04:09 -05:00
Jason Wen
fefb6a3a25 Merge branch 'dec-redo' into master-dev-c3-new 2025-01-16 18:04:03 -05:00
Jason Wen
13cb725652 Merge branch 'master-new' into long-distance-btn-exp-mode 2025-01-16 18:03:35 -05:00
Jason Wen
b219e07ac1 Merge branch 'master-new' into dec-redo
# Conflicts:
#	selfdrive/car/card.py
2025-01-16 18:03:13 -05:00
Jason Wen
90d38a7968 fix tests 2025-01-16 14:24:27 -05:00
Jason Wen
bb83e1fe8f Merge branch 'dec-redo' into master-dev-c3-new 2025-01-16 12:09:28 -05:00
Jason Wen
baf0506033 use walrus (needs cleanup) 2025-01-16 12:08:15 -05:00
Jason Wen
a3db53044e fix wrong typing and variable name 2025-01-16 12:00:20 -05:00
Jason Wen
8673c31e46 Revert "use walrus for None"
This reverts commit 5f2396d490.
2025-01-16 11:59:13 -05:00
Jason Wen
5f2396d490 use walrus for None 2025-01-16 11:56:07 -05:00
Jason Wen
e2ff34417f Merge branch 'dec-redo' into master-dev-c3-new 2025-01-15 15:12:39 -05:00
Jason Wen
4ac4fc855f don't set to exp mode initial if DEC is active 2025-01-15 15:12:13 -05:00
Jason Wen
cea20d1945 Merge branch 'long-distance-btn-exp-mode' into master-dev-c3-new 2025-01-15 13:41:45 -05:00
Jason Wen
353aa611e7 Merge branch 'dec-redo' into master-dev-c3-new
# Conflicts:
#	sunnypilot/selfdrive/controls/lib/longitudinal_planner.py
2025-01-15 13:41:22 -05:00
Jason Wen
391a3160a0 no need 2025-01-15 11:00:08 -05:00
Jason Wen
6b4ef00ae6 fix 2025-01-15 10:57:51 -05:00
Jason Wen
d937062724 more 2025-01-15 01:51:22 -05:00
Jason Wen
f94eeb9780 Revert "explicit type hints"
This reverts commit c205497b
2025-01-15 01:46:31 -05:00
Jason Wen
c6474fc3ad move to constants py 2025-01-15 01:45:29 -05:00
Jason Wen
d7f0e8dd04 unused 2025-01-15 01:28:15 -05:00
Jason Wen
f3d94fc291 Longitudinal: Distance button hold to toggle Chill/Experimental Mode 2025-01-15 01:26:23 -05:00
Jason Wen
c205497b15 explicit type hints 2025-01-15 00:18:47 -05:00
Jason Wen
45f3c70596 move around 2025-01-14 21:55:56 -05:00
Jason Wen
21793721cc rename 2025-01-14 21:29:42 -05:00
Jason Wen
d01b02b185 more 2025-01-14 20:50:57 -05:00
Jason Wen
f4af0aa422 update name 2025-01-14 16:42:41 -05:00
Jason Wen
d3ecdbb850 fix dec engagement 2025-01-14 00:23:46 -05:00
Jason Wen
1f39c4ccfb remove to fail test 2025-01-14 00:04:48 -05:00
Jason Wen
a56e1e6e69 slight cleanup 2025-01-14 00:01:31 -05:00
Jason Wen
e630546250 more logs 2025-01-13 23:57:21 -05:00
Jason Wen
85faddc7af this is why it was never using DEC 2025-01-13 23:46:38 -05:00
Jason Wen
223abdc536 never used 2025-01-13 23:26:07 -05:00
Jason Wen
dd12f86804 Merge branch 'master-new' into dec-redo
# Conflicts:
#	common/params.cc
2025-01-13 23:11:06 -05:00
Jason Wen
dff2a5796d update name 2025-01-13 11:41:11 -05:00
Jason Wen
70918ccff3 Merge branch 'master-new' into dec-redo 2025-01-13 10:23:12 -05:00
Jason Wen
f6ef036158 use internal frame 2025-01-12 18:57:22 -05:00
Jason Wen
67692babdc Revert "pass sm.frame from plannerd"
This reverts commit a8deaa69b8.
2025-01-12 18:55:38 -05:00
Jason Wen
ea6cd3429b Revert "fix test"
This reverts commit 635b15f2bc.
2025-01-12 18:55:33 -05:00
Jason Wen
635b15f2bc fix test 2025-01-12 18:54:17 -05:00
Jason Wen
b42c060b2e more explicit 2025-01-12 18:37:47 -05:00
Jason Wen
1c1ef06489 more 2025-01-12 18:28:20 -05:00
Jason Wen
1b2586914f more fixes 2025-01-12 18:24:58 -05:00
Jason Wen
a8deaa69b8 pass sm.frame from plannerd 2025-01-12 18:20:06 -05:00
Jason Wen
c34a21980e fix type hint 2025-01-12 18:13:52 -05:00
rav4kumar
6e86d242cd window time 2025-01-12 16:03:17 -07:00
rav4kumar
7113d4a76a smoother trans 2025-01-12 15:54:50 -07:00
Jason Wen
5074881d6d unused 2025-01-12 17:40:21 -05:00
Jason Wen
bf3350b7f2 type hint 2025-01-12 17:38:49 -05:00
Jason Wen
c64b679704 sync with stock 2025-01-12 17:16:34 -05:00
Jason Wen
f645e2cdb0 check live param 2025-01-12 17:12:31 -05:00
Jason Wen
426d41a0a7 rename 2025-01-12 16:56:57 -05:00
Jason Wen
de305a81d5 into their own 2025-01-12 16:52:40 -05:00
Jason Wen
e4c29a2c58 stuff 2025-01-12 16:50:16 -05:00
Jason Wen
c3fde45000 more cleanup 2025-01-12 16:48:44 -05:00
Jason Wen
e7d27f0bb9 move around 2025-01-12 16:46:43 -05:00
Jason Wen
12981e02f7 static method 2025-01-12 16:46:15 -05:00
Jason Wen
e1cf216c89 some more 2025-01-12 16:44:54 -05:00
Jason Wen
cc507a5cd9 cleanup 2025-01-12 16:40:07 -05:00
rav4kumar
87ca1513f4 had moved to car_state 2025-01-12 14:29:22 -07:00
rav4kumar
1d422ce5cf static 2025-01-12 14:06:50 -07:00
rav4kumar
cb04017945 Merge branch 'dec-new-refactored-rebased-no-ui' of https://github.com/sunnypilot/sunnypilot into dec-new-refactored-rebased-no-ui 2025-01-12 13:49:17 -07:00
rav4kumar
7d6c9d1a8c static test 2025-01-12 13:48:08 -07:00
Jason Wen
ba6559d32a Merge branch 'master-new' into dec-new-refactored-rebased-no-ui 2025-01-12 15:40:47 -05:00
DevTekVE
c0c74e3761 **Refactor DEC module structure for better organization**
Moved DEC-related files from `dec` to `lib` for improved clarity and consistency within the project structure. Updated all relevant import paths to reflect the new locations. Ensured functionality remains unaffected with these changes.
2025-01-12 21:03:38 +01:00
DevTekVE
898f5b89ed Refactor DEC into a dedicated longitudinal planner class
Move Dynamic Experimental Control (DEC) logic to a new `DecLongitudinalPlanner` class for better modularity and maintainability. This simplifies the `LongitudinalPlanner` by delegating DEC-specific behavior and consolidates related methods into a single file. Additionally, redundant code was removed to improve readability and reduce complexity.
2025-01-12 20:51:35 +01:00
DevTekVE
f861e8d678 Format 2025-01-12 16:40:43 +01:00
DevTekVE
fd6ec85e20 Refactor MpcSource definition and update references.
Moved MpcSource enum into LongitudinalPlanSP for better encapsulation. Updated references in helpers.py to use the new path. This change improves code organization and maintains functionality.
2025-01-12 16:40:20 +01:00
DevTekVE
4a46bccc20 rebase fix 2025-01-12 16:33:23 +01:00
DevTekVE
73e658bf8c Formatting 2025-01-12 16:33:23 +01:00
DevTekVE
ec25ca070a Format 2025-01-12 16:33:23 +01:00
DevTekVE
84b6af094f Add missing import for messaging in helpers.py
The `messaging` module was added to resolve potential issues with undefined references. This change ensures all required imports are present, improving the reliability and maintainability of the code.
2025-01-12 16:33:23 +01:00
DevTekVE
55b6eae92e Refactor and modularize DynamicExperimentalController logic
Moved DynamicExperimentalController logic and helper functions to a dedicated module for better readability and maintainability. Simplified longitudinal planner logic by introducing reusable methods to manage MPC mode and longitudinal plan publishing. Adjusted file structure for dynamic controller-related components and updated relevant imports.
2025-01-12 16:33:22 +01:00
DevTekVE
760e7e847a new line... 2025-01-12 16:33:22 +01:00
DevTekVE
4b64f85f85 Replace unittest with pytest for dynamic controller tests
Migrated dynamic controller tests from unittest to pytest for improved readability and maintainability. Refactored mock setup using pytest fixtures and monkeypatching while preserving test coverage.
2025-01-12 16:33:22 +01:00
DevTekVE
8dd750fd83 Disabling unittest file to allow checks on the pipeline to succeed.
Pending to remove this, but leaving it to validate the move to pytest is okay before merging
2025-01-12 16:33:21 +01:00
DevTekVE
5e62ccad12 Integrate radar parameter into dynamic controller's pytest tests
Added a `has_radar` parameter to the test functions in the dynamic controller's pytest file. This allows each function to run both with and without radar inputs, thus enhancing the coverage of our test cases.
2025-01-12 16:33:21 +01:00
DevTekVE
2fb0545344 Migrated to pytest using claude 2025-01-12 16:33:21 +01:00
DevTekVE
684431e3f6 Refactor test indentation for dynamic controller tests
Adjust indentation and formatting in test_dynamic_controller.py to ensure consistency and readability. This change does not alter functionality but improves the maintainability of the test code.
2025-01-12 16:33:21 +01:00
DevTekVE
ba9fbd52d7 Refactor test_dynamic_controller and fix formatting issues
Added a new import for STOP_AND_GO_FRAME and corrected a float initialization for v_ego in MockCarState. Also fixed indentation in the test_standstill_detection method for consistency.
2025-01-12 16:33:20 +01:00
rav4kumar
1dfaf347f8 unitee testt 2025-01-12 16:33:20 +01:00
rav4kumar
91922fb9b6 fix static test 2025-01-12 16:33:20 +01:00
rav4kumar
c4ed5a4617 ff 2025-01-12 16:33:20 +01:00
rav4kumar
90435f0a92 fix static test 2025-01-12 16:33:19 +01:00
Kumar
3c434e78e2 Update sunnypilot/selfdrive/controls/lib/dynamic_experimental_controller.py
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
2025-01-12 16:33:19 +01:00
Kumar
75f6f73798 Update sunnypilot/selfdrive/controls/lib/dynamic_experimental_controller.py
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
2025-01-12 16:33:19 +01:00
rav4kumar
c5524c6d42 init dec 2025-01-12 16:33:19 +01:00
47 changed files with 1341 additions and 74 deletions

View File

@@ -84,7 +84,19 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
}
}
struct CustomReserved2 @0xf35cc4560bbf6ec2 {
struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
dec @0 :DynamicExperimentalControl;
struct DynamicExperimentalControl {
state @0 :DynamicExperimentalControlState;
enabled @1 :Bool;
active @2 :Bool;
enum DynamicExperimentalControlState {
acc @0;
blended @1;
}
}
}
struct CustomReserved3 @0xda96579883444c35 {

View File

@@ -199,6 +199,7 @@ struct OnroadEvent @0xc4fa6047f024e718 {
silentParkBrake @162;
controlsMismatchLateral @163;
hyundaiRadarTracksConfirmed @164;
experimentalModeSwitched @165;
soundsUnavailableDEPRECATED @47;
}
@@ -2641,7 +2642,7 @@ struct Event {
# DON'T change which struct it points to
selfdriveStateSP @107 :Custom.SelfdriveStateSP;
modelManagerSP @108 :Custom.ModelManagerSP;
customReserved2 @109 :Custom.CustomReserved2;
longitudinalPlanSP @109 :Custom.LongitudinalPlanSP;
customReserved3 @110 :Custom.CustomReserved3;
customReserved4 @111 :Custom.CustomReserved4;
customReserved5 @112 :Custom.CustomReserved5;

View File

@@ -77,6 +77,7 @@ _services: dict[str, tuple] = {
# sunnypilot
"modelManagerSP": (False, 1., 1),
"selfdriveStateSP": (True, 100., 10),
"longitudinalPlanSP": (True, 20., 10),
# debug
"uiDebug": (True, 0., 1),

View File

@@ -229,6 +229,8 @@ std::unordered_map<std::string, uint32_t> keys = {
{"HyundaiRadarTracksConfirmed", PERSISTENT},
{"HyundaiRadarTracksPersistent", PERSISTENT},
{"HyundaiRadarTracksToggle", PERSISTENT},
{"DynamicExperimentalControl", PERSISTENT},
};
} // namespace

View File

@@ -122,6 +122,9 @@ class Car:
MadsParams().set_alternative_experience(self.CP)
MadsParams().set_car_specific_params(self.CP)
# Dynamic Experimental Control
self.dynamic_experimental_control = self.params.get_bool("DynamicExperimentalControl")
openpilot_enabled_toggle = self.params.get_bool("OpenpilotEnabledToggle")
controller_available = self.CI.CC is not None and openpilot_enabled_toggle and not self.CP.dashcamOnly
@@ -204,7 +207,7 @@ class Car:
self.v_cruise_helper.update_v_cruise(CS, self.sm['carControl'].enabled, self.is_metric)
if self.sm['carControl'].enabled and not self.CC_prev.enabled:
# Use CarState w/ buttons from the step selfdrived enables on
self.v_cruise_helper.initialize_v_cruise(self.CS_prev, self.experimental_mode)
self.v_cruise_helper.initialize_v_cruise(self.CS_prev, self.experimental_mode, self.dynamic_experimental_control)
# TODO: mirror the carState.cruiseState struct?
CS.vCruise = float(self.v_cruise_helper.v_cruise_kph)
@@ -278,6 +281,10 @@ class Car:
while not evt.is_set():
self.is_metric = self.params.get_bool("IsMetric")
self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl
# sunnypilot
self.dynamic_experimental_control = self.params.get_bool("DynamicExperimentalControl")
time.sleep(0.1)
def card_thread(self):

View File

@@ -120,12 +120,13 @@ class VCruiseHelper:
self.button_timers[b.type.raw] = 1 if b.pressed else 0
self.button_change_states[b.type.raw] = {"standstill": CS.cruiseState.standstill, "enabled": enabled}
def initialize_v_cruise(self, CS, experimental_mode: bool) -> None:
def initialize_v_cruise(self, CS, experimental_mode: bool, dynamic_experimental_control: bool) -> None:
# initializing is handled by the PCM
if self.CP.pcmCruise:
return
initial = V_CRUISE_INITIAL_EXPERIMENTAL_MODE if experimental_mode else V_CRUISE_INITIAL
initial_experimental_mode = experimental_mode and not dynamic_experimental_control
initial = V_CRUISE_INITIAL_EXPERIMENTAL_MODE if initial_experimental_mode else V_CRUISE_INITIAL
if any(b.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for b in CS.buttonEvents) and self.v_cruise_initialized:
self.v_cruise_kph = self.v_cruise_kph_last

View File

@@ -57,16 +57,16 @@ class TestVCruiseHelper:
for _ in range(2):
self.v_cruise_helper.update_v_cruise(car.CarState(cruiseState={"available": False}), enabled=False, is_metric=False)
def enable(self, v_ego, experimental_mode):
def enable(self, v_ego, experimental_mode, dynamic_experimental_control):
# Simulates user pressing set with a current speed
self.v_cruise_helper.initialize_v_cruise(car.CarState(vEgo=v_ego), experimental_mode)
self.v_cruise_helper.initialize_v_cruise(car.CarState(vEgo=v_ego), experimental_mode, dynamic_experimental_control)
def test_adjust_speed(self):
"""
Asserts speed changes on falling edges of buttons.
"""
self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False)
self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False, False)
for btn in (ButtonType.accelCruise, ButtonType.decelCruise):
for pressed in (True, False):
@@ -90,7 +90,7 @@ class TestVCruiseHelper:
CS.buttonEvents = [ButtonEvent(type=ButtonType.decelCruise, pressed=pressed)]
self.v_cruise_helper.update_v_cruise(CS, enabled=enabled, is_metric=False)
if pressed:
self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False)
self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False, False)
# Expected diff on enabling. Speed should not change on falling edge of pressed
assert not pressed == self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last
@@ -100,7 +100,7 @@ class TestVCruiseHelper:
Asserts we don't increment set speed if user presses resume/accel to exit cruise standstill.
"""
self.enable(0, False)
self.enable(0, False, False)
for standstill in (True, False):
for pressed in (True, False):
@@ -120,7 +120,7 @@ class TestVCruiseHelper:
for v_ego in np.linspace(0, 100, 101):
self.reset_cruise_speed_state()
self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False)
self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False, False)
# first decrement speed, then perform gas pressed logic
expected_v_cruise_kph = self.v_cruise_helper.v_cruise_kph - IMPERIAL_INCREMENT
@@ -142,10 +142,11 @@ class TestVCruiseHelper:
"""
for experimental_mode in (True, False):
for v_ego in np.linspace(0, 100, 101):
self.reset_cruise_speed_state()
assert not self.v_cruise_helper.v_cruise_initialized
for dynamic_experimental_control in (True, False):
for v_ego in np.linspace(0, 100, 101):
self.reset_cruise_speed_state()
assert not self.v_cruise_helper.v_cruise_initialized
self.enable(float(v_ego), experimental_mode)
assert V_CRUISE_INITIAL <= self.v_cruise_helper.v_cruise_kph <= V_CRUISE_MAX
assert self.v_cruise_helper.v_cruise_initialized
self.enable(float(v_ego), experimental_mode, dynamic_experimental_control)
assert V_CRUISE_INITIAL <= self.v_cruise_helper.v_cruise_kph <= V_CRUISE_MAX
assert self.v_cruise_helper.v_cruise_initialized

View File

@@ -15,6 +15,8 @@ from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_speed_
from openpilot.selfdrive.car.cruise import V_CRUISE_MAX, V_CRUISE_UNSET
from openpilot.common.swaglog import cloudlog
from openpilot.sunnypilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlannerSP
LON_MPC_STEP = 0.2 # first step is 0.2s
A_CRUISE_MIN = -1.2
A_CRUISE_MAX_VALS = [1.6, 1.2, 0.8, 0.6]
@@ -66,10 +68,11 @@ def get_accel_from_plan(speeds, accels, action_t=DT_MDL, vEgoStopping=0.05):
return a_target, should_stop
class LongitudinalPlanner:
class LongitudinalPlanner(LongitudinalPlannerSP):
def __init__(self, CP, init_v=0.0, init_a=0.0, dt=DT_MDL):
self.CP = CP
self.mpc = LongitudinalMpc(dt=dt)
LongitudinalPlannerSP.__init__(self, self.CP, self.mpc)
self.fcw = False
self.dt = dt
self.allow_throttle = True
@@ -104,7 +107,10 @@ class LongitudinalPlanner:
return x, v, a, j, throttle_prob
def update(self, sm):
LongitudinalPlannerSP.update(self, sm)
self.mpc.mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc'
if dec_mpc_mode := self.get_mpc_mode():
self.mpc.mode = dec_mpc_mode
if len(sm['carControl'].orientationNED) == 3:
accel_coast = get_coast_accel(sm['carControl'].orientationNED[1])
@@ -205,3 +211,5 @@ class LongitudinalPlanner:
longitudinalPlan.allowThrottle = bool(self.allow_throttle)
pm.send('longitudinalPlan', plan_send)
self.publish_longitudinal_plan_sp(sm, pm)

View File

@@ -18,7 +18,7 @@ def main():
ldw = LaneDepartureWarning()
longitudinal_planner = LongitudinalPlanner(CP)
pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance'])
pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP'])
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState'],
poll='modelV2', ignore_avg_freq=['radarState'])

View File

@@ -1066,6 +1066,10 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
EventName.hyundaiRadarTracksConfirmed: {
ET.PERMANENT: NormalPermanentAlert("Radar tracks available. Restart the car to initialize")
},
EventName.experimentalModeSwitched: {
ET.WARNING: NormalPermanentAlert("Experimental Mode Switched", duration=1.5)
}
}

View File

@@ -25,6 +25,7 @@ from openpilot.system.version import get_build_metadata
from openpilot.sunnypilot.mads.mads import ModularAssistiveDrivingSystem
from openpilot.sunnypilot.selfdrive.car.car_specific import CarSpecificEventsSP
from openpilot.sunnypilot.selfdrive.car.experimental_switcher import ExperimentalSwitcher
REPLAY = "REPLAY" in os.environ
SIMULATION = "SIMULATION" in os.environ
@@ -44,7 +45,7 @@ SafetyModel = car.CarParams.SafetyModel
IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
class SelfdriveD:
class SelfdriveD(ExperimentalSwitcher):
def __init__(self, CP=None):
self.params = Params()
@@ -140,6 +141,8 @@ class SelfdriveD:
self.car_events_sp = CarSpecificEventsSP(self.CP, self.params)
ExperimentalSwitcher.__init__(self, self.params)
def update_events(self, CS):
"""Compute onroadEvents from carState"""
@@ -367,12 +370,18 @@ class SelfdriveD:
if self.sm['modelV2'].frameDropPerc > 20:
self.events.add(EventName.modeldLagging)
# toggle experimental mode once on distance button hold
if self.CP.openpilotLongitudinalControl:
ExperimentalSwitcher.update(self, CS, self.events, self.experimental_mode)
# decrement personality on distance button press
if self.CP.openpilotLongitudinalControl:
if any(not be.pressed and be.type == ButtonType.gapAdjustCruise for be in CS.buttonEvents):
self.personality = (self.personality - 1) % 3
self.params.put_nonblocking('LongitudinalPersonality', str(self.personality))
self.events.add(EventName.personalityChanged)
if not self.experimental_mode_switched:
self.personality = (self.personality - 1) % 3
self.params.put_nonblocking('LongitudinalPersonality', str(self.personality))
self.events.add(EventName.personalityChanged)
self.experimental_mode_switched = False
def data_sample(self):
car_state = messaging.recv_one(self.car_state_sock)

View File

@@ -40,6 +40,12 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
"",
"../assets/img_experimental_white.svg",
},
{
"DynamicExperimentalControl",
tr("Enable Dynamic Experimental Control"),
tr("Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal."),
"../assets/offroad/icon_blank.png",
},
{
"DisengageOnAccelerator",
tr("Disengage on Accelerator Pedal"),

View File

@@ -2,14 +2,16 @@
#include <QVBoxLayout>
#include <memory>
#include "selfdrive/ui/qt/onroad/buttons.h"
#include "selfdrive/ui/qt/onroad/driver_monitoring.h"
#include "selfdrive/ui/qt/onroad/model.h"
#include "selfdrive/ui/qt/widgets/cameraview.h"
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/qt/onroad/buttons.h"
#include "selfdrive/ui/sunnypilot/qt/onroad/hud.h"
#define ExperimentalButton ExperimentalButtonSP
#else
#include "selfdrive/ui/qt/onroad/buttons.h"
#include "selfdrive/ui/qt/onroad/hud.h"
#endif

View File

@@ -44,6 +44,10 @@ void ExperimentalButton::updateState(const UIState &s) {
void ExperimentalButton::paintEvent(QPaintEvent *event) {
QPainter p(this);
drawButton(p);
}
void ExperimentalButton::drawButton(QPainter &p) {
QPixmap img = experimental_mode ? experimental_img : engage_img;
drawIcon(p, QPoint(btn_size / 2, btn_size / 2), img, QColor(0, 0, 0, 166), (isDown() || !engageable) ? 0.6 : 1.0);
}

View File

@@ -16,13 +16,17 @@ class ExperimentalButton : public QPushButton {
public:
explicit ExperimentalButton(QWidget *parent = 0);
void updateState(const UIState &s);
virtual void updateState(const UIState &s);
private:
void paintEvent(QPaintEvent *event) override;
void changeMode();
Params params;
protected:
virtual void drawButton(QPainter &p);
QPixmap engage_img;
QPixmap experimental_img;
bool experimental_mode;

View File

@@ -23,6 +23,7 @@ qt_src = [
"sunnypilot/qt/offroad/settings/sunnypilot_panel.cc",
"sunnypilot/qt/offroad/settings/trips_panel.cc",
"sunnypilot/qt/onroad/annotated_camera.cc",
"sunnypilot/qt/onroad/buttons.cc",
"sunnypilot/qt/onroad/hud.cc",
"sunnypilot/qt/onroad/model.cc",
"sunnypilot/qt/onroad/onroad_home.cc",

View File

@@ -189,11 +189,16 @@ void SoftwarePanelSP::updateLabels() {
* @brief Shows dialog prompting user to reset calibration after model download
*/
void SoftwarePanelSP::showResetParamsDialog() {
const auto confirmMsg = tr("Model download has started in the background.") + "\n" +
tr("We STRONGLY suggest you to reset calibration. Would you like to do that now?");
const auto confirmMsg = QString("%1<br><br><b>%2</b><br><br><b>%3</b>")
.arg(tr("Model download has started in the background."))
.arg(tr("We STRONGLY suggest you to reset calibration."))
.arg(tr("Would you like to do that now?"));
const auto button_text = tr("Reset Calibration");
if (showConfirmationDialog(confirmMsg, button_text, false)) {
QString content("<body><h2 style=\"text-align: center;\">" + tr("Driving Model Selector") + "</h2><br>"
"<p style=\"text-align: center; margin: 0 128px; font-size: 50px;\">" + confirmMsg + "</p></body>");
if (showConfirmationDialog(content, button_text, false)) {
params.remove("CalibrationParams");
params.remove("LiveTorqueParameters");
}

View File

@@ -53,7 +53,7 @@ private:
const QString final_message = QString("%1%2").arg(!message.isEmpty() ? message + "\n" : QString(), warning_message);
const QString final_buttonText = !confirmButtonText.isEmpty() ? confirmButtonText : QString(tr("Continue") + " %1").arg(show_metered_warning ? tr("on Metered") : "");
return ConfirmationDialog::confirm(final_message, final_buttonText, parent);
return ConfirmationDialog(final_message, final_buttonText, tr("Cancel"), true, parent).exec();
}
bool is_metered{};

View File

@@ -0,0 +1,51 @@
/**
* 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/onroad/buttons.h"
#include <QPainter>
ExperimentalButtonSP::ExperimentalButtonSP(QWidget *parent) : ExperimentalButton(parent) {
QObject::disconnect(uiState(), &UIState::uiUpdate, this, &ExperimentalButton::updateState);
QObject::connect(uiState(), &UIState::uiUpdate, this, &ExperimentalButtonSP::updateState);
}
void ExperimentalButtonSP::updateState(const UIState &s) {
ExperimentalButton::updateState(s);
const auto long_plan_sp = (*s.sm)["longitudinalPlanSP"].getLongitudinalPlanSP();
int mode = int(long_plan_sp.getDec().getState());
if ((long_plan_sp.getDec().getActive() != dynamic_experimental_control) || (mode != dec_mpc_mode)) {
dynamic_experimental_control = long_plan_sp.getDec().getActive();
dec_mpc_mode = mode;
update();
}
}
void ExperimentalButtonSP::drawButton(QPainter &p) {
if (dynamic_experimental_control) {
QPixmap left_half = engage_img.copy(0, 0, engage_img.width() / 2, engage_img.height());
QPixmap right_half = experimental_img.copy(experimental_img.width() / 2, 0, experimental_img.width() / 2, experimental_img.height());
QPixmap combined_img(engage_img.width(), engage_img.height());
combined_img.fill(Qt::transparent);
QPainter combined_painter(&combined_img);
combined_painter.setOpacity(dec_mpc_mode == 1 ? 0.1 : 1.0);
combined_painter.drawPixmap(0, 0, left_half);
combined_painter.setOpacity(dec_mpc_mode == 1 ? 1.0 : 0.1);
combined_painter.drawPixmap(engage_img.width() / 2, 0, right_half);
combined_painter.end();
drawIcon(p, QPoint(btn_size / 2, btn_size / 2), combined_img, QColor(0, 0, 0, 166), (isDown() || !engageable) ? 0.6 : 1.0);
} else {
ExperimentalButton::drawButton(p);
}
}

View File

@@ -0,0 +1,24 @@
/**
* 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/qt/onroad/buttons.h"
class ExperimentalButtonSP : public ExperimentalButton {
Q_OBJECT
public:
explicit ExperimentalButtonSP(QWidget *parent = nullptr);
void updateState(const UIState &s) override;
private:
void drawButton(QPainter &p) override;
bool dynamic_experimental_control;
int dec_mpc_mode;
};

View File

@@ -18,7 +18,7 @@ UIStateSP::UIStateSP(QObject *parent) : UIState(parent) {
"modelV2", "controlsState", "liveCalibration", "radarState", "deviceState",
"pandaStates", "carParams", "driverMonitoringState", "carState", "driverStateV2",
"wideRoadCameraState", "managerState", "selfdriveState", "longitudinalPlan",
"modelManagerSP", "selfdriveStateSP",
"modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP",
});
// update timer

View File

@@ -1105,10 +1105,6 @@ This may take up to a minute.</source>
<source>SELECT</source>
<translation type="unfinished">اختيار</translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration. Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Reset Calibration</source>
<translation type="unfinished">إعادة ضبط المعايرة</translation>
@@ -1193,6 +1189,22 @@ This may take up to a minute.</source>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cancel</source>
<translation type="unfinished">إلغاء</translation>
</message>
<message>
<source>Driving Model Selector</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SshControl</name>
@@ -1423,6 +1435,14 @@ This may take up to a minute.</source>
<source>Enable driver monitoring even when openpilot is not engaged.</source>
<translation>تمكين مراقبة السائق حتى عندما لا يكون نظام OpenPilot مُفعّلاً.</translation>
</message>
<message>
<source>Enable Dynamic Experimental Control</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Updater</name>

View File

@@ -1089,10 +1089,6 @@ This may take up to a minute.</source>
<source>SELECT</source>
<translation type="unfinished">AUSWÄHLEN</translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration. Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Reset Calibration</source>
<translation type="unfinished">Neu kalibrieren</translation>
@@ -1177,6 +1173,22 @@ This may take up to a minute.</source>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cancel</source>
<translation type="unfinished">Abbrechen</translation>
</message>
<message>
<source>Driving Model Selector</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SshControl</name>
@@ -1407,6 +1419,14 @@ This may take up to a minute.</source>
<source>Enable driver monitoring even when openpilot is not engaged.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable Dynamic Experimental Control</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Updater</name>

View File

@@ -1089,10 +1089,6 @@ Esto puede tardar un minuto.</translation>
<source>SELECT</source>
<translation type="unfinished">SELECCIONAR</translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration. Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Reset Calibration</source>
<translation type="unfinished">Formatear Calibración</translation>
@@ -1177,6 +1173,22 @@ Esto puede tardar un minuto.</translation>
<source>Default</source>
<translation>Por Defecto</translation>
</message>
<message>
<source>Cancel</source>
<translation type="unfinished">Cancelar</translation>
</message>
<message>
<source>Driving Model Selector</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SshControl</name>
@@ -1407,6 +1419,14 @@ Esto puede tardar un minuto.</translation>
<source>Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode.</source>
<translation>Activar el control longitudinal (fase experimental) para permitir el modo Experimental.</translation>
</message>
<message>
<source>Enable Dynamic Experimental Control</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Updater</name>

View File

@@ -1089,10 +1089,6 @@ Cela peut prendre jusqu&apos;à une minute.</translation>
<source>SELECT</source>
<translation type="unfinished">SÉLECTIONNER</translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration. Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Reset Calibration</source>
<translation type="unfinished">Réinitialiser la calibration</translation>
@@ -1177,6 +1173,22 @@ Cela peut prendre jusqu&apos;à une minute.</translation>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cancel</source>
<translation type="unfinished">Annuler</translation>
</message>
<message>
<source>Driving Model Selector</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SshControl</name>
@@ -1407,6 +1419,14 @@ Cela peut prendre jusqu&apos;à une minute.</translation>
<source>Enable driver monitoring even when openpilot is not engaged.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable Dynamic Experimental Control</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Updater</name>

View File

@@ -1083,10 +1083,6 @@ This may take up to a minute.</source>
<source>SELECT</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration. Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Reset Calibration</source>
<translation type="unfinished"></translation>
@@ -1171,6 +1167,22 @@ This may take up to a minute.</source>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Driving Model Selector</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SshControl</name>
@@ -1401,6 +1413,14 @@ This may take up to a minute.</source>
<source>Enable driver monitoring even when openpilot is not engaged.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable Dynamic Experimental Control</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Updater</name>

View File

@@ -1085,10 +1085,6 @@ This may take up to a minute.</source>
<source>SELECT</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration. Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Reset Calibration</source>
<translation type="unfinished"> </translation>
@@ -1173,6 +1169,22 @@ This may take up to a minute.</source>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Driving Model Selector</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SshControl</name>
@@ -1403,6 +1415,14 @@ This may take up to a minute.</source>
<source>Enable driver monitoring even when openpilot is not engaged.</source>
<translation>Openpilot이 .</translation>
</message>
<message>
<source>Enable Dynamic Experimental Control</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Updater</name>

View File

@@ -1089,10 +1089,6 @@ Isso pode levar até um minuto.</translation>
<source>SELECT</source>
<translation type="unfinished">SELECIONE</translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration. Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Reset Calibration</source>
<translation type="unfinished">Reinicializar Calibragem</translation>
@@ -1177,6 +1173,22 @@ Isso pode levar até um minuto.</translation>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cancel</source>
<translation type="unfinished">Cancelar</translation>
</message>
<message>
<source>Driving Model Selector</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SshControl</name>
@@ -1407,6 +1419,14 @@ Isso pode levar até um minuto.</translation>
<source>Enable driver monitoring even when openpilot is not engaged.</source>
<translation>Habilite o monitoramento do motorista mesmo quando o openpilot não estiver acionado.</translation>
</message>
<message>
<source>Enable Dynamic Experimental Control</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Updater</name>

View File

@@ -1085,10 +1085,6 @@ This may take up to a minute.</source>
<source>SELECT</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration. Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Reset Calibration</source>
<translation type="unfinished"></translation>
@@ -1173,6 +1169,22 @@ This may take up to a minute.</source>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Driving Model Selector</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SshControl</name>
@@ -1403,6 +1415,14 @@ This may take up to a minute.</source>
<source>Enable driver monitoring even when openpilot is not engaged.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable Dynamic Experimental Control</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Updater</name>

View File

@@ -1083,10 +1083,6 @@ This may take up to a minute.</source>
<source>SELECT</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration. Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Reset Calibration</source>
<translation type="unfinished">Kalibrasyonu sıfırla</translation>
@@ -1171,6 +1167,22 @@ This may take up to a minute.</source>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Driving Model Selector</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SshControl</name>
@@ -1401,6 +1413,14 @@ This may take up to a minute.</source>
<source>Enable driver monitoring even when openpilot is not engaged.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable Dynamic Experimental Control</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Updater</name>

View File

@@ -1085,10 +1085,6 @@ This may take up to a minute.</source>
<source>SELECT</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration. Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Reset Calibration</source>
<translation type="unfinished"></translation>
@@ -1173,6 +1169,22 @@ This may take up to a minute.</source>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Driving Model Selector</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SshControl</name>
@@ -1403,6 +1415,14 @@ This may take up to a minute.</source>
<source>Enable driver monitoring even when openpilot is not engaged.</source>
<translation>使openpilot未激活时也启用驾驶员监控</translation>
</message>
<message>
<source>Enable Dynamic Experimental Control</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Updater</name>

View File

@@ -1085,10 +1085,6 @@ This may take up to a minute.</source>
<source>SELECT</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration. Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Reset Calibration</source>
<translation type="unfinished"></translation>
@@ -1173,6 +1169,22 @@ This may take up to a minute.</source>
<source>Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Driving Model Selector</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>We STRONGLY suggest you to reset calibration.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Would you like to do that now?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SshControl</name>
@@ -1403,6 +1415,14 @@ This may take up to a minute.</source>
<source>Enable driver monitoring even when openpilot is not engaged.</source>
<translation>使openpilot未激活時也啟用駕駛監控</translation>
</message>
<message>
<source>Enable Dynamic Experimental Control</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Updater</name>

View File

View File

@@ -0,0 +1,32 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from cereal import car, log
ButtonType = car.CarState.ButtonEvent.Type
EventName = log.OnroadEvent.EventName
class ButtonHoldTracker:
def __init__(self):
self.button_frame_counts = {}
def update(self, CS) -> None:
if CS.cruiseState.available:
self.update_button_timers(CS)
def update_button_timers(self, CS) -> None:
for button_type, held_frames in self.button_frame_counts.items():
if held_frames > 0:
self.button_frame_counts[button_type] += 1
for event in CS.buttonEvents:
event_type = event.type.raw
if not (event.pressed and self.button_frame_counts.get(event_type, 0) > 0): # No need to reset if the button is "pulsing" always "pressed"
self.button_frame_counts[event_type] = int(event.pressed)
def get_hold_duration(self, button_type):
return self.button_frame_counts.get(button_type, 0)

View File

@@ -0,0 +1,30 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from openpilot.sunnypilot.selfdrive.car.button_hold_tracker import ButtonHoldTracker, ButtonType, EventName
DISTANCE_LONG_PRESS = 50
class ExperimentalSwitcher:
def __init__(self, params, button_type=ButtonType.gapAdjustCruise):
self.params = params
self.button_type = button_type
self._button_tracker = ButtonHoldTracker()
self.experimental_mode_switched = False # Meant to be toggled off by this class's children.
def update(self, CS, events, experimental_mode):
self._button_tracker.update(CS)
if CS.cruiseState.available:
self.update_mode(events, experimental_mode)
def update_mode(self, events, experimental_mode) -> None:
if self._button_tracker.get_hold_duration(self.button_type) >= DISTANCE_LONG_PRESS and not self.experimental_mode_switched:
experimental_mode = not experimental_mode
self.params.put_bool_nonblocking("ExperimentalMode", experimental_mode)
events.add(EventName.experimentalModeSwitched)
self.experimental_mode_switched = True

View File

@@ -0,0 +1,81 @@
"""
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 pytest
from cereal import car
from sunnypilot.selfdrive.car.button_hold_tracker import ButtonHoldTracker, ButtonType
@pytest.fixture
def setup_button_tracker():
return ButtonHoldTracker()
class TestButtonHoldTracker:
@pytest.mark.parametrize(
"button_type, button_events, initial_timer, expected_timer",
[
# Basic button press scenarios
(ButtonType.gapAdjustCruise, [car.CarState.ButtonEvent(type=ButtonType.gapAdjustCruise, pressed=True)], 0, 1),
(ButtonType.gapAdjustCruise, [car.CarState.ButtonEvent(type=ButtonType.gapAdjustCruise, pressed=False)], 1, 0),
# Auto-increment when already held
(ButtonType.gapAdjustCruise, [], 5, 6),
(ButtonType.cancel, [], 10, 11),
(ButtonType.leftBlinker, [], 1, 2),
# Multiple button events in same frame
(ButtonType.gapAdjustCruise,
[
car.CarState.ButtonEvent(type=ButtonType.cancel, pressed=True),
car.CarState.ButtonEvent(type=ButtonType.gapAdjustCruise, pressed=False)
], 0, 0),
# Events for different buttons shouldn't affect each other
(ButtonType.gapAdjustCruise, [car.CarState.ButtonEvent(type=ButtonType.cancel, pressed=True)], 5, 6),
# Press while already held (like if it was pulsing the event for some reason)
(ButtonType.cancel, [car.CarState.ButtonEvent(type=ButtonType.cancel, pressed=True)], 5, 6),
# Edge cases
(ButtonType.leftBlinker, [], 0, 0),
(ButtonType.cancel, [car.CarState.ButtonEvent(type=ButtonType.cancel, pressed=False)], 0, 0),
],
ids=[
"initial_button_press",
"button_release",
"increment_held_gap_adjust",
"increment_held_cancel",
"increment_held_blinker",
"multiple_events_same_frame",
"different_button_no_effect",
"press_while_held",
"zero_state_no_events",
"release_when_not_held"
]
)
def test_update_button_timers(self, mocker, setup_button_tracker, button_type, button_events, initial_timer, expected_timer):
tracker = setup_button_tracker
mock_CS = mocker.Mock(buttonEvents=button_events, cruiseState=mocker.Mock(available=True))
if initial_timer > 0:
tracker.button_frame_counts[button_type] = initial_timer
tracker.update(mock_CS)
timer_value = tracker.get_hold_duration(button_type)
assert timer_value == expected_timer, f"Expected timer for {button_type} to be {expected_timer}, but got {timer_value}"
def test_cruise_state_unavailable(self, mocker, setup_button_tracker):
tracker = setup_button_tracker
mock_CS = mocker.Mock(
buttonEvents=[car.CarState.ButtonEvent(type=ButtonType.gapAdjustCruise, pressed=True)],
cruiseState=mocker.Mock(available=False)
)
tracker.update(mock_CS)
assert tracker.get_hold_duration(ButtonType.gapAdjustCruise) == 0

View File

@@ -0,0 +1,55 @@
"""
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 pytest
from cereal import car
from sunnypilot.selfdrive.car.experimental_switcher import ExperimentalSwitcher, ButtonType, EventName, DISTANCE_LONG_PRESS
@pytest.fixture
def setup_switcher(mocker):
mock_params = mocker.Mock()
return ExperimentalSwitcher(mock_params), mock_params
class TestExperimentalSwitcher:
@pytest.mark.parametrize(
"use_experimental_mode, long_press, expected_mode, expected_experimental_mode",
[
(False, True, True, True), # Normal -> Experimental via long press
(True, True, False, True), # Experimental -> Normal via long press
(False, False, False, False) # No change without long press
],
ids=[
"enable_experimental_mode",
"disable_experimental_mode",
"no_mode_change_without_long_press"
]
)
def test_update_mode_switches(self, mocker, setup_switcher, use_experimental_mode, long_press, expected_mode, expected_experimental_mode):
switcher, params = setup_switcher
mock_events = mocker.Mock()
mock_CS = mocker.Mock(
buttonEvents=[car.CarState.ButtonEvent(type=ButtonType.gapAdjustCruise, pressed=True)],
cruiseState=mocker.Mock(available=True)
)
if long_press:
for _ in range(DISTANCE_LONG_PRESS + 1):
switcher.update(mock_CS, mock_events, use_experimental_mode)
switcher.update_mode(mock_events, use_experimental_mode)
if expected_experimental_mode:
params.put_bool_nonblocking.assert_called_once_with("ExperimentalMode", expected_mode)
mock_events.add.assert_called_once_with(EventName.experimentalModeSwitched)
assert switcher.experimental_mode_switched is True
else:
params.put_bool_nonblocking.assert_not_called()
mock_events.add.assert_not_called()
assert switcher.experimental_mode_switched is False

View File

@@ -0,0 +1,26 @@
class WMACConstants:
LEAD_WINDOW_SIZE = 5
LEAD_PROB = 0.5
SLOW_DOWN_WINDOW_SIZE = 4
SLOW_DOWN_PROB = 0.6
SLOW_DOWN_BP = [0., 10., 20., 30., 40., 50., 55., 60.]
#SLOW_DOWN_DIST = [25., 38., 55., 75., 95., 115., 130., 150.]
SLOW_DOWN_DIST = [30., 45., 60., 80., 100., 120., 135., 150.]
SLOWNESS_WINDOW_SIZE = 12
SLOWNESS_PROB = 0.5
SLOWNESS_CRUISE_OFFSET = 1.05
DANGEROUS_TTC_WINDOW_SIZE = 3
DANGEROUS_TTC = 2.3
MPC_FCW_WINDOW_SIZE = 10
MPC_FCW_PROB = 0.5
class SNG_State:
off = 0
stopped = 1
going = 2

View File

@@ -0,0 +1,390 @@
# The MIT License
#
# Copyright (c) 2019-, Rick Lan, dragonpilot community, and a number of other of contributors.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Version = 2025-1-18
import numpy as np
from cereal import messaging
from opendbc.car import structs
from openpilot.common.numpy_fast import interp
from openpilot.common.params import Params
from openpilot.common.realtime import DT_MDL
from openpilot.sunnypilot.selfdrive.controls.lib.dec.constants import WMACConstants, SNG_State
# d-e2e, from modeldata.h
TRAJECTORY_SIZE = 33
HIGHWAY_CRUISE_KPH = 70
STOP_AND_GO_FRAME = 60
SET_MODE_TIMEOUT = 10
V_ACC_MIN = 9.72
class GenericMovingAverageCalculator:
def __init__(self, window_size):
self.window_size = window_size
self.data = []
self.total = 0
def add_data(self, value: float) -> None:
if len(self.data) == self.window_size:
self.total -= self.data.pop(0)
self.data.append(value)
self.total += value
def get_moving_average(self) -> float | None:
return None if len(self.data) == 0 else self.total / len(self.data)
def reset_data(self) -> None:
self.data = []
self.total = 0
class WeightedMovingAverageCalculator:
def __init__(self, window_size):
self.window_size = window_size
self.data = []
self.weights = np.linspace(1, 3, window_size) # Linear weights, adjust as needed
def add_data(self, value: float) -> None:
if len(self.data) == self.window_size:
self.data.pop(0)
self.data.append(value)
def get_weighted_average(self) -> float | None:
if len(self.data) == 0:
return None
weighted_sum: float = float(np.dot(self.data, self.weights[-len(self.data):]))
weight_total: float = float(np.sum(self.weights[-len(self.data):]))
return weighted_sum / weight_total
def reset_data(self) -> None:
self.data = []
class DynamicExperimentalController:
def __init__(self, CP: structs.CarParams, mpc, params=None):
self._CP = CP
self._mpc = mpc
self._params = params or Params()
self._enabled: bool = self._params.get_bool("DynamicExperimentalControl")
self._active: bool = False
self._mode: str = 'acc'
self._frame: int = 0
# Use weighted moving average for filtering leads
self._lead_gmac = WeightedMovingAverageCalculator(window_size=WMACConstants.LEAD_WINDOW_SIZE)
self._has_lead_filtered = False
self._has_lead_filtered_prev = False
self._slow_down_gmac = WeightedMovingAverageCalculator(window_size=WMACConstants.SLOW_DOWN_WINDOW_SIZE)
self._has_slow_down: bool = False
self._slow_down_confidence: float = 0.0
self._has_blinkers = False
self._slowness_gmac = WeightedMovingAverageCalculator(window_size=WMACConstants.SLOWNESS_WINDOW_SIZE)
self._has_slowness: bool = False
self._has_nav_instruction = False
self._dangerous_ttc_gmac = WeightedMovingAverageCalculator(window_size=WMACConstants.DANGEROUS_TTC_WINDOW_SIZE)
self._has_dangerous_ttc: bool = False
self._v_ego_kph = 0.
self._v_cruise_kph = 0.
self._has_lead = False
self._has_standstill = False
self._has_standstill_prev = False
self._sng_transit_frame = 0
self._sng_state = SNG_State.off
self._mpc_fcw_gmac = WeightedMovingAverageCalculator(window_size=WMACConstants.MPC_FCW_WINDOW_SIZE)
self._has_mpc_fcw: bool = False
self._mpc_fcw_crash_cnt = 0
self._set_mode_timeout = 0
def _read_params(self) -> None:
if self._frame % int(1. / DT_MDL) == 0:
self._enabled = self._params.get_bool("DynamicExperimentalControl")
def mode(self) -> str:
return str(self._mode)
def enabled(self) -> bool:
return self._enabled
def active(self) -> bool:
return self._active
@staticmethod
def _anomaly_detection(recent_data: list[float], threshold: float = 2.0, context_check: bool = True) -> bool:
"""
Basic anomaly detection using standard deviation.
"""
if len(recent_data) < 5:
return False
mean: float = float(np.mean(recent_data))
std_dev: float = float(np.std(recent_data))
anomaly: bool = bool(recent_data[-1] > mean + threshold * std_dev)
# Context check to ensure repeated anomaly
if context_check:
return np.count_nonzero(np.array(recent_data) > mean + threshold * std_dev) > 1
return anomaly
def _adaptive_slowdown_threshold(self) -> float:
"""
Adapts the slow-down threshold based on vehicle speed and recent behavior.
"""
slowdown_scaling_factor: float = (1.0 + 0.05 * np.log(1 + len(self._slow_down_gmac.data)))
adaptive_threshold: float = float(
interp(self._v_ego_kph, WMACConstants.SLOW_DOWN_BP, WMACConstants.SLOW_DOWN_DIST) * slowdown_scaling_factor
)
return adaptive_threshold
def _smoothed_lead_detection(self, lead_prob: float, smoothing_factor: float = 0.2):
"""
Smoothing the lead detection to avoid erratic behavior.
"""
lead_filtering: float = (1 - smoothing_factor) * self._has_lead_filtered + smoothing_factor * lead_prob
return lead_filtering > WMACConstants.LEAD_PROB
def _adaptive_lead_prob_threshold(self) -> float:
"""
Adapts lead probability threshold based on driving conditions.
"""
if self._v_ego_kph > HIGHWAY_CRUISE_KPH:
return float(WMACConstants.LEAD_PROB + 0.1) # Increase the threshold on highways
return float(WMACConstants.LEAD_PROB)
def _update_calculations(self, sm: messaging.SubMaster) -> None:
car_state = sm['carState']
lead_one = sm['radarState'].leadOne
md = sm['modelV2']
self._v_ego_kph = car_state.vEgo * 3.6
self._v_cruise_kph = car_state.vCruise
self._has_lead = lead_one.status
self._has_standstill = car_state.standstill
# fcw detection
self._mpc_fcw_gmac.add_data(self._mpc_fcw_crash_cnt > 0)
if _mpc_fcw_weighted_average := self._mpc_fcw_gmac.get_weighted_average():
self._has_mpc_fcw = _mpc_fcw_weighted_average > WMACConstants.MPC_FCW_PROB
else:
self._has_mpc_fcw = False
# nav enable detection
# self._has_nav_instruction = md.navEnabledDEPRECATED and maneuver_distance / max(car_state.vEgo, 1) < 13
# lead detection with smoothing
self._lead_gmac.add_data(lead_one.status)
self._has_lead_filtered = (self._lead_gmac.get_weighted_average() or -1.) > WMACConstants.LEAD_PROB
#lead_prob = self._lead_gmac.get_weighted_average() or 0
#self._has_lead_filtered = self._smoothed_lead_detection(lead_prob)
# adaptive slow down detection
adaptive_threshold = self._adaptive_slowdown_threshold()
slow_down_trigger = len(md.orientation.x) == len(md.position.x) == TRAJECTORY_SIZE and md.position.x[TRAJECTORY_SIZE - 1] < adaptive_threshold
self._slow_down_gmac.add_data(slow_down_trigger)
if _has_slow_down_weighted_average := self._slow_down_gmac.get_weighted_average():
self._has_slow_down = _has_slow_down_weighted_average > WMACConstants.SLOW_DOWN_PROB
self._slow_down_confidence = _has_slow_down_weighted_average # Store confidence level
else:
self._has_slow_down = False
self._slow_down_confidence = 0.0 # No confidence if no slowdown
# anomaly detection for slow down events
if self._anomaly_detection(self._slow_down_gmac.data):
self._slow_down_confidence *= 0.85 # Reduce confidence
self._has_slow_down = self._slow_down_confidence > WMACConstants.SLOW_DOWN_PROB
# blinker detection
self._has_blinkers = car_state.leftBlinker or car_state.rightBlinker
# sng detection
if self._has_standstill:
self._sng_state = SNG_State.stopped
self._sng_transit_frame = 0
else:
if self._sng_transit_frame == 0:
if self._sng_state == SNG_State.stopped:
self._sng_state = SNG_State.going
self._sng_transit_frame = STOP_AND_GO_FRAME
elif self._sng_state == SNG_State.going:
self._sng_state = SNG_State.off
elif self._sng_transit_frame > 0:
self._sng_transit_frame -= 1
# slowness detection
if not self._has_standstill:
self._slowness_gmac.add_data(self._v_ego_kph <= (self._v_cruise_kph * WMACConstants.SLOWNESS_CRUISE_OFFSET))
if _slowness_weighted_average := self._slowness_gmac.get_weighted_average():
self._has_slowness = _slowness_weighted_average > WMACConstants.SLOWNESS_PROB
else:
self._has_slowness = False
# dangerous TTC detection
if not self._has_lead_filtered and self._has_lead_filtered_prev:
self._dangerous_ttc_gmac.reset_data()
self._has_dangerous_ttc = False
if self._has_lead and car_state.vEgo >= 0.01:
self._dangerous_ttc_gmac.add_data(lead_one.dRel / car_state.vEgo)
if _dangerous_ttc_weighted_average := self._dangerous_ttc_gmac.get_weighted_average():
self._has_dangerous_ttc = _dangerous_ttc_weighted_average <= WMACConstants.DANGEROUS_TTC
else:
self._has_dangerous_ttc = False
# keep prev values
self._has_standstill_prev = self._has_standstill
self._has_lead_filtered_prev = self._has_lead_filtered
def _radarless_mode(self) -> None:
# when mpc fcw crash prob is high
# use blended to slow down quickly
if self._has_mpc_fcw:
self._set_mode('blended')
return
# Nav enabled and distance to upcoming turning is 300 or below
# if self._has_nav_instruction:
# self._set_mode('blended')
# return
# when blinker is on and speed is driving below V_ACC_MIN: blended
# we don't want it to switch mode at higher speed, blended may trigger hard brake
# if self._has_blinkers and self._v_ego_kph < V_ACC_MIN:
# self._set_mode('blended')
# return
# when at highway cruise and SNG: blended
# ensuring blended mode is used because acc is bad at catching SNG lead car
# especially those who accel very fast and then brake very hard.
# if self._sng_state == SNG_State.going and self._v_cruise_kph >= V_ACC_MIN:
# self._set_mode('blended')
# return
# when standstill: blended
# in case of lead car suddenly move away under traffic light, acc mode won't brake at traffic light.
if self._has_standstill:
self._set_mode('blended')
return
# when detecting slow down scenario: blended
# e.g. traffic light, curve, stop sign etc.
if self._has_slow_down:
self._set_mode('blended')
return
# when detecting lead slow down: blended
# use blended for higher braking capability
if self._has_dangerous_ttc:
self._set_mode('blended')
return
# car driving at speed lower than set speed: acc
if self._has_slowness:
self._set_mode('acc')
return
self._set_mode('acc')
def _radar_mode(self) -> None:
# when mpc fcw crash prob is high
# use blended to slow down quickly
if self._has_mpc_fcw:
self._set_mode('blended')
return
# If there is a filtered lead, the vehicle is not in standstill, and the lead vehicle's yRel meets the condition,
if self._has_lead_filtered and not self._has_standstill:
self._set_mode('acc')
return
# when blinker is on and speed is driving below V_ACC_MIN: blended
# we don't want it to switch mode at higher speed, blended may trigger hard brake
# if self._has_blinkers and self._v_ego_kph < V_ACC_MIN:
# self._set_mode('blended')
# return
# when standstill: blended
# in case of lead car suddenly move away under traffic light, acc mode won't brake at traffic light.
if self._has_standstill:
self._set_mode('blended')
return
# when detecting slow down scenario: blended
# e.g. traffic light, curve, stop sign etc.
if self._has_slow_down:
self._set_mode('blended')
return
# car driving at speed lower than set speed: acc
if self._has_slowness:
self._set_mode('acc')
return
# Nav enabled and distance to upcoming turning is 300 or below
# if self._has_nav_instruction:
# self._set_mode('blended')
# return
self._set_mode('acc')
def set_mpc_fcw_crash_cnt(self) -> None:
self._mpc_fcw_crash_cnt = self._mpc.crash_cnt
def _set_mode(self, mode: str) -> None:
if self._set_mode_timeout == 0:
self._mode = mode
if mode == 'blended':
self._set_mode_timeout = SET_MODE_TIMEOUT
if self._set_mode_timeout > 0:
self._set_mode_timeout -= 1
def update(self, sm: messaging.SubMaster) -> None:
self._read_params()
self.set_mpc_fcw_crash_cnt()
self._update_calculations(sm)
if self._CP.radarUnavailable:
self._radarless_mode()
else:
self._radar_mode()
self._active = sm['selfdriveState'].experimentalMode and self._enabled
self._frame += 1

View File

@@ -0,0 +1,248 @@
import pytest
import numpy as np
from openpilot.common.params import Params
from openpilot.sunnypilot.selfdrive.controls.lib.dec.constants import WMACConstants, SNG_State
from openpilot.sunnypilot.selfdrive.controls.lib.dec.dec import DynamicExperimentalController, TRAJECTORY_SIZE, STOP_AND_GO_FRAME
class MockInterp:
def __call__(self, x, xp, fp):
return np.interp(x, xp, fp)
class MockCarState:
def __init__(self, v_ego=0., standstill=False, left_blinker=False, right_blinker=False):
self.vEgo = v_ego
self.standstill = standstill
self.leftBlinker = left_blinker
self.rightBlinker = right_blinker
class MockLeadOne:
def __init__(self, status=False, d_rel=0):
self.status = status
self.dRel = d_rel
class MockModelData:
def __init__(self, x_vals=None, positions=None):
self.orientation = type('Orientation', (), {'x': x_vals})()
self.position = type('Position', (), {'x': positions})()
class MockControlState:
def __init__(self, v_cruise=0):
self.vCruise = v_cruise
@pytest.fixture
def interp(monkeypatch):
mock_interp = MockInterp()
monkeypatch.setattr('openpilot.common.numpy_fast.interp', mock_interp)
return mock_interp
@pytest.fixture
def controller(interp):
params = Params()
params.put_bool("DynamicExperimentalControl", True)
return DynamicExperimentalController()
def test_initial_state(controller):
"""Test initial state of the controller"""
assert controller._mode == 'acc'
assert not controller._has_lead
assert not controller._has_standstill
assert controller._sng_state == SNG_State.off
assert not controller._has_lead_filtered
assert not controller._has_slow_down
assert not controller._has_dangerous_ttc
assert not controller._has_mpc_fcw
@pytest.mark.parametrize("has_radar", [True, False], ids=["with_radar", "without_radar"])
def test_standstill_detection(controller, has_radar):
"""Test standstill detection and state transitions"""
car_state = MockCarState(standstill=True)
lead_one = MockLeadOne()
md = MockModelData(x_vals=[0] * TRAJECTORY_SIZE, positions=[150] * TRAJECTORY_SIZE)
controls_state = MockControlState()
# Test transition to standstill
controller.update(not has_radar, car_state, lead_one, md, controls_state)
assert controller._sng_state == SNG_State.stopped
assert controller.get_mpc_mode() == 'blended'
# Test transition from standstill to moving
car_state.standstill = False
controller.update(not has_radar, car_state, lead_one, md, controls_state)
assert controller._sng_state == SNG_State.going
# Test complete transition to normal driving
for _ in range(STOP_AND_GO_FRAME + 1):
controller.update(not has_radar, car_state, lead_one, md, controls_state)
assert controller._sng_state == SNG_State.off
@pytest.mark.parametrize("has_radar", [True, False], ids=["with_radar", "without_radar"])
def test_lead_detection(controller, has_radar):
"""Test lead vehicle detection and filtering"""
car_state = MockCarState(v_ego=20) # 72 kph
lead_one = MockLeadOne(status=True, d_rel=50) # Safe distance
md = MockModelData(x_vals=[0] * TRAJECTORY_SIZE, positions=[150] * TRAJECTORY_SIZE)
controls_state = MockControlState(v_cruise=72)
# Let moving average stabilize
for _ in range(WMACConstants.LEAD_WINDOW_SIZE + 1):
controller.update(not has_radar, car_state, lead_one, md, controls_state)
assert controller._has_lead_filtered
expected_mode = 'acc' if has_radar else 'blended'
assert controller.get_mpc_mode() == expected_mode
# Test lead loss detection
lead_one.status = False
for _ in range(WMACConstants.LEAD_WINDOW_SIZE + 1):
controller.update(not has_radar, car_state, lead_one, md, controls_state)
assert not controller._has_lead_filtered
@pytest.mark.parametrize("has_radar", [True, False], ids=["with_radar", "without_radar"])
def test_slow_down_detection(controller, has_radar):
"""Test slow down detection based on trajectory"""
car_state = MockCarState(v_ego=10/3.6) # 10 kph
lead_one = MockLeadOne()
x_vals = [0] * TRAJECTORY_SIZE
positions = [20] * TRAJECTORY_SIZE # Position within slow down threshold
md = MockModelData(x_vals=x_vals, positions=positions)
controls_state = MockControlState(v_cruise=30)
# Test slow down detection
for _ in range(WMACConstants.SLOW_DOWN_WINDOW_SIZE + 1):
controller.update(not has_radar, car_state, lead_one, md, controls_state)
assert controller._has_slow_down
assert controller.get_mpc_mode() == 'blended'
# Test slow down recovery
positions = [200] * TRAJECTORY_SIZE # Position outside slow down threshold
md = MockModelData(x_vals=x_vals, positions=positions)
for _ in range(WMACConstants.SLOW_DOWN_WINDOW_SIZE + 1):
controller.update(not has_radar, car_state, lead_one, md, controls_state)
assert not controller._has_slow_down
@pytest.mark.parametrize("has_radar", [True, False], ids=["with_radar", "without_radar"])
def test_dangerous_ttc_detection(controller, has_radar):
"""Test Time-To-Collision detection and handling"""
car_state = MockCarState(v_ego=10) # 36 kph
lead_one = MockLeadOne(status=True)
md = MockModelData(x_vals=[0] * TRAJECTORY_SIZE, positions=[150] * TRAJECTORY_SIZE)
controls_state = MockControlState(v_cruise=36)
# First establish normal conditions with lead
lead_one.dRel = 100 # Safe distance
for _ in range(WMACConstants.LEAD_WINDOW_SIZE + 1): # First establish lead detection
controller.update(not has_radar, car_state, lead_one, md, controls_state)
assert controller._has_lead_filtered # Verify lead is detected
# Now test dangerous TTC detection
lead_one.dRel = 10 # 10m distance - should trigger dangerous TTC
# TTC = dRel/vEgo = 10/10 = 1s (which is less than DANGEROUS_TTC = 2.3s)
# Need to update multiple times to allow the weighted average to stabilize
for _ in range(WMACConstants.DANGEROUS_TTC_WINDOW_SIZE * 2):
controller.update(not has_radar, car_state, lead_one, md, controls_state)
assert controller._has_dangerous_ttc, "TTC of 1s should be considered dangerous"
expected_mode = 'acc' if has_radar else 'blended'
assert controller.get_mpc_mode() == expected_mode, f"Should be in [{expected_mode}] mode with dangerous TTC"
@pytest.mark.parametrize("has_radar", [True, False], ids=["with_radar", "without_radar"])
def test_mode_transitions(controller, has_radar):
"""Test comprehensive mode transitions under different conditions"""
# Initialize with normal driving conditions
car_state = MockCarState(v_ego=25) # 90 kph
lead_one = MockLeadOne(status=False)
md = MockModelData(x_vals=[0] * TRAJECTORY_SIZE, positions=[200] * TRAJECTORY_SIZE)
controls_state = MockControlState(v_cruise=100)
def stabilize_filters():
"""Helper to let all moving averages stabilize"""
for _ in range(max(WMACConstants.LEAD_WINDOW_SIZE, WMACConstants.SLOW_DOWN_WINDOW_SIZE,
WMACConstants.DANGEROUS_TTC_WINDOW_SIZE, WMACConstants.MPC_FCW_WINDOW_SIZE) + 1):
controller.update(not has_radar, car_state, lead_one, md, controls_state)
# Test 1: Normal driving -> ACC mode
stabilize_filters()
assert controller.get_mpc_mode() == 'acc', "Should be in ACC mode under normal driving conditions"
# Test 2: Standstill -> Blended mode
car_state.standstill = True
controller.update(not has_radar, car_state, lead_one, md, controls_state)
assert controller.get_mpc_mode() == 'blended', "Should be in blended mode during standstill"
# Test 3: Lead car appears -> ACC mode
car_state = MockCarState(v_ego=20) # Reset car state
lead_one.status = True
lead_one.dRel = 50 # Safe distance
stabilize_filters()
assert not controller._has_dangerous_ttc, "Should not have dangerous TTC"
assert controller.get_mpc_mode() == 'acc', "Should be in ACC mode with safe lead distance"
# Test 4: Dangerous TTC -> Blended mode
car_state = MockCarState(v_ego=20) # 72 kph
lead_one.status = True
lead_one.dRel = 50 # First establish normal lead detection
# First establish lead detection
for _ in range(WMACConstants.LEAD_WINDOW_SIZE + 1):
controller.update(not has_radar, car_state, lead_one, md, controls_state)
assert controller._has_lead_filtered # Verify lead is detected
# Now create dangerous TTC condition
lead_one.dRel = 20 # This creates a TTC of 1s, well below DANGEROUS_TTC
for _ in range(WMACConstants.DANGEROUS_TTC_WINDOW_SIZE * 2):
controller.update(not has_radar, car_state, lead_one, md, controls_state)
assert controller._has_dangerous_ttc, "Should detect dangerous TTC condition"
expected_mode = 'acc' if has_radar else 'blended'
assert controller.get_mpc_mode() == expected_mode, f"Should be in [{expected_mode}] mode with dangerous TTC"
@pytest.mark.parametrize("has_radar", [True, False], ids=["with_radar", "without_radar"])
def test_mpc_fcw_handling(controller, has_radar):
"""Test MPC FCW crash count handling and mode transitions"""
car_state = MockCarState(v_ego=20)
lead_one = MockLeadOne()
md = MockModelData(x_vals=[0] * TRAJECTORY_SIZE, positions=[150] * TRAJECTORY_SIZE)
controls_state = MockControlState(v_cruise=72)
# Test FCW activation
controller.set_mpc_fcw_crash_cnt(5)
for _ in range(WMACConstants.MPC_FCW_WINDOW_SIZE + 1):
controller.update(not has_radar, car_state, lead_one, md, controls_state)
assert controller._has_mpc_fcw
assert controller.get_mpc_mode() == 'blended'
# Test FCW recovery
controller.set_mpc_fcw_crash_cnt(0)
for _ in range(WMACConstants.MPC_FCW_WINDOW_SIZE + 1):
controller.update(not has_radar, car_state, lead_one, md, controls_state)
assert not controller._has_mpc_fcw
def test_radar_unavailable_handling(controller):
"""Test behavior transitions between radar available and unavailable states"""
car_state = MockCarState(v_ego=27.78) # 100 kph
lead_one = MockLeadOne(status=True, d_rel=50)
md = MockModelData(x_vals=[0] * TRAJECTORY_SIZE, positions=[150] * TRAJECTORY_SIZE)
controls_state = MockControlState(v_cruise=100)
# Test with radar available
for _ in range(WMACConstants.LEAD_WINDOW_SIZE + 1):
controller.update(False, car_state, lead_one, md, controls_state)
radar_mode = controller.get_mpc_mode()
# Test with radar unavailable
for _ in range(WMACConstants.LEAD_WINDOW_SIZE + 1):
controller.update(True, car_state, lead_one, md, controls_state)
radarless_mode = controller.get_mpc_mode()
assert radar_mode is not None
assert radarless_mode is not None

View File

@@ -0,0 +1,41 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from cereal import messaging, custom
from opendbc.car import structs
from openpilot.sunnypilot.selfdrive.controls.lib.dec.dec import DynamicExperimentalController
DecState = custom.LongitudinalPlanSP.DynamicExperimentalControl.DynamicExperimentalControlState
class LongitudinalPlannerSP:
def __init__(self, CP: structs.CarParams, mpc):
self.dec = DynamicExperimentalController(CP, mpc)
def get_mpc_mode(self) -> str | None:
if not self.dec.active():
return None
return self.dec.mode()
def update(self, sm: messaging.SubMaster) -> None:
self.dec.update(sm)
def publish_longitudinal_plan_sp(self, sm: messaging.SubMaster, pm: messaging.PubMaster) -> None:
plan_sp_send = messaging.new_message('longitudinalPlanSP')
plan_sp_send.valid = sm.all_checks(service_list=['carState', 'controlsState'])
longitudinalPlanSP = plan_sp_send.longitudinalPlanSP
# Dynamic Experimental Control
dec = longitudinalPlanSP.dec
dec.state = DecState.blended if self.dec.mode() == 'blended' else DecState.acc
dec.enabled = self.dec.enabled()
dec.active = self.dec.active()
pm.send('longitudinalPlanSP', plan_sp_send)

View File

@@ -43,6 +43,7 @@ def manager_init() -> None:
]
sunnypilot_default_params: list[tuple[str, str | bytes]] = [
("DynamicExperimentalControl", "0"),
("Mads", "1"),
("MadsMainCruiseAllowed", "1"),
("MadsPauseLateralOnBrake", "0"),