Compare commits

...

168 Commits

Author SHA1 Message Date
firestar5683 95a7f3b398 Connor 2026-09-09 17:58:50 -05:00
firestar5683 22707891bd cilantro lime 2026-09-09 17:55:05 -05:00
AngusBell97 962d8b6719 Galaxy: show developer mode guidance
Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
2026-09-09 16:30:43 -05:00
AngusBell97 0f3bdb34b3 Galaxy: keep GM auto-hold visibility consistent
Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
2026-09-09 16:28:47 -05:00
AngusBell97 c2921c1a8f Galaxy: unify longitudinal control modes
Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
2026-09-09 16:26:07 -05:00
AngusBell97 a19beda327 Galaxy: add driving personality profiles
Add configurable acceleration, braking, following-distance, and advanced smoothness profiles to Galaxy with off-road writes and readback validation.

Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
2026-09-09 16:07:41 -05:00
firestar5683 b7775991bf build 2026-09-09 15:33:21 -05:00
firestar5683 eedd73e522 iPhone FoldGate 2026-09-09 15:32:38 -05:00
Prabhaav Pillai 50e2c21dbd click for home! 2026-09-09 13:22:01 -04:00
Prabhaav Pillai 54c3fb13f3 galaxy banner clarity 2026-09-09 12:56:14 -04:00
Prabhaav Pillai 7f3bd61292 make the theme toggle consistient with back button 2026-09-09 02:44:16 -04:00
firestarsdog dec4a0884a Big Boom 2026-09-09 02:36:46 -04:00
firestarsdog 4edc8ab86a Revert "trying to make firefox less laggy"
This reverts commit b3a14cb48d.
2026-09-09 02:30:05 -04:00
Prabhaav Pillai b3a14cb48d trying to make firefox less laggy 2026-09-09 02:14:29 -04:00
firestarsdog f47322cbee are your fingies fixed 2026-09-09 02:09:10 -04:00
firestarsdog a08065f282 zik try dis 2026-09-09 01:45:06 -04:00
firestar5683 a33bec1ca4 uno mas lil dip 2026-09-08 21:15:21 -05:00
firestar5683 ca3d8a3816 Make external GPU CPU pinning conditional 2026-09-08 18:48:40 -05:00
firestar5683 2360ff9b0f build 2026-09-08 18:19:54 -05:00
firestar5683 0976fd804d The Final Countdown 2026-09-08 18:19:19 -05:00
firestar5683 2504441a4e build 2026-09-08 10:57:22 -05:00
firestar5683 0b5ccb31e1 The Rice Cake 2026-09-08 10:52:51 -05:00
firestar5683 b91ea3e1da Update manifest.json 2026-09-07 22:27:28 -05:00
firestar5683 1588f7041a App 2026-09-07 22:09:45 -05:00
firestar5683 bcf152e6f7 Sleppy time 2026-09-07 21:57:32 -05:00
firestar5683 249b03a3f5 ray 2026-09-07 11:47:50 -05:00
firestar5683 9ae473355a allow smol when beeg 2026-09-07 11:33:10 -05:00
whoisdomi 2ab6195d9b CSC rewrite
Credit: @whoisdomi
2026-09-07 11:32:03 -05:00
firestar5683 5df8b59964 Chestnut Diagnostics 2026-09-07 10:51:29 -05:00
firestar5683 c03d06b408 lfa 2026-09-07 09:39:54 -05:00
firestar5683 a695335f21 nos 2026-09-07 06:31:41 -05:00
firestar5683 9d1043ad01 build 2026-09-06 21:29:46 -05:00
firestar5683 a5d7db3b29 wowie zowie 2026-09-06 21:27:44 -05:00
firestar5683 bb284d0bc4 Fix offline GPU firmware and Connect streaming 2026-09-06 21:18:04 -05:00
firestarsdog 67bf3adfd4 LaCroixosse 2026-09-06 17:24:20 -04:00
firestar5683 7a83f8f430 fixes 2026-09-06 10:35:47 -05:00
firestar5683 4b507c960a Restore sunnypilot HKG attribution
Document reconstructed HKG lineage from sunnypilot's hkg-angle-steering-2025 branches and later opendbc history. Preserve applicable notices, credit upstream contributors, and mark the derived controller, CAN, platform, safety, and test boundaries.

Reference snapshots: sunnypilot/sunnypilot cfb38312db33779f4727c983d372474a56ccb5d8; sunnypilot/opendbc cc4b08625a98e94b318cab15e45e05dad58042bd; sunnypilot/opendbc f95f996f5917dcbbf2e32fe51b606a24cf836af6.
2026-09-06 09:58:12 -05:00
firestar5683 37908b698d BluePilot Rocks - restore Ford attribution
Document the substantial BluePilot bp-7.0 lineage behind StarPilot's
Ford support, preserve upstream license notices, credit the original
contributors, and add durable source references.
2026-09-06 08:16:52 -05:00
firestarsdog 759ff3107b GM/Bolt CAN GPS Accuracy 2026-09-06 04:19:00 -04:00
firestarsdog 422488562f SLC Alert QoL 2026-09-06 02:04:33 -04:00
Prabhaav Pillai 7f1f926c47 build - first from me 2026-09-06 01:58:30 -04:00
Prabhaav Pillai 9cb1b6b11d big dipper 2026-09-06 01:35:01 -04:00
firestarsdog 7d46313213 Sluglas, the Stripper Slug 2026-09-06 00:50:57 -04:00
firestarsdog edf76af796 Revert "Test fix - revert if nukes galaxy lol"
This reverts commit f51059956c.
2026-09-05 22:28:13 -04:00
firestarsdog f51059956c Test fix - revert if nukes galaxy lol 2026-09-05 19:18:02 -04:00
firestar5683 604a433ee4 optimize 2026-09-05 13:51:44 -05:00
firestar5683 87d007eb10 Patterson sand, llc 2026-09-05 13:13:53 -05:00
firestar5683 ab77a59497 In the naming is the catching 2026-09-05 11:50:30 -05:00
firestar5683 54a03a91b0 fix 2026-09-05 11:36:18 -05:00
firestar5683 cecc9bc8b6 build 2026-09-05 11:24:26 -05:00
firestar5683 cec1a0fb62 Guten Morgen 2026-09-05 11:21:21 -05:00
firestar5683 097d63caef and this is my lab 2026-09-04 23:01:30 -05:00
firestar5683 de9cb64165 Four Score & 7 2026-09-04 22:47:02 -05:00
firestar5683 53e5c5246d build 2026-09-04 21:58:10 -05:00
firestar5683 b5ab54ab6d this is my laboratory 2026-09-04 21:56:35 -05:00
Prabhaav Pillai f55ad9162d More Vue native windows. Reduce Duplicate code within API. 2026-09-04 15:52:40 -04:00
firestar5683 901b93ac57 yeetit 2026-09-04 10:46:11 -05:00
firestar5683 6850a8cdba blows chunks 2026-09-04 00:13:38 -05:00
firestar5683 dd7ac353bd Team Noah 2026-09-03 23:31:33 -05:00
firestar5683 fed4ce6ee0 cleanup
Original PRs: #115, #116, #117, and #118 by @1454
2026-09-03 15:22:26 -05:00
firestar5683 ab6351541f build 2026-09-03 15:15:10 -05:00
1454 26ce46ba1d controls: smooth CEM to ACC handoff
Blend MPC back in over the final 5 mph of the CEM limit while preserving strong E2E braking. Slew positive acceleration and freeze the integrator during the experimental-mode exit so the handoff stays smooth. Keep the existing lead and confidence gates on the release path.

Original PR: #115 by @1454
2026-09-03 15:12:30 -05:00
1454 c4ce84d037 gm: clarify Silverado standstill engagement comment
Original PR: #118 by @1454
2026-09-03 15:07:48 -05:00
1454 fbb0fccb0e Allow Silverado CC-long buttons at a complete stop.
minEnableSpeed is 0, so a strict greater-than check never armed actuation at 0 m/s.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-03 15:07:33 -05:00
1454 f43cbab52e Keep Silverado CC min-enable at 0.
CC-long used to overwrite that floor back to 24 mph, which blocked the same from-stop engage path as the camera truck.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-03 15:07:33 -05:00
1454 4a50ca040f Allow Silverado cruise engage at a stop.
Drop the 5 kph minEnableSpeed so SET is not blocked while creeping below 3 mph.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-03 15:07:32 -05:00
1454 815267797c Stop LongPitch from stacking hill throttle on a positive planner command.
Honor the toggle on pedal-long cars and cap leftover uphill grade feedforward so a 10-speed does not skip-shift 10-8.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-03 15:07:28 -05:00
1454 02e0f1ec9a soundd: narrow custom clip fallback errors
Original PR: #116 by @1454
2026-09-03 15:07:26 -05:00
1454 509e6876f1 Never let one broken clip abort soundd startup.
Any decode failure on a candidate now falls through to the next path or skip.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-03 15:07:15 -05:00
1454 b321dd71e6 Skip truncated WAV payloads that cannot decode as samples.
Reject odd-length PCM and catch ValueError so a broken theme file cannot take soundd down.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-03 15:07:14 -05:00
1454 cc8c0dfb16 Keep stock critical alerts if goat or a theme WAV fails.
Catch truncated files that raise EOFError, and only replace a critical chime with goat when that clip actually loaded.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-03 15:07:14 -05:00
1454 ffb2b2c1e3 Fall back to stock sounds when a custom clip is invalid.
Skip empty WAVs so playback never divides by zero, and keep packaged critical alerts if a theme file is malformed.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-03 15:07:14 -05:00
1454 91d4b27aa3 Keep soundd alive when custom theme clips are missing.
Skip missing or invalid wavs instead of crashing at startup so stock chimes still play on a fresh image.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-03 15:07:14 -05:00
firestar5683 693df09c88 build 2026-09-03 15:04:45 -05:00
firestar5683 d2fb362876 what's that mean, dumby 2026-09-03 15:01:53 -05:00
firestar5683 f18cf22104 that's not a power button 2026-09-03 12:27:31 -05:00
firestarsdog 352b23ddf5 Not quite a pollo bowl 2026-09-03 01:09:51 -04:00
Prabhaav Pillai c7a3e3297a cookie to authenticate mobile 2026-09-03 00:16:13 -04:00
Prabhaav Pillai b390513ad1 Add command to launch local Galaxy web UI in host_tool_runner.sh 2026-09-02 23:59:25 -04:00
Prabhaav Pillai 6f5e267493 Mobile Friendly Galaxy 2026-09-02 23:40:08 -04:00
firestar5683 bb3b1429eb I thought you said weast 2026-09-02 22:07:29 -05:00
firestar5683 3b4a570564 build 2026-09-02 20:06:05 -05:00
firestar5683 a3bdcf2417 nope 2026-09-02 20:05:29 -05:00
firestar5683 f553b8071d subuwu 2026-09-02 17:37:45 -05:00
firestar5683 f7fad2a4d5 build 2026-09-02 17:31:48 -05:00
firestar5683 5fe8b17467 weh 2026-09-02 17:31:24 -05:00
firestar5683 51d8062c36 hi 2026-09-02 13:56:15 -05:00
firestar5683 5122b8df42 h 2026-09-02 11:54:07 -05:00
firestar5683 3d4c625deb actually refresh 2026-09-01 21:30:45 -05:00
firestar5683 dff446fe5e Fix Bluetooth pairing 2026-09-01 20:50:19 -05:00
firestar5683 75577ecfca Update launch_env.sh 2026-09-01 13:36:21 -05:00
firestar5683 41bca45bdd build 2026-09-01 13:32:07 -05:00
firestar5683 3ebc6b9903 Break 2026-09-01 13:31:37 -05:00
firestar5683 5e13c2d61b Update AGNOS 19.6.15 manifest 2026-09-01 13:31:24 -05:00
firestar5683 d646413db3 build 2026-09-01 09:42:54 -05:00
firestar5683 d0fc9f9f46 weevil 2026-09-01 09:26:29 -05:00
firestar5683 eef1d0513e fix 2026-08-31 23:10:17 -05:00
firestar5683 401319431a hi 2026-08-31 16:10:43 -05:00
firestar5683 6f49c1cfc9 hi 2026-08-31 14:10:18 -05:00
firestar5683 9f2f79a652 build 2026-08-31 12:38:45 -05:00
firestar5683 4d9df8b147 foghorn leghorn 2026-08-31 12:31:15 -05:00
firestar5683 138e4cca06 fix 2026-08-31 11:58:21 -05:00
firestarsdog 5d32cccaf1 Spruce it up 2026-08-31 03:50:24 -04:00
firestar5683 764417845f build 2026-08-30 21:03:05 -05:00
firestar5683 3fb2bcdcee 60kW ?! k 2026-08-30 20:59:58 -05:00
firestarsdog d8ac4dc57a Fix Volt OBD CC FP 2026-08-30 19:29:08 -04:00
firestarsdog 2620f9f9bd Big Tooth 2 2026-08-30 04:28:08 -04:00
firestarsdog 16e561c5b5 Big Tooth & Demos 2026-08-30 03:30:02 -04:00
firestar5683 6420724913 build 2026-08-29 22:42:35 -05:00
firestar5683 388662ceb0 bingo 2026-08-29 22:42:35 -05:00
firestar5683 b5286ec678 fix 2026-08-29 22:33:35 -05:00
firestar5683 aa9eeae40e Push Butt 2026-08-29 22:13:29 -05:00
firestar5683 d426054dec desktop 2026-08-29 21:38:15 -05:00
firestar5683 b22babb0b5 build 2026-08-29 21:12:45 -05:00
firestar5683 147b9df247 bluey 2026-08-29 21:12:45 -05:00
firestar5683 02a45f13a2 kia 2026-08-29 16:12:04 -05:00
firestar5683 5f256e60d9 lower voltage more 2026-08-29 15:56:22 -05:00
firestar5683 b6964f0142 fix 2026-08-29 15:21:35 -05:00
firestar5683 83c3ee8e26 lower power 2026-08-29 14:47:41 -05:00
Lukas Heintz 7926c2183d Tesla: smooth gas override jerk transition
Adapted from commaai/opendbc#3249 by @lukasloetkolben.
2026-08-29 14:33:56 -05:00
firestar5683 7d39054b6c gps 2026-08-29 14:17:05 -05:00
firestar5683 79fa8bd566 Merge corrected PRs #104 and #105
PR #104 by @inauner; PR #105 by @dirwin31.

Co-authored-by: inauner <inauner@users.noreply.github.com>

Co-authored-by: dirwin31 <dirwin31@users.noreply.github.com>
2026-08-29 14:09:01 -05:00
firestar5683 ae82751776 Merge PR #105 by @dirwin31
Rework the Galaxy dashcam routes page and downloads.

Co-authored-by: dirwin31 <dirwin31@users.noreply.github.com>
2026-08-29 14:08:17 -05:00
firestar5683 0911d9c208 Merge PR #104 by @inauner
Toyota door lock and low-voltage fixes.

Co-authored-by: inauner <inauner@users.noreply.github.com>
2026-08-29 14:02:55 -05:00
firestar5683 465ac2651a ICCU FAILURE 2026-08-29 11:55:16 -05:00
firestar5683 4486dc2f14 steer errors 2026-08-28 23:12:10 -05:00
firestar5683 f546a3e07a PIKA 2026-08-28 22:19:02 -05:00
Prabhaav Pillai 3acff1e3d7 Enhance vehicle annotation tool with side labels and mirrored preview adjustments consistient with pip-cam 2026-08-28 23:03:02 -04:00
firestar5683 f198977141 build 2026-08-28 21:52:37 -05:00
firestar5683 dc62d0e27a Sokka John 2026-08-28 21:50:54 -05:00
firestar5683 6b1ad03acf butt 2026-08-28 15:22:24 -05:00
firestar5683 ebb096a956 estate sale 2026-08-28 13:45:11 -05:00
firestar5683 11b2987c50 DRV ECU 2026-08-28 10:41:17 -05:00
firestarsdog ec0ab9d300 Big UI GPU Widget 2026-08-28 01:02:49 -04:00
firestar5683 f501a4de37 nighty night 2026-08-28 00:01:11 -05:00
firestar5683 63f8828c01 2018-2022 Accord radar 2026-08-27 23:20:58 -05:00
firestar5683 aa1d691304 The Last Dragon 2026-08-27 21:33:02 -05:00
firestar5683 ac95896551 moar 2026-08-27 21:33:02 -05:00
firestar5683 443c35228d iPod stuck on replay
f
2026-08-27 21:32:50 -05:00
dirwin31 4d281c22d7 Stream Mp4 downloads 2026-08-27 18:38:47 -07:00
dirwin31 6c1ec6798f Better Messaging 2026-08-27 18:25:51 -07:00
dirwin31 386a6f9216 Matching the field of view - pretty please 2026-08-27 18:12:25 -07:00
dirwin31 c02bb05113 Search is still hard 2026-08-27 18:11:03 -07:00
dirwin31 695573c057 Stop the video jumping 2026-08-27 18:01:49 -07:00
dirwin31 f0d8de490c so tired of changing these files 2026-08-27 17:56:50 -07:00
dirwin31 7bfe5ab830 When you just miss one value. 2026-08-27 17:47:48 -07:00
dirwin31 f07d588534 Searching is hard 2026-08-27 17:44:39 -07:00
dirwin31 a0aa48f82a Fix search and sort 2026-08-27 17:36:07 -07:00
dirwin31 3c852bdbb0 Fix Search, no jumpy video 2026-08-27 17:23:32 -07:00
dirwin31 f5ba6362ce Title updates and fix the ugly close button 2026-08-27 17:05:45 -07:00
dirwin31 2d93c29890 Allow Unpreserved Deletes Only 2026-08-27 16:49:24 -07:00
dirwin31 c0b8f1cb01 Try again 2026-08-27 15:58:57 -07:00
dirwin31 e19aed96da Try again for smooth swap 2026-08-27 15:53:53 -07:00
dirwin31 f6d7442c05 Make Low to High Smoother 2026-08-27 15:32:06 -07:00
dirwin31 8bcf4c7abf Tweaky Tweaky 2026-08-27 15:17:20 -07:00
dirwin31 f85bfb277b Ahhh Controls go BRRR 2026-08-27 13:25:57 -07:00
firestar5683 b2602bc4e7 v16 2026-08-27 15:11:41 -05:00
firestar5683 3e176f3884 bop 2026-08-27 14:49:17 -05:00
firestar5683 f79ab05fa5 wat 2026-08-27 14:43:40 -05:00
firestar5683 5b47ff584a build 2026-08-27 14:26:00 -05:00
firestar5683 af2e153e52 GUM 2026-08-27 14:24:43 -05:00
dirwin31 b619371441 Bug Fix 2026-08-27 11:56:58 -07:00
dirwin31 e3bfb66ded Update Layout 2026-08-27 11:43:35 -07:00
dirwin31 328f2d9ff7 Routes Page - Cleanup 2026-08-27 10:59:42 -07:00
dirwin31 b3c417ce05 Change to an overlay 2026-08-26 19:59:01 -07:00
dirwin31 00fc6d8158 Add ability to download route logs 2026-08-26 19:38:51 -07:00
inauner 47ccab65d9 hardware: add 30s sustained low voltage debounce and sentry power off notifications 2026-08-26 13:52:27 -07:00
inauner 979b297652 galaxy: fix Toyota and Lexus door lock and unlock vehicle check and execution 2026-08-26 13:00:10 -07:00
820 changed files with 92186 additions and 11491 deletions
+1
View File
@@ -81,6 +81,7 @@ selfdrive/modeld/models/*.pkl
# openpilot log files # openpilot log files
*.bz2 *.bz2
*.zst *.zst
!selfdrive/modeld/firmware/amdgpu/*.zst
build/ build/
+162
View File
@@ -0,0 +1,162 @@
# Credits and Code Provenance
StarPilot stands on work by comma.ai, FrogPilot, and many other open-source contributors. This
document records provenance that is not adequately represented by StarPilot's historical commit
authorship. It is not an assertion that an upstream contributor endorses, maintains, or is
responsible for StarPilot's adaptation.
## Ford support adapted from BluePilot
StarPilot commit [`3f6ccd104e826643887930b41ad3b4086b833d32`](https://github.com/firestar5683/StarPilot/commit/3f6ccd104e826643887930b41ad3b4086b833d32)
introduced a substantial adaptation of Ford work from BluePilot's `bp-7.0` branch. That commit's
message thanked the project, but it did not record a source revision or preserve the upstream
authors in the code. This file corrects that provenance gap without rewriting published history.
The exact checkout used for the original port was not recorded and therefore cannot now be proven.
At the time of the import,
[`3a838bba6d3d280592c2fd0496b3378977ae25f1`](https://github.com/BluePilotDev/bluepilot/commit/3a838bba6d3d280592c2fd0496b3378977ae25f1),
was the tip of the public `bp-7.0` line. Some imported platform lines instead trace to the
contemporaneous development line at
[`59e3f2f16a38ff0c16d173b0ccddded23aaa1cd8`](https://github.com/BluePilotDev/bluepilot/commit/59e3f2f16a38ff0c16d173b0ccddded23aaa1cd8).
Those histories were merged the next day as
[`e1d051d7ba270261b4455068bd68f1a58db15a4a`](https://github.com/BluePilotDev/bluepilot/commit/e1d051d7ba270261b4455068bd68f1a58db15a4a),
which is the complete `bp-7.0` snapshot used for this audit. This reconstruction is deliberately
recorded as a range rather than pretending that the missing original source SHA can be recovered.
### Upstream authors and work
- **Alan Polk (`alan-polk`)** is the principal upstream author of the Ford curvature controller,
angle-primary controller, manual-turn behavior, controller integration, and related panda safety
work. Important lineage commits include
[`db2bdff05`](https://github.com/BluePilotDev/bluepilot/commit/db2bdff05df103d71df62f45c2a3cb5211aba6e6),
[`d0aac605f`](https://github.com/BluePilotDev/bluepilot/commit/d0aac605f99d37e9da205e419f7989c1e9eaa386),
[`8f8d6d15f`](https://github.com/BluePilotDev/bluepilot/commit/8f8d6d15f0a590f42b78de964ffb0d0af7f5d63d), and
[`97867c1eb`](https://github.com/BluePilotDev/bluepilot/commit/97867c1eb57b7472f6fc3de62f0fef576e5a5497).
- **John Christman** contributed `bp-7.0` lateral integration, platform data, and the upstream
anti-stall work referenced by StarPilot's recovery logic, including
[`ec0ab181c`](https://github.com/BluePilotDev/bluepilot/commit/ec0ab181c344dccaae053e763bc6ce269551a2d8) and
[`9012f7666`](https://github.com/BluePilotDev/bluepilot/commit/9012f76666a5c90764fcaec40832b9a607488c27),
with additional vehicle data in
[`f0c6bf51f`](https://github.com/BluePilotDev/bluepilot/commit/f0c6bf51f19bb7b74226ead622cabef56e602125).
- **Jacob Neulight** contributed measured shadow-curvature and steering-pinion curvature/safety
work, including
[`699c17d9f`](https://github.com/BluePilotDev/bluepilot/commit/699c17d9fde62c690eed9d68eed7d731744d5137) and
[`26030f3cb`](https://github.com/BluePilotDev/bluepilot/commit/26030f3cb56a169ab0cf2b5a5ad15fa9af391b10).
- **Nathan Ingraham** contributed the separate high-speed damping adjustment in
[`1db52bc79`](https://github.com/BluePilotDev/bluepilot/commit/1db52bc79e607ddb00214254dd4d732615e27fe4) and
[`3610e3f18`](https://github.com/BluePilotDev/bluepilot/commit/3610e3f18a0ad37d793d7ada61dbc739ab1077a3).
- **Praeuner** contributed the damping-range update in
[`b600a8fdb`](https://github.com/BluePilotDev/bluepilot/commit/b600a8fdb985a2219fad4006ce9444b7071b52da).
- **tonesto7** contributed to the Ford integration and CAN-message history represented in the
imported branch, including
[`7f9212f7f`](https://github.com/BluePilotDev/bluepilot/commit/7f9212f7f8dc22c4a0a22443158554f4d3652a3c).
- **Haibin Wen and sunnypilot contributors** are named in file-level notices on upstream Ford
extension files that informed StarPilot's vehicle-state and lateral-limit integration. Their
published copyright and license notices are retained in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
- **Dirk Petersen, Alex Troxel, Kacer Aleks, and other BluePilot contributors** supplied Ford
fingerprints, VIN/platform data, tests, and integration changes that are represented in the
imported vehicle-support set. Examples include
[`3706b5645`](https://github.com/BluePilotDev/bluepilot/commit/3706b5645c270ad83cfecc3e92618924f0963166),
[`694bdbb7a`](https://github.com/BluePilotDev/bluepilot/commit/694bdbb7a9cc70af04d0118e891df96e47ded7c7), and
[`a02c06ef3`](https://github.com/BluePilotDev/bluepilot/commit/a02c06ef37acff518a511ba0a88a4dc65f506353).
This list identifies contributors whose work was found during the repository and blame audit. The
BluePilot repository and its Git history remain the authoritative record and may identify further
contributors.
### Local-to-upstream map
| StarPilot area | Upstream lineage | What StarPilot changed |
| --- | --- | --- |
| `starpilot/car/ford/lateral.py` | `human_turn.py`, `lateral_curv_ext.py`, and `values_ext.py` at the reference snapshot above; earlier StarPilot revisions also adapted `lateral_angle_ext.py` | Consolidated the runtime implementation on the extended-curvature strategy, integrated StarPilot Params, and added live-delay curvature lookahead and local tuning behavior. |
| `starpilot/car/ford/fordcan.py` | `fordcan_ext.py` and the extended-lateral protocol, especially `8f8d6d15f` | Reduced the extension to the curvature CAN constructors used by StarPilot and adapted it to the local controller interface. |
| `opendbc_repo/opendbc/car/ford/` | Ford controller/state/interface/radar/platform changes in the `bp-7.0` snapshot | Integrated the changes directly into StarPilot's opendbc layout instead of retaining sunnypilot mixins; subsequent fixes and behavior differ by file. |
| `opendbc_repo/opendbc/safety/modes/ford.h` and Ford safety tests | BluePilot panda enforcement for four-signal curvature and angle-primary control, especially `8f8d6d15f`, plus shadow-curvature work | Retained the extended-curvature checks, removed runtime path-angle selection, and continued adding local regression coverage. |
| Ford Params and settings surfaces | BluePilot's curvature tuning concepts | Renamed and implemented in StarPilot's native Params/Galaxy architecture; no BluePilot or sunnypilot UI classes were retained. |
The local code has materially diverged, but the first four rows remain derivative in design and in
parts of their implementation. Future ports should cite the exact upstream commit in the importing
commit and at the relevant source boundary; when history can be retained cleanly, use a merge,
subtree, or cherry-pick with origin metadata rather than a single squashed attribution.
## Hyundai, Kia, and Genesis support adapted from sunnypilot
Portions of StarPilot's HKG angle steering, CAN integration, panda safety enforcement, safety tests,
firmware fingerprints, and cruise-button management are adapted from sunnypilot. Some upstream HKG
authorship is retained in StarPilot's Git history, and StarPilot commit
[`e33305151`](https://github.com/firestar5683/StarPilot/commit/e33305151ba852ea3350aa9f7a12d1c8bd137c43)
identified its ICBM/CSLC work as a sunnypilot port. Other substantial imports were committed locally
without recording an exact source revision, so this section documents the reconstructed lineage.
The exact checkout used for each historical import cannot now be proven. The reference snapshots
used for this audit are:
- [`sunnypilot/sunnypilot` `hkg-angle-steering-2025`](https://github.com/sunnypilot/sunnypilot/tree/hkg-angle-steering-2025)
at [`cfb38312d`](https://github.com/sunnypilot/sunnypilot/commit/cfb38312db33779f4727c983d372474a56ccb5d8),
whose opendbc submodule points to the next snapshot.
- [`sunnypilot/opendbc` `hkg-angle-steering-2025`](https://github.com/sunnypilot/opendbc/tree/hkg-angle-steering-2025)
at [`cc4b08625`](https://github.com/sunnypilot/opendbc/commit/cc4b08625a98e94b318cab15e45e05dad58042bd).
- [`sunnypilot/opendbc` `master`](https://github.com/sunnypilot/opendbc)
at [`f95f996f5`](https://github.com/sunnypilot/opendbc/commit/f95f996f5917dcbbf2e32fe51b606a24cf836af6),
used to audit later HKG fingerprints and extension history.
### Upstream authors and work
- **Haibin (Jason) Wen** contributed the original Kia EV9 HDA2/LFA2 angle-steering port, Hyundai
angle integration and signals, non-SCC platform support, and Intelligent Cruise Button Management.
Important lineage commits include
[`abe78de2c`](https://github.com/sunnypilot/opendbc/commit/abe78de2c2f9d8774d343c35c181d27e9d944392),
[`333de6f1a`](https://github.com/sunnypilot/opendbc/commit/333de6f1a2097a27709a652d5862bad05d83ed1f),
[`559a37426`](https://github.com/sunnypilot/opendbc/commit/559a37426976171ceb56c541bec9362abd8b8bd2), and
[`862828ad6`](https://github.com/sunnypilot/opendbc/commit/862828ad6f870fb23dd8670fe2a18dc19313217b).
- **Shane Smiskol** contributed foundational angle-command limiting, driver-override behavior, and
EPS-fault avoidance, including
[`228a397a3`](https://github.com/sunnypilot/opendbc/commit/228a397a37618de1ecbaf06b70efe3e0e0a8eec6) and
[`42d84ff6c`](https://github.com/sunnypilot/opendbc/commit/42d84ff6ca3d48529b195395d652ad777ad397ca).
- **DevTekVE** contributed substantial angle-controller integration, tuning, platform support, panda
safety logic, and tests, including
[`c77c7ec2e`](https://github.com/sunnypilot/opendbc/commit/c77c7ec2e39959b65c42e2d70aa943fccd606361),
[`7ded99dba`](https://github.com/sunnypilot/opendbc/commit/7ded99dba145c56754e8bbe47217eb174ec03396),
[`3819ca7f0`](https://github.com/sunnypilot/opendbc/commit/3819ca7f0d2576771924bd60f47d3e8676b5d583), and
[`8d134e98f`](https://github.com/sunnypilot/opendbc/commit/8d134e98f3151a1aec354f93e81c3a4269788991).
- **Nicholas Evans, dany7915, janpoo6427, Tinkerpet, royjr, Taylor Hoshino, Intelli, Joshua Mack,
Mark McCallister, Discountchubbs, and other sunnypilot contributors** supplied vehicle ports,
fingerprints, firmware data, safety-limit updates, and related integration represented in the
adapted HKG support.
This list identifies contributors found during the repository, commit, and blame audit. The
sunnypilot repositories and their Git histories remain the authoritative record and may identify
additional contributors.
### Local-to-upstream map
| StarPilot area | Upstream lineage | What StarPilot changed |
| --- | --- | --- |
| `opendbc_repo/opendbc/car/hyundai/carcontroller.py` | Angle controller and driver-override work in `sunnypilot/opendbc` `hkg-angle-steering-2025` | Integrated the controller into StarPilot's opendbc layout and added substantial local longitudinal, smoothing, recovery, and platform behavior. |
| `opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py`, `interface.py`, and `values.py` | Angle commands, signal selection, safety flags, limits, and platform integration from the angle branch | Combined later upstream changes with StarPilot flags, Params, platform tuning, and local CAN/CAN-FD behavior. |
| `opendbc_repo/opendbc/safety/modes/hyundai_canfd.h` and its tests | Angle-command safety enforcement and regression tests from the angle branch | Extended and reorganized the safety mode and tests for StarPilot's current supported modes. |
| `opendbc_repo/opendbc/car/hyundai/fingerprints.py` and HKG platform data | sunnypilot HKG extension/fingerprint history on `master` plus angle-branch vehicle ports | Flattened extension data into the local opendbc tree and continued adding and updating platforms. |
| HKG cruise-button management and settings integration | sunnypilot ICBM/CSLC concepts and implementation history, including `862828ad6` and `2277e3d49` | Adapted the feature to StarPilot/FrogPilot controls and settings; later revisions changed or removed portions of the original integration. |
The current implementation is not a wholesale copy of either reference snapshot and has diverged
substantially. Its HKG angle-control and supporting safety architecture nevertheless remain
derivative in design and in identifiable portions of the implementation. The applicable upstream
notices are preserved in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
## Policy for future third-party ports
Before publishing a third-party port:
1. Read every repository-level and file-level license that may cover the source. Stop and ask the
rightsholder when the terms or their scope conflict.
2. Record the repository URL, exact commit SHA, source paths/symbols, authors found in the relevant
history, and the local destination in the importing commit and this file.
3. Preserve required copyright and license notices verbatim. Reorganization, translation, and
AI-assisted rewriting do not remove source provenance.
4. Retain authorship/history with a merge, subtree, or `cherry-pick -x` when practical. For a true
adaptation, keep the local adapter as commit author and use an `Adapted-from:` trailer and source
comments; do not add `Co-authored-by:` for a person without their agreement.
5. Describe material local deviations and make clear that upstream contributors do not support or
endorse the downstream version.
See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for licensing notices.
+1
View File
@@ -1,4 +1,5 @@
Copyright (c) 2018, Comma.ai, Inc. Copyright (c) 2018, Comma.ai, Inc.
Copyright (c) 2026, firestar5683 and StarPilot 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: 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:
+19
View File
@@ -21,6 +21,20 @@ but [has expanded to offer Quality-Of-Life improvements for all](#features)!
StarPilot is built off of [FrogPilot](https://github.com/FrogAi/FrogPilot) StarPilot is built off of [FrogPilot](https://github.com/FrogAi/FrogPilot)
and supports the major features FrogPilot offers. and supports the major features FrogPilot offers.
Ford-specific lateral-control and vehicle-support work includes substantial adaptations from
[BluePilot](https://github.com/BluePilotDev/bluepilot/tree/bp-7.0), principally developed by
[Alan Polk](https://github.com/alan-polk) with additional BluePilot contributors. StarPilot's
implementation has since diverged, but that does not erase its lineage. See [CREDITS.md](CREDITS.md)
for the code-level provenance and [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for applicable
upstream notices and terms. BluePilot and its contributors do not maintain or endorse StarPilot;
please direct support requests for this adaptation to the StarPilot project.
Hyundai, Kia, and Genesis angle steering and related vehicle support include substantial adaptations
from [sunnypilot](https://github.com/sunnypilot/sunnypilot/tree/hkg-angle-steering-2025) and its
[opendbc angle-steering branch](https://github.com/sunnypilot/opendbc/tree/hkg-angle-steering-2025).
StarPilot's implementation has diverged significantly; the upstream contributors do not maintain
this adaptation. Detailed code lineage is recorded in [CREDITS.md](CREDITS.md#hyundai-kia-and-genesis-support-adapted-from-sunnypilot).
StarPilot has a vibrant, welcoming community [discord](https://firestar.link/discord). StarPilot has a vibrant, welcoming community [discord](https://firestar.link/discord).
Stop by to chat or ask questions! Stop by to chat or ask questions!
@@ -77,4 +91,9 @@ Uses your comma's sysroot/toolchain
* Custom long maneuver tests, specifically designed for regen-only vehicles * Custom long maneuver tests, specifically designed for regen-only vehicles
## Third-Party Notices ## Third-Party Notices
* Portions of this software include modified versions of the Material Design Icons provided by Google under the Apache License 2.0. A copy of the license is included in the `LICENSE-MDI` file. * Portions of this software include modified versions of the Material Design Icons provided by Google under the Apache License 2.0. A copy of the license is included in the `LICENSE-MDI` file.
* Ford support includes software adapted from BluePilot's `bp-7.0` branch. The source repository contains both a standard MIT notice and a separate custom SUNNYPILOT LLC notice; StarPilot preserves both in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
* Hyundai, Kia, and Genesis support includes software adapted directly from sunnypilot's HKG angle-steering branch and sunnypilot/opendbc. StarPilot preserves the applicable notices in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
> This project uses software from Haibin Wen and SUNNYPILOT LLC and is licensed under a custom license requiring permission for use.
+125
View File
@@ -0,0 +1,125 @@
# Third-Party Notices
This file supplements StarPilot's root `LICENSE`. StarPilot-original contributions are offered
under that MIT license. Third-party material remains subject to its original terms; inclusion here
does not relicense it or imply endorsement by an upstream project or contributor.
## BluePilot `bp-7.0` Ford support
Portions of StarPilot's Ford lateral control, CAN integration, panda safety logic, vehicle data,
and related tests are adapted from the public BluePilot repository:
- Source: <https://github.com/BluePilotDev/bluepilot/tree/bp-7.0>
- Audited `bp-7.0` snapshot: [`e1d051d7ba270261b4455068bd68f1a58db15a4a`](https://github.com/BluePilotDev/bluepilot/commit/e1d051d7ba270261b4455068bd68f1a58db15a4a)
- Historical reconstruction: [CREDITS.md](CREDITS.md#ford-support-adapted-from-bluepilot)
- Provenance and contributors: [CREDITS.md](CREDITS.md)
## sunnypilot Hyundai, Kia, and Genesis support
Portions of StarPilot's HKG angle steering, CAN integration, panda safety enforcement and tests,
firmware fingerprints, platform data, and cruise-button management are adapted directly from the
public sunnypilot repositories:
- Source: <https://github.com/sunnypilot/sunnypilot/tree/hkg-angle-steering-2025>
- Audited superproject snapshot: [`cfb38312db33779f4727c983d372474a56ccb5d8`](https://github.com/sunnypilot/sunnypilot/commit/cfb38312db33779f4727c983d372474a56ccb5d8)
- Source: <https://github.com/sunnypilot/opendbc/tree/hkg-angle-steering-2025>
- Audited angle-branch snapshot: [`cc4b08625a98e94b318cab15e45e05dad58042bd`](https://github.com/sunnypilot/opendbc/commit/cc4b08625a98e94b318cab15e45e05dad58042bd)
- Later HKG history was audited against `sunnypilot/opendbc` `master` at
[`f95f996f5917dcbbf2e32fe51b606a24cf836af6`](https://github.com/sunnypilot/opendbc/commit/f95f996f5917dcbbf2e32fe51b606a24cf836af6).
- Historical reconstruction and contributors: [CREDITS.md](CREDITS.md#hyundai-kia-and-genesis-support-adapted-from-sunnypilot)
## Applicable upstream license notices
The upstream repositories and branches identified above publish both `LICENSE` and `LICENSE.md`.
Their READMEs point to `LICENSE` for openpilot licensing, while some extension files point to
`LICENSE.md`. Because the scope of those two notices is not unambiguous, StarPilot preserves both
and treats the more restrictive notice conservatively for upstream-derived material. This is a
record of the published notices, not a legal conclusion about their scope.
### Upstream `LICENSE` notice (MIT)
Copyright (c) 2018, Comma.ai, Inc.
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.
### Upstream `opendbc` `LICENSE` notice (MIT)
Copyright (c) 2020, Comma.ai, Inc.
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.
### Upstream extension file notice
Several upstream Ford and HKG extension files carry this file-level notice:
> 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.
StarPilot's Ford and HKG integrations were informed by extension files carrying this notice. The
reference to “MIT License” conflicts with the nonstandard restrictions in the referenced upstream
`LICENSE.md`; both texts are retained here rather than silently choosing between them.
### Upstream `LICENSE.md` notice (published as “Custom MIT License”)
The following notice is reproduced verbatim from the reference branch:
> # Custom MIT License
>
> Copyright (c) 2024, Haibin Wen, SUNNYPILOT LLC
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to view and modify the Software, subject to the following conditions:
>
> 1. **Permission Required**: Permission Required for Commercial, For-Profit, or Closed Source Use: Use of the Software, in whole or in part, for any commercial purposes, for-profit projects, or in closed source projects requires explicit written permission from the original author(s).
>
> 2. **Redistribution**: Any redistribution of the Software, modified or unmodified, must retain this license notice and the following acknowledgment:
> "This software is licensed under a custom license requiring permission for use."
>
> 3. **Visibility**: Any project that uses the Software must visibly mention the following acknowledgment:
> "This project uses software from Haibin Wen and SUNNYPILOT LLC and is licensed under a custom license requiring permission for use."
>
> 4. **No Warranty**: 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.
>
> Contact sunnypilot Support <support@sunnypilot.ai> for permission requests.
>
> ---
>
> Haibin Wen, SUNNYPILOT LLC
Required upstream acknowledgments:
> This software is licensed under a custom license requiring permission for use.
>
> This project uses software from Haibin Wen and SUNNYPILOT LLC and is licensed under a custom license requiring permission for use.
For commercial, for-profit, or closed-source use, consult the upstream notice and obtain any
permission it requires. The upstream repository's simultaneous publication of two differently
scoped notices should be clarified with the relevant copyright holders before relying on one to
the exclusion of the other.
Binary file not shown.
+1
View File
@@ -774,6 +774,7 @@ struct ChestnutState {
pcieLtssm @7 :UInt8; pcieLtssm @7 :UInt8;
supplyVoltage @8 :UInt16; # mV supplyVoltage @8 :UInt16; # mV
supplyCurrent @9 :Int16; # mA supplyCurrent @9 :Int16; # mA
supplyFault @10 :Bool;
} }
struct RadarState @0x9a185389d6fdd05f { struct RadarState @0x9a185389d6fdd05f {
Binary file not shown.
+65 -7
View File
@@ -16,6 +16,10 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"AthenadUploadQueue", {PERSISTENT, JSON}}, {"AthenadUploadQueue", {PERSISTENT, JSON}},
{"AthenadRecentlyViewedRoutes", {PERSISTENT, STRING}}, {"AthenadRecentlyViewedRoutes", {PERSISTENT, STRING}},
{"BootCount", {PERSISTENT, INT}}, {"BootCount", {PERSISTENT, INT}},
{"BluetoothAudioAddress", {PERSISTENT, STRING}},
{"BluetoothAudioTestActive", {CLEAR_ON_MANAGER_START | DONT_LOG, BOOL}},
{"BluetoothDisconnectControllersOffroad", {PERSISTENT, BOOL, "0"}},
{"BluetoothEnabled", {PERSISTENT, BOOL, "0"}},
{"CalibrationParams", {PERSISTENT, BYTES}}, {"CalibrationParams", {PERSISTENT, BYTES}},
{"CameraDebugExpGain", {CLEAR_ON_MANAGER_START, STRING}}, {"CameraDebugExpGain", {CLEAR_ON_MANAGER_START, STRING}},
{"CameraDebugExpTime", {CLEAR_ON_MANAGER_START, STRING}}, {"CameraDebugExpTime", {CLEAR_ON_MANAGER_START, STRING}},
@@ -84,6 +88,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"IsTakingSnapshot", {CLEAR_ON_MANAGER_START, BOOL}}, {"IsTakingSnapshot", {CLEAR_ON_MANAGER_START, BOOL}},
{"IsTestedBranch", {CLEAR_ON_MANAGER_START, BOOL}}, {"IsTestedBranch", {CLEAR_ON_MANAGER_START, BOOL}},
{"JoystickDebugMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, {"JoystickDebugMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
{"JoystickControlDevice", {PERSISTENT, STRING}},
{"LanguageSetting", {PERSISTENT, STRING, "main_en"}}, {"LanguageSetting", {PERSISTENT, STRING, "main_en"}},
{"LastAthenaPingTime", {CLEAR_ON_MANAGER_START, INT}}, {"LastAthenaPingTime", {CLEAR_ON_MANAGER_START, INT}},
{"LastGPSPosition", {PERSISTENT, STRING}}, {"LastGPSPosition", {PERSISTENT, STRING}},
@@ -105,10 +110,17 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"LocationFilterInitialState", {PERSISTENT, BYTES}}, {"LocationFilterInitialState", {PERSISTENT, BYTES}},
{"LongitudinalManeuverMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, {"LongitudinalManeuverMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
{"LongitudinalPersonality", {PERSISTENT, INT, std::to_string(static_cast<int>(cereal::LongitudinalPersonality::STANDARD))}}, {"LongitudinalPersonality", {PERSISTENT, INT, std::to_string(static_cast<int>(cereal::LongitudinalPersonality::STANDARD))}},
{"LongitudinalPersonalityProfiles", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}},
{"NetworkMetered", {PERSISTENT, BOOL}}, {"NetworkMetered", {PERSISTENT, BOOL}},
{"ObdMultiplexingChanged", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}}, {"ObdMultiplexingChanged", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
{"ObdMultiplexingEnabled", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}}, {"ObdMultiplexingEnabled", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
{"Offroad_CarUnrecognized", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}}, {"Offroad_CarUnrecognized", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_ChestnutNotDetected", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_ChestnutOverheated", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_ChestnutPcieUnavailable", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_ChestnutUncompiled", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_ChestnutUpdateFailed", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_ChestnutUsbSlow", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_ConnectivityNeeded", {CLEAR_ON_MANAGER_START, JSON}}, {"Offroad_ConnectivityNeeded", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_ConnectivityNeededPrompt", {CLEAR_ON_MANAGER_START, JSON}}, {"Offroad_ConnectivityNeededPrompt", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_ExcessiveActuation", {PERSISTENT, JSON}}, {"Offroad_ExcessiveActuation", {PERSISTENT, JSON}},
@@ -255,6 +267,32 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"CustomAccelProfile45MPH", {PERSISTENT, FLOAT, "1.0", "1.0", 3}}, {"CustomAccelProfile45MPH", {PERSISTENT, FLOAT, "1.0", "1.0", 3}},
{"CustomAccelProfile56MPH", {PERSISTENT, FLOAT, "0.8", "0.8", 3}}, {"CustomAccelProfile56MPH", {PERSISTENT, FLOAT, "0.8", "0.8", 3}},
{"CustomAccelProfile89MPH", {PERSISTENT, FLOAT, "0.6", "0.6", 3}}, {"CustomAccelProfile89MPH", {PERSISTENT, FLOAT, "0.6", "0.6", 3}},
{"CustomAccelProfileBreakpointsInitialized", {PERSISTENT, BOOL, "0", "0", 3}},
{"CustomAccelProfilePointCount", {PERSISTENT, INT, "7", "7", 3}},
{"CustomAccelProfileBreakpoint1MPH", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
{"CustomAccelProfileBreakpoint2MPH", {PERSISTENT, FLOAT, "11.184681", "11.184681", 3}},
{"CustomAccelProfileBreakpoint3MPH", {PERSISTENT, FLOAT, "22.369363", "22.369363", 3}},
{"CustomAccelProfileBreakpoint4MPH", {PERSISTENT, FLOAT, "33.554044", "33.554044", 3}},
{"CustomAccelProfileBreakpoint5MPH", {PERSISTENT, FLOAT, "44.738726", "44.738726", 3}},
{"CustomAccelProfileBreakpoint6MPH", {PERSISTENT, FLOAT, "55.923407", "55.923407", 3}},
{"CustomAccelProfileBreakpoint7MPH", {PERSISTENT, FLOAT, "89.477452", "89.477452", 3}},
{"CustomAccelProfileBreakpoint8MPH", {PERSISTENT, FLOAT, "100.662133", "100.662133", 3}},
{"CustomAccelProfileBreakpoint9MPH", {PERSISTENT, FLOAT, "111.846815", "111.846815", 3}},
{"CustomAccelProfileBreakpoint10MPH", {PERSISTENT, FLOAT, "123.031496", "123.031496", 3}},
{"CustomAccelProfileBreakpoint11MPH", {PERSISTENT, FLOAT, "134.216178", "134.216178", 3}},
{"CustomAccelProfileBreakpoint12MPH", {PERSISTENT, FLOAT, "145.400859", "145.400859", 3}},
{"CustomAccelProfilePoint1Accel", {PERSISTENT, FLOAT, "3.0", "3.0", 3}},
{"CustomAccelProfilePoint2Accel", {PERSISTENT, FLOAT, "2.5", "2.5", 3}},
{"CustomAccelProfilePoint3Accel", {PERSISTENT, FLOAT, "2.0", "2.0", 3}},
{"CustomAccelProfilePoint4Accel", {PERSISTENT, FLOAT, "1.5", "1.5", 3}},
{"CustomAccelProfilePoint5Accel", {PERSISTENT, FLOAT, "1.0", "1.0", 3}},
{"CustomAccelProfilePoint6Accel", {PERSISTENT, FLOAT, "0.8", "0.8", 3}},
{"CustomAccelProfilePoint7Accel", {PERSISTENT, FLOAT, "0.6", "0.6", 3}},
{"CustomAccelProfilePoint8Accel", {PERSISTENT, FLOAT, "0.55", "0.55", 3}},
{"CustomAccelProfilePoint9Accel", {PERSISTENT, FLOAT, "0.5", "0.5", 3}},
{"CustomAccelProfilePoint10Accel", {PERSISTENT, FLOAT, "0.45", "0.45", 3}},
{"CustomAccelProfilePoint11Accel", {PERSISTENT, FLOAT, "0.4", "0.4", 3}},
{"CustomAccelProfilePoint12Accel", {PERSISTENT, FLOAT, "0.35", "0.35", 3}},
{"CustomCruise", {PERSISTENT, FLOAT, "1.0", "1.0", 2, SETTINGS_SIMPLE}}, {"CustomCruise", {PERSISTENT, FLOAT, "1.0", "1.0", 2, SETTINGS_SIMPLE}},
{"CustomCruiseLong", {PERSISTENT, FLOAT, "5.0", "5.0", 2, SETTINGS_SIMPLE}}, {"CustomCruiseLong", {PERSISTENT, FLOAT, "5.0", "5.0", 2, SETTINGS_SIMPLE}},
{"CustomPersonalities", {PERSISTENT, BOOL, "0", "0", 2}}, {"CustomPersonalities", {PERSISTENT, BOOL, "0", "0", 2}},
@@ -280,6 +318,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"DeveloperSidebarMetric7", {PERSISTENT, INT, "7", "0", 3}}, {"DeveloperSidebarMetric7", {PERSISTENT, INT, "7", "0", 3}},
{"DeveloperUI", {PERSISTENT, BOOL, "0", "0", 3}}, {"DeveloperUI", {PERSISTENT, BOOL, "0", "0", 3}},
{"GalaxyDeveloperMode", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 0, SETTINGS_SIMPLE}}, {"GalaxyDeveloperMode", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
{"GalaxyMobileDefault", {PERSISTENT | DONT_LOG, BOOL, "1", "1", 0, SETTINGS_SIMPLE}},
{"DeveloperWidgets", {PERSISTENT, BOOL, "1", "0", 3}}, {"DeveloperWidgets", {PERSISTENT, BOOL, "1", "0", 3}},
{"DeviceManagement", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}}, {"DeviceManagement", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
{"DeviceShutdown", {PERSISTENT, INT, "6", "6", 1, SETTINGS_SIMPLE}}, {"DeviceShutdown", {PERSISTENT, INT, "6", "6", 1, SETTINGS_SIMPLE}},
@@ -300,6 +339,12 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"DownloadAllModels", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}}, {"DownloadAllModels", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
{"DownloadMaps", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}}, {"DownloadMaps", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
{"DriverCamera", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}}, {"DriverCamera", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
{"ActiveBigModel", {PERSISTENT, STRING}},
{"ActiveBigModelName", {PERSISTENT, STRING}},
{"ActiveBigModelVersion", {PERSISTENT, STRING}},
{"ActiveSmallModel", {PERSISTENT, STRING}},
{"ActiveSmallModelName", {PERSISTENT, STRING}},
{"ActiveSmallModelVersion", {PERSISTENT, STRING}},
{"Model", {PERSISTENT, STRING, "rdf43", "rdf43", 1}}, {"Model", {PERSISTENT, STRING, "rdf43", "rdf43", 1}},
{"ModelVersion", {PERSISTENT, STRING, "v15", "v15", 1}}, {"ModelVersion", {PERSISTENT, STRING, "v15", "v15", 1}},
{"DrivingModel", {PERSISTENT, STRING, "rdf43", "rdf43", 1}}, {"DrivingModel", {PERSISTENT, STRING, "rdf43", "rdf43", 1}},
@@ -342,17 +387,13 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"ForceStandstill", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}}, {"ForceStandstill", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"FordLKASButtonControlMigrated", {PERSISTENT, BOOL, "0", "0"}}, {"FordLKASButtonControlMigrated", {PERSISTENT, BOOL, "0", "0"}},
{"ForceTorqueController", {PERSISTENT, BOOL, "0", "0", 3}}, {"ForceTorqueController", {PERSISTENT, BOOL, "0", "0", 3}},
{"FordAngleBlend", {PERSISTENT, FLOAT, "0.5", "0.5", 2}}, // These Ford curvature tuning concepts descend from BluePilot bp-7.0. StarPilot's key names and
{"FordAngleHighSpeedDamping", {PERSISTENT, FLOAT, "1.0", "1.0", 2}}, // settings integration are local; see /CREDITS.md and /THIRD_PARTY_NOTICES.md for provenance.
{"FordAngleHighSpeedFactor", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
{"FordAngleLaneChangeFactor", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
{"FordAngleLowSpeedFactor", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
{"FordCurvatureBlendHigh", {PERSISTENT, FLOAT, "0.4", "0.4", 2}}, {"FordCurvatureBlendHigh", {PERSISTENT, FLOAT, "0.4", "0.4", 2}},
{"FordCurvatureBlendLow", {PERSISTENT, FLOAT, "0.4", "0.4", 2}}, {"FordCurvatureBlendLow", {PERSISTENT, FLOAT, "0.4", "0.4", 2}},
{"FordCurvatureLaneChangeFactor", {PERSISTENT, FLOAT, "0.85", "0.85", 2}}, {"FordCurvatureLaneChangeFactor", {PERSISTENT, FLOAT, "0.85", "0.85", 2}},
{"FordHandsFreeCluster", {PERSISTENT, BOOL, "0", "0", 2}}, {"FordHandsFreeCluster", {PERSISTENT, BOOL, "0", "0", 2}},
{"FordHumanTurnDetection", {PERSISTENT, BOOL, "1", "1", 2}}, {"FordHumanTurnDetection", {PERSISTENT, BOOL, "1", "1", 2}},
{"FordLateralMode", {PERSISTENT, INT, "1", "1", 2}},
{"FLMActiveOverrides", {PERSISTENT, JSON, "{}", "{}", 2}}, {"FLMActiveOverrides", {PERSISTENT, JSON, "{}", "{}", 2}},
{"FLMActiveProfileId", {PERSISTENT, STRING, "", "", 2}}, {"FLMActiveProfileId", {PERSISTENT, STRING, "", "", 2}},
{"FLMSubmittedTune", {CLEAR_ON_MANAGER_START, JSON, "{}", "{}"}}, {"FLMSubmittedTune", {CLEAR_ON_MANAGER_START, JSON, "{}", "{}"}},
@@ -365,6 +406,12 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"StarPilotCarParamsPersistent", {PERSISTENT, BYTES, "", ""}}, {"StarPilotCarParamsPersistent", {PERSISTENT, BYTES, "", ""}},
{"StarPilotDongleId", {PERSISTENT | DONT_LOG, STRING, "", "", 0}}, {"StarPilotDongleId", {PERSISTENT | DONT_LOG, STRING, "", "", 0}},
{"StarPilotFavoriteSlots", {PERSISTENT, JSON, "[]", "[]", 1}}, {"StarPilotFavoriteSlots", {PERSISTENT, JSON, "[]", "[]", 1}},
{"ControllerActionSlots", {PERSISTENT, JSON, "[]", "[]", 1}},
{"WheelControlLearnSlot", {CLEAR_ON_MANAGER_START | DONT_LOG, INT}},
{"WheelControlMappings", {PERSISTENT, JSON, "[]", "[]", 1}},
{"WheelControlStatus", {CLEAR_ON_MANAGER_START | DONT_LOG, JSON, "{}", "{}"}},
{"WheelControlTestActive", {CLEAR_ON_MANAGER_START | DONT_LOG, BOOL}},
{"WheelControlsEnabled", {PERSISTENT, BOOL, "0"}},
{"StarPilotStats", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}}, {"StarPilotStats", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}},
{"StarPilotTogglesUpdated", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}}, {"StarPilotTogglesUpdated", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
{"GoatScream", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}}, {"GoatScream", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
@@ -418,7 +465,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"LeadDepartingAlert", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}}, {"LeadDepartingAlert", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
{"LeadDetectionThreshold", {PERSISTENT, INT, "35", "50", 3}}, {"LeadDetectionThreshold", {PERSISTENT, INT, "35", "50", 3}},
{"LeadIndicator", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}}, {"LeadIndicator", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"LeadInfo", {PERSISTENT, BOOL, "1", "0", 3}}, {"LeadInfo", {PERSISTENT, BOOL, "0", "0", 3}},
{"LeadInfoMode", {PERSISTENT, INT, "2", "2", 3}},
{"LKASButtonControl", {PERSISTENT, INT, "5", "0", 2, SETTINGS_SIMPLE}}, {"LKASButtonControl", {PERSISTENT, INT, "5", "0", 2, SETTINGS_SIMPLE}},
{"LockDoors", {PERSISTENT, BOOL, "1", "0", 0}}, {"LockDoors", {PERSISTENT, BOOL, "1", "0", 0}},
{"LockDoorsTimer", {PERSISTENT, INT, "0", "0", 0}}, {"LockDoorsTimer", {PERSISTENT, INT, "0", "0", 0}},
@@ -474,6 +522,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"ModeButtonControl", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}}, {"ModeButtonControl", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
{"ModelDownloadProgress", {CLEAR_ON_MANAGER_START, STRING, "", ""}}, {"ModelDownloadProgress", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
{"ModelDrivesAndScores", {PERSISTENT, JSON, "{}", "{}"}}, {"ModelDrivesAndScores", {PERSISTENT, JSON, "{}", "{}"}},
{"ModelLabConfig", {PERSISTENT, JSON, "{}", "{}"}},
{"ModelLabModelToDownload", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
{"ModelLabRuntime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, JSON, "{}", "{}"}},
{"ModelReleasedDates", {PERSISTENT, STRING, "", "", 1}}, {"ModelReleasedDates", {PERSISTENT, STRING, "", "", 1}},
{"ModelRandomizer", {PERSISTENT, BOOL, "0", "0", 2}}, {"ModelRandomizer", {PERSISTENT, BOOL, "0", "0", 2}},
{"LatSmoothSeconds", {PERSISTENT, FLOAT, "0.1", "0.1", 3}}, {"LatSmoothSeconds", {PERSISTENT, FLOAT, "0.1", "0.1", 3}},
@@ -507,6 +558,11 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"FavoriteVirtualDecelCruiseCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}}, {"FavoriteVirtualDecelCruiseCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
{"FavoriteTrafficModeCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}}, {"FavoriteTrafficModeCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
{"WheelButtonBookmarkCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}}, {"WheelButtonBookmarkCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
{"WheelControlAOLCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
{"WheelControlDisengageCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
{"WheelControlEngageCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
{"WheelControlForceCoastCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
{"WheelControlPulseGlideCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
{"openpilotMinutes", {PERSISTENT, INT, "0", "0", 0}}, {"openpilotMinutes", {PERSISTENT, INT, "0", "0", 0}},
{"OverpassRequests", {PERSISTENT, JSON, "{}", "{}"}}, {"OverpassRequests", {PERSISTENT, JSON, "{}", "{}"}},
{"PathColor", {PERSISTENT, STRING, "", "", 2, SETTINGS_SIMPLE}}, {"PathColor", {PERSISTENT, STRING, "", "", 2, SETTINGS_SIMPLE}},
@@ -555,6 +611,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"RelaxedJerkDeceleration", {PERSISTENT, FLOAT, "100.0", "100.0", 3}}, {"RelaxedJerkDeceleration", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"RelaxedJerkSpeed", {PERSISTENT, FLOAT, "100.0", "100.0", 3}}, {"RelaxedJerkSpeed", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"RelaxedJerkSpeedDecrease", {PERSISTENT, FLOAT, "100.0", "100.0", 3}}, {"RelaxedJerkSpeedDecrease", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"ReverseCruise", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
{"RivianAngleControl", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}}, {"RivianAngleControl", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"RivianAngleSaturated", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL, "0", "0"}}, {"RivianAngleSaturated", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL, "0", "0"}},
{"RivianToiRecoveryFailed", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL, "0", "0"}}, {"RivianToiRecoveryFailed", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL, "0", "0"}},
@@ -666,6 +723,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"SubaruSNG", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}}, {"SubaruSNG", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
{"SubaruSNGManualParkingBrake", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}}, {"SubaruSNGManualParkingBrake", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"SubaruStopStartOff", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}}, {"SubaruStopStartOff", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"SubaruRedneckCruise", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"TacoTune", {PERSISTENT, BOOL, "0", "0", 2}}, {"TacoTune", {PERSISTENT, BOOL, "0", "0", 2}},
{"TeslaCoopSteering", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}}, {"TeslaCoopSteering", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"TestAlert", {CLEAR_ON_MANAGER_START, STRING, "", ""}}, {"TestAlert", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
Binary file not shown.
@@ -0,0 +1,44 @@
from types import SimpleNamespace
from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR
from openpilot.starpilot.common.lateral_only_experimental import (
experimental_mode_available,
lateral_only_experimental_available,
)
def test_telluride_platform_allows_lateral_only_experimental_mode():
CP = SimpleNamespace(
carFingerprint=HYUNDAI_CAR.HYUNDAI_PALISADE_2023,
openpilotLongitudinalControl=False,
)
assert lateral_only_experimental_available(CP)
assert experimental_mode_available(CP)
def test_lateral_only_mode_does_not_expand_other_stock_acc_cars():
CP = SimpleNamespace(
carFingerprint=HYUNDAI_CAR.HYUNDAI_SONATA,
openpilotLongitudinalControl=False,
)
assert not lateral_only_experimental_available(CP)
assert not experimental_mode_available(CP)
old_palisade = SimpleNamespace(
carFingerprint=HYUNDAI_CAR.HYUNDAI_PALISADE,
openpilotLongitudinalControl=False,
)
assert not lateral_only_experimental_available(old_palisade)
def test_normal_experimental_mode_remains_available_with_openpilot_long():
CP = SimpleNamespace(
carFingerprint=HYUNDAI_CAR.HYUNDAI_SONATA,
openpilotLongitudinalControl=True,
)
assert not lateral_only_experimental_available(CP)
assert experimental_mode_available(CP)
+26 -1
View File
@@ -5,7 +5,7 @@ import threading
import time import time
import uuid import uuid
from openpilot.common.params import Params, ParamKeyFlag, UnknownKeyName from openpilot.common.params import Params, ParamKeyFlag, ParamKeyType, UnknownKeyName
class TestParams: class TestParams:
def setup_method(self): def setup_method(self):
@@ -128,6 +128,31 @@ class TestParams:
assert self.params.get("LiveParameters") is None assert self.params.get("LiveParameters") is None
assert self.params.get("LiveParameters", return_default=True) is None assert self.params.get("LiveParameters", return_default=True) is None
def test_longitudinal_personality_profiles_json_round_trip(self):
key = "LongitudinalPersonalityProfiles"
value = {
"schemaVersion": 1,
"enabled": False,
"axes": {
"acceleration": {
"speed": {"unit": "mph", "values": [0.0, 11.184681, 22.369363, 33.554044, 44.738726, 55.923407, 89.477452]},
"value": {"unit": "m/s^2", "meaning": "maximum_requested_acceleration"},
},
"braking": {
"speed": {"unit": "mph", "values": [0.0, 11.184681, 22.369363, 33.554044, 44.738726, 55.923407, 89.477452]},
"value": {"unit": "m/s^2", "meaning": "cruise_slc_deceleration_magnitude"},
},
"following": {"speed": {"unit": "mph", "values": [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]}, "value": {"unit": "s", "meaning": "base_time_headway"}},
},
"profiles": {},
}
self.params.remove(key)
assert self.params.get_type(key) == ParamKeyType.JSON
assert self.params.get(key) is None
self.params.put(key, value)
assert self.params.get(key) == value
def test_params_get_type(self): def test_params_get_type(self):
# json # json
self.params.put("ApiCache_DriveStats", {"a": 0}) self.params.put("ApiCache_DriveStats", {"a": 0})
+4 -3
View File
@@ -4,7 +4,7 @@
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. 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.
# 452 Supported Cars # 453 Supported Cars
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br>&nbsp;|Video|Setup Video| |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br>&nbsp;|Video|Setup Video|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@@ -76,7 +76,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Chevrolet|Volt 2019|Adaptive Cruise Control (ACC) & LKAS|Stock|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM SDGM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Volt 2019">Buy Here</a></sub></details>||| |Chevrolet|Volt 2019|Adaptive Cruise Control (ACC) & LKAS|Stock|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM SDGM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Volt 2019">Buy Here</a></sub></details>|||
|Chevrolet|Volt ASCM Harness 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Volt ASCM Harness 2017-18">Buy Here</a></sub></details>|<a href="https://youtu.be/QeMCN_4TFfQ" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|| |Chevrolet|Volt ASCM Harness 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Volt ASCM Harness 2017-18">Buy Here</a></sub></details>|<a href="https://youtu.be/QeMCN_4TFfQ" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Chevrolet|Volt Camera Harness 2017-18|Flashed camera-forward integration with ACC|openpilot available[<sup>1</sup>](#footnotes)|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Volt Camera Harness 2017-18">Buy Here</a></sub></details>||| |Chevrolet|Volt Camera Harness 2017-18|Flashed camera-forward integration with ACC|openpilot available[<sup>1</sup>](#footnotes)|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Volt Camera Harness 2017-18">Buy Here</a></sub></details>|||
|Chevrolet|Volt No-ACC 2017-18|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Volt No-ACC 2017-18">Buy Here</a></sub></details>||| |Chevrolet|Volt No-ACC 2016-18 (OBD Harness)|Redneck ACC|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-II connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Volt No-ACC 2016-18 (OBD Harness)">Buy Here</a></sub></details>|||
|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica 2017-18">Buy Here</a></sub></details>||| |Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica 2017-18">Buy Here</a></sub></details>|||
|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica 2019-20">Buy Here</a></sub></details>||| |Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica 2019-20">Buy Here</a></sub></details>|||
|Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica 2021-23">Buy Here</a></sub></details>||| |Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica 2021-23">Buy Here</a></sub></details>|||
@@ -465,7 +465,7 @@ A supported vehicle is one that just works when you install a comma device. All
These additional vehicle ports are maintained by StarPilot rather than upstream openpilot. These additional vehicle ports are maintained by StarPilot rather than upstream openpilot.
# 16 Community Cars # 17 Community Cars
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br>&nbsp;|Video|Setup Video| |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br>&nbsp;|Video|Setup Video|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@@ -478,6 +478,7 @@ These additional vehicle ports are maintained by StarPilot rather than upstream
|Kia|Ceed Plug-in Hybrid Non-SCC 2022|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai I connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Ceed Plug-in Hybrid Non-SCC 2022">Buy Here</a></sub></details>||| |Kia|Ceed Plug-in Hybrid Non-SCC 2022|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai I connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Ceed Plug-in Hybrid Non-SCC 2022">Buy Here</a></sub></details>|||
|Kia|Forte Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Forte Non-SCC 2019">Buy Here</a></sub></details>||| |Kia|Forte Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Forte Non-SCC 2019">Buy Here</a></sub></details>|||
|Kia|Forte Non-SCC 2021|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Forte Non-SCC 2021">Buy Here</a></sub></details>||| |Kia|Forte Non-SCC 2021|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Forte Non-SCC 2021">Buy Here</a></sub></details>|||
|Kia|Ray EV 2025|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Ray EV 2025">Buy Here</a></sub></details>|||
|Kia|Seltos Non-SCC 2023-24|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Seltos Non-SCC 2023-24">Buy Here</a></sub></details>||| |Kia|Seltos Non-SCC 2023-24|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Seltos Non-SCC 2023-24">Buy Here</a></sub></details>|||
|Tesla|Model S (Pre-AP) 2012-14|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None||| |Tesla|Model S (Pre-AP) 2012-14|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None|||
|Toyota|Matrix Retrofit 2005|Custom retrofit|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Matrix Retrofit 2005">Buy Here</a></sub></details>||| |Toyota|Matrix Retrofit 2005|Custom retrofit|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Toyota A connector<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Matrix Retrofit 2005">Buy Here</a></sub></details>|||
+125 -161
View File
@@ -1,146 +1,128 @@
# StarPilot Unified Model Rebuild # StarPilot Model Rebuild
This workflow rebuilds StarPilot driving and driver-monitoring artifacts for the vendored tinygrad revision. Driving-model behavior versions remain manifest metadata; every runtime driving artifact uses the `tinygrad_single_v1` layout. This is the supported workflow for changing the vendored tinygrad revision and
releasing a new model manifest generation. A manifest generation represents one
tinygrad ABI. Model behavior versions (`v8` through `v16`) are independent and
must remain unchanged when only tinygrad changes.
## Safety The current generation is **v25**, pinned to tinygrad
`e837e367aac9e1a66e689f4f32ce20ca9367df13`. The supported compiler is
`comma@192.168.3.110`; never substitute another comma without explicit approval.
- The supported build device is `comma@192.168.3.109`. ## Release Contract
- Never run these commands against `192.168.3.110`.
- Normal artifacts target QCOM. External-GPU artifacts must be compiled explicitly and tagged in the manifest.
- Keep source ONNX files and compiled PKLs on the T5 workspace, not the comma.
## Workspace - Keep every existing StarPilot model ID stable across manifest generations.
- Store HF artifacts under `models/v25/<model-id>/`.
- Store GitHub fallback artifacts on the `Models` branch under `v25/<model-id>/`.
- Name every logical artifact `<model-id>_driving_tinygrad.pkl`.
- Publish native chunks as `.chunkNNofNN` plus `.chunkmanifest`.
- Serialize every v25 driving artifact out-of-band; this applies to normal QCOM
models as well as external-GPU models.
- Include `artifact_sha256` and `artifact_chunk_count` in the manifest.
- Set `uses_external_gpu: true` only for models compiled for Chestnut.
- Do not rename an artifact from another tinygrad revision. PKLs must either use
the exact v25 pin or be rebuilt with it.
- Do not add models absent from the existing StarPilot catalog unless the
release explicitly requests them.
The default workspace is: The downloader checks Hugging Face first and GitHub second. There is no GitLab
fallback. The HF manifest lives only at `manifests/model_names_v25.json`, old
artifacts live under `models/v24/`, and current artifacts live under
`models/v25/`. The v25 downloader never probes unversioned or v24 artifact
paths; missing v25 artifacts fail safely instead of loading an incompatible
pickle.
## Tinygrad Bump
1. Record the exact tinygrad commit used by the compatible source catalog.
2. Replace `tinygrad_repo/` from that commit, excluding nested Git metadata.
3. Write the full SHA to `tinygrad_repo/TINYGRAD_COMMIT`.
4. Review upstream `modeld`, compiler, parser, and camera-warp changes. Merge
required ABI changes into StarPilot's existing multi-model runtime; never
replace StarPilot `modeld.py` wholesale.
5. Increment `MANIFEST_CANDIDATES` to a new single version. Do not fall back to
the prior manifest because its PKLs target a different tinygrad ABI.
6. Sync the exact tree to the compiler before building anything:
```bash
./dev sync
rsync -az --delete --exclude=.git --exclude=__pycache__ -e ssh \
tinygrad_repo/ comma@192.168.3.110:/data/openpilot/tinygrad_repo/
rsync -az -e ssh selfdrive/modeld/ \
comma@192.168.3.110:/data/openpilot/selfdrive/modeld/
rsync -az -e ssh scripts/model_compiler.py \
comma@192.168.3.110:/data/openpilot/scripts/model_compiler.py
rsync -az -e ssh models comma@192.168.3.110:/data/openpilot/models
```
Confirm the device marker before compiling:
```bash
ssh comma@192.168.3.110 \
'cat /data/openpilot/tinygrad_repo/TINYGRAD_COMMIT'
```
## Reuse Compatible Artifacts
Reusing an artifact is preferred when its catalog records the exact same
tinygrad SHA and exact same source-model commit. Display names and release dates
are not sufficient proof. Copy compatible chunks server-side so the Mac never
stores a second multi-gigabyte artifact, but rename every destination chunk to
the stable StarPilot model ID.
Example:
```bash
hf buckets cp \
'hf://datasets/<source>/<path>/<source-file>.chunk01of02' \
'hf://buckets/StarPilot-Driving/StarPilot-Resources/models/v25/pop223/pop223_driving_tinygrad.pkl.chunk01of02'
```
Write `2` to `pop223_driving_tinygrad.pkl.chunkmanifest`, upload it last, and
put the source artifact's full SHA-256 and chunk count into the v25 manifest.
Upload the manifest only after every listed artifact directory is complete.
## Compile Missing Models
Archived sources live under:
```text ```text
/Volumes/T5/StarPilot-Model-Rebuild-2026-06-22/ hf://buckets/StarPilot-Driving/StarPilot-Resources/onnx/<source-id>/
``` ```
Important directories: Stage one model at a time in `/data/openpilot/uncompiledmodels`; this avoids
filling the comma and prevents `./models` from selecting stale input files.
- `onnx/<model-id>/`: ID-prefixed source ONNX files.
- `compiled/`: completed unified driving PKLs.
- `driver-monitoring/`: DM ONNX, model PKL, metadata, and camera warps.
- `ready-for-resources/`: flat repository-upload handoff.
- Oversized models are represented by repository-safe `.p00`, `.p01`, and `.sha256` files in `ready-for-resources/`.
- `logs/`: one remote compilation log per model.
- `results/`: source and artifact checksum records.
- `manifests/`: source `model_names_v22.json` and namespaced release `model_names_v23.json`.
- The v23 manifest and compiled artifacts are published together in the resource repository's `Models` branch.
## Initialize And Extract
```bash ```bash
python3 scripts/model_rebuild_pipeline.py init ./models --<model-id> --version <behavior-version>
python3 scripts/model_rebuild_pipeline.py extract \ ./models --<gpu-model-id> --version v16 --gpu
--base-manifest /path/to/model_names_v21.json
``` ```
Extraction streams Git blobs directly to disk. LFS pointers are resolved from the local object cache or fetched by object ID, then checked against the pointer SHA-256 and size. Binary ONNX data is never stored in a shell variable. The default input is a single supercombo ONNX. For legacy sources use:
To retry one source:
```bash ```bash
python3 scripts/model_rebuild_pipeline.py extract \ ./models --<model-id> --input-format split --version <behavior-version>
--model pop22 \
--base-manifest /path/to/model_names_v21.json
``` ```
The original catalog sources are defined in `scripts/model_source_map_v22.json`. Every non-local release build emits an OOB artifact as native chunks and removes
Recovered late-model and supercombo sources, including RDF2, are defined in the temporary full PKL. `./models --local-<id>` intentionally keeps one OOB PKL
`scripts/model_source_map_v23.json`. The v23 map is intentionally separate so for local use.
adding a recovered iteration cannot alter the older model source history.
## Compile The resumable bulk helper is:
Compile one model:
```bash ```bash
STAR_PILOT_MODEL_REMOTE=comma@192.168.3.110 \
python3 scripts/model_rebuild_pipeline.py compile \ python3 scripts/model_rebuild_pipeline.py compile \
--model pop22 \ --workspace /Volumes/T5/StarPilot-Model-Rebuild \
--base-manifest /path/to/model_names_v21.json --source-map scripts/model_source_map_v25.json \
--base-manifest ~/StarPilot-Resources/model_names_v25.json
``` ```
Compile or resume the full catalog: Failures are recorded under `results/`; rerun the same command to resume.
```bash ## Driver Monitoring And Default
python3 scripts/model_rebuild_pipeline.py compile \
--base-manifest /path/to/model_names_v21.json
```
Existing artifacts are skipped unless `--force` is passed. Each model is staged in its own remote input directory, compiled on `.109`, copied back to the T5, hashed, and copied into `ready-for-resources/`. Failures are written to `results/<id>_failure.json`; rerunning the same command resumes incomplete models. Driver monitoring is built once per tinygrad generation:
Validate one or all completed artifacts with synthetic camera inputs on QCOM:
```bash
python3 scripts/model_rebuild_pipeline.py validate \
--model pop22 \
--base-manifest /path/to/model_names_v21.json
```
The lower-level device compiler also supports direct use:
```bash
./models --model pop22 --input-format split --version v11
./models --model deeprl3v2 --input-format supercombo --version v15
```
For a model that cannot run on the device GPU, compile with the USB AMD GPU attached:
```bash
./models --lebowski --gpu
```
The ASM2464PD bridge must run the current tinygrad custom firmware from
https://github.com/tinygrad/asm2464pd-firmware. Its USB product string starts
with `custom`; the legacy `USB 3.2 PCIe TinyEnclosure` patch is not compatible
with comma's current external-GPU runtime. Firmware flashing is a separate,
explicit hardware setup step and StarPilot never performs it automatically.
The dynamic flag (`--lebowski` above) sets the output and manifest model ID;
when only one source model is staged, its ONNX filename does not need to match
that ID. Input format and behavior version are inferred. `--external-gpu`
remains available as a compatibility alias for `--gpu`.
This emits a streaming out-of-band pickle and keeps QCOM available for camera warps. Its manifest entry must include:
```json
{
"id": "lebowski",
"uses_external_gpu": true
}
```
Only tagged models activate the external GPU. If the GPU or artifact is unavailable, runtime falls back to the built-in model; all untagged models retain the existing QCOM path.
`--version` records behavioral semantics only. It does not change artifact layout.
If the compiled PKL exceeds 100 MiB, `./models` automatically keeps the full
local PKL and creates 95 MiB upload parts beside it:
```text
deeprl3v2_driving_tinygrad.pkl
deeprl3v2_driving_tinygrad.pkl.p00
deeprl3v2_driving_tinygrad.pkl.p01
deeprl3v2_driving_tinygrad.pkl.sha256
```
To split an already compiled artifact:
```bash
./models --split-artifact /path/to/deeprl3v2_driving_tinygrad.pkl \
--output-dir /path/to/upload-ready
```
Upload only the numbered parts and checksum when the full PKL exceeds the
repository limit. The downloader reassembles into a temporary file, verifies
the companion SHA-256, and atomically installs the final PKL. No manifest field
is required for multipart artifacts.
## Driver Monitoring
Stage the current DM ONNX in `uncompiledmodels`, then run:
```bash ```bash
./models --dm \ ./models --dm \
@@ -148,61 +130,43 @@ Stage the current DM ONNX in `uncompiledmodels`, then run:
--output-dir /tmp/dm_artifacts --output-dir /tmp/dm_artifacts
``` ```
This builds: Replace these four files together:
- `dmonitoring_model_tinygrad.pkl` - `dmonitoring_model_tinygrad.pkl`
- `dmonitoring_model_metadata.pkl` - `dmonitoring_model_metadata.pkl`
- `dm_warp_1928x1208_tinygrad.pkl` - `dm_warp_1928x1208_tinygrad.pkl`
- `dm_warp_1344x760_tinygrad.pkl` - `dm_warp_1344x760_tinygrad.pkl`
All four files must be updated together. Recompile RDF V4 with the same pin and replace the built-in
`selfdrive/modeld/models/driving_tinygrad.pkl` native chunk set. Never commit a
full built-in PKL over the repository limit.
## Manifest ## Validation
Generate the base manifest after compilation, then namespace the release artifacts as v23: Run repository tests first:
```bash ```bash
python3 scripts/model_rebuild_pipeline.py manifest \ ./dev sync
--base-manifest /path/to/model_names_v21.json ./.venv/bin/pytest -q -n0 \
starpilot/assets/tests/test_model_pipeline.py \
common/tests/test_file_chunker.py \
scripts/tests/test_model_release.py
``` ```
```bash For representative v8, v11, v12, v15, v16, and GPU artifacts, validate both
python3 scripts/namespace_model_artifacts.py \ camera resolutions on real QCOM and require finite plan, lane-line, road-edge,
--workspace /Volumes/T5/StarPilot-Model-Rebuild-2026-06-22 \ lead, pose, and action outputs. Then start `modeld` and confirm stable
--base-manifest /Volumes/T5/StarPilot-Model-Rebuild-2026-06-22/manifests/model_names_v22.json \ `modelV2` publication. Validate DM `driverStateV2` at both resolutions.
--manifest-version v23 --suffix 3
```
The namespace command changes IDs such as `tr1422` to `tr14223`, renames the ## Device Migration
compiled and upload-ready files, and writes an ID map. It preserves display
names and behavioral versions. The current model manager requests v23 only;
the manifest is fetched from `Models/model_names_v23.json`, while v22 remains
available for devices that have not updated yet.
After importing newly compiled sources, normalize the release namespace before When `ModelManifestVersion` changes, the model manager retains the selected
copying files into either resource repository: model ID but deletes every non-local downloaded driving artifact from the old
generation, including full PKLs, `.pNN` parts, native chunks, and chunk
manifests. It then downloads that ID's v25 chunks. Local models and DM files are
not deleted. If the selected v25 artifact cannot be downloaded and verified,
the manager selects the built-in RDF V4 model.
```bash Test this explicitly before release by starting with a v24 selected model and
python3 scripts/reconcile_v23_artifacts.py \ checking that no v24 driving artifact remains under `/data/models` after the
--workspace /Volumes/T5/StarPilot-Model-Rebuild-2026-06-22 v25 manifest is applied.
```
This maps recovered source IDs to their v23 release IDs, removes duplicate
macOS metadata files, and adds `rdf23` for Regret Driven Framework V2. It does
not overwrite a conflicting artifact.
Repository-hosted multipart files are discovered by naming convention, so no
size, hash, format, or part-count metadata is required.
`uses_external_gpu` is optional and defaults to `false`.
## Runtime Verification
Compilation validates JIT capture/replay, pickle round-trip, finite outputs, metadata slices, and both camera warps. Before release:
1. Select representative v8, v11, v12, v15, and supercombo models.
2. Confirm `modeld` stays running.
3. Confirm finite `modelV2` path, lane-line, lead, pose, and action data.
4. Confirm `driverStateV2` on both supported camera resolutions.
5. Test download, selection, deletion, randomization, migration, and fallback in both device UIs and Galaxy.
The built-in RDF artifact is `selfdrive/modeld/models/driving_tinygrad.pkl`. If migration cannot download the selected v23 artifact, StarPilot switches to that built-in model.
+1 -1
View File
@@ -21,7 +21,7 @@ fi
export QCOM_PRIORITY=12 export QCOM_PRIORITY=12
if [ -z "$AGNOS_VERSION" ]; then if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="19.6.10" export AGNOS_VERSION="19.6.20"
fi fi
if [ -z "$AGNOS_ACCEPTED_VERSIONS" ]; then if [ -z "$AGNOS_ACCEPTED_VERSIONS" ]; then
Executable
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
exec python3 "$DIR/scripts/model_release.py" "$@"
+1 -1
View File
@@ -85,7 +85,7 @@
|Chevrolet|Volt 2019|Adaptive Cruise Control (ACC) & LKAS|[Upstream](#upstream)| |Chevrolet|Volt 2019|Adaptive Cruise Control (ACC) & LKAS|[Upstream](#upstream)|
|Chevrolet|Volt ASCM Harness 2017-18|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| |Chevrolet|Volt ASCM Harness 2017-18|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|Chevrolet|Volt Camera Harness 2017-18|Flashed camera-forward integration with ACC|[Upstream](#upstream)| |Chevrolet|Volt Camera Harness 2017-18|Flashed camera-forward integration with ACC|[Upstream](#upstream)|
|Chevrolet|Volt No-ACC 2017-18|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| |Chevrolet|Volt No-ACC 2016-18 (OBD Harness)|Redneck ACC|[Upstream](#upstream)|
|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| |Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| |Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|Chrysler|Pacifica 2021-23|All|[Upstream](#upstream)| |Chrysler|Pacifica 2021-23|All|[Upstream](#upstream)|
+1 -1
View File
@@ -194,7 +194,7 @@ def get_checksum_state(dbc_name: str) -> ChecksumState | None:
return ChecksumState(4, 2, 3, 5, False, SignalType.HONDA_CHECKSUM, honda_checksum) return ChecksumState(4, 2, 3, 5, False, SignalType.HONDA_CHECKSUM, honda_checksum)
elif dbc_name.startswith(("toyota_", "lexus_")): elif dbc_name.startswith(("toyota_", "lexus_")):
return ChecksumState(8, -1, 7, -1, False, SignalType.TOYOTA_CHECKSUM, toyota_checksum) return ChecksumState(8, -1, 7, -1, False, SignalType.TOYOTA_CHECKSUM, toyota_checksum)
elif dbc_name.startswith("hyundai_canfd_generated"): elif dbc_name.startswith(("hyundai_canfd_generated", "hyundai_radar_210_21f_generated")):
return ChecksumState(16, -1, 0, -1, True, SignalType.HKG_CAN_FD_CHECKSUM, hkg_can_fd_checksum) return ChecksumState(16, -1, 0, -1, True, SignalType.HKG_CAN_FD_CHECKSUM, hkg_can_fd_checksum)
elif dbc_name.startswith(("vw_mqb", "vw_mqbevo", "vw_meb")): elif dbc_name.startswith(("vw_mqb", "vw_mqbevo", "vw_meb")):
return ChecksumState(8, 4, 0, 0, True, SignalType.VOLKSWAGEN_MQB_MEB_CHECKSUM, volkswagen_mqb_meb_checksum) return ChecksumState(8, 4, 0, 0, True, SignalType.VOLKSWAGEN_MQB_MEB_CHECKSUM, volkswagen_mqb_meb_checksum)
+5
View File
@@ -132,6 +132,7 @@ class CANParser:
self.dbc: DBC = DBC(dbc_name) self.dbc: DBC = DBC(dbc_name)
self.vl: dict[int | str, dict[str, float]] = VLDict(self) self.vl: dict[int | str, dict[str, float]] = VLDict(self)
self.vl_raw: dict[int | str, bytes] = {}
self.vl_all: dict[int | str, dict[str, list[float]]] = {} self.vl_all: dict[int | str, dict[str, list[float]]] = {}
self.ts_nanos: dict[int | str, dict[str, int]] = {} self.ts_nanos: dict[int | str, dict[str, int]] = {}
self.addresses: set[int] = set() self.addresses: set[int] = set()
@@ -166,6 +167,8 @@ class CANParser:
signals_dict = {s: 0.0 for s in signal_names} signals_dict = {s: 0.0 for s in signal_names}
dict.__setitem__(self.vl, msg.address, signals_dict) dict.__setitem__(self.vl, msg.address, signals_dict)
dict.__setitem__(self.vl, msg.name, signals_dict) dict.__setitem__(self.vl, msg.name, signals_dict)
self.vl_raw[msg.address] = bytes(msg.size)
self.vl_raw[msg.name] = bytes(msg.size)
self.vl_all[msg.address] = defaultdict(list) self.vl_all[msg.address] = defaultdict(list)
self.vl_all[msg.name] = self.vl_all[msg.address] self.vl_all[msg.name] = self.vl_all[msg.address]
self.ts_nanos[msg.address] = {s: 0 for s in signal_names} self.ts_nanos[msg.address] = {s: 0 for s in signal_names}
@@ -247,6 +250,8 @@ class CANParser:
vl_addr[sig.name] = state.vals[i] vl_addr[sig.name] = state.vals[i]
vl_all_addr[sig.name] = state.all_vals[i] vl_all_addr[sig.name] = state.all_vals[i]
ts_addr[sig.name] = state.timestamps[-1] ts_addr[sig.name] = state.timestamps[-1]
self.vl_raw[address] = bytes(dat)
self.vl_raw[state.name] = bytes(dat)
if not bus_empty: if not bus_empty:
self.last_nonempty_nanos = t self.last_nonempty_nanos = t
+2
View File
@@ -644,6 +644,8 @@ struct CarParams {
fcaGiorgio @32; fcaGiorgio @32;
rivian @33; rivian @33;
volkswagenMeb @34; volkswagenMeb @34;
teslaPreAP @35;
volvo @36;
} }
enum SteerControlType { enum SteerControlType {
+8 -2
View File
@@ -10,6 +10,7 @@ from opendbc.car.carlog import carlog
from opendbc.car.structs import CarParams, CarParamsT from opendbc.car.structs import CarParams, CarParamsT
from opendbc.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars from opendbc.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars
from opendbc.car.fw_versions import ObdCallback, get_fw_versions_ordered, get_present_ecus, match_fw_to_car from opendbc.car.fw_versions import ObdCallback, get_fw_versions_ordered, get_present_ecus, match_fw_to_car
from opendbc.car.hyundai.values import kia_ray_ev_vin
from opendbc.car.mock.values import CAR as MOCK from opendbc.car.mock.values import CAR as MOCK
from opendbc.car.toyota.values import ToyotaSafetyFlags from opendbc.car.toyota.values import ToyotaSafetyFlags
from opendbc.car.values import BRANDS from opendbc.car.values import BRANDS
@@ -246,8 +247,13 @@ def fingerprint(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_mu
set_obd_multiplexing(True) set_obd_multiplexing(True)
# VIN query only reliably works through OBDII # VIN query only reliably works through OBDII
vin_rx_addr, vin_rx_bus, vin = get_vin(can_recv, can_send, (0, 1)) vin_rx_addr, vin_rx_bus, vin = get_vin(can_recv, can_send, (0, 1))
ecu_rx_addrs = get_present_ecus(can_recv, can_send, set_obd_multiplexing, num_pandas=num_pandas) skip_fw_buses = {1} if kia_ray_ev_vin(vin) else set()
car_fw = get_fw_versions_ordered(can_recv, can_send, set_obd_multiplexing, vin, ecu_rx_addrs, num_pandas=num_pandas) if skip_fw_buses:
carlog.warning("Kia Ray EV: skipping CAN1 firmware queries")
ecu_rx_addrs = get_present_ecus(can_recv, can_send, set_obd_multiplexing,
num_pandas=num_pandas, skip_buses=skip_fw_buses)
car_fw = get_fw_versions_ordered(can_recv, can_send, set_obd_multiplexing, vin, ecu_rx_addrs,
num_pandas=num_pandas, skip_buses=skip_fw_buses)
cached = False cached = False
exact_fw_match, fw_candidates = match_fw_to_car(car_fw, vin) exact_fw_match, fw_candidates = match_fw_to_car(car_fw, vin)
+47 -73
View File
@@ -1,13 +1,15 @@
import math import math
import numpy as np import numpy as np
from opendbc.can import CANPacker from opendbc.can import CANPacker
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, DT_CTRL, apply_hysteresis, structs from opendbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, DT_CTRL, structs
from opendbc.car.lateral import ISO_LATERAL_ACCEL, apply_std_steer_angle_limits from opendbc.car.lateral import ISO_LATERAL_ACCEL, apply_std_steer_angle_limits
from opendbc.car.ford import fordcan from opendbc.car.ford import fordcan
from opendbc.car.ford.values import CarControllerParams, FordFlags, CAR from opendbc.car.ford.values import CarControllerParams, FordFlags
from opendbc.car.interfaces import CarControllerBase, V_CRUISE_MAX from opendbc.car.interfaces import CarControllerBase, V_CRUISE_MAX
# This Ford extension boundary substantially adapts BluePilot bp-7.0 work. See the root CREDITS.md
# (including Alan Polk's d0aac605f and db2bdff05) and THIRD_PARTY_NOTICES.md.
from openpilot.starpilot.car.ford import fordcan as starpilot_fordcan from openpilot.starpilot.car.ford import fordcan as starpilot_fordcan
from openpilot.starpilot.car.ford.lateral import FordLateralController, FordLateralMode, FordLateralResult from openpilot.starpilot.car.ford.lateral import FordLateralController, FordLateralResult
LongCtrlState = structs.CarControl.Actuators.LongControlState LongCtrlState = structs.CarControl.Actuators.LongControlState
VisualAlert = structs.CarControl.HUDControl.VisualAlert VisualAlert = structs.CarControl.HUDControl.VisualAlert
@@ -18,27 +20,31 @@ AVERAGE_ROAD_ROLL = 0.06 # ~3.4 degrees, 6% superelevation. higher actual roll
MAX_LATERAL_ACCEL = ISO_LATERAL_ACCEL - (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL) # ~2.4 m/s^2 MAX_LATERAL_ACCEL = ISO_LATERAL_ACCEL - (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL) # ~2.4 m/s^2
class FordStockCruiseButton:
"""Resolve Ford's context-sensitive cancel/resume switch for stock ACC."""
def __init__(self):
self.pressed = False
self.cancel = False
self.resume = False
def update(self, pressed: bool, cruise_available: bool, cruise_enabled: bool) -> tuple[bool, bool]:
if pressed and not self.pressed:
self.cancel = cruise_available and cruise_enabled
self.resume = cruise_available and not cruise_enabled
elif not pressed:
self.cancel = False
self.resume = False
self.pressed = pressed
return self.cancel, self.resume
def apply_ford_angle(desired_angle_deg: float, current_angle_deg: float) -> float: def apply_ford_angle(desired_angle_deg: float, current_angle_deg: float) -> float:
relative_angle = desired_angle_deg - current_angle_deg relative_angle = desired_angle_deg - current_angle_deg
return float(np.clip(relative_angle, -5.8, 5.8)) return float(np.clip(relative_angle, -5.8, 5.8))
def anti_overshoot(apply_curvature, apply_curvature_last, v_ego):
diff = 0.1
tau = 5 # 5s smooths over the overshoot
dt = DT_CTRL * CarControllerParams.STEER_STEP
alpha = 1 - np.exp(-dt / tau)
lataccel = apply_curvature * (v_ego ** 2)
last_lataccel = apply_curvature_last * (v_ego ** 2)
last_lataccel = apply_hysteresis(lataccel, last_lataccel, diff)
last_lataccel = alpha * lataccel + (1 - alpha) * last_lataccel
output_curvature = last_lataccel / (max(v_ego, 1) ** 2)
return float(np.interp(v_ego, [5, 10], [apply_curvature, output_curvature]))
def apply_ford_curvature_limits(apply_curvature, apply_curvature_last, current_curvature, v_ego_raw, steering_angle, lat_active, CP): def apply_ford_curvature_limits(apply_curvature, apply_curvature_last, current_curvature, v_ego_raw, steering_angle, lat_active, CP):
# No blending at low speed due to lack of torque wind-up and inaccurate current curvature # No blending at low speed due to lack of torque wind-up and inaccurate current curvature
if v_ego_raw > 9: if v_ego_raw > 9:
@@ -73,7 +79,6 @@ class CarController(CarControllerBase):
self.apply_curvature_last = 0 self.apply_curvature_last = 0
self.apply_angle_last = 0 self.apply_angle_last = 0
self.anti_overshoot_curvature_last = 0
self.accel = 0.0 self.accel = 0.0
self.gas = 0.0 self.gas = 0.0
self.brake_request = False self.brake_request = False
@@ -83,8 +88,8 @@ class CarController(CarControllerBase):
self.lead_distance_bars_last = None self.lead_distance_bars_last = None
self.distance_bar_frame = 0 self.distance_bar_frame = 0
self.ford_lateral = None if CP.flags & FordFlags.LKA_STEERING else FordLateralController(CP) self.ford_lateral = None if CP.flags & FordFlags.LKA_STEERING else FordLateralController(CP)
self.ford_shadow_curvature = 0.0 self.ford_extended_lateral_announced = False
self.ford_lateral_announced_mode = FordLateralMode.native self.stock_cruise_button = FordStockCruiseButton()
def update(self, CC, CS, now_nanos, starpilot_toggles): def update(self, CC, CS, now_nanos, starpilot_toggles):
can_sends = [] can_sends = []
@@ -100,9 +105,23 @@ class CarController(CarControllerBase):
self.ford_lateral.update_inputs() self.ford_lateral.update_inputs()
### acc buttons ### ### acc buttons ###
stock_cancel = False
stock_resume = False
if not self.CP.openpilotLongitudinalControl:
stock_cancel, stock_resume = self.stock_cruise_button.update(
bool(CS.buttons_stock_values["CcAslButtnCnclResPress"]),
CS.out.cruiseState.available,
CS.out.cruiseState.enabled,
)
if CC.cruiseControl.cancel: if CC.cruiseControl.cancel:
can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.camera, CS.buttons_stock_values, cancel=True)) can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.camera, CS.buttons_stock_values, cancel=True))
can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.main, CS.buttons_stock_values, cancel=True)) can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.main, CS.buttons_stock_values, cancel=True))
elif (stock_cancel or stock_resume) and (self.frame % CarControllerParams.BUTTONS_STEP) == 0:
can_sends.append(fordcan.create_button_msg(
self.packer, self.CAN.camera, CS.buttons_stock_values, cancel=stock_cancel, resume=stock_resume))
can_sends.append(fordcan.create_button_msg(
self.packer, self.CAN.main, CS.buttons_stock_values, cancel=stock_cancel, resume=stock_resume))
elif CC.cruiseControl.resume and (self.frame % CarControllerParams.BUTTONS_STEP) == 0: elif CC.cruiseControl.resume and (self.frame % CarControllerParams.BUTTONS_STEP) == 0:
can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.camera, CS.buttons_stock_values, resume=True)) can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.camera, CS.buttons_stock_values, resume=True))
can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.main, CS.buttons_stock_values, resume=True)) can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.main, CS.buttons_stock_values, resume=True))
@@ -136,70 +155,26 @@ class CarController(CarControllerBase):
can_sends.append(fordcan.create_lka_msg(self.packer, self.CAN, active=lka_active, apply_angle=self.apply_angle_last, can_sends.append(fordcan.create_lka_msg(self.packer, self.CAN, active=lka_active, apply_angle=self.apply_angle_last,
direction=direction, ramp_type=ramp_type, curvature=-self.apply_curvature_last)) direction=direction, ramp_type=ramp_type, curvature=-self.apply_curvature_last))
else: else:
lateral_mode = self.ford_lateral.mode if (self.frame % CarControllerParams.STEER_STEP) == 0:
lateral_mode_ready = lateral_mode == self.ford_lateral_announced_mode lateral = self.ford_lateral.update(CC, CS, actuators) \
if self.ford_extended_lateral_announced else FordLateralResult()
# Keep the original Ford path available without changing its command behavior.
if lateral_mode == FordLateralMode.native:
if (self.frame % CarControllerParams.STEER_STEP) == 0:
if not lateral_mode_ready:
self.apply_curvature_last = 0.0
apply_curvature = 0.0
elif self.CP.carFingerprint in (CAR.FORD_BRONCO_SPORT_MK1, CAR.FORD_F_150_MK14):
self.anti_overshoot_curvature_last = anti_overshoot(
actuators.curvature, self.anti_overshoot_curvature_last, CS.out.vEgoRaw)
apply_curvature = self.anti_overshoot_curvature_last
else:
apply_curvature = actuators.curvature
current_curvature = -CS.out.yawRate / max(CS.out.vEgoRaw, 0.1)
self.apply_curvature_last = apply_ford_curvature_limits(
apply_curvature, self.apply_curvature_last, current_curvature,
CS.out.vEgoRaw, 0., CC.latActive and lateral_mode_ready, self.CP)
if self.CP.flags & FordFlags.CANFD:
mode = 1 if CC.latActive and lateral_mode_ready else 0
counter = (self.frame // CarControllerParams.STEER_STEP) % 0x10
can_sends.append(fordcan.create_lat_ctl2_msg(
self.packer, self.CAN, mode, 0., 0., -self.apply_curvature_last, 0., counter))
else:
can_sends.append(fordcan.create_lat_ctl_msg(
self.packer, self.CAN, CC.latActive and lateral_mode_ready, 0., 0., -self.apply_curvature_last, 0.))
elif (self.frame % CarControllerParams.STEER_STEP) == 0:
if not lateral_mode_ready:
lateral = FordLateralResult(shadow_curvature=self.ford_lateral._current_curvature(CS))
elif lateral_mode == FordLateralMode.angle:
lateral = self.ford_lateral.update_angle(CC, CS, actuators)
else:
lateral = self.ford_lateral.update_curvature(CC, CS, actuators)
self.apply_curvature_last = lateral.curvature self.apply_curvature_last = lateral.curvature
self.ford_shadow_curvature = lateral.shadow_curvature
if self.CP.flags & FordFlags.CANFD: if self.CP.flags & FordFlags.CANFD:
counter = (self.frame // CarControllerParams.STEER_STEP) % 0x10 counter = (self.frame // CarControllerParams.STEER_STEP) % 0x10
can_sends.append(starpilot_fordcan.create_lat_ctl2_msg( can_sends.append(starpilot_fordcan.create_lat_ctl2_msg(
self.packer, self.CAN, 1 if lateral.active else 0, self.packer, self.CAN, 1 if lateral.active else 0,
lateral.ramp_type, lateral.precision_type, lateral.ramp_type, lateral.precision_type,
-lateral.path_offset, -lateral.path_angle,
-lateral.curvature, -lateral.curvature_rate, counter)) -lateral.curvature, -lateral.curvature_rate, counter))
else: else:
can_sends.append(starpilot_fordcan.create_lat_ctl_msg( can_sends.append(starpilot_fordcan.create_lat_ctl_msg(
self.packer, self.CAN, lateral.active, self.packer, self.CAN, lateral.active,
lateral.ramp_type, lateral.precision_type, lateral.ramp_type, lateral.precision_type,
-lateral.path_offset, -lateral.path_angle,
-lateral.curvature, -lateral.curvature_rate)) -lateral.curvature, -lateral.curvature_rate))
if (self.frame % CarControllerParams.LKA_STEP) == 0: if (self.frame % CarControllerParams.LKA_STEP) == 0:
if lateral_mode == FordLateralMode.native: can_sends.append(starpilot_fordcan.create_lka_msg(self.packer, self.CAN))
can_sends.append(fordcan.create_lka_msg(self.packer, self.CAN)) self.ford_extended_lateral_announced = True
else:
angle_mode = lateral_mode == FordLateralMode.angle
shadow_curvature = -self.ford_lateral._current_curvature(CS)
if angle_mode:
shadow_curvature = -self.ford_shadow_curvature
can_sends.append(starpilot_fordcan.create_lka_msg(
self.packer, self.CAN, angle_mode=angle_mode, shadow_curvature=shadow_curvature))
self.ford_lateral_announced_mode = lateral_mode
### longitudinal control ### ### longitudinal control ###
# send acc msg at 50Hz # send acc msg at 50Hz
@@ -257,8 +232,7 @@ class CarController(CarControllerBase):
show_distance_bars = self.frame - self.distance_bar_frame < 400 show_distance_bars = self.frame - self.distance_bar_frame < 400
hands_free_cluster = bool( hands_free_cluster = bool(
self.ford_lateral is not None self.ford_lateral is not None
and self.ford_lateral.mode != FordLateralMode.native and self.ford_extended_lateral_announced
and self.ford_lateral.mode == self.ford_lateral_announced_mode
and self.ford_lateral.hands_free_cluster_enabled) and self.ford_lateral.hands_free_cluster_enabled)
can_sends.append(fordcan.create_acc_ui_msg(self.packer, self.CAN, self.CP, main_on, CC.latActive, can_sends.append(fordcan.create_acc_ui_msg(self.packer, self.CAN, self.CP, main_on, CC.latActive,
fcw_alert, CS.out.cruiseState.standstill, show_distance_bars, fcw_alert, CS.out.cruiseState.standstill, show_distance_bars,
+77 -2
View File
@@ -1,3 +1,6 @@
# Ford-specific additions first imported in StarPilot 3f6ccd104e substantially adapt BluePilot
# bp-7.0 vehicle-state work, including a sunnypilot extension with the Haibin Wen/contributors
# notice retained in THIRD_PARTY_NOTICES.md. See the repository root CREDITS.md for provenance.
from cereal import custom from cereal import custom
from opendbc.can import CANDefine, CANParser from opendbc.can import CANDefine, CANParser
from opendbc.car import Bus, create_button_events, structs from opendbc.car import Bus, create_button_events, structs
@@ -209,7 +212,79 @@ class CarState(CarStateBase):
def get_can_parsers(CP): def get_can_parsers(CP):
gps_config = get_car_gps_config(CP) gps_config = get_car_gps_config(CP)
gps_messages = [(name, 0) for name in gps_config.messages] if gps_config is not None else [] gps_messages = [(name, 0) for name in gps_config.messages] if gps_config is not None else []
pt_messages = [
("BrakeSysFeatures", 50),
("Yaw_Data_FD1", 100),
("DesiredTorqBrk", 50),
("EngVehicleSpThrottle", 100),
("EngVehicleSpThrottle2", 50),
("BrakeSnData_4", 50),
("EngBrakeData", 10),
("EPAS_INFO", 50),
("Cluster_Info1_FD1", 10),
("Steering_Data_FD1", 10),
("BodyInfo_3_FD1", 2),
("RCMStatusMessage2_FD1", 10),
("BCM_Lamp_Stat_FD1", 0),
*gps_messages,
]
if CP.flags & FordFlags.ALT_STEER_ANGLE:
pt_messages += [
("SteeringPinion_Data_Alt", 100),
("ParkAid_Data", 50),
]
else:
pt_messages += [("SteeringPinion_Data", 100)]
if CP.flags & FordFlags.CANFD:
pt_messages += [
("Lane_Assist_Data3_FD1", 33),
("Cluster_Info_3_FD1", 10),
]
else:
pt_messages += [("INSTRUMENT_PANEL", 1)]
if CP.transmissionType == TransmissionType.automatic:
if CP.flags & FordFlags.CANFD:
pt_messages += [("Gear_Shift_by_Wire_FD1", 10)]
elif CP.flags & FordFlags.ALT_STEER_ANGLE:
pt_messages += [("TransGearData", 10)]
else:
pt_messages += [("PowertrainData_10", 10)]
if CP.enableBsm and not (CP.flags & FordFlags.CANFD):
pt_messages += [
("Side_Detect_L_Stat", 5),
("Side_Detect_R_Stat", 5),
]
cam_messages = [
("ACCDATA", 50),
("ACCDATA_2", 50),
("ACCDATA_3", 5),
("IPMA_Data", 1),
]
if CP.flags & FordFlags.CANFD:
cam_messages += [
("Traffic_RecognitnData", 1),
("IPMA_Data2", 1),
]
else:
cam_messages += [("Traffic_RecognitnData", 0)]
if CP.enableBsm and CP.flags & FordFlags.CANFD:
cam_messages += [
("Side_Detect_L_Stat", 5),
("Side_Detect_R_Stat", 5),
]
if CP.flags & FordFlags.LKA_STEERING:
cam_messages += [("LateralMotionControl", 20)]
return { return {
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], gps_messages, CanBus(CP).main), Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, CanBus(CP).main),
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus(CP).camera), Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], cam_messages, CanBus(CP).camera),
} }
@@ -1,4 +1,6 @@
""" AUTO-FORMATTED USING opendbc/car/debug/format_fingerprints.py, EDIT STRUCTURE THERE.""" """ AUTO-FORMATTED USING opendbc/car/debug/format_fingerprints.py, EDIT STRUCTURE THERE."""
# Some Ford firmware entries first imported in StarPilot 3f6ccd104e came from BluePilot bp-7.0
# contributors. The exact authors and source revisions are recorded in the root CREDITS.md.
from opendbc.car.structs import CarParams from opendbc.car.structs import CarParams
from opendbc.car.ford.values import CAR from opendbc.car.ford.values import CAR
@@ -1,3 +1,5 @@
# Ford-specific additions first imported in StarPilot 3f6ccd104e substantially adapt BluePilot
# bp-7.0 interface work. See the repository root CREDITS.md and THIRD_PARTY_NOTICES.md.
import numpy as np import numpy as np
from opendbc.car import Bus, get_safety_config, structs from opendbc.car import Bus, get_safety_config, structs
from opendbc.car.carlog import carlog from opendbc.car.carlog import carlog
@@ -1,3 +1,5 @@
# Ford-specific additions first imported in StarPilot 3f6ccd104e substantially adapt BluePilot
# bp-7.0 radar work. See the repository root CREDITS.md and THIRD_PARTY_NOTICES.md.
import numpy as np import numpy as np
from collections import deque from collections import deque
from typing import cast from typing import cast
@@ -4,10 +4,12 @@ from types import SimpleNamespace
from hypothesis import settings, given, strategies as st from hypothesis import settings, given, strategies as st
from parameterized import parameterized from parameterized import parameterized
import pytest
from opendbc.car import Bus, gen_empty_fingerprint from opendbc.car import Bus, gen_empty_fingerprint
from opendbc.can import CANPacker from opendbc.can import CANPacker
from opendbc.car.ford import fordcan from opendbc.car.ford import fordcan
from opendbc.car.ford.carcontroller import FordStockCruiseButton
from opendbc.car.gps import FORD_MACH_E_GPS_MESSAGES, get_car_gps_config, parse_ford_can_gps from opendbc.car.gps import FORD_MACH_E_GPS_MESSAGES, get_car_gps_config, parse_ford_can_gps
from opendbc.car.structs import CarParams from opendbc.car.structs import CarParams
from opendbc.car.fw_versions import build_fw_dict from opendbc.car.fw_versions import build_fw_dict
@@ -18,6 +20,24 @@ from opendbc.car.ford.fingerprints import FW_VERSIONS
Ecu = CarParams.Ecu Ecu = CarParams.Ecu
def test_stock_cruise_button_latches_context_until_release():
button = FordStockCruiseButton()
assert button.update(True, cruise_available=True, cruise_enabled=True) == (True, False)
assert button.update(True, cruise_available=True, cruise_enabled=False) == (True, False)
assert button.update(False, cruise_available=True, cruise_enabled=False) == (False, False)
assert button.update(True, cruise_available=True, cruise_enabled=False) == (False, True)
assert button.update(True, cruise_available=True, cruise_enabled=True) == (False, True)
assert button.update(False, cruise_available=True, cruise_enabled=True) == (False, False)
def test_stock_cruise_button_ignores_press_with_cruise_master_off():
button = FordStockCruiseButton()
assert button.update(True, cruise_available=False, cruise_enabled=False) == (False, False)
ECU_ADDRESSES = { ECU_ADDRESSES = {
Ecu.eps: 0x730, # Power Steering Control Module (PSCM) Ecu.eps: 0x730, # Power Steering Control Module (PSCM)
Ecu.abs: 0x760, # Anti-Lock Brake System (ABS) Ecu.abs: 0x760, # Anti-Lock Brake System (ABS)
@@ -274,6 +294,23 @@ def test_mach_e_can_gps_messages_are_optional_main_bus_inputs():
assert all(parser.message_states[address].ignore_alive for address in (0x462, 0x463, 0x464)) assert all(parser.message_states[address].ignore_alive for address in (0x462, 0x463, 0x464))
def test_lightning_low_rate_camera_messages_use_declared_frequencies():
cp = CarInterface.get_params(CAR.FORD_F_150_LIGHTNING_MK1, gen_empty_fingerprint(), [], True, False, False, None)
cp.enableBsm = True
parser = CarInterface.CarState.get_can_parsers(cp)[Bus.cam]
expected_frequencies = {
"IPMA_Data": 1,
"Traffic_RecognitnData": 1,
"Side_Detect_L_Stat": 5,
"Side_Detect_R_Stat": 5,
}
for message, frequency in expected_frequencies.items():
state = parser.message_states[parser.dbc.name_to_msg[message].address]
assert state.frequency == frequency
assert state.timeout_threshold == pytest.approx(10e9 / frequency)
def test_hands_free_cluster_status_is_opt_in(): def test_hands_free_cluster_status_is_opt_in():
packer = CANPacker("ford_lincoln_base_pt") packer = CANPacker("ford_lincoln_base_pt")
CAN = SimpleNamespace(main=0) CAN = SimpleNamespace(main=0)
+2
View File
@@ -1,3 +1,5 @@
# Ford platform data and limits first imported in StarPilot 3f6ccd104e substantially adapt
# BluePilot bp-7.0 contributor work. See the repository root CREDITS.md and THIRD_PARTY_NOTICES.md.
import copy import copy
import re import re
from dataclasses import dataclass, field, replace from dataclasses import dataclass, field, replace
+12 -6
View File
@@ -170,7 +170,9 @@ def match_fw_to_car(fw_versions: list[CarParams.CarFw], vin: str, allow_exact: b
return True, set() return True, set()
def get_present_ecus(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, num_pandas: int = 1) -> set[EcuAddrBusType]: def get_present_ecus(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback,
num_pandas: int = 1, skip_buses: set[int] | None = None) -> set[EcuAddrBusType]:
skip_buses = skip_buses or set()
# queries are split by OBD multiplexing mode # queries are split by OBD multiplexing mode
queries: dict[bool, list[list[EcuAddrBusType]]] = {True: [], False: []} queries: dict[bool, list[list[EcuAddrBusType]]] = {True: [], False: []}
parallel_queries: dict[bool, list[EcuAddrBusType]] = {True: [], False: []} parallel_queries: dict[bool, list[EcuAddrBusType]] = {True: [], False: []}
@@ -178,7 +180,7 @@ def get_present_ecus(can_recv: CanRecvCallable, can_send: CanSendCallable, set_o
for brand, config, r in REQUESTS: for brand, config, r in REQUESTS:
# Skip query if no panda available # Skip query if no panda available
if r.bus > num_pandas * 4 - 1: if r.bus > num_pandas * 4 - 1 or r.bus in skip_buses:
continue continue
for ecu_type, addr, sub_addr in config.get_all_ecus(VERSIONS[brand]): for ecu_type, addr, sub_addr in config.get_all_ecus(VERSIONS[brand]):
@@ -235,7 +237,8 @@ def get_brand_ecu_matches(ecu_rx_addrs: set[EcuAddrBusType]) -> dict[str, list[b
def get_fw_versions_ordered(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, vin: str, def get_fw_versions_ordered(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, vin: str,
ecu_rx_addrs: set[EcuAddrBusType], timeout: float = 0.1, num_pandas: int = 1, progress: bool = False) -> list[CarParams.CarFw]: ecu_rx_addrs: set[EcuAddrBusType], timeout: float = 0.1, num_pandas: int = 1,
progress: bool = False, skip_buses: set[int] | None = None) -> list[CarParams.CarFw]:
"""Queries for FW versions ordering brands by likelihood, breaks when exact match is found""" """Queries for FW versions ordering brands by likelihood, breaks when exact match is found"""
all_car_fw = [] all_car_fw = []
@@ -248,7 +251,8 @@ def get_fw_versions_ordered(can_recv: CanRecvCallable, can_send: CanSendCallable
if True not in brand_matches[brand]: if True not in brand_matches[brand]:
continue continue
car_fw = get_fw_versions(can_recv, can_send, set_obd_multiplexing, query_brand=brand, timeout=timeout, num_pandas=num_pandas, progress=progress) car_fw = get_fw_versions(can_recv, can_send, set_obd_multiplexing, query_brand=brand, timeout=timeout,
num_pandas=num_pandas, progress=progress, skip_buses=skip_buses)
all_car_fw.extend(car_fw) all_car_fw.extend(car_fw)
# If there is a match using this brand's FW alone, finish querying early # If there is a match using this brand's FW alone, finish querying early
@@ -260,7 +264,9 @@ def get_fw_versions_ordered(can_recv: CanRecvCallable, can_send: CanSendCallable
def get_fw_versions(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, query_brand: str = None, def get_fw_versions(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, query_brand: str = None,
extra: OfflineFwVersions = None, timeout: float = 0.1, num_pandas: int = 1, progress: bool = False) -> list[CarParams.CarFw]: extra: OfflineFwVersions = None, timeout: float = 0.1, num_pandas: int = 1, progress: bool = False,
skip_buses: set[int] | None = None) -> list[CarParams.CarFw]:
skip_buses = skip_buses or set()
versions = VERSIONS.copy() versions = VERSIONS.copy()
if query_brand is not None: if query_brand is not None:
@@ -298,7 +304,7 @@ def get_fw_versions(can_recv: CanRecvCallable, can_send: CanSendCallable, set_ob
for addr_chunk in chunks(addr_group): for addr_chunk in chunks(addr_group):
for brand, config, r in requests: for brand, config, r in requests:
# Skip query if no panda available # Skip query if no panda available
if r.bus > num_pandas * 4 - 1: if r.bus > num_pandas * 4 - 1 or r.bus in skip_buses:
continue continue
# Toggle OBD multiplexing for each request # Toggle OBD multiplexing for each request
+18 -7
View File
@@ -6,7 +6,7 @@ from opendbc.car.lateral import apply_driver_steer_torque_limits
from opendbc.car.gm import gmcan from opendbc.car.gm import gmcan
from opendbc.car.common.conversions import Conversions as CV from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.gm.values import ( from opendbc.car.gm.values import (
ASCM_INT, CAMERA_ACC_CAR, CAR, CC_ONLY_CAR, CC_REGEN_PADDLE_CAR, DBC, EV_CAR, SDGM_CAR, AccState, CanBus, CarControllerParams, ASCM_INT, CAMERA_ACC_CAR, CAR, CC_ONLY_CAR, CC_REGEN_PADDLE_CAR, DBC, EV_CAR, GM_AUTO_HOLD_CARS, SDGM_CAR, AccState, CanBus, CarControllerParams,
CruiseButtons, GMFlags, GMSafetyFlags, CruiseButtons, GMFlags, GMSafetyFlags,
) )
from opendbc.car.interfaces import CarControllerBase from opendbc.car.interfaces import CarControllerBase
@@ -172,7 +172,7 @@ def should_send_cc_button_spam(CP, CC, CS):
return ( return (
bool(CP.flags & GMFlags.CC_LONG.value) and bool(CP.flags & GMFlags.CC_LONG.value) and
CC.longActive and CC.longActive and
CS.out.vEgo > CP.minEnableSpeed CS.out.vEgo >= CP.minEnableSpeed
) )
@@ -256,6 +256,17 @@ def shape_truck_pitch_accel(pitch_accel: float, v_ego: float, enabled: bool) ->
return pitch_accel * scale return pitch_accel * scale
MAX_UPHILL_GRADE_FF = 0.20
def limit_grade_feedforward(planner_accel: float, pitch_accel: float) -> float:
if pitch_accel > 0.0 and planner_accel > 0.0:
return 0.0
if pitch_accel > MAX_UPHILL_GRADE_FF:
return MAX_UPHILL_GRADE_FF
return pitch_accel
def shape_truck_friction_brake(apply_brake: int, accel_cmd: float, stopping: bool, active: bool) -> tuple[int, bool]: def shape_truck_friction_brake(apply_brake: int, accel_cmd: float, stopping: bool, active: bool) -> tuple[int, bool]:
if apply_brake <= 0: if apply_brake <= 0:
return 0, False return 0, False
@@ -298,7 +309,7 @@ def supports_volt_auto_hold(CP, auto_hold_enabled: bool):
auto_hold_enabled and auto_hold_enabled and
getattr(CP, "openpilotLongitudinalControl", False) and getattr(CP, "openpilotLongitudinalControl", False) and
stock_hold_safety_ready and stock_hold_safety_ready and
CP.carFingerprint in AUTO_HOLD_VOLT_CARS CP.carFingerprint in GM_AUTO_HOLD_CARS
) )
@@ -1003,14 +1014,13 @@ class CarController(CarControllerBase):
self.truck_follow_accel = 0.0 self.truck_follow_accel = 0.0
else: else:
long_pitch_enabled = bool(getattr(starpilot_toggles, "long_pitch", True)) long_pitch_enabled = bool(getattr(starpilot_toggles, "long_pitch", True))
pedal_long_path = bool(self.CP.enableGasInterceptorDEPRECATED and (self.CP.flags & GMFlags.PEDAL_LONG.value))
long_pitch_for_powertrain = long_pitch_enabled or pedal_long_path
if self.is_volt: if self.is_volt:
if long_pitch_for_powertrain and len(CC.orientationNED) == 3 and CS.out.vEgo > self.CP.vEgoStopping: if long_pitch_enabled and len(CC.orientationNED) == 3 and CS.out.vEgo > self.CP.vEgoStopping:
volt_pitch_accel = math.sin(CC.orientationNED[1]) * ACCELERATION_DUE_TO_GRAVITY volt_pitch_accel = math.sin(CC.orientationNED[1]) * ACCELERATION_DUE_TO_GRAVITY
else: else:
volt_pitch_accel = 0.0 volt_pitch_accel = 0.0
volt_pitch_accel = limit_grade_feedforward(accel, volt_pitch_accel)
aero_drag_accel = (0.5 * self.coeffDrag * self.frontalArea * self.airDensity * CS.out.vEgo ** 2) / self.mass aero_drag_accel = (0.5 * self.coeffDrag * self.frontalArea * self.airDensity * CS.out.vEgo ** 2) / self.mass
accel_cmd = float(np.clip(accel + aero_drag_accel + volt_pitch_accel, self.params.ACCEL_MIN, self.params.ACCEL_MAX)) accel_cmd = float(np.clip(accel + aero_drag_accel + volt_pitch_accel, self.params.ACCEL_MIN, self.params.ACCEL_MAX))
@@ -1031,7 +1041,7 @@ class CarController(CarControllerBase):
if self.apply_brake > 0: if self.apply_brake > 0:
self.apply_gas = self.params.INACTIVE_REGEN self.apply_gas = self.params.INACTIVE_REGEN
else: else:
if long_pitch_for_powertrain and len(CC.orientationNED) == 3 and CS.out.vEgo > self.CP.vEgoStopping: if long_pitch_enabled and len(CC.orientationNED) == 3 and CS.out.vEgo > self.CP.vEgoStopping:
accel_due_to_pitch = math.sin(CC.orientationNED[1]) * ACCELERATION_DUE_TO_GRAVITY accel_due_to_pitch = math.sin(CC.orientationNED[1]) * ACCELERATION_DUE_TO_GRAVITY
else: else:
accel_due_to_pitch = 0.0 accel_due_to_pitch = 0.0
@@ -1048,6 +1058,7 @@ class CarController(CarControllerBase):
not self.CP.enableGasInterceptorDEPRECATED not self.CP.enableGasInterceptorDEPRECATED
) )
accel_due_to_pitch = shape_truck_pitch_accel(accel_due_to_pitch, CS.out.vEgo, truck_long_smoothing) accel_due_to_pitch = shape_truck_pitch_accel(accel_due_to_pitch, CS.out.vEgo, truck_long_smoothing)
accel_due_to_pitch = limit_grade_feedforward(actuators.accel, accel_due_to_pitch)
accel_input = actuators.accel + accel_due_to_pitch accel_input = actuators.accel + accel_due_to_pitch
if truck_long_smoothing: if truck_long_smoothing:
accel_input = shape_truck_positive_accel( accel_input = shape_truck_positive_accel(
+88 -2
View File
@@ -1,9 +1,11 @@
import copy import copy
import math
from cereal import custom from cereal import custom
from opendbc.can import CANDefine, CANParser from opendbc.can import CANDefine, CANParser
from opendbc.car import Bus, create_button_events, structs from opendbc.car import Bus, create_button_events, structs
from opendbc.car import DT_CTRL from opendbc.car import DT_CTRL
from opendbc.car.common.conversions import Conversions as CV from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.gps import get_car_gps_config
from opendbc.car.interfaces import CarStateBase from opendbc.car.interfaces import CarStateBase
from opendbc.car.gm.values import ( from opendbc.car.gm.values import (
ALT_ACCS, ALT_ACCS,
@@ -30,6 +32,7 @@ STANDSTILL_THRESHOLD = 10 * 0.0311
VOLT_EBCM_BRAKE_PRESSED_THRESHOLD = 6 / 0xd0 VOLT_EBCM_BRAKE_PRESSED_THRESHOLD = 6 / 0xd0
AUTO_HOLD_MIN_DRIVE_TIME_S = 3.0 AUTO_HOLD_MIN_DRIVE_TIME_S = 3.0
AUTO_HOLD_REGEN_RELEASE_COOLDOWN_S = 1.0 AUTO_HOLD_REGEN_RELEASE_COOLDOWN_S = 1.0
ACC_STARTUP_FAULT_GRACE_PERIOD_S = 5.0
BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.DECEL_SET: ButtonType.decelCruise, BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.DECEL_SET: ButtonType.decelCruise,
CruiseButtons.MAIN: ButtonType.mainCruise, CruiseButtons.CANCEL: ButtonType.cancel} CruiseButtons.MAIN: ButtonType.mainCruise, CruiseButtons.CANCEL: ButtonType.cancel}
@@ -65,6 +68,24 @@ def update_auto_hold_drive_timers(in_drive_for_hold: bool, moving_for_hold: bool
return auto_hold_drive_time, one_pedal_drive_time return auto_hold_drive_time, one_pedal_drive_time
def update_startup_acc_fault_suppression(car_fingerprint: str, system_power_mode: int,
previous_system_power_mode: int, timer: float,
acc_state: int, friction_brake_unavailable: bool) -> tuple[float, bool]:
if car_fingerprint != CAR.BUICK_LACROSSE:
return 0.0, False
if system_power_mode == 2 and previous_system_power_mode != 2:
timer = ACC_STARTUP_FAULT_GRACE_PERIOD_S
elif system_power_mode != 2:
timer = 0.0
if timer <= 0.0 or acc_state != AccState.FAULTED:
return 0.0, False
timer = max(timer - DT_CTRL, 0.0)
return timer, timer > 0.0 and not friction_brake_unavailable
class CarState(CarStateBase): class CarState(CarStateBase):
def __init__(self, CP, FPCP): def __init__(self, CP, FPCP):
super().__init__(CP, FPCP) super().__init__(CP, FPCP)
@@ -103,7 +124,56 @@ class CarState(CarStateBase):
self.lkas_previously_enabled = 0 self.lkas_previously_enabled = 0
self.lkas_enabled = 0 self.lkas_enabled = 0
self.pcm_acc_status = AccState.OFF self.pcm_acc_status = AccState.OFF
self.system_power_mode = 0
self.startup_acc_fault_suppression_timer = 0.0
self.stock_fcw_alert = 0 self.stock_fcw_alert = 0
self.car_gps_config = get_car_gps_config(CP)
self.car_gps_supported = self.car_gps_config is not None
self.car_gps = None
self._car_gps_timestamp_nanos = 0
self._prev_gps_lat = None
self._prev_gps_lon = None
self._last_gps_bearing = None
def _update_car_gps(self, cp, v_ego: float = 0.0) -> None:
if self.car_gps_config is None:
return
timestamps = [max(cp.ts_nanos[name].values(), default=0) for name in self.car_gps_config.messages]
if not all(timestamps) or max(timestamps) - min(timestamps) > 2_000_000_000:
return
timestamp_nanos = max(timestamps)
if timestamp_nanos <= self._car_gps_timestamp_nanos:
return
gps = self.car_gps_config.decoder(*(cp.vl[name] for name in self.car_gps_config.messages))
if gps is not None:
gps["timestamp_nanos"] = timestamp_nanos
if gps["hasFix"]:
lat, lon = gps["latitude"], gps["longitude"]
if self._prev_gps_lat is not None and (lat, lon) != (self._prev_gps_lat, self._prev_gps_lon):
d_lat = (lat - self._prev_gps_lat) * 111139.0
d_lon = (lon - self._prev_gps_lon) * 111139.0 * math.cos(math.radians(lat))
if math.hypot(d_lat, d_lon) > 1.5 and v_ego > 1.0 and not self.moving_backward:
self._last_gps_bearing = math.degrees(math.atan2(d_lon, d_lat)) % 360.0
self._prev_gps_lat, self._prev_gps_lon = lat, lon
bearing = self._last_gps_bearing if self._last_gps_bearing is not None else 0.0
gps["speed"] = max(0.0, v_ego)
gps["bearingDeg"] = bearing
gps["bearingAccuracyDeg"] = 5.0 if (v_ego > 1.0 and self._last_gps_bearing is not None) else 180.0
heading_rad = math.radians(bearing)
gps["vNED"] = [v_ego * math.cos(heading_rad), v_ego * math.sin(heading_rad), 0.0]
else:
self._prev_gps_lat = self._prev_gps_lon = None
self.car_gps = gps
self._car_gps_timestamp_nanos = timestamp_nanos
def get_car_gps(self):
return self.car_gps
def update_button_enable(self, buttonEvents: list[structs.CarState.ButtonEvent]): def update_button_enable(self, buttonEvents: list[structs.CarState.ButtonEvent]):
if not self.CP.pcmCruise: if not self.CP.pcmCruise:
@@ -189,6 +259,8 @@ class CarState(CarStateBase):
ret.standstill = abs(pt_cp.vl["EBCMWheelSpdRear"]["RLWheelSpd"]) <= STANDSTILL_THRESHOLD and \ ret.standstill = abs(pt_cp.vl["EBCMWheelSpdRear"]["RLWheelSpd"]) <= STANDSTILL_THRESHOLD and \
abs(pt_cp.vl["EBCMWheelSpdRear"]["RRWheelSpd"]) <= STANDSTILL_THRESHOLD abs(pt_cp.vl["EBCMWheelSpdRear"]["RRWheelSpd"]) <= STANDSTILL_THRESHOLD
self._update_car_gps(pt_cp, ret.vEgo)
if pt_cp.vl["ECMPRDNL2"]["ManualMode"] == 1: if pt_cp.vl["ECMPRDNL2"]["ManualMode"] == 1:
ret.gearShifter = self.parse_gear_shifter("T") ret.gearShifter = self.parse_gear_shifter("T")
else: else:
@@ -299,8 +371,18 @@ class CarState(CarStateBase):
ret.cruiseState.available = pt_cp.vl["ECMEngineStatus"]["CruiseMainOn"] != 0 ret.cruiseState.available = pt_cp.vl["ECMEngineStatus"]["CruiseMainOn"] != 0
ret.espDisabled = pt_cp.vl["ESPStatus"]["TractionControlOn"] != 1 ret.espDisabled = pt_cp.vl["ESPStatus"]["TractionControlOn"] != 1
ret.accFaulted = (pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.FAULTED or acc_state = pt_cp.vl["AcceleratorPedal2"]["CruiseState"]
pt_cp.vl["EBCMFrictionBrakeStatus"]["FrictionBrakeUnavailable"] == 1) friction_brake_unavailable = pt_cp.vl["EBCMFrictionBrakeStatus"]["FrictionBrakeUnavailable"] == 1
self.startup_acc_fault_suppression_timer, suppress_startup_acc_fault = update_startup_acc_fault_suppression(
self.CP.carFingerprint,
int(pt_cp.vl["BCMGeneralPlatformStatus"]["SystemPowerMode"]),
self.system_power_mode,
self.startup_acc_fault_suppression_timer,
acc_state,
friction_brake_unavailable,
)
self.system_power_mode = int(pt_cp.vl["BCMGeneralPlatformStatus"]["SystemPowerMode"])
ret.accFaulted = (acc_state == AccState.FAULTED and not suppress_startup_acc_fault) or friction_brake_unavailable
ret.cruiseState.enabled = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] != AccState.OFF ret.cruiseState.enabled = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] != AccState.OFF
ret.cruiseState.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL ret.cruiseState.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL
@@ -428,6 +510,9 @@ class CarState(CarStateBase):
@staticmethod @staticmethod
def get_can_parsers(CP): def get_can_parsers(CP):
gps_config = get_car_gps_config(CP)
gps_messages = [(name, 0) for name in gps_config.messages] if gps_config is not None else []
volt_like = { volt_like = {
CAR.CHEVROLET_VOLT, CAR.CHEVROLET_VOLT,
CAR.CHEVROLET_VOLT_2019, CAR.CHEVROLET_VOLT_2019,
@@ -451,6 +536,7 @@ class CarState(CarStateBase):
("PSCMSteeringAngle", 100), ("PSCMSteeringAngle", 100),
("ECMAcceleratorPos", 80), ("ECMAcceleratorPos", 80),
("SportMode", 0), ("SportMode", 0),
*gps_messages,
] ]
prndl2_rate = 10 if CP.carFingerprint in kaofui_state_cars else 40 prndl2_rate = 10 if CP.carFingerprint in kaofui_state_cars else 40
+14 -15
View File
@@ -30,20 +30,20 @@ FINGERPRINTS = {
CAR.BUICK_LACROSSE: [{ CAR.BUICK_LACROSSE: [{
190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 353: 3, 381: 6, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 1, 508: 8, 510: 8, 528: 5, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 5, 707: 8, 753: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 872: 1, 882: 8, 890: 1, 892: 2, 893: 1, 894: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1243: 3, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1904: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1914: 7, 1916: 7, 1918: 7, 1919: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8, 2026: 8 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 353: 3, 381: 6, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 1, 508: 8, 510: 8, 528: 5, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 5, 707: 8, 753: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 872: 1, 882: 8, 890: 1, 892: 2, 893: 1, 894: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1243: 3, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1904: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1914: 7, 1916: 7, 1918: 7, 1919: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8, 2026: 8
}], }],
# CAR.CHEVROLET_VOLT_CC: [ CAR.CHEVROLET_VOLT_CC: [
# FIXME: Need a message to distinguish flashed from non-flashed # Captured no-ACC Volt fingerprints for OBD-C/L&P harness installations
# Volt Premier w/o acc 2016 # Volt Premier w/o ACC 2016
# { {
# 170: 8, 171: 8, 189: 7, 190: 6, 192: 5, 193: 8, 197: 8, 199: 4, 201: 6, 209: 7, 211: 2, 241: 6, 288: 5, 289: 1, 290: 1, 298: 2, 304: 8, 308: 4, 309: 8, 311: 8, 313: 8, 320: 8, 328: 1, 352: 5, 368: 8, 381: 6, 384: 8, 386: 5, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 458: 8, 479: 3, 481: 7, 485: 8, 489: 5, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 3, 508: 8, 512: 3, 528: 4, 530: 8, 532: 6, 537: 4, 539: 8, 542: 7, 546: 7, 550: 8, 554: 3, 558: 8, 560: 6, 562: 8, 563: 5, 564: 5, 565: 8, 566: 5, 567: 3, 568: 1, 577: 8, 578: 8, 594: 8, 647: 3, 707: 8, 711: 6, 717: 5, 761: 7, 800: 6, 810: 8, 821: 4, 823: 7, 832: 8, 840: 5, 842: 6, 844: 8, 866: 4, 869: 4, 961: 8, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 5, 1003: 5, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1602: 8, 1618: 8, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7, 1928: 7, 1930: 7, 2016: 8, 2017: 8, 2018: 8, 2019: 8, 2020: 8, 2024: 8, 2025: 8, 2028: 8 170: 8, 171: 8, 189: 7, 190: 6, 192: 5, 193: 8, 197: 8, 199: 4, 201: 6, 209: 7, 211: 2, 241: 6, 288: 5, 289: 1, 290: 1, 298: 2, 304: 8, 308: 4, 309: 8, 311: 8, 313: 8, 320: 8, 328: 1, 352: 5, 368: 8, 381: 6, 384: 8, 386: 5, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 458: 8, 479: 3, 481: 7, 485: 8, 489: 5, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 3, 508: 8, 512: 3, 528: 4, 530: 8, 532: 6, 537: 4, 539: 8, 542: 7, 546: 7, 550: 8, 554: 3, 558: 8, 560: 6, 562: 8, 563: 5, 564: 5, 565: 8, 566: 5, 567: 3, 568: 1, 577: 8, 578: 8, 594: 8, 647: 3, 707: 8, 711: 6, 717: 5, 761: 7, 800: 6, 810: 8, 821: 4, 823: 7, 832: 8, 840: 5, 842: 6, 844: 8, 866: 4, 869: 4, 961: 8, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 5, 1003: 5, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1602: 8, 1618: 8, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7, 1928: 7, 1930: 7, 2016: 8, 2017: 8, 2018: 8, 2019: 8, 2020: 8, 2024: 8, 2025: 8, 2028: 8
# }, },
# { {
# 201: 8, 493: 8, 495: 4, 193: 8, 197: 8, 209: 7, 171: 8, 456: 8, 199: 4, 489: 8, 211: 2, 499: 3, 390: 7, 532: 6, 568: 1, 761: 7, 381: 6, 485: 8, 189: 7, 479: 3, 711: 6, 501: 8, 241: 6, 717: 5, 869: 4, 389: 2, 454: 8, 170: 8, 190: 6, 497: 8, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 500: 6, 508: 8, 528: 4, 647: 3, 1105: 6, 1005: 6, 481: 7, 844: 8, 866: 4, 564: 5, 969: 8, 388: 8, 352: 5, 562: 8, 961: 8, 386: 8, 707: 8, 977: 8, 979: 7, 298: 8, 840: 5, 842: 5, 988: 6, 1001: 8, 560: 8, 546: 7, 558: 8, 309: 8, 995: 7, 311: 8, 566: 5, 567:3, 989: 8, 384: 4, 800: 6, 1033: 7, 1034: 7, 313: 8, 554: 3, 810: 8, 1017: 8, 1019: 2, 1020: 8, 1217: 8, 1223: 3, 1233: 8, 1227: 4, 1417: 8, 1009: 8, 1221: 5, 1275: 3, 1225: 7, 289: 8, 550: 8, 1273: 3, 1928: 7, 1187: 4, 1265: 8, 1927: 7, 1267: 1, 1906: 7, 288: 5, 304: 1, 328: 1, 1912: 7, 320: 3, 1910: 7, 563: 5, 1249: 8, 1930: 7, 1257: 6, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 565: 5, 1280: 4, 1907: 7 201: 8, 493: 8, 495: 4, 193: 8, 197: 8, 209: 7, 171: 8, 456: 8, 199: 4, 489: 8, 211: 2, 499: 3, 390: 7, 532: 6, 568: 1, 761: 7, 381: 6, 485: 8, 189: 7, 479: 3, 711: 6, 501: 8, 241: 6, 717: 5, 869: 4, 389: 2, 454: 8, 170: 8, 190: 6, 497: 8, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 500: 6, 508: 8, 528: 4, 647: 3, 1105: 6, 1005: 6, 481: 7, 844: 8, 866: 4, 564: 5, 969: 8, 388: 8, 352: 5, 562: 8, 961: 8, 386: 8, 707: 8, 977: 8, 979: 7, 298: 8, 840: 5, 842: 5, 988: 6, 1001: 8, 560: 8, 546: 7, 558: 8, 309: 8, 995: 7, 311: 8, 566: 5, 567:3, 989: 8, 384: 4, 800: 6, 1033: 7, 1034: 7, 313: 8, 554: 3, 810: 8, 1017: 8, 1019: 2, 1020: 8, 1217: 8, 1223: 3, 1233: 8, 1227: 4, 1417: 8, 1009: 8, 1221: 5, 1275: 3, 1225: 7, 289: 8, 550: 8, 1273: 3, 1928: 7, 1187: 4, 1265: 8, 1927: 7, 1267: 1, 1906: 7, 288: 5, 304: 1, 328: 1, 1912: 7, 320: 3, 1910: 7, 563: 5, 1249: 8, 1930: 7, 1257: 6, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 565: 5, 1280: 4, 1907: 7
# }, },
# # Volt Premier w/o ACC 2018 + Pedal # Volt Premier w/o ACC 2018 + Pedal
# { {
# 189: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 288: 5, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 497: 8, 500: 6, 501: 8, 513: 6, 528: 4, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 5, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 717: 5, 761: 7, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1922: 7, 1930: 7 189: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 288: 5, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 497: 8, 500: 6, 501: 8, 513: 6, 528: 4, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 5, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 717: 5, 761: 7, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1922: 7, 1930: 7
# } }
# ], ],
CAR.BUICK_REGAL: [{ CAR.BUICK_REGAL: [{
190: 8, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 8, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 8, 419: 8, 422: 4, 426: 8, 431: 8, 442: 8, 451: 8, 452: 8, 453: 8, 455: 7, 456: 8, 463: 3, 479: 8, 481: 7, 485: 8, 487: 8, 489: 8, 495: 8, 497: 8, 499: 3, 500: 8, 501: 8, 508: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 569: 3, 573: 1, 577: 8, 578: 8, 579: 8, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 882: 8, 884: 8, 890: 1, 892: 2, 893: 2, 894: 1, 961: 8, 967: 8, 969: 8, 977: 8, 979: 8, 985: 8, 1001: 8, 1005: 6, 1009: 8, 1011: 8, 1013: 3, 1017: 8, 1020: 8, 1024: 8, 1025: 8, 1026: 8, 1027: 8, 1028: 8, 1029: 8, 1030: 8, 1031: 8, 1032: 2, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 8, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 8, 1263: 8, 1265: 8, 1267: 8, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1601: 8, 1602: 8, 1603: 7, 1611: 8, 1618: 8, 1906: 8, 1907: 7, 1912: 7, 1914: 7, 1916: 7, 1919: 7, 1930: 7, 2016: 8, 2018: 8, 2019: 8, 2024: 8, 2026: 8 190: 8, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 8, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 8, 419: 8, 422: 4, 426: 8, 431: 8, 442: 8, 451: 8, 452: 8, 453: 8, 455: 7, 456: 8, 463: 3, 479: 8, 481: 7, 485: 8, 487: 8, 489: 8, 495: 8, 497: 8, 499: 3, 500: 8, 501: 8, 508: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 569: 3, 573: 1, 577: 8, 578: 8, 579: 8, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 882: 8, 884: 8, 890: 1, 892: 2, 893: 2, 894: 1, 961: 8, 967: 8, 969: 8, 977: 8, 979: 8, 985: 8, 1001: 8, 1005: 6, 1009: 8, 1011: 8, 1013: 3, 1017: 8, 1020: 8, 1024: 8, 1025: 8, 1026: 8, 1027: 8, 1028: 8, 1029: 8, 1030: 8, 1031: 8, 1032: 2, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 8, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 8, 1263: 8, 1265: 8, 1267: 8, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1601: 8, 1602: 8, 1603: 7, 1611: 8, 1618: 8, 1906: 8, 1907: 7, 1912: 7, 1914: 7, 1916: 7, 1919: 7, 1930: 7, 2016: 8, 2018: 8, 2019: 8, 2024: 8, 2026: 8
}], }],
@@ -207,7 +207,6 @@ FINGERPRINTS = {
FINGERPRINTS.update({ FINGERPRINTS.update({
CAR.CHEVROLET_VOLT_ASCM: FINGERPRINTS[CAR.CHEVROLET_VOLT], CAR.CHEVROLET_VOLT_ASCM: FINGERPRINTS[CAR.CHEVROLET_VOLT],
CAR.CHEVROLET_VOLT_CAMERA: [{**fp, CAMERA_DIAGNOSTIC_ADDRESS: 8, CAMERA_DIAGNOSTIC_RX_ADDRESS: 8} for fp in FINGERPRINTS[CAR.CHEVROLET_VOLT]], CAR.CHEVROLET_VOLT_CAMERA: [{**fp, CAMERA_DIAGNOSTIC_ADDRESS: 8, CAMERA_DIAGNOSTIC_RX_ADDRESS: 8} for fp in FINGERPRINTS[CAR.CHEVROLET_VOLT]],
CAR.CHEVROLET_VOLT_CC: FINGERPRINTS[CAR.CHEVROLET_VOLT],
CAR.GMC_ACADIA_ASCM: FINGERPRINTS[CAR.GMC_ACADIA], CAR.GMC_ACADIA_ASCM: FINGERPRINTS[CAR.GMC_ACADIA],
CAR.CHEVROLET_MALIBU_ASCM: FINGERPRINTS[CAR.CHEVROLET_MALIBU], CAR.CHEVROLET_MALIBU_ASCM: FINGERPRINTS[CAR.CHEVROLET_MALIBU],
CAR.CADILLAC_ESCALADE_ASCM: FINGERPRINTS[CAR.CADILLAC_ESCALADE], CAR.CADILLAC_ESCALADE_ASCM: FINGERPRINTS[CAR.CADILLAC_ESCALADE],
+50 -10
View File
@@ -1,4 +1,4 @@
from opendbc.car import DT_CTRL from opendbc.car import DT_CTRL, structs
from opendbc.car.can_definitions import CanData from opendbc.car.can_definitions import CanData
from opendbc.car.common.conversions import Conversions as CV from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.gm.values import CAR, CanBus, CruiseButtons, GMFlags from opendbc.car.gm.values import CAR, CanBus, CruiseButtons, GMFlags
@@ -28,6 +28,11 @@ BOLT_CC_BUTTON_CARS = {
BOLT_CC_TARGET_DEADBAND_MPH = 0.75 BOLT_CC_TARGET_DEADBAND_MPH = 0.75
BOLT_CC_REVERSE_CONFIRM_S = 0.6 BOLT_CC_REVERSE_CONFIRM_S = 0.6
BOLT_CC_DIRECTION_MEMORY_S = 1.5 BOLT_CC_DIRECTION_MEMORY_S = 1.5
VOLT_CC_CARS = {
CAR.CHEVROLET_VOLT_CC,
}
VOLT_CC_TARGET_DEADBAND_MPH = 5.0
VOLT_CC_ACCEL_DEADBAND_MS2 = 0.15
def malibu_phase_map_for_button(button): def malibu_phase_map_for_button(button):
@@ -336,6 +341,36 @@ def stabilize_bolt_cc_button(controller, CP, requested_button):
return requested_button return requested_button
def _create_volt_cc_spam_command(CS, actuators, ms_convert):
accel = float(actuators.accel)
speed_setpoint = int(round(CS.out.cruiseState.speed * ms_convert))
ego_speed = CS.out.vEgo * ms_convert
v_cruise_kph = float(getattr(CS.out, "vCruise", 0.0))
if 0.0 < v_cruise_kph < 255.0:
is_metric = ms_convert == CV.MS_TO_KPH
target_setpoint = v_cruise_kph if is_metric else v_cruise_kph * CV.KPH_TO_MPH
target_deadband = VOLT_CC_TARGET_DEADBAND_MPH * (CV.MPH_TO_KPH if is_metric else 1.0)
if abs(target_setpoint - speed_setpoint) <= target_deadband:
return CruiseButtons.INIT, float("inf")
if abs(accel) <= VOLT_CC_ACCEL_DEADBAND_MS2:
return CruiseButtons.INIT, float("inf")
if accel < 0.0:
if speed_setpoint > ego_speed + 3.0:
rate = 0.2
else:
rate = max(1.0 / (-accel * ms_convert), 0.2)
return CruiseButtons.DECEL_SET, rate
if speed_setpoint < ego_speed - 3.0:
rate = 0.2
else:
rate = max(1.0 / (accel * ms_convert), 0.2)
return CruiseButtons.RES_ACCEL, rate
def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggles): def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggles):
accel = actuators.accel accel = actuators.accel
v_ego = CS.out.vEgo v_ego = CS.out.vEgo
@@ -350,12 +385,15 @@ def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggl
target_deadband = BOLT_CC_TARGET_DEADBAND_MPH * (CV.MPH_TO_KPH if is_metric else 1.0) if bolt_cc else 0.0 target_deadband = BOLT_CC_TARGET_DEADBAND_MPH * (CV.MPH_TO_KPH if is_metric else 1.0) if bolt_cc else 0.0
comparison_setpoint = projected_setpoint if bolt_cc else desired_setpoint comparison_setpoint = projected_setpoint if bolt_cc else desired_setpoint
if CS.CP.minEnableSpeed - (desired_setpoint / ms_convert) > 3.25: if CS.CP.carFingerprint in VOLT_CC_CARS:
cruise_btn = CruiseButtons.CANCEL cruise_btn, rate = _create_volt_cc_spam_command(CS, actuators, ms_convert)
elif comparison_setpoint < speed_setpoint - target_deadband and speed_setpoint > CS.CP.minEnableSpeed * ms_convert + 1: else:
cruise_btn = CruiseButtons.DECEL_SET if CS.CP.minEnableSpeed - (desired_setpoint / ms_convert) > 3.25:
elif comparison_setpoint > speed_setpoint + target_deadband: cruise_btn = CruiseButtons.CANCEL
cruise_btn = CruiseButtons.RES_ACCEL elif comparison_setpoint < speed_setpoint - target_deadband and speed_setpoint > CS.CP.minEnableSpeed * ms_convert + 1:
cruise_btn = CruiseButtons.DECEL_SET
elif comparison_setpoint > speed_setpoint + target_deadband:
cruise_btn = CruiseButtons.RES_ACCEL
cruise_btn = stabilize_bolt_cc_button(controller, CS.CP, cruise_btn) cruise_btn = stabilize_bolt_cc_button(controller, CS.CP, cruise_btn)
if cruise_btn == CruiseButtons.CANCEL: if cruise_btn == CruiseButtons.CANCEL:
@@ -420,9 +458,11 @@ def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggl
idx = (CS.buttons_counter + 1) % 4 # Need to predict the next idx for '22-23 EUV idx = (CS.buttons_counter + 1) % 4 # Need to predict the next idx for '22-23 EUV
msgs = [create_buttons(packer, CanBus.POWERTRAIN, idx, cruise_btn)] msgs = [create_buttons(packer, CanBus.POWERTRAIN, idx, cruise_btn)]
# Flashed camera-forward Volt CC installs also need the button spoof on the # A camera-forward Volt CC install needs the button spoof on both sides.
# camera side. Removed-camera installs set NO_CAMERA and keep this PT-only. # The OBD-C/L&P gateway variant has no camera bus and remains PT-only.
if CS.CP.carFingerprint == CAR.CHEVROLET_VOLT_CC and not (CS.CP.flags & GMFlags.NO_CAMERA.value): if (CS.CP.carFingerprint == CAR.CHEVROLET_VOLT_CC and
getattr(CS.CP, "networkLocation", None) == structs.CarParams.NetworkLocation.fwdCamera and
not (CS.CP.flags & GMFlags.NO_CAMERA.value)):
msgs.append(create_buttons(packer, CanBus.CAMERA, idx, cruise_btn)) msgs.append(create_buttons(packer, CanBus.CAMERA, idx, cruise_btn))
return msgs return msgs
else: else:
+20 -18
View File
@@ -15,6 +15,7 @@ from opendbc.car.gm.values import (
CC_ONLY_CAR, CC_ONLY_CAR,
CC_REGEN_PADDLE_CAR, CC_REGEN_PADDLE_CAR,
EV_CAR, EV_CAR,
GM_AUTO_HOLD_CARS,
SDGM_CAR, SDGM_CAR,
CarControllerParams, CarControllerParams,
CanBus, CanBus,
@@ -273,7 +274,6 @@ class CarInterface(CarInterfaceBase):
kaofui_camera_cars = { kaofui_camera_cars = {
CAR.CHEVROLET_VOLT_CAMERA, CAR.CHEVROLET_VOLT_CAMERA,
CAR.CHEVROLET_VOLT_CC,
CAR.CHEVROLET_MALIBU_HYBRID_CC, CAR.CHEVROLET_MALIBU_HYBRID_CC,
} }
bolt_cc_camera_cars = { bolt_cc_camera_cars = {
@@ -409,7 +409,7 @@ class CarInterface(CarInterfaceBase):
ret.steerActuatorDelay = 0.1 # Default delay, not measured yet ret.steerActuatorDelay = 0.1 # Default delay, not measured yet
ret.steerLimitTimer = 0.4 ret.steerLimitTimer = 0.4
ret.radarTimeStepDEPRECATED = 0.0667 # GM radar runs at 15Hz instead of the standard 20Hz ret.radarTimeStepDEPRECATED = 0.15 if candidate == CAR.BUICK_LACROSSE else 0.0667
ret.longitudinalActuatorDelay = 0.5 # large delay to initially start braking ret.longitudinalActuatorDelay = 0.5 # large delay to initially start braking
if candidate in ( if candidate in (
@@ -441,7 +441,7 @@ class CarInterface(CarInterfaceBase):
elif candidate in (CAR.BUICK_LACROSSE, CAR.BUICK_LACROSSE_ASCM, CAR.BUICK_LACROSSE_ASCM_19US): elif candidate in (CAR.BUICK_LACROSSE, CAR.BUICK_LACROSSE_ASCM, CAR.BUICK_LACROSSE_ASCM_19US):
CarInterfaceBase.configure_torque_tune(CAR.BUICK_LACROSSE, ret.lateralTuning) CarInterfaceBase.configure_torque_tune(CAR.BUICK_LACROSSE, ret.lateralTuning)
if candidate == CAR.BUICK_LACROSSE_ASCM_19US: if candidate == CAR.BUICK_LACROSSE_ASCM_19US:
ret.minSteerSpeed = 27 * CV.MPH_TO_MS ret.minSteerSpeed = 28 * CV.MPH_TO_MS
elif candidate == CAR.CADILLAC_ESCALADE: elif candidate == CAR.CADILLAC_ESCALADE:
ret.minEnableSpeed = -1. # engage speed is decided by pcm ret.minEnableSpeed = -1. # engage speed is decided by pcm
@@ -501,9 +501,7 @@ class CarInterface(CarInterfaceBase):
ret.flags |= GMFlags.PEDAL_LONG.value ret.flags |= GMFlags.PEDAL_LONG.value
elif candidate in (CAR.CHEVROLET_SILVERADO, CAR.CHEVROLET_SILVERADO_CC): elif candidate in (CAR.CHEVROLET_SILVERADO, CAR.CHEVROLET_SILVERADO_CC):
# On the Bolt, the ECM and camera independently check that you are either above 5 kph or at a stop ret.minEnableSpeed = 0.
# with foot on brake to allow engagement, but this platform only has that check in the camera.
# TODO: check if this is split by EV/ICE with more platforms in the future
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate in (CAR.CHEVROLET_EQUINOX, CAR.CHEVROLET_EQUINOX_CC): elif candidate in (CAR.CHEVROLET_EQUINOX, CAR.CHEVROLET_EQUINOX_CC):
@@ -667,7 +665,8 @@ class CarInterface(CarInterfaceBase):
ret.alphaLongitudinalAvailable = False ret.alphaLongitudinalAvailable = False
ret.openpilotLongitudinalControl = not disable_openpilot_long ret.openpilotLongitudinalControl = not disable_openpilot_long
ret.pcmCruise = False ret.pcmCruise = False
ret.minEnableSpeed = 24 * CV.MPH_TO_MS if candidate not in (CAR.CHEVROLET_SILVERADO, CAR.CHEVROLET_SILVERADO_CC):
ret.minEnableSpeed = 24 * CV.MPH_TO_MS
ret.radarUnavailable = True ret.radarUnavailable = True
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_CC_LONG.value ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_CC_LONG.value
@@ -685,6 +684,8 @@ class CarInterface(CarInterfaceBase):
if candidate in CC_ONLY_CAR: if candidate in CC_ONLY_CAR:
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_NO_ACC.value ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_NO_ACC.value
if candidate == CAR.CHEVROLET_VOLT_CC and ret.networkLocation == NetworkLocation.gateway:
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_VOLT_CC_GATEWAY.value
if candidate in SDGM_CAR and ACCELERATOR_POS_MSG not in fingerprint[CanBus.POWERTRAIN]: if candidate in SDGM_CAR and ACCELERATOR_POS_MSG not in fingerprint[CanBus.POWERTRAIN]:
ret.flags |= GMFlags.FORCE_BRAKE_C9.value ret.flags |= GMFlags.FORCE_BRAKE_C9.value
@@ -698,7 +699,7 @@ class CarInterface(CarInterfaceBase):
if ACCELERATOR_POS_MSG not in fingerprint[CanBus.POWERTRAIN]: if ACCELERATOR_POS_MSG not in fingerprint[CanBus.POWERTRAIN]:
ret.flags |= GMFlags.NO_ACCELERATOR_POS_MSG.value ret.flags |= GMFlags.NO_ACCELERATOR_POS_MSG.value
if candidate == CAR.CHEVROLET_VOLT and ret.networkLocation == NetworkLocation.gateway: if candidate in (CAR.CHEVROLET_VOLT, CAR.CHEVROLET_VOLT_CC) and ret.networkLocation == NetworkLocation.gateway:
# Reuse the no-camera safety bit as an ASCM Volt selector for the alternate EBCM brake path. # Reuse the no-camera safety bit as an ASCM Volt selector for the alternate EBCM brake path.
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_NO_CAMERA.value ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_NO_CAMERA.value
@@ -710,18 +711,19 @@ class CarInterface(CarInterfaceBase):
if remote_start_boots_comma: if remote_start_boots_comma:
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_REMOTE_START_BOOTS_COMMA.value ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_REMOTE_START_BOOTS_COMMA.value
volt_stock_friction_brake_safety = ( gm_stock_friction_brake_safety = (
ret.openpilotLongitudinalControl and ret.openpilotLongitudinalControl and
(gm_auto_hold or volt_one_pedal_mode) and (
candidate in { (gm_auto_hold and candidate in GM_AUTO_HOLD_CARS) or
CAR.CHEVROLET_VOLT, (volt_one_pedal_mode and candidate in {
CAR.CHEVROLET_VOLT_2019, CAR.CHEVROLET_VOLT,
CAR.CHEVROLET_VOLT_ASCM, CAR.CHEVROLET_VOLT_2019,
CAR.CHEVROLET_VOLT_CAMERA, CAR.CHEVROLET_VOLT_ASCM,
} CAR.CHEVROLET_VOLT_CAMERA,
})
)
) )
if volt_stock_friction_brake_safety: if gm_stock_friction_brake_safety:
# Reuse the paddle-scheduler safety bit as a Volt stock friction-brake
# marker on non-pedal paths. Auto hold and one-pedal can run while OP # marker on non-pedal paths. Auto hold and one-pedal can run while OP
# longitudinal is configured but not currently active, so the bit must # longitudinal is configured but not currently active, so the bit must
# be present regardless of the current long-control mode. Do not expose # be present regardless of the current long-control mode. Do not expose
@@ -53,6 +53,7 @@ from opendbc.car.gm.carcontroller import (
get_testing_ground_1_brake_switch_bias, get_testing_ground_1_brake_switch_bias,
get_acc_dashboard_status_active, get_acc_dashboard_status_active,
get_stock_cc_active_for_cancel, get_stock_cc_active_for_cancel,
limit_grade_feedforward,
shape_bolt_acc_pedal_low_speed_friction, shape_bolt_acc_pedal_low_speed_friction,
shape_truck_friction_brake, shape_truck_friction_brake,
shape_truck_pitch_accel, shape_truck_pitch_accel,
@@ -430,6 +431,15 @@ def test_volt_auto_hold_requires_toggle_supported_non_cc_only_volt_and_stock_saf
), ),
True, True,
) )
assert supports_volt_auto_hold(
SimpleNamespace(
carFingerprint=CAR.BUICK_LACROSSE,
openpilotLongitudinalControl=True,
networkLocation=CarParams.NetworkLocation.gateway,
safetyConfigs=stock_safety,
),
True,
)
assert not supports_volt_auto_hold( assert not supports_volt_auto_hold(
SimpleNamespace( SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT, carFingerprint=CAR.CHEVROLET_VOLT,
@@ -895,6 +905,20 @@ def test_shape_truck_pitch_accel_is_inactive_without_truck_tuning():
assert shape_truck_pitch_accel(-0.30, 30.0, False) == pytest.approx(-0.30) assert shape_truck_pitch_accel(-0.30, 30.0, False) == pytest.approx(-0.30)
def test_limit_grade_feedforward_does_not_stack_on_positive_planner():
assert limit_grade_feedforward(0.40, 0.50) == 0.0
def test_limit_grade_feedforward_caps_uphill_hold():
assert limit_grade_feedforward(0.0, 0.50) == pytest.approx(0.20)
assert limit_grade_feedforward(-0.10, 0.50) == pytest.approx(0.20)
def test_limit_grade_feedforward_keeps_downhill_help():
assert limit_grade_feedforward(0.40, -0.30) == pytest.approx(-0.30)
assert limit_grade_feedforward(-0.20, -0.30) == pytest.approx(-0.30)
def test_shape_truck_friction_brake_suppresses_boundary_chatter(): def test_shape_truck_friction_brake_suppresses_boundary_chatter():
assert shape_truck_friction_brake(14, -0.3, False, False) == (0, False) assert shape_truck_friction_brake(14, -0.3, False, False) == (0, False)
+445 -1
View File
@@ -8,7 +8,12 @@ from opendbc.can import CANPacker, CANParser
from opendbc.car import Bus, DT_CTRL, structs from opendbc.car import Bus, DT_CTRL, structs
from opendbc.car.car_helpers import interfaces from opendbc.car.car_helpers import interfaces
from opendbc.car.gm import gmcan from opendbc.car.gm import gmcan
from opendbc.car.gm.carstate import CarState as GMCarState, get_hard_cruise_buttons, update_auto_hold_drive_timers from opendbc.car.gm.carstate import (
CarState as GMCarState,
get_hard_cruise_buttons,
update_auto_hold_drive_timers,
update_startup_acc_fault_suppression,
)
from opendbc.car.gm.carcontroller import ( from opendbc.car.gm.carcontroller import (
VisualAlert, VisualAlert,
get_acc_dashboard_always_one, get_acc_dashboard_always_one,
@@ -20,6 +25,7 @@ from opendbc.car.gm.carcontroller import (
) )
import opendbc.car.gm.interface as gm_interface import opendbc.car.gm.interface as gm_interface
from opendbc.car.common.conversions import Conversions as CV from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.gps import CHEVROLET_BOLT_GPS_CARS, CHEVROLET_BOLT_GPS_MESSAGES, get_car_gps_config, parse_chevrolet_bolt_can_gps
from opendbc.car.gm.fingerprints import FINGERPRINTS from opendbc.car.gm.fingerprints import FINGERPRINTS
from opendbc.car.gm.values import ASCM_INT, CAMERA_ACC_CAR, CAR, CC_ONLY_CAR, DBC, GM_RX_OFFSET, CarControllerParams, CruiseButtons, GMFlags, GMSafetyFlags from opendbc.car.gm.values import ASCM_INT, CAMERA_ACC_CAR, CAR, CC_ONLY_CAR, DBC, GM_RX_OFFSET, CarControllerParams, CruiseButtons, GMFlags, GMSafetyFlags
from opendbc.safety import ALTERNATIVE_EXPERIENCE from opendbc.safety import ALTERNATIVE_EXPERIENCE
@@ -65,7 +71,216 @@ class TestGMFingerprint:
assert finger.get(required_addr) == 8, required_addr assert finger.get(required_addr) == 8, required_addr
class TestBoltGps:
@parameterized.expand(CHEVROLET_BOLT_GPS_CARS)
def test_all_bolt_generations_are_registered(self, car_model):
config = get_car_gps_config(SimpleNamespace(carFingerprint=car_model, brand="gm"))
assert config is not None
assert config.messages == CHEVROLET_BOLT_GPS_MESSAGES
gps = parse_chevrolet_bolt_can_gps({
"GPSLatitude": 145292743.0,
"GPSLongitude": -267520892.0,
})
assert gps is not None
assert gps["hasFix"]
assert gps["latitude"] == pytest.approx(40.3590953)
assert gps["longitude"] == pytest.approx(-74.3113589)
def test_invalid_bolt_position_does_not_become_a_fix(self):
gps = parse_chevrolet_bolt_can_gps({"GPSLatitude": 0.0, "GPSLongitude": -2147483648.0})
assert gps is not None
assert not gps["hasFix"]
assert gps["latitude"] == 0.0
assert gps["longitude"] == 0.0
def test_bolt_gps_accuracy_metrics(self):
gps = parse_chevrolet_bolt_can_gps({"GPSLatitude": 145292743.0, "GPSLongitude": -267520892.0})
assert gps is not None
assert gps["horizontalAccuracy"] == 6.0
assert gps["verticalAccuracy"] == 10.0
assert gps["speedAccuracy"] == 0.5
def test_bolt_gps_heading_and_speed_derivation(self):
cp = SimpleNamespace(
brand="gm",
carFingerprint=CAR.CHEVROLET_BOLT_CC_2018_2021,
flags=0,
networkLocation=structs.CarParams.NetworkLocation.gateway,
transmissionType=structs.CarParams.TransmissionType.direct,
enableBsm=False,
enableGasInterceptorDEPRECATED=False,
pcmCruise=False,
)
fpcp = custom.StarPilotCarParams.new_message()
cs = GMCarState(cp, fpcp)
# First position (stationary)
mock_cp = SimpleNamespace(
ts_nanos={"TCICOnStarGPSPosition": {"GPSLatitude": 1_000_000}},
vl={"TCICOnStarGPSPosition": {"GPSLatitude": 145292743.0, "GPSLongitude": -267520892.0}},
)
cs._update_car_gps(mock_cp, v_ego=0.0)
gps = cs.get_car_gps()
assert gps is not None
assert gps["speed"] == 0.0
assert gps["bearingDeg"] == 0.0
assert gps["bearingAccuracyDeg"] == 180.0
# Move East at 15 m/s
mock_cp.ts_nanos["TCICOnStarGPSPosition"]["GPSLatitude"] = 2_000_000_000
mock_cp.vl["TCICOnStarGPSPosition"]["GPSLongitude"] = -267520892.0 + 1000.0 # Eastward shift
cs._update_car_gps(mock_cp, v_ego=15.0)
gps = cs.get_car_gps()
assert gps is not None
assert gps["speed"] == 15.0
assert gps["bearingDeg"] == pytest.approx(90.0, abs=1.0)
assert gps["bearingAccuracyDeg"] == 5.0
assert gps["vNED"][1] > 0.0 # East velocity positive
# Stop moving (v_ego=0.0): heading should be retained, not reset to 0
mock_cp.ts_nanos["TCICOnStarGPSPosition"]["GPSLatitude"] = 3_000_000_000
cs._update_car_gps(mock_cp, v_ego=0.0)
gps = cs.get_car_gps()
assert gps is not None
assert gps["speed"] == 0.0
assert gps["bearingDeg"] == pytest.approx(90.0, abs=1.0)
# Reversing: coordinate changes while moving backward should not flip heading
cs.moving_backward = True
mock_cp.ts_nanos["TCICOnStarGPSPosition"]["GPSLatitude"] = 4_000_000_000
mock_cp.vl["TCICOnStarGPSPosition"]["GPSLongitude"] = -267520892.0 - 1000.0 # Westward shift
cs._update_car_gps(mock_cp, v_ego=3.0)
gps = cs.get_car_gps()
assert gps is not None
assert gps["bearingDeg"] == pytest.approx(90.0, abs=1.0)
# Drive True North: verify bearing is 0.0 deg and accuracy is 5.0 deg (not degraded to 180.0)
cs.moving_backward = False
mock_cp.ts_nanos["TCICOnStarGPSPosition"]["GPSLatitude"] = 5_000_000_000
mock_cp.vl["TCICOnStarGPSPosition"]["GPSLatitude"] = 145292743.0 + 1000.0 # Northward shift
cs._update_car_gps(mock_cp, v_ego=12.0)
gps = cs.get_car_gps()
assert gps is not None
assert gps["bearingDeg"] == pytest.approx(0.0, abs=1.0)
assert gps["bearingAccuracyDeg"] == 5.0
# Tunnel / fix loss: invalid coordinates cause hasFix=False and clear previous coordinates
mock_cp.ts_nanos["TCICOnStarGPSPosition"]["GPSLatitude"] = 6_000_000_000
mock_cp.vl["TCICOnStarGPSPosition"]["GPSLatitude"] = 0.0
mock_cp.vl["TCICOnStarGPSPosition"]["GPSLongitude"] = 0.0
cs._update_car_gps(mock_cp, v_ego=20.0)
gps = cs.get_car_gps()
assert gps is not None
assert not gps["hasFix"]
assert cs._prev_gps_lat is None and cs._prev_gps_lon is None
# Tunnel exit: GPS fix re-acquired 5 km away heading South
# The first sample after fix loss sets initial coordinates without calculating a phantom jump vector
mock_cp.ts_nanos["TCICOnStarGPSPosition"]["GPSLatitude"] = 7_000_000_000
mock_cp.vl["TCICOnStarGPSPosition"]["GPSLatitude"] = 145292743.0 - 50000.0
mock_cp.vl["TCICOnStarGPSPosition"]["GPSLongitude"] = -267520892.0
cs._update_car_gps(mock_cp, v_ego=20.0)
gps = cs.get_car_gps()
assert gps is not None
assert gps["hasFix"]
assert gps["bearingDeg"] == pytest.approx(0.0, abs=1.0)
assert cs._prev_gps_lat is not None
# Second sample: moving Southward -> bearing smoothly updates to 180 deg
mock_cp.ts_nanos["TCICOnStarGPSPosition"]["GPSLatitude"] = 8_000_000_000
mock_cp.vl["TCICOnStarGPSPosition"]["GPSLatitude"] = 145292743.0 - 51000.0
cs._update_car_gps(mock_cp, v_ego=20.0)
gps = cs.get_car_gps()
assert gps is not None
assert gps["bearingDeg"] == pytest.approx(180.0, abs=1.0)
@parameterized.expand(CHEVROLET_BOLT_GPS_CARS)
def test_gps_message_is_added_to_powertrain_parser(self, car_model):
cp = SimpleNamespace(
brand="gm",
carFingerprint=car_model,
flags=0,
networkLocation=structs.CarParams.NetworkLocation.gateway,
transmissionType=structs.CarParams.TransmissionType.direct,
enableBsm=False,
enableGasInterceptorDEPRECATED=False,
)
parsers = GMCarState.get_can_parsers(cp)
assert all(message in parsers[Bus.pt].vl for message in CHEVROLET_BOLT_GPS_MESSAGES)
class TestGMCarState:
def test_lacrosse_startup_acc_fault_is_suppressed(self):
timer, suppressed = update_startup_acc_fault_suppression(
CAR.BUICK_LACROSSE, 2, 0, 0.0, 3, False,
)
assert suppressed
assert timer == pytest.approx(5.0 - DT_CTRL)
timer, suppressed = update_startup_acc_fault_suppression(
CAR.BUICK_LACROSSE, 2, 2, timer, 0, False,
)
assert timer == 0.0
assert not suppressed
def test_lacrosse_persistent_acc_fault_is_reported_after_startup(self):
timer, suppressed = update_startup_acc_fault_suppression(
CAR.BUICK_LACROSSE, 2, 0, 0.0, 3, False,
)
for _ in range(int(5.0 / DT_CTRL)):
timer, suppressed = update_startup_acc_fault_suppression(
CAR.BUICK_LACROSSE, 2, 2, timer, 3, False,
)
assert timer == 0.0
assert not suppressed
def test_lacrosse_brake_unavailable_fault_is_never_suppressed(self):
_, suppressed = update_startup_acc_fault_suppression(
CAR.BUICK_LACROSSE, 2, 0, 0.0, 3, True,
)
assert not suppressed
def test_startup_acc_fault_suppression_is_scoped_to_lacrosse(self):
_, suppressed = update_startup_acc_fault_suppression(
CAR.BUICK_REGAL, 2, 0, 0.0, 3, False,
)
assert not suppressed
class TestGMInterface: class TestGMInterface:
def test_lacrosse_obd_and_ascm_integrations_remain_separate(self):
obd_params = interfaces[CAR.BUICK_LACROSSE].get_params(
CAR.BUICK_LACROSSE,
_empty_fingerprint(),
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
ascm_params = interfaces[CAR.BUICK_LACROSSE_ASCM].get_params(
CAR.BUICK_LACROSSE_ASCM,
_empty_fingerprint(),
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
assert obd_params.networkLocation == structs.CarParams.NetworkLocation.gateway
assert obd_params.openpilotLongitudinalControl
assert obd_params.radarTimeStepDEPRECATED == pytest.approx(0.15)
assert ascm_params.networkLocation == structs.CarParams.NetworkLocation.fwdCamera
assert not ascm_params.openpilotLongitudinalControl
assert ascm_params.radarTimeStepDEPRECATED == pytest.approx(0.0667)
@parameterized.expand([ @parameterized.expand([
CAR.CHEVROLET_BOLT_CC_2017, CAR.CHEVROLET_BOLT_CC_2017,
CAR.CHEVROLET_BOLT_CC_2018_2021, CAR.CHEVROLET_BOLT_CC_2018_2021,
@@ -152,6 +367,14 @@ class TestGMInterface:
assert car_params.minSteerSpeed == pytest.approx(7 * CV.MPH_TO_MS) assert car_params.minSteerSpeed == pytest.approx(7 * CV.MPH_TO_MS)
def test_lacrosse_2019_ascm_min_steer_speed_is_28_mph(self):
car_model = CAR.BUICK_LACROSSE_ASCM_19US
CarInterface = interfaces[car_model]
car_params = CarInterface.get_params(car_model, _empty_fingerprint(), [], alpha_long=False, is_release=False, docs=False,
starpilot_toggles=_test_starpilot_toggles())
assert car_params.minSteerSpeed == pytest.approx(28 * CV.MPH_TO_MS)
@parameterized.expand([ @parameterized.expand([
("interceptor", True), ("interceptor", True),
("ascm_int", False), ("ascm_int", False),
@@ -201,6 +424,45 @@ class TestGMInterface:
assert car_params.flags & GMFlags.NO_CAMERA.value assert car_params.flags & GMFlags.NO_CAMERA.value
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_CAMERA.value assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_CAMERA.value
def test_volt_cc_obd_gateway_uses_cc_long_no_camera_path(self):
CarInterface = interfaces[CAR.CHEVROLET_VOLT_CC]
car_params = CarInterface.get_params(
CAR.CHEVROLET_VOLT_CC,
_empty_fingerprint(),
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
assert car_params.networkLocation == structs.CarParams.NetworkLocation.gateway
assert car_params.flags & GMFlags.CC_LONG.value
assert car_params.flags & GMFlags.NO_CAMERA.value
assert not (car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.HW_CAM.value)
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_CC_LONG.value
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_ACC.value
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_CAMERA.value
parsers = CarInterface.CarState.get_can_parsers(car_params)
assert "ECMCruiseControl" in parsers[Bus.pt].vl
assert not parsers[Bus.cam].vl
def test_other_cc_only_gateway_does_not_use_volt_cc_safety_path(self):
CarInterface = interfaces[CAR.CHEVROLET_SILVERADO_CC]
car_params = CarInterface.get_params(
CAR.CHEVROLET_SILVERADO_CC,
_empty_fingerprint(),
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
assert car_params.networkLocation == structs.CarParams.NetworkLocation.gateway
assert not (car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_VOLT_CC_GATEWAY.value)
def test_volt_ascm_sparse_fingerprint_without_camera_does_not_set_no_camera(self): def test_volt_ascm_sparse_fingerprint_without_camera_does_not_set_no_camera(self):
CarInterface = interfaces[CAR.CHEVROLET_VOLT_ASCM] CarInterface = interfaces[CAR.CHEVROLET_VOLT_ASCM]
fingerprint = { fingerprint = {
@@ -226,11 +488,36 @@ class TestGMInterface:
assert car_params.openpilotLongitudinalControl assert car_params.openpilotLongitudinalControl
assert not car_params.enableGasInterceptorDEPRECATED assert not car_params.enableGasInterceptorDEPRECATED
assert car_params.minEnableSpeed == pytest.approx(0.0)
assert list(car_params.longitudinalTuning.kpBP) == pytest.approx([0.0, 5.0, 15.0, 35.0]) assert list(car_params.longitudinalTuning.kpBP) == pytest.approx([0.0, 5.0, 15.0, 35.0])
assert list(car_params.longitudinalTuning.kpV) == pytest.approx([0.02, 0.03, 0.028, 0.022]) assert list(car_params.longitudinalTuning.kpV) == pytest.approx([0.02, 0.03, 0.028, 0.022])
assert list(car_params.longitudinalTuning.kiBP) == pytest.approx([0.0, 5.0, 15.0, 35.0]) assert list(car_params.longitudinalTuning.kiBP) == pytest.approx([0.0, 5.0, 15.0, 35.0])
assert list(car_params.longitudinalTuning.kiV) == pytest.approx([0.20, 0.18, 0.13, 0.08]) assert list(car_params.longitudinalTuning.kiV) == pytest.approx([0.20, 0.18, 0.13, 0.08])
def test_silverado_camera_acc_allows_engage_from_stop(self):
CarInterface = interfaces[CAR.CHEVROLET_SILVERADO]
fingerprint = _empty_fingerprint()
fingerprint[0] = FINGERPRINTS[CAR.CHEVROLET_SILVERADO][0].copy()
car_params = CarInterface.get_params(CAR.CHEVROLET_SILVERADO, fingerprint, [], alpha_long=False, is_release=False,
docs=False, starpilot_toggles=_test_starpilot_toggles())
assert car_params.minEnableSpeed == pytest.approx(0.0)
def test_silverado_cc_allows_engage_from_stop(self):
CarInterface = interfaces[CAR.CHEVROLET_SILVERADO_CC]
car_params = CarInterface.get_params(
CAR.CHEVROLET_SILVERADO_CC,
_empty_fingerprint(),
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
assert car_params.minEnableSpeed == pytest.approx(0.0)
def test_blazer_uses_softer_low_speed_stop_hold_tune(self): def test_blazer_uses_softer_low_speed_stop_hold_tune(self):
CarInterface = interfaces[CAR.CHEVROLET_BLAZER] CarInterface = interfaces[CAR.CHEVROLET_BLAZER]
fingerprint = _empty_fingerprint() fingerprint = _empty_fingerprint()
@@ -283,6 +570,43 @@ class TestGMInterface:
assert car_params.openpilotLongitudinalControl assert car_params.openpilotLongitudinalControl
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value
def test_buick_lacrosse_auto_hold_sets_stock_hold_safety_bit_with_op_long_enabled(self):
params = Params()
try:
params.put_bool("GMAutoHold", True)
car_params = interfaces[CAR.BUICK_LACROSSE].get_params(
CAR.BUICK_LACROSSE,
_empty_fingerprint(),
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
finally:
params.remove("GMAutoHold")
assert car_params.openpilotLongitudinalControl
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value
def test_buick_lacrosse_auto_hold_is_off_when_toggle_is_disabled(self):
params = Params()
try:
params.put_bool("GMAutoHold", False)
car_params = interfaces[CAR.BUICK_LACROSSE].get_params(
CAR.BUICK_LACROSSE,
_empty_fingerprint(),
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
finally:
params.remove("GMAutoHold")
assert not car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value
def test_volt_auto_hold_does_not_set_stock_hold_safety_bit_with_op_long_disabled(self): def test_volt_auto_hold_does_not_set_stock_hold_safety_bit_with_op_long_disabled(self):
CarInterface = interfaces[CAR.CHEVROLET_VOLT_ASCM] CarInterface = interfaces[CAR.CHEVROLET_VOLT_ASCM]
fingerprint = _empty_fingerprint() fingerprint = _empty_fingerprint()
@@ -526,6 +850,13 @@ class TestGMCarController:
assert not should_send_cc_button_spam(SimpleNamespace(flags=GMFlags.CC_LONG.value, minEnableSpeed=10.0), cc, cs) assert not should_send_cc_button_spam(SimpleNamespace(flags=GMFlags.CC_LONG.value, minEnableSpeed=10.0), cc, cs)
assert not should_send_cc_button_spam(SimpleNamespace(flags=0, minEnableSpeed=10.0), cc, cs) assert not should_send_cc_button_spam(SimpleNamespace(flags=0, minEnableSpeed=10.0), cc, cs)
def test_cc_button_spam_allows_standstill_when_min_enable_is_zero(self):
cp = SimpleNamespace(flags=GMFlags.CC_LONG.value, minEnableSpeed=0.0)
cc = SimpleNamespace(longActive=True)
cs = SimpleNamespace(out=SimpleNamespace(vEgo=0.0, cruiseState=SimpleNamespace(enabled=False)))
assert should_send_cc_button_spam(cp, cc, cs)
def test_volt_cc_redneck_spam_is_mirrored_to_camera_bus(self): def test_volt_cc_redneck_spam_is_mirrored_to_camera_bus(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt]) packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(0.3 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0) controller = SimpleNamespace(frame=int(0.3 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
@@ -533,6 +864,7 @@ class TestGMCarController:
CP=SimpleNamespace( CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC, carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=0, flags=0,
networkLocation=structs.CarParams.NetworkLocation.fwdCamera,
minEnableSpeed=24 * CV.MPH_TO_MS, minEnableSpeed=24 * CV.MPH_TO_MS,
), ),
buttons_counter=2, buttons_counter=2,
@@ -547,6 +879,117 @@ class TestGMCarController:
assert [msg[2] for msg in msgs] == [0, 2] assert [msg[2] for msg in msgs] == [0, 2]
def test_volt_cc_redneck_holds_setpoint_without_planner_acceleration(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(2.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=0.0,
),
buttons_counter=2,
out=SimpleNamespace(
vEgo=60.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=60.0 * CV.KPH_TO_MS),
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=0.0), SimpleNamespace(is_metric=True),
)
assert msgs == []
assert controller.apply_speed == 60
def test_volt_cc_redneck_rate_limits_setpoint_changes_by_planner_acceleration(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(0.5 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=0.0,
),
buttons_counter=2,
out=SimpleNamespace(
vEgo=60.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=60.0 * CV.KPH_TO_MS),
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=0.5), SimpleNamespace(is_metric=True),
)
assert msgs == []
controller.frame = int(0.7 / DT_CTRL)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=0.5), SimpleNamespace(is_metric=True),
)
assert len(msgs) == 1
def test_volt_cc_redneck_holds_when_stock_setpoint_is_within_target_deadband(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(2.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=0.0,
),
buttons_counter=2,
out=SimpleNamespace(
vEgo=100.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=99.0 * CV.KPH_TO_MS),
vCruise=100.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=0.5), SimpleNamespace(is_metric=True),
)
assert msgs == []
assert controller.apply_speed == 99
def test_volt_cc_redneck_catches_up_when_target_exceeds_deadband(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(0.5 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=0.0,
),
buttons_counter=2,
out=SimpleNamespace(
vEgo=90.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=90.0 * CV.KPH_TO_MS),
vCruise=100.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=0.5), SimpleNamespace(is_metric=True),
)
assert msgs == []
controller.frame = int(0.7 / DT_CTRL)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=0.5), SimpleNamespace(is_metric=True),
)
assert len(msgs) == 1
assert controller.apply_speed == 91
def test_volt_cc_no_camera_redneck_spam_stays_on_powertrain_bus(self): def test_volt_cc_no_camera_redneck_spam_stays_on_powertrain_bus(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt]) packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(0.3 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0) controller = SimpleNamespace(frame=int(0.3 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
@@ -554,6 +997,7 @@ class TestGMCarController:
CP=SimpleNamespace( CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC, carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value, flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=24 * CV.MPH_TO_MS, minEnableSpeed=24 * CV.MPH_TO_MS,
), ),
buttons_counter=2, buttons_counter=2,
+10 -1
View File
@@ -175,6 +175,7 @@ class GMSafetyFlags(IntFlag):
FLAG_GM_REMOTE_START_BOOTS_COMMA = 8192 FLAG_GM_REMOTE_START_BOOTS_COMMA = 8192
FLAG_GM_PANDA_3D1_SCHED = 16384 FLAG_GM_PANDA_3D1_SCHED = 16384
FLAG_GM_PANDA_PADDLE_SCHED = 32768 FLAG_GM_PANDA_PADDLE_SCHED = 32768
FLAG_GM_VOLT_CC_GATEWAY = 16384
class Footnote(Enum): class Footnote(Enum):
@@ -247,7 +248,7 @@ class CAR(Platforms):
dbc_dict=CHEVROLET_VOLT.dbc_dict, dbc_dict=CHEVROLET_VOLT.dbc_dict,
) )
CHEVROLET_VOLT_CC = GMPlatformConfig( CHEVROLET_VOLT_CC = GMPlatformConfig(
[GMCarDocs("Chevrolet Volt No-ACC 2017-18", min_enable_speed=0)], [GMCarDocs("Chevrolet Volt No-ACC 2016-18 (OBD Harness)", "Redneck ACC", min_enable_speed=0)],
CHEVROLET_VOLT.specs, CHEVROLET_VOLT.specs,
dbc_dict=CHEVROLET_VOLT.dbc_dict, dbc_dict=CHEVROLET_VOLT.dbc_dict,
) )
@@ -532,6 +533,14 @@ EV_CAR = {
CAR.CHEVROLET_MALIBU_HYBRID_CC, CAR.CHEVROLET_MALIBU_HYBRID_CC,
} }
GM_AUTO_HOLD_CARS = {
CAR.CHEVROLET_VOLT,
CAR.CHEVROLET_VOLT_2019,
CAR.CHEVROLET_VOLT_ASCM,
CAR.CHEVROLET_VOLT_CAMERA,
CAR.BUICK_LACROSSE,
}
# We're integrated at the camera with VOACC on these cars (instead of ASCM w/ OBD-II harness) # We're integrated at the camera with VOACC on these cars (instead of ASCM w/ OBD-II harness)
CAMERA_ACC_CAR = { CAMERA_ACC_CAR = {
CAR.CHEVROLET_BOLT_ACC_2022_2023, CAR.CHEVROLET_BOLT_ACC_2022_2023,
+51
View File
@@ -7,6 +7,7 @@ from typing import Any
from opendbc.car.common.conversions import Conversions as CV from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.ford.values import CAR as FORD_CAR from opendbc.car.ford.values import CAR as FORD_CAR
from opendbc.car.gm.values import CAR as GM_CAR
CarGpsSample = dict[str, Any] CarGpsSample = dict[str, Any]
@@ -90,11 +91,53 @@ def parse_ford_can_gps(nav1: Mapping[str, float], nav2: Mapping[str, float], nav
} }
def parse_chevrolet_bolt_can_gps(position: Mapping[str, float]) -> CarGpsSample | None:
"""Decode the Bolt's OnStar GPS position message."""
try:
latitude = float(position["GPSLatitude"]) / 3_600_000.0
longitude = float(position["GPSLongitude"]) / 3_600_000.0
except (KeyError, TypeError, ValueError):
return None
coordinates_valid = (
math.isfinite(latitude) and math.isfinite(longitude) and
-90.0 <= latitude <= 90.0 and -180.0 <= longitude <= 180.0 and
(latitude != 0.0 or longitude != 0.0)
)
if not coordinates_valid:
latitude = longitude = 0.0
return {
"latitude": latitude,
"longitude": longitude,
"altitude": 0.0,
"speed": 0.0,
"bearingDeg": 0.0,
"horizontalAccuracy": 6.0,
"unixTimestampMillis": int(datetime.now(UTC).timestamp() * 1000),
"verticalAccuracy": 10.0,
"bearingAccuracyDeg": 180.0,
"speedAccuracy": 0.5,
"hasFix": coordinates_valid,
"satelliteCount": 0,
"vNED": [0.0, 0.0, 0.0],
}
FORD_MACH_E_GPS_MESSAGES = ( FORD_MACH_E_GPS_MESSAGES = (
"APIMGPS_Data_Nav_1_FD1", "APIMGPS_Data_Nav_1_FD1",
"APIMGPS_Data_Nav_2_FD1", "APIMGPS_Data_Nav_2_FD1",
"APIMGPS_Data_Nav_3_FD1", "APIMGPS_Data_Nav_3_FD1",
) )
CHEVROLET_BOLT_GPS_MESSAGES = ("TCICOnStarGPSPosition",)
CHEVROLET_BOLT_GPS_CARS = (
GM_CAR.CHEVROLET_BOLT_ACC_2022_2023,
GM_CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL,
GM_CAR.CHEVROLET_BOLT_CC_2022_2023,
GM_CAR.CHEVROLET_BOLT_CC_2018_2021,
GM_CAR.CHEVROLET_BOLT_CC_2017,
)
CAR_GPS_CONFIGS: dict[str, CarGpsConfig] = { CAR_GPS_CONFIGS: dict[str, CarGpsConfig] = {
@@ -103,6 +146,14 @@ CAR_GPS_CONFIGS: dict[str, CarGpsConfig] = {
messages=FORD_MACH_E_GPS_MESSAGES, messages=FORD_MACH_E_GPS_MESSAGES,
decoder=parse_ford_can_gps, decoder=parse_ford_can_gps,
), ),
**{
car: CarGpsConfig(
brand="gm",
messages=CHEVROLET_BOLT_GPS_MESSAGES,
decoder=parse_chevrolet_bolt_can_gps,
)
for car in CHEVROLET_BOLT_GPS_CARS
},
} }
@@ -1107,6 +1107,14 @@ def test_crv_5g_bosch_a_radar_dbc_wired_for_parser_unit_tests():
assert ri.rcp.bus == CanBus(cp).camera assert ri.rcp.bus == CanBus(cp).camera
def test_accord_bosch_a_radar_stays_disabled_until_validated():
cp = CarInterface.get_non_essential_params(CAR.HONDA_ACCORD)
assert cp.radarUnavailable is True
ri = CarInterface.RadarInterface(cp)
assert ri.bosch_a_radar is False
assert ri.rcp is None
def test_civic_bosch_object_feed_uses_camera_side_acc_can(): def test_civic_bosch_object_feed_uses_camera_side_acc_can():
ri = make_radar_interface() ri = make_radar_interface()
can = CanBus(CP) can = CanBus(CP)
@@ -1180,5 +1188,6 @@ def test_bosch_a_toggle_defaults_on_but_allowlist_still_gates_platforms():
Params().remove("HondaBoschARadar") Params().remove("HondaBoschARadar")
assert CarInterface.get_non_essential_params(CAR.HONDA_CIVIC_BOSCH).radarUnavailable is False assert CarInterface.get_non_essential_params(CAR.HONDA_CIVIC_BOSCH).radarUnavailable is False
assert CarInterface.get_non_essential_params(CAR.HONDA_ACCORD).radarUnavailable is True assert CarInterface.get_non_essential_params(CAR.HONDA_ACCORD).radarUnavailable is True
assert CarInterface.get_non_essential_params(CAR.HONDA_ACCORD_11G).radarUnavailable is True
finally: finally:
Params().put_bool("HondaBoschARadar", original) Params().put_bool("HondaBoschARadar", original)
-3
View File
@@ -533,9 +533,6 @@ HONDA_BOSCH_ALT_RADAR = CAR.with_flags(HondaFlags.BOSCH_ALT_RADAR)
# HondaBoschARadar. This describes hardware compatibility only; it is deliberately separate from the # HondaBoschARadar. This describes hardware compatibility only; it is deliberately separate from the
# verified set below so a newly supported model cannot start using unvalidated radar data by accident. # verified set below so a newly supported model cannot start using unvalidated radar data by accident.
HONDA_BOSCH_A = HONDA_BOSCH - HONDA_BOSCH_RADARLESS - HONDA_BOSCH_CANFD - HONDA_BOSCH_ALT_RADAR HONDA_BOSCH_A = HONDA_BOSCH - HONDA_BOSCH_RADARLESS - HONDA_BOSCH_CANFD - HONDA_BOSCH_ALT_RADAR
# Add individual CAR entries only after the exact platform has a real capture and decoder replay
# validation. The Civic and CR-V 5G captures both exercise the plain Bosch-A object bank; every
# other Bosch-A variant remains disabled until it gets the same verification.
HONDA_BOSCH_A_RADAR_VERIFIED = frozenset({CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CRV_5G}) HONDA_BOSCH_A_RADAR_VERIFIED = frozenset({CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CRV_5G})
HONDA_BOSCH_TJA_CONTROL = CAR.with_flags(HondaFlags.BOSCH_TJA_CONTROL) HONDA_BOSCH_TJA_CONTROL = CAR.with_flags(HondaFlags.BOSCH_TJA_CONTROL)
HONDA_CAMERA_MESSAGE_CARS = { HONDA_CAMERA_MESSAGE_CARS = {
@@ -1,5 +1,7 @@
from dataclasses import dataclass from dataclasses import dataclass
# Provenance: portions of HKG angle control are adapted from sunnypilot/opendbc's
# hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md and THIRD_PARTY_NOTICES.md.
import numpy as np import numpy as np
from opendbc.can import CANPacker from opendbc.can import CANPacker
from opendbc.car import Bus, DT_CTRL, make_tester_present_msg, rate_limit, structs from opendbc.car import Bus, DT_CTRL, make_tester_present_msg, rate_limit, structs
@@ -8,8 +10,8 @@ from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_steer_an
from opendbc.car.common.conversions import Conversions as CV from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.hyundai import hyundaicanfd, hyundaican from opendbc.car.hyundai import hyundaicanfd, hyundaican
from opendbc.car.hyundai.hyundaicanfd import CanBus from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \ from opendbc.car.hyundai.values import HyundaiFlags, HyundaiSafetyFlags, HyundaiStarPilotFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, CANFD_ALT_BUTTONS_RESUME_CAR, kia_ev6_gt_line_longitudinal_tuning, \ CANFD_RADAR_ECU_KEEPALIVE_CAR, CANFD_ALT_BUTTONS_RESUME_CAR, kia_ev6_gt_line_longitudinal_tuning, \
KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID
from opendbc.car.interfaces import CarControllerBase from opendbc.car.interfaces import CarControllerBase
from opendbc.car.vehicle_model import VehicleModel from opendbc.car.vehicle_model import VehicleModel
@@ -24,6 +26,9 @@ LongCtrlState = structs.CarControl.Actuators.LongControlState
MAX_ANGLE = 85 MAX_ANGLE = 85
MAX_ANGLE_FRAMES = 89 MAX_ANGLE_FRAMES = 89
MAX_ANGLE_CONSECUTIVE_FRAMES = 2 MAX_ANGLE_CONSECUTIVE_FRAMES = 2
CANCEL_BUTTON_DELAY_FRAMES = 10
CANFD_BLINDSPOT_STATUS_STALE_NS = 200_000_000 CANFD_BLINDSPOT_STATUS_STALE_NS = 200_000_000
CANFD_CAMERA_LEAD_STALE_NS = 300_000_000 CANFD_CAMERA_LEAD_STALE_NS = 300_000_000
CANFD_LEAD_MIN_DISTANCE = 0.1 CANFD_LEAD_MIN_DISTANCE = 0.1
@@ -180,13 +185,6 @@ def should_use_ev6_gt_line_stop_direct_tracking(ev6_gt_line: bool, stopping: boo
return bool(ev6_gt_line and stopping and v_ego > EV6_GT_LINE_STOP_BRAKE_CAP_MAX_SPEED and accel_cmd < actual_accel) return bool(ev6_gt_line and stopping and v_ego > EV6_GT_LINE_STOP_BRAKE_CAP_MAX_SPEED and accel_cmd < actual_accel)
def apply_carnival_steering_override(car_fingerprint, steering_pressed: bool,
apply_steer_req: bool, apply_torque: int) -> tuple[bool, int]:
if car_fingerprint == CAR.KIA_CARNIVAL_2025 and steering_pressed:
return False, 0
return apply_steer_req, apply_torque
def update_ev9_longitudinal_tuning(state: EV9LongitudinalTuningState, enabled: bool, def update_ev9_longitudinal_tuning(state: EV9LongitudinalTuningState, enabled: bool,
stopping: bool, v_ego: float) -> EV9LongitudinalTuningState: stopping: bool, v_ego: float) -> EV9LongitudinalTuningState:
if not enabled: if not enabled:
@@ -438,6 +436,12 @@ def suppress_redundant_gv70_brake_cancel(CP, brake_pressed: bool, lat_active: bo
) )
def clear_ioniq_6_torque_when_request_inactive(CP, apply_torque: int, apply_steer_req: bool) -> int:
if CP.carFingerprint == CAR.HYUNDAI_IONIQ_6 and not apply_steer_req:
return 0
return apply_torque
class CarController(CarControllerBase): class CarController(CarControllerBase):
def __init__(self, dbc_names, CP): def __init__(self, dbc_names, CP):
super().__init__(dbc_names, CP) super().__init__(dbc_names, CP)
@@ -455,6 +459,7 @@ class CarController(CarControllerBase):
self.apply_angle_last = 0.0 self.apply_angle_last = 0.0
self.car_fingerprint = CP.carFingerprint self.car_fingerprint = CP.carFingerprint
self.last_button_frame = 0 self.last_button_frame = 0
self.cancel_counter = 0
self.redneck_button_frame = 0 self.redneck_button_frame = 0
self.ecu_disable_failed = False self.ecu_disable_failed = False
self._ecu_disable_checked = False self._ecu_disable_checked = False
@@ -471,6 +476,12 @@ class CarController(CarControllerBase):
self._dash_lat_disengage_blink_frame = 0 self._dash_lat_disengage_blink_frame = 0
self._dash_lat_disengage_init = False self._dash_lat_disengage_init = False
self._dash_prev_lat_active = False self._dash_prev_lat_active = False
self._ray_lkas11_active = False
self._ray_lfa_8byte = CP.carFingerprint == CAR.KIA_RAY_EV and bool(
getattr(CP, "safetyConfigs", None) and
CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CAN_REFRESH_MSGS
)
self._ray_lfa_packer = CANPacker("hyundai_kia_ray_lfa") if self._ray_lfa_8byte else None
def _update_dash_icon_state(self, CC): def _update_dash_icon_state(self, CC):
if CC.latActive: if CC.latActive:
@@ -620,9 +631,7 @@ class CarController(CarControllerBase):
if not CC.latActive: if not CC.latActive:
apply_torque = 0 apply_torque = 0
apply_steer_req, apply_torque = apply_carnival_steering_override( apply_torque = clear_ioniq_6_torque_when_request_inactive(self.CP, apply_torque, apply_steer_req)
self.CP.carFingerprint, CS.out.steeringPressed, apply_steer_req, apply_torque,
)
# Hold torque with induced temporary fault when cutting the actuation bit # Hold torque with induced temporary fault when cutting the actuation bit
# FIXME: we don't use this with CAN FD? # FIXME: we don't use this with CAN FD?
@@ -719,6 +728,8 @@ class CarController(CarControllerBase):
if self.CP.flags & HyundaiFlags.ENABLE_BLINKERS: if self.CP.flags & HyundaiFlags.ENABLE_BLINKERS:
can_sends.append(make_tester_present_msg(0x7b1, self.CAN.ECAN, suppress_response=True)) can_sends.append(make_tester_present_msg(0x7b1, self.CAN.ECAN, suppress_response=True))
self.cancel_counter = self.cancel_counter + 1 if CC.cruiseControl.cancel else 0
# *** CAN/CAN FD specific *** # *** CAN/CAN FD specific ***
if self.CP.flags & HyundaiFlags.CANFD: if self.CP.flags & HyundaiFlags.CANFD:
can_sends.extend(self.create_canfd_msgs(now_nanos, apply_steer_req, apply_torque, apply_angle, set_speed_in_units, accel, can_sends.extend(self.create_canfd_msgs(now_nanos, apply_steer_req, apply_torque, apply_angle, set_speed_in_units, accel,
@@ -744,6 +755,7 @@ class CarController(CarControllerBase):
can_sends = [] can_sends = []
can_canfd_blended = bool(self.CP.flags & HyundaiFlags.CAN_CANFD_BLENDED) can_canfd_blended = bool(self.CP.flags & HyundaiFlags.CAN_CANFD_BLENDED)
blended_hda2 = can_canfd_blended and bool(self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING) blended_hda2 = can_canfd_blended and bool(self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING)
longitudinal_active = bool(self.long_active_ecu and getattr(CC, "longActive", False))
# HUD messages # HUD messages
sys_warning, sys_state, left_lane_warning, right_lane_warning = process_hud_alert(CC.enabled, self.car_fingerprint, sys_warning, sys_state, left_lane_warning, right_lane_warning = process_hud_alert(CC.enabled, self.car_fingerprint,
@@ -752,6 +764,7 @@ class CarController(CarControllerBase):
if blended_hda2: if blended_hda2:
can_sends.extend(hyundaicanfd.create_steering_messages( can_sends.extend(hyundaicanfd.create_steering_messages(
self.packer, self.CP, self.CAN, CC.enabled, apply_steer_req, apply_torque, 0.0, self.packer, self.CP, self.CAN, CC.enabled, apply_steer_req, apply_torque, 0.0,
longitudinal_active=longitudinal_active,
)) ))
if self.long_active_ecu: if self.long_active_ecu:
can_sends.extend(hyundaican.create_lkas11_can_canfd_blended( can_sends.extend(hyundaican.create_lkas11_can_canfd_blended(
@@ -772,14 +785,19 @@ class CarController(CarControllerBase):
hud_control.leftLaneVisible, hud_control.rightLaneVisible, hud_control.leftLaneVisible, hud_control.rightLaneVisible,
left_lane_warning, right_lane_warning, CS.msg_364)) left_lane_warning, right_lane_warning, CS.msg_364))
else: else:
can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.CP, apply_torque, apply_steer_req, if self.CP.carFingerprint != CAR.KIA_RAY_EV or self._ray_lkas11_active:
torque_fault, CS.lkas11, sys_warning, sys_state, CC.enabled, can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.CP, apply_torque, apply_steer_req,
hud_control.leftLaneVisible, hud_control.rightLaneVisible, torque_fault, CS.lkas11, sys_warning, sys_state, CC.enabled,
left_lane_warning, right_lane_warning, lka_icon)) hud_control.leftLaneVisible, hud_control.rightLaneVisible,
left_lane_warning, right_lane_warning, lka_icon))
if self.CP.carFingerprint == CAR.KIA_RAY_EV:
self._ray_lkas11_active = True
if getattr(self.FPCP, "flags", 0) & HyundaiStarPilotFlags.HAS_LKAS12:
can_sends.append(hyundaican.create_lkas12(self.packer, CS.lkas12))
# Button messages # Button messages
if not self.long_active_ecu: if not self.long_active_ecu:
if CC.cruiseControl.cancel: if self.cancel_counter > CANCEL_BUTTON_DELAY_FRAMES:
can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP)) can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP))
elif CC.cruiseControl.resume: elif CC.cruiseControl.resume:
# send resume at a max freq of 10Hz # send resume at a max freq of 10Hz
@@ -821,7 +839,10 @@ class CarController(CarControllerBase):
# 20 Hz LFA MFA message # 20 Hz LFA MFA message
if self.frame % 5 == 0 and (self.CP.flags & HyundaiFlags.SEND_LFA.value or (self.long_active_ecu and blended_hda2)): if self.frame % 5 == 0 and (self.CP.flags & HyundaiFlags.SEND_LFA.value or (self.long_active_ecu and blended_hda2)):
can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled, self.frame, self.CP, lfa_icon)) if self._ray_lfa_8byte:
can_sends.append(hyundaican.create_ray_lfahda_mfc(self._ray_lfa_packer, CC.latActive, lfa_icon))
else:
can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled, self.frame, self.CP, lfa_icon))
# 5 Hz ACC options # 5 Hz ACC options
if self.frame % 20 == 0 and self.long_active_ecu and not can_canfd_blended: if self.frame % 20 == 0 and self.long_active_ecu and not can_canfd_blended:
@@ -838,7 +859,15 @@ class CarController(CarControllerBase):
can_sends = [] can_sends = []
lka_steering = self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING lka_steering = self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING
lka_steering_long = lka_steering and self.long_active_ecu longitudinal_active = bool(self.long_active_ecu and getattr(CC, "longActive", False))
lfa_status_cars = (
CAR.HYUNDAI_IONIQ_6,
CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN,
CAR.KIA_EV6,
)
lfa_longitudinal_active = self.CP.openpilotLongitudinalControl \
if self.CP.carFingerprint in lfa_status_cars else longitudinal_active
lka_steering_long = lka_steering and lfa_longitudinal_active
ccnc_non_hda2 = self.CP.flags & HyundaiFlags.CCNC and not lka_steering ccnc_non_hda2 = self.CP.flags & HyundaiFlags.CCNC and not lka_steering
use_egmp_dynamic_long_tuning = egmp_dynamic_longitudinal_tuning(self.CP) and self.long_active_ecu and \ use_egmp_dynamic_long_tuning = egmp_dynamic_longitudinal_tuning(self.CP) and self.long_active_ecu and \
CC.actuators.longControlState in (LongCtrlState.starting, LongCtrlState.pid, LongCtrlState.stopping) CC.actuators.longControlState in (LongCtrlState.starting, LongCtrlState.pid, LongCtrlState.stopping)
@@ -849,9 +878,6 @@ class CarController(CarControllerBase):
) )
# steering control # steering control
# The first-generation Electrified GV70 expects the synthesized LKAS status
# payload. Forwarding its stock status bits leaves lane-safety state asserted
# while StarPilot is suppressing the stock LFA path.
preserve_stock_lkas = bool(self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING) and \ preserve_stock_lkas = bool(self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING) and \
not self.long_active_ecu and self.CP.carFingerprint != CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN and \ not self.long_active_ecu and self.CP.carFingerprint != CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN and \
preserve_stock_canfd_lkas_status(self.CP.carFingerprint) preserve_stock_canfd_lkas_status(self.CP.carFingerprint)
@@ -870,7 +896,7 @@ class CarController(CarControllerBase):
if angle_lkas_alt: if angle_lkas_alt:
steering_msg_active = bool(steering_msg_active and drive_gear) steering_msg_active = bool(steering_msg_active and drive_gear)
angle_lkas_alt_standstill_handoff = bool(getattr(CS.out, "standstill", False) and not CC.latActive) angle_lkas_alt_standstill_handoff = bool(getattr(CS.out, "standstill", False) and not CC.latActive)
forward_stock_lkas = angle_lkas_alt and ( forward_stock_lkas = self.CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR and angle_lkas_alt and (
angle_lkas_alt_standstill_handoff or not (drive_gear and (CC.latActive or CC.enabled)) angle_lkas_alt_standstill_handoff or not (drive_gear and (CC.latActive or CC.enabled))
) )
preserve_stock_lfa_status = preserve_stock_canfd_lfa_status(self.CP.carFingerprint) preserve_stock_lfa_status = preserve_stock_canfd_lfa_status(self.CP.carFingerprint)
@@ -879,7 +905,8 @@ class CarController(CarControllerBase):
steering_msg_active, apply_torque, apply_angle, steering_msg_active, apply_torque, apply_angle,
CS.stock_lfa_msg if preserve_stock_lfa_status else None, CS.stock_lfa_msg if preserve_stock_lfa_status else None,
CS.stock_lkas_msg if preserve_stock_lkas else None, CS.stock_lkas_msg if preserve_stock_lkas else None,
lka_icon=lka_icon)) lka_icon=lka_icon,
longitudinal_active=lfa_longitudinal_active))
direct_steering_active = ccnc_angle_long and drive_gear and CC.latActive and self.direct_angle_request_allowed and not CS.angle_steering_fault direct_steering_active = ccnc_angle_long and drive_gear and CC.latActive and self.direct_angle_request_allowed and not CS.angle_steering_fault
inactive_steering_angle = float(np.clip(CS.angle_steering_angle, inactive_steering_angle = float(np.clip(CS.angle_steering_angle,
-self.params.ANGLE_LIMITS.STEER_ANGLE_MAX, -self.params.ANGLE_LIMITS.STEER_ANGLE_MAX,
@@ -971,7 +998,7 @@ class CarController(CarControllerBase):
# The front radar treats ADAS_DRV's 0x100 broadcast as its host heartbeat # The front radar treats ADAS_DRV's 0x100 broadcast as its host heartbeat
# and stops publishing object tracks when it disappears. # and stops publishing object tracks when it disappears.
radar_heartbeat_step = 1 if ccnc_angle_long else 4 radar_heartbeat_step = 1 if ccnc_angle_long else 4
if self.CP.carFingerprint in CANFD_RADAR_LIVE_LONGITUDINAL_CAR and self.frame % radar_heartbeat_step == 0: if self.CP.carFingerprint in CANFD_RADAR_ECU_KEEPALIVE_CAR and self.frame % radar_heartbeat_step == 0:
can_sends.append(hyundaicanfd.create_accelerator_brake_alt_spoof(0, self.frame // radar_heartbeat_step, can_sends.append(hyundaicanfd.create_accelerator_brake_alt_spoof(0, self.frame // radar_heartbeat_step,
CS.out.brakePressed, CS.out.gasPressed, CS.out.brakePressed, CS.out.gasPressed,
self.CP.carFingerprint)) self.CP.carFingerprint))
@@ -1046,7 +1073,7 @@ class CarController(CarControllerBase):
if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS: if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, self.CAN, CS.cruise_info)) can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, self.CAN, CS.cruise_info))
self.last_button_frame = self.frame self.last_button_frame = self.frame
else: elif self.cancel_counter > CANCEL_BUTTON_DELAY_FRAMES:
for _ in range(20): for _ in range(20):
can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, CS.buttons_counter + 1, Buttons.CANCEL)) can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, CS.buttons_counter + 1, Buttons.CANCEL))
self.last_button_frame = self.frame self.last_button_frame = self.frame
+46 -10
View File
@@ -2,6 +2,8 @@ from collections import deque
import copy import copy
import math import math
# Provenance: portions of HKG angle-state integration are adapted from sunnypilot/opendbc's
# hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md and THIRD_PARTY_NOTICES.md.
from cereal import custom from cereal import custom
from opendbc.can import CANDefine, CANParser from opendbc.can import CANDefine, CANParser
from opendbc.car import Bus, create_button_events, structs from opendbc.car import Bus, create_button_events, structs
@@ -36,6 +38,8 @@ CLASSIC_MEDIA_BUTTON_CARS = frozenset({
def get_non_scc_cruise_signals(CP) -> tuple[str, str, str, str, str, str]: def get_non_scc_cruise_signals(CP) -> tuple[str, str, str, str, str, str]:
if CP.carFingerprint == CAR.KIA_RAY_EV:
return "LABEL11", "CC_React", "LABEL11", "CC_Engaged", "E_EMS11", "Cruise_Limit_Target"
if CP.flags & HyundaiFlags.EV: if CP.flags & HyundaiFlags.EV:
return "LABEL11", "CC_React", "EMS12", "ACC_ACT", "E_EMS11", "Cruise_Limit_Target" return "LABEL11", "CC_React", "EMS12", "ACC_ACT", "E_EMS11", "Cruise_Limit_Target"
if CP.flags & HyundaiFlags.HYBRID: if CP.flags & HyundaiFlags.HYBRID:
@@ -142,6 +146,7 @@ class CarState(CarStateBase):
self.msg_364 = {} self.msg_364 = {}
self.lfa_block_msg = {} self.lfa_block_msg = {}
self.stock_lkas_msg = {} self.stock_lkas_msg = {}
self.lkas12 = {}
self.stock_lfa_msg = {} self.stock_lfa_msg = {}
self.stock_lfahda_cluster_msg = {} self.stock_lfahda_cluster_msg = {}
self.stock_camera_lead_visible = False self.stock_camera_lead_visible = False
@@ -173,8 +178,10 @@ class CarState(CarStateBase):
# Main button also can trigger an engagement on these cars # Main button also can trigger an engagement on these cars
return any(btn in ENABLE_BUTTONS for btn in self.cruise_buttons) or any(self.main_buttons) return any(btn in ENABLE_BUTTONS for btn in self.cruise_buttons) or any(self.main_buttons)
def update_main_cruise(self, ret: structs.CarState) -> bool: def update_main_cruise(self, ret: structs.CarState,
if any(be.type == ButtonType.mainCruise and be.pressed for be in ret.buttonEvents): button_events: list[structs.CarState.ButtonEvent] | None = None) -> bool:
button_events = ret.buttonEvents if button_events is None else button_events
if any(be.type == ButtonType.mainCruise and be.pressed for be in button_events):
self.main_cruise_on = not self.main_cruise_on self.main_cruise_on = not self.main_cruise_on
return bool(ret.cruiseState.available and self.main_cruise_on) return bool(ret.cruiseState.available and self.main_cruise_on)
@@ -240,10 +247,20 @@ class CarState(CarStateBase):
return button_events return button_events
def create_lkas_button_events(self, cp: CANParser, prev_lda_button: int) -> list[structs.CarState.ButtonEvent]: def create_lkas_button_events(self, cp: CANParser, prev_lda_button: int) -> list[structs.CarState.ButtonEvent]:
if self.CP.carFingerprint == CAR.HYUNDAI_SONATA: if self.CP.carFingerprint == CAR.KIA_RAY_EV:
self.lda_button = int(cp.vl["BCM_PO_11"]["RAY_LKAS_BTN"] != 0) \
if cp.ts_nanos["BCM_PO_11"]["RAY_LKAS_BTN"] > 0 else 0
elif self.CP.carFingerprint == CAR.HYUNDAI_SONATA:
self.lda_button = int(cp.vl["BCM_PO_11"]["LDA_BTN"]) if cp.ts_nanos["BCM_PO_11"]["LDA_BTN"] > 0 else 0 self.lda_button = int(cp.vl["BCM_PO_11"]["LDA_BTN"]) if cp.ts_nanos["BCM_PO_11"]["LDA_BTN"] > 0 else 0
elif self.CP.carFingerprint == CAR.HYUNDAI_SONATA_HYBRID: elif self.CP.carFingerprint == CAR.HYUNDAI_SONATA_HYBRID:
self.lda_button = self.get_sonata_hybrid_lkas_button_state(cp) self.lda_button = self.get_sonata_hybrid_lkas_button_state(cp)
elif self.CP.carFingerprint == CAR.HYUNDAI_ELANTRA_HEV_2024:
lda_samples = [
*cp.vl_all["CLU13"]["CF_Clu_LdwsLkasSW"],
*cp.vl_all["BCM_PO_11"]["LDA_BTN"],
]
if lda_samples:
self.lda_button = int(any(lda_samples))
else: else:
source_states = ( source_states = (
int(cp.vl["CLU13"]["CF_Clu_LdwsLkasSW"]) if cp.ts_nanos["CLU13"]["CF_Clu_LdwsLkasSW"] > 0 else 0, int(cp.vl["CLU13"]["CF_Clu_LdwsLkasSW"]) if cp.ts_nanos["CLU13"]["CF_Clu_LdwsLkasSW"] > 0 else 0,
@@ -327,6 +344,15 @@ class CarState(CarStateBase):
ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > self.params.STEER_THRESHOLD, 5) ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > self.params.STEER_THRESHOLD, 5)
ret.steerFaultTemporary = cp.vl["MDPS12"]["CF_Mdps_ToiUnavail"] != 0 or cp.vl["MDPS12"]["CF_Mdps_ToiFlt"] != 0 ret.steerFaultTemporary = cp.vl["MDPS12"]["CF_Mdps_ToiUnavail"] != 0 or cp.vl["MDPS12"]["CF_Mdps_ToiFlt"] != 0
prev_cruise_buttons = self.cruise_buttons[-1]
prev_main_buttons = self.main_buttons[-1]
prev_lda_button = self.lda_button
main_button_events = []
if self.main_cruise_tracking:
self.cruise_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwState"])
self.main_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwMain"])
main_button_events = create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise})
# cruise state # cruise state
no_scc = bool(self.CP.flags & HyundaiFlags.NON_SCC) no_scc = bool(self.CP.flags & HyundaiFlags.NON_SCC)
if no_scc: if no_scc:
@@ -350,6 +376,9 @@ class CarState(CarStateBase):
ret.cruiseState.nonAdaptive = cp_cruise.vl[scc_msg]["SCCInfoDisplay"] == 2. # Shows 'Cruise Control' on dash ret.cruiseState.nonAdaptive = cp_cruise.vl[scc_msg]["SCCInfoDisplay"] == 2. # Shows 'Cruise Control' on dash
ret.cruiseState.speed = cp_cruise.vl[scc_msg]["VSetDis"] * speed_conv ret.cruiseState.speed = cp_cruise.vl[scc_msg]["VSetDis"] * speed_conv
if self.CP.openpilotLongitudinalControl and self.main_cruise_tracking:
ret.cruiseState.available = self.update_main_cruise(ret, main_button_events)
if self.CP.flags & HyundaiFlags.CAN_CANFD_BLENDED: if self.CP.flags & HyundaiFlags.CAN_CANFD_BLENDED:
if self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING: if self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING:
self.lfa_block_msg = copy.copy(cp_cam.vl["CAM_0x2a4"]) self.lfa_block_msg = copy.copy(cp_cam.vl["CAM_0x2a4"])
@@ -417,21 +446,22 @@ class CarState(CarStateBase):
self.lkas11 = {} self.lkas11 = {}
else: else:
self.lkas11 = copy.copy(cp_cam.vl["LKAS11"]) self.lkas11 = copy.copy(cp_cam.vl["LKAS11"])
if getattr(self.FPCP, "flags", 0) & HyundaiStarPilotFlags.HAS_LKAS12:
self.lkas12 = copy.copy(cp_cam.vl["LKAS12"])
self.clu11 = copy.copy(cp.vl["CLU11"]) self.clu11 = copy.copy(cp.vl["CLU11"])
self.steer_state = cp.vl["MDPS12"]["CF_Mdps_ToiActive"] # 0 NOT ACTIVE, 1 ACTIVE self.steer_state = cp.vl["MDPS12"]["CF_Mdps_ToiActive"] # 0 NOT ACTIVE, 1 ACTIVE
prev_cruise_buttons = self.cruise_buttons[-1] if not self.main_cruise_tracking:
prev_main_buttons = self.main_buttons[-1] self.cruise_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwState"])
prev_lda_button = self.lda_button self.main_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwMain"])
main_button_events = create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise})
lkas_button_events = [] lkas_button_events = []
self.cruise_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwState"])
self.main_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwMain"])
if self.CP.carFingerprint in ALT_BUS_LDA_BUTTON_CARS and cp_alt is not None and self.get_alt_bus_lda_button_raw_state(cp_alt)[1] > 0: if self.CP.carFingerprint in ALT_BUS_LDA_BUTTON_CARS and cp_alt is not None and self.get_alt_bus_lda_button_raw_state(cp_alt)[1] > 0:
lkas_button_events = self.create_alt_bus_lda_button_events(cp_alt) lkas_button_events = self.create_alt_bus_lda_button_events(cp_alt)
else: else:
lkas_button_events = self.create_lkas_button_events(cp, prev_lda_button) lkas_button_events = self.create_lkas_button_events(cp, prev_lda_button)
ret.buttonEvents = [*self.create_cruise_button_events(self.cruise_buttons[-1], prev_cruise_buttons), ret.buttonEvents = [*self.create_cruise_button_events(self.cruise_buttons[-1], prev_cruise_buttons),
*create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise}), *main_button_events,
*lkas_button_events] *lkas_button_events]
ret.blockPcmEnable = not self.recent_button_interaction() ret.blockPcmEnable = not self.recent_button_interaction()
@@ -700,6 +730,12 @@ class CarState(CarStateBase):
("BCM_PO_11", 0), ("BCM_PO_11", 0),
("CLU13", 0), ("CLU13", 0),
] ]
if CP.carFingerprint == CAR.KIA_RAY_EV:
msgs += [
("LABEL11", 10),
("E_EMS11", 100),
("ELECT_GEAR", 100),
]
if CP.carFingerprint in CLASSIC_MEDIA_BUTTON_CARS: if CP.carFingerprint in CLASSIC_MEDIA_BUTTON_CARS:
# Steering-wheel media switches are event-driven on the refresh Elantra. # Steering-wheel media switches are event-driven on the refresh Elantra.
msgs.append(("GW_SWRC_PE", 0)) msgs.append(("GW_SWRC_PE", 0))
@@ -708,7 +744,7 @@ class CarState(CarStateBase):
parsers = { parsers = {
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], msgs, 0), Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], msgs, 0),
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 2), Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS12", 0)], 2),
} }
if CP.carFingerprint in ALT_BUS_LDA_BUTTON_CARS: if CP.carFingerprint in ALT_BUS_LDA_BUTTON_CARS:
parsers[Bus.alt] = CANParser(DBC[CP.carFingerprint][Bus.pt], [("CLU13", 0)], 1) parsers[Bus.alt] = CANParser(DBC[CP.carFingerprint][Bus.pt], [("CLU13", 0)], 1)
@@ -1,4 +1,6 @@
""" AUTO-FORMATTED USING opendbc/car/debug/format_fingerprints.py, EDIT STRUCTURE THERE.""" """ AUTO-FORMATTED USING opendbc/car/debug/format_fingerprints.py, EDIT STRUCTURE THERE."""
# Provenance: portions of HKG firmware data are adapted from sunnypilot/opendbc master at
# f95f996f5 and its hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md.
from opendbc.car.structs import CarParams from opendbc.car.structs import CarParams
from opendbc.car.hyundai.values import CAR from opendbc.car.hyundai.values import CAR
@@ -1696,4 +1698,9 @@ FW_VERSIONS = {
b'\xf1\x00BC3 LKA AT EUR LHD 1.00 1.01 99211-Q0100 261', b'\xf1\x00BC3 LKA AT EUR LHD 1.00 1.01 99211-Q0100 261',
], ],
}, },
CAR.KIA_RAY_EV: {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00TAM MFC AT KOR LHD 1.00 1.02 99211-E2000 230901',
],
},
} }
+35 -3
View File
@@ -40,7 +40,7 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
CAR.HYUNDAI_ELANTRA_HEV_2021, CAR.HYUNDAI_SONATA_HYBRID, CAR.HYUNDAI_KONA_EV, CAR.HYUNDAI_KONA_HEV, CAR.HYUNDAI_KONA_EV_2022, CAR.HYUNDAI_ELANTRA_HEV_2021, CAR.HYUNDAI_SONATA_HYBRID, CAR.HYUNDAI_KONA_EV, CAR.HYUNDAI_KONA_HEV, CAR.HYUNDAI_KONA_EV_2022,
CAR.HYUNDAI_SANTA_FE_2022, CAR.KIA_K5_2021, CAR.HYUNDAI_IONIQ_HEV_2022, CAR.HYUNDAI_SANTA_FE_HEV_2022, CAR.HYUNDAI_SANTA_FE_2022, CAR.KIA_K5_2021, CAR.HYUNDAI_IONIQ_HEV_2022, CAR.HYUNDAI_SANTA_FE_HEV_2022,
CAR.HYUNDAI_SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020, CAR.KIA_CEED, CAR.KIA_XCEED_PHEV, CAR.HYUNDAI_SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020, CAR.KIA_CEED, CAR.KIA_XCEED_PHEV,
CAR.HYUNDAI_AZERA_6TH_GEN, CAR.HYUNDAI_AZERA_HEV_6TH_GEN, CAR.HYUNDAI_CUSTIN_1ST_GEN, CAR.HYUNDAI_KONA_2022, CAR.HYUNDAI_AZERA_6TH_GEN, CAR.HYUNDAI_AZERA_HEV_6TH_GEN, CAR.HYUNDAI_CUSTIN_1ST_GEN, CAR.HYUNDAI_KONA_2022, CAR.KIA_RAY_EV,
CAR.HYUNDAI_ELANTRA_2024, CAR.HYUNDAI_ELANTRA_HEV_2024): CAR.HYUNDAI_ELANTRA_2024, CAR.HYUNDAI_ELANTRA_HEV_2024):
values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1) values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1)
values["CF_Lkas_LdwsOpt_USM"] = 2 values["CF_Lkas_LdwsOpt_USM"] = 2
@@ -60,7 +60,7 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0 values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0
# Likely cars lacking the ability to show individual lane lines in the dash # Likely cars lacking the ability to show individual lane lines in the dash
elif CP.carFingerprint in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL): elif CP.carFingerprint in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.HYUNDAI_KONA_NON_SCC):
# SysWarning 4 = keep hands on wheel + beep # SysWarning 4 = keep hands on wheel + beep
values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0 values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0
@@ -68,7 +68,7 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
# SysState 1-2 = white car + lanes # SysState 1-2 = white car + lanes
# SysState 3 = green car + lanes, green steering wheel # SysState 3 = green car + lanes, green steering wheel
# SysState 4 = green car + lanes # SysState 4 = green car + lanes
values["CF_Lkas_LdwsSysState"] = 3 if enabled else 1 values["CF_Lkas_LdwsSysState"] = lka_icon if CP.carFingerprint == CAR.HYUNDAI_KONA_NON_SCC else 3 if enabled else 1
values["CF_Lkas_LdwsOpt_USM"] = 2 # non-2 changes above SysState definition values["CF_Lkas_LdwsOpt_USM"] = 2 # non-2 changes above SysState definition
# these have no effect # these have no effect
@@ -80,6 +80,14 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
# Genesis and Optima fault when forwarding while engaged # Genesis and Optima fault when forwarding while engaged
values["CF_Lkas_LdwsActivemode"] = 2 values["CF_Lkas_LdwsActivemode"] = 2
if CP.carFingerprint == CAR.KIA_RAY_EV:
if not enabled:
values["CF_Lkas_LdwsActivemode"] = lkas11["CF_Lkas_LdwsActivemode"]
values["CF_Lkas_LdwsSysState"] = lkas11["CF_Lkas_LdwsSysState"]
values["CF_Lkas_FcwOpt_USM"] = lkas11["CF_Lkas_FcwOpt_USM"]
values["CF_Lkas_LdwsOpt_USM"] = 0
values["CF_Lkas_Chksum"] = 0
dat = packer.make_can_msg("LKAS11", 0, values)[1] dat = packer.make_can_msg("LKAS11", 0, values)[1]
if CP.flags & HyundaiFlags.CHECKSUM_CRC8: if CP.flags & HyundaiFlags.CHECKSUM_CRC8:
@@ -98,6 +106,19 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
return packer.make_can_msg("LKAS11", 0, values) return packer.make_can_msg("LKAS11", 0, values)
def create_lkas12(packer, lkas12):
values = {s: lkas12[s] for s in (
"CF_Lkas_TsrSlifOpt",
"CF_LkasTsrStatus",
"CF_Lkas_TsrSpeed_Display_Clu",
"CF_LkasTsrSpeed_Display_Navi",
"CF_Lkas_TsrAddinfo_Display",
"CF_Lkas_Daw_USM",
) if s in lkas12}
values["CF_LkasDawStatus"] = 0
return packer.make_can_msg("LKAS12", 0, values)
def create_checksum_can_canfd_blended(packer, bus, addr, values): def create_checksum_can_canfd_blended(packer, bus, addr, values):
dat = packer.make_can_msg(addr, bus, values)[1] dat = packer.make_can_msg(addr, bus, values)[1]
return hyundai_checksum(dat[1:8]) return hyundai_checksum(dat[1:8])
@@ -182,6 +203,17 @@ def create_lfahda_mfc(packer, enabled, frame=None, CP=None, lfa_icon=None):
return packer.make_can_msg("LFAHDA_MFC", bus, values) return packer.make_can_msg("LFAHDA_MFC", bus, values)
def create_ray_lfahda_mfc(packer, lat_active, lfa_icon):
values = {
"HDA_USM": 2,
"HDA_Icon_State": 2 if lfa_icon else 0,
"HDA_VSetReq": 0,
"HDA_Icon_Wheel": int(lat_active),
"LFA_Icon_State": lfa_icon,
}
return packer.make_can_msg("LFAHDA_MFC", 0, values)
def create_acc_commands_can_canfd_blended(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed, def create_acc_commands_can_canfd_blended(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed,
stopping, long_override, use_fca, CP): stopping, long_override, use_fca, CP):
commands = [] commands = []
@@ -1,4 +1,6 @@
import copy import copy
# Provenance: portions of HKG angle-command construction are adapted from sunnypilot/opendbc's
# hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md and THIRD_PARTY_NOTICES.md.
import numpy as np import numpy as np
from opendbc.car import CanBusBase, CanData from opendbc.car import CanBusBase, CanData
from opendbc.car.common.conversions import Conversions as CV from opendbc.car.common.conversions import Conversions as CV
@@ -63,61 +65,6 @@ def _update_checksum(packer, address: int, dat: bytearray) -> None:
_set_value(dat, sig_checksum, checksum) _set_value(dat, sig_checksum, checksum)
def _set_little_endian_bits(dat: bytearray, lsb: int, size: int, value: int) -> None:
"""Write the legacy HDA-II field layout without changing the generated DBC aliases."""
value &= (1 << size) - 1
bit = lsb
remaining = size
while remaining:
byte = bit // 8
shift = bit % 8
chunk_size = min(remaining, 8 - shift)
mask = ((1 << chunk_size) - 1) << shift
dat[byte] = (dat[byte] & ~mask) | ((value & ((1 << chunk_size) - 1)) << shift)
value >>= chunk_size
bit += chunk_size
remaining -= chunk_size
def _create_gv70_lka_status_msg(packer, CAN, message_name: str, bus: int, enabled: bool,
lat_active: bool, apply_torque: int):
values = {
"LKA_MODE": 2,
"LKA_ICON": 2 if enabled else 1,
"TORQUE_REQUEST": apply_torque,
"STEER_REQ": 1 if lat_active else 0,
"LKA_ASSIST": 0,
"STEER_MODE": 0,
"DAMP_FACTOR": 100,
}
address, raw, _ = packer.make_can_msg(message_name, bus, values)
dat = bytearray(raw)
legacy_fields = (
(24, 3, 2),
(27, 3, 0),
(30, 2, 0),
(32, 2, 0),
(34, 2, 0),
(36, 2, 0),
(38, 3, 2 if enabled else 1),
(52, 2, 1 if lat_active else 0),
(54, 2, 0),
(56, 1, 0),
(60, 4, 0),
(80, 2, 0),
)
for lsb, size, value in legacy_fields:
_set_little_endian_bits(dat, lsb, size, value)
_set_little_endian_bits(dat, 64 if message_name == "LKAS" else 104, 8, 100)
if message_name == "LKAS":
_set_little_endian_bits(dat, 84, 3, 0)
_update_checksum(packer, address, dat)
return address, bytes(dat), bus
def _create_angle_lfa_msg(packer, CAN, values, apply_angle: float, lat_active: bool, torque_reduction_gain: float): def _create_angle_lfa_msg(packer, CAN, values, apply_angle: float, lat_active: bool, torque_reduction_gain: float):
address = packer.dbc.name_to_msg["LFA"].address address = packer.dbc.name_to_msg["LFA"].address
dat = packer.pack(address, values) dat = packer.pack(address, values)
@@ -152,16 +99,12 @@ def create_angle_adas_cmd(packer, CAN, apply_angle: float, lat_active: bool, tor
def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque, apply_angle, def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque, apply_angle,
lfa_base_values=None, lkas_base_values=None, lka_icon=None): lfa_base_values=None, lkas_base_values=None, lka_icon=None,
longitudinal_active=None):
if lka_icon is None: if lka_icon is None:
lka_icon = 2 if enabled else 1 lka_icon = 2 if enabled else 1
if longitudinal_active is None:
if CP.carFingerprint == CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN and CP.flags & HyundaiFlags.CANFD_LKA_STEERING: longitudinal_active = CP.openpilotLongitudinalControl
ret = []
if CP.openpilotLongitudinalControl:
ret.append(_create_gv70_lka_status_msg(packer, CAN, "LFA", CAN.ECAN, enabled, lat_active, apply_torque))
ret.append(_create_gv70_lka_status_msg(packer, CAN, "LKAS", CAN.ACAN, enabled, lat_active, apply_torque))
return ret
angle_lkas_alt = CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING and CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT angle_lkas_alt = CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING and CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT
@@ -255,7 +198,7 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque,
ret = [] ret = []
if CP.flags & HyundaiFlags.CANFD_LKA_STEERING: if CP.flags & HyundaiFlags.CANFD_LKA_STEERING:
lkas_msg = "LKAS_ALT" if CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT else "LKAS" lkas_msg = "LKAS_ALT" if CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT else "LKAS"
if CP.openpilotLongitudinalControl and not CP.flags & HyundaiFlags.CAN_CANFD_BLENDED: if longitudinal_active and not CP.flags & HyundaiFlags.CAN_CANFD_BLENDED:
ret.append(packer.make_can_msg("LFA", CAN.ECAN, lfa_values)) ret.append(packer.make_can_msg("LFA", CAN.ECAN, lfa_values))
ret.append(packer.make_can_msg(lkas_msg, CAN.ACAN, lkas_values)) ret.append(packer.make_can_msg(lkas_msg, CAN.ACAN, lkas_values))
else: else:
+17 -9
View File
@@ -1,4 +1,6 @@
import time import time
# Provenance: portions of HKG angle integration are adapted from sunnypilot/opendbc's
# hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md and THIRD_PARTY_NOTICES.md.
from opendbc.car import get_safety_config, structs, uds from opendbc.car import get_safety_config, structs, uds
from opendbc.car.hyundai.hyundaicanfd import CanBus from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.values import HyundaiFlags, CAR, CarControllerParams, \ from opendbc.car.hyundai.values import HyundaiFlags, CAR, CarControllerParams, \
@@ -6,6 +8,7 @@ from opendbc.car.hyundai.values import HyundaiFlags, CAR, CarControllerParams, \
CANFD_SECURITYACCESS_CAR, \ CANFD_SECURITYACCESS_CAR, \
CANFD_ANGLE_LONGITUDINAL_CAR, \ CANFD_ANGLE_LONGITUDINAL_CAR, \
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, \ CANFD_RADAR_LIVE_LONGITUDINAL_CAR, \
CANFD_RADAR_ECU_KEEPALIVE_CAR, \
RADAR_LIVE_LONGITUDINAL_CAR, \ RADAR_LIVE_LONGITUDINAL_CAR, \
UNSUPPORTED_LONGITUDINAL_CAR, HyundaiSafetyFlags, \ UNSUPPORTED_LONGITUDINAL_CAR, HyundaiSafetyFlags, \
LEGACY_LONGITUDINAL_CAR, \ LEGACY_LONGITUDINAL_CAR, \
@@ -25,6 +28,15 @@ from openpilot.starpilot.common.testing_grounds import testing_ground
ButtonType = structs.CarState.ButtonEvent.Type ButtonType = structs.CarState.ButtonEvent.Type
Ecu = structs.CarParams.Ecu Ecu = structs.CarParams.Ecu
def get_communication_control_request(car_fingerprint):
if car_fingerprint in CANFD_RADAR_ECU_KEEPALIVE_CAR:
return bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, uds.CONTROL_TYPE.ENABLE_RX_DISABLE_TX,
uds.MESSAGE_TYPE.NORMAL])
return bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, 0x80 | uds.CONTROL_TYPE.DISABLE_RX_DISABLE_TX,
uds.MESSAGE_TYPE.NORMAL])
# Cancel button can sometimes be ACC pause/resume button, main button can also enable on some cars # Cancel button can sometimes be ACC pause/resume button, main button can also enable on some cars
ENABLE_BUTTONS = (ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.cancel, ButtonType.mainCruise) ENABLE_BUTTONS = (ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.cancel, ButtonType.mainCruise)
@@ -48,7 +60,7 @@ def apply_platform_longitudinal_params(ret: structs.CarParams) -> None:
def apply_kia_ev6_gt_line_longitudinal_params(ret: structs.CarParams) -> None: def apply_kia_ev6_gt_line_longitudinal_params(ret: structs.CarParams) -> None:
ret.startAccel = 1.4 ret.startAccel = 1.4
ret.longitudinalActuatorDelay = 0.35 ret.longitudinalActuatorDelay = 0.5
ret.vEgoStarting = 0.5 ret.vEgoStarting = 0.5
@@ -224,6 +236,9 @@ class CarInterface(CarInterfaceBase):
else: else:
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.hyundai, 0)] ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.hyundai, 0)]
if candidate == CAR.KIA_RAY_EV and fingerprint[CAN.CAM].get(0x485) == 8:
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.CAN_REFRESH_MSGS.value
if ret.flags & HyundaiFlags.CAMERA_SCC: if ret.flags & HyundaiFlags.CAMERA_SCC:
ret.safetyConfigs[0].safetyParam |= HyundaiSafetyFlags.CAMERA_SCC.value ret.safetyConfigs[0].safetyParam |= HyundaiSafetyFlags.CAMERA_SCC.value
if candidate in (CAR.HYUNDAI_ELANTRA_2024, CAR.HYUNDAI_ELANTRA_HEV_2024): if candidate in (CAR.HYUNDAI_ELANTRA_2024, CAR.HYUNDAI_ELANTRA_HEV_2024):
@@ -348,14 +363,7 @@ class CarInterface(CarInterfaceBase):
params = Params() params = Params()
if communication_control is None: if communication_control is None:
if CP.carFingerprint in CANFD_RADAR_LIVE_LONGITUDINAL_CAR: communication_control = get_communication_control_request(CP.carFingerprint)
# Don't use 0x80 suppress bit so we can read the ECU response.
# Use ENABLE_RX_DISABLE_TX (0x01) so the ECU can still receive from rear radars for BSM
# while blocking SCC TX.
communication_control = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, uds.CONTROL_TYPE.ENABLE_RX_DISABLE_TX, uds.MESSAGE_TYPE.NORMAL])
else:
# 0x80 silences response for other cars (original behavior)
communication_control = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, 0x80 | uds.CONTROL_TYPE.DISABLE_RX_DISABLE_TX, uds.MESSAGE_TYPE.NORMAL])
ecu_log(f"=== init() called: opLong={CP.openpilotLongitudinalControl}, flags=0x{CP.flags:x}, safetyParam={CP.safetyConfigs[-1].safetyParam} ===") ecu_log(f"=== init() called: opLong={CP.openpilotLongitudinalControl}, flags=0x{CP.flags:x}, safetyParam={CP.safetyConfigs[-1].safetyParam} ===")
@@ -19,6 +19,9 @@ MRR30_RADAR_START_ADDR = 0x210
MRR30_RADAR_MSG_COUNT = 16 MRR30_RADAR_MSG_COUNT = 16
MRR35_RADAR_START_ADDR = 0x3A5 MRR35_RADAR_START_ADDR = 0x3A5
MRR35_RADAR_MSG_COUNT = 32 MRR35_RADAR_MSG_COUNT = 32
GV70_RADAR_START_ADDR = 0x210
GV70_RADAR_MSG_COUNT = 16
GV70_RADAR_DBC = "hyundai_radar_210_21f_generated"
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -30,6 +33,7 @@ class RadarTrackConfig:
frequency: int = 50 frequency: int = 50
parser_msg_count: int | None = None parser_msg_count: int | None = None
expected_length: int | None = None expected_length: int | None = None
dbc_name: str | None = None
@property @property
def can_parser_msg_count(self) -> int: def can_parser_msg_count(self) -> int:
@@ -47,6 +51,10 @@ RADAR_TRACK_CONFIGS = {
def get_radar_track_config(car_fingerprint, flags: int = 0) -> RadarTrackConfig | None: def get_radar_track_config(car_fingerprint, flags: int = 0) -> RadarTrackConfig | None:
if car_fingerprint == CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN:
return RadarTrackConfig(GV70_RADAR_START_ADDR, GV70_RADAR_MSG_COUNT, "gv70_210", bus=0,
frequency=20, expected_length=32, dbc_name=GV70_RADAR_DBC)
radar_dbc = DBC[car_fingerprint].get(Bus.radar) radar_dbc = DBC[car_fingerprint].get(Bus.radar)
if car_fingerprint == CAR.GENESIS_G90 and radar_dbc == HYUNDAI_MANDO_FRONT_RADAR_DBC: if car_fingerprint == CAR.GENESIS_G90 and radar_dbc == HYUNDAI_MANDO_FRONT_RADAR_DBC:
return RadarTrackConfig(RADAR_START_ADDR, G90_RADAR_MSG_COUNT, "mando", parser_msg_count=RADAR_MSG_COUNT) return RadarTrackConfig(RADAR_START_ADDR, G90_RADAR_MSG_COUNT, "mando", parser_msg_count=RADAR_MSG_COUNT)
@@ -65,6 +73,10 @@ def radar_tracks_available(radar_config: RadarTrackConfig | None, fingerprint) -
if radar_config is None: if radar_config is None:
return False return False
if radar_config.radar_type == "gv70_210":
return all(fingerprint[radar_config.bus].get(addr) == radar_config.expected_length
for addr in range(radar_config.start_addr, radar_config.start_addr + radar_config.msg_count))
msg_len = fingerprint[radar_config.bus].get(radar_config.start_addr) msg_len = fingerprint[radar_config.bus].get(radar_config.start_addr)
if msg_len is None: if msg_len is None:
return False return False
@@ -78,7 +90,8 @@ def get_radar_can_parser(CP, radar_config):
messages = [(f"RADAR_TRACK_{addr:x}", radar_config.frequency) messages = [(f"RADAR_TRACK_{addr:x}", radar_config.frequency)
for addr in range(radar_config.start_addr, radar_config.start_addr + radar_config.can_parser_msg_count)] for addr in range(radar_config.start_addr, radar_config.start_addr + radar_config.can_parser_msg_count)]
return CANParser(DBC[CP.carFingerprint][Bus.radar], messages, radar_config.bus) dbc_name = radar_config.dbc_name or DBC[CP.carFingerprint][Bus.radar]
return CANParser(dbc_name, messages, radar_config.bus)
class RadarInterface(RadarInterfaceBase): class RadarInterface(RadarInterfaceBase):
@@ -223,6 +236,27 @@ class RadarInterface(RadarInterfaceBase):
del self.pts[track_key] del self.pts[track_key]
continue continue
if radar_type == "gv70_210":
for i in ("1", "2"):
track_key = addr * 2 + int(i) - 1
valid = msg[f"{i}_STATE"] in (3, 4)
if valid:
pt = self.pts.get(track_key)
if pt is None:
pt = structs.RadarData.RadarPoint()
pt.trackId = self.track_id
self.track_id += 1
self.pts[track_key] = pt
pt.measured = True
pt.dRel = msg[f"{i}_LONG_DIST"]
pt.yRel = msg[f"{i}_LAT_DIST"]
pt.vRel = msg[f"{i}_REL_SPEED"]
pt.aRel = msg[f"{i}_REL_ACCEL"]
pt.yvRel = msg[f"{i}_REL_LAT_SPEED"]
elif track_key in self.pts:
del self.pts[track_key]
continue
if radar_type == "mrrevo14f": if radar_type == "mrrevo14f":
for i in ("1", "2"): for i in ("1", "2"):
track_key = addr * 2 + int(i) - 1 track_key = addr * 2 + int(i) - 1
@@ -7,7 +7,7 @@ from opendbc.can import CANPacker, CANParser
from opendbc.car import Bus, ButtonType, gen_empty_fingerprint, structs from opendbc.car import Bus, ButtonType, gen_empty_fingerprint, structs
from opendbc.car.structs import CarControl, CarParams from opendbc.car.structs import CarControl, CarParams
from opendbc.car.fw_versions import build_fw_dict, match_fw_to_car from opendbc.car.fw_versions import build_fw_dict, match_fw_to_car
from opendbc.car.hyundai.carcontroller import CarController, Ioniq6LongitudinalTuningState, GenesisG90LongitudinalTuningState, \ from opendbc.car.hyundai.carcontroller import CarController, CANCEL_BUTTON_DELAY_FRAMES, Ioniq6LongitudinalTuningState, GenesisG90LongitudinalTuningState, \
EV9LongitudinalTuningState, update_ev9_longitudinal_tuning, \ EV9LongitudinalTuningState, update_ev9_longitudinal_tuning, \
BlindspotWarningState, update_blindspot_warning, \ BlindspotWarningState, update_blindspot_warning, \
reset_egmp_longitudinal_tuning, \ reset_egmp_longitudinal_tuning, \
@@ -20,20 +20,21 @@ from opendbc.car.hyundai.carcontroller import CarController, Ioniq6LongitudinalT
should_track_stop_accel_directly_for_car, \ should_track_stop_accel_directly_for_car, \
preserve_stock_canfd_lfa_status, \ preserve_stock_canfd_lfa_status, \
preserve_stock_canfd_lkas_status, \ preserve_stock_canfd_lkas_status, \
apply_carnival_steering_override, \ suppress_redundant_gv70_brake_cancel, \
suppress_redundant_gv70_brake_cancel clear_ioniq_6_torque_when_request_inactive
from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, decode_ioniq_6_blindspot_radar_state, \ from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, decode_ioniq_6_blindspot_radar_state, \
get_canfd_cruise_available get_canfd_cruise_available
from opendbc.car.hyundai.interface import CarInterface, KIA_EV9_ACCEL_MAX from opendbc.car.hyundai.interface import CarInterface, KIA_EV9_ACCEL_MAX, get_communication_control_request
from opendbc.car.hyundai import hyundaican, hyundaicanfd from opendbc.car.hyundai import hyundaican, hyundaicanfd
from opendbc.car.hyundai.hyundaicanfd import CanBus, hkg_can_fd_checksum from opendbc.car.hyundai.hyundaicanfd import CanBus, hkg_can_fd_checksum
from opendbc.car.hyundai.radar_interface import MRREVO14F_RADAR_START_ADDR, MRR30_RADAR_START_ADDR, MRR35_RADAR_START_ADDR, \ from opendbc.car.hyundai.radar_interface import MRREVO14F_RADAR_START_ADDR, MRR30_RADAR_START_ADDR, MRR35_RADAR_START_ADDR, \
RADAR_START_ADDR, get_radar_track_config RADAR_START_ADDR, RadarInterface, get_radar_track_config, radar_tracks_available
from opendbc.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, DATELESS_FUZZY_CARS, \ from opendbc.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, DATELESS_FUZZY_CARS, \
HYBRID_CAR, EV_CAR, FW_QUERY_CONFIG, LEGACY_SAFETY_MODE_CAR, CANFD_FUZZY_WHITELIST, \ HYBRID_CAR, EV_CAR, FW_QUERY_CONFIG, LEGACY_SAFETY_MODE_CAR, CANFD_FUZZY_WHITELIST, \
UNSUPPORTED_LONGITUDINAL_CAR, PLATFORM_CODE_ECUS, HYUNDAI_VERSION_REQUEST_LONG, \ UNSUPPORTED_LONGITUDINAL_CAR, PLATFORM_CODE_ECUS, HYUNDAI_VERSION_REQUEST_LONG, \
LEGACY_LONGITUDINAL_CAR, DBC, HyundaiFlags, get_platform_codes, HyundaiSafetyFlags, \ LEGACY_LONGITUDINAL_CAR, DBC, HyundaiFlags, get_platform_codes, HyundaiSafetyFlags, \
HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags, Buttons, CarControllerParams, kia_ev6_gt_line_longitudinal_tuning HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags, Buttons, CarControllerParams, \
CANFD_RADAR_ECU_KEEPALIVE_CAR, CANFD_RADAR_LIVE_LONGITUDINAL_CAR, kia_ev6_gt_line_longitudinal_tuning
LongCtrlState = CarControl.Actuators.LongControlState LongCtrlState = CarControl.Actuators.LongControlState
from opendbc.car.hyundai.fingerprints import FW_VERSIONS from opendbc.car.hyundai.fingerprints import FW_VERSIONS
@@ -79,6 +80,7 @@ HYUNDAI_NON_SCC_CARS = (
CAR.HYUNDAI_ELANTRA_HEV_2022_NON_SCC, CAR.HYUNDAI_ELANTRA_HEV_2022_NON_SCC,
CAR.HYUNDAI_KONA_NON_SCC, CAR.HYUNDAI_KONA_NON_SCC,
CAR.HYUNDAI_KONA_EV_NON_SCC, CAR.HYUNDAI_KONA_EV_NON_SCC,
CAR.KIA_RAY_EV,
CAR.KIA_CEED_PHEV_2022_NON_SCC, CAR.KIA_CEED_PHEV_2022_NON_SCC,
CAR.KIA_FORTE_2019_NON_SCC, CAR.KIA_FORTE_2019_NON_SCC,
CAR.KIA_FORTE_2021_NON_SCC, CAR.KIA_FORTE_2021_NON_SCC,
@@ -128,6 +130,29 @@ def get_test_toggles() -> SimpleNamespace:
class TestHyundaiFingerprint: class TestHyundaiFingerprint:
def test_ev6_uses_stock_hda2_communication_control_path(self):
stock_request = bytes([0x28, 0x83, 0x01])
radar_keepalive_request = bytes([0x28, 0x01, 0x01])
assert CAR.KIA_EV6 in CANFD_RADAR_LIVE_LONGITUDINAL_CAR
assert CAR.KIA_EV6 not in CANFD_RADAR_ECU_KEEPALIVE_CAR
assert get_communication_control_request(CAR.KIA_EV6) == stock_request
assert get_communication_control_request(CAR.HYUNDAI_IONIQ_6) == radar_keepalive_request
def test_carnival_hev_low_speed_torque_rate_limits(self):
CP = CarInterface.get_params(CAR.KIA_CARNIVAL_HEV_4TH_GEN, gen_empty_fingerprint(), [],
False, False, False, None)
carnival_2025_cp = CarInterface.get_params(CAR.KIA_CARNIVAL_2025, gen_empty_fingerprint(), [],
False, False, False, None)
low_speed = CarControllerParams(CP, 10.0)
high_speed = CarControllerParams(CP, 20.0)
carnival_2025_low_speed = CarControllerParams(carnival_2025_cp, 10.0)
assert (low_speed.STEER_DELTA_UP, low_speed.STEER_DELTA_DOWN) == (2, 3)
assert (high_speed.STEER_DELTA_UP, high_speed.STEER_DELTA_DOWN) == (2, 3)
assert (carnival_2025_low_speed.STEER_DELTA_UP, carnival_2025_low_speed.STEER_DELTA_DOWN) == (10, 8)
@pytest.mark.parametrize("candidate", (CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN)) @pytest.mark.parametrize("candidate", (CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN))
def test_carnival_uses_clean_canfd_lfa_status(self, candidate): def test_carnival_uses_clean_canfd_lfa_status(self, candidate):
assert not preserve_stock_canfd_lfa_status(candidate) assert not preserve_stock_canfd_lfa_status(candidate)
@@ -209,12 +234,6 @@ class TestHyundaiFingerprint:
assert parser.vl["LKAS_ALT"]["TORQUE_REQUEST"] == 123 assert parser.vl["LKAS_ALT"]["TORQUE_REQUEST"] == 123
assert parser.vl["LKAS_ALT"]["STEER_REQ"] == 1 assert parser.vl["LKAS_ALT"]["STEER_REQ"] == 1
def test_carnival_steering_override_is_scoped_to_2025_platform(self):
assert apply_carnival_steering_override(CAR.KIA_CARNIVAL_2025, True, True, 123) == (False, 0)
assert apply_carnival_steering_override(CAR.KIA_CARNIVAL_2025, False, True, 123) == (True, 123)
assert apply_carnival_steering_override(CAR.KIA_CARNIVAL_HEV_4TH_GEN, True, True, 123) == (True, 123)
assert apply_carnival_steering_override(CAR.HYUNDAI_IONIQ_6, True, True, 123) == (True, 123)
def test_canfd_torque_bsm_parser_registers_rear_blindspots(self): def test_canfd_torque_bsm_parser_registers_rear_blindspots(self):
CP = CarParams.new_message() CP = CarParams.new_message()
CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN
@@ -417,6 +436,42 @@ class TestHyundaiFingerprint:
assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CANFD_LKA_STEERING assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CANFD_LKA_STEERING
assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.EV_GAS assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.EV_GAS
gv70_radar_config = get_radar_track_config(CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN)
assert gv70_radar_config.radar_type == "gv70_210"
for addr in range(gv70_radar_config.start_addr, gv70_radar_config.start_addr + gv70_radar_config.msg_count):
gv70_fingerprint[gv70_radar_config.bus][addr] = gv70_radar_config.expected_length
assert radar_tracks_available(gv70_radar_config, gv70_fingerprint)
CP = CarInterface.get_params(CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, gv70_fingerprint, gv70_car_fw,
True, False, False, None)
assert not CP.radarUnavailable
radar = RadarInterface(CP)
packer = CANPacker(gv70_radar_config.dbc_name)
messages = []
for addr in range(gv70_radar_config.start_addr, gv70_radar_config.start_addr + gv70_radar_config.msg_count):
message = packer.make_can_msg(f"RADAR_TRACK_{addr:x}", 0, {
"1_STATE": 3,
"1_LONG_DIST": 25.0,
"1_LAT_DIST": 0.5,
"1_REL_SPEED": -2.0,
"1_REL_LAT_SPEED": 0.1,
"1_REL_ACCEL": -0.2,
})
data = bytearray(message[1])
checksum = hkg_can_fd_checksum(addr, None, data)
data[0] = checksum & 0xff
data[1] = (checksum >> 8) & 0xff
messages.append((message[0], bytes(data), message[2]))
radar_data = radar.update([(1, messages)])
assert radar_data is not None
assert len(radar_data.points) == 16
assert radar_data.points[0].dRel == pytest.approx(25.0)
other_config = get_radar_track_config(CAR.HYUNDAI_IONIQ_5)
assert other_config.radar_type == "mrr30"
assert other_config.dbc_name is None
for candidate in HYUNDAI_NON_SCC_CARS: for candidate in HYUNDAI_NON_SCC_CARS:
CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], True, False, False, None) CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], True, False, False, None)
assert bool(CP.flags & HyundaiFlags.NON_SCC) assert bool(CP.flags & HyundaiFlags.NON_SCC)
@@ -561,6 +616,14 @@ class TestHyundaiFingerprint:
assert not (CP.flags & HyundaiFlags.CANFD_LKA_STEERING) assert not (CP.flags & HyundaiFlags.CANFD_LKA_STEERING)
assert bool(CP.flags & HyundaiFlags.CANFD_CAMERA_SCC) assert bool(CP.flags & HyundaiFlags.CANFD_CAMERA_SCC)
def test_ioniq_6_clears_torque_with_inactive_safety_request(self):
ioniq_6_cp = SimpleNamespace(carFingerprint=CAR.HYUNDAI_IONIQ_6)
other_cp = SimpleNamespace(carFingerprint=CAR.KIA_EV6)
assert clear_ioniq_6_torque_when_request_inactive(ioniq_6_cp, -409, False) == 0
assert clear_ioniq_6_torque_when_request_inactive(ioniq_6_cp, -409, True) == -409
assert clear_ioniq_6_torque_when_request_inactive(other_cp, -409, False) == -409
palisade_2023 = CarInterface.get_params(CAR.HYUNDAI_PALISADE_2023, gen_empty_fingerprint(), [], True, False, False, None) palisade_2023 = CarInterface.get_params(CAR.HYUNDAI_PALISADE_2023, gen_empty_fingerprint(), [], True, False, False, None)
assert palisade_2023.flags & HyundaiFlags.CAN_CANFD_BLENDED assert palisade_2023.flags & HyundaiFlags.CAN_CANFD_BLENDED
assert DBC[palisade_2023.carFingerprint][Bus.pt] == "hyundai_palisade_2023_generated" assert DBC[palisade_2023.carFingerprint][Bus.pt] == "hyundai_palisade_2023_generated"
@@ -676,6 +739,126 @@ class TestHyundaiFingerprint:
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == 2 assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == 2
def test_kona_non_scc_uses_no_individual_lane_lkas_status(self):
CP = CarInterface.get_params(CAR.HYUNDAI_KONA_NON_SCC, gen_empty_fingerprint(), [], False, False, False, None)
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0)], 0)
lkas11 = parser.vl["LKAS11"]
msg = hyundaican.create_lkas11(
packer, 0, CP, 0, True, False, lkas11, False, 4, False,
True, True, 0, 0, 2,
)
parser.update([(1, [msg])])
assert parser.vl["LKAS11"]["CF_Lkas_LdwsSysState"] == 2
assert parser.vl["LKAS11"]["CF_Lkas_LdwsOpt_USM"] == 2
assert parser.vl["LKAS11"]["CF_Lkas_LdwsActivemode"] == 0
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == 0
def test_kia_ray_ev_preserves_stock_inactive_lkas_status(self):
fingerprint = gen_empty_fingerprint()
fingerprint[2][0x485] = 4
CP = CarInterface.get_params(CAR.KIA_RAY_EV, fingerprint, [], False, False, False, None)
assert CP.flags & HyundaiFlags.SEND_LFA
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0)], 0)
lkas11 = parser.vl["LKAS11"]
lkas11.update({
"CF_Lkas_LdwsActivemode": 0,
"CF_Lkas_LdwsSysState": 1,
"CF_Lkas_FcwOpt_USM": 1,
})
msg = hyundaican.create_lkas11(
packer, 0, CP, 0, True, False, lkas11, False, 4, False,
True, True, 0, 0, 2,
)
parser.update([(1, [msg])])
assert parser.vl["LKAS11"]["CF_Lkas_LdwsActivemode"] == 0
assert parser.vl["LKAS11"]["CF_Lkas_LdwsSysState"] == 1
assert parser.vl["LKAS11"]["CF_Lkas_LdwsOpt_USM"] == 0
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == 1
def test_kia_ray_ev_uses_active_lkas_status_when_enabled(self):
fingerprint = gen_empty_fingerprint()
fingerprint[2][0x485] = 4
CP = CarInterface.get_params(CAR.KIA_RAY_EV, fingerprint, [], False, False, False, None)
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0)], 0)
lkas11 = parser.vl["LKAS11"]
lkas11.update({
"CF_Lkas_LdwsActivemode": 0,
"CF_Lkas_LdwsSysState": 1,
"CF_Lkas_FcwOpt_USM": 1,
})
msg = hyundaican.create_lkas11(
packer, 0, CP, 0, True, False, lkas11, False, 4, True,
True, True, 0, 0, 2,
)
parser.update([(1, [msg])])
assert parser.vl["LKAS11"]["CF_Lkas_LdwsActivemode"] == 3
assert parser.vl["LKAS11"]["CF_Lkas_LdwsSysState"] == 4
assert parser.vl["LKAS11"]["CF_Lkas_LdwsOpt_USM"] == 0
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == 2
def test_kia_ray_ev_delays_first_lkas11(self):
fingerprint = gen_empty_fingerprint()
fingerprint[2][0x485] = 4
CP = CarInterface.get_params(CAR.KIA_RAY_EV, fingerprint, [], False, False, False, None)
controller = CarController(DBC[CP.carFingerprint], CP)
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0)], 0)
hud_control = SimpleNamespace(
visualAlert=CarControl.HUDControl.VisualAlert.none,
leftLaneVisible=True,
rightLaneVisible=True,
leftLaneDepart=False,
rightLaneDepart=False,
)
CS = SimpleNamespace(lkas11=parser.vl["LKAS11"], redneck_send_button=Buttons.NONE)
CC = SimpleNamespace(enabled=False, cruiseControl=SimpleNamespace(cancel=False, resume=False, override=False))
actuators = SimpleNamespace(longControlState=LongCtrlState.off)
first = controller.create_can_msgs(True, 0, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 2, 0)
second = controller.create_can_msgs(True, 0, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 2, 0)
assert not any(addr == 0x340 for addr, _, _ in first)
assert any(addr == 0x340 for addr, _, _ in second)
def test_stock_scc_cancel_waits_for_factory_disengagement(self):
CP = CarInterface.get_params(CAR.HYUNDAI_SANTA_FE_2022, gen_empty_fingerprint(), [], False, False, False, None)
controller = CarController(DBC[CP.carFingerprint], CP)
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0), ("CLU11", 0)], 0)
hud_control = SimpleNamespace(
visualAlert=CarControl.HUDControl.VisualAlert.none,
leftLaneVisible=True,
rightLaneVisible=True,
leftLaneDepart=False,
rightLaneDepart=False,
)
CS = SimpleNamespace(
lkas11=parser.vl["LKAS11"],
clu11=parser.vl["CLU11"],
redneck_send_button=Buttons.NONE,
is_metric=False,
)
CC = SimpleNamespace(enabled=False, cruiseControl=SimpleNamespace(cancel=True, resume=False))
actuators = SimpleNamespace(longControlState=LongCtrlState.off)
for counter in range(1, CANCEL_BUTTON_DELAY_FRAMES + 1):
controller.cancel_counter = counter
msgs = controller.create_can_msgs(True, 0, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 2, 2)
assert not any(addr == 0x4F1 for addr, _, _ in msgs)
controller.cancel_counter = CANCEL_BUTTON_DELAY_FRAMES + 1
msgs = controller.create_can_msgs(True, 0, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 2, 2)
assert any(addr == 0x4F1 for addr, _, _ in msgs)
@pytest.mark.parametrize("candidate", (CAR.HYUNDAI_ELANTRA_2024, CAR.HYUNDAI_ELANTRA_HEV_2024)) @pytest.mark.parametrize("candidate", (CAR.HYUNDAI_ELANTRA_2024, CAR.HYUNDAI_ELANTRA_HEV_2024))
def test_hyundai_can_refresh_platforms_use_refresh_dbc_and_safety_param(self, candidate): def test_hyundai_can_refresh_platforms_use_refresh_dbc_and_safety_param(self, candidate):
CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], False, False, False, None) CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], False, False, False, None)
@@ -720,6 +903,74 @@ class TestHyundaiFingerprint:
palisade_2023 = CarInterface.get_params(CAR.HYUNDAI_PALISADE_2023, gen_empty_fingerprint(), [], True, False, False, None) palisade_2023 = CarInterface.get_params(CAR.HYUNDAI_PALISADE_2023, gen_empty_fingerprint(), [], True, False, False, None)
assert palisade_2023.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.HAS_LDA_BUTTON assert palisade_2023.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.HAS_LDA_BUTTON
def test_lkas12_da_warning_is_filtered_for_camera_fingerprint(self):
fingerprint = gen_empty_fingerprint()
fingerprint[2][0x53E] = 6
CP = CarInterface.get_params(CAR.HYUNDAI_SONATA_HYBRID, fingerprint, [], False, False, False, None)
FPCP = CarInterface.get_starpilot_params(CAR.HYUNDAI_SONATA_HYBRID, fingerprint, [], CP, get_test_toggles())
assert FPCP.flags & HyundaiStarPilotFlags.HAS_LKAS12
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
stock = {
"CF_Lkas_TsrSlifOpt": 3,
"CF_LkasTsrStatus": 2,
"CF_Lkas_TsrSpeed_Display_Clu": 80,
"CF_LkasTsrSpeed_Display_Navi": 70,
"CF_Lkas_TsrAddinfo_Display": 1,
"CF_Lkas_Daw_USM": 0,
"CF_LkasDawStatus": 1,
}
msg = hyundaican.create_lkas12(packer, stock)
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS12", 0)], 0)
parser.update([(1, [msg])])
assert parser.can_valid
assert parser.vl["LKAS12"]["CF_LkasDawStatus"] == 0
assert parser.vl["LKAS12"]["CF_Lkas_TsrSpeed_Display_Clu"] == 80
no_lkas12 = CarInterface.get_params(CAR.HYUNDAI_SONATA_HYBRID, gen_empty_fingerprint(), [], False, False, False, None)
no_lkas12_fpcp = CarInterface.get_starpilot_params(
CAR.HYUNDAI_SONATA_HYBRID, gen_empty_fingerprint(), [], no_lkas12, get_test_toggles(),
)
assert not (no_lkas12_fpcp.flags & HyundaiStarPilotFlags.HAS_LKAS12)
def test_ray_ev_does_not_treat_eight_byte_53e_as_lkas12(self):
fingerprint = gen_empty_fingerprint()
fingerprint[2][0x53E] = 8
CP = CarInterface.get_params(CAR.KIA_RAY_EV, fingerprint, [], False, False, False, None)
FPCP = CarInterface.get_starpilot_params(CAR.KIA_RAY_EV, fingerprint, [], CP, get_test_toggles())
assert not (FPCP.flags & HyundaiStarPilotFlags.HAS_LKAS12)
def test_ray_ev_does_not_treat_eight_byte_485_as_lfa(self):
fingerprint = gen_empty_fingerprint()
fingerprint[2][0x485] = 8
CP = CarInterface.get_params(CAR.KIA_RAY_EV, fingerprint, [], False, False, False, None)
assert CP.flags & HyundaiFlags.SEND_LFA
assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CAN_REFRESH_MSGS
def test_ray_ev_uses_carrot_eight_byte_lfa_frame(self):
fingerprint = gen_empty_fingerprint()
fingerprint[2][0x485] = 8
CP = CarInterface.get_params(CAR.KIA_RAY_EV, fingerprint, [], False, False, False, None)
controller = CarController(DBC[CP.carFingerprint], CP)
msg = hyundaican.create_ray_lfahda_mfc(controller._ray_lfa_packer, True, 2)
assert msg[0] == 0x485
assert len(msg[1]) == 8
assert msg[1][0] & 0x03 == 2
assert msg[1][2] & 0x10 == 0x10
assert msg[1][3] & 0x03 == 2
def test_non_ray_legacy_platform_keeps_53e_lkas12_detection(self):
fingerprint = gen_empty_fingerprint()
fingerprint[2][0x53E] = 8
CP = CarInterface.get_params(CAR.HYUNDAI_SONATA_HYBRID, fingerprint, [], False, False, False, None)
FPCP = CarInterface.get_starpilot_params(CAR.HYUNDAI_SONATA_HYBRID, fingerprint, [], CP, get_test_toggles())
assert FPCP.flags & HyundaiStarPilotFlags.HAS_LKAS12
def test_carnival_lka_button_does_not_enable_angle_steering_safety(self): def test_carnival_lka_button_does_not_enable_angle_steering_safety(self):
fingerprint = gen_empty_fingerprint() fingerprint = gen_empty_fingerprint()
fingerprint[0][0x391] = 8 fingerprint[0][0x391] = 8
@@ -737,7 +988,7 @@ class TestHyundaiFingerprint:
@pytest.mark.parametrize("candidate, tracks_main_cruise", ( @pytest.mark.parametrize("candidate, tracks_main_cruise", (
(CAR.HYUNDAI_ELANTRA_2021, False), (CAR.HYUNDAI_ELANTRA_2021, False),
(CAR.HYUNDAI_ELANTRA_HEV_2024, False), (CAR.HYUNDAI_ELANTRA_HEV_2024, True),
(CAR.HYUNDAI_SONATA_HYBRID, False), (CAR.HYUNDAI_SONATA_HYBRID, False),
)) ))
def test_legacy_hyundai_long_main_cruise_tracking_is_vehicle_specific(self, candidate, tracks_main_cruise): def test_legacy_hyundai_long_main_cruise_tracking_is_vehicle_specific(self, candidate, tracks_main_cruise):
@@ -788,6 +1039,7 @@ class TestHyundaiFingerprint:
(CAR.HYUNDAI_ELANTRA_2022_NON_SCC, ("EMS16", "LVR12"), ()), (CAR.HYUNDAI_ELANTRA_2022_NON_SCC, ("EMS16", "LVR12"), ()),
(CAR.HYUNDAI_ELANTRA_HEV_2022_NON_SCC, ("E_CRUISE_CONTROL", "ELECT_GEAR"), ("EMS16",)), (CAR.HYUNDAI_ELANTRA_HEV_2022_NON_SCC, ("E_CRUISE_CONTROL", "ELECT_GEAR"), ("EMS16",)),
(CAR.HYUNDAI_KONA_EV_NON_SCC, ("LABEL11", "EMS12", "E_EMS11"), ()), (CAR.HYUNDAI_KONA_EV_NON_SCC, ("LABEL11", "EMS12", "E_EMS11"), ()),
(CAR.KIA_RAY_EV, ("LABEL11", "E_EMS11", "ELECT_GEAR"), ("EMS12", "SCC11", "SCC12")),
]) ])
def test_non_scc_cruise_message_selection(self, candidate, expected_msgs, unexpected_msgs): def test_non_scc_cruise_message_selection(self, candidate, expected_msgs, unexpected_msgs):
toggles = get_test_toggles() toggles = get_test_toggles()
@@ -806,6 +1058,69 @@ class TestHyundaiFingerprint:
assert not ret.cruiseState.enabled assert not ret.cruiseState.enabled
assert ret.cruiseState.speed == 0 assert ret.cruiseState.speed == 0
def test_kia_ray_ev_decodes_cruise_state(self):
toggles = get_test_toggles()
CP = CarInterface.get_params(CAR.KIA_RAY_EV, gen_empty_fingerprint(), [], True, False, False, toggles)
FPCP = CarInterface.get_starpilot_params(CAR.KIA_RAY_EV, gen_empty_fingerprint(), [], CP, toggles)
car_state = CarState(CP, FPCP)
can_parsers = car_state.get_can_parsers(CP)
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
can_parsers[Bus.pt].update([(1_000_000_000, [
packer.make_can_msg("LABEL11", 0, {"CC_React": 1, "CC_Engaged": 1}),
packer.make_can_msg("E_EMS11", 0, {"Cruise_Limit_Target": 10, "Accel_Pedal_Pos": 0}),
packer.make_can_msg("ELECT_GEAR", 0, {"Elect_Gear_Shifter": 5}),
])])
ret, _ = car_state.update(can_parsers, toggles)
assert ret.cruiseState.available
assert ret.cruiseState.enabled
assert ret.cruiseState.speed == pytest.approx(10 * 0.2777778)
def test_kia_ray_ev_decodes_bcm_lkas_button_pulse(self):
toggles = get_test_toggles()
CP = CarInterface.get_params(CAR.KIA_RAY_EV, gen_empty_fingerprint(), [], True, False, False, toggles)
FPCP = CarInterface.get_starpilot_params(CAR.KIA_RAY_EV, gen_empty_fingerprint(), [], CP, toggles)
car_state = CarState(CP, FPCP)
can_parsers = car_state.get_can_parsers(CP)
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
def update(ray_lkas_button: int, frame: int):
msg = packer.make_can_msg("BCM_PO_11", 0, {"RAY_LKAS_BTN": ray_lkas_button})
can_parsers[Bus.pt].update([(frame, [msg])])
return car_state.update(can_parsers, toggles)[0]
update(0, 1)
ret = update(1, 2)
assert any(be.type == ButtonType.lkas and be.pressed for be in ret.buttonEvents)
ret = update(0, 3)
assert any(be.type == ButtonType.lkas and not be.pressed for be in ret.buttonEvents)
ret = update(1, 4)
assert any(be.type == ButtonType.lkas and be.pressed for be in ret.buttonEvents)
raw_button_msg = packer.make_can_msg("BCM_PO_11", 0, {"RAY_LKAS_BTN": 1})
assert raw_button_msg[1][0] == 0x10
def test_non_ray_does_not_use_ray_lkas_signal(self):
CP = CarInterface.get_params(CAR.KIA_FORTE_2021_NON_SCC, gen_empty_fingerprint(), [], False, False, False, None)
car_state = CarState(CP, CarInterface.get_starpilot_params(CAR.KIA_FORTE_2021_NON_SCC,
gen_empty_fingerprint(), [], CP, get_test_toggles()))
parser_cycle = SimpleNamespace(
vl={
"CLU13": {"CF_Clu_LdwsLkasSW": 0},
"BCM_PO_11": {"LDA_BTN": 0, "RAY_LKAS_BTN": 1},
},
ts_nanos={
"CLU13": {"CF_Clu_LdwsLkasSW": 1},
"BCM_PO_11": {"LDA_BTN": 1, "RAY_LKAS_BTN": 1},
},
)
assert not car_state.create_lkas_button_events(parser_cycle, 0)
def test_hyundai_redneck_cruise_availability(self, monkeypatch): def test_hyundai_redneck_cruise_availability(self, monkeypatch):
class FakeParams: class FakeParams:
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -1099,7 +1414,7 @@ class TestHyundaiFingerprint:
assert CP.startAccel == pytest.approx(1.4) assert CP.startAccel == pytest.approx(1.4)
assert CP.vEgoStarting == pytest.approx(0.5) assert CP.vEgoStarting == pytest.approx(0.5)
assert CP.longitudinalActuatorDelay == pytest.approx(0.35) assert CP.longitudinalActuatorDelay == pytest.approx(0.5)
assert CP.vEgoStopping == pytest.approx(0.3) assert CP.vEgoStopping == pytest.approx(0.3)
assert CP.stoppingDecelRate == pytest.approx(0.4) assert CP.stoppingDecelRate == pytest.approx(0.4)
assert kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin) assert kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin)
@@ -1128,7 +1443,7 @@ class TestHyundaiFingerprint:
assert CP.startAccel == pytest.approx(1.4) assert CP.startAccel == pytest.approx(1.4)
assert CP.vEgoStarting == pytest.approx(0.5) assert CP.vEgoStarting == pytest.approx(0.5)
assert CP.longitudinalActuatorDelay == pytest.approx(0.35) assert CP.longitudinalActuatorDelay == pytest.approx(0.5)
assert kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin, testing_ground_active=True) assert kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin, testing_ground_active=True)
assert not kia_ev6_gt_line_longitudinal_tuning(CAR.KIA_EV6_2025, CP.carVin, testing_ground_active=True) assert not kia_ev6_gt_line_longitudinal_tuning(CAR.KIA_EV6_2025, CP.carVin, testing_ground_active=True)
@@ -1476,6 +1791,20 @@ class TestHyundaiFingerprint:
ret = update(0, 3) ret = update(0, 3)
assert any(be.type == ButtonType.lkas and not be.pressed for be in ret.buttonEvents) assert any(be.type == ButtonType.lkas and not be.pressed for be in ret.buttonEvents)
def test_elantra_hev_lkas_button_keeps_a_short_parser_cycle_edge(self):
car_state = CarState.__new__(CarState)
car_state.CP = SimpleNamespace(carFingerprint=CAR.HYUNDAI_ELANTRA_HEV_2024)
car_state.lda_button = 0
parser_cycle = SimpleNamespace(vl_all={
"CLU13": {"CF_Clu_LdwsLkasSW": [0]},
"BCM_PO_11": {"LDA_BTN": [1, 0]},
})
events = car_state.create_lkas_button_events(parser_cycle, 0)
assert any(be.type == ButtonType.lkas and be.pressed for be in events)
assert car_state.lda_button == 1
def test_sonata_hybrid_uses_main_bus_lkas_parser(self): def test_sonata_hybrid_uses_main_bus_lkas_parser(self):
toggles = get_test_toggles() toggles = get_test_toggles()
fingerprint = gen_empty_fingerprint() fingerprint = gen_empty_fingerprint()
@@ -2197,14 +2526,15 @@ class TestHyundaiFingerprint:
assert parser.vl["LKAS_ALT"]["ADAS_ACIAnglTqRedcGainVal"] == pytest.approx(0.0) assert parser.vl["LKAS_ALT"]["ADAS_ACIAnglTqRedcGainVal"] == pytest.approx(0.0)
assert parser.vl["LKAS_ALT"]["ADAS_StrAnglReqVal"] == pytest.approx(8.5) assert parser.vl["LKAS_ALT"]["ADAS_StrAnglReqVal"] == pytest.approx(8.5)
def test_gv70_electrified_synthesizes_lkas_status_payload(self): def test_gv70_electrified_uses_generic_lkas_status_payload(self):
CP = CarParams.new_message() CP = CarParams.new_message()
CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_LKA_STEERING) CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_LKA_STEERING)
CP.openpilotLongitudinalControl = False CP.openpilotLongitudinalControl = True
controller = CarController(DBC[CP.carFingerprint], CP) controller = CarController(DBC[CP.carFingerprint], CP)
controller.frame = 1 controller.frame = 1
controller.long_active_ecu = True
can_bus = CanBus(CP) can_bus = CanBus(CP)
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS", 0)], can_bus.ACAN) parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS", 0)], can_bus.ACAN)
stock_lkas = { stock_lkas = {
@@ -2240,11 +2570,12 @@ class TestHyundaiFingerprint:
parser.update([(1, lkas_msgs)]) parser.update([(1, lkas_msgs)])
assert parser.can_valid assert parser.can_valid
assert parser.vl["LKAS"]["HAS_LANE_SAFETY"] == 0 assert parser.vl["LKAS"]["HAS_LANE_SAFETY"] == 0
assert parser.vl["LKAS"]["DAMP_FACTOR"] == 100 assert parser.vl["LKAS"]["DAMP_FACTOR"] == 0
assert parser.vl["LKAS"]["TORQUE_REQUEST"] == 0 assert parser.vl["LKAS"]["TORQUE_REQUEST"] == 0
assert parser.vl["LKAS"]["STEER_REQ"] == 1 assert parser.vl["LKAS"]["STEER_REQ"] == 1
assert parser.vl["LKAS"]["STEER_MODE"] == 0
assert parser.vl["LKAS"]["NEW_SIGNAL_2"] == 0
CP.openpilotLongitudinalControl = True
lfa_parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LFA", 0)], can_bus.ECAN) lfa_parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LFA", 0)], can_bus.ECAN)
lfa_msgs = hyundaicanfd.create_steering_messages(controller.packer, CP, can_bus, True, True, 0, 0.0) lfa_msgs = hyundaicanfd.create_steering_messages(controller.packer, CP, can_bus, True, True, 0, 0.0)
assert [(controller.packer.dbc.addr_to_msg[addr].name, bus) for addr, _, bus in lfa_msgs] == [("LFA", can_bus.ECAN), ("LKAS", can_bus.ACAN)] assert [(controller.packer.dbc.addr_to_msg[addr].name, bus) for addr, _, bus in lfa_msgs] == [("LFA", can_bus.ECAN), ("LKAS", can_bus.ACAN)]
@@ -2252,6 +2583,46 @@ class TestHyundaiFingerprint:
assert lfa_parser.can_valid assert lfa_parser.can_valid
assert lfa_parser.vl["LFA"]["DAMP_FACTOR"] == 100 assert lfa_parser.vl["LFA"]["DAMP_FACTOR"] == 100
cc.longActive = False
inactive_msgs = controller.create_canfd_msgs(0, True, 0.44, 0.0, 0.0, 0.0, False,
cc.hudControl, cs, cc, get_test_toggles(), lka_icon=2, lfa_icon=2)
steering_names = [(controller.packer.dbc.addr_to_msg[addr].name, bus) for addr, _, bus in inactive_msgs
if controller.packer.dbc.addr_to_msg[addr].name in ("LFA", "LKAS")]
assert steering_names == [("LFA", can_bus.ECAN), ("LKAS", can_bus.ACAN)]
controller.frame = 1
cc.longActive = True
active_msgs = controller.create_canfd_msgs(0, True, 0.44, 0.0, 0.0, 0.0, False,
cc.hudControl, cs, cc, get_test_toggles(), lka_icon=2, lfa_icon=2)
steering_names = [(controller.packer.dbc.addr_to_msg[addr].name, bus) for addr, _, bus in active_msgs
if controller.packer.dbc.addr_to_msg[addr].name in ("LFA", "LKAS")]
assert steering_names == [("LFA", can_bus.ECAN), ("LKAS", can_bus.ACAN)]
@pytest.mark.parametrize("car", [CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6])
def test_egmp_keeps_lfa_status_when_longitudinal_is_inactive(self, car):
CP = CarParams.new_message()
CP.carFingerprint = car
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_LKA_STEERING)
CP.openpilotLongitudinalControl = True
controller = CarController(DBC[CP.carFingerprint], CP)
cc = SimpleNamespace(
enabled=False, latActive=False, longActive=False,
actuators=SimpleNamespace(longControlState=LongCtrlState.off),
leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace(),
)
cs = SimpleNamespace(
stock_lfa_msg=None, stock_lkas_msg=None,
left_blindspot_from_radar=False, right_blindspot_from_radar=False,
out=SimpleNamespace(gearShifter=structs.CarState.GearShifter.park),
)
controller.frame = 1
for controller.long_active_ecu in (False, True):
msgs = controller.create_canfd_msgs(0, False, 0.0, 0.0, 0.0, 0.0, False,
cc.hudControl, cs, cc, get_test_toggles(), lka_icon=1, lfa_icon=1)
assert any(addr == 0x12A for addr, _, _ in msgs)
def test_gv70_electrified_longitudinal_uses_hda2_scc_contract(self): def test_gv70_electrified_longitudinal_uses_hda2_scc_contract(self):
CP = CarParams.new_message() CP = CarParams.new_message()
CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN
@@ -2383,6 +2754,36 @@ class TestHyundaiFingerprint:
get_test_toggles(), lka_icon=1, lfa_icon=1) get_test_toggles(), lka_icon=1, lfa_icon=1)
assert len([msg for msg in msgs if msg[0] == 0x110]) == expected_lkas_msgs assert len([msg for msg in msgs if msg[0] == 0x110]) == expected_lkas_msgs
@pytest.mark.parametrize("standstill", [False, True])
def test_sportage_angle_lkas_alt_publishes_inactive_status(self, standstill):
CP = CarParams.new_message()
CP.carFingerprint = CAR.KIA_SPORTAGE_HEV_2026
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.HYBRID | HyundaiFlags.CANFD_ANGLE_STEERING |
HyundaiFlags.CANFD_LKA_STEERING | HyundaiFlags.CANFD_LKA_STEERING_ALT)
CP.openpilotLongitudinalControl = False
controller = CarController(DBC[CP.carFingerprint], CP)
can_bus = CanBus(CP)
cc = SimpleNamespace(enabled=False, latActive=False,
actuators=SimpleNamespace(longControlState=LongCtrlState.off),
leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace())
cs = SimpleNamespace(stock_lfa_msg=None, stock_lkas_msg={},
out=SimpleNamespace(standstill=standstill, steeringAngleDeg=0.0,
gearShifter=structs.CarState.GearShifter.drive))
msgs = controller.create_canfd_msgs(0, False, 0.0, 0.0, 0.0, 0.0, False, cc.hudControl, cs, cc,
get_test_toggles(), lka_icon=1, lfa_icon=1)
lkas_msgs = [msg for msg in msgs if msg[0] == 0x110]
assert len(lkas_msgs) == 1
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS_ALT", 0)], can_bus.ACAN)
parser.update([(1, lkas_msgs)])
assert parser.can_valid
assert parser.vl["LKAS_ALT"]["LKA_ICON"] == 1
assert parser.vl["LKAS_ALT"]["LKA_SysIndReq"] == 1
assert parser.vl["LKAS_ALT"]["LKA_RcgSta"] == 0
assert parser.vl["LKAS_ALT"]["LKAS_ANGLE_ACTIVE"] == 1
def test_ev9_inactive_angle_steering_does_not_suppress_stock_lfa(self): def test_ev9_inactive_angle_steering_does_not_suppress_stock_lfa(self):
CP = CarParams.new_message() CP = CarParams.new_message()
CP.carFingerprint = CAR.KIA_EV9 CP.carFingerprint = CAR.KIA_EV9
@@ -3478,7 +3879,9 @@ class TestHyundaiFingerprint:
def test_platform_code_ecus_available(self, subtests): def test_platform_code_ecus_available(self, subtests):
# TODO: add queries for these non-CAN FD cars to get EPS # TODO: add queries for these non-CAN FD cars to get EPS
no_eps_platforms = CANFD_CAR | {CAR.KIA_SORENTO, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H, no_eps_platforms = CANFD_CAR | {CAR.KIA_SORENTO, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H,
CAR.KIA_OPTIMA_H_G4_FL, CAR.HYUNDAI_SONATA_LF, CAR.HYUNDAI_TUCSON, CAR.GENESIS_G90, CAR.GENESIS_G80, CAR.HYUNDAI_ELANTRA} CAR.KIA_OPTIMA_H_G4_FL, CAR.HYUNDAI_SONATA_LF, CAR.HYUNDAI_TUCSON, CAR.GENESIS_G90, CAR.GENESIS_G80,
CAR.HYUNDAI_ELANTRA, CAR.KIA_RAY_EV}
no_fwd_radar_platforms = {CAR.KIA_RAY_EV}
# Asserts ECU keys essential for fuzzy fingerprinting are available on all platforms # Asserts ECU keys essential for fuzzy fingerprinting are available on all platforms
for car_model, ecus in FW_VERSIONS.items(): for car_model, ecus in FW_VERSIONS.items():
@@ -3486,6 +3889,8 @@ class TestHyundaiFingerprint:
for platform_code_ecu in PLATFORM_CODE_ECUS: for platform_code_ecu in PLATFORM_CODE_ECUS:
if platform_code_ecu in (Ecu.fwdRadar, Ecu.eps) and car_model == CAR.HYUNDAI_GENESIS: if platform_code_ecu in (Ecu.fwdRadar, Ecu.eps) and car_model == CAR.HYUNDAI_GENESIS:
continue continue
if platform_code_ecu == Ecu.fwdRadar and car_model in no_fwd_radar_platforms:
continue
if platform_code_ecu == Ecu.eps and car_model in no_eps_platforms: if platform_code_ecu == Ecu.eps and car_model in no_eps_platforms:
continue continue
assert platform_code_ecu in [e[0] for e in ecus] assert platform_code_ecu in [e[0] for e in ecus]
+25 -2
View File
@@ -2,6 +2,8 @@ import re
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import IntFlag from enum import IntFlag
# Provenance: portions of HKG angle limits, flags, and platform data are adapted from
# sunnypilot/opendbc's hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md.
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, CarSpecs, DbcDict, PlatformConfig, Platforms, uds from opendbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, CarSpecs, DbcDict, PlatformConfig, Platforms, uds
from opendbc.car.lateral import AngleSteeringLimits, ISO_LATERAL_ACCEL from opendbc.car.lateral import AngleSteeringLimits, ISO_LATERAL_ACCEL
from opendbc.car.common.conversions import Conversions as CV from opendbc.car.common.conversions import Conversions as CV
@@ -45,8 +47,12 @@ class CarControllerParams:
self.STEER_DRIVER_MULTIPLIER = 2 self.STEER_DRIVER_MULTIPLIER = 2
self.STEER_THRESHOLD = 100 self.STEER_THRESHOLD = 100
if vEgoRaw < 15.0: # below ~34 mph - more aggressive for tight turns if vEgoRaw < 15.0: # below ~34 mph - more aggressive for tight turns
self.STEER_DELTA_UP = 10 if CP.carFingerprint == CAR.KIA_CARNIVAL_HEV_4TH_GEN:
self.STEER_DELTA_DOWN = 8 self.STEER_DELTA_UP = 2
self.STEER_DELTA_DOWN = 3
else:
self.STEER_DELTA_UP = 10
self.STEER_DELTA_DOWN = 8
else: else:
self.STEER_DELTA_UP = 2 self.STEER_DELTA_UP = 2
self.STEER_DELTA_DOWN = 3 self.STEER_DELTA_DOWN = 3
@@ -111,6 +117,7 @@ class HyundaiSafetyFlags(IntFlag):
class HyundaiStarPilotSafetyFlags(IntFlag): class HyundaiStarPilotSafetyFlags(IntFlag):
AOL_MAIN_LKAS_ON_ENGAGE = 128
AOL_MAIN_LKAS_SYNC = 32 AOL_MAIN_LKAS_SYNC = 32
HAS_LDA_BUTTON = 1024 HAS_LDA_BUTTON = 1024
AOL_LKAS_ON_ENGAGE = 2048 AOL_LKAS_ON_ENGAGE = 2048
@@ -119,6 +126,7 @@ class HyundaiStarPilotSafetyFlags(IntFlag):
class HyundaiStarPilotFlags(IntFlag): class HyundaiStarPilotFlags(IntFlag):
SPEED_LIMIT_AVAILABLE = 1 SPEED_LIMIT_AVAILABLE = 1
MAIN_CRUISE_STATE_TRACKING = 2 ** 2 MAIN_CRUISE_STATE_TRACKING = 2 ** 2
HAS_LKAS12 = 2 ** 9
class HyundaiFlags(IntFlag): class HyundaiFlags(IntFlag):
@@ -940,6 +948,11 @@ class CAR(Platforms):
HYUNDAI_KONA_EV.specs, HYUNDAI_KONA_EV.specs,
flags=HyundaiFlags.EV | HyundaiFlags.ALT_LIMITS, flags=HyundaiFlags.EV | HyundaiFlags.ALT_LIMITS,
) )
KIA_RAY_EV = HyundaiNonSccPlatformConfig(
[HyundaiNonSccCarDocs("Kia Ray EV 2025", car_parts=CarParts.common([CarHarness.hyundai_h]))],
CarSpecs(mass=1295, wheelbase=2.52, steerRatio=14.5),
flags=HyundaiFlags.EV | HyundaiFlags.CHECKSUM_CRC8,
)
KIA_CEED_PHEV_2022_NON_SCC = HyundaiNonSccPlatformConfig( KIA_CEED_PHEV_2022_NON_SCC = HyundaiNonSccPlatformConfig(
[HyundaiNonSccCarDocs("Kia Ceed Plug-in Hybrid Non-SCC 2022", car_parts=CarParts.common([CarHarness.hyundai_i]))], [HyundaiNonSccCarDocs("Kia Ceed Plug-in Hybrid Non-SCC 2022", car_parts=CarParts.common([CarHarness.hyundai_i]))],
CarSpecs(mass=1650, wheelbase=2.65, steerRatio=13.75, tireStiffnessFactor=0.5), CarSpecs(mass=1650, wheelbase=2.65, steerRatio=13.75, tireStiffnessFactor=0.5),
@@ -987,6 +1000,10 @@ KIA_EV6_GT_LINE_LONG_TUNING_VDS_PREFIXES = frozenset({
}) })
KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID = "5" KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID = "5"
KIA_RAY_EV_VIN_VDS_PREFIXES = frozenset({
"CG81A",
})
ALT_BUS_LDA_BUTTON_CARS = frozenset() ALT_BUS_LDA_BUTTON_CARS = frozenset()
ALT_BUS_LDA_BUTTON_SWL_STAT_CARS = frozenset() ALT_BUS_LDA_BUTTON_SWL_STAT_CARS = frozenset()
@@ -1001,6 +1018,10 @@ def kia_ev6_gt_line_longitudinal_tuning(car_fingerprint, vin: str, testing_groun
return car_fingerprint == CAR.KIA_EV6 and (vin_match or testing_ground_active) return car_fingerprint == CAR.KIA_EV6 and (vin_match or testing_ground_active)
def kia_ray_ev_vin(vin: str) -> bool:
return isinstance(vin, str) and len(vin) == 17 and vin[3:8] in KIA_RAY_EV_VIN_VDS_PREFIXES
def get_platform_codes(fw_versions: list[bytes]) -> set[tuple[bytes, bytes | None]]: def get_platform_codes(fw_versions: list[bytes]) -> set[tuple[bytes, bytes | None]]:
# Returns unique, platform-specific identification codes for a set of versions # Returns unique, platform-specific identification codes for a set of versions
codes = set() # (code-Optional[part], date) codes = set() # (code-Optional[part], date)
@@ -1197,7 +1218,9 @@ CANFD_ALT_BUTTONS_RESUME_CAR = {CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_
CANFD_CORNER_RADAR_BSM_CAR = {CAR.HYUNDAI_IONIQ_6, CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9} CANFD_CORNER_RADAR_BSM_CAR = {CAR.HYUNDAI_IONIQ_6, CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9}
CANFD_RADAR_LIVE_LONGITUDINAL_CAR = { CANFD_RADAR_LIVE_LONGITUDINAL_CAR = {
CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_IONIQ_5_PE, CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6, CAR.KIA_EV9, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_IONIQ_5_PE, CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6, CAR.KIA_EV9, CAR.GENESIS_GV60_EV_1ST_GEN,
CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN,
} }
CANFD_RADAR_ECU_KEEPALIVE_CAR = CANFD_RADAR_LIVE_LONGITUDINAL_CAR - {CAR.KIA_EV6}
RADAR_LIVE_LONGITUDINAL_CAR = CANFD_RADAR_LIVE_LONGITUDINAL_CAR | { RADAR_LIVE_LONGITUDINAL_CAR = CANFD_RADAR_LIVE_LONGITUDINAL_CAR | {
CAR.HYUNDAI_IONIQ, CAR.HYUNDAI_IONIQ,
CAR.HYUNDAI_KONA_EV_2022, CAR.HYUNDAI_KONA_EV_2022,
+25 -3
View File
@@ -22,7 +22,7 @@ from opendbc.car.honda.values import CAR as HONDA, HONDA_BOSCH, HondaFlags, Hond
from opendbc.car.hyundai.hyundaicanfd import CanBus from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.values import CAR as HYUNDAI, CANFD_CAR, HyundaiFlags, HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags, ALT_BUS_LDA_BUTTON_CARS from opendbc.car.hyundai.values import CAR as HYUNDAI, CANFD_CAR, HyundaiFlags, HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags, ALT_BUS_LDA_BUTTON_CARS
from opendbc.car.mock.values import CAR as MOCK from opendbc.car.mock.values import CAR as MOCK
from opendbc.car.subaru.values import CAR as SUBARU, SubaruSafetyFlags from opendbc.car.subaru.values import CAR as SUBARU, SUBARU_REDNECK_CRUISE_CARS, SubaruSafetyFlags
from opendbc.car.toyota.values import CAR as TOYOTA, NO_DSU_CAR, TSS2_CAR, UNSUPPORTED_DSU_CAR, ToyotaStarPilotFlags, ToyotaSafetyFlags from opendbc.car.toyota.values import CAR as TOYOTA, NO_DSU_CAR, TSS2_CAR, UNSUPPORTED_DSU_CAR, ToyotaStarPilotFlags, ToyotaSafetyFlags
from opendbc.car.values import PLATFORMS from opendbc.car.values import PLATFORMS
from opendbc.can import CANParser from opendbc.can import CANParser
@@ -240,11 +240,17 @@ class CarInterfaceBase(ABC):
if 0x1FA in fingerprint[CAN.ECAN]: if 0x1FA in fingerprint[CAN.ECAN]:
fp_ret.flags |= HyundaiStarPilotFlags.SPEED_LIMIT_AVAILABLE.value fp_ret.flags |= HyundaiStarPilotFlags.SPEED_LIMIT_AVAILABLE.value
if candidate != HYUNDAI.KIA_RAY_EV and not (CP.flags & HyundaiFlags.CANFD) and 0x53E in fingerprint[2]:
fp_ret.flags |= HyundaiStarPilotFlags.HAS_LKAS12.value
fp_ret.redneckCruiseAvailable = bool(CP.flags & HyundaiFlags.NON_SCC) and not bool(CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS) fp_ret.redneckCruiseAvailable = bool(CP.flags & HyundaiFlags.NON_SCC) and not bool(CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS)
if fp_ret.redneckCruiseAvailable and params.get_bool("RedneckCruise"): if fp_ret.redneckCruiseAvailable and params.get_bool("RedneckCruise"):
fp_ret.pcmCruiseSpeed = False fp_ret.pcmCruiseSpeed = False
CP.openpilotLongitudinalControl = True CP.openpilotLongitudinalControl = True
if candidate == HYUNDAI.HYUNDAI_ELANTRA_HEV_2024 and CP.openpilotLongitudinalControl:
fp_ret.flags |= HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING.value
hyundai_has_lda_button = not (CP.flags & HyundaiFlags.CANFD) and ( hyundai_has_lda_button = not (CP.flags & HyundaiFlags.CANFD) and (
0x391 in fingerprint[0] or 0x391 in fingerprint[0] or
0x50C in fingerprint[0] or 0x50C in fingerprint[0] or
@@ -257,8 +263,16 @@ class CarInterfaceBase(ABC):
if getattr(starpilot_toggles, "always_on_lateral_lkas", False): if getattr(starpilot_toggles, "always_on_lateral_lkas", False):
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
# LKASButtonControl == 9 means BUTTON_FUNCTIONS["AOL_TOGGLE"] in starpilot_variables. if candidate in (HYUNDAI.HYUNDAI_ELANTRA_HEV_2024, HYUNDAI.HYUNDAI_SONATA_HYBRID) and \
if params.get_bool("AlwaysOnLateral") and params.get_int("LKASButtonControl") == 9: getattr(starpilot_toggles, "always_on_lateral_main", False):
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_ON_ENGAGE.value
if candidate == HYUNDAI.HYUNDAI_SONATA_HYBRID:
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
# The refresh Elantra's safety mapping comes from the resolved Galaxy
# toggle above, not from this legacy persisted-parameter fallback.
if candidate != HYUNDAI.HYUNDAI_ELANTRA_HEV_2024 and \
params.get_bool("AlwaysOnLateral") and params.get_int("LKASButtonControl") == 9:
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
if candidate == HYUNDAI.HYUNDAI_SONATA_HYBRID and getattr(starpilot_toggles, "always_on_lateral_lkas", False) and \ if candidate == HYUNDAI.HYUNDAI_SONATA_HYBRID and getattr(starpilot_toggles, "always_on_lateral_lkas", False) and \
@@ -286,6 +300,14 @@ class CarInterfaceBase(ABC):
if getattr(starpilot_toggles, "subaru_sng", False): if getattr(starpilot_toggles, "subaru_sng", False):
fp_ret.safetyConfigs[-1].safetyParam |= SubaruSafetyFlags.STOP_AND_GO.value fp_ret.safetyConfigs[-1].safetyParam |= SubaruSafetyFlags.STOP_AND_GO.value
fp_ret.redneckCruiseAvailable = candidate in SUBARU_REDNECK_CRUISE_CARS
if fp_ret.redneckCruiseAvailable and params.get_bool("SubaruRedneckCruise") and \
not CP.openpilotLongitudinalControl:
fp_ret.pcmCruiseSpeed = False
CP.openpilotLongitudinalControl = True
CP.safetyConfigs[-1].safetyParam |= SubaruSafetyFlags.REDNECK_CRUISE.value
fp_ret.safetyConfigs[-1].safetyParam |= SubaruSafetyFlags.REDNECK_CRUISE.value
return fp_ret return fp_ret
@staticmethod @staticmethod
@@ -4,7 +4,7 @@ from opendbc.car import Bus, DT_CTRL, make_tester_present_msg, structs
from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_std_steer_angle_limits, apply_steer_angle_limits_vm, common_fault_avoidance from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_std_steer_angle_limits, apply_steer_angle_limits_vm, common_fault_avoidance
from opendbc.car.interfaces import CarControllerBase from opendbc.car.interfaces import CarControllerBase
from opendbc.car.subaru import subarucan from opendbc.car.subaru import subarucan
from opendbc.car.subaru.values import CAR, DBC, GLOBAL_ES_ADDR, CanBus, CarControllerParams, SubaruFlags from opendbc.car.subaru.values import CAR, DBC, GLOBAL_ES_ADDR, SUBARU_STOP_START_CARS, CanBus, CarControllerParams, SubaruFlags
from opendbc.car.vehicle_model import VehicleModel from opendbc.car.vehicle_model import VehicleModel
# FIXME: These limits aren't exact. The real limit is more than likely over a larger time period and # FIXME: These limits aren't exact. The real limit is more than likely over a larger time period and
@@ -22,6 +22,7 @@ _LEGACY_2025_REENGAGE_MAX_STEER_RATE = 2.0
_LEGACY_2025_REENGAGE_MAX_ANGLE_DELTA = 1.0 _LEGACY_2025_REENGAGE_MAX_ANGLE_DELTA = 1.0
_LEGACY_2025_RECLAIM_FRAMES = 36 _LEGACY_2025_RECLAIM_FRAMES = 36
_LEGACY_2025_RECLAIM_EXPONENT = 2.5 _LEGACY_2025_RECLAIM_EXPONENT = 2.5
_ANGLE_OVERRIDE_CONFIRM_FRAMES = 2
_ANGLE_OVERRIDE_HOLD_FRAMES = 10 _ANGLE_OVERRIDE_HOLD_FRAMES = 10
_ANGLE_REENGAGE_SETTLE_FRAMES = 8 _ANGLE_REENGAGE_SETTLE_FRAMES = 8
_ANGLE_REENGAGE_MAX_STEER_RATE = 2.0 _ANGLE_REENGAGE_MAX_STEER_RATE = 2.0
@@ -31,9 +32,13 @@ _ANGLE_RECLAIM_EXPONENT = 2.5
_ANGLE_MADS_MIN_SPEED = 0.44704 _ANGLE_MADS_MIN_SPEED = 0.44704
_ANGLE_MADS_MAX_STEER_ANGLE = 120.0 _ANGLE_MADS_MAX_STEER_ANGLE = 120.0
_STOP_START_STARTUP_DELAY_FRAMES = 100 _STOP_START_STARTUP_DELAY_FRAMES = 100
_STOP_START_STARTUP_DEADLINE_FRAMES = 300 # StarPilot's first populated toggle message can arrive several seconds after
# the car controller starts while fingerprinting and settings settle.
_STOP_START_STARTUP_DEADLINE_FRAMES = 1000
_STOP_START_PULSE_FRAMES = 30 _STOP_START_PULSE_FRAMES = 30
_STOP_START_PULSE_PERIOD_FRAMES = 5 _STOP_START_PULSE_PERIOD_FRAMES = 5
_REDNECK_BUTTON_INTERVAL_FRAMES = 10
_REDNECK_BUTTON_COPIES = 2
def get_safety_CP(): def get_safety_CP():
@@ -47,6 +52,7 @@ class CarController(CarControllerBase):
self.apply_torque_last = 0 self.apply_torque_last = 0
self.apply_steer_last = 0 self.apply_steer_last = 0
self.driver_override = False self.driver_override = False
self.angle_override_confirm_frames = 0
self.legacy_2025_lkas_active = False self.legacy_2025_lkas_active = False
self.legacy_2025_handoff_active = False self.legacy_2025_handoff_active = False
self.legacy_2025_override_hold_frames = 0 self.legacy_2025_override_hold_frames = 0
@@ -71,7 +77,7 @@ class CarController(CarControllerBase):
self.angle_bus = CanBus.angle_for_cp(CP) self.angle_bus = CanBus.angle_for_cp(CP)
self.status_bus = CanBus.camera if CP.flags & SubaruFlags.D_PLATFORM_CAMERA else CanBus.main self.status_bus = CanBus.camera if CP.flags & SubaruFlags.D_PLATFORM_CAMERA else CanBus.main
if CP.flags & SubaruFlags.LKAS_ANGLE: if CP.flags & SubaruFlags.LKAS_ANGLE and CP.carFingerprint != CAR.SUBARU_OUTBACK_2023:
self.VM = VehicleModel(get_safety_CP()) self.VM = VehicleModel(get_safety_CP())
self.prev_close_distance = 0 self.prev_close_distance = 0
@@ -83,14 +89,15 @@ class CarController(CarControllerBase):
self.stop_start_initial_state = None self.stop_start_initial_state = None
self.stop_start_counter = 0 self.stop_start_counter = 0
self.stop_start_acknowledged = False self.stop_start_acknowledged = False
self.last_redneck_button_frame = 0
def _stop_start_off_request(self, CC, CS, starpilot_toggles): def _stop_start_off_request(self, CC, CS, starpilot_toggles):
"""Send one bounded Outback Stop/Start OFF request after ignition. """Send one bounded Subaru Stop/Start OFF request after ignition.
This is intentionally opt-in and limited to a stationary vehicle in This is intentionally opt-in and limited to a stationary vehicle in
Park/Neutral. A single ignition session gets at most one attempt. Park/Neutral. A single ignition session gets at most one attempt.
""" """
if self.CP.carFingerprint != CAR.SUBARU_OUTBACK_2023 or \ if self.CP.carFingerprint not in SUBARU_STOP_START_CARS or \
not getattr(starpilot_toggles, "subaru_stop_start_off", False) or self.stop_start_attempted: not getattr(starpilot_toggles, "subaru_stop_start_off", False) or self.stop_start_attempted:
return None return None
@@ -133,12 +140,15 @@ class CarController(CarControllerBase):
return None return None
msg = subarucan.create_stop_start_control( msg = subarucan.create_stop_start_control(
self.packer, dashlights_msg, counter=self.stop_start_counter, bus=self.main_bus, self.packer, dashlights_msg, raw_dat=getattr(CS, "dashlights_dat", None),
counter=self.stop_start_counter, bus=CanBus.alt_for_cp(self.CP),
) )
self.stop_start_counter = (self.stop_start_counter + 1) % 0x10 self.stop_start_counter = (self.stop_start_counter + 1) % 0x10
return msg return msg
def _reset_legacy_2025_handoff(self): def _reset_legacy_2025_handoff(self):
self.driver_override = False
self.angle_override_confirm_frames = 0
self.legacy_2025_handoff_active = False self.legacy_2025_handoff_active = False
self.legacy_2025_override_hold_frames = 0 self.legacy_2025_override_hold_frames = 0
self.legacy_2025_reengage_settle_frames = 0 self.legacy_2025_reengage_settle_frames = 0
@@ -151,7 +161,8 @@ class CarController(CarControllerBase):
self._reset_legacy_2025_handoff() self._reset_legacy_2025_handoff()
return False return False
if getattr(CS.out, "steeringPressed", False): driver_override = self._update_angle_driver_override(CS)
if driver_override:
self.legacy_2025_handoff_active = True self.legacy_2025_handoff_active = True
self.legacy_2025_override_hold_frames = _LEGACY_2025_OVERRIDE_HOLD_FRAMES self.legacy_2025_override_hold_frames = _LEGACY_2025_OVERRIDE_HOLD_FRAMES
self.legacy_2025_reengage_settle_frames = 0 self.legacy_2025_reengage_settle_frames = 0
@@ -202,6 +213,8 @@ class CarController(CarControllerBase):
return target_angle return target_angle
def _reset_angle_handoff(self): def _reset_angle_handoff(self):
self.driver_override = False
self.angle_override_confirm_frames = 0
self.angle_handoff_active = False self.angle_handoff_active = False
self.angle_override_hold_frames = 0 self.angle_override_hold_frames = 0
self.angle_reengage_settle_frames = 0 self.angle_reengage_settle_frames = 0
@@ -214,7 +227,8 @@ class CarController(CarControllerBase):
self._reset_angle_handoff() self._reset_angle_handoff()
return False return False
if getattr(CS.out, "steeringPressed", False): driver_override = self._update_angle_driver_override(CS)
if driver_override:
self.angle_handoff_active = True self.angle_handoff_active = True
self.angle_override_hold_frames = _ANGLE_OVERRIDE_HOLD_FRAMES self.angle_override_hold_frames = _ANGLE_OVERRIDE_HOLD_FRAMES
self.angle_reengage_settle_frames = 0 self.angle_reengage_settle_frames = 0
@@ -253,6 +267,22 @@ class CarController(CarControllerBase):
self.angle_reclaim_start_angle = CS.out.steeringAngleDeg self.angle_reclaim_start_angle = CS.out.steeringAngleDeg
return True return True
def _update_angle_driver_override(self, CS):
"""Debounce the higher-confidence raw torque override signal for angle cars."""
abs_torque = abs(getattr(CS.out, "steeringTorque", 0.0))
if self.driver_override:
if abs_torque < self.p.STEER_OVERRIDE_TORQUE_LOW:
self.driver_override = False
elif abs_torque > self.p.STEER_OVERRIDE_TORQUE_HIGH:
self.angle_override_confirm_frames += 1
if self.angle_override_confirm_frames >= _ANGLE_OVERRIDE_CONFIRM_FRAMES:
self.driver_override = True
self.angle_override_confirm_frames = 0
else:
self.angle_override_confirm_frames = 0
return self.driver_override
def _angle_reclaim_target(self, target_angle): def _angle_reclaim_target(self, target_angle):
if self.angle_reclaim_frames <= 0: if self.angle_reclaim_frames <= 0:
return target_angle return target_angle
@@ -272,7 +302,7 @@ class CarController(CarControllerBase):
lkas_available = CC.latActive and (not mads_only or mads_only_ok) and \ lkas_available = CC.latActive and (not mads_only or mads_only_ok) and \
CS.out.gearShifter == structs.CarState.GearShifter.drive and not CS.out.standstill CS.out.gearShifter == structs.CarState.GearShifter.drive and not CS.out.standstill
manual_handoff = self._legacy_2025_manual_handoff(CS, lkas_available) manual_handoff = self._legacy_2025_manual_handoff(CS, CC.latActive)
lkas_active = lkas_available and not manual_handoff lkas_active = lkas_available and not manual_handoff
steer_target = self._legacy_2025_reclaim_target(CC.actuators.steeringAngleDeg) if lkas_active else CC.actuators.steeringAngleDeg steer_target = self._legacy_2025_reclaim_target(CC.actuators.steeringAngleDeg) if lkas_active else CC.actuators.steeringAngleDeg
@@ -295,14 +325,14 @@ class CarController(CarControllerBase):
lkas_available = CC.latActive and (not mads_only or mads_only_ok) and \ lkas_available = CC.latActive and (not mads_only or mads_only_ok) and \
CS.out.gearShifter == structs.CarState.GearShifter.drive and not CS.out.standstill CS.out.gearShifter == structs.CarState.GearShifter.drive and not CS.out.standstill
manual_handoff = self._angle_manual_handoff(CS, lkas_available) manual_handoff = self._angle_manual_handoff(CS, CC.latActive)
lkas_active = lkas_available and not manual_handoff lkas_active = lkas_available and not manual_handoff
if lkas_active and not self.angle_lkas_active: if lkas_active and not self.angle_lkas_active:
self.apply_steer_last = CS.out.steeringAngleDeg self.apply_steer_last = CS.out.steeringAngleDeg
steer_target = self._angle_reclaim_target(CC.actuators.steeringAngleDeg) if lkas_active else CC.actuators.steeringAngleDeg steer_target = self._angle_reclaim_target(CC.actuators.steeringAngleDeg) if lkas_active else CC.actuators.steeringAngleDeg
if self.CP.carFingerprint == CAR.SUBARU_ASCENT_2023: if self.CP.carFingerprint in (CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023):
apply_steer = apply_std_steer_angle_limits( apply_steer = apply_std_steer_angle_limits(
steer_target, steer_target,
self.apply_steer_last, self.apply_steer_last,
@@ -325,19 +355,13 @@ class CarController(CarControllerBase):
self.angle_lkas_active = lkas_active self.angle_lkas_active = lkas_active
return subarucan.create_steering_control_angle(self.packer, apply_steer, lkas_active, self.angle_bus) return subarucan.create_steering_control_angle(self.packer, apply_steer, lkas_active, self.angle_bus)
abs_torque = abs(CS.out.steeringTorque)
if abs_torque > self.p.STEER_OVERRIDE_TORQUE_HIGH:
self.driver_override = True
elif abs_torque < self.p.STEER_OVERRIDE_TORQUE_LOW:
self.driver_override = False
mads_only = CC.latActive and not getattr(CC, "enabled", False) mads_only = CC.latActive and not getattr(CC, "enabled", False)
mads_only_ok = CS.out.vEgoRaw > _ANGLE_MADS_MIN_SPEED and \ mads_only_ok = CS.out.vEgoRaw > _ANGLE_MADS_MIN_SPEED and \
abs(CS.out.steeringAngleDeg) < _ANGLE_MADS_MAX_STEER_ANGLE abs(CS.out.steeringAngleDeg) < _ANGLE_MADS_MAX_STEER_ANGLE
lkas_available = CC.latActive and (not mads_only or mads_only_ok) and \ lkas_available = CC.latActive and (not mads_only or mads_only_ok) and \
getattr(CS.out, "gearShifter", structs.CarState.GearShifter.drive) == structs.CarState.GearShifter.drive and \ getattr(CS.out, "gearShifter", structs.CarState.GearShifter.drive) == structs.CarState.GearShifter.drive and \
not getattr(CS.out, "standstill", False) not getattr(CS.out, "standstill", False)
manual_handoff = self._angle_manual_handoff(CS, lkas_available) manual_handoff = self._angle_manual_handoff(CS, CC.latActive)
lat_active = lkas_available and not self.driver_override and not manual_handoff lat_active = lkas_available and not self.driver_override and not manual_handoff
if lat_active and not self.angle_lkas_active: if lat_active and not self.angle_lkas_active:
self.apply_steer_last = CS.out.steeringAngleDeg self.apply_steer_last = CS.out.steeringAngleDeg
@@ -388,6 +412,10 @@ class CarController(CarControllerBase):
actuators = CC.actuators actuators = CC.actuators
hud_control = CC.hudControl hud_control = CC.hudControl
pcm_cancel_cmd = CC.cruiseControl.cancel pcm_cancel_cmd = CC.cruiseControl.cancel
subaru_redneck_cruise = bool(
self.CP.carFingerprint == CAR.SUBARU_IMPREZA_2020 and
getattr(starpilot_toggles, "subaru_redneck_cruise", False)
)
can_sends = [] can_sends = []
@@ -452,7 +480,8 @@ class CarController(CarControllerBase):
else: else:
if self.frame % 10 == 0: if self.frame % 10 == 0:
can_sends.append(subarucan.create_es_dashstatus(self.packer, self.frame // 10, CS.es_dashstatus_msg, CC.enabled, can_sends.append(subarucan.create_es_dashstatus(self.packer, self.frame // 10, CS.es_dashstatus_msg, CC.enabled,
self.CP.openpilotLongitudinalControl, CC.longActive, hud_control.leadVisible, self.CP.openpilotLongitudinalControl and not subaru_redneck_cruise,
CC.longActive, hud_control.leadVisible,
self.status_bus)) self.status_bus))
can_sends.append(subarucan.create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, CC.latActive, hud_control.visualAlert, can_sends.append(subarucan.create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, CC.latActive, hud_control.visualAlert,
@@ -470,7 +499,7 @@ class CarController(CarControllerBase):
can_sends.append(subarucan.create_brake_pedal(self.packer, self.frame // 2, CS.brake_pedal_msg, can_sends.append(subarucan.create_brake_pedal(self.packer, self.frame // 2, CS.brake_pedal_msg,
speed_cmd, pcm_cancel_cmd)) speed_cmd, pcm_cancel_cmd))
if self.CP.openpilotLongitudinalControl: if self.CP.openpilotLongitudinalControl and not subaru_redneck_cruise:
if self.frame % 5 == 0: if self.frame % 5 == 0:
can_sends.append(subarucan.create_es_status(self.packer, self.frame // 5, CS.es_status_msg, can_sends.append(subarucan.create_es_status(self.packer, self.frame // 5, CS.es_status_msg,
self.CP.openpilotLongitudinalControl, CC.longActive, cruise_rpm)) self.CP.openpilotLongitudinalControl, CC.longActive, cruise_rpm))
@@ -486,6 +515,20 @@ class CarController(CarControllerBase):
bus = CanBus.alt_for_cp(self.CP) if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else self.main_bus bus = CanBus.alt_for_cp(self.CP) if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else self.main_bus
can_sends.append(subarucan.create_es_distance(self.packer, CS.es_distance_msg["COUNTER"] + 1, CS.es_distance_msg, bus, pcm_cancel_cmd)) can_sends.append(subarucan.create_es_distance(self.packer, CS.es_distance_msg["COUNTER"] + 1, CS.es_distance_msg, bus, pcm_cancel_cmd))
if subaru_redneck_cruise:
redneck_button = {
1: subarucan.CRUISE_BUTTON_RESUME,
2: subarucan.CRUISE_BUTTON_SET,
}.get(getattr(CS, "redneck_send_button", 0))
cruise_buttons_msg = getattr(CS, "cruise_buttons_msg", None)
if redneck_button and cruise_buttons_msg and self.frame - self.last_redneck_button_frame >= _REDNECK_BUTTON_INTERVAL_FRAMES:
counter = (int(cruise_buttons_msg["COUNTER"]) + 1) % 0x10
for copy_idx in range(_REDNECK_BUTTON_COPIES):
can_sends.append(subarucan.create_cruise_buttons(
self.packer, counter + copy_idx, cruise_buttons_msg, redneck_button, self.main_bus,
))
self.last_redneck_button_frame = self.frame
if self.CP.flags & SubaruFlags.DISABLE_EYESIGHT: if self.CP.flags & SubaruFlags.DISABLE_EYESIGHT:
# Tester present (keeps eyesight disabled) # Tester present (keeps eyesight disabled)
if self.frame % 100 == 0: if self.frame % 100 == 0:
+32 -8
View File
@@ -1,12 +1,20 @@
import copy import copy
from cereal import custom from cereal import custom
from opendbc.can import CANDefine, CANParser from opendbc.can import CANDefine, CANParser
from opendbc.car import Bus, structs from opendbc.car import Bus, create_button_events, structs
from opendbc.car.common.conversions import Conversions as CV from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.interfaces import CarStateBase from opendbc.car.interfaces import CarStateBase
from opendbc.car.subaru.values import CAR, DBC, CanBus, SubaruFlags from opendbc.car.subaru.values import DBC, CanBus, SUBARU_REDNECK_CRUISE_CARS, SUBARU_STOP_START_CARS, SubaruFlags
from opendbc.car import CanSignalRateCalculator from opendbc.car import CanSignalRateCalculator
ButtonType = structs.CarState.ButtonEvent.Type
SUBARU_CRUISE_BUTTONS = {
"Main": ButtonType.mainCruise,
"Set": ButtonType.decelCruise,
"Resume": ButtonType.accelCruise,
}
class CarState(CarStateBase): class CarState(CarStateBase):
def __init__(self, CP, FPCP): def __init__(self, CP, FPCP):
@@ -16,7 +24,10 @@ class CarState(CarStateBase):
self.angle_rate_calulator = CanSignalRateCalculator(50) self.angle_rate_calulator = CanSignalRateCalculator(50)
self.dashlights_msg = {} self.dashlights_msg = {}
self.dashlights_dat = b""
self.stop_start_state = 0 self.stop_start_state = 0
self.cruise_buttons_msg = {}
self.cruise_buttons = {button: 0 for button in SUBARU_CRUISE_BUTTONS}
def update(self, can_parsers, starpilot_toggles) -> structs.CarState: def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
cp = can_parsers[Bus.pt] cp = can_parsers[Bus.pt]
@@ -26,9 +37,11 @@ class CarState(CarStateBase):
cp_angle = cp_main if self.CP.flags & SubaruFlags.D_PLATFORM else cp cp_angle = cp_main if self.CP.flags & SubaruFlags.D_PLATFORM else cp
ret = structs.CarState() ret = structs.CarState()
if self.CP.carFingerprint == CAR.SUBARU_OUTBACK_2023: if self.CP.carFingerprint in SUBARU_STOP_START_CARS:
self.dashlights_msg = copy.copy(cp.vl["Dashlights"]) stop_start_cp = cp_alt if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp
self.stop_start_state = cp.vl["Engine_Stop_Start"]["STOP_START_STATE"] self.dashlights_msg = copy.copy(stop_start_cp.vl["Dashlights"])
self.dashlights_dat = stop_start_cp.vl_raw["Dashlights"]
self.stop_start_state = stop_start_cp.vl["Engine_Stop_Start"]["STOP_START_STATE"]
throttle_msg = cp.vl["Throttle"] if not (self.CP.flags & SubaruFlags.HYBRID) else cp_alt.vl["Throttle_Hybrid"] throttle_msg = cp.vl["Throttle"] if not (self.CP.flags & SubaruFlags.HYBRID) else cp_alt.vl["Throttle_Hybrid"]
ret.gasPressed = throttle_msg["Throttle_Pedal"] > 1e-5 ret.gasPressed = throttle_msg["Throttle_Pedal"] > 1e-5
@@ -72,14 +85,14 @@ class CarState(CarStateBase):
if self.CP.flags & SubaruFlags.LKAS_ANGLE: if self.CP.flags & SubaruFlags.LKAS_ANGLE:
ret.steeringAngleDeg = cp_angle.vl["Steering_2"]["Steering_Angle"] ret.steeringAngleDeg = cp_angle.vl["Steering_2"]["Steering_Angle"]
steering_updated = len(cp_angle.vl_all["Steering_2"]["Steering_Angle"]) > 0 steering_counter = cp_angle.vl["Steering_2"]["COUNTER"]
else: else:
ret.steeringAngleDeg = cp.vl["Steering_Torque"]["Steering_Angle"] ret.steeringAngleDeg = cp.vl["Steering_Torque"]["Steering_Angle"]
steering_updated = len(cp.vl_all["Steering_Torque"]["Steering_Angle"]) > 0 steering_counter = cp.vl["Steering_Torque"].get("COUNTER", 0)
if not (self.CP.flags & SubaruFlags.PREGLOBAL): if not (self.CP.flags & SubaruFlags.PREGLOBAL):
# ideally we get this from the car, but unclear if it exists. diagnostic software doesn't even have it # ideally we get this from the car, but unclear if it exists. diagnostic software doesn't even have it
ret.steeringRateDeg = self.angle_rate_calulator.update(ret.steeringAngleDeg, steering_updated) ret.steeringRateDeg = self.angle_rate_calulator.update(ret.steeringAngleDeg, steering_counter)
ret.steeringTorque = cp_angle.vl["Steering_Torque"]["Steer_Torque_Sensor"] ret.steeringTorque = cp_angle.vl["Steering_Torque"]["Steer_Torque_Sensor"]
ret.steeringTorqueEps = cp_angle.vl["Steering_Torque"]["Steer_Torque_Output"] ret.steeringTorqueEps = cp_angle.vl["Steering_Torque"]["Steer_Torque_Output"]
@@ -133,6 +146,17 @@ class CarState(CarStateBase):
self.es_status_msg = copy.copy(cp_es_brake.vl["ES_Status"]) self.es_status_msg = copy.copy(cp_es_brake.vl["ES_Status"])
self.cruise_control_msg = copy.copy(cp_cruise.vl["CruiseControl"]) self.cruise_control_msg = copy.copy(cp_cruise.vl["CruiseControl"])
if self.CP.carFingerprint in SUBARU_REDNECK_CRUISE_CARS:
cruise_buttons = cp.vl["Cruise_Buttons"]
if getattr(starpilot_toggles, "subaru_redneck_cruise", False):
ret.buttonEvents = []
for button, button_type in SUBARU_CRUISE_BUTTONS.items():
ret.buttonEvents.extend(create_button_events(
int(bool(cruise_buttons[button])), self.cruise_buttons[button], {1: button_type},
))
self.cruise_buttons = {button: int(bool(cruise_buttons[button])) for button in SUBARU_CRUISE_BUTTONS}
self.cruise_buttons_msg = copy.copy(cruise_buttons)
if not (self.CP.flags & SubaruFlags.HYBRID): if not (self.CP.flags & SubaruFlags.HYBRID):
self.es_distance_msg = copy.copy(cp_es_distance.vl["ES_Distance"]) self.es_distance_msg = copy.copy(cp_es_distance.vl["ES_Distance"])
+3 -3
View File
@@ -3,7 +3,7 @@ from opendbc.car.disable_ecu import disable_ecu
from opendbc.car.interfaces import CarInterfaceBase from opendbc.car.interfaces import CarInterfaceBase
from opendbc.car.subaru.carcontroller import CarController from opendbc.car.subaru.carcontroller import CarController
from opendbc.car.subaru.carstate import CarState from opendbc.car.subaru.carstate import CarState
from opendbc.car.subaru.values import CAR, CanBus, GLOBAL_ES_ADDR, SubaruFlags, SubaruSafetyFlags from opendbc.car.subaru.values import CAR, CanBus, GLOBAL_ES_ADDR, SUBARU_STOP_START_CARS, SubaruFlags, SubaruSafetyFlags
class CarInterface(CarInterfaceBase): class CarInterface(CarInterfaceBase):
@@ -40,9 +40,9 @@ class CarInterface(CarInterfaceBase):
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.D_PLATFORM.value ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.D_PLATFORM.value
if ret.flags & SubaruFlags.D_PLATFORM_CAMERA: if ret.flags & SubaruFlags.D_PLATFORM_CAMERA:
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.D_PLATFORM_CAMERA.value ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.D_PLATFORM_CAMERA.value
if candidate == CAR.SUBARU_OUTBACK_2023: if candidate in SUBARU_STOP_START_CARS:
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.STOP_START_BUTTON.value ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.STOP_START_BUTTON.value
if candidate in (CAR.SUBARU_LEGACY_2025, CAR.SUBARU_ASCENT_2023): if candidate in (CAR.SUBARU_LEGACY_2025, CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023):
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.FIXED_ANGLE_LIMITS.value ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.FIXED_ANGLE_LIMITS.value
ret.steerLimitTimer = 0.4 ret.steerLimitTimer = 0.4
+32 -3
View File
@@ -3,6 +3,10 @@ from opendbc.car.subaru.values import CanBus
VisualAlert = structs.CarControl.HUDControl.VisualAlert VisualAlert = structs.CarControl.HUDControl.VisualAlert
CRUISE_BUTTON_MAIN = 1
CRUISE_BUTTON_SET = 2
CRUISE_BUTTON_RESUME = 3
def create_steering_control(packer, apply_torque, steer_req): def create_steering_control(packer, apply_torque, steer_req):
values = { values = {
@@ -67,6 +71,19 @@ def create_es_distance(packer, frame, es_distance_msg, bus, pcm_cancel_cmd, long
return packer.make_can_msg("ES_Distance", bus, values) return packer.make_can_msg("ES_Distance", bus, values)
def create_cruise_buttons(packer, frame, cruise_buttons_msg, button, bus=CanBus.main):
values = {s: cruise_buttons_msg[s] for s in [
"CHECKSUM",
"Signal1",
"Signal2",
]}
values["COUNTER"] = frame % 0x10
values["Main"] = button == CRUISE_BUTTON_MAIN
values["Set"] = button == CRUISE_BUTTON_SET
values["Resume"] = button == CRUISE_BUTTON_RESUME
return packer.make_can_msg("Cruise_Buttons", bus, values)
def create_es_lkas_state(packer, frame, es_lkas_state_msg, enabled, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart, def create_es_lkas_state(packer, frame, es_lkas_state_msg, enabled, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart,
bus=CanBus.main): bus=CanBus.main):
values = {s: es_lkas_state_msg[s] for s in [ values = {s: es_lkas_state_msg[s] for s in [
@@ -182,12 +199,24 @@ def create_es_dashstatus(packer, frame, dashstatus_msg, enabled, long_enabled, l
return packer.make_can_msg("ES_DashStatus", bus, values) return packer.make_can_msg("ES_DashStatus", bus, values)
def create_stop_start_control(packer, dashlights_msg, counter=None, bus=CanBus.alt): def create_stop_start_control(packer, dashlights_msg, raw_dat=None, counter=None, bus=CanBus.alt):
"""Create the Outback 2023-24 momentary Stop/Start button request. """Create the supported Subaru momentary Stop/Start button request.
Dashlights is a stock periodic message, so preserve the live frame and only Dashlights is a stock periodic message, so preserve the live frame and only
change the event bit. CANPacker calculates the Subaru checksum for us. change the counter, event bit, and checksum. The raw frame is needed because
the DBC does not describe every byte in this message.
""" """
if raw_dat:
dat = bytearray(raw_dat)
if len(dat) != 8:
raise ValueError(f"Dashlights frame must be 8 bytes, got {len(dat)}")
if counter is None:
counter = (int(dashlights_msg.get("COUNTER", 0)) + 1) % 0x10
dat[1] = (dat[1] & 0xF0) | (counter % 0x10)
dat[6] |= 0x40 # STOP_START, big-endian bit 54
dat[0] = ((0x390 & 0xFF) + ((0x390 >> 8) & 0xFF) + sum(dat[1:])) & 0xFF
return 0x390, bytes(dat), bus
values = dict(dashlights_msg) values = dict(dashlights_msg)
if counter is None: if counter is None:
counter = (int(values.get("COUNTER", 0)) + 1) % 0x10 counter = (int(values.get("COUNTER", 0)) + 1) % 0x10
@@ -5,7 +5,7 @@ from types import SimpleNamespace
import pytest import pytest
from opendbc.can import CANPacker, CANParser from opendbc.can import CANPacker, CANParser
from opendbc.car import Bus, fw_versions, structs from opendbc.car import Bus, fw_versions, gen_empty_fingerprint, structs
from opendbc.car.fw_query_definitions import StdQueries from opendbc.car.fw_query_definitions import StdQueries
from opendbc.car.subaru import subarucan from opendbc.car.subaru import subarucan
from opendbc.car.subaru.carcontroller import CarController from opendbc.car.subaru.carcontroller import CarController
@@ -67,6 +67,56 @@ def test_preglobal_sng_does_not_send_standstill_keepalive_without_manual_toggle(
assert speed_cmd is False assert speed_cmd is False
def test_redneck_cruise_buttons_use_resume_for_increase_and_set_for_decrease():
dbc = DBC[CAR.SUBARU_IMPREZA_2020][Bus.pt]
packer = CANPacker(dbc)
parser = CANParser(dbc, [("Cruise_Buttons", 0)], CanBus.main)
stock_buttons = defaultdict(int)
resume_msg = subarucan.create_cruise_buttons(
packer, 1, stock_buttons, subarucan.CRUISE_BUTTON_RESUME, CanBus.main,
)
parser.update([(1, [resume_msg])])
assert parser.vl["Cruise_Buttons"]["Resume"] == 1
assert parser.vl["Cruise_Buttons"]["Set"] == 0
set_msg = subarucan.create_cruise_buttons(
packer, 2, stock_buttons, subarucan.CRUISE_BUTTON_SET, CanBus.main,
)
parser.update([(2, [set_msg])])
assert parser.vl["Cruise_Buttons"]["Resume"] == 0
assert parser.vl["Cruise_Buttons"]["Set"] == 1
def test_redneck_cruise_is_only_available_on_the_experimental_impreza(monkeypatch):
class FakeParams:
def __init__(self, **_kwargs):
pass
def get_bool(self, key):
return key == "SubaruRedneckCruise"
monkeypatch.setattr("opendbc.car.interfaces.Params", FakeParams)
toggles = SimpleNamespace(subaru_sng=False)
impreza_cp = CarInterface.get_non_essential_params(CAR.SUBARU_IMPREZA_2020)
impreza_fpcp = CarInterface.get_starpilot_params(
CAR.SUBARU_IMPREZA_2020, gen_empty_fingerprint(), [], impreza_cp, toggles,
)
assert impreza_fpcp.redneckCruiseAvailable
assert not impreza_fpcp.pcmCruiseSpeed
assert impreza_cp.openpilotLongitudinalControl
assert impreza_cp.safetyConfigs[0].safetyParam & SubaruSafetyFlags.REDNECK_CRUISE
old_impreza_cp = CarInterface.get_non_essential_params(CAR.SUBARU_IMPREZA)
old_impreza_fpcp = CarInterface.get_starpilot_params(
CAR.SUBARU_IMPREZA, gen_empty_fingerprint(), [], old_impreza_cp, toggles,
)
assert not old_impreza_fpcp.redneckCruiseAvailable
assert old_impreza_fpcp.pcmCruiseSpeed
assert not old_impreza_cp.openpilotLongitudinalControl
class TestSubaruFingerprint: class TestSubaruFingerprint:
def test_eyesight_queries_do_not_change_diagnostic_state(self, monkeypatch): def test_eyesight_queries_do_not_change_diagnostic_state(self, monkeypatch):
camera_requests = [request for request in FW_QUERY_CONFIG.requests if CarParams.Ecu.fwdCamera in request.whitelist_ecus] camera_requests = [request for request in FW_QUERY_CONFIG.requests if CarParams.Ecu.fwdCamera in request.whitelist_ecus]
@@ -194,7 +244,7 @@ def test_outback_2023_uses_d_platform_bus_layout():
assert CP.flags & SubaruFlags.D_PLATFORM assert CP.flags & SubaruFlags.D_PLATFORM
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.STOP_START_BUTTON assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.STOP_START_BUTTON
assert not (CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.LEGACY_2025_ANGLE_LIMITS) assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.FIXED_ANGLE_LIMITS
assert CanBus.main_for_cp(CP) == CanBus.alt assert CanBus.main_for_cp(CP) == CanBus.alt
assert CanBus.angle_for_cp(CP) == CanBus.main assert CanBus.angle_for_cp(CP) == CanBus.main
assert parsers[Bus.pt].bus == CanBus.alt assert parsers[Bus.pt].bus == CanBus.alt
@@ -206,10 +256,32 @@ def test_outback_2023_uses_d_platform_bus_layout():
assert CP.lateralSmoothSeconds == pytest.approx(0.4) assert CP.lateralSmoothSeconds == pytest.approx(0.4)
def test_stop_start_request_is_bounded_and_uses_live_dashlights(): @pytest.mark.parametrize("platform", [CAR.SUBARU_OUTBACK_2023, CAR.SUBARU_LEGACY_2025])
CP = CarInterface.get_non_essential_params(CAR.SUBARU_OUTBACK_2023) def test_stop_start_inputs_are_captured_for_supported_models(platform):
CP = CarInterface.get_non_essential_params(platform)
car_state = CarState(CP, None)
parsers = car_state.get_can_parsers(CP)
raw_dashlights = bytes.fromhex("13031407875a8100")
parsers[Bus.alt].vl["Dashlights"]["COUNTER"] = 6
parsers[Bus.alt].vl["Dashlights"]["STOP_START"] = 0
parsers[Bus.alt].vl["Engine_Stop_Start"]["STOP_START_STATE"] = 3
parsers[Bus.alt].vl_raw["Dashlights"] = raw_dashlights
car_state.update(parsers, SimpleNamespace(subaru_sng=False))
assert car_state.dashlights_msg["COUNTER"] == 6
assert car_state.dashlights_dat == raw_dashlights
assert car_state.stop_start_state == 3
@pytest.mark.parametrize("platform, expected_bus, start_frame", [
(CAR.SUBARU_OUTBACK_2023, CanBus.alt, 101),
(CAR.SUBARU_LEGACY_2025, CanBus.alt, 401),
])
def test_stop_start_request_is_bounded_and_uses_live_dashlights(platform, expected_bus, start_frame):
CP = CarInterface.get_non_essential_params(platform)
controller = CarController({}, CP) controller = CarController({}, CP)
controller.frame = 101 controller.frame = start_frame
class TestActuators: class TestActuators:
steeringAngleDeg = 0.0 steeringAngleDeg = 0.0
@@ -228,6 +300,7 @@ def test_stop_start_request_is_bounded_and_uses_live_dashlights():
CS = SimpleNamespace( CS = SimpleNamespace(
canValid=True, canValid=True,
dashlights_msg={"COUNTER": 6, "STOP_START": 0}, dashlights_msg={"COUNTER": 6, "STOP_START": 0},
dashlights_dat=bytes.fromhex("13061407875a8100"),
stop_start_state=0, stop_start_state=0,
out=SimpleNamespace( out=SimpleNamespace(
standstill=True, standstill=True,
@@ -239,9 +312,10 @@ def test_stop_start_request_is_bounded_and_uses_live_dashlights():
_, can_sends = controller.update(CC, CS, 0, toggles) _, can_sends = controller.update(CC, CS, 0, toggles)
stop_start_msgs = [msg for msg in can_sends if msg[0] == 0x390] stop_start_msgs = [msg for msg in can_sends if msg[0] == 0x390]
assert len(stop_start_msgs) == 1 assert len(stop_start_msgs) == 1
assert stop_start_msgs[0][2] == CanBus.alt assert stop_start_msgs[0][2] == expected_bus
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("Dashlights", 0)], CanBus.alt) assert stop_start_msgs[0][1] == bytes.fromhex("57071407875ac100")
parser.update([(1, [stop_start_msgs[0]])]) parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("Dashlights", 0)], expected_bus)
parser.update([(expected_bus, [stop_start_msgs[0]])])
assert parser.vl["Dashlights"]["STOP_START"] == 1 assert parser.vl["Dashlights"]["STOP_START"] == 1
assert parser.vl["Dashlights"]["COUNTER"] == 7 assert parser.vl["Dashlights"]["COUNTER"] == 7
@@ -262,6 +336,7 @@ def test_legacy_2025_uses_gen2_angle_bus_layout():
assert not (CP.flags & SubaruFlags.D_PLATFORM_CAMERA) assert not (CP.flags & SubaruFlags.D_PLATFORM_CAMERA)
assert not (CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM_CAMERA) assert not (CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM_CAMERA)
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.FIXED_ANGLE_LIMITS assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.FIXED_ANGLE_LIMITS
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.STOP_START_BUTTON
assert CanBus.main_for_cp(CP) == CanBus.main assert CanBus.main_for_cp(CP) == CanBus.main
assert CanBus.angle_for_cp(CP) == CanBus.main assert CanBus.angle_for_cp(CP) == CanBus.main
assert parsers[Bus.pt].bus == CanBus.main assert parsers[Bus.pt].bus == CanBus.main
@@ -351,6 +426,7 @@ def test_legacy_2025_waits_for_manual_steering_to_settle_before_reengaging():
vEgoRaw=6.2, vEgoRaw=6.2,
steeringAngleDeg=-121.55, steeringAngleDeg=-121.55,
steeringRateDeg=350.0, steeringRateDeg=350.0,
steeringTorque=250.0,
steeringPressed=True, steeringPressed=True,
gearShifter=structs.CarState.GearShifter.drive, gearShifter=structs.CarState.GearShifter.drive,
standstill=False, standstill=False,
@@ -359,10 +435,14 @@ def test_legacy_2025_waits_for_manual_steering_to_settle_before_reengaging():
msg = controller.lateral_angle(CC, CS) msg = controller.lateral_angle(CC, CS)
parser.update([(1, [msg])]) parser.update([(1, [msg])])
msg = controller.lateral_angle(CC, CS)
parser.update([(2, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0 assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg) assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
CS.out.steeringPressed = False CS.out.steeringPressed = False
CS.out.steeringTorque = 0.0
CS.out.steeringAngleDeg = -113.78 CS.out.steeringAngleDeg = -113.78
msg = controller.lateral_angle(CC, CS) msg = controller.lateral_angle(CC, CS)
parser.update([(2, [msg])]) parser.update([(2, [msg])])
@@ -411,6 +491,7 @@ def test_legacy_2025_manual_handoff_reclaim_is_gradual():
vEgoRaw=3.7, vEgoRaw=3.7,
steeringAngleDeg=2.5, steeringAngleDeg=2.5,
steeringRateDeg=-45.0, steeringRateDeg=-45.0,
steeringTorque=250.0,
steeringPressed=True, steeringPressed=True,
gearShifter=structs.CarState.GearShifter.drive, gearShifter=structs.CarState.GearShifter.drive,
standstill=False, standstill=False,
@@ -419,9 +500,13 @@ def test_legacy_2025_manual_handoff_reclaim_is_gradual():
msg = controller.lateral_angle(CC, CS) msg = controller.lateral_angle(CC, CS)
parser.update([(1, [msg])]) parser.update([(1, [msg])])
msg = controller.lateral_angle(CC, CS)
parser.update([(2, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0 assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
CS.out.steeringPressed = False CS.out.steeringPressed = False
CS.out.steeringTorque = 0.0
CS.out.steeringRateDeg = 0.0 CS.out.steeringRateDeg = 0.0
for i in range(19): for i in range(19):
msg = controller.lateral_angle(CC, CS) msg = controller.lateral_angle(CC, CS)
@@ -461,6 +546,25 @@ def test_ascent_2023_uses_gen2_angle_bus_layout():
assert controller.status_bus == CanBus.main assert controller.status_bus == CanBus.main
def test_ascent_steering_rate_retains_last_can_sample():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023)
car_state = CarState(CP, None)
parsers = car_state.get_can_parsers(CP)
toggles = SimpleNamespace(subaru_sng=False)
parsers[Bus.pt].vl["Steering_2"]["Steering_Angle"] = 1.0
parsers[Bus.pt].vl["Steering_2"]["COUNTER"] = 1
car_state.update(parsers, toggles)
parsers[Bus.pt].vl["Steering_2"]["Steering_Angle"] = 2.0
parsers[Bus.pt].vl["Steering_2"]["COUNTER"] = 2
state, _ = car_state.update(parsers, toggles)
assert state.steeringRateDeg == pytest.approx(50.0)
state, _ = car_state.update(parsers, toggles)
assert state.steeringRateDeg == pytest.approx(50.0)
def test_other_angle_platforms_keep_existing_bus_layout(): def test_other_angle_platforms_keep_existing_bus_layout():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_CROSSTREK_2025) CP = CarInterface.get_non_essential_params(CAR.SUBARU_CROSSTREK_2025)
parsers = CarState.get_can_parsers(CP) parsers = CarState.get_can_parsers(CP)
@@ -480,6 +584,10 @@ def test_angle_controller_tracks_driver_override():
msg = controller.lateral_angle(CC, CS) msg = controller.lateral_angle(CC, CS)
assert not controller.driver_override
msg = controller.lateral_angle(CC, CS)
assert controller.driver_override assert controller.driver_override
assert controller.p.STEER_OVERRIDE_TORQUE_HIGH == 150 assert controller.p.STEER_OVERRIDE_TORQUE_HIGH == 150
assert controller.p.STEER_OVERRIDE_TORQUE_LOW == 100 assert controller.p.STEER_OVERRIDE_TORQUE_LOW == 100
@@ -533,15 +641,16 @@ def test_angle_controller_blocks_low_speed_mads_engagement():
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1 assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
def test_ascent_angle_controller_uses_fixed_angle_rate_limits(): @pytest.mark.parametrize("platform", (CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023))
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023) def test_angle_controller_uses_fixed_angle_rate_limits(platform):
CP = CarInterface.get_non_essential_params(platform)
controller = CarController({}, CP) controller = CarController({}, CP)
CC = SimpleNamespace(enabled=True, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=-14.88)) CC = SimpleNamespace(enabled=True, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=-14.88))
CS = SimpleNamespace(out=SimpleNamespace( CS = SimpleNamespace(out=SimpleNamespace(
vEgoRaw=21.66, vEgoRaw=21.66,
steeringAngleDeg=-25.77, steeringAngleDeg=-25.77,
steeringRateDeg=0.0, steeringRateDeg=0.0,
steeringTorque=-149.0, steeringTorque=-250.0,
steeringPressed=False, steeringPressed=False,
gearShifter=structs.CarState.GearShifter.drive, gearShifter=structs.CarState.GearShifter.drive,
standstill=False, standstill=False,
@@ -563,7 +672,7 @@ def test_angle_controller_yields_until_manual_steering_settles(platform):
vEgoRaw=21.66, vEgoRaw=21.66,
steeringAngleDeg=-25.06, steeringAngleDeg=-25.06,
steeringRateDeg=35.0, steeringRateDeg=35.0,
steeringTorque=-149.0, steeringTorque=-250.0,
steeringPressed=True, steeringPressed=True,
gearShifter=structs.CarState.GearShifter.drive, gearShifter=structs.CarState.GearShifter.drive,
standstill=False, standstill=False,
@@ -572,10 +681,14 @@ def test_angle_controller_yields_until_manual_steering_settles(platform):
msg = controller.lateral_angle(CC, CS) msg = controller.lateral_angle(CC, CS)
parser.update([(1, [msg])]) parser.update([(1, [msg])])
msg = controller.lateral_angle(CC, CS)
parser.update([(2, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0 assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg) assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
CS.out.steeringPressed = False CS.out.steeringPressed = False
CS.out.steeringTorque = 0.0
CS.out.steeringAngleDeg = -17.91 CS.out.steeringAngleDeg = -17.91
CS.out.steeringRateDeg = 0.0 CS.out.steeringRateDeg = 0.0
for i in range(18): for i in range(18):
@@ -613,11 +726,20 @@ def test_ascent_angle_controller_blocks_parking_lot_aol_engagement():
CS.out.steeringRateDeg = 0.0 CS.out.steeringRateDeg = 0.0
msg = controller.lateral_angle(CC, CS) msg = controller.lateral_angle(CC, CS)
parser.update([(2, [msg])]) parser.update([(2, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
for i in range(8):
msg = controller.lateral_angle(CC, CS)
parser.update([(3 + i, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
msg = controller.lateral_angle(CC, CS)
parser.update([(11, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1 assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
CS.out.gearShifter = structs.CarState.GearShifter.reverse CS.out.gearShifter = structs.CarState.GearShifter.reverse
msg = controller.lateral_angle(CC, CS) msg = controller.lateral_angle(CC, CS)
parser.update([(3, [msg])]) parser.update([(12, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0 assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg) assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
+10
View File
@@ -89,6 +89,7 @@ class SubaruSafetyFlags(IntFlag):
D_PLATFORM_CAMERA = 64 D_PLATFORM_CAMERA = 64
FIXED_ANGLE_LIMITS = 128 FIXED_ANGLE_LIMITS = 128
STOP_START_BUTTON = 256 STOP_START_BUTTON = 256
REDNECK_CRUISE = 512
LEGACY_2025_ANGLE_LIMITS = FIXED_ANGLE_LIMITS LEGACY_2025_ANGLE_LIMITS = FIXED_ANGLE_LIMITS
@@ -270,6 +271,15 @@ class CAR(Platforms):
) )
SUBARU_STOP_START_CARS = (
CAR.SUBARU_OUTBACK_2023,
CAR.SUBARU_LEGACY_2025,
)
SUBARU_REDNECK_CRUISE_CARS = (
CAR.SUBARU_IMPREZA_2020,
)
SUBARU_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ SUBARU_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION) p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION)
SUBARU_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ SUBARU_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
@@ -68,13 +68,13 @@ class CarController(CarControllerBase):
state = 13 if CC.cruiseControl.cancel else 4 # 4=ACC_ON, 13=ACC_CANCEL_GENERIC_SILENT state = 13 if CC.cruiseControl.cancel else 4 # 4=ACC_ON, 13=ACC_CANCEL_GENERIC_SILENT
accel = float(np.clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX)) accel = float(np.clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX))
cntr = (self.frame // 4) % 8 cntr = (self.frame // 4) % 8
can_sends.append(self.tesla_can.create_longitudinal_command(state, accel, cntr, CS.out.vEgo, CC.longActive)) can_sends.append(self.tesla_can.create_longitudinal_command(state, accel, cntr, self.frame, CS.out.vEgo, CS.out.gasPressed))
else: else:
# Increment counter so cancel is prioritized even without openpilot longitudinal # Increment counter so cancel is prioritized even without openpilot longitudinal
if CC.cruiseControl.cancel: if CC.cruiseControl.cancel:
cntr = (CS.das_control["DAS_controlCounter"] + 1) % 8 cntr = (CS.das_control["DAS_controlCounter"] + 1) % 8
can_sends.append(self.tesla_can.create_longitudinal_command(13, 0, cntr, CS.out.vEgo, False)) can_sends.append(self.tesla_can.create_longitudinal_command(13, 0, cntr, self.frame, CS.out.vEgo, False))
# TODO: HUD control # TODO: HUD control
new_actuators = actuators.as_builder() new_actuators = actuators.as_builder()
@@ -88,8 +88,7 @@ class TeslaCANPreAP:
else: else:
values.update(_STW_DEFAULTS) values.update(_STW_DEFAULTS)
# Preserve the live stalk layout, but force VSL enable on engage/resume spoofs. values["VSL_Enbl_Rq"] = 1
values["VSL_Enbl_Rq"] = 0 if button_to_press == 1 else 1
data = self.packer.make_can_msg("STW_ACTN_RQ", bus, values)[1] data = self.packer.make_can_msg("STW_ACTN_RQ", bus, values)[1]
values["CRC_STW_ACTN_RQ"] = _crc8_stw(data[:7]) values["CRC_STW_ACTN_RQ"] = _crc8_stw(data[:7])
@@ -0,0 +1,29 @@
"""Byte-level invariants for Pre-AP stalk spoof frames."""
from opendbc.can import CANPacker
from opendbc.car.tesla.preap.teslacan import TeslaCANPreAP, _STW_DEFAULTS
from opendbc.car.tesla.values import CANBUS, CruiseButtons
def _spoof(button):
tc = TeslaCANPreAP(CANPacker("tesla_can"))
msg_stw = {"MC_STW_ACTN_RQ": 5, "CRC_STW_ACTN_RQ": 0, "DTR_Dist_Rq": 255}
msg_stw.update(_STW_DEFAULTS)
msg_stw["VSL_Enbl_Rq"] = 0
_, dat, _ = tc.create_action_request(button, CANBUS.party, 6, msg_stw)
return dat
def test_vsl_enable_bit_is_set_on_cancel():
dat = _spoof(CruiseButtons.CANCEL)
assert (dat[0] >> 6) & 1 == 1
def test_vsl_enable_bit_is_set_on_set_accel():
dat = _spoof(CruiseButtons.SET_ACCEL)
assert (dat[0] >> 6) & 1 == 1
def test_stalk_button_and_vsl_bits_match_real_set_accel_frame():
dat = _spoof(CruiseButtons.SET_ACCEL)
assert dat[0] == 0x50
+11 -3
View File
@@ -1,3 +1,5 @@
import numpy as np
from opendbc.car.common.conversions import Conversions as CV from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.tesla.values import CANBUS, CarControllerParams from opendbc.car.tesla.values import CANBUS, CarControllerParams
@@ -5,6 +7,7 @@ from opendbc.car.tesla.values import CANBUS, CarControllerParams
class TeslaCAN: class TeslaCAN:
def __init__(self, packer): def __init__(self, packer):
self.packer = packer self.packer = packer
self.gas_release_frame = 0
def create_steering_control(self, angle, enabled): def create_steering_control(self, angle, enabled):
values = { values = {
@@ -15,15 +18,20 @@ class TeslaCAN:
return self.packer.make_can_msg("DAS_steeringControl", CANBUS.party, values) return self.packer.make_can_msg("DAS_steeringControl", CANBUS.party, values)
def create_longitudinal_command(self, acc_state, accel, counter, v_ego, active): def create_longitudinal_command(self, acc_state, accel, counter, frame, v_ego, gas_pressed):
set_speed = min(max(v_ego + accel, 0) * CV.MS_TO_KPH, 400) set_speed = min(max(v_ego + accel, 0) * CV.MS_TO_KPH, 400)
if gas_pressed:
self.gas_release_frame = frame
jerk = float(np.interp(frame - self.gas_release_frame, [0, 100], [0.0, CarControllerParams.JERK_LIMIT_MAX]))
values = { values = {
"DAS_setSpeed": set_speed, "DAS_setSpeed": set_speed,
"DAS_accState": acc_state, "DAS_accState": acc_state,
"DAS_aebEvent": 0, "DAS_aebEvent": 0,
"DAS_jerkMin": CarControllerParams.JERK_LIMIT_MIN, "DAS_jerkMin": -jerk,
"DAS_jerkMax": CarControllerParams.JERK_LIMIT_MAX, "DAS_jerkMax": jerk,
"DAS_accelMin": accel, "DAS_accelMin": accel,
"DAS_accelMax": max(accel, 0), "DAS_accelMax": max(accel, 0),
"DAS_controlCounter": counter, "DAS_controlCounter": counter,
@@ -3,6 +3,7 @@ import pytest
from opendbc.car.common.conversions import Conversions as CV from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.tesla.carstate import update_tesla_gas_pressed from opendbc.car.tesla.carstate import update_tesla_gas_pressed
from opendbc.car.tesla.teslacan import TeslaCAN from opendbc.car.tesla.teslacan import TeslaCAN
from opendbc.car.tesla.values import CarControllerParams
class RecordingPacker: class RecordingPacker:
@@ -10,7 +11,6 @@ class RecordingPacker:
return name, bus, values return name, bus, values
@pytest.mark.parametrize("active", [False, True])
@pytest.mark.parametrize( @pytest.mark.parametrize(
("v_ego", "accel", "expected_set_speed"), ("v_ego", "accel", "expected_set_speed"),
[ [
@@ -20,12 +20,37 @@ class RecordingPacker:
(120.0, 2.0, 400.0), (120.0, 2.0, 400.0),
], ],
) )
def test_longitudinal_set_speed_tracks_accel_continuously(active, v_ego, accel, expected_set_speed): def test_longitudinal_set_speed_tracks_accel_continuously(v_ego, accel, expected_set_speed):
_, _, values = TeslaCAN(RecordingPacker()).create_longitudinal_command(4, accel, 0, v_ego, active) _, _, values = TeslaCAN(RecordingPacker()).create_longitudinal_command(4, accel, 0, 200, v_ego, False)
assert values["DAS_setSpeed"] == pytest.approx(expected_set_speed) assert values["DAS_setSpeed"] == pytest.approx(expected_set_speed)
def test_longitudinal_jerk_ramps_after_gas_release():
can = TeslaCAN(RecordingPacker())
_, _, pressed = can.create_longitudinal_command(4, 0, 0, 20, 20, True)
_, _, halfway = can.create_longitudinal_command(4, 0, 1, 70, 20, False)
_, _, complete = can.create_longitudinal_command(4, 0, 2, 120, 20, False)
assert pressed["DAS_jerkMin"] == pytest.approx(0.0)
assert pressed["DAS_jerkMax"] == pytest.approx(0.0)
assert halfway["DAS_jerkMin"] == pytest.approx(-CarControllerParams.JERK_LIMIT_MAX / 2)
assert halfway["DAS_jerkMax"] == pytest.approx(CarControllerParams.JERK_LIMIT_MAX / 2)
assert complete["DAS_jerkMin"] == pytest.approx(-CarControllerParams.JERK_LIMIT_MAX)
assert complete["DAS_jerkMax"] == pytest.approx(CarControllerParams.JERK_LIMIT_MAX)
def test_longitudinal_jerk_release_timer_resets_while_gas_is_pressed():
can = TeslaCAN(RecordingPacker())
can.create_longitudinal_command(4, 0, 0, 20, 20, True)
_, _, values = can.create_longitudinal_command(4, 0, 1, 80, 20, True)
assert values["DAS_jerkMin"] == pytest.approx(0.0)
assert values["DAS_jerkMax"] == pytest.approx(0.0)
def test_tesla_gas_pressed_hysteresis_prevents_release_chatter(): def test_tesla_gas_pressed_hysteresis_prevents_release_chatter():
assert update_tesla_gas_pressed(False, 0.4) is False assert update_tesla_gas_pressed(False, 0.4) is False
assert update_tesla_gas_pressed(False, 0.8) is False assert update_tesla_gas_pressed(False, 0.8) is False
-1
View File
@@ -125,7 +125,6 @@ class CarControllerParams:
ACCEL_MAX = 2.0 # m/s^2 ACCEL_MAX = 2.0 # m/s^2
ACCEL_MIN = -3.48 # m/s^2 ACCEL_MIN = -3.48 # m/s^2
JERK_LIMIT_MAX = 4.9 # m/s^3, ACC faults at 5.0 JERK_LIMIT_MAX = 4.9 # m/s^3, ACC faults at 5.0
JERK_LIMIT_MIN = -4.9 # m/s^3, ACC faults at 5.0
class TeslaSafetyFlags(IntFlag): class TeslaSafetyFlags(IntFlag):
+8
View File
@@ -14,6 +14,7 @@ from opendbc.car.tesla.values import CAR as TESLA
from opendbc.car.toyota.values import CAR as TOYOTA from opendbc.car.toyota.values import CAR as TOYOTA
from opendbc.car.values import Platform from opendbc.car.values import Platform
from opendbc.car.volkswagen.values import CAR as VOLKSWAGEN from opendbc.car.volkswagen.values import CAR as VOLKSWAGEN
from opendbc.car.volvo.values import CAR as VOLVO
from opendbc.car.body.values import CAR as COMMA from opendbc.car.body.values import CAR as COMMA
from opendbc.car.psa.values import CAR as PSA from opendbc.car.psa.values import CAR as PSA
@@ -75,6 +76,7 @@ non_tested_cars = [
HYUNDAI.HYUNDAI_ELANTRA_HEV_2024, HYUNDAI.HYUNDAI_ELANTRA_HEV_2024,
HYUNDAI.HYUNDAI_KONA_EV_NON_SCC, HYUNDAI.HYUNDAI_KONA_EV_NON_SCC,
HYUNDAI.HYUNDAI_KONA_NON_SCC, HYUNDAI.HYUNDAI_KONA_NON_SCC,
HYUNDAI.KIA_RAY_EV,
HYUNDAI.HYUNDAI_PALISADE_2023, HYUNDAI.HYUNDAI_PALISADE_2023,
HYUNDAI.KIA_CEED_PHEV_2022_NON_SCC, HYUNDAI.KIA_CEED_PHEV_2022_NON_SCC,
HYUNDAI.KIA_FORTE_2019_NON_SCC, HYUNDAI.KIA_FORTE_2019_NON_SCC,
@@ -105,6 +107,12 @@ non_tested_cars = [
TOYOTA.TOYOTA_COROLLA, TOYOTA.TOYOTA_COROLLA,
TOYOTA.TOYOTA_RAV4H, TOYOTA.TOYOTA_RAV4H,
# No recorded routes yet
VOLVO.VOLVO_V40,
VOLVO.VOLVO_XC40_RECHARGE,
VOLVO.VOLVO_S60_RECHARGE,
VOLVO.POLESTAR_2,
] ]
non_tested_cars.extend(CC_ONLY_CAR) non_tested_cars.extend(CC_ONLY_CAR)
@@ -300,6 +300,89 @@ class TestCarInterfaces:
) )
assert fp_car_params.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value assert fp_car_params.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
def test_hyundai_elantra_hev_auto_aol_uses_main_engage_flag(self):
toggles = get_test_starpilot_toggles()
toggles.always_on_lateral_main = True
fingerprint = {bus: {} for bus in range(8)}
car_params = HyundaiCarInterface.get_params(
HYUNDAI_CAR.HYUNDAI_ELANTRA_HEV_2024,
fingerprint,
[],
alpha_long=True,
is_release=False,
docs=False,
starpilot_toggles=toggles,
)
fp_car_params = HyundaiCarInterface.get_starpilot_params(
HYUNDAI_CAR.HYUNDAI_ELANTRA_HEV_2024,
fingerprint,
[],
car_params,
toggles,
)
assert fp_car_params.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_ON_ENGAGE.value
assert not (fp_car_params.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value)
def test_hyundai_elantra_hev_lkas_aol_uses_lkas_engage_flag(self):
toggles = get_test_starpilot_toggles()
toggles.always_on_lateral_lkas = True
fingerprint = {bus: {} for bus in range(8)}
car_params = HyundaiCarInterface.get_params(
HYUNDAI_CAR.HYUNDAI_ELANTRA_HEV_2024,
fingerprint,
[],
alpha_long=True,
is_release=False,
docs=False,
starpilot_toggles=toggles,
)
fp_car_params = HyundaiCarInterface.get_starpilot_params(
HYUNDAI_CAR.HYUNDAI_ELANTRA_HEV_2024,
fingerprint,
[],
car_params,
toggles,
)
assert fp_car_params.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
assert not (fp_car_params.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_ON_ENGAGE.value)
@pytest.mark.parametrize(
("candidate", "sets_main_aol_flag"),
(
(HYUNDAI_CAR.HYUNDAI_SONATA_HYBRID, True),
(HYUNDAI_CAR.HYUNDAI_SONATA, False),
),
)
def test_hyundai_main_aol_engage_flag_is_scoped_to_hybrid(self, candidate, sets_main_aol_flag):
toggles = get_test_starpilot_toggles()
toggles.always_on_lateral_main = True
fingerprint = {bus: {} for bus in range(8)}
car_params = HyundaiCarInterface.get_params(
candidate,
fingerprint,
[],
alpha_long=True,
is_release=False,
docs=False,
starpilot_toggles=toggles,
)
fp_car_params = HyundaiCarInterface.get_starpilot_params(
candidate,
fingerprint,
[],
car_params,
toggles,
)
has_main_aol_flag = bool(fp_car_params.safetyConfigs[-1].safetyParam &
HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_ON_ENGAGE.value)
assert has_main_aol_flag is sets_main_aol_flag
def test_toyota_disable_openpilot_long_sets_stock_long_safety_flag(self): def test_toyota_disable_openpilot_long_sets_stock_long_safety_flag(self):
CarInterface = interfaces[TOYOTA_CAR.TOYOTA_PRIUS_TSS2] CarInterface = interfaces[TOYOTA_CAR.TOYOTA_PRIUS_TSS2]
fingerprint = {bus: {} for bus in range(8)} fingerprint = {bus: {} for bus in range(8)}
@@ -283,6 +283,7 @@ class TestFwFingerprintTiming:
'tesla': 0.1, 'tesla': 0.1,
'toyota': 0.7, 'toyota': 0.7,
'volkswagen': 0.65, 'volkswagen': 0.65,
'volvo': 0.0,
'rivian': 0.3, 'rivian': 0.3,
'psa': 0.1, 'psa': 0.1,
}, },
@@ -106,6 +106,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
"KIA_CARNIVAL_4TH_GEN" = [1.75, 1.75, 0.15] "KIA_CARNIVAL_4TH_GEN" = [1.75, 1.75, 0.15]
"KIA_CARNIVAL_2025" = [1.75, 1.75, 0.15] "KIA_CARNIVAL_2025" = [1.75, 1.75, 0.15]
"KIA_CARNIVAL_HEV_4TH_GEN" = [1.75, 1.75, 0.15] "KIA_CARNIVAL_HEV_4TH_GEN" = [1.75, 1.75, 0.15]
"KIA_RAY_EV" = [1.8, 2.0, 0.15]
"GMC_ACADIA" = [1.6, 1.6, 0.2] "GMC_ACADIA" = [1.6, 1.6, 0.2]
"LEXUS_IS_TSS2" = [2.0, 2.0, 0.1] "LEXUS_IS_TSS2" = [2.0, 2.0, 0.1]
"HYUNDAI_KONA_EV_2ND_GEN" = [2.5, 2.5, 0.1] "HYUNDAI_KONA_EV_2ND_GEN" = [2.5, 2.5, 0.1]
@@ -142,6 +143,9 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
"HONDA_NBOX_2G" = [1.2, 1.2, 0.2] "HONDA_NBOX_2G" = [1.2, 1.2, 0.2]
"ACURA_TLX_2G" = [1.2, 1.2, 0.15] "ACURA_TLX_2G" = [1.2, 1.2, 0.15]
"PORSCHE_MACAN_MK1" = [2.0, 2.0, 0.2] "PORSCHE_MACAN_MK1" = [2.0, 2.0, 0.2]
"VOLVO_XC40_RECHARGE" = [1.5, 1.5, 0.1]
"VOLVO_S60_RECHARGE" = [1.5, 1.5, 0.1]
"VOLVO_V40" = [1.5, 1.5, 0.1]
# Dashcam or fallback configured as ideal car # Dashcam or fallback configured as ideal car
"MOCK" = [10.0, 10, 0.0] "MOCK" = [10.0, 10, 0.0]
@@ -136,3 +136,5 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
"CHEVROLET_SILVERADO_CC" = "CHEVROLET_SILVERADO" "CHEVROLET_SILVERADO_CC" = "CHEVROLET_SILVERADO"
"CADILLAC_XT4_CC" = "CADILLAC_XT4" "CADILLAC_XT4_CC" = "CADILLAC_XT4"
"CADILLAC_XT6" = "GMC_ACADIA" "CADILLAC_XT6" = "GMC_ACADIA"
"POLESTAR_2" = "VOLVO_XC40_RECHARGE"
@@ -11,7 +11,7 @@ from opendbc.car.interfaces import CarControllerBase
from opendbc.car.toyota import toyotacan from opendbc.car.toyota import toyotacan
from opendbc.car.toyota.values import CAR, MIN_ACC_SPEED, NO_STOP_TIMER_CAR, PEDAL_TRANSITION, TSS2_CAR, \ from opendbc.car.toyota.values import CAR, MIN_ACC_SPEED, NO_STOP_TIMER_CAR, PEDAL_TRANSITION, TSS2_CAR, \
CarControllerParams, ToyotaFlags, \ CarControllerParams, ToyotaFlags, \
UNSUPPORTED_DSU_CAR, LEGACY_PRIUS_CAR UNSUPPORTED_DSU_CAR, LEGACY_PRIUS_CAR, TOYOTA_AUTO_HOLD_CARS
from opendbc.can import CANPacker from opendbc.can import CANPacker
Ecu = structs.CarParams.Ecu Ecu = structs.CarParams.Ecu
@@ -37,12 +37,16 @@ TOYOTA_COAST_BRAKE_DISABLE_ACCEL = -0.06 # m/s^2
TOYOTA_NO_LEAD_COAST_BRAKE_ACCEL = -0.30 # m/s^2 TOYOTA_NO_LEAD_COAST_BRAKE_ACCEL = -0.30 # m/s^2
TOYOTA_INTERCEPTOR_COMFORT_TARGET_ACCEL = 2.0 # m/s^2 TOYOTA_INTERCEPTOR_COMFORT_TARGET_ACCEL = 2.0 # m/s^2
TOYOTA_NO_LEAD_CRUISE_SIGN_FLIP_MIN_SET_SPEED_ERROR = 0.35 # m/s TOYOTA_NO_LEAD_CRUISE_SIGN_FLIP_MIN_SET_SPEED_ERROR = 0.35 # m/s
TOYOTA_RAV4_LAUNCH_PEDAL_BLEND_SPEED = 5.0 # m/s
TOYOTA_RAV4_LAUNCH_PEDAL_SCALE = 0.11
TOYOTA_RAV4_LOW_SPEED_PEDAL_SCALE = 0.23
TOYOTA_AUTO_HOLD_ACCEL = -1.0
TOYOTA_AUTO_HOLD_ACTIVATION_FRAMES = 100
# LKA limits # LKA limits
# EPS faults if you apply torque while the steering rate is above 100 deg/s for too long # EPS faults if you apply torque while the steering rate is above 100 deg/s for too long
MAX_STEER_RATE = 100 # deg/s MAX_STEER_RATE = 100 # deg/s
MAX_STEER_RATE_FRAMES = 18 # tx control frames needed before torque can be cut MAX_STEER_RATE_FRAMES = 18 # tx control frames needed before torque can be cut
TOYOTA_HIGHLANDER_TSS2_MAX_STEER_RATE_FRAMES = 8
# EPS allows user torque above threshold for 50 frames before permanently faulting # EPS allows user torque above threshold for 50 frames before permanently faulting
MAX_USER_TORQUE = 500 MAX_USER_TORQUE = 500
@@ -73,9 +77,20 @@ def should_bypass_toyota_long_pid(CP, starpilot_toggles=None) -> bool:
) or highlander_sdsu) ) or highlander_sdsu)
def get_steer_rate_limit_frames(car_fingerprint) -> int: def supports_toyota_auto_hold(CP, auto_hold_enabled: bool) -> bool:
return (TOYOTA_HIGHLANDER_TSS2_MAX_STEER_RATE_FRAMES return (
if car_fingerprint == CAR.TOYOTA_HIGHLANDER_TSS2 else MAX_STEER_RATE_FRAMES) auto_hold_enabled and
CP.carFingerprint in TOYOTA_AUTO_HOLD_CARS and
bool(CP.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value)
)
def get_rav4_interceptor_pedal_scale(v_ego: float) -> float:
return float(np.interp(
max(float(v_ego), 0.0),
[0.0, TOYOTA_RAV4_LAUNCH_PEDAL_BLEND_SPEED, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION],
[TOYOTA_RAV4_LAUNCH_PEDAL_SCALE, TOYOTA_RAV4_LOW_SPEED_PEDAL_SCALE, 0.3, 0.0],
))
def get_long_tune(CP, params): def get_long_tune(CP, params):
@@ -229,7 +244,6 @@ class CarController(CarControllerBase):
self.standstill_req = False self.standstill_req = False
self.permit_braking = True self.permit_braking = True
self.steer_rate_counter = 0 self.steer_rate_counter = 0
self.steer_rate_limit_frames = get_steer_rate_limit_frames(self.CP.carFingerprint)
self.distance_button = 0 self.distance_button = 0
# *** start long control state *** # *** start long control state ***
@@ -250,11 +264,8 @@ class CarController(CarControllerBase):
self.secoc_prev_reset_counter = 0 self.secoc_prev_reset_counter = 0
self.doors_locked = False self.doors_locked = False
self.auto_brake_hold = bool(self.CP.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value)
self.brake_hold_active = False self.brake_hold_active = False
self._brake_hold_counter = 0 self._brake_hold_counter = 0
self._brake_hold_reset = False
self._prev_brake_pressed = False
def _compute_interceptor_gas_cmd(self, CC, CS): def _compute_interceptor_gas_cmd(self, CC, CS):
if not (self.CP.enableGasInterceptorDEPRECATED and self.CP.openpilotLongitudinalControl and CC.longActive): if not (self.CP.enableGasInterceptorDEPRECATED and self.CP.openpilotLongitudinalControl and CC.longActive):
@@ -265,7 +276,7 @@ class CarController(CarControllerBase):
max_interceptor_gas = 0.5 max_interceptor_gas = 0.5
if self.CP.carFingerprint == CAR.TOYOTA_RAV4: if self.CP.carFingerprint == CAR.TOYOTA_RAV4:
pedal_scale = float(np.interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.15, 0.3, 0.0])) pedal_scale = get_rav4_interceptor_pedal_scale(CS.out.vEgo)
elif self.CP.carFingerprint in (CAR.TOYOTA_COROLLA, CAR.TOYOTA_MATRIX_RETROFIT): elif self.CP.carFingerprint in (CAR.TOYOTA_COROLLA, CAR.TOYOTA_MATRIX_RETROFIT):
pedal_scale = float(np.interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.3, 0.4, 0.0])) pedal_scale = float(np.interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.3, 0.4, 0.0]))
else: else:
@@ -300,26 +311,24 @@ class CarController(CarControllerBase):
self.last_standstill = CS.out.standstill self.last_standstill = CS.out.standstill
def create_auto_brake_hold_messages(self, CS: structs.CarState, brake_hold_allowed_timer: int = 100): def update_auto_hold_state(self, CS: structs.CarState, cancel_requested: bool = False,
can_sends = [] activation_frames: int = TOYOTA_AUTO_HOLD_ACTIVATION_FRAMES):
brake_hold_allowed = (CS.out.standstill and CS.out.cruiseState.available and brake_hold_allowed = (not cancel_requested and CS.out.standstill and CS.out.cruiseState.available and
not CS.out.gasPressed and not CS.out.cruiseState.enabled and not CS.out.gasPressed and not CS.out.cruiseState.enabled and
CS.out.gearShifter not in (PARK, REVERSE)) CS.out.gearShifter not in (PARK, REVERSE))
if brake_hold_allowed: if brake_hold_allowed and not self.brake_hold_active and CS.out.brakePressed:
self._brake_hold_counter += 1 self._brake_hold_counter += 1
self.brake_hold_active = self._brake_hold_counter > brake_hold_allowed_timer and not self._brake_hold_reset self.brake_hold_active = self._brake_hold_counter > activation_frames
self._brake_hold_reset = not self._prev_brake_pressed and CS.out.brakePressed and not self._brake_hold_reset elif not brake_hold_allowed:
else:
self._brake_hold_counter = 0 self._brake_hold_counter = 0
self.brake_hold_active = False self.brake_hold_active = False
self._brake_hold_reset = False
self._prev_brake_pressed = CS.out.brakePressed
if self.frame % 2 == 0: return self.brake_hold_active
can_sends.append(toyotacan.create_brake_hold_command(self.packer, self.frame, CS.pre_collision_2, self.brake_hold_active))
return can_sends def reset_auto_hold_state(self):
self._brake_hold_counter = 0
self.brake_hold_active = False
def update(self, CC, CS, now_nanos, starpilot_toggles): def update(self, CC, CS, now_nanos, starpilot_toggles):
actuators = CC.actuators actuators = CC.actuators
@@ -354,7 +363,7 @@ class CarController(CarControllerBase):
# >100 degree/sec steering fault prevention # >100 degree/sec steering fault prevention
self.steer_rate_counter, apply_steer_req = common_fault_avoidance( self.steer_rate_counter, apply_steer_req = common_fault_avoidance(
abs(CS.out.steeringRateDeg) >= MAX_STEER_RATE, lat_active, abs(CS.out.steeringRateDeg) >= MAX_STEER_RATE, lat_active,
self.steer_rate_counter, self.steer_rate_limit_frames, self.steer_rate_counter, MAX_STEER_RATE_FRAMES,
) )
if not lat_active: if not lat_active:
@@ -416,8 +425,10 @@ class CarController(CarControllerBase):
# *** gas and brake *** # *** gas and brake ***
self._update_standstill_request(CC, CS, actuators, starpilot_toggles) self._update_standstill_request(CC, CS, actuators, starpilot_toggles)
if self.auto_brake_hold: if supports_toyota_auto_hold(self.CP, getattr(starpilot_toggles, "toyota_auto_hold", False)):
can_sends.extend(self.create_auto_brake_hold_messages(CS)) self.update_auto_hold_state(CS, pcm_cancel_cmd)
else:
self.reset_auto_hold_state()
interceptor_gas_cmd = self._compute_interceptor_gas_cmd(CC, CS) interceptor_gas_cmd = self._compute_interceptor_gas_cmd(CC, CS)
@@ -525,6 +536,11 @@ class CarController(CarControllerBase):
pcm_accel_cmd = float(np.clip(pcm_accel_cmd, self.params.ACCEL_MIN, self.params.ACCEL_MAX)) pcm_accel_cmd = float(np.clip(pcm_accel_cmd, self.params.ACCEL_MIN, self.params.ACCEL_MAX))
if self.brake_hold_active:
pcm_accel_cmd = TOYOTA_AUTO_HOLD_ACCEL
self.permit_braking = True
self.standstill_req = True
main_accel_cmd = 0. if self.CP.flags & ToyotaFlags.SECOC.value else pcm_accel_cmd main_accel_cmd = 0. if self.CP.flags & ToyotaFlags.SECOC.value else pcm_accel_cmd
can_sends.append(toyotacan.create_accel_command(self.packer, main_accel_cmd, pcm_cancel_cmd, self.permit_braking, self.standstill_req, lead, can_sends.append(toyotacan.create_accel_command(self.packer, main_accel_cmd, pcm_cancel_cmd, self.permit_braking, self.standstill_req, lead,
CS.acc_type, fcw_alert, self.distance_button, CS.acc_type, fcw_alert, self.distance_button,
+16 -8
View File
@@ -75,6 +75,7 @@ class CarState(CarStateBase):
self.distance_button = 0 self.distance_button = 0
self.pcm_follow_distance = 0 self.pcm_follow_distance = 0
self.pcm_acc_status = 0
self.acc_type = 1 self.acc_type = 1
self.lkas_hud = {} self.lkas_hud = {}
@@ -89,8 +90,6 @@ class CarState(CarStateBase):
self.has_can_filter = self.FPCP.flags & ToyotaStarPilotFlags.RADAR_CAN_FILTER.value self.has_can_filter = self.FPCP.flags & ToyotaStarPilotFlags.RADAR_CAN_FILTER.value
self.has_SDSU = self.FPCP.flags & ToyotaStarPilotFlags.SMART_DSU.value self.has_SDSU = self.FPCP.flags & ToyotaStarPilotFlags.SMART_DSU.value
self.has_ZSS = self.FPCP.flags & ToyotaStarPilotFlags.ZSS.value self.has_ZSS = self.FPCP.flags & ToyotaStarPilotFlags.ZSS.value
self.auto_brake_hold = bool(self.CP.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value)
self.pre_collision_2 = {}
def update(self, can_parsers, starpilot_toggles) -> structs.CarState: def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
cp = can_parsers[Bus.pt] cp = can_parsers[Bus.pt]
@@ -208,6 +207,7 @@ class CarState(CarStateBase):
if self.CP.openpilotLongitudinalControl: if self.CP.openpilotLongitudinalControl:
ret.accFaulted = ret.accFaulted or cp.vl["PCM_CRUISE_2"]["LOW_SPEED_LOCKOUT"] == 2 ret.accFaulted = ret.accFaulted or cp.vl["PCM_CRUISE_2"]["LOW_SPEED_LOCKOUT"] == 2
prev_pcm_acc_status = self.pcm_acc_status
self.pcm_acc_status = cp.vl["PCM_CRUISE"]["CRUISE_STATE"] self.pcm_acc_status = cp.vl["PCM_CRUISE"]["CRUISE_STATE"]
if self.CP.carFingerprint not in (NO_STOP_TIMER_CAR - TSS2_CAR): if self.CP.carFingerprint not in (NO_STOP_TIMER_CAR - TSS2_CAR):
# ignore standstill state in certain vehicles, since pcm allows to restart with just an acceleration request # ignore standstill state in certain vehicles, since pcm allows to restart with just an acceleration request
@@ -225,9 +225,6 @@ class CarState(CarStateBase):
if self.CP.carFingerprint != CAR.TOYOTA_PRIUS_V: if self.CP.carFingerprint != CAR.TOYOTA_PRIUS_V:
self.lkas_hud = copy.copy(cp_cam.vl["LKAS_HUD"]) self.lkas_hud = copy.copy(cp_cam.vl["LKAS_HUD"])
if self.auto_brake_hold:
self.pre_collision_2 = copy.copy(cp_cam.vl["PRE_COLLISION_2"])
if self.CP.carFingerprint not in UNSUPPORTED_DSU_CAR: if self.CP.carFingerprint not in UNSUPPORTED_DSU_CAR:
self.pcm_follow_distance = cp.vl["PCM_CRUISE_2"]["PCM_FOLLOW_DISTANCE"] self.pcm_follow_distance = cp.vl["PCM_CRUISE_2"]["PCM_FOLLOW_DISTANCE"]
@@ -245,6 +242,11 @@ class CarState(CarStateBase):
buttonEvents += create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise}) buttonEvents += create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise})
if self.CP.carFingerprint in LEGACY_PRIUS_CAR and not self.has_SDSU:
prev_distance_button = self.distance_button
self.distance_button = cp_acc.vl["ACC_CONTROL"]["DISTANCE"]
buttonEvents += create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise})
if self.CP.carFingerprint in DISTANCE_BUTTON_CAR: if self.CP.carFingerprint in DISTANCE_BUTTON_CAR:
prev_distance_button = self.distance_button prev_distance_button = self.distance_button
self.distance_button = cp.vl["PCM_CRUISE_4"]["DISTANCE"] self.distance_button = cp.vl["PCM_CRUISE_4"]["DISTANCE"]
@@ -259,8 +261,8 @@ class CarState(CarStateBase):
buttonEvents += create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise}) buttonEvents += create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise})
buttonEvents += [ buttonEvents += [
*create_button_events(self.pcm_acc_status == 9, False, {1: ButtonType.accelCruise}), *create_button_events(self.pcm_acc_status == 9, prev_pcm_acc_status == 9, {1: ButtonType.accelCruise}),
*create_button_events(self.pcm_acc_status == 10, False, {1: ButtonType.decelCruise}), *create_button_events(self.pcm_acc_status == 10, prev_pcm_acc_status == 10, {1: ButtonType.decelCruise}),
] ]
fp_ret.dashboardSpeedLimit = calculate_speed_limit(cp_cam) fp_ret.dashboardSpeedLimit = calculate_speed_limit(cp_cam)
@@ -294,14 +296,20 @@ class CarState(CarStateBase):
pt_messages = [ pt_messages = [
("BLINKERS_STATE", float('nan')), ("BLINKERS_STATE", float('nan')),
] ]
cam_messages = []
if CP.enableGasInterceptorDEPRECATED: if CP.enableGasInterceptorDEPRECATED:
pt_messages.append(("GAS_SENSOR", 50)) pt_messages.append(("GAS_SENSOR", 50))
if CP.carFingerprint in LEGACY_PRIUS_CAR:
pt_messages.append(("ACC_CONTROL", float('nan')))
if CP.flags & ToyotaFlags.DSU_BYPASS.value:
cam_messages.append(("ACC_CONTROL", float('nan')))
if CP.carFingerprint in DISTANCE_BUTTON_CAR: if CP.carFingerprint in DISTANCE_BUTTON_CAR:
pt_messages.append(("PCM_CRUISE_4", 1)) pt_messages.append(("PCM_CRUISE_4", 1))
return { return {
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, 0), Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, 0),
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 2), Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], cam_messages, 2),
} }
+4 -4
View File
@@ -2,9 +2,9 @@ from opendbc.car import Bus, structs, get_safety_config, uds
from opendbc.car.toyota.carstate import CarState from opendbc.car.toyota.carstate import CarState
from opendbc.car.toyota.carcontroller import CarController from opendbc.car.toyota.carcontroller import CarController
from opendbc.car.toyota.radar_interface import RadarInterface from opendbc.car.toyota.radar_interface import RadarInterface
from opendbc.car.toyota.values import Ecu, CAR, DBC, ToyotaFlags, CarControllerParams, TSS2_CAR, RADAR_ACC_CAR, SECOC_CAR, NO_DSU_CAR, \ from opendbc.car.toyota.values import Ecu, CAR, DBC, ToyotaFlags, CarControllerParams, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, \
MIN_ACC_SPEED, EPS_SCALE, NO_STOP_TIMER_CAR, ANGLE_CONTROL_CAR, \ MIN_ACC_SPEED, EPS_SCALE, NO_STOP_TIMER_CAR, ANGLE_CONTROL_CAR, \
ToyotaSafetyFlags, LEGACY_PRIUS_CAR ToyotaSafetyFlags, LEGACY_PRIUS_CAR, TOYOTA_AUTO_HOLD_CARS
from opendbc.car.disable_ecu import disable_ecu from opendbc.car.disable_ecu import disable_ecu
from opendbc.car.interfaces import CarInterfaceBase from opendbc.car.interfaces import CarInterfaceBase
from opendbc.safety import ALTERNATIVE_EXPERIENCE from opendbc.safety import ALTERNATIVE_EXPERIENCE
@@ -164,8 +164,8 @@ class CarInterface(CarInterfaceBase):
ret.safetyConfigs[0].safetyParam |= ToyotaSafetyFlags.GAS_INTERCEPTOR.value ret.safetyConfigs[0].safetyParam |= ToyotaSafetyFlags.GAS_INTERCEPTOR.value
toyota_auto_hold = Params(return_defaults=True).get_bool("ToyotaAutoHold") toyota_auto_hold = Params(return_defaults=True).get_bool("ToyotaAutoHold")
if toyota_auto_hold and candidate in (TSS2_CAR - RADAR_ACC_CAR - SECOC_CAR): if toyota_auto_hold and ret.openpilotLongitudinalControl and candidate in TOYOTA_AUTO_HOLD_CARS:
ret.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ALLOW_AEB ret.alternativeExperience |= ALTERNATIVE_EXPERIENCE.TOYOTA_AUTO_HOLD
ret.flags |= ToyotaFlags.AUTO_BRAKE_HOLD.value ret.flags |= ToyotaFlags.AUTO_BRAKE_HOLD.value
if not ret.openpilotLongitudinalControl: if not ret.openpilotLongitudinalControl:
@@ -10,17 +10,19 @@ from opendbc.car.fw_versions import build_fw_dict, match_fw_to_car
from opendbc.car.toyota import toyotacan from opendbc.car.toyota import toyotacan
from opendbc.car.toyota.carcontroller import CarController, get_camry_hybrid_feedforward, get_long_tune, get_prius_feedforward, \ from opendbc.car.toyota.carcontroller import CarController, get_camry_hybrid_feedforward, get_long_tune, get_prius_feedforward, \
get_prius_positive_feedforward_scale, \ get_prius_positive_feedforward_scale, \
get_steer_rate_limit_frames, \ get_rav4_interceptor_pedal_scale, \
limit_interceptor_pcm_accel, \ limit_interceptor_pcm_accel, \
limit_interceptor_stopping_accel, limit_no_lead_cruise_sign_flip, \ limit_interceptor_stopping_accel, limit_no_lead_cruise_sign_flip, \
limit_prius_stopping_accel, should_bypass_toyota_long_pid, update_permit_braking limit_prius_stopping_accel, should_bypass_toyota_long_pid, supports_toyota_auto_hold, \
update_permit_braking
from opendbc.car.toyota.carstate import CarState, LKAS_BUTTON_CAR, calculate_interceptor_gas_pressed, create_lkas_button_events from opendbc.car.toyota.carstate import CarState, LKAS_BUTTON_CAR, calculate_interceptor_gas_pressed, create_lkas_button_events
from opendbc.car.toyota.fingerprints import FW_VERSIONS from opendbc.car.toyota.fingerprints import FW_VERSIONS
from opendbc.car.toyota.interface import CarInterface from opendbc.car.toyota.interface import CarInterface
from opendbc.car.toyota.radar_interface import RadarInterface, TSSP_RADAR_EGO_SPEED_SCALE from opendbc.car.toyota.radar_interface import RadarInterface, TSSP_RADAR_EGO_SPEED_SCALE
from opendbc.car.toyota.values import CAR, DBC, TSS2_CAR, ANGLE_CONTROL_CAR, RADAR_ACC_CAR, SECOC_CAR, \ from opendbc.car.toyota.values import CAR, DBC, MIN_ACC_SPEED, TSS2_CAR, ANGLE_CONTROL_CAR, RADAR_ACC_CAR, SECOC_CAR, \
FW_QUERY_CONFIG, PLATFORM_CODE_ECUS, FUZZY_EXCLUDED_PLATFORMS, \ FW_QUERY_CONFIG, PLATFORM_CODE_ECUS, FUZZY_EXCLUDED_PLATFORMS, \
ToyotaFlags, ToyotaSafetyFlags, ToyotaStarPilotFlags, get_platform_codes ToyotaFlags, ToyotaSafetyFlags, ToyotaStarPilotFlags, TOYOTA_AUTO_HOLD_CARS, \
get_platform_codes
from opendbc.safety import ALTERNATIVE_EXPERIENCE from opendbc.safety import ALTERNATIVE_EXPERIENCE
from openpilot.common.params import Params from openpilot.common.params import Params
@@ -187,13 +189,15 @@ class TestToyotaInterfaces:
if car_model in TSS2_CAR and car_model not in SECOC_CAR: if car_model in TSS2_CAR and car_model not in SECOC_CAR:
assert dbc[Bus.pt] == "toyota_nodsu_pt_generated" assert dbc[Bus.pt] == "toyota_nodsu_pt_generated"
def test_auto_hold_sets_flag_on_supported_tss2(self): @pytest.mark.parametrize("candidate", [CAR.TOYOTA_CAMRY_TSS2, CAR.TOYOTA_RAV4, CAR.TOYOTA_RAV4H])
def test_auto_hold_sets_flag_on_supported_toyota(self, candidate):
params = Params() params = Params()
try: try:
params.put_bool("ToyotaAutoHold", True) params.put_bool("ToyotaAutoHold", True)
car_params = CarInterface.get_params( car_params = CarInterface.get_params(
CAR.TOYOTA_CAMRY_TSS2, candidate,
{bus: {} for bus in range(8)}, {bus: ({0x2FF: 8} if candidate in (CAR.TOYOTA_RAV4, CAR.TOYOTA_RAV4H) and bus == 0 else {})
for bus in range(8)},
[], [],
alpha_long=False, alpha_long=False,
is_release=False, is_release=False,
@@ -204,7 +208,30 @@ class TestToyotaInterfaces:
params.remove("ToyotaAutoHold") params.remove("ToyotaAutoHold")
assert car_params.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value assert car_params.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value
assert car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.ALLOW_AEB assert car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.TOYOTA_AUTO_HOLD
assert not car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.ALLOW_AEB
can_parsers = CarState.get_can_parsers(car_params)
car_state = CarState(car_params, SimpleNamespace(flags=0))
car_state.update(can_parsers, SimpleNamespace(cluster_offset=1.0))
assert "PRE_COLLISION_2" not in can_parsers[Bus.cam].vl
@pytest.mark.parametrize("candidate", [CAR.TOYOTA_CAMRY_TSS2, CAR.TOYOTA_RAV4, CAR.TOYOTA_RAV4H])
def test_auto_hold_is_disabled_by_default(self, candidate):
params = Params()
params.remove("ToyotaAutoHold")
car_params = CarInterface.get_params(
candidate,
{bus: {} for bus in range(8)},
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=SimpleNamespace(),
)
assert not car_params.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value
assert not car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.TOYOTA_AUTO_HOLD
def test_prius_openpilot_long_uses_hybrid_long_defaults(self): def test_prius_openpilot_long_uses_hybrid_long_defaults(self):
car_params = CarInterface.get_params( car_params = CarInterface.get_params(
@@ -707,10 +734,6 @@ class TestToyotaFingerprint:
class TestToyotaCarController: class TestToyotaCarController:
def test_highlander_tss2_uses_early_steer_rate_fault_guard(self):
assert get_steer_rate_limit_frames(CAR.TOYOTA_HIGHLANDER_TSS2) == 8
assert get_steer_rate_limit_frames(CAR.TOYOTA_RAV4_TSS2) == 18
@staticmethod @staticmethod
def _make_controller(*, standstill_req=False, last_standstill=False): def _make_controller(*, standstill_req=False, last_standstill=False):
controller = CarController.__new__(CarController) controller = CarController.__new__(CarController)
@@ -723,6 +746,8 @@ class TestToyotaCarController:
controller.standstill_req = standstill_req controller.standstill_req = standstill_req
controller.last_standstill = last_standstill controller.last_standstill = last_standstill
controller.accel = 0.0 controller.accel = 0.0
controller.brake_hold_active = False
controller._brake_hold_counter = 0
return controller return controller
@staticmethod @staticmethod
@@ -755,6 +780,74 @@ class TestToyotaCarController:
assert controller.standstill_req is True assert controller.standstill_req is True
def test_toyota_auto_hold_requires_toggle_supported_car_and_capability(self):
CP = SimpleNamespace(
carFingerprint=CAR.TOYOTA_CAMRY_TSS2,
flags=ToyotaFlags.AUTO_BRAKE_HOLD.value,
)
assert supports_toyota_auto_hold(CP, True)
assert supports_toyota_auto_hold(SimpleNamespace(
carFingerprint=CAR.TOYOTA_RAV4,
flags=ToyotaFlags.AUTO_BRAKE_HOLD.value,
), True)
assert supports_toyota_auto_hold(SimpleNamespace(
carFingerprint=CAR.TOYOTA_RAV4H,
flags=ToyotaFlags.AUTO_BRAKE_HOLD.value,
), True)
assert not supports_toyota_auto_hold(CP, False)
assert not supports_toyota_auto_hold(SimpleNamespace(
carFingerprint=CAR.TOYOTA_CAMRY_TSS2,
flags=0,
), True)
assert not supports_toyota_auto_hold(SimpleNamespace(
carFingerprint=CAR.TOYOTA_CAMRY,
flags=ToyotaFlags.AUTO_BRAKE_HOLD.value,
), True)
assert TOYOTA_AUTO_HOLD_CARS >= {CAR.TOYOTA_RAV4, CAR.TOYOTA_RAV4H}
def test_toyota_auto_hold_latches_after_brake_press_until_gas(self):
controller = self._make_controller()
controller.packer = CANPacker(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt])
cs = SimpleNamespace(
out=SimpleNamespace(
standstill=True,
cruiseState=SimpleNamespace(available=True, enabled=False),
gasPressed=False,
brakePressed=True,
gearShifter=structs.CarState.GearShifter.drive,
),
)
controller.update_auto_hold_state(cs, activation_frames=0)
assert controller.brake_hold_active
cs.out.brakePressed = False
controller.update_auto_hold_state(cs, activation_frames=0)
assert controller.brake_hold_active
cs.out.gasPressed = True
controller.update_auto_hold_state(cs, activation_frames=0)
assert not controller.brake_hold_active
def test_toyota_auto_hold_does_not_trigger_without_brake_press(self):
controller = self._make_controller()
controller.packer = CANPacker(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt])
cs = SimpleNamespace(
out=SimpleNamespace(
standstill=True,
cruiseState=SimpleNamespace(available=True, enabled=False),
gasPressed=False,
brakePressed=False,
gearShifter=structs.CarState.GearShifter.drive,
),
)
controller.update_auto_hold_state(cs, activation_frames=0)
assert not controller.brake_hold_active
def test_prius_resume_request_releases_standstill_latch(self): def test_prius_resume_request_releases_standstill_latch(self):
controller = self._make_controller(standstill_req=True, last_standstill=True) controller = self._make_controller(standstill_req=True, last_standstill=True)
@@ -892,32 +985,30 @@ class TestToyotaCarController:
assert parser.can_valid assert parser.can_valid
assert parser.vl["ACC_CONTROL"]["ALLOW_LONG_PRESS"] == 1 assert parser.vl["ACC_CONTROL"]["ALLOW_LONG_PRESS"] == 1
def test_auto_brake_hold_sends_modified_pre_collision_after_timer(self): def test_auto_hold_uses_acc_control_brake_path(self):
controller = self._make_controller() controller = self._make_controller()
controller.packer = CANPacker(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt]) controller.packer = CANPacker(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt])
controller.frame = 0
controller.brake_hold_active = False
controller._brake_hold_counter = 0
controller._brake_hold_reset = False
controller._prev_brake_pressed = False
cs = SimpleNamespace( cs = SimpleNamespace(
out=SimpleNamespace( out=SimpleNamespace(
standstill=True, standstill=True,
cruiseState=SimpleNamespace(available=True, enabled=False), cruiseState=SimpleNamespace(available=True, enabled=False),
gasPressed=False, gasPressed=False,
brakePressed=False, brakePressed=True,
gearShifter=structs.CarState.GearShifter.drive, gearShifter=structs.CarState.GearShifter.drive,
), ),
pre_collision_2={},
) )
can_sends = controller.create_auto_brake_hold_messages(cs, brake_hold_allowed_timer=0) controller.update_auto_hold_state(cs, activation_frames=0)
can_sends = [toyotacan.create_accel_command(
controller.packer, -1.0, False, True, True, False, 1, False, 0, False,
)]
parser = CANParser(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt], [("PRE_COLLISION_2", 0)], 0) parser = CANParser(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt], [("ACC_CONTROL", 0)], 0)
parser.update([(1, can_sends)]) parser.update([(1, can_sends)])
assert controller.brake_hold_active assert controller.brake_hold_active
assert parser.vl["PRE_COLLISION_2"]["DSS1GDRV"] == -1.0 assert parser.vl["ACC_CONTROL"]["ACCEL_CMD"] == -1.0
assert parser.vl["PRE_COLLISION_2"]["PBRTRGR"] == 1 assert parser.vl["ACC_CONTROL"]["PERMIT_BRAKING"] == 1
assert parser.vl["ACC_CONTROL"]["RELEASE_STANDSTILL"] == 0
def test_interceptor_stop_and_go_holds_small_launch_at_standstill(self): def test_interceptor_stop_and_go_holds_small_launch_at_standstill(self):
controller = self._make_controller() controller = self._make_controller()
@@ -945,6 +1036,27 @@ class TestToyotaCarController:
assert 0.0 < gas_cmd <= 0.5 assert 0.0 < gas_cmd <= 0.5
def test_rav4_interceptor_launch_mapping_is_softer_than_generic_mapping(self):
controller = self._make_controller()
controller.CP.enableGasInterceptorDEPRECATED = True
controller.CP.carFingerprint = CAR.TOYOTA_RAV4
controller.accel = 1.5
rav4_gas = controller._compute_interceptor_gas_cmd(
SimpleNamespace(longActive=True),
SimpleNamespace(out=SimpleNamespace(standstill=False, vEgo=1.0)),
)
controller.CP.carFingerprint = CAR.TOYOTA_AVALON_2019
generic_gas = controller._compute_interceptor_gas_cmd(
SimpleNamespace(longActive=True),
SimpleNamespace(out=SimpleNamespace(standstill=False, vEgo=1.0)),
)
assert rav4_gas < generic_gas
assert get_rav4_interceptor_pedal_scale(5.0) == pytest.approx(0.23)
assert get_rav4_interceptor_pedal_scale(MIN_ACC_SPEED) == pytest.approx(0.3)
def test_interceptor_corolla_scales_with_accel_request_when_pedal_enables_sng(self): def test_interceptor_corolla_scales_with_accel_request_when_pedal_enables_sng(self):
controller = self._make_controller() controller = self._make_controller()
controller.CP.enableGasInterceptorDEPRECATED = True controller.CP.enableGasInterceptorDEPRECATED = True
@@ -1043,6 +1155,36 @@ class TestToyotaCarController:
class TestToyotaCarState: class TestToyotaCarState:
@pytest.mark.parametrize("candidate", [CAR.TOYOTA_PRIUS, CAR.TOYOTA_PRIUS_RETROFIT])
def test_legacy_prius_distance_button_generates_events(self, candidate):
params = CarInterface.get_params(
candidate,
{bus: {} for bus in range(8)},
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=SimpleNamespace(force_torque_controller=False, nnff=False, nnff_lite=False),
)
starpilot_params = CarInterface.get_starpilot_params(candidate, {bus: {} for bus in range(8)}, [], params, SimpleNamespace())
car_state = CarState(params, starpilot_params)
can_parsers = car_state.get_can_parsers(params)
assert "ACC_CONTROL" in can_parsers[Bus.pt].vl
assert ("ACC_CONTROL" in can_parsers[Bus.cam].vl) == bool(params.flags & ToyotaFlags.DSU_BYPASS.value)
can_parsers[Bus.pt].vl["ACC_CONTROL"]["DISTANCE"] = 1
ret, _ = car_state.update(can_parsers, SimpleNamespace(cluster_offset=1.0))
assert [(event.type, event.pressed) for event in ret.buttonEvents] == [
(structs.CarState.ButtonEvent.Type.gapAdjustCruise, True),
]
can_parsers[Bus.pt].vl["ACC_CONTROL"]["DISTANCE"] = 0
ret, _ = car_state.update(can_parsers, SimpleNamespace(cluster_offset=1.0))
assert [(event.type, event.pressed) for event in ret.buttonEvents] == [
(structs.CarState.ButtonEvent.Type.gapAdjustCruise, False),
]
def test_lkas_button_platforms(self): def test_lkas_button_platforms(self):
assert CAR.TOYOTA_PRIUS in LKAS_BUTTON_CAR assert CAR.TOYOTA_PRIUS in LKAS_BUTTON_CAR
assert TSS2_CAR <= LKAS_BUTTON_CAR assert TSS2_CAR <= LKAS_BUTTON_CAR
@@ -89,38 +89,6 @@ def create_pcs_commands(packer, accel, active, mass):
return [msg1, msg2] return [msg1, msg2]
def create_brake_hold_command(packer, frame, pre_collision_2, brake_hold_active):
values = {s: pre_collision_2[s] for s in [
"DSS1GDRV",
"DS1STAT2",
"DS1STBK2",
"PCSWAR",
"PCSALM",
"PCSOPR",
"PCSABK",
"PBATRGR",
"PPTRGR",
"IBTRGR",
"CLEXTRGR",
"IRLT_REQ",
"BRKHLD",
"AVSTRGR",
"VGRSTRGR",
"PREFILL",
"PBRTRGR",
"PCSDIS",
"PBPREPMP",
] if s in pre_collision_2}
if brake_hold_active:
values = {
"DSS1GDRV": 0x3FF,
"PBRTRGR": frame % 730 < 727,
}
return packer.make_can_msg("PRE_COLLISION_2", 0, values)
def create_acc_cancel_command(packer): def create_acc_cancel_command(packer):
values = { values = {
"GAS_RELEASED": 0, "GAS_RELEASED": 0,
@@ -624,6 +624,11 @@ ANGLE_CONTROL_CAR = CAR.with_flags(ToyotaFlags.ANGLE_CONTROL)
SECOC_CAR = CAR.with_flags(ToyotaFlags.SECOC) SECOC_CAR = CAR.with_flags(ToyotaFlags.SECOC)
TOYOTA_AUTO_HOLD_CARS = (TSS2_CAR - RADAR_ACC_CAR - SECOC_CAR) | {
CAR.TOYOTA_RAV4,
CAR.TOYOTA_RAV4H,
}
# no resume button press required # no resume button press required
NO_STOP_TIMER_CAR = CAR.with_flags(ToyotaFlags.NO_STOP_TIMER) NO_STOP_TIMER_CAR = CAR.with_flags(ToyotaFlags.NO_STOP_TIMER)
+2 -1
View File
@@ -14,8 +14,9 @@ from opendbc.car.subaru.values import CAR as SUBARU
from opendbc.car.tesla.values import CAR as TESLA from opendbc.car.tesla.values import CAR as TESLA
from opendbc.car.toyota.values import CAR as TOYOTA from opendbc.car.toyota.values import CAR as TOYOTA
from opendbc.car.volkswagen.values import CAR as VOLKSWAGEN from opendbc.car.volkswagen.values import CAR as VOLKSWAGEN
from opendbc.car.volvo.values import CAR as VOLVO
Platform = BODY | CHRYSLER | FORD | GM | HONDA | HYUNDAI | MAZDA | MOCK | NISSAN | PSA | RIVIAN | SUBARU | TESLA | TOYOTA | VOLKSWAGEN Platform = BODY | CHRYSLER | FORD | GM | HONDA | HYUNDAI | MAZDA | MOCK | NISSAN | PSA | RIVIAN | SUBARU | TESLA | TOYOTA | VOLKSWAGEN | VOLVO
BRANDS = get_args(Platform) BRANDS = get_args(Platform)
PLATFORMS: dict[str, Platform] = {str(platform): platform for brand in BRANDS for platform in brand} PLATFORMS: dict[str, Platform] = {str(platform): platform for brand in BRANDS for platform in brand}
@@ -0,0 +1 @@
# Volvo CMA platform support for openpilot
@@ -0,0 +1,343 @@
from collections import deque
import numpy as np
from opendbc.can.packer import CANPacker
from opendbc.car import Bus
from opendbc.car.interfaces import CarControllerBase
from opendbc.car.lateral import apply_std_steer_angle_limits
from opendbc.car.volvo.helpers import LCA3CounterSync
from opendbc.car.volvo.volvocan import (create_c1_cancel, create_c1_pscm_message, create_c1_steering_control, create_lca_message,
create_pscm_message, create_lca_3_message, create_lca_2_message, create_lca_4_message, create_lca_5_message,
create_lca_6_message, create_lca_7_message, create_pscm_related_message)
from opendbc.car.volvo.values import CAR, CarControllerParams, VolvoC1PlatformConfig
class CarController(CarControllerBase):
def __init__(self, dbc_names, CP):
super().__init__(dbc_names, CP)
self.is_c1 = isinstance(CAR(CP.carFingerprint).config, VolvoC1PlatformConfig)
self.packer = CANPacker(dbc_names[Bus.pt] if self.is_c1 else dbc_names[Bus.party])
self.apply_angle_last = 0.0 # Track last applied steering angle
self.c1_torque_samples = deque(maxlen=CarControllerParams.C1_N_ZERO_TORQUE)
self.c1_recovery_until = -1
self.gear_acc = 60
self.lca_4_acc = 0 # Bresenham accumulator for 29 Hz
# Counter management for LCA_2
self.lca_2_counter_1 = None # Will grab initial value from CarState
self.lca_2_counter_2 = None
# Counter management for PSCM_RELATED
self.pscm_related_counter = None # Will grab initial value from CarState
# Counter management for LCA_3 (pattern-based)
self.lca_3_counter_sync = LCA3CounterSync()
# Counter management for LCA_5 (formerly SPEED_1)
self.lca_5_counter = None # Will grab initial value from CarState
self.last_lat_active = False # Track state
self.lca_7_acc = 0 # Bresenham accumulator for 29 Hz
self.lca_7_last_steer = 0 # used to calculate change in steer from last update
# LCA torque-authority envelope state. Both arms are persistent across frames.
# See CarControllerParams.LCA_AUTH_* and route_analysis/lca_override_mechanism.md.
# When lat_active goes True they ramp up from 0 to ±MAX at REBUILD_RATE; on
# driver override they collapse at COLLAPSE_RATE (symmetric until SPLIT, then
# asymmetric: yielding arm → 0, counter arm holds at ±PLATEAU).
self.lca_auth_pos = 0.0
self.lca_auth_neg = 0.0
# "Light contact" rising-edge detector for haptic-ack on resting hands.
# Per-frame |drv| derivative; LIGHT_HOLD_FRAMES counter ticks down while
# the brief-yield window is active and does not re-arm during that window.
self.lca_auth_drv_prev = 0.0
self.lca_auth_light_frames = 0
# Frames since real_override was last active — used to gate light_collapse
# re-arming during active co-steering (must be > LIGHT_COOLDOWN_FRAMES).
self.lca_auth_real_off_frames = 1000 # large initial → light can fire immediately
# Override-mode latch with hysteresis (enter at ENTER, exit at EXIT).
# Prevents threshold flapping when driver torque hovers near the boundary,
# which caused ~10 Hz EPS-torque ripple felt during lane-change overrides.
self.lca_auth_override_active = False
# LP-filtered |drv| for yield-arm magnitude calculation. Suppresses 1-2
# unit driver-torque jitter that would otherwise propagate (~10x amplified
# via YIELD_SLOPE) into envelope ripple felt at the wheel.
self.lca_auth_drv_mag_filt = 0.0
def update(self, CC, CS, now_nanos, starpilot_toggles):
if self.is_c1:
return self._update_c1(CC, CS)
can_sends = []
actuators = CC.actuators
# Detect disengagement
if not CC.latActive and self.last_lat_active:
#self.lca_commands.reset() # Clear state ← IMPORTANT!
pass
lat_active = CC.latActive
# lateral control - angle-based steering
# NOTE: LCA message is sent every frame (even when inactive) to replace stock LCA
# Stock LCA is permanently blocked by panda safety, so we must always send
if self.frame % CarControllerParams.STEER_STEP == 0: # 100 Hz
# Get desired steering angle from controlsd (LatControlAngle)
apply_angle = actuators.steeringAngleDeg # degrees
# Clamp commanded angle to actual ± ANGLE_ERROR. Without this, a driver override
# lets the model's plan drift far from the wheel's actual position; on release,
# the EPS slams back toward that stale command and overshoots. Stock Volvo Pilot
# Assist keeps this gap inside ~2° even under sustained override.
apply_angle = float(np.clip(
apply_angle,
CS.out.steeringAngleDeg - CarControllerParams.ANGLE_ERROR,
CS.out.steeringAngleDeg + CarControllerParams.ANGLE_ERROR,
))
# Rate limit + inactive passthrough (apply_angle = steering angle when not lat_active)
apply_angle = apply_std_steer_angle_limits(apply_angle, self.apply_angle_last, CS.out.vEgoRaw,
CS.out.steeringAngleDeg, lat_active, CarControllerParams.ANGLE_LIMITS)
# Update LCA torque-authority envelope (replicates stock Pilot Assist's
# easy-override and bounce-free release). Stock holds both arms at ±614
# in steady state; on override the arms collapse to a shifted plateau
# (counter arm deeper than yielding arm); rebuilds at +230 c/s.
P = CarControllerParams
DT = 0.01 # 100 Hz
# Override trigger uses an explicit |steeringTorque| threshold rather than
# CS.steeringPressed, which is a very-sensitive DM-fallback floor (raw>2)
# — fires from resting hands alone and is not an override-intent signal.
drv_mag = abs(CS.out.steeringTorque)
drv_rate = drv_mag - self.lca_auth_drv_prev
self.lca_auth_drv_prev = drv_mag
# LP filter on |drv| used for yield-arm magnitude — absorbs 1-2 unit
# driver-torque jitter that would otherwise propagate into ~10 unit
# envelope ripple via the YIELD_SLOPE multiplier.
self.lca_auth_drv_mag_filt = ((1.0 - P.LCA_AUTH_YIELD_LP_ALPHA) * self.lca_auth_drv_mag_filt
+ P.LCA_AUTH_YIELD_LP_ALPHA * drv_mag)
# Hysteretic override latch — enter at ENTER, hold until drv drops below
# EXIT. Eliminates ~10 Hz envelope flapping when |drv| hovers near a
# single threshold during sustained co-steering.
if not self.lca_auth_override_active and drv_mag > P.LCA_AUTH_OVERRIDE_ENTER:
self.lca_auth_override_active = True
elif self.lca_auth_override_active and drv_mag < P.LCA_AUTH_OVERRIDE_EXIT:
self.lca_auth_override_active = False
real_override = self.lca_auth_override_active
# Track frames since real_override was last active. Used to gate light
# contact re-firing — light_collapse must NOT trigger while the driver
# is actively co-steering (real_override repeatedly entering/exiting).
if real_override:
self.lca_auth_real_off_frames = 0
else:
self.lca_auth_real_off_frames += 1
# Per-frame rising edge into the "light contact" zone arms a brief-yield
# window for haptic acknowledgment of hand-on-wheel. Suppressed while
# the window is already active OR real_override has been off less than
# LIGHT_COOLDOWN_FRAMES (i.e., user is actively co-steering).
if (drv_mag > P.LCA_AUTH_LIGHT_THRESH and
drv_rate > P.LCA_AUTH_LIGHT_RISE_DELTA and
self.lca_auth_light_frames == 0 and
self.lca_auth_real_off_frames > P.LCA_AUTH_LIGHT_COOLDOWN_FRAMES):
self.lca_auth_light_frames = P.LCA_AUTH_LIGHT_HOLD_FRAMES
else:
self.lca_auth_light_frames = max(0, self.lca_auth_light_frames - 1)
light_collapse = self.lca_auth_light_frames > 0
overriding = real_override or light_collapse
# Collapse rate scales with driver torque so a sharp pothole jolt drops the
# envelope faster than a soft sustained press. Floor at base rate so light
# contact still produces a perceptible (but small) dip.
collapse_rate = P.LCA_AUTH_COLLAPSE_RATE * max(1.0, drv_mag / float(P.LCA_AUTH_OVERRIDE_ENTER))
step = collapse_rate * DT
if not lat_active:
self.lca_auth_pos = 0.0
self.lca_auth_neg = 0.0
self.lca_auth_drv_prev = 0.0
self.lca_auth_light_frames = 0
self.lca_auth_override_active = False
self.lca_auth_drv_mag_filt = 0.0
self.lca_auth_real_off_frames = 1000
elif overriding:
if self.lca_auth_pos > P.LCA_AUTH_SPLIT or -self.lca_auth_neg > P.LCA_AUTH_SPLIT:
# Symmetric collapse phase: both arms shrink toward ±SPLIT
self.lca_auth_pos = max(float(P.LCA_AUTH_SPLIT), self.lca_auth_pos - step)
self.lca_auth_neg = min(-float(P.LCA_AUTH_SPLIT), self.lca_auth_neg + step)
else:
# Asymmetric plateau phase. CS.out.steeringTorque > 0 in openpilot
# convention = driver pushing right → yields right authority
# (LOOSELY/+ arm), retains left (INV/- arm).
# Yield arm scales with |drv| above OVERRIDE_THRESH — strong presses
# (potholes, hard corrections) cross past zero so EPS hands the wheel
# to the driver in their direction.
excess = max(0.0, self.lca_auth_drv_mag_filt - float(P.LCA_AUTH_OVERRIDE_ENTER))
yield_signed = float(P.LCA_AUTH_YIELD_BASE) - P.LCA_AUTH_YIELD_SLOPE * excess
yield_signed = max(float(P.LCA_AUTH_YIELD_MIN), min(yield_signed, float(P.LCA_AUTH_YIELD_BASE)))
if CS.out.steeringTorque > 0: # driver pushing right
target_pos = yield_signed # yield arm (+ side)
target_neg = -float(P.LCA_AUTH_PLATEAU_COUNTER) # counter arm
else: # driver pushing left (or zero — default to symmetric collapse direction)
target_pos = float(P.LCA_AUTH_PLATEAU_COUNTER) # counter arm
target_neg = -yield_signed # yield arm ( side)
# Drive each arm toward its plateau target at COLLAPSE_RATE
self.lca_auth_pos = max(target_pos, self.lca_auth_pos - step) \
if self.lca_auth_pos > target_pos \
else min(target_pos, self.lca_auth_pos + step)
self.lca_auth_neg = min(target_neg, self.lca_auth_neg + step) \
if self.lca_auth_neg < target_neg \
else max(target_neg, self.lca_auth_neg - step)
else:
# No override → rebuild both arms toward saturation
rebuild_step = P.LCA_AUTH_REBUILD_RATE * DT
self.lca_auth_pos = min(float(P.LCA_AUTH_MAX), self.lca_auth_pos + rebuild_step)
self.lca_auth_neg = max(-float(P.LCA_AUTH_MAX), self.lca_auth_neg - rebuild_step)
# LCA - 0x58 - 100 Hz (angle-based)
can_sends.append(create_lca_message(self.packer, lat_active, apply_angle, CS.msg_lca,
authority_pos=int(round(self.lca_auth_pos)),
authority_neg=int(round(self.lca_auth_neg))))
self.apply_angle_last = apply_angle
# PSCM (bus 2 -> 0) - 0x16 - 100 Hz
can_sends.append(create_pscm_message(self.packer, lat_active, CS.msg_pscm, self.frame))
# EGSM - 0x45 - 100 Hz
#can_sends.append(create_egsm_message(self.packer, CS.msg_egsm))
# PSCM_RELATED (bus 2 -> 0) - 0x17 - 100 Hz
# Initialize counter from CarState on first run
if self.pscm_related_counter is None:
self.pscm_related_counter = CS.msg_pscm_related['SIG1_BYTE_1_HI_NIBBLE']
# Increment counter by +1, wrap from 14 → 0 (modulo 15)
self.pscm_related_counter = (self.pscm_related_counter + 1) % 15
can_sends.append(create_pscm_related_message(self.packer, lat_active, CS.pilot_assist_engaged,
CS.msg_pscm_related, self.pscm_related_counter))
# LCA_3 - 0x57 - avg 66.66 Hz
#if (self.frame * 67) % 100 < 67: # if (self.frame % 3) < 2:
# 0x57 at ~66.67 Hz: send on 2 out of every 3 frames
# Pattern: send on frame % 3 == 0 or 2, skip when frame % 3 == 1
if self.frame % 3 != 1: # → 2/3 * 100 Hz = 66.67 Hz
# Update counter with observed value, get counter to send
counter, is_synced = self.lca_3_counter_sync.update(CS.msg_lca_3['COUNTER_1'])
can_sends.append(create_lca_3_message(self.packer, lat_active, apply_angle, CS.msg_lca_3, counter))
#can_sends.append(create_0x1a_message(self.packer, CS.msg_0x1a))
# SPEED messages - 0x60, 0x68 - 50 Hz
if self.frame % 2 == 0: # 50 Hz
#can_sends.append(create_speed_message(self.packer, CS.msg_speed))
#can_sends.append(create_speed_2_message(self.packer, CS.msg_speed_2))
pass
# LCA_2 - 0x69 - 50 Hz
# Spoof PILOT_ASSIST_ENGAGED to keep PSCM accepting LCA commands
if self.frame % 2 == 0: # 50 Hz
# Initialize counters from CarState on first run
if self.lca_2_counter_1 is None:
self.lca_2_counter_1 = CS.msg_lca_2['COUNTER_1']
self.lca_2_counter_2 = CS.msg_lca_2['COUNTER_2']
# Increment counters (COUNTER_1 by +2, COUNTER_2 by +4, both modulo 16)
self.lca_2_counter_1 = (self.lca_2_counter_1 + 2) % 16
self.lca_2_counter_2 = (self.lca_2_counter_2 + 4) % 16
can_sends.append(create_lca_2_message(self.packer, lat_active, CS.msg_lca_2,
self.lca_2_counter_1, self.lca_2_counter_2))
# LCA_5 (formerly SPEED_1) - 0x67 - 50 Hz
# Contains wheel speeds + LCA signals (LCA_TURN_BITS, LCA_5_STEER)
if self.frame % 2 == 0: # 50 Hz
# Initialize counter from CarState on first run
if self.lca_5_counter is None:
self.lca_5_counter = CS.msg_lca_5['COUNTER']
# Increment counter by +4, wrap at 15 (0xF never used)
self.lca_5_counter = (self.lca_5_counter + 4) % 15
can_sends.append(create_lca_5_message(self.packer, lat_active, apply_angle,
CS.msg_lca_5, self.lca_5_counter))
# LCA_4 - 0x90 - 29 Hz
# Spoof LCA_ENABLE bits to maintain PA ON state when openpilot is active
# Using Bresenham-style accumulator for precise 29 Hz
self.lca_4_acc += 29
if self.lca_4_acc >= 100:
self.lca_4_acc -= 100
can_sends.append(create_lca_4_message(self.packer, lat_active, CS.msg_lca_4, apply_angle))
# LCA_6 - 0X97 - 25 Hz
if self.frame % 4 == 0: # 25 Hz
can_sends.append(create_lca_6_message(self.packer, lat_active, CS.msg_lca_6, apply_angle))
# LCA_7 - 0x92 - 29 Hz
# Using Bresenham-style accumulator for precise 29 Hz
self.lca_7_acc += 29
if self.lca_7_acc >= 100:
self.lca_7_acc -= 100
delta_steer = apply_angle - self.lca_7_last_steer
can_sends.append(create_lca_7_message(self.packer, lat_active, CS.msg_lca_7, apply_angle, delta_steer))
self.lca_7_last_steer = apply_angle
# GEAR_POSITION - 0x80 - 40 Hz
#self.gear_acc += 40 # Bresenham-style approach
#if self.gear_acc >= 100:
# self.gear_acc -= 100
if self.frame % 5 == 0 or self.frame % 5 == 2: # 2/5 * 100 Hz = 40 Hz # openpilot forward delay causes DTC in EGSM, but fixes DTC in PSCM
#can_sends.append(create_gear_position_message(self.packer, CS.msg_gear_position))
pass
new_actuators = actuators.as_builder()
new_actuators.steeringAngleDeg = self.apply_angle_last
self.frame += 1
self.last_lat_active = CC.latActive
return new_actuators, can_sends
def _update_c1(self, CC, CS):
can_sends = []
actuators = CC.actuators
if self.frame % 2 == 0: # stock FSM1 and PSCM1 messages are 50 Hz
requested_active = CC.latActive and CS.out.vEgo > self.CP.minSteerSpeed
recovering = requested_active and self.frame < self.c1_recovery_until
if not requested_active:
self.c1_torque_samples.clear()
self.c1_recovery_until = -1
elif recovering:
self.c1_torque_samples.clear()
else:
if self.c1_recovery_until >= 0:
self.c1_recovery_until = -1
self.c1_torque_samples.clear()
self.c1_torque_samples.append(CS.c1_lka_torque)
if (len(self.c1_torque_samples) == CarControllerParams.C1_N_ZERO_TORQUE and
all(torque == 0 for torque in self.c1_torque_samples)):
self.c1_recovery_until = self.frame + 100
self.c1_torque_samples.clear()
recovering = True
lat_active = requested_active and not recovering
desired_angle = float(np.clip(
actuators.steeringAngleDeg,
CS.out.steeringAngleDeg - CarControllerParams.C1_ANGLE_ERROR,
CS.out.steeringAngleDeg + CarControllerParams.C1_ANGLE_ERROR,
))
apply_angle = apply_std_steer_angle_limits(
desired_angle, self.apply_angle_last, CS.out.vEgoRaw,
CS.out.steeringAngleDeg, lat_active, CarControllerParams.C1_ANGLE_LIMITS,
)
can_sends.append(create_c1_pscm_message(self.packer, CS.c1_msg_pscm))
can_sends.append(create_c1_steering_control(self.packer, apply_angle, lat_active))
self.apply_angle_last = apply_angle
if CC.cruiseControl.cancel and self.frame % 10 == 0:
can_sends.append(create_c1_cancel(self.packer))
new_actuators = actuators.as_builder()
new_actuators.steeringAngleDeg = self.apply_angle_last
self.frame += 1
return new_actuators, can_sends
+237
View File
@@ -0,0 +1,237 @@
from cereal import custom
from opendbc.car import Bus, ButtonType, create_button_events, structs
from opendbc.can.parser import CANParser
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.interfaces import CarStateBase
from opendbc.car.volvo.values import CAR, DBC, VolvoC1PlatformConfig, VolvoSPAPlatformConfig
GearShifter = structs.CarState.GearShifter
TransmissionType = structs.CarParams.TransmissionType
# main-bus SPEED (0x60) is raw counts in the DBC; measured against GPS ground speed.
# Must match VOLVO_SPEED_TO_MS in opendbc/safety/modes/volvo.h.
SPEED_TO_MS = 0.003977
STEERING_PRESSED_THRESHOLD = 2
class CarState(CarStateBase):
def __init__(self, CP, FPCP):
super().__init__(CP, FPCP)
self.is_c1 = isinstance(CAR(CP.carFingerprint).config, VolvoC1PlatformConfig)
self.is_spa = isinstance(CAR(CP.carFingerprint).config, VolvoSPAPlatformConfig)
self.gas_pressed_prev = False
self.dispatch_lca_2_msg = False
self.msg_pscm = {}
self.msg_lca = {}
self.msg_lca_2 = {}
self.msg_lca_3 = {}
self.msg_gear_position = {}
self.pilot_assist_engaged = False
self.msg_lca_5 = {} # Formerly msg_speed_1
self.msg_speed = {}
self.msg_speed_2 = {}
self.msg_0x1a = {}
self.msg_egsm = {}
self.msg_pscm_related = {}
self.msg_lca_4 = {}
self.msg_lca_6 = {}
self.msg_lca_7 = {}
self.c1_msg_pscm = {}
self.c1_lka_torque = 0
self.c1_button_states = {
"ACCOnOffBtn": False,
"ACCStopBtn": False,
"ACCSetBtn": False,
"ACCResumeBtn": False,
"ACCMinusBtn": False,
"TimeGapIncreaseBtn": False,
"TimeGapDecreaseBtn": False,
}
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
if self.is_c1:
return self._update_c1(can_parsers)
cp_main = can_parsers[Bus.main]
cp_pt = can_parsers[Bus.pt]
cp_party = can_parsers[Bus.party]
ret = structs.CarState()
# car speed
# SPEED on the main bus, not BUS1_SPEED on the PT bus: the main bus is identical
# across harnesses, while which car bus lands on PT (bus 1) is not, and the PT DBC
# in use depends on the fingerprint. Regressed against GPS ground speed over two
# routes on different harnesses: r=0.99989 both, residual sd 0.35-0.40 km/h.
ret.vEgoRaw = cp_main.vl["SPEED"]["SPEED"] * SPEED_TO_MS
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
ret.standstill = ret.vEgoRaw <= 0.1 # 0.1 m/s
# gas
# CMA ECM_1.ACCELERATOR_PEDAL_POS is raw 0-255 (DBC factor 1, idle ~20).
# SPA ECM_1.ACCELERATOR_PEDAL_POS is DBC-scaled to percent (factor 0.00390625, idle ~0).
# Thresholds must match volvo.h (see opendbc/safety/modes/volvo.h GAS_PRESSED_THRESHOLD_*)
# and opendbc/safety/tests/test_volvo.py::test_gas_threshold_self_consistent.
if self.is_spa:
ret.gasPressed = cp_pt.vl["ECM_1"]["ACCELERATOR_PEDAL_POS"] > 1.0 # percent
else:
ret.gasPressed = cp_pt.vl["ECM_1"]["ACCELERATOR_PEDAL_POS"] > 20+1 # raw counts, 20 baseline + 1 tolerance
# brake
#ret.brakePressed = bool(cp_main.vl["LCA_2"]["BRAKE_PEDAL_PRESSED_A"] or cp_main.vl["LCA_2"]["BRAKE_PEDAL_PRESSED_B"])
# BRAKE_PEDAL_PRESSED_A goes active when user starts pressing brake pedal, but no brake light is on yet due to tolerance
# BRAKE_PEDAL_PRESSED_B goes active when when the brake pedal is pressed above minimum threshold, brake light is on
ret.brakePressed = cp_main.vl["LCA_2"]["BRAKE_PEDAL_PRESSED_B"] == 1
ret.parkingBrake = False # TODO: add parking brake
# stability control - becomes true when ESC intervenes (e.g., aquaplaning)
ret.espActive = cp_main.vl["LCA_2"]["ESC_ACTUATING"] == 1 and cp_main.vl["LCA_2"]["ESC_ELIGIBLE"] == 1
# steering wheel
ret.steeringAngleDeg = cp_party.vl['PSCM']['PSCM_ANGLE_SENSOR'] # openpilot expects a negative value for a right turn
#ret.steeringAngleDeg = cp_party.vl['SAS']['SAS_ANGLE_SENSOR']
ret.steeringTorque = -cp_party.vl['DRIVER_INPUT']['STEERING_DRIVER_INPUT'] # Car right turn is negative, openpilot right turn is positive
driver_input = abs(cp_party.vl['DRIVER_INPUT']['STEERING_DRIVER_INPUT'])
ret.steeringPressed = driver_input > STEERING_PRESSED_THRESHOLD
# EPS status - placeholder until actual signal is found
self.eps_active = True # Assume EPS is active for now
if self.is_spa:
# SPA: byte 0 bit 1, inverted (0 = cruise on, 1 = cruise off)
cruise_raw = cp_pt.vl["BUS1_CRUISE_CONTROL"]["CRUISE_CONTROL_SPA_ENABLED"] == 1
else:
# CMA: two separate boolean signals
cruise_raw = cp_pt.vl["BUS1_CRUISE_CONTROL"]["CRUISE_CONTROL_ENABLED"] == 1 or cp_pt.vl["BUS1_CRUISE_CONTROL"]["CRUISE_CONTROL_ENABLED_IDLE_TRAFFIC"] == 1
ret.cruiseState.enabled = cruise_raw
self.gas_pressed_prev = ret.gasPressed
ret.cruiseState.available = True # TODO: Determine actual availability
ret.cruiseState.speed = 0 # TODO: Find cruise set speed (not required for lateral control)
ret.cruiseState.nonAdaptive = False
ret.cruiseState.standstill = ret.standstill # False # Todo: Find cruise control standstill signal
# gear
gearPosition = cp_main.vl['GEAR_POSITION']['GEAR_POSITION'] # 0: P; 1: R; 2: N; 3: D; 4: B;
if gearPosition == 0:
ret.gearShifter = GearShifter.park
elif gearPosition == 1:
ret.gearShifter = GearShifter.reverse
elif gearPosition == 2:
ret.gearShifter = GearShifter.neutral
elif gearPosition == 3:
ret.gearShifter = GearShifter.drive
elif gearPosition == 4:
ret.gearShifter = GearShifter.drive
# blinkers TODO FlexRay
ret.leftBlinker = False
ret.rightBlinker = False
# lock info TODO FlexRay
ret.doorOpen = False # TODO: add door open
ret.seatbeltUnlatched = False # TODO: add seatbelt unlatched
# Store entire message dictionaries
self.msg_pscm = cp_party.vl['PSCM']
self.msg_lca = cp_main.vl['LCA']
self.msg_lca_2 = cp_main.vl['LCA_2']
self.msg_lca_3 = cp_main.vl['LCA_3']
self.msg_lca_4 = cp_main.vl['LCA_4']
self.msg_lca_5 = cp_main.vl['LCA_5']
self.msg_lca_6 = cp_main.vl['LCA_6']
self.msg_lca_7 = cp_main.vl['LCA_7']
self.msg_speed = cp_main.vl['SPEED']
self.msg_speed_2 = cp_main.vl['SPEED_2']
self.msg_gear_position = cp_main.vl['GEAR_POSITION']
self.msg_egsm = cp_party.vl['EGSM']
self.msg_pscm_related = cp_party.vl['PSCM_RELATED']
self.pilot_assist_engaged = cp_main.vl['LCA_2']['PILOT_ASSIST_ENGAGED'] == 1
fp_ret = custom.StarPilotCarState.new_message()
return ret, fp_ret
def _update_c1(self, can_parsers):
cp = can_parsers[Bus.pt]
cp_cam = can_parsers[Bus.cam]
ret = structs.CarState()
ret.vEgoRaw = cp.vl["VehicleSpeed1"]["VehicleSpeed"] * CV.KPH_TO_MS
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
ret.standstill = ret.vEgoRaw < 0.1
ret.steeringAngleDeg = cp.vl["PSCM1"]["SteeringAngleServo"]
ret.steeringTorque = cp.vl["PSCM1"]["LKATorque"]
ret.steeringPressed = False
ret.gasPressed = cp.vl["PedalandBrake"]["AccPedal"] > 5.0
ret.brakePressed = bool(cp.vl["PedalandBrake"]["BrakePedalActive2"] or
cp.vl["PedalandBrake"]["BrakePedalActive"])
ret.gearShifter = {
0: GearShifter.park,
1: GearShifter.reverse,
2: GearShifter.neutral,
3: GearShifter.drive,
}.get(int(cp.vl["TCM0"]["GearShifter"]), GearShifter.unknown)
ret.cruiseState.available = bool(cp_cam.vl["FSM0"]["ACCStatusOnOff"])
ret.cruiseState.enabled = bool(cp_cam.vl["FSM0"]["ACCStatusActive"])
ret.cruiseState.speed = cp.vl["ACC"]["SpeedTargetACC"] * CV.KPH_TO_MS
ret.cruiseState.nonAdaptive = False
ret.cruiseState.standstill = ret.standstill
turn_signal = int(cp.vl["MiscCarInfo"]["TurnSignal"])
ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_stalk(
50, turn_signal == 1, turn_signal == 3)
ret.doorOpen = False
ret.seatbeltUnlatched = False
button_types = {
"ACCOnOffBtn": ButtonType.mainCruise,
"ACCStopBtn": ButtonType.cancel,
"ACCSetBtn": ButtonType.setCruise,
"ACCResumeBtn": ButtonType.resumeCruise,
"ACCMinusBtn": ButtonType.decelCruise,
"TimeGapIncreaseBtn": ButtonType.gapAdjustCruise,
"TimeGapDecreaseBtn": ButtonType.gapAdjustCruise,
}
button_events = []
for signal, button_type in button_types.items():
pressed = bool(cp.vl["CCButtons"][signal])
button_events.extend(create_button_events(pressed, self.c1_button_states[signal], {True: button_type}))
self.c1_button_states[signal] = pressed
ret.buttonEvents = button_events
self.c1_msg_pscm = cp.vl["PSCM1"]
self.c1_lka_torque = int(cp.vl["PSCM1"]["LKATorque"])
fp_ret = custom.StarPilotCarState.new_message()
return ret, fp_ret
@staticmethod
def get_can_parsers(CP):
if isinstance(CAR(CP.carFingerprint).config, VolvoC1PlatformConfig):
return {
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [
("VehicleSpeed1", 50),
("CCButtons", 100),
("PSCM1", 50),
("PedalandBrake", 100),
("TCM0", 10),
("ACC", 17),
("MiscCarInfo", 25),
], 0),
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.cam], [
("FSM0", 100),
("FSM1", 50),
], 2),
}
return {
Bus.main: CANParser(DBC[CP.carFingerprint][Bus.main], [], 0),
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 1),
Bus.party: CANParser(DBC[CP.carFingerprint][Bus.party], [], 2),
}
@@ -0,0 +1,26 @@
# ruff: noqa: E501
""" AUTO-FORMATTED USING opendbc/car/debug/format_fingerprints.py, EDIT STRUCTURE THERE."""
from opendbc.car.volvo.values import CAR
FINGERPRINTS = {
CAR.VOLVO_V40: [
# V40 2017
{8: 8, 16: 8, 48: 8, 64: 8, 85: 8, 101: 8, 112: 8, 114: 8, 117: 8, 128: 8, 176: 8, 192: 8, 208: 8, 224: 8, 240: 8, 245: 8, 256: 8, 272: 8, 288: 8, 291: 8, 293: 8, 304: 8, 325: 8, 336: 8, 352: 8, 424: 8, 432: 8, 437: 8, 464: 8, 472: 8, 480: 8, 528: 8, 608: 8, 624: 8, 640: 8, 648: 8, 652: 8, 656: 8, 657: 8, 681: 8, 693: 8, 704: 8, 707: 8, 709: 8, 816: 8, 832: 8, 848: 8, 853: 8, 864: 8, 880: 8, 912: 8, 928: 8, 943: 8, 944: 8, 968: 8, 970: 8, 976: 8, 992: 8, 997: 8, 1024: 8, 1029: 8, 1061: 8, 1072: 8, 1409: 8},
# V40 2015
{8: 8, 16: 8, 64: 8, 85: 8, 101: 8, 112: 8, 114: 8, 117: 8, 128: 8, 176: 8, 192: 8, 224: 8, 240: 8, 245: 8, 256: 8, 272: 8, 288: 8, 291: 8, 293: 8, 304: 8, 325: 8, 336: 8, 424: 8, 432: 8, 437: 8, 464: 8, 472: 8, 480: 8, 528: 8, 608: 8, 648: 8, 652: 8, 656: 8, 657: 8, 681: 8, 693: 8, 704: 8, 707: 8, 709: 8, 816: 8, 832: 8, 864: 8, 880: 8, 912: 8, 928: 8, 943: 8, 944: 8, 968: 8, 970: 8, 976: 8, 992: 8, 997: 8, 1024: 8, 1029: 8, 1061: 8, 1072: 8, 1409: 8},
# V40 2014
{8: 8, 16: 8, 64: 8, 85: 8, 101: 8, 112: 8, 114: 8, 117: 8, 128: 8, 176: 8, 192: 8, 224: 8, 240: 8, 245: 8, 256: 8, 272: 8, 288: 8, 291: 8, 293: 8, 304: 8, 325: 8, 336: 8, 424: 8, 432: 8, 437: 8, 464: 8, 472: 8, 480: 8, 528: 8, 608: 8, 648: 8, 652: 8, 657: 8, 681: 8, 693: 8, 704: 8, 707: 8, 709: 8, 816: 8, 864: 8, 880: 8, 912: 8, 928: 8, 943: 8, 944: 8, 968: 8, 970: 8, 976: 8, 992: 8, 997: 8, 1024: 8, 1029: 8, 1072: 8, 1409: 8},
],
CAR.VOLVO_XC40_RECHARGE: [{
21: 8, 22: 8, 23: 8, 26: 8, 69: 8, 85: 8, 87: 8, 88: 8, 96: 8, 103: 8, 104: 8, 105: 8, 128: 8, 144: 8, 146: 8, 147: 8, 151: 8, 304: 8, 336: 8, 339: 8, 341: 8, 394: 8, 395: 8, 640: 8, 656: 8, 666: 8, 773: 8, 778: 8, 1072: 8, 1120: 8, 1302: 8, 1336: 8, 1407: 8, 1422: 8, 1423: 8, 1424: 8, 1425: 8, 1426: 8
}],
CAR.VOLVO_S60_RECHARGE: [{
21: 8, 22: 8, 23: 8, 26: 8, 69: 8, 85: 8, 87: 8, 88: 8, 96: 8, 103: 8, 104: 8, 105: 8, 128: 8, 144: 8, 146: 8, 147: 8, 151: 8, 336: 8, 339: 8, 341: 8, 395: 8, 587: 8, 640: 8, 656: 8, 666: 8, 773: 8, 778: 8, 1072: 8, 1120: 8, 1298: 8, 1302: 8, 1319: 8, 1336: 8, 1407: 8, 1422: 8, 1423: 8, 1424: 8, 1425: 8, 1426: 8, 1554: 8, 1587: 8, 1718: 8, 1792: 8, 1793: 8, 1798: 8, 1799: 8, 1810: 8, 1813: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1843: 8, 1856: 8, 1858: 8, 1859: 8, 1860: 8, 1862: 8, 1863: 8, 1871: 8, 1872: 8, 1875: 8, 1879: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1920: 8, 1927: 8, 1937: 8, 1943: 8, 1953: 8, 1954: 8, 1955: 8, 1968: 8, 1969: 8, 1971: 8, 1975: 8, 1988: 8, 1990: 8, 2000: 8, 2001: 8, 2002: 8, 2004: 8, 2017: 8, 2018: 8, 2020: 8
}],
CAR.POLESTAR_2: [{
7: 4, 21: 8, 22: 8, 23: 8, 26: 8, 35: 8, 37: 8, 53: 8, 58: 8, 67: 8, 69: 8, 70: 8, 85: 8, 87: 8, 88: 8, 96: 8, 103: 8, 104: 8, 105: 8, 112: 8, 117: 8, 128: 8, 133: 8, 138: 8, 144: 8, 146: 8, 147: 8, 151: 8, 256: 8, 277: 8, 278: 8, 284: 8, 293: 8, 309: 8, 320: 8, 325: 8, 336: 8, 339: 8, 341: 8, 348: 8, 349: 8, 352: 8, 368: 8, 370: 8, 373: 8, 375: 8, 376: 8, 389: 8, 395: 8, 400: 8, 408: 8, 411: 8, 417: 8, 420: 8, 426: 8, 429: 8, 435: 8, 440: 8, 464: 8, 556: 8, 640: 8, 656: 8, 666: 8, 773: 8, 778: 8, 789: 8, 791: 8, 800: 8, 805: 8, 807: 8, 816: 8, 821: 8, 832: 8, 837: 8, 841: 8, 848: 8, 853: 8, 854: 8, 858: 8, 860: 8, 873: 8, 882: 8, 889: 8, 890: 8, 891: 8, 892: 8, 893: 8, 896: 8, 899: 8, 901: 8, 917: 8, 919: 8, 1043: 8, 1045: 8, 1061: 8, 1072: 8, 1077: 8, 1088: 8, 1093: 8, 1120: 8, 1127: 8, 1160: 8, 1168: 8, 1171: 8, 1174: 8, 1175: 8, 1177: 8, 1296: 8, 1302: 8, 1320: 8, 1334: 8, 1336: 8, 1407: 8, 1422: 8, 1423: 8, 1424: 8, 1425: 8, 1426: 8, 1428: 8, 1429: 8, 1430: 8, 1431: 8, 1432: 8, 1554: 8, 1584: 8, 1587: 8, 2022: 8
}],
}
FW_VERSIONS: dict[str, dict[tuple, list[bytes]]] = {
}
+397
View File
@@ -0,0 +1,397 @@
def checksum_lca_2_message(b0: int, b5: int) -> int:
"""
Compute checksum for VCU1 CAN ID 0x69 from bytes b0 and b5.
b0: first data byte (MSB) of the frame (usually 0x18 in your logs)
b5: sixth data byte of the frame (what you called Byte5)
Returns: checksum byte (0..255) that goes into byte index 6.
"""
if b0 == 0 and b5 == 128: # Hotfix openpilot test (don't know where this alleged test message comes from)
return 0
# Masks per checksum bit (bit 0..7) for b0 and b5
M0 = [0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00]
M5 = [0x83, 0x86, 0xCF, 0xCD, 0x09, 0x02, 0x44, 0x89]
def parity8(x: int) -> int:
# 1 if x has an odd number of bits set, else 0
x ^= x >> 4
x ^= x >> 2
x ^= x >> 1
return x & 1
b0 &= 0xFF
b5 &= 0xFF
c = 0
for bit in range(8):
p = 0
if M0[bit]:
p ^= parity8(b0 & M0[bit])
if M5[bit]:
p ^= parity8(b5 & M5[bit])
c |= (p << bit)
return c & 0xFF
def checksum_2_0x69_message(b0: int, b1: int, b3: int = 0, b4: int = 0) -> int:
"""
Compute checksum byte (b2) for CAN ID 0x69 (LCA_2 message).
The checksum depends on bytes 0, 1, 3, and 4. During normal driving (BYTE_1_MSBS_3=0),
bytes 3-4 (NEW_SIGNAL_2) are always 0, so only b0 and b1 matter. During stability
control events (aquaplaning, etc.), BYTE_1_MSBS_3 becomes non-zero and bytes 3-4
contain non-zero values that affect the checksum.
Args:
b0: Byte 0 (usually 0x18)
b1: Byte 1 ([7:5] BYTE_1_MSBS_3 | [4] PILOT_ASSIST_ENGAGED | [3:0] COUNTER_1)
b3: Byte 3 (NEW_SIGNAL_2 high byte, default 0)
b4: Byte 4 (NEW_SIGNAL_2 low byte, default 0)
Returns:
Checksum byte (0-255) for position 2
"""
b0 &= 0xFF
b1 &= 0xFF
b3 &= 0xFF
b4 &= 0xFF
def bit(byte, pos):
return (byte >> pos) & 1
c = 0
# Bit 0
c |= (bit(b0, 0) ^ bit(b1, 2) ^ bit(b1, 3) ^ bit(b1, 4) ^ bit(b1, 5) ^ bit(b4, 0) ^ bit(b4, 1)) << 0
# Bit 1
c |= (bit(b0, 1) ^ bit(b0, 3) ^ bit(b1, 0) ^ bit(b1, 3) ^ bit(b1, 5) ^ bit(b1, 6) ^
bit(b3, 1) ^ bit(b4, 0) ^ bit(b4, 1) ^ bit(b4, 2)) << 1
# Bit 2
c |= (bit(b0, 0) ^ bit(b0, 3) ^ bit(b1, 1) ^ bit(b1, 2) ^ bit(b1, 3) ^ bit(b1, 4) ^ bit(b1, 5) ^ bit(b1, 6) ^
bit(b3, 0) ^ bit(b3, 1) ^ bit(b4, 0) ^ bit(b4, 2) ^ bit(b4, 3)) << 2
# Bit 3
c |= (bit(b0, 0) ^ bit(b0, 1) ^ bit(b1, 0) ^ bit(b1, 4) ^ bit(b1, 6) ^
bit(b3, 0) ^ bit(b3, 1) ^ bit(b4, 0) ^ bit(b4, 3) ^ bit(b4, 4)) << 3
# Bit 4
c |= (bit(b0, 0) ^ bit(b0, 1) ^ bit(b0, 3) ^ bit(b1, 1) ^ bit(b1, 2) ^ bit(b1, 3) ^ bit(b1, 4) ^
bit(b4, 4) ^ bit(b4, 5)) << 4
# Bit 5
c |= (bit(b0, 1) ^ bit(b1, 0) ^ bit(b1, 2) ^ bit(b1, 3) ^ bit(b1, 5) ^
bit(b3, 1) ^ bit(b4, 5) ^ bit(b4, 6)) << 5
# Bit 6
c |= (bit(b0, 3) ^ bit(b1, 0) ^ bit(b1, 1) ^ bit(b1, 3) ^ bit(b1, 6) ^
bit(b3, 0) ^ bit(b4, 6) ^ bit(b4, 7)) << 6
# Bit 7
c |= (bit(b0, 3) ^ bit(b1, 1) ^ bit(b1, 2) ^ bit(b1, 4) ^ bit(b4, 0) ^ bit(b4, 7)) << 7
return c & 0xFF
def checksum_1_pscm_related_message(b1, b2):
"""
Computes checksum #1 (goes in byte[0]) for PSCM-related 0x17 message.
Depends only on (byte[1], byte[2]).
b1 = SIG1 counter (high nibble) | LCA_ENABLED_ECHO (low nibble)
b2 = 0x80 | SIG1 counter replica (low nibble)
Linear over GF(2), same shape as checksum_lca_2_message: each output bit is
the parity of a fixed mask over b1 and b2. Solved from 137,965 logged PSCM
frames (60 distinct (b1,b2) keys, leave-one-out cross-validated 60/60).
This replaces a 45-entry lookup table that covered only LCA_ENABLED_ECHO in
{0, 1, 4} and returned 0 on a miss. During an ESC intervention the rack
reports ECHO=6, so openpilot transmitted 128 consecutive frames with an
invalid checksum (0x00) before this fix.
Note: bit 3 of b1 (LCA_ENABLED_ECHO >= 8) has never been observed on the bus,
so its contribution is unconstrained by the data and is taken to be zero.
"""
# Masks per checksum bit (bit 0..7) for b1 and b2
M1 = [0x41, 0x82, 0x55, 0xF3, 0xA7, 0x46, 0x94, 0x20]
M2 = [0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80]
def parity8(x: int) -> int:
# 1 if x has an odd number of bits set, else 0
x ^= x >> 4
x ^= x >> 2
x ^= x >> 1
return x & 1
b1 &= 0xFF
b2 &= 0xFF
c = 0
for bit in range(8):
p = 0
if M1[bit]:
p ^= parity8(b1 & M1[bit])
if M2[bit]:
p ^= parity8(b2 & M2[bit])
c |= (p << bit)
return c & 0xFF
def checksum_2_pscm_related_message(b2):
"""
Computes checksum #2 (goes in byte[3]) for PSCM-related 0x17 message.
Depends only on byte[2].
"""
lut = {
0x80: 0xBF,
0x81: 0xF3,
0x82: 0x27,
0x83: 0x6B,
0x84: 0x92,
0x85: 0xDE,
0x86: 0x0A,
0x87: 0x46,
0x88: 0xE5,
0x89: 0xA9,
0x8A: 0x7D,
0x8B: 0x31,
0x8C: 0xC8,
0x8D: 0x84,
0x8E: 0x50,
}
return lut.get(b2, 0)
def checksum_lca_4_message(*args) -> int:
"""
Placeholder for LCA_4 (0x90) checksum calculation.
TODO: Implementation will be provided after checksum analysis is complete.
For now, returns 0 as a placeholder.
Args:
*args: Byte values needed for checksum calculation (TBD)
Returns:
Checksum byte (0-255)
"""
# Placeholder - will be replaced with actual checksum algorithm
return 0
class LCA3CounterSync:
"""
Best-effort pattern synchronization for LCA_3 COUNTER_1.
The counter follows a 20-element pattern that cycles based on transmission count.
We track recent observed counter values and match them against the pattern to
determine the current index. While not synchronized, we pass through stock values.
Once synchronized, we permanently use the pattern.
"""
PATTERN = [2, 2, 1, 2, 2, 2, 1, 2, 2, 3, 0, 2, 3, 2, 0, 2, 3, 2, 0, 3]
PATTERN_LEN = 20
WINDOW_SIZE = 5 # Track last 5 values for matching
MIN_CONFIDENCE = 4 # Need 4 consecutive matches to sync
def __init__(self):
self.pattern_index = None # Current index in pattern (None = not synced)
self.observed_window = [] # Circular buffer of last N observed values
self.confidence = 0 # Number of consecutive successful matches
def update(self, observed_counter: int) -> tuple:
"""
Update with newly observed counter value from stock message.
Args:
observed_counter: Counter value from CS.msg_lca_3['COUNTER_1']
Returns:
Tuple of (counter_to_send, is_synchronized)
"""
# If already synchronized, ignore stock and use our pattern permanently
if self.pattern_index is not None:
counter_to_send = self.PATTERN[self.pattern_index]
self.pattern_index = (self.pattern_index + 1) % self.PATTERN_LEN
return counter_to_send, True
# Not synchronized yet - try to find pattern index
self.observed_window.append(observed_counter)
if len(self.observed_window) > self.WINDOW_SIZE:
self.observed_window.pop(0)
# Attempt to sync if we have enough samples
if len(self.observed_window) >= 3:
self._attempt_sync()
# While not synced, pass through stock counter
return observed_counter, False
def _attempt_sync(self):
"""Try to find current pattern index based on observed window."""
# Try to match observation window against all positions in pattern
best_match_idx = None
best_match_len = 0
for start_idx in range(self.PATTERN_LEN):
match_len = self._count_match(start_idx)
if match_len > best_match_len:
best_match_len = match_len
best_match_idx = start_idx
# Require MIN_CONFIDENCE matching values to declare sync
if best_match_len >= self.MIN_CONFIDENCE:
# The match tells us where we WERE in the pattern
# We need to set index to NEXT position for next transmission
self.pattern_index = (best_match_idx + len(self.observed_window)) % self.PATTERN_LEN
self.confidence = best_match_len
def _count_match(self, pattern_start_idx: int) -> int:
"""
Count how many values in observed_window match pattern starting at pattern_start_idx.
Returns:
Number of consecutive matching values from start
"""
match_count = 0
for i, observed in enumerate(self.observed_window):
pattern_idx = (pattern_start_idx + i) % self.PATTERN_LEN
if observed == self.PATTERN[pattern_idx]:
match_count += 1
else:
break # Stop at first mismatch
return match_count
def is_synchronized(self) -> bool:
"""Returns True if we have synchronized to the pattern."""
return self.pattern_index is not None
def checksum_lca_5_message(byte0: int, byte1: int, byte3: int, byte4: int, byte5: int) -> int:
"""
Calculate checksum for LCA_5 message 0x67 (byte 2)
Args:
byte0: Byte 0 (0-255)
byte1: Byte 1 (0-255)
byte3: Byte 3 (0-255)
byte4: Byte 4 (0-255)
byte5: Byte 5 (0-255)
Returns:
int: Checksum value (0-255) for byte 2
Example:
>>> checksum = checksum_lca_5_message(0x80, 0x00, 0x4F, 0x00, 0x00)
>>> print(f"0x{checksum:02X}")
0x32
"""
# Helper function to extract a bit (LSB = bit 0)
def bit(byte_val, pos):
return (byte_val >> pos) & 1
checksum = 0
# Bit 0: XOR of 13 bits
checksum |= (
bit(byte0, 2) ^ bit(byte0, 3) ^ bit(byte0, 5) ^ bit(byte0, 7) ^
bit(byte1, 2) ^
bit(byte3, 4) ^ bit(byte3, 6) ^
bit(byte4, 0) ^ bit(byte4, 1) ^ bit(byte4, 5) ^ bit(byte4, 6) ^
bit(byte5, 3) ^ bit(byte5, 6)
) << 0
# Bit 1: XOR of 12 bits
checksum |= (
bit(byte0, 0) ^ bit(byte0, 3) ^ bit(byte0, 4) ^ bit(byte0, 7) ^
bit(byte1, 0) ^ bit(byte1, 3) ^
bit(byte3, 5) ^ bit(byte3, 7) ^
bit(byte4, 2) ^ bit(byte4, 6) ^
bit(byte5, 4) ^ bit(byte5, 7)
) << 1
# Bit 2: XOR of 17 bits
checksum |= (
bit(byte0, 1) ^ bit(byte0, 2) ^ bit(byte0, 3) ^ bit(byte0, 4) ^
bit(byte1, 0) ^ bit(byte1, 1) ^ bit(byte1, 2) ^ bit(byte1, 4) ^
bit(byte3, 4) ^
bit(byte4, 1) ^ bit(byte4, 3) ^ bit(byte4, 5) ^ bit(byte4, 6) ^
bit(byte5, 1) ^ bit(byte5, 3) ^ bit(byte5, 5) ^ bit(byte5, 6)
) << 2
# Bit 3: XOR of 19 bits
checksum |= (
bit(byte0, 0) ^ bit(byte0, 4) ^ bit(byte0, 7) ^
bit(byte1, 1) ^ bit(byte1, 3) ^ bit(byte1, 5) ^
bit(byte3, 4) ^ bit(byte3, 5) ^ bit(byte3, 6) ^
bit(byte4, 0) ^ bit(byte4, 1) ^ bit(byte4, 2) ^ bit(byte4, 4) ^ bit(byte4, 5) ^
bit(byte5, 1) ^ bit(byte5, 2) ^ bit(byte5, 3) ^ bit(byte5, 4) ^ bit(byte5, 7)
) << 3
# Bit 4: XOR of 16 bits
checksum |= (
bit(byte0, 1) ^ bit(byte0, 2) ^ bit(byte0, 3) ^ bit(byte0, 7) ^
bit(byte1, 4) ^ bit(byte1, 6) ^
bit(byte3, 4) ^ bit(byte3, 5) ^ bit(byte3, 7) ^
bit(byte4, 1) ^ bit(byte4, 2) ^ bit(byte4, 3) ^
bit(byte5, 2) ^ bit(byte5, 4) ^ bit(byte5, 5) ^ bit(byte5, 6)
) << 4
# Bit 5: XOR of 15 bits
checksum |= (
bit(byte0, 0) ^ bit(byte0, 2) ^ bit(byte0, 3) ^ bit(byte0, 4) ^
bit(byte1, 5) ^ bit(byte1, 7) ^
bit(byte3, 5) ^ bit(byte3, 6) ^
bit(byte4, 2) ^ bit(byte4, 3) ^ bit(byte4, 4) ^
bit(byte5, 3) ^ bit(byte5, 5) ^ bit(byte5, 6) ^ bit(byte5, 7)
) << 5
# Bit 6: XOR of 19 bits
checksum |= (
bit(byte0, 0) ^ bit(byte0, 1) ^ bit(byte0, 3) ^ bit(byte0, 4) ^ bit(byte0, 5) ^ bit(byte0, 7) ^
bit(byte1, 0) ^ bit(byte1, 6) ^
bit(byte3, 4) ^ bit(byte3, 6) ^ bit(byte3, 7) ^
bit(byte4, 0) ^ bit(byte4, 3) ^ bit(byte4, 4) ^ bit(byte4, 5) ^
bit(byte5, 1) ^ bit(byte5, 4) ^ bit(byte5, 6) ^ bit(byte5, 7)
) << 6
# Bit 7: XOR of 15 bits
checksum |= (
bit(byte0, 1) ^ bit(byte0, 2) ^ bit(byte0, 4) ^ bit(byte0, 5) ^
bit(byte1, 1) ^ bit(byte1, 7) ^
bit(byte3, 5) ^ bit(byte3, 7) ^
bit(byte4, 0) ^ bit(byte4, 4) ^ bit(byte4, 5) ^ bit(byte4, 6) ^
bit(byte5, 2) ^ bit(byte5, 5) ^ bit(byte5, 7)
) << 7
return checksum
# Test examples
if __name__ == "__main__":
print("CAN 0x67 Checksum Calculator")
print("=" * 60)
# Test cases
tests = [
([0x80, 0x00, 0x4F, 0x00, 0x00, 0xBA, 0x00], 0x32),
([0x80, 0x00, 0x8F, 0x00, 0x00, 0xBA, 0x00], 0x89),
([0x80, 0x00, 0xCF, 0x00, 0x00, 0xBA, 0x00], 0xE0),
([0x80, 0x00, 0x1F, 0x00, 0x00, 0xBA, 0x00], 0x06),
]
all_passed = True
for i, (bytes_list, expected) in enumerate(tests, 1):
calculated = checksum_lca_5_message(*bytes_list[:5])
status = "" if calculated == expected else ""
print(f"\nTest {i}: {status}")
print(f" Bytes: {' '.join(f'{b:02X}' for b in bytes_list)}")
print(f" Expected: 0x{expected:02X}")
print(f" Calculated: 0x{calculated:02X}")
if calculated != expected:
all_passed = False
print("\n" + "=" * 60)
if all_passed:
print("All tests passed! ✓")
else:
print("Some tests failed! ✗")
@@ -0,0 +1,47 @@
from opendbc.car import structs, get_safety_config
from opendbc.car.interfaces import CarInterfaceBase
from opendbc.car.volvo.carcontroller import CarController
from opendbc.car.volvo.carstate import CarState
from opendbc.car.volvo.values import CAR, VolvoC1PlatformConfig, VolvoSafetyFlags, VolvoSPAPlatformConfig
TransmissionType = structs.CarParams.TransmissionType
SAFETY_VOLVO = structs.CarParams.SafetyModel.volvo
class CarInterface(CarInterfaceBase):
CarState = CarState
CarController = CarController
@staticmethod
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
ret.brand = 'volvo'
platform = CAR(candidate).config
safety_param = 0
if isinstance(platform, VolvoSPAPlatformConfig):
safety_param = VolvoSafetyFlags.SPA.value
elif isinstance(platform, VolvoC1PlatformConfig):
safety_param = VolvoSafetyFlags.C1.value
ret.safetyConfigs = [get_safety_config(SAFETY_VOLVO, safety_param)]
#ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.noOutput)]
ret.dashcamOnly = False
ret.steerActuatorDelay = 0.2 if isinstance(platform, VolvoC1PlatformConfig) else 0.3
ret.steerLimitTimer = 0.1
ret.steerAtStandstill = not isinstance(platform, VolvoC1PlatformConfig)
# Use angle-based steering control for Volvo CMA platform
ret.steerControlType = structs.CarParams.SteerControlType.angle
# Note: No lateral tuning configuration needed for basic angle control
ret.radarUnavailable = True
ret.alphaLongitudinalAvailable = False
ret.pcmCruise = True
if isinstance(platform, VolvoC1PlatformConfig):
ret.transmissionType = TransmissionType.automatic
return ret
@@ -0,0 +1 @@
@@ -0,0 +1,57 @@
import pytest
from cereal import custom
from opendbc.can.packer import CANPacker
from opendbc.car import Bus, ButtonType, CanData, structs
from opendbc.car.volvo.carstate import CarState
from opendbc.car.volvo.interface import CarInterface
from opendbc.car.volvo.values import CAR, DBC
def _can_data(msg):
address, data, bus = msg
return CanData(address, data, bus)
def test_c1_carstate_decodes_vehicle_and_cruise_signals():
cp = CarInterface.get_non_essential_params(CAR.VOLVO_V40)
cs = CarState(cp, custom.StarPilotCarParams.new_message())
parsers = CarState.get_can_parsers(cp)
packer = CANPacker(DBC[cp.carFingerprint][Bus.pt])
messages = [
packer.make_can_msg("VehicleSpeed1", 0, {"VehicleSpeed": 72}),
packer.make_can_msg("CCButtons", 0, {"ACCStopBtn": 1, "ACCSetBtn": 1}),
packer.make_can_msg("PSCM1", 0, {"SteeringAngleServo": -12.5, "LKATorque": 7}),
packer.make_can_msg("PedalandBrake", 0, {"AccPedal": 6, "BrakePedalActive2": 1}),
packer.make_can_msg("TCM0", 0, {"GearShifter": 3}),
packer.make_can_msg("ACC", 0, {"SpeedTargetACC": 100}),
packer.make_can_msg("MiscCarInfo", 0, {"TurnSignal": 1}),
packer.make_can_msg("FSM0", 2, {"ACCStatusOnOff": 1, "ACCStatusActive": 1}),
packer.make_can_msg("FSM1", 2, {}),
]
packets = [(1_000_000, [_can_data(msg) for msg in messages])]
for parser in parsers.values():
parser.update(packets)
ret, _ = cs.update(parsers, None)
assert ret.vEgoRaw == pytest.approx(20.0)
assert ret.steeringAngleDeg == pytest.approx(-12.5, abs=0.05)
assert ret.steeringTorque == 7
assert ret.gasPressed and ret.brakePressed
assert ret.gearShifter == structs.CarState.GearShifter.drive
assert ret.cruiseState.available and ret.cruiseState.enabled
assert ret.cruiseState.speed == pytest.approx(100 / 3.6)
assert ret.leftBlinker and not ret.rightBlinker
assert len(ret.buttonEvents) == 2
assert any(event.type == ButtonType.cancel and event.pressed for event in ret.buttonEvents)
assert any(event.type == ButtonType.setCruise and event.pressed for event in ret.buttonEvents)
release = packer.make_can_msg("CCButtons", 0, {})
packets = [(2_000_000, [_can_data(release)])]
for parser in parsers.values():
parser.update(packets)
ret, _ = cs.update(parsers, None)
assert len(ret.buttonEvents) == 2
assert any(event.type == ButtonType.cancel and not event.pressed for event in ret.buttonEvents)
assert any(event.type == ButtonType.setCruise and not event.pressed for event in ret.buttonEvents)
@@ -0,0 +1,59 @@
import unittest
from opendbc.car.volvo.helpers import (
checksum_1_pscm_related_message,
checksum_2_pscm_related_message,
)
# (b1, b2) -> byte[0], observed on the bus.
# b1 = SIG1 counter (high nibble) | LCA_ENABLED_ECHO (low nibble), b2 = 0x80 | counter.
# ECHO 0/1/4 are normal driving; ECHO 6 only appears while ESC is intervening and was
# the case that used to fall through to a 0x00 checksum.
PSCM_RELATED_CHECKSUM_1 = {
(0x00, 0x80): 0xD4, (0x01, 0x80): 0xC9, (0x04, 0x80): 0xA0, (0x06, 0x80): 0x9A,
(0x10, 0x81): 0x98, (0x11, 0x81): 0x85, (0x14, 0x81): 0xEC, (0x16, 0x81): 0xD6,
(0x20, 0x82): 0x4C, (0x21, 0x82): 0x51, (0x24, 0x82): 0x38, (0x26, 0x82): 0x02,
(0x30, 0x83): 0x00, (0x31, 0x83): 0x1D, (0x34, 0x83): 0x74, (0x36, 0x83): 0x4E,
(0x40, 0x84): 0xF9, (0x41, 0x84): 0xE4, (0x44, 0x84): 0x8D, (0x46, 0x84): 0xB7,
(0x50, 0x85): 0xB5, (0x51, 0x85): 0xA8, (0x54, 0x85): 0xC1, (0x56, 0x85): 0xFB,
(0x60, 0x86): 0x61, (0x61, 0x86): 0x7C, (0x64, 0x86): 0x15, (0x66, 0x86): 0x2F,
(0x70, 0x87): 0x2D, (0x71, 0x87): 0x30, (0x74, 0x87): 0x59, (0x76, 0x87): 0x63,
(0x80, 0x88): 0x8E, (0x81, 0x88): 0x93, (0x84, 0x88): 0xFA, (0x86, 0x88): 0xC0,
(0x90, 0x89): 0xC2, (0x91, 0x89): 0xDF, (0x94, 0x89): 0xB6, (0x96, 0x89): 0x8C,
(0xA0, 0x8A): 0x16, (0xA1, 0x8A): 0x0B, (0xA4, 0x8A): 0x62, (0xA6, 0x8A): 0x58,
(0xB0, 0x8B): 0x5A, (0xB1, 0x8B): 0x47, (0xB4, 0x8B): 0x2E, (0xB6, 0x8B): 0x14,
(0xC0, 0x8C): 0xA3, (0xC1, 0x8C): 0xBE, (0xC4, 0x8C): 0xD7, (0xC6, 0x8C): 0xED,
(0xD0, 0x8D): 0xEF, (0xD1, 0x8D): 0xF2, (0xD4, 0x8D): 0x9B, (0xD6, 0x8D): 0xA1,
(0xE0, 0x8E): 0x3B, (0xE1, 0x8E): 0x26, (0xE4, 0x8E): 0x4F, (0xE6, 0x8E): 0x75,
}
# b2 -> byte[3], same source
PSCM_RELATED_CHECKSUM_2 = {
0x80: 0xBF, 0x81: 0xF3, 0x82: 0x27, 0x83: 0x6B, 0x84: 0x92,
0x85: 0xDE, 0x86: 0x0A, 0x87: 0x46, 0x88: 0xE5, 0x89: 0xA9,
0x8A: 0x7D, 0x8B: 0x31, 0x8C: 0xC8, 0x8D: 0x84, 0x8E: 0x50,
}
class TestPscmRelatedChecksums(unittest.TestCase):
def test_checksum_1_matches_the_car(self):
for (b1, b2), expected in PSCM_RELATED_CHECKSUM_1.items():
with self.subTest(b1=hex(b1), b2=hex(b2)):
assert checksum_1_pscm_related_message(b1, b2) == expected
def test_checksum_1_covers_esc_echo(self):
# regression: ECHO=6 used to miss the lookup table and return 0x00, which the
# receiving ECU logged as a checksum fault for as long as ESC was active
for counter in range(15):
b1, b2 = (counter << 4) | 6, 0x80 | counter
with self.subTest(counter=counter):
assert checksum_1_pscm_related_message(b1, b2) == PSCM_RELATED_CHECKSUM_1[(b1, b2)]
def test_checksum_2_matches_the_car(self):
for b2, expected in PSCM_RELATED_CHECKSUM_2.items():
with self.subTest(b2=hex(b2)):
assert checksum_2_pscm_related_message(b2) == expected
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,137 @@
from collections import defaultdict
from types import SimpleNamespace
from opendbc.car.volvo.carcontroller import CarController
from opendbc.car.volvo.helpers import checksum_lca_5_message
from opendbc.car.volvo.interface import CarInterface
from opendbc.car.volvo.values import CAR, DBC
from opendbc.car.volvo.volvocan import create_c1_checksum
def _zero_message():
return defaultdict(int)
def _state():
return SimpleNamespace(
out=SimpleNamespace(steeringAngleDeg=0.0, vEgoRaw=12.0, steeringTorque=0.0),
msg_lca=_zero_message(),
msg_pscm=_zero_message(),
msg_pscm_related=_zero_message(),
msg_lca_3=_zero_message(),
msg_lca_2=_zero_message(),
msg_lca_5=_zero_message(),
msg_lca_4=_zero_message(),
msg_lca_6=_zero_message(),
msg_lca_7=_zero_message(),
pilot_assist_engaged=False,
)
class _Actuators:
steeringAngleDeg = 30.0
def as_builder(self):
return SimpleNamespace(steeringAngleDeg=self.steeringAngleDeg)
def test_controller_emits_valid_eight_byte_messages_and_lca5_checksum():
cp = CarInterface.get_non_essential_params("POLESTAR_2")
controller = CarController(DBC[cp.carFingerprint], cp)
cs = _state()
cc = SimpleNamespace(latActive=True, actuators=_Actuators())
actuators, can_sends = controller.update(cc, cs, 0, None)
assert can_sends
assert {msg[2] for msg in can_sends} == {0, 2}
assert all(len(msg[1]) == 8 for msg in can_sends)
assert 0.0 < actuators.steeringAngleDeg < 540.0
lca5 = next(msg for msg in can_sends if msg[0] == 0x67)
data = lca5[1]
assert data[2] == checksum_lca_5_message(data[0], data[1], data[3], data[4], data[5])
def test_controller_relays_stock_lca5_angle_when_inactive():
cp = CarInterface.get_non_essential_params("VOLVO_XC40_RECHARGE")
controller = CarController(DBC[cp.carFingerprint], cp)
cs = _state()
cs.msg_lca_5["LCA_5_STEER"] = 12.0
cc = SimpleNamespace(latActive=False, actuators=_Actuators())
_, can_sends = controller.update(cc, cs, 0, None)
lca5 = next(msg for msg in can_sends if msg[0] == 0x67)
# The inactive path must not manufacture a new angle command.
raw = ((lca5[1][6] & 0x7F) << 8) | lca5[1][7]
if raw & (1 << 14):
raw -= 1 << 15
assert abs(raw * 0.05596 - 12.0) < 0.1
def _c1_state():
return SimpleNamespace(
out=SimpleNamespace(steeringAngleDeg=10.0, vEgo=12.0, vEgoRaw=12.0),
c1_lka_torque=5,
c1_msg_pscm=_zero_message(),
)
def test_c1_controller_emits_checked_steering_and_pscm_relay():
cp = CarInterface.get_non_essential_params(CAR.VOLVO_V40)
controller = CarController(DBC[cp.carFingerprint], cp)
cc = SimpleNamespace(
latActive=True,
actuators=_Actuators(),
cruiseControl=SimpleNamespace(cancel=False),
)
actuators, can_sends = controller.update(cc, _c1_state(), 0, None)
assert [(msg[0], msg[2]) for msg in can_sends] == [(0x125, 2), (0xD0, 0)]
fsm = can_sends[1][1]
assert fsm[7] & 0x3 == 3
assert fsm[6] == create_c1_checksum(fsm)
assert 0.0 < actuators.steeringAngleDeg <= 2.0
def test_c1_controller_sends_only_cancel_button():
cp = CarInterface.get_non_essential_params(CAR.VOLVO_V40)
controller = CarController(DBC[cp.carFingerprint], cp)
cc = SimpleNamespace(
latActive=False,
actuators=_Actuators(),
cruiseControl=SimpleNamespace(cancel=True),
)
_, can_sends = controller.update(cc, _c1_state(), 0, None)
buttons = next(msg for msg in can_sends if msg[0] == 0x10)
assert buttons[2] == 0
assert buttons[1][7] == 0x10
assert buttons[1][6] == 0
def test_c1_controller_temporarily_drops_steering_on_zero_torque_fault():
cp = CarInterface.get_non_essential_params(CAR.VOLVO_V40)
controller = CarController(DBC[cp.carFingerprint], cp)
cs = _c1_state()
cs.c1_lka_torque = 0
cc = SimpleNamespace(
latActive=True,
actuators=_Actuators(),
cruiseControl=SimpleNamespace(cancel=False),
)
directions = []
for _ in range(23):
_, can_sends = controller.update(cc, cs, 0, None)
directions.extend(msg[1][7] & 0x3 for msg in can_sends if msg[0] == 0xD0)
assert directions[:-1] == [3] * 11
assert directions[-1] == 0
while controller.frame <= 122:
_, can_sends = controller.update(cc, cs, 0, None)
fsm = next(msg for msg in can_sends if msg[0] == 0xD0)
assert fsm[1][7] & 0x3 == 3
+196
View File
@@ -0,0 +1,196 @@
from dataclasses import dataclass, field
from enum import IntFlag
from opendbc.car.structs import CarParams
from opendbc.car import Bus, CarSpecs, DbcDict, PlatformConfig, Platforms
from opendbc.car.lateral import AngleSteeringLimits
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.docs_definitions import CarDocs, CarHarness, CarParts
from opendbc.car.fw_query_definitions import FwQueryConfig
Ecu = CarParams.Ecu
# C1 support is adapted from the original dragonpilot V40 port:
# https://github.com/dragonpilot/dragonpilot/commit/773dce507082d931236b64dca8024dce9625446f
class VolvoSafetyFlags(IntFlag):
SPA = 1
C1 = 2
class CarControllerParams:
STEER_STEP = 1 # 100 Hz LCA command frequency (controlsd runs at 100 Hz)
# Max commanded-vs-actual steering angle error (deg). Stock Volvo Pilot Assist holds
# commanded within ~1.7° of actual even under sustained driver override; bounding the
# command to actual ± this error prevents the stale-command snap-back that causes
# aggressive post-release overcorrection.
ANGLE_ERROR = 3.0
# LCA torque-authority envelope, modeled after stock Pilot Assist behavior.
# LCA_STEER_LOOSELY (positive arm) and LCA_STEER_LOOSELY_INV (negative arm)
# form a directional envelope that PSCM applies to its EPS torque. Stock PA:
# - holds both arms at saturation (±LCA_AUTH_MAX) when no driver torque
# - on driver override, collapses both arms symmetrically at COLLAPSE_RATE
# until envelope reaches ~±LCA_AUTH_SPLIT, then splits asymmetrically:
# the arm matching driver direction (yielding) settles at ±PLATEAU_YIELD,
# the counter arm holds at ±PLATEAU_COUNTER (yield is shallower than counter)
# - rebuilds at REBUILD_RATE after release (~3 s back to saturation)
# See route_analysis/lca_override_mechanism.md for the data behind these.
LCA_AUTH_MAX = 614 # signal saturation
LCA_AUTH_PLATEAU_COUNTER = 130 # counter-arm magnitude during sustained override
# Override trigger thresholds on |CS.out.steeringTorque| (op-convention raw
# units, mirror of DRIVER_INPUT). Must be ABOVE the resting-hand noise floor
# (CS.steeringPressed uses |raw|>2 as a sensitive DM-fallback floor and does
# NOT indicate override intent — don't use it for envelope triggering).
# Hysteresis: enter override at ENTER, exit at EXIT (< ENTER) to prevent the
# envelope flapping between collapse and rebuild when driver torque hovers
# near a single threshold (was causing ~10 Hz EPS-torque ripple in lane
# changes when driver applied 6-8 raw to "ride along" with op).
LCA_AUTH_OVERRIDE_ENTER = 5
LCA_AUTH_OVERRIDE_EXIT = 3
# "Light contact" / haptic-acknowledgment region. When |drv| crosses into
# [LIGHT_THRESH, OVERRIDE_THRESH] from below, briefly collapse the envelope
# for LIGHT_HOLD_FRAMES (a haptic confirmation of hand-on-wheel detection),
# then rebuild even while the contact persists. Prevents the driver from
# needing to sustain force just to feel that the system noticed them — helps
# with hand-fatigue / RSI.
# Rising edge detected via per-frame derivative; the brief-yield window does
# NOT re-arm while still active, so a steady elevated torque only triggers
# one yield and then the envelope rebuilds.
# Cooldown: light_collapse only fires when real_override has been off for
# LIGHT_COOLDOWN_FRAMES — suppresses repeated firings during active
# co-steering (lane changes), where |drv| oscillates and would otherwise
# re-arm the haptic-ack window each time, causing felt ripple.
LCA_AUTH_LIGHT_THRESH = 3 # min |drv| to consider as contact
LCA_AUTH_LIGHT_RISE_DELTA = 1.0 # min per-frame increase in |drv| to count as rising contact
LCA_AUTH_LIGHT_HOLD_FRAMES = 15 # ~150 ms of yield on fresh light contact
LCA_AUTH_LIGHT_COOLDOWN_FRAMES = 30 # ~300 ms quiet-time on real_override before light contact re-arms
# Yield-arm plateau scales with driver-torque magnitude so brief strong presses
# (potholes, lane corrections) get full yield while light sustained pressure
# only gets a soft yield. yield_signed = YIELD_BASE YIELD_SLOPE *
# max(0, drv_mag_filt OVERRIDE_ENTER), clamped to [YIELD_MIN, YIELD_BASE].
# At |drv|=7 (just over threshold): yield = +60 (light resistance).
# At |drv|=14: yield ≈ -4 (crosses past zero — EPS hands wheel to driver).
# drv_mag_filt is a low-pass of |drv| (alpha=0.04, ~250 ms time constant) —
# without it, 1-2 unit driver-torque jitter became ~10 unit yield-arm jitter
# which PSCM converted to felt ripple at sustained co-steering pressure.
LCA_AUTH_YIELD_BASE = 60 # yield-arm magnitude at the override threshold
LCA_AUTH_YIELD_SLOPE = 8 # counts of yield reduction per unit |drv torque| above threshold
LCA_AUTH_YIELD_MIN = -30 # cap how far past zero the yield arm can go (full hand-over)
LCA_AUTH_YIELD_LP_ALPHA = 0.04 # LP-filter coefficient on |drv| for yield calc (~250 ms tau)
LCA_AUTH_SPLIT = 200 # symmetric → asymmetric handover
LCA_AUTH_REBUILD_RATE = 230 # counts/s (≈ 2.7 s rebuild from 0 to 614)
LCA_AUTH_COLLAPSE_RATE = 2500 # counts/s base (scales with |drv|/THRESH for sharper pothole jolts)
# Angle limits for rate limiting
ANGLE_LIMITS: AngleSteeringLimits = AngleSteeringLimits(
540, # deg - 1.5 turns to lock
([0., 5., 25.], [2.5, 1.5, .2]), # rate up limits at different speeds
([0., 5., 25.], [5., 2., .3]), # rate down limits at different speeds
)
C1_STEER_NO = 0
C1_STEER = 3
C1_N_ZERO_TORQUE = 12
C1_ANGLE_ERROR = 20.0
C1_ANGLE_DELTA_BP = [0., 8.33, 13.89, 19.44, 25., 30.55, 36.1]
C1_ANGLE_DELTA_UP = [2., 1.2, .25, .20, .15, .10, .10]
C1_ANGLE_DELTA_DOWN = [2., 1.2, .25, .20, .15, .10, .10]
C1_ANGLE_LIMITS: AngleSteeringLimits = AngleSteeringLimits(
359.9,
(C1_ANGLE_DELTA_BP, C1_ANGLE_DELTA_UP),
(C1_ANGLE_DELTA_BP, C1_ANGLE_DELTA_DOWN),
)
@dataclass
class VolvoCarDocs(CarDocs):
package: str = "Pilot Assist & Adaptive Cruise Control"
car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.custom]))
@dataclass
class VolvoCMAPlatformConfig(PlatformConfig):
dbc_dict: DbcDict = field(default_factory=lambda: {
Bus.main: 'volvo_mid_1',
Bus.party: 'volvo_mid_1',
Bus.pt: 'volvo_front_1_cma',
})
@dataclass
class VolvoSPAPlatformConfig(PlatformConfig):
dbc_dict: DbcDict = field(default_factory=lambda: {
Bus.main: 'volvo_mid_1',
Bus.party: 'volvo_mid_1',
Bus.pt: 'volvo_front_1_spa',
})
@dataclass
class VolvoC1PlatformConfig(PlatformConfig):
dbc_dict: DbcDict = field(default_factory=lambda: {
Bus.pt: 'volvo_v40_2017_pt',
Bus.cam: 'volvo_v40_2017_pt',
})
class CAR(Platforms):
VOLVO_V40 = VolvoC1PlatformConfig(
[VolvoCarDocs("Volvo V40 2013-19")],
CarSpecs(
mass=1610,
wheelbase=2.647,
steerRatio=14.7,
centerToFrontRatio=0.44,
minSteerSpeed=1.0 * CV.KPH_TO_MS,
),
)
VOLVO_XC40_RECHARGE = VolvoCMAPlatformConfig(
[VolvoCarDocs("Volvo XC40 Recharge 2021-23")],
CarSpecs(
mass=2170,
wheelbase=2.702,
steerRatio=15.8,
centerToFrontRatio=0.52,
),
)
VOLVO_S60_RECHARGE = VolvoSPAPlatformConfig(
[VolvoCarDocs("Volvo S60 Recharge 2024")],
CarSpecs(
mass=2020,
wheelbase=2.872,
steerRatio=16.2,
centerToFrontRatio=0.516,
),
)
# Polestar 2 is technically CMA, but appears to use SPA DBC for CAN 1 bus
POLESTAR_2 = VolvoSPAPlatformConfig(
[VolvoCarDocs("Polestar 2 2020-25")],
CarSpecs(
mass=2123,
wheelbase=2.735,
steerRatio=15.8,
centerToFrontRatio=0.52,
),
)
# FW Query configuration for Volvo CMA platform
# FW_QUERY_CONFIG = FwQueryConfig(
# requests=[
# Request(
# [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.UDS_VERSION_REQUEST],
# [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.UDS_VERSION_RESPONSE],
# bus=0,
# ),
# ],
# )
FW_QUERY_CONFIG = FwQueryConfig(
requests=[]
)
DBC = CAR.create_dbc_map()
+505
View File
@@ -0,0 +1,505 @@
from opendbc.car.volvo.helpers import (checksum_lca_2_message, checksum_2_0x69_message, checksum_1_pscm_related_message,
checksum_2_pscm_related_message, checksum_lca_5_message)
from opendbc.car.carlog import carlog
def create_c1_pscm_message(packer, msg_pscm: dict):
values = {
"LKATorque": 0,
"SteeringAngleServo": msg_pscm["SteeringAngleServo"],
"byte0": msg_pscm["byte0"],
"byte3": msg_pscm["byte3"],
"byte4": msg_pscm["byte4"],
"byte7": msg_pscm["byte7"],
"LKAActive": int(msg_pscm["LKAActive"]) & 0xD,
}
return packer.make_can_msg("PSCM1", 2, values)
def create_c1_checksum(data: bytes) -> int:
angle_raw = ((data[4] & 0x3F) << 8) | data[5]
direction = data[7] & 0x3
checksum_sum = (data[3] + direction + angle_raw + (angle_raw >> 8)) & 0xFF
return checksum_sum ^ 0xFF
def create_c1_steering_control(packer, apply_angle: float, lat_active: bool):
values = {
"SET_X_E3": 0xE3,
"SET_X_B4": 0xB4,
"SET_X_08": 0x08,
"TrqLim": 0,
"LKAAngleReq": apply_angle,
"LKASteerDirection": 3 if lat_active else 0,
"SET_X_25": 0x25,
"SET_X_02": 0x02,
}
data = packer.make_can_msg("FSM1", 0, values)[1]
values["Checksum"] = create_c1_checksum(data)
return packer.make_can_msg("FSM1", 0, values)
def create_c1_cancel(packer):
return packer.make_can_msg("CCButtons", 0, {"ACCStopBtn": 1})
def create_lca_message(packer, lat_active: bool, apply_angle: float, msg_lca: dict,
authority_pos: int = 614, authority_neg: int = -614,
overrides: dict | None = None):
"""
Create LCA (Lane Centering Assist) steering command for Volvo CMA platform.
Uses angle-based control via the LCA_STEER signal.
NOTE: This message must be sent continuously (even when inactive) because
stock LCA is permanently blocked by panda safety. When lat_active=False,
we send a safe/inactive LCA message to maintain PSCM communication.
Args:
packer: CAN packer instance
lat_active: Whether lateral control is active
apply_angle: Steering angle in degrees (positive = left, negative = right)
msg_lca: Dictionary containing LCA message values
authority_pos: LCA_STEER_LOOSELY value [0..614] right-pull torque-authority
envelope. Saturated (614) for stock-equivalent stiff feel; the
carcontroller envelope tracker collapses this on driver override
and rebuilds slowly to reproduce stock PA's easy-override feel.
authority_neg: LCA_STEER_LOOSELY_INV value [-614..0] left-pull authority.
overrides: Optional dict of signal overrides (keys are UPPERCASE DBC signal names)
"""
if not lat_active:
return packer.make_can_msg('LCA', 2, msg_lca)
# In openpilot, a positive angle corresponds to a LEFT turn.
# In Volvo, a positive LCA_STEER value corresponds to a LEFT turn.
values = {
'NEW_SIGNAL_1': 3,
'LCA_ENABLE_INV': 0 if lat_active else 1,
'LANE_KEEP_ACTIVE_INV': 3,
'LCA_STEER_LOOSELY': int(authority_pos) if lat_active else 0,
'NEW_SIGNAL_7': 7,
'LCA_STEER_LOOSELY_INV': int(authority_neg) if lat_active else 0,
# Steering rate - Stock LCA increased from 35 to 39 steppedly when steering request was overridden by openpilot that couldn't steer enough
'LCA_RATE_OF_CHANGE': 80 if lat_active else 251,
'LCA_STEER': msg_lca['LCA_STEER'],
'NEW_SIGNAL_6': 15,
}
# Apply any overrides from live testing config
if overrides:
for key, val in overrides.items():
values[key] = val
return packer.make_can_msg('LCA', 2, values)
def create_pscm_message(packer, lat_active: bool, msg_pscm: dict, frame: int):
values = {
'PSCM_ANGLE_SENSOR': msg_pscm['PSCM_ANGLE_SENSOR'],
'BIT_0': msg_pscm['BIT_0'],
'HANDS_ON_STEERING_WHEEL_A': msg_pscm['HANDS_ON_STEERING_WHEEL_A'],
'HANDS_ON_STEERING_WHEEL_B': msg_pscm['HANDS_ON_STEERING_WHEEL_B'],
'BYTE_4': msg_pscm['BYTE_4'],
'DRIVER_INPUT_DEVIATION': msg_pscm['DRIVER_INPUT_DEVIATION'],
'BYTE_6': msg_pscm['BYTE_6'],
'BYTE_7': msg_pscm['BYTE_7'],
}
# Spoof hands on wheel while openpilot is actively steering, so the stock EPS
# doesn't fault/nag on torque that didn't come from a human.
if lat_active:
values['HANDS_ON_STEERING_WHEEL_B'] = 186 if frame % 2 == 0 else 154 # msg_pscm['HANDS_ON_STEERING_WHEEL_B']
values['HANDS_ON_STEERING_WHEEL_A'] = 195 if frame % 2 == 0 else 249 # msg_pscm['HANDS_ON_STEERING_WHEEL_A']
return packer.make_can_msg('PSCM', 0, values)
def create_lca_3_message(packer, lat_active: bool, apply_angle: float, msg_lca_3: dict, counter_value: int):
"""
Create LCA_3 message for Volvo CMA platform.
This message enables PSCM to accept LCA commands.
Args:
packer: CAN packer instance
lat_active: Whether lateral control is active
apply_angle: Steering angle in degrees (used for direction indicator)
msg_lca_3: Dictionary containing LCA_3 message values
counter_value: Counter value to use (from pattern or stock)
"""
values = {
'NEW_SIGNAL_3': 0 if lat_active else msg_lca_3['NEW_SIGNAL_3'],
'LCA_ACCEPT_COMMANDS_RELATED': 15 if lat_active else msg_lca_3['LCA_ACCEPT_COMMANDS_RELATED'],
'NEW_SIGNAL_2': 0 if lat_active else msg_lca_3['NEW_SIGNAL_2'],
'NEW_SIGNAL_5': 30 if lat_active else msg_lca_3['NEW_SIGNAL_5'],
'LCA_ACCEPT_COMMANDS_INV': 0 if lat_active else msg_lca_3['LCA_ACCEPT_COMMANDS_INV'],
'NEW_SIGNAL_4': 3 if lat_active else msg_lca_3['NEW_SIGNAL_4'],
'SPEED_A': msg_lca_3['SPEED_A'],
'SPEED_B': msg_lca_3['SPEED_B'],
'NEW_SIGNAL_8': 1 if lat_active else msg_lca_3['NEW_SIGNAL_8'],
'NEW_SIGNAL_7': 3 if lat_active else msg_lca_3['NEW_SIGNAL_7'],
'NEW_SIGNAL_9': msg_lca_3['NEW_SIGNAL_9'],
'COUNTER_1': counter_value,
}
return packer.make_can_msg('LCA_3', 2, values)
def diff_dicts(a, b):
only_in_a = a.keys() - b.keys()
only_in_b = b.keys() - a.keys()
in_both = a.keys() & b.keys()
changed = {k: (a[k], b[k]) for k in in_both if a[k] != b[k]}
return {
"only_in_a": {k: a[k] for k in only_in_a},
"only_in_b": {k: b[k] for k in only_in_b},
"changed": changed,
}
def create_lca_2_message(packer, lat_active: bool, msg_lca_2: dict, counter_1: int, counter_2: int):
"""
Create LCA_2 message to spoof PILOT_ASSIST_ENGAGED when openpilot is active.
When lat_active=True, we set PILOT_ASSIST_ENGAGED=1 to make PSCM accept LCA commands,
even if the driver has disabled stock Pilot Assist.
Args:
packer: CAN packer instance
lat_active: Whether lateral control is active
msg_lca_2: Dictionary containing LCA_2 message values from car
counter_1: Managed COUNTER_1 value (increments by +2 mod 16)
counter_2: Managed COUNTER_2 value (increments by +4 mod 16)
"""
#return packer.make_can_msg('LCA_2', 2, msg_lca_2)
#if not lat_active:
# return packer.make_can_msg('LCA_2', 2, msg_lca_2)
#values = dict(msg_lca_2)
values = {
'BYTE_0': 24 if lat_active else msg_lca_2['BYTE_0'], # 24 always
'COUNTER_1': msg_lca_2['COUNTER_1'], # Byte 1 Low Nibble [5:8] - 4-bit counter that increments by +2 (modulo 16)
'PILOT_ASSIST_ENGAGED': 1 if lat_active else msg_lca_2['PILOT_ASSIST_ENGAGED'], # Byte 1 [4]
'BYTE_1_BITFIELD_0': msg_lca_2['BYTE_1_BITFIELD_0'],
'ESC_ACTUATING': msg_lca_2['ESC_ACTUATING'],
'ESC_ELIGIBLE': msg_lca_2['ESC_ELIGIBLE'],
'CHECKSUM_2': msg_lca_2['CHECKSUM_2'], # Checksum on bytes 0 and 1
'NEW_SIGNAL_2': 0 if lat_active else msg_lca_2['NEW_SIGNAL_2'],
'COUNTER_2': msg_lca_2['COUNTER_2'], # Byte 5 Low Nibble - 4-bit counter that increments by +4 (modulo 16)
'NEW_SIGNAL_3': 3 if lat_active else msg_lca_2['NEW_SIGNAL_3'],
'BRAKE_PEDAL_PRESSED_B': msg_lca_2['BRAKE_PEDAL_PRESSED_B'],
'BRAKE_PEDAL_PRESSED_A': msg_lca_2['BRAKE_PEDAL_PRESSED_A'],
'CHECKSUM_1': msg_lca_2['CHECKSUM_1'], # Byte 6 is a checksum based on Bytes 1, 2, and 5 only
'BYTE_7': 0 if lat_active else msg_lca_2['BYTE_7'],
}
dat = packer.make_can_msg('LCA_2', 2, values)
built_bytes = dat[1] # dat is (addr, bytes, bus) tuple - extract bytes
b0 = built_bytes[0]
b1 = built_bytes[1]
b2 = built_bytes[2]
b3 = built_bytes[3]
b4 = built_bytes[4]
b5 = built_bytes[5]
values['CHECKSUM_1'] = checksum_lca_2_message(b0, b5)
# Only validate when not active and message is valid (BYTE_0 should be 24, not 0)
if not lat_active:
#assert values['CHECKSUM_1'] == msg_lca_2['CHECKSUM_1']
if values['CHECKSUM_1'] != msg_lca_2['CHECKSUM_1']:
carlog.warning("[volvocan.py] LCA_2 CHECKSUM mismatch")
print(f"b0={b0}, b1={b1}, b2={b2}, b5={b5}, calculated={values['CHECKSUM_1']}, expected={msg_lca_2['CHECKSUM_1']}")
#assert False
# Checksum 2 - depends on bytes 0, 1, 3, and 4
checksum_2 = checksum_2_0x69_message(b0, b1, b3, b4)
values['CHECKSUM_2'] = checksum_2
if not lat_active:
if values['CHECKSUM_2'] != msg_lca_2['CHECKSUM_2']:
carlog.warning("[volvocan.py] LCA_2 CHECKSUM_2 mismatch")
print(f"b0={b0}, b1={b1}, b3={b3}, b4={b4}, calculated={values['CHECKSUM_2']}, expected={msg_lca_2['CHECKSUM_2']}")
#assert False
values['COUNTER_1'] = counter_1
values['COUNTER_2'] = counter_2
# Re-pack with updated counters to get correct bytes for checksum calculation
dat = packer.make_can_msg('LCA_2', 2, values)
built_bytes = dat[1] # dat is (addr, bytes, bus) tuple - extract bytes
b0 = built_bytes[0]
b1 = built_bytes[1]
b3 = built_bytes[3]
b4 = built_bytes[4]
b5 = built_bytes[5]
values['CHECKSUM_1'] = checksum_lca_2_message(b0, b5)
values['CHECKSUM_2'] = checksum_2_0x69_message(b0, b1, b3, b4)
return packer.make_can_msg('LCA_2', 2, values)
def create_lca_5_message(packer, lat_active: bool, target_angle_deg: float, msg_lca_5: dict, counter: int,
overrides: dict | None = None):
"""
Create LCA_5 message (0x67) with angle-based steering control.
Args:
packer: CAN packer instance
lat_active: Whether lateral control is active
target_angle_deg: Target steering angle in degrees (positive = left, negative = right)
msg_lca_5: Stock LCA_5 values from car
counter: Counter value (0-15, increments by 4)
overrides: Optional dict of signal overrides (keys are UPPERCASE DBC signal names)
Returns:
CAN message for LCA_5 on bus 2
"""
# DBC defines LCA_5_STEER as 15-bit signed with scale 0.05596 deg/count
# Packer handles the encoding automatically - just pass the angle in degrees
# Build values dictionary (wheel speeds and counter unchanged)
values = {
'WHEEL_SPEED_1': msg_lca_5['WHEEL_SPEED_1'],
'NEW_SIGNAL_4': msg_lca_5['NEW_SIGNAL_4'],
'NEW_SIGNAL_1': msg_lca_5['NEW_SIGNAL_1'],
'WHEEL_SPEED_2': msg_lca_5['WHEEL_SPEED_2'],
'NEW_SIGNAL_5': msg_lca_5['NEW_SIGNAL_5'],
'NEW_SIGNAL_2': msg_lca_5['NEW_SIGNAL_2'],
'LCA_5_STEER': target_angle_deg if lat_active else msg_lca_5['LCA_5_STEER'],
'COUNTER': counter,
}
# Apply any overrides from live testing config
if overrides:
for key, val in overrides.items():
values[key] = val
dat = packer.make_can_msg('LCA_5', 2, values)
built_bytes = dat[1] # dat is (addr, bytes, bus) tuple - extract bytes
values['CHECKSUM'] = checksum_lca_5_message(built_bytes[0], built_bytes[1], built_bytes[3], built_bytes[4], built_bytes[5])
return packer.make_can_msg('LCA_5', 2, values)
def create_speed_message(packer, msg_speed: dict):
"""
Forward SPEED message (0x60) by copying all bytes.
Args:
packer: CAN packer instance
msg_speed: Dictionary containing SPEED message values from car
"""
values = {
'SPEED': msg_speed['SPEED'],
'NEW_SIGNAL_1': msg_speed['NEW_SIGNAL_1'],
'NEW_SIGNAL_2': msg_speed['NEW_SIGNAL_2'],
'NEW_SIGNAL_3': msg_speed['NEW_SIGNAL_3'],
'NEW_SIGNAL_4': msg_speed['NEW_SIGNAL_4'],
'NEW_SIGNAL_5': msg_speed['NEW_SIGNAL_5'],
'NEW_SIGNAL_6': msg_speed['NEW_SIGNAL_6'],
}
return packer.make_can_msg('SPEED', 2, values)
def create_speed_2_message(packer, msg_speed_2: dict):
"""
Forward SPEED_2 message (0x68) by copying all bytes.
Args:
packer: CAN packer instance
msg_speed_2: Dictionary containing SPEED_2 message values from car
"""
values = {
'WHEEL_SPEED_LEFT': msg_speed_2['WHEEL_SPEED_LEFT'],
'WHEEL_SPEED_RIGHT': msg_speed_2['WHEEL_SPEED_RIGHT'],
'NEW_SIGNAL_1': msg_speed_2['NEW_SIGNAL_1'],
'NEW_SIGNAL_2': msg_speed_2['NEW_SIGNAL_2'],
'NEW_SIGNAL_3': msg_speed_2['NEW_SIGNAL_3'],
'NEW_SIGNAL_4': msg_speed_2['NEW_SIGNAL_4'],
'COUNTER_1': msg_speed_2['COUNTER_1'],
'COUNTER_2': msg_speed_2['COUNTER_2'],
}
return packer.make_can_msg('SPEED_2', 2, values)
def create_speed_3_message(packer, msg_speed_3: dict):
"""
Forward SPEED_3 message (0x60) by copying all bytes.
Args:
packer: CAN packer instance
msg_speed_3: Dictionary containing SPEED_3 message values from car
"""
values = {
'ALL_BYTES': msg_speed_3['ALL_BYTES'],
}
return packer.make_can_msg('SPEED_3', 2, values)
def create_0x1a_message(packer, msg_0x1a: dict):
"""
Forward 0x1A message by copying all bytes.
Args:
packer: CAN packer instance
msg_0x1a: Dictionary containing 0x1A message values from car
"""
values = {
'ALL_BYTES': msg_0x1a['ALL_BYTES'],
}
return packer.make_can_msg('NEW_MSG_1A', 2, values)
def create_gear_position_message(packer, msg_gear_position: dict):
"""
Forward GEAR_POSITION message by copying all bytes.
Args:
packer: CAN packer instance
msg_gear_position: Dictionary containing GEAR_POSITION message values from car
"""
values = dict(msg_gear_position)
values['GEAR_POSITION'] = msg_gear_position['GEAR_POSITION'] # 3
return packer.make_can_msg('GEAR_POSITION', 2, values)
def create_egsm_message(packer, msg_egsm: dict):
"""
Forward EGSM message by copying all bytes.
Args:
packer: CAN packer instance
msg_egsm: Dictionary containing EGSM message values from car
"""
values = {
'ALL_BYTES': msg_egsm['ALL_BYTES'],
}
return packer.make_can_msg('EGSM', 0, values)
def create_pscm_related_message(packer, lat_active: bool, stock_lca_engaged: bool, msg_pscm_related: dict, sig1_counter: int):
# BO_ 23 PSCM_RELATED: 8 XXX
# SG_ CHECKSUM : 7|8@0+ (1,0) [0|255] "" XXX
# SG_ LCA_ENABLED_ECHO : 11|4@0+ (1,0) [0|15] "" XXX
# SG_ SIG1_BYTE_1_HI_NIBBLE : 15|4@0+ (1,0) [0|15] "" XXX
# SG_ SIG1_REPLICA_BYTE_2_LO_NIBLE : 19|4@0+ (1,0) [0|15] "" XXX
# SG_ NEW_SIGNAL_2 : 23|4@0+ (1,0) [0|15] "" XXX
# SG_ BYTE_3 : 31|8@0+ (1,0) [0|255] "" XXX
# SG_ BYTE_4_5 : 39|16@0+ (1,0) [0|65535] "" XXX
# SG_ BYTE_6 : 55|8@0+ (1,0) [0|255] "" XXX
# SG_ BYTE_7 : 63|8@0+ (1,0) [0|255] "" XXX
values = dict(msg_pscm_related)
# Update SIG1 counter (same value in both locations for redundancy)
values['SIG1_BYTE_1_HI_NIBBLE'] = sig1_counter
values['SIG1_REPLICA_BYTE_2_LO_NIBLE'] = sig1_counter
dat = packer.make_can_msg('PSCM_RELATED', 0, values)
built_bytes = dat[1] # dat is (addr, bytes, bus) tuple - extract bytes
b1 = built_bytes[1]
b2 = built_bytes[2]
values['CHECKSUM_1'] = checksum_1_pscm_related_message(b1, b2)
values['CHECKSUM_2'] = checksum_2_pscm_related_message(b2)
#assert values['CHECKSUM_1'] == msg_pscm_related['CHECKSUM_1']
#assert values['CHECKSUM_2'] == msg_pscm_related['CHECKSUM_2']
if lat_active and not stock_lca_engaged:
values['LCA_ENABLED_ECHO'] = 0
b1 = packer.make_can_msg('PSCM_RELATED', 0, values)[1][1]
values['CHECKSUM_1'] = checksum_1_pscm_related_message(b1, b2)
return packer.make_can_msg('PSCM_RELATED', 0, values)
def create_lca_4_message(packer, lat_active: bool, msg_lca_4: dict, lca_4_steer: int,
overrides: dict | None = None):
"""
Create LCA_4 (0x90) message to maintain Pilot Assist state when openpilot is active.
Critical: LCA_ENABLE (byte 1 bits 0-1) must be held at 3 (both bits=1) when lat_active.
When PA turns off, these bits start varying (become counters). We need to keep them
stable at 3 to fool PSCM into thinking PA is still on, allowing LCA commands to be accepted.
Based on analysis from route_analysis/pilot_assist_off/BASELINE_FILTERED_FINDINGS.md:
- Message 0x090 byte 1 bits 0-1 are PA state signals
- During PA ON: bits are stable at 3 (binary 11)
- During PA OFF: bits start varying (counters)
- PSCM uses this to determine whether to accept LCA steering commands
Args:
packer: CAN packer instance
lat_active: Whether lateral control is active
msg_lca_4: Dictionary containing LCA_4 message values from car
lca_4_steer: Pre-computed signed angle with hysteresis applied
overrides: Optional dict of signal overrides (keys are UPPERCASE DBC signal names)
Returns:
CAN message for LCA_4 on bus 2
"""
if not lat_active:
# When not active, just relay stock message unchanged
return packer.make_can_msg('LCA_4', 2, msg_lca_4)
# When lat_active, force LCA_ENABLE to 3 (PA ON state)
values = {
'BYTE_0': msg_lca_4['BYTE_0'],
'LCA_ENABLE': 3, # Force bits 0-1 to 1 (value=3 means both bits set)
'BYTE_1_FLAGS': msg_lca_4['BYTE_1_FLAGS'],
'BYTE_1_NIBBLE_HI': msg_lca_4['BYTE_1_NIBBLE_HI'],
'BYTE_2_3': msg_lca_4['BYTE_2_3'],
'YAW_RATE': msg_lca_4['YAW_RATE'],
'BYTE_6': msg_lca_4['BYTE_6'],
'BYTE_7_NIBBLE_LO': msg_lca_4['BYTE_7_NIBBLE_LO'],
'BYTE_7_NIBBLE_HI': msg_lca_4['BYTE_7_NIBBLE_HI'],
}
# Apply any overrides from live testing config
if overrides:
for key, val in overrides.items():
values[key] = val
# TODO: Add checksum calculation when checksum function is implemented
# If message has a checksum signal, it would be calculated here like:
# values['CHECKSUM'] = checksum_lca_4_message(...)
# TODO: Add checksum validation when not active (once checksum is known)
# if not lat_active and 'CHECKSUM' in msg_lca_4:
# if values['CHECKSUM'] != msg_lca_4['CHECKSUM']:
# carlog.warning("[volvocan.py] LCA_4 CHECKSUM mismatch")
return packer.make_can_msg('LCA_4', 2, values)
def create_lca_6_message(packer, lat_active: bool, msg_lca_6: dict, lca_6_steer: int,
overrides: dict | None = None):
if not lat_active:
# When not active, just relay stock message unchanged
return packer.make_can_msg('LCA_6', 2, msg_lca_6)
values = {
'LCA_6_STEER': msg_lca_6['LCA_6_STEER'],
'LCA_6_STEER_2': msg_lca_6['LCA_6_STEER_2'],
'NEW_SIGNAL_1': msg_lca_6['NEW_SIGNAL_1'],
'NEW_SIGNAL_2': msg_lca_6['NEW_SIGNAL_2'],
'NEW_SIGNAL_3': msg_lca_6['NEW_SIGNAL_3'],
'NEW_SIGNAL_4': msg_lca_6['NEW_SIGNAL_4'],
'NEW_SIGNAL_5': msg_lca_6['NEW_SIGNAL_5'],
}
# Apply any overrides from live testing config
if overrides:
for key, val in overrides.items():
values[key] = val
return packer.make_can_msg('LCA_6', 2, values)
def create_lca_7_message(packer, lat_active: bool, msg_lca_7: dict, lca_7_steer: int, lca_7_delta_steer: int,
overrides: dict | None = None, steer_active: bool = False):
if not lat_active:
# When not active, just relay stock message unchanged
return packer.make_can_msg('LCA_7', 2, msg_lca_7)
values = {
'LCA_7_STEER': msg_lca_7['LCA_7_STEER'],
'LCA_7_DELTA_STEER': msg_lca_7['LCA_7_DELTA_STEER'],
'NEW_SIGNAL_1': msg_lca_7['NEW_SIGNAL_1'],
'NEW_SIGNAL_2': msg_lca_7['NEW_SIGNAL_2'],
'NEW_SIGNAL_3': msg_lca_7['NEW_SIGNAL_3'],
'NEW_SIGNAL_4': msg_lca_7['NEW_SIGNAL_4'],
}
# Apply any overrides from live testing config
if overrides:
for key, val in overrides.items():
values[key] = val
return packer.make_can_msg('LCA_7', 2, values)
File diff suppressed because it is too large Load Diff
@@ -1497,9 +1497,11 @@ BO_ 913 BCM_PO_11: 8 Vector__XXX
SG_ BCM_Door_Dri_Status : 5|1@0+ (1,0) [0|1] "" PT_ESC_ABS SG_ BCM_Door_Dri_Status : 5|1@0+ (1,0) [0|1] "" PT_ESC_ABS
SG_ BCM_Shift_R_MT_SW_Status : 39|2@0+ (1,0) [0|3] "" PT_ESC_ABS SG_ BCM_Shift_R_MT_SW_Status : 39|2@0+ (1,0) [0|3] "" PT_ESC_ABS
SG_ LDA_BTN : 4|1@0+ (1,0) [0|1] "" XXX SG_ LDA_BTN : 4|1@0+ (1,0) [0|1] "" XXX
SG_ RAY_LKAS_BTN : 4|1@0+ (1,0) [0|1] "" XXX
BO_ 1426 LABEL11: 8 XXX BO_ 1426 LABEL11: 8 XXX
SG_ CC_React : 34|1@1+ (1,0) [0|1] "" XXX SG_ CC_React : 34|1@1+ (1,0) [0|1] "" XXX
SG_ CC_Engaged : 35|1@1+ (1,0) [0|1] "" XXX
BO_ 910 WHL_SPD12_FS: 5 iBAU BO_ 910 WHL_SPD12_FS: 5 iBAU
SG_ CRC : 0|8@1+ (1,0) [0|0] "" Vector__XXX SG_ CRC : 0|8@1+ (1,0) [0|0] "" Vector__XXX
@@ -1497,9 +1497,11 @@ BO_ 913 BCM_PO_11: 8 Vector__XXX
SG_ BCM_Door_Dri_Status : 5|1@0+ (1,0) [0|1] "" PT_ESC_ABS SG_ BCM_Door_Dri_Status : 5|1@0+ (1,0) [0|1] "" PT_ESC_ABS
SG_ BCM_Shift_R_MT_SW_Status : 39|2@0+ (1,0) [0|3] "" PT_ESC_ABS SG_ BCM_Shift_R_MT_SW_Status : 39|2@0+ (1,0) [0|3] "" PT_ESC_ABS
SG_ LDA_BTN : 4|1@0+ (1,0) [0|1] "" XXX SG_ LDA_BTN : 4|1@0+ (1,0) [0|1] "" XXX
SG_ RAY_LKAS_BTN : 4|1@0+ (1,0) [0|1] "" XXX
BO_ 1426 LABEL11: 8 XXX BO_ 1426 LABEL11: 8 XXX
SG_ CC_React : 34|1@1+ (1,0) [0|1] "" XXX SG_ CC_React : 34|1@1+ (1,0) [0|1] "" XXX
SG_ CC_Engaged : 35|1@1+ (1,0) [0|1] "" XXX
BO_ 910 WHL_SPD12_FS: 5 iBAU BO_ 910 WHL_SPD12_FS: 5 iBAU
SG_ CRC : 0|8@1+ (1,0) [0|0] "" Vector__XXX SG_ CRC : 0|8@1+ (1,0) [0|0] "" Vector__XXX
@@ -0,0 +1,25 @@
VERSION ""
NS_ :
NS_DESC_
CM_
BA_DEF_
BA_
VAL_
BS_:
BU_: XXX
BO_ 1157 LFAHDA_MFC: 8 XXX
SG_ HDA_USM : 0|2@1+ (1,0) [0|3] "" XXX
SG_ HDA_Active : 2|1@1+ (1,0) [0|1] "" XXX
SG_ HDA_Icon_State : 3|2@1+ (1,0) [0|3] "" XXX
SG_ HDA_Chime : 7|1@1+ (1,0) [0|1] "" XXX
SG_ HDA_VSetReq : 8|8@1+ (1,0) [0|255] "km/h" XXX
SG_ LFA_SysWarning : 16|3@1+ (1,0) [0|7] "" XXX
SG_ HDA_Icon_Wheel : 20|1@1+ (1,0) [0|1] "" XXX
SG_ HDA_LdwSysState : 21|2@1+ (1,0) [0|3] "" XXX
SG_ LFA_Icon_State : 24|2@1+ (1,0) [0|3] "" XXX
SG_ LFA_USM : 27|2@1+ (1,0) [0|3] "" XXX
SG_ HDA_SysWarning : 29|2@1+ (1,0) [0|3] "" XXX
File diff suppressed because it is too large Load Diff
+245
View File
@@ -0,0 +1,245 @@
BO_ 21 DRIVER_INPUT: 8 XXX
SG_ BYTE_0 : 7|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_1 : 15|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_2 : 16|8@1+ (1,0) [0|255] "" XXX
SG_ STEERING_DRIVER_RATE_OF_CHANGE : 31|8@0- (1,0) [0|255] "" XXX
SG_ BYTE_4 : 39|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_5 : 40|8@1+ (1,0) [0|255] "" XXX
SG_ STEERING_DRIVER_INPUT : 55|8@0- (1,1) [0|255] "" XXX
SG_ BYTE_7 : 63|8@0+ (1,0) [0|255] "" XXX
BO_ 22 PSCM: 8 XXX
SG_ PSCM_ANGLE_SENSOR : 6|15@0- (0.05596,0) [-916|916] "º" XXX
SG_ BIT_0 : 7|1@0+ (1,0) [0|1] "" XXX
SG_ HANDS_ON_STEERING_WHEEL_A : 23|8@0+ (1,0) [0|255] "" XXX
SG_ HANDS_ON_STEERING_WHEEL_B : 31|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_4 : 39|8@0+ (1,0) [0|255] "" XXX
SG_ DRIVER_INPUT_DEVIATION : 47|8@0- (1,0) [-128|127] "" XXX
SG_ BYTE_6 : 55|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_7 : 63|8@0+ (1,0) [0|255] "" XXX
BO_ 23 PSCM_RELATED: 8 XXX
SG_ CHECKSUM_1 : 7|8@0+ (1,0) [0|255] "" XXX
SG_ LCA_ENABLED_ECHO : 11|4@0+ (1,0) [0|15] "" XXX
SG_ SIG1_BYTE_1_HI_NIBBLE : 15|4@0+ (1,0) [0|15] "" XXX
SG_ SIG1_REPLICA_BYTE_2_LO_NIBLE : 19|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_2 : 23|4@0+ (1,0) [0|15] "" XXX
SG_ CHECKSUM_2 : 31|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_4 : 39|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_5 : 47|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_6 : 55|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_7 : 63|8@0+ (1,0) [0|255] "" XXX
BO_ 26 NEW_MSG_1A: 8 XXX
SG_ NEW_SIGNAL_2 : 5|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_1 : 7|1@0+ (1,0) [0|1] "" XXX
BO_ 38 NEW_MSG_26: 8 XXX
SG_ NEW_SIGNAL_1 : 5|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_2 : 15|8@0+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_4 : 35|12@0+ (1,0) [0|4095] "" XXX
SG_ NEW_SIGNAL_3 : 39|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_6 : 51|12@0+ (1,0) [0|4095] "" XXX
SG_ NEW_SIGNAL_5 : 55|1@0+ (1,0) [0|1] "" XXX
BO_ 55 NEW_MSG_37: 8 XXX
SG_ ACCELERATOR_PEDAL_RATE_OF_CHANGE : 6|15@0+ (1,0) [0|32767] "" XXX
BO_ 69 EGSM: 8 XXX
SG_ COUNTER_1 : 3|12@0+ (1,0) [0|4095] "" XXX
SG_ GEAR_LEVER_DBC_INCOHERENT : 5|2@0+ (1,0) [0|3] "" XXX
SG_ COUNTER_2 : 19|12@0+ (1,0) [0|4095] "" XXX
SG_ GEAR_1ST_RATCH : 20|1@0+ (1,0) [0|1] "" XXX
SG_ GEAR_RATCHED_UP : 21|1@0+ (1,0) [0|1] "" XXX
SG_ GEAR_RATCHED_DOWN : 22|1@0+ (1,0) [0|1] "" XXX
SG_ GEAR_PRESSED : 36|2@0+ (1,0) [0|3] "" XXX
SG_ PARKING_BRAKE_BUTTON : 38|1@0+ (1,0) [0|1] "" XXX
SG_ COUNTER_3 : 51|12@0+ (1,0) [0|4095] "" XXX
BO_ 85 SAS: 8 XXX
SG_ SAS_ANGLE_SENSOR : 6|15@0- (0.05596,0) [0|32767] "º" XXX
SG_ NEW_SIGNAL_1 : 7|1@0+ (1,0) [0|1] "" XXX
SG_ SAS_INPUT_ACTIVITY : 21|6@0+ (1,0) [0|16383] "" XXX
SG_ NEW_SIGNAL_2 : 23|2@0+ (1,0) [0|3] "" XXX
SG_ SAS_RATE_OF_CHANGE : 31|8@0- (1,0) [0|255] "" XXX
SG_ SAS_CHECKSUM : 39|8@0+ (1,0) [0|4095] "" XXX
SG_ NEW_SIGNAL_4 : 43|20@0+ (1,0) [0|1048575] "" XXX
SG_ SAS_COUNTER : 44|4@1+ (1,0) [0|15] "" XXX
BO_ 87 LCA_3: 8 XXX
SG_ NEW_SIGNAL_3 : 0|1@0+ (1,0) [0|1] "" XXX
SG_ LCA_ACCEPT_COMMANDS_RELATED : 4|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_2 : 7|3@0+ (1,0) [0|7] "" XXX
SG_ NEW_SIGNAL_5 : 12|5@0+ (1,0) [0|31] "" XXX
SG_ LCA_ACCEPT_COMMANDS_INV : 13|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_4 : 15|2@0+ (1,0) [0|3] "" XXX
SG_ SPEED_A : 23|16@0+ (1,0) [0|65535] "" XXX
SG_ SPEED_B : 39|16@0+ (1,0) [0|65535] "" XXX
SG_ NEW_SIGNAL_8 : 51|4@0+ (1,0) [0|15] "" XXX
SG_ COUNTER_1 : 53|2@0+ (1,0) [0|3] "" XXX
SG_ NEW_SIGNAL_7 : 55|2@0+ (1,0) [0|3] "" XXX
SG_ NEW_SIGNAL_9 : 63|8@0+ (1,0) [0|255] "" XXX
BO_ 88 LCA: 8 XXX
SG_ NEW_SIGNAL_3 : 1|2@0+ (1,0) [0|3] "" XXX
SG_ NEW_SIGNAL_8 : 2|1@0+ (1,0) [0|1] "" XXX
SG_ LCA_ENABLE_INV : 3|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_2 : 5|2@0+ (1,0) [0|3] "" XXX
SG_ NEW_SIGNAL_1 : 7|2@0+ (1,0) [0|3] "" XXX
SG_ LCA_STEER_LOOSELY_1 : 15|8@0+ (1,0) [0|255] "" XXX
SG_ LCA_STEER_ACTIVE_INCOHERENT : 16|1@0+ (1,0) [0|1] "" XXX
SG_ LCA_STEER_ACTIVE : 18|2@0+ (1,0) [0|3] "" XXX
SG_ NEW_SIGNAL_7 : 21|3@0+ (1,0) [0|7] "" XXX
SG_ NEW_SIGNAL_9 : 23|2@0+ (1,0) [0|3] "" XXX
SG_ LCA_STEER_LOOSELY_2 : 31|8@0+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_4 : 39|8@0+ (1,0) [0|255] "" XXX
SG_ CURVE_RIGHT : 45|6@0+ (1,0) [0|63] "" XXX
SG_ NEW_SIGNAL_5 : 46|2@1+ (1,0) [0|3] "" XXX
SG_ LCA_STEER : 55|8@0+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_10 : 59|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_6 : 60|4@1+ (1,0) [0|15] "" XXX
BO_ 96 SPEED_3: 8 XXX
SG_ NEW_SIGNAL_1 : 0|8@1+ (1,0) [0|255] "" XXX
SG_ SPEED_COUNTER : 15|8@0+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_3 : 23|8@0+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_5 : 24|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_2 : 27|3@0+ (1,0) [0|7] "" XXX
SG_ NEW_SIGNAL_4 : 31|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_8 : 34|3@0+ (1,0) [0|7] "" XXX
SG_ NEW_SIGNAL_6 : 35|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_7 : 39|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_9 : 40|8@1+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_10 : 55|8@0+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_11 : 56|8@1+ (1,0) [0|255] "" XXX
BO_ 103 LCA_5: 8 XXX
SG_ WHEEL_SPEED_1 : 6|15@0+ (0.1,0) [0|255] "rpm" XXX
SG_ NEW_SIGNAL_4 : 7|1@0+ (1,0) [0|1] "" XXX
SG_ CHECKSUM : 23|8@0+ (1,0) [0|4095] "" XXX
SG_ NEW_SIGNAL_1 : 24|4@1+ (1,0) [0|15] "" XXX
SG_ COUNTER : 28|4@1+ (1,0) [0|15] "" XXX
SG_ WHEEL_SPEED_2 : 39|15@0+ (0.1,0) [0|32767] "rpm" XXX
SG_ NEW_SIGNAL_5 : 40|1@0+ (1,0) [0|1] "" XXX
SG_ LCA_TURN_BITS : 55|8@0+ (1,0) [0|255] "" XXX
SG_ LCA_5_STEER : 63|8@0+ (1,0) [0|255] "" XXX
BO_ 104 SPEED_2: 8 XXX
SG_ WHEEL_SPEED_3 : 6|15@0+ (0.1,0) [0|32767] "rpm" XXX
SG_ WHEEL_SPEED_4 : 39|15@0+ (0.1,0) [0|32767] "rpm" XXX
SG_ NEW_SIGNAL_2 : 51|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_3 : 54|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_1 : 56|8@1+ (1,0) [0|255] "" XXX
BO_ 105 LCA_2: 8 XXX
SG_ BYTE_0 : 7|8@0+ (1,0) [0|2047] "" XXX
SG_ COUNTER_1 : 11|4@0+ (1,0) [0|4095] "" XXX
SG_ PILOT_ASSIST_ENGAGED : 12|1@0+ (1,0) [0|1] "" XXX
SG_ BYTE_1_MSBS_3 : 15|3@0+ (1,0) [0|7] "" XXX
SG_ CHECKSUM_2 : 23|8@0+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_2 : 31|16@0+ (1,0) [0|65535] "" XXX
SG_ COUNTER_2 : 43|4@0+ (1,0) [0|4095] "" XXX
SG_ NEW_SIGNAL_3 : 45|2@0+ (1,0) [0|3] "" XXX
SG_ BRAKE_PEDAL_PRESSED_B : 46|1@0+ (1,0) [0|1] "" XXX
SG_ BRAKE_PEDAL_PRESSED_A : 47|1@0+ (-1,1) [0|1] "" XXX
SG_ CHECKSUM_1 : 55|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_7 : 63|8@0+ (1,0) [0|255] "" XXX
BO_ 112 BUS1_SPEED: 8 XXX
SG_ BUS1_SPEED : 23|16@0+ (0.01886,0) [0|65535] "m/s" XXX
BO_ 128 GEAR_POSITION: 8 XXX
SG_ NEW_SIGNAL_7 : 3|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_6 : 7|3@0+ (1,0) [0|7] "" XXX
SG_ NEW_SIGNAL_8 : 12|4@1+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_1 : 43|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_3 : 47|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_2 : 48|4@1+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_4 : 55|1@0+ (1,0) [0|1] "" XXX
SG_ GEAR_POSITION : 57|2@0+ (1,0) [0|3] "" XXX
SG_ NEW_SIGNAL_5 : 63|1@0+ (1,0) [0|1] "" XXX
BO_ 144 LCA_4: 8 XXX
SG_ BYTE_0 : 7|8@0+ (1,0) [0|16383] "" XXX
SG_ LCA_ENABLE : 9|2@0+ (1,0) [0|3] "" XXX
SG_ BYTE_1_FLAGS : 11|2@0+ (1,0) [0|3] "" XXX
SG_ BYTE_1_NIBBLE_HI : 15|4@0+ (1,0) [0|63] "" XXX
SG_ BYTE_2_3 : 23|16@0- (1,0) [0|65535] "" XXX
SG_ YAW_RATE : 39|16@0- (1,0) [0|65535] "" XXX
SG_ BYTE_6 : 55|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_7_NIBBLE_LO : 59|4@0+ (1,0) [0|15] "" XXX
SG_ BYTE_7_NIBBLE_HI : 60|4@1+ (1,0) [0|15] "" XXX
BO_ 146 NEW_MSG_92: 8 XXX
SG_ NEW_SIGNAL_1 : 7|8@0+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_2 : 12|4@1+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_3 : 23|8@0+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_4 : 25|7@1+ (1,0) [0|127] "" XXX
SG_ NEW_SIGNAL_5 : 38|7@0+ (1,0) [0|127] "" XXX
SG_ NEW_SIGNAL_6 : 40|7@1+ (1,0) [0|127] "" XXX
SG_ NEW_SIGNAL_7 : 48|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_8 : 49|3@1+ (1,0) [0|7] "" XXX
SG_ NEW_SIGNAL_9 : 57|7@1+ (1,0) [0|127] "" XXX
BO_ 147 NEW_MSG_93: 8 XXX
SG_ NEW_SIGNAL_1 : 38|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_2 : 47|8@0+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_4 : 52|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_3 : 55|1@0+ (1,0) [0|1] "" XXX
BO_ 151 LCA_SUSPECT: 8 XXX
SG_ NEW_SIGNAL_1 : 7|8@0+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_2 : 8|8@1+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_3 : 23|8@0+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_6 : 25|2@0+ (1,0) [0|3] "" XXX
SG_ NEW_SIGNAL_5 : 26|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_4 : 27|5@1+ (1,0) [0|31] "" XXX
SG_ NEW_SIGNAL_7 : 39|8@0+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_9 : 43|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_8 : 44|4@1+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_10 : 48|8@1+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_12 : 56|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_11 : 63|7@0+ (1,0) [0|127] "" XXX
BO_ 336 NEW_MSG_150: 8 XXX
SG_ IGN_DRAFT : 5|2@0+ (1,0) [0|3] "" XXX
BO_ 341 NEW_MSG_155: 8 XXX
SG_ NEW_SIGNAL_1 : 23|8@0+ (1,0) [0|255] "" XXX
BO_ 592 ECM_1: 8 XXX
SG_ ACCELERATOR_PEDAL_POS : 31|8@0+ (1,0) [0|255] "" XXX
BO_ 773 NEW_MSG_305: 8 XXX
SG_ NEW_SIGNAL_1 : 23|8@0+ (1,0) [0|255] "" XXX
BO_ 778 NEW_MSG_30A: 8 XXX
SG_ NEW_SIGNAL_1 : 15|8@0+ (1,0) [0|255] "" XXX
BO_ 832 BUS1_CRUISE_CONTROL: 8 XXX
SG_ CRUISE_CONTROL_ENABLED : 56|1@0+ (1,0) [0|1] "" XXX
SG_ CRUISE_CONTROL_ENABLED_IDLE_TRAFFIC : 57|1@0+ (1,0) [0|1] "" XXX
BO_ 896 NEW_MSG_380: 8 XXX
SG_ NEW_SIGNAL_1 : 23|8@0+ (1,0) [0|255] "" XXX
BO_ 1336 NEW_MSG_538: 8 XXX
SG_ SWM_MULTIMEDIA_ACTIVITY : 22|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_1 : 23|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_3 : 24|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_2 : 29|1@0+ (1,0) [0|1] "" XXX
CM_ BO_ 23 "Might be related to PSCM 0x16";
CM_ SG_ 87 LCA_ACCEPT_COMMANDS_RELATED "Goes to 15 when actively operating LCA";
CM_ SG_ 87 LCA_ACCEPT_COMMANDS_INV "Goes to 0 when PSCM allowed to receive LCA commands";
CM_ SG_ 87 NEW_SIGNAL_9 "NEW_SIGNAL_9 appears to be similar to LCA_5_STEER, but different scale, and zero-point is at 128. I haven't seen what happens once LCA_TURN_BITS wrap";
CM_ SG_ 88 LCA_ENABLE_INV "Master enable, active-low";
CM_ SG_ 88 CURVE_RIGHT "Only appears to be HIGH on curve right, LOW curve left";
CM_ SG_ 88 LCA_STEER "Seems torque-based, signed, follows the road curvature";
CM_ SG_ 103 WHEEL_SPEED_1 "Possible Front Left (FL)";
CM_ SG_ 103 WHEEL_SPEED_2 "Possible Front Right (FR)";
CM_ SG_ 103 LCA_TURN_BITS "Two-byte torque encoding (high byte). Left: 128->134 (increments), Right: 255->249 (decrements), Neutral: 186";
CM_ SG_ 103 LCA_5_STEER "Two-byte torque encoding (low byte). Left: 0->255 (wraps at boundary), Right: 255->0 (wraps at boundary)";
CM_ SG_ 104 WHEEL_SPEED_3 "Possible Rear Left (RR)";
CM_ SG_ 104 WHEEL_SPEED_4 "Possibe Rear Right (RR)";
CM_ SG_ 592 ACCELERATOR_PEDAL_POS "Full gas ~200; Idle ~20";
@@ -0,0 +1,14 @@
BO_ 55 NEW_MSG_37: 8 XXX
SG_ ACCELERATOR_PEDAL_RATE_OF_CHANGE : 6|15@0+ (1,0) [0|32767] "" XXX
BO_ 112 BUS1_SPEED: 8 XXX
SG_ BUS1_SPEED : 23|16@0+ (0.01886,0) [0|65535] "m/s" XXX
BO_ 592 ECM_1: 8 XXX
SG_ ACCELERATOR_PEDAL_POS : 31|8@0+ (1,0) [0|255] "" XXX
BO_ 832 BUS1_CRUISE_CONTROL: 8 XXX
SG_ CRUISE_CONTROL_ENABLED : 56|1@0+ (1,0) [0|1] "" XXX
SG_ CRUISE_CONTROL_ENABLED_IDLE_TRAFFIC : 57|1@0+ (1,0) [0|1] "" XXX
CM_ SG_ 592 ACCELERATOR_PEDAL_POS "Full gas ~200; Idle ~20";
@@ -0,0 +1,10 @@
BO_ 37 ECM_1: 8 XXX
SG_ ACCELERATOR_PEDAL_POS : 6|15@0+ (0.00390625,0) [0|32767] "%" XXX
BO_ 117 BUS1_SPEED: 8 XXX
SG_ BUS1_SPEED : 6|15@0+ (0.0044704,0) [0|32767] "" XXX
BO_ 841 BUS1_CRUISE_CONTROL: 8 XXX
SG_ CRUISE_CONTROL_SPA_ENABLED : 1|1@0+ (-1,1) [0|1] "" XXX
CM_ SG_ 117 BUS1_SPEED "m/s";
+188
View File
@@ -0,0 +1,188 @@
BO_ 21 DRIVER_INPUT: 8 XXX
SG_ BYTE_0 : 7|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_1 : 15|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_2 : 16|8@1+ (1,0) [0|255] "" XXX
SG_ STEERING_DRIVER_RATE_OF_CHANGE : 31|8@0- (1,0) [0|255] "" XXX
SG_ BYTE_4 : 39|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_5 : 40|8@1+ (1,0) [0|255] "" XXX
SG_ STEERING_DRIVER_INPUT : 55|8@0- (1,1) [0|255] "" XXX
SG_ BYTE_7 : 63|8@0+ (1,0) [0|255] "" XXX
BO_ 22 PSCM: 8 XXX
SG_ PSCM_ANGLE_SENSOR : 6|15@0- (0.05596,0) [-916|916] "º" XXX
SG_ BIT_0 : 7|1@0+ (1,0) [0|1] "" XXX
SG_ HANDS_ON_STEERING_WHEEL_A : 23|8@0+ (1,0) [0|255] "" XXX
SG_ HANDS_ON_STEERING_WHEEL_B : 31|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_4 : 39|8@0+ (1,0) [0|255] "" XXX
SG_ DRIVER_INPUT_DEVIATION : 47|8@0- (1,0) [-128|127] "" XXX
SG_ BYTE_6 : 55|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_7 : 63|8@0+ (1,0) [0|255] "" XXX
BO_ 23 PSCM_RELATED: 8 XXX
SG_ CHECKSUM_1 : 7|8@0+ (1,0) [0|255] "" XXX
SG_ LCA_ENABLED_ECHO : 11|4@0+ (1,0) [0|15] "" XXX
SG_ SIG1_BYTE_1_HI_NIBBLE : 15|4@0+ (1,0) [0|15] "" XXX
SG_ SIG1_REPLICA_BYTE_2_LO_NIBLE : 19|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_2 : 23|4@0+ (1,0) [0|15] "" XXX
SG_ CHECKSUM_2 : 31|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_4_5 : 39|16@0+ (1,0) [0|65535] "" XXX
SG_ BYTE_6 : 55|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_7 : 63|8@0+ (1,0) [0|255] "" XXX
BO_ 69 EGSM: 8 XXX
SG_ COUNTER_1 : 3|12@0+ (1,0) [0|4095] "" XXX
SG_ GEAR_LEVER_DBC_INCOHERENT : 5|2@0+ (1,0) [0|3] "" XXX
SG_ COUNTER_2 : 19|12@0+ (1,0) [0|4095] "" XXX
SG_ GEAR_1ST_RATCH : 20|1@0+ (1,0) [0|1] "" XXX
SG_ GEAR_RATCHED_UP : 21|1@0+ (1,0) [0|1] "" XXX
SG_ GEAR_RATCHED_DOWN : 22|1@0+ (1,0) [0|1] "" XXX
SG_ GEAR_PRESSED : 36|2@0+ (1,0) [0|3] "" XXX
SG_ PARKING_BRAKE_BUTTON : 38|1@0+ (1,0) [0|1] "" XXX
SG_ COUNTER_3 : 51|12@0+ (1,0) [0|4095] "" XXX
BO_ 85 SAS: 8 XXX
SG_ SAS_ANGLE_SENSOR : 6|15@0- (0.05596,0) [0|32767] "º" XXX
SG_ SAS_RATE_OF_CHANGE : 21|14@0- (1,0) [0|16383] "" XXX
SG_ SAS_CHECKSUM : 39|8@0+ (1,0) [0|4095] "" XXX
SG_ SAS_COUNTER : 44|4@1+ (1,0) [0|15] "" XXX
BO_ 87 LCA_3: 8 XXX
SG_ NEW_SIGNAL_3 : 0|1@0+ (1,0) [0|1] "" XXX
SG_ LCA_ACCEPT_COMMANDS_RELATED : 4|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_2 : 7|3@0+ (1,0) [0|7] "" XXX
SG_ NEW_SIGNAL_5 : 12|5@0+ (1,0) [0|31] "" XXX
SG_ LCA_ACCEPT_COMMANDS_INV : 13|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_4 : 15|2@0+ (1,0) [0|3] "" XXX
SG_ SPEED_A : 23|16@0+ (1,0) [0|65535] "" XXX
SG_ SPEED_B : 39|16@0+ (1,0) [0|65535] "" XXX
SG_ NEW_SIGNAL_8 : 51|4@0+ (1,0) [0|15] "" XXX
SG_ COUNTER_1 : 53|2@0+ (1,0) [0|3] "" XXX
SG_ NEW_SIGNAL_7 : 55|2@0+ (1,0) [0|3] "" XXX
SG_ NEW_SIGNAL_9 : 63|8@0+ (1,0) [0|255] "" XXX
BO_ 88 LCA: 8 XXX
SG_ LCA_STEER_LOOSELY : 2|11@0- (1,0) [0|2047] "" XXX
SG_ LCA_ENABLE_INV : 3|1@0+ (1,0) [0|1] "" XXX
SG_ LANE_KEEP_ACTIVE_INV : 7|2@0+ (1,0) [0|3] "" XXX
SG_ LCA_STEER_LOOSELY_INV : 18|11@0- (1,0) [0|2047] "" XXX
SG_ NEW_SIGNAL_7 : 21|3@0+ (1,0) [0|7] "" XXX
SG_ LCA_RATE_OF_CHANGE : 39|8@0+ (1,0) [0|255] "" XXX
SG_ LCA_STEER : 45|14@0- (1,0) [0|16383] "" XXX
SG_ NEW_SIGNAL_1 : 47|2@0+ (1,0) [0|3] "" XXX
SG_ NEW_SIGNAL_6 : 60|4@1+ (1,0) [0|15] "" XXX
BO_ 96 SPEED: 8 XXX
SG_ SPEED : 6|15@0+ (1,0) [0|32767] "" XXX
SG_ NEW_SIGNAL_1 : 7|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_2 : 23|12@0+ (1,0) [0|4095] "" XXX
SG_ NEW_SIGNAL_4 : 24|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_3 : 27|3@0+ (1,0) [0|7] "" XXX
SG_ NEW_SIGNAL_6 : 35|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_5 : 36|1@0+ (1,0) [0|1] "" XXX
BO_ 103 LCA_5: 8 XXX
SG_ WHEEL_SPEED_1 : 6|15@0+ (0.1,0) [0|255] "rpm" XXX
SG_ NEW_SIGNAL_4 : 7|1@0+ (1,0) [0|1] "" XXX
SG_ CHECKSUM : 23|8@0+ (1,0) [0|4095] "" XXX
SG_ NEW_SIGNAL_1 : 24|4@1+ (1,0) [0|15] "" XXX
SG_ COUNTER : 28|4@1+ (1,0) [0|15] "" XXX
SG_ WHEEL_SPEED_2 : 39|15@0+ (0.1,0) [0|32767] "rpm" XXX
SG_ NEW_SIGNAL_5 : 40|1@0+ (1,0) [0|1] "" XXX
SG_ LCA_5_STEER : 54|15@0- (0.05596,0) [0|32767] "deg" XXX
SG_ NEW_SIGNAL_2 : 55|1@0+ (1,0) [0|1] "" XXX
BO_ 104 SPEED_2: 8 XXX
SG_ WHEEL_SPEED_LEFT : 6|15@0+ (1,0) [0|32767] "" XXX
SG_ NEW_SIGNAL_1 : 7|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_2 : 23|12@0+ (1,0) [0|4095] "" XXX
SG_ NEW_SIGNAL_3 : 27|4@0+ (1,0) [0|15] "" XXX
SG_ WHEEL_SPEED_RIGHT : 39|15@0+ (1,0) [0|32767] "" XXX
SG_ COUNTER_1 : 51|12@0+ (1,0) [0|4095] "" XXX
SG_ NEW_SIGNAL_4 : 53|1@0+ (1,0) [0|1] "" XXX
SG_ COUNTER_2 : 54|1@0+ (1,0) [0|1] "" XXX
BO_ 105 LCA_2: 8 XXX
SG_ BYTE_0 : 7|8@0+ (1,0) [0|2047] "" XXX
SG_ COUNTER_1 : 11|4@0+ (1,0) [0|4095] "" XXX
SG_ PILOT_ASSIST_ENGAGED : 12|1@0+ (1,0) [0|1] "" XXX
SG_ ESC_ACTUATING : 13|1@0+ (1,0) [0|1] "" XXX
SG_ ESC_ELIGIBLE : 14|1@0+ (1,0) [0|1] "" XXX
SG_ BYTE_1_BITFIELD_0 : 15|1@0+ (1,0) [0|7] "" XXX
SG_ CHECKSUM_2 : 23|8@0+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_2 : 31|16@0+ (1,0) [0|65535] "" XXX
SG_ COUNTER_2 : 43|4@0+ (1,0) [0|4095] "" XXX
SG_ NEW_SIGNAL_3 : 45|2@0+ (1,0) [0|3] "" XXX
SG_ BRAKE_PEDAL_PRESSED_B : 46|1@0+ (1,0) [0|1] "" XXX
SG_ BRAKE_PEDAL_PRESSED_A : 47|1@0+ (-1,1) [0|1] "" XXX
SG_ CHECKSUM_1 : 55|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_7 : 63|8@0+ (1,0) [0|255] "" XXX
BO_ 128 GEAR_POSITION: 8 XXX
SG_ NEW_SIGNAL_3 : 3|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_2 : 4|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_1 : 7|3@0+ (1,0) [0|7] "" XXX
SG_ NEW_SIGNAL_5 : 11|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_4 : 15|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_17 : 17|2@0+ (1,0) [0|3] "" XXX
SG_ AEB_A : 18|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_18 : 19|1@0+ (1,0) [0|1] "" XXX
SG_ AEB_B : 20|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_6 : 23|3@0+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_7 : 24|8@1+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_8 : 39|8@0+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_13 : 43|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_10 : 46|3@0+ (1,0) [0|7] "" XXX
SG_ NEW_SIGNAL_9 : 47|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_14 : 51|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_12 : 54|3@0+ (1,0) [0|7] "" XXX
SG_ NEW_SIGNAL_11 : 55|1@0+ (1,0) [0|1] "" XXX
SG_ GEAR_POSITION : 58|3@0+ (1,0) [0|7] "" XXX
SG_ NEW_SIGNAL_16 : 62|4@0+ (1,0) [0|15] "" XXX
SG_ NEW_SIGNAL_15 : 63|1@0+ (1,0) [0|1] "" XXX
BO_ 144 LCA_4: 8 XXX
SG_ BYTE_0 : 7|8@0+ (1,0) [0|16383] "" XXX
SG_ LCA_ENABLE : 9|2@0+ (1,0) [0|3] "" XXX
SG_ BYTE_1_FLAGS : 11|2@0+ (1,0) [0|3] "" XXX
SG_ BYTE_1_NIBBLE_HI : 15|4@0+ (1,0) [0|63] "" XXX
SG_ BYTE_2_3 : 23|16@0- (1,0) [0|65535] "" XXX
SG_ YAW_RATE : 39|16@0- (1,0) [0|65535] "" XXX
SG_ BYTE_6 : 55|8@0+ (1,0) [0|255] "" XXX
SG_ BYTE_7_NIBBLE_LO : 59|4@0+ (1,0) [0|15] "" XXX
SG_ BYTE_7_NIBBLE_HI : 60|4@1+ (1,0) [0|15] "" XXX
BO_ 146 LCA_7: 8 XXX
SG_ NEW_SIGNAL_1 : 7|12@0+ (1,0) [0|4095] "" XXX
SG_ NEW_SIGNAL_2 : 11|4@0+ (1,0) [0|15] "" XXX
SG_ LCA_7_STEER : 23|15@0- (1,0) [0|32767] "" XXX
SG_ NEW_SIGNAL_3 : 24|2@0+ (1,0) [0|3] "" XXX
SG_ LCA_7_DELTA_STEER : 38|15@0- (1,0) [0|32767] "" XXX
SG_ NEW_SIGNAL_4 : 55|16@0+ (1,0) [0|65535] "" XXX
BO_ 151 LCA_6: 8 XXX
SG_ LCA_6_STEER : 7|16@0- (1,0) [0|65535] "" XXX
SG_ NEW_SIGNAL_1 : 23|14@0- (1,0) [0|16383] "" XXX
SG_ NEW_SIGNAL_3 : 25|2@0+ (1,0) [0|3] "" XXX
SG_ NEW_SIGNAL_2 : 39|12@0+ (1,0) [0|4095] "" XXX
SG_ NEW_SIGNAL_4 : 43|4@0+ (1,0) [0|15] "" XXX
SG_ LCA_6_STEER_2 : 55|15@0- (1,0) [0|32767] "" XXX
SG_ NEW_SIGNAL_5 : 56|1@0+ (1,0) [0|1] "" XXX
BO_ 336 NEW_MSG_150: 8 XXX
SG_ IGN_DRAFT : 5|2@0+ (1,0) [0|3] "" XXX
BO_ 1336 NEW_MSG_538: 8 XXX
SG_ SWM_MULTIMEDIA_ACTIVITY : 22|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_1 : 23|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_3 : 24|1@0+ (1,0) [0|1] "" XXX
SG_ NEW_SIGNAL_2 : 29|1@0+ (1,0) [0|1] "" XXX
CM_ BO_ 23 "Might be related to PSCM 0x16";
CM_ SG_ 87 LCA_ACCEPT_COMMANDS_RELATED "Goes to 15 when actively operating LCA";
CM_ SG_ 87 LCA_ACCEPT_COMMANDS_INV "Goes to 0 when PSCM allowed to receive LCA commands";
CM_ SG_ 88 LCA_ENABLE_INV "Master enable, active-low";
CM_ SG_ 103 WHEEL_SPEED_1 "Possible Front Left (FL)";
CM_ SG_ 103 WHEEL_SPEED_2 "Possible Front Right (FR)";
CM_ SG_ 105 ESC_ACTUATING "Goes to 1 in combination with ESC_ELIGIBLE when ESC is actively actuating";
CM_ SG_ 105 ESC_ELIGIBLE "Goes to 1 on e.g. speed bump, but doesn't trigger any intervention";
VAL_ 128 GEAR_POSITION 0 "Park" 1 "Reverse" 2 "Neutral" 3 "Drive" 4 "B-Mode";
+1
View File
@@ -11,3 +11,4 @@ class ALTERNATIVE_EXPERIENCE:
ALWAYS_ON_LATERAL = 32 ALWAYS_ON_LATERAL = 32
GM_REMAP_CANCEL_TO_DISTANCE = 64 GM_REMAP_CANCEL_TO_DISTANCE = 64
TOYOTA_AUTO_HOLD = 128
@@ -34,6 +34,7 @@
#define SAFETY_RIVIAN 33U #define SAFETY_RIVIAN 33U
#define SAFETY_VOLKSWAGEN_MEB 34U #define SAFETY_VOLKSWAGEN_MEB 34U
#define SAFETY_TESLA_PREAP 35U #define SAFETY_TESLA_PREAP 35U
#define SAFETY_VOLVO 36U
#define GET_BIT(msg, b) ((bool)!!(((msg)->data[((b) / 8U)] >> ((b) % 8U)) & 0x1U)) #define GET_BIT(msg, b) ((bool)!!(((msg)->data[((b) / 8U)] >> ((b) % 8U)) & 0x1U))
#define GET_FLAG(value, mask) (((value) & (mask)) == (mask)) #define GET_FLAG(value, mask) (((value) & (mask)) == (mask))
@@ -338,6 +339,7 @@ extern bool gm_remote_start_boots_comma;
#define ALT_EXP_ALWAYS_ON_LATERAL 32 #define ALT_EXP_ALWAYS_ON_LATERAL 32
#define ALT_EXP_GM_REMAP_CANCEL_TO_DISTANCE 64 #define ALT_EXP_GM_REMAP_CANCEL_TO_DISTANCE 64
#define ALT_EXP_TOYOTA_AUTO_HOLD 128
extern int alternative_experience; extern int alternative_experience;
@@ -380,4 +382,5 @@ extern const safety_hooks volkswagen_mqb_hooks;
extern const safety_hooks volkswagen_pq_hooks; extern const safety_hooks volkswagen_pq_hooks;
extern const safety_hooks rivian_hooks; extern const safety_hooks rivian_hooks;
extern const safety_hooks psa_hooks; extern const safety_hooks psa_hooks;
extern const safety_hooks volvo_hooks;
extern const safety_hooks tesla_preap_hooks; extern const safety_hooks tesla_preap_hooks;
+24 -70
View File
@@ -2,6 +2,12 @@
#include "opendbc/safety/declarations.h" #include "opendbc/safety/declarations.h"
// StarPilot's extended Ford curvature enforcement below is substantially adapted from
// BluePilot bp-7.0 panda work, principally Alan Polk's 8f8d6d15f0a590f42b78de964ffb0d0af7f5d63d
// See /CREDITS.md and /THIRD_PARTY_NOTICES.md. This comment does not attribute the surrounding
// upstream openpilot code.
// Safety-relevant CAN messages for Ford vehicles. // Safety-relevant CAN messages for Ford vehicles.
#define FORD_EngBrakeData 0x165U // RX from PCM, for driver brake pedal and cruise state #define FORD_EngBrakeData 0x165U // RX from PCM, for driver brake pedal and cruise state
#define FORD_EngVehicleSpThrottle 0x204U // RX from PCM, for driver throttle input #define FORD_EngVehicleSpThrottle 0x204U // RX from PCM, for driver throttle input
@@ -88,8 +94,8 @@ static bool ford_get_quality_flag_valid(const CANPacket_t *msg) {
static bool ford_lka_steering = false; static bool ford_lka_steering = false;
static bool ford_extended_lateral = false; static bool ford_extended_lateral = false;
static bool ford_angle_mode = false; static bool ford_longitudinal = false;
static int16_t ford_shadow_curvature = 0; static bool ford_cancel_resume_button = false;
// Curvature rate limits // Curvature rate limits
#define FORD_LIMITS(limit_lateral_acceleration) { \ #define FORD_LIMITS(limit_lateral_acceleration) { \
@@ -136,38 +142,6 @@ static const AngleSteeringLimits FORD_STEERING_LIMITS = FORD_LIMITS(false);
static const AngleSteeringLimits FORD_EXTENDED_STEERING_LIMITS = FORD_EXTENDED_LIMITS(false); static const AngleSteeringLimits FORD_EXTENDED_STEERING_LIMITS = FORD_EXTENDED_LIMITS(false);
static int ford_desired_path_angle_last = 0;
static bool ford_path_angle_checks(int desired_path_angle, bool steer_control_enabled) {
bool violation = false;
if (steer_control_enabled) {
float speed = ((float)vehicle_speed.min / VEHICLE_SPEED_FACTOR) - 1.0;
const struct lookup_t path_angle_rate = {
.x = {10., 15., 25.},
.y = {0.0561, 0.04335, 0.00918},
};
int max_delta = (safety_interpolate(path_angle_rate, speed) * 2000.0) + 1.0;
violation |= safety_max_limit_check(desired_path_angle,
ford_desired_path_angle_last + max_delta,
ford_desired_path_angle_last - max_delta);
} else {
violation |= desired_path_angle != 0;
}
ford_desired_path_angle_last = violation ? 0 : desired_path_angle;
return violation;
}
static bool ford_shadow_curvature_check(int desired_curvature, bool steer_control_enabled,
const AngleSteeringLimits limits) {
if (steer_control_enabled && limits.enforce_angle_error &&
((vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR) > limits.angle_error_min_speed)) {
int lowest_allowed = angle_meas.min - limits.max_angle_error - 1;
int highest_allowed = angle_meas.max + limits.max_angle_error + 1;
return safety_max_limit_check(desired_curvature, highest_allowed, lowest_allowed);
}
return false;
}
static void ford_rx_hook(const CANPacket_t *msg) { static void ford_rx_hook(const CANPacket_t *msg) {
if (msg->bus == FORD_MAIN_BUS) { if (msg->bus == FORD_MAIN_BUS) {
// Update in motion state from standstill signal // Update in motion state from standstill signal
@@ -219,6 +193,10 @@ static void ford_rx_hook(const CANPacket_t *msg) {
acc_main_on = (cruise_state == 3U) || cruise_engaged; acc_main_on = (cruise_state == 3U) || cruise_engaged;
} }
if (msg->addr == FORD_Steering_Data_FD1) {
ford_cancel_resume_button = ((msg->data[2] >> 5) & 1U) != 0U;
}
} }
} }
@@ -276,7 +254,8 @@ static bool ford_tx_hook(const CANPacket_t *msg) {
// if cancel button is pressed when cruise isn't engaged. // if cancel button is pressed when cruise isn't engaged.
bool violation = false; bool violation = false;
violation |= ((msg->data[1] >> 0) & 1U) && !cruise_engaged_prev; // Signal: CcAslButtnCnclPress (cancel) violation |= ((msg->data[1] >> 0) & 1U) && !cruise_engaged_prev; // Signal: CcAslButtnCnclPress (cancel)
violation |= ((msg->data[3] >> 1) & 1U) && !controls_allowed; // Signal: CcAsllButtnResPress (resume) bool stock_resume_from_driver = !ford_longitudinal && acc_main_on && ford_cancel_resume_button;
violation |= ((msg->data[3] >> 1) & 1U) && !(controls_allowed || stock_resume_from_driver); // Signal: CcAsllButtnResPress (resume)
if (violation) { if (violation) {
tx = false; tx = false;
@@ -293,10 +272,8 @@ static bool ford_tx_hook(const CANPacket_t *msg) {
} }
if (!ford_lka_steering) { if (!ford_lka_steering) {
ford_angle_mode = (msg->data[4] & 0x1U) != 0U;
ford_extended_lateral = (msg->data[4] & 0x2U) != 0U; ford_extended_lateral = (msg->data[4] & 0x2U) != 0U;
ford_shadow_curvature = (int16_t)((msg->data[5] << 8) | msg->data[6]); if ((msg->data[4] & 0x1U) != 0U) {
if (ford_angle_mode && !ford_extended_lateral) {
tx = false; tx = false;
} }
} }
@@ -320,20 +297,9 @@ static bool ford_tx_hook(const CANPacket_t *msg) {
if (ford_extended_lateral) { if (ford_extended_lateral) {
violation |= desired_path_offset != 0; violation |= desired_path_offset != 0;
violation |= (desired_curvature_rate < -4096) || (desired_curvature_rate > 4095); violation |= (desired_curvature_rate < -4096) || (desired_curvature_rate > 4095);
violation |= ford_path_angle_checks(desired_path_angle, steer_control_enabled); violation |= desired_path_angle != 0;
if (ford_angle_mode) { violation |= steer_angle_cmd_checks(desired_curvature, steer_control_enabled,
violation |= (desired_path_angle < -1000) || (desired_path_angle > 1047); FORD_EXTENDED_STEERING_LIMITS);
violation |= desired_curvature != 0;
violation |= steer_control_enabled && !(aol_allowed || controls_allowed);
int shadow_curvature_can = ROUND((float)ford_shadow_curvature * 0.05);
violation |= ford_shadow_curvature_check(shadow_curvature_can, steer_control_enabled,
FORD_EXTENDED_STEERING_LIMITS);
desired_angle_last = 0;
} else {
violation |= desired_path_angle != 0;
violation |= steer_angle_cmd_checks(desired_curvature, steer_control_enabled,
FORD_EXTENDED_STEERING_LIMITS);
}
if (!steer_control_enabled) { if (!steer_control_enabled) {
violation |= (desired_curvature != 0) || (desired_curvature_rate != 0); violation |= (desired_curvature != 0) || (desired_curvature_rate != 0);
} }
@@ -370,20 +336,9 @@ static bool ford_tx_hook(const CANPacket_t *msg) {
if (ford_extended_lateral) { if (ford_extended_lateral) {
violation |= desired_path_offset != 0; violation |= desired_path_offset != 0;
violation |= (desired_curvature_rate < -1024) || (desired_curvature_rate > 1023); violation |= (desired_curvature_rate < -1024) || (desired_curvature_rate > 1023);
violation |= ford_path_angle_checks(desired_path_angle, steer_control_enabled); violation |= desired_path_angle != 0;
if (ford_angle_mode) { violation |= steer_angle_cmd_checks(desired_curvature, steer_control_enabled,
violation |= (desired_path_angle < -1000) || (desired_path_angle > 1047); FORD_CANFD_EXTENDED_STEERING_LIMITS);
violation |= desired_curvature != 0;
violation |= steer_control_enabled && !(aol_allowed || controls_allowed);
int shadow_curvature_can = ROUND((float)ford_shadow_curvature * 0.05);
violation |= ford_shadow_curvature_check(shadow_curvature_can, steer_control_enabled,
FORD_CANFD_EXTENDED_STEERING_LIMITS);
desired_angle_last = 0;
} else {
violation |= desired_path_angle != 0;
violation |= steer_angle_cmd_checks(desired_curvature, steer_control_enabled,
FORD_CANFD_EXTENDED_STEERING_LIMITS);
}
if (!steer_control_enabled) { if (!steer_control_enabled) {
violation |= (desired_curvature != 0) || (desired_curvature_rate != 0); violation |= (desired_curvature != 0) || (desired_curvature_rate != 0);
} }
@@ -415,6 +370,7 @@ static safety_config ford_init(uint16_t param) {
{.msg = {{FORD_Yaw_Data_FD1, 0, 8, 100U, .max_counter = 255U}, { 0 }, { 0 }}}, {.msg = {{FORD_Yaw_Data_FD1, 0, 8, 100U, .max_counter = 255U}, { 0 }, { 0 }}},
// These messages have no counter or checksum // These messages have no counter or checksum
{.msg = {{FORD_EngBrakeData, 0, 8, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, {.msg = {{FORD_EngBrakeData, 0, 8, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
{.msg = {{FORD_Steering_Data_FD1, 0, 8, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
{.msg = {{FORD_EngVehicleSpThrottle, 0, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, {.msg = {{FORD_EngVehicleSpThrottle, 0, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
{.msg = {{FORD_DesiredTorqBrk, 0, 8, 50U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, {.msg = {{FORD_DesiredTorqBrk, 0, 8, 50U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
}; };
@@ -453,11 +409,9 @@ static safety_config ford_init(uint16_t param) {
const bool ford_canfd = GET_FLAG(param, FORD_PARAM_CANFD); const bool ford_canfd = GET_FLAG(param, FORD_PARAM_CANFD);
ford_lka_steering = GET_FLAG(param, FORD_PARAM_LKA_STEERING); ford_lka_steering = GET_FLAG(param, FORD_PARAM_LKA_STEERING);
ford_extended_lateral = false; ford_extended_lateral = false;
ford_angle_mode = false; ford_cancel_resume_button = false;
ford_shadow_curvature = 0;
ford_desired_path_angle_last = 0;
bool ford_longitudinal = false; ford_longitudinal = false;
#ifdef ALLOW_DEBUG #ifdef ALLOW_DEBUG
const uint16_t FORD_PARAM_LONGITUDINAL = 1; const uint16_t FORD_PARAM_LONGITUDINAL = 1;
+9
View File
@@ -559,6 +559,7 @@ static safety_config gm_init(uint16_t param) {
const uint16_t GM_PARAM_REMOTE_START_BOOTS_COMMA = 8192; const uint16_t GM_PARAM_REMOTE_START_BOOTS_COMMA = 8192;
const uint16_t GM_PARAM_PANDA_3D1_SCHED = 16384; const uint16_t GM_PARAM_PANDA_3D1_SCHED = 16384;
const uint16_t GM_PARAM_PANDA_PADDLE_SCHED = 32768U; const uint16_t GM_PARAM_PANDA_PADDLE_SCHED = 32768U;
const uint16_t GM_PARAM_VOLT_CC_GATEWAY = 16384U;
static const LongitudinalLimits GM_ASCM_LONG_LIMITS = { static const LongitudinalLimits GM_ASCM_LONG_LIMITS = {
.max_gas = 8191, .max_gas = 8191,
@@ -706,6 +707,11 @@ static safety_config gm_init(uint16_t param) {
{0xBD, 0, 7, .check_relay = false}, {0x1F5, 0, 8, .check_relay = false}, {0xBD, 0, 7, .check_relay = false}, {0x1F5, 0, 8, .check_relay = false},
{0x184, 2, 8, .check_relay = false}, {0x1E1, 2, 7, .check_relay = false}}; // camera bus {0x184, 2, 8, .check_relay = false}, {0x1E1, 2, 7, .check_relay = false}}; // camera bus
static const CanMsg GM_CC_LONG_ASCM_TX_MSGS[] = {{0x180, 0, 4, .check_relay = true}, {0x409, 0, 7, .check_relay = false},
{0x40A, 0, 7, .check_relay = false}, {0x370, 0, 6, .check_relay = false},
{0x1E1, 0, 7, .check_relay = false}, {0x3D1, 0, 8, .check_relay = false},
{0xBD, 0, 7, .check_relay = false}, {0x1F5, 0, 8, .check_relay = false}};
gm_hw = GET_FLAG(param, GM_PARAM_HW_CAM) ? GM_CAM : GM_ASCM; gm_hw = GET_FLAG(param, GM_PARAM_HW_CAM) ? GM_CAM : GM_ASCM;
gm_sdgm = GET_FLAG(param, GM_PARAM_HW_SDGM); gm_sdgm = GET_FLAG(param, GM_PARAM_HW_SDGM);
gm_ascm_int = GET_FLAG(param, GM_PARAM_HW_ASCM_INT); gm_ascm_int = GET_FLAG(param, GM_PARAM_HW_ASCM_INT);
@@ -714,6 +720,7 @@ static safety_config gm_init(uint16_t param) {
gm_cc_long = GET_FLAG(param, GM_PARAM_CC_LONG); gm_cc_long = GET_FLAG(param, GM_PARAM_CC_LONG);
gm_has_acc = !GET_FLAG(param, GM_PARAM_NO_ACC); gm_has_acc = !GET_FLAG(param, GM_PARAM_NO_ACC);
gm_pedal_long = GET_FLAG(param, GM_PARAM_PEDAL_LONG); gm_pedal_long = GET_FLAG(param, GM_PARAM_PEDAL_LONG);
const bool gm_volt_cc_gateway = GET_FLAG(param, GM_PARAM_VOLT_CC_GATEWAY) && gm_no_camera && !gm_pedal_long && !gm_has_acc;
enable_gas_interceptor = GET_FLAG(param, GM_PARAM_PEDAL_INTERCEPTOR); enable_gas_interceptor = GET_FLAG(param, GM_PARAM_PEDAL_INTERCEPTOR);
gm_force_ascm = GET_FLAG(param, GM_PARAM_HW_ASCM_LONG); gm_force_ascm = GET_FLAG(param, GM_PARAM_HW_ASCM_LONG);
gm_force_brake_c9 = GET_FLAG(param, GM_PARAM_FORCE_BRAKE_C9); gm_force_brake_c9 = GET_FLAG(param, GM_PARAM_FORCE_BRAKE_C9);
@@ -781,6 +788,8 @@ static safety_config gm_init(uint16_t param) {
} else { } else {
ret = BUILD_SAFETY_CFG(gm_rx_checks, GM_SDGM_TX_MSGS); ret = BUILD_SAFETY_CFG(gm_rx_checks, GM_SDGM_TX_MSGS);
} }
} else if (gm_cc_long && gm_volt_cc_gateway && (gm_hw == GM_ASCM) && !gm_sdgm) {
ret = BUILD_SAFETY_CFG(gm_rx_checks, GM_CC_LONG_ASCM_TX_MSGS);
} else if ((gm_hw == GM_CAM) || gm_sdgm) { } else if ((gm_hw == GM_CAM) || gm_sdgm) {
// FIXME: cppcheck thinks that gm_cam_long is always false. This is not true // FIXME: cppcheck thinks that gm_cam_long is always false. This is not true
// if ALLOW_DEBUG is defined but cppcheck is run without ALLOW_DEBUG // if ALLOW_DEBUG is defined but cppcheck is run without ALLOW_DEBUG
@@ -29,6 +29,7 @@ const LongitudinalLimits HYUNDAI_LONG_LIMITS = {
{0x340, 0, 8, .check_relay = true}, /* LKAS11 Bus 0 */ \ {0x340, 0, 8, .check_relay = true}, /* LKAS11 Bus 0 */ \
{0x4F1, scc_bus, 4, .check_relay = false}, /* CLU11 Bus 0 (radar-SCC) or 2 (camera-SCC) */ \ {0x4F1, scc_bus, 4, .check_relay = false}, /* CLU11 Bus 0 (radar-SCC) or 2 (camera-SCC) */ \
{0x485, 0, (can_refresh) ? 8 : 4, .check_relay = true}, /* LFAHDA_MFC Bus 0 */ \ {0x485, 0, (can_refresh) ? 8 : 4, .check_relay = true}, /* LFAHDA_MFC Bus 0 */ \
{0x53E, 0, 6, .check_relay = false}, /* LKAS12 replacement after camera advertises it */ \
#define HYUNDAI_LONG_COMMON_TX_MSGS(scc_bus, can_refresh) \ #define HYUNDAI_LONG_COMMON_TX_MSGS(scc_bus, can_refresh) \
HYUNDAI_COMMON_TX_MSGS(scc_bus, can_refresh) \ HYUNDAI_COMMON_TX_MSGS(scc_bus, can_refresh) \
@@ -140,6 +141,12 @@ static uint32_t hyundai_get_checksum(const CANPacket_t *msg) {
return chksum; return chksum;
} }
static void hyundai_rx_all_hook(const CANPacket_t *msg) {
if ((msg->addr == 0x53EU) && (msg->bus == 2U) && (GET_LEN(msg) == 6U)) {
hyundai_has_lkas12 = true;
}
}
static uint32_t hyundai_compute_checksum(const CANPacket_t *msg) { static uint32_t hyundai_compute_checksum(const CANPacket_t *msg) {
uint8_t chksum = 0; uint8_t chksum = 0;
if (msg->addr == 0x386U) { if (msg->addr == 0x386U) {
@@ -284,6 +291,10 @@ static bool hyundai_tx_hook(const CANPacket_t *msg) {
bool tx = true; bool tx = true;
if ((msg->addr == 0x53EU) && !hyundai_has_lkas12) {
tx = false;
}
// FCA11: Block any potential actuation. The blended HDA II layout uses // FCA11: Block any potential actuation. The blended HDA II layout uses
// different static fields, but its explicit AEB/FCA request bits stay zero. // different static fields, but its explicit AEB/FCA request bits stay zero.
if (msg->addr == 0x38DU) { if (msg->addr == 0x38DU) {
@@ -695,6 +706,7 @@ static safety_config hyundai_legacy_init(uint16_t param) {
const safety_hooks hyundai_hooks = { const safety_hooks hyundai_hooks = {
.init = hyundai_init, .init = hyundai_init,
.rx = hyundai_rx_hook, .rx = hyundai_rx_hook,
.rx_all = hyundai_rx_all_hook,
.tx = hyundai_tx_hook, .tx = hyundai_tx_hook,
.get_counter = hyundai_get_counter, .get_counter = hyundai_get_counter,
.get_checksum = hyundai_get_checksum, .get_checksum = hyundai_get_checksum,
@@ -704,6 +716,7 @@ const safety_hooks hyundai_hooks = {
const safety_hooks hyundai_legacy_hooks = { const safety_hooks hyundai_legacy_hooks = {
.init = hyundai_legacy_init, .init = hyundai_legacy_init,
.rx = hyundai_rx_hook, .rx = hyundai_rx_hook,
.rx_all = hyundai_rx_all_hook,
.tx = hyundai_tx_hook, .tx = hyundai_tx_hook,
.get_counter = hyundai_get_counter, .get_counter = hyundai_get_counter,
.get_checksum = hyundai_get_checksum, .get_checksum = hyundai_get_checksum,

Some files were not shown because too many files have changed in this diff Show More