Compare commits

...

436 Commits

Author SHA1 Message Date
firestar5683 52999cb7b2 Aldi 2026-09-30 11:44:01 -04:00
firestarsdog 1e6b221d53 push it 2026-09-30 02:23:29 -04:00
firestarsdog 11900616bf cache it 2026-09-30 01:36:24 -04:00
firestarsdog a60333e513 still purple 2026-09-30 00:46:35 -04:00
firestarsdog 9d87c4c8cb UI Pass 2026-09-29 19:25:10 -04:00
firestarsdog a45ecf73ab Unify Big UI speed limit card 2026-09-29 02:39:36 -04:00
firestarsdog 452dc42868 SLC 2026-09-29 00:58:17 -04:00
firestar5683 faf5b53321 Ferd Long 2026-09-28 14:57:33 -05:00
firestar5683 ac8028a21e panda 2026-09-28 13:52:16 -05:00
firestar5683 96ef704da7 Desires 2026-09-28 13:51:49 -05:00
firestarsdog 8d01d881cb polygons 2026-09-28 01:25:40 -04:00
firestarsdog 7d2f012387 model renderer optimization 2026-09-28 00:44:41 -04:00
firestarsdog da7af42e49 PathEdge cleanup 2026-09-27 23:13:35 -04:00
firestarsdog b1e7a5c46e Favorite menu cleanup 2026-09-27 22:45:20 -04:00
firestar5683 7b4643208e EV9 2026-09-27 16:14:22 -05:00
firestar5683 2dd44a6368 AOL/TESLA 2026-09-27 12:44:53 -05:00
firestar5683 19b2264e43 build 2026-09-27 11:02:12 -05:00
firestar5683 a19ed91f61 Tunes and Bugfixes 2026-09-27 11:01:27 -05:00
firestarsdog 04c2353096 You & I 2026-09-27 00:59:39 -04:00
firestar5683 6cae0cebe7 Accept AGNOS 19.8.2 2026-09-25 15:52:36 -05:00
firestar5683 ba901b5f55 panda 2026-09-25 12:25:25 -05:00
firestar5683 e6a60d6cba leaky faucet 2026-09-25 12:25:05 -05:00
firestar5683 d638e62811 build 2026-09-24 22:14:06 -05:00
firestar5683 18465ed4ef ray pedal adjust 2026-09-24 22:13:45 -05:00
firestar5683 f5672221a6 Agnos Update 2026-09-24 21:45:50 -05:00
firestar5683 99c5efa680 build 2026-09-24 17:39:10 -05:00
firestar5683 1648a100e3 alt path brake hold 2026-09-24 17:38:49 -05:00
firestar5683 d3a74f61e4 build 2026-09-24 17:13:20 -05:00
firestar5683 0c6ee69362 Long day 2026-09-24 17:12:32 -05:00
firestar5683 aaf1061111 Sportage Exception 2026-09-23 19:06:59 -05:00
firestar5683 79c61f479a Update starpilot_version.py 2026-09-22 23:49:59 -05:00
firestar5683 f0cac32351 Update starpilot_version.py 2026-09-22 23:49:29 -05:00
firestar5683 5bc666676a Tunes 2026-09-22 22:36:20 -05:00
firestar5683 2a528414ed Ray Pedal Path 2026-09-22 16:44:12 -05:00
firestar5683 ecda0c61d9 Corolla 2026-09-22 16:18:40 -05:00
firestar5683 399a40ca22 build 2026-09-22 16:02:08 -05:00
firestar5683 e47133be1a AOL No default 2026-09-22 15:55:47 -05:00
firestar5683 5ce64a49a8 Reduce first settings open cost and repeated vehicle catalog parsing 2026-09-22 14:38:35 -05:00
firestar5683 4c47955498 Keep navigation animation timing consistent under onroad load 2026-09-22 14:38:35 -05:00
firestar5683 fab2494f8e Batch small UI lane and road edge projections 2026-09-22 14:38:35 -05:00
firestar5683 96a75ba908 link commits 2026-09-22 13:45:25 -05:00
firestar5683 3a41fe663a preap 2026-09-22 13:27:02 -05:00
firestar5683 678af78347 Settle UI page transitions exactly and keep animation timing consistent 2026-09-22 09:26:12 -05:00
firestar5683 9d8a523471 Upload only the visible small UI camera region 2026-09-22 09:26:12 -05:00
firestar5683 b7cd0caff2 Keep UI scheduling below planning and stabilize speed limit pulses 2026-09-22 09:26:12 -05:00
firestar5683 cdc6b3bd68 Reduce onroad UI work and move parameter refresh off render thread 2026-09-22 09:26:12 -05:00
firestar5683 7f0c5673b4 pre-ap 2026-09-22 08:37:31 -05:00
firestar5683 2a13cc7fe2 aussie 2026-09-21 22:53:00 -05:00
firestar5683 7c6038fe28 build 2026-09-21 21:26:32 -05:00
firestar5683 5925aecd5b Flight Delayed 2026-09-21 21:24:05 -05:00
firestar5683 e6390c32e1 Audit every fleet route against current controller and safety hooks 2026-09-21 21:13:05 -05:00
firestar5683 1590a5cc2b Exercise AOL state machine across fleet platform fixtures 2026-09-21 21:08:27 -05:00
firestar5683 78d412d1d0 Add strict offline fleet TX safety audit core 2026-09-21 15:26:25 -05:00
firestar5683 d34a756929 test: count AOL authorization before replay TX checks 2026-09-21 15:16:17 -05:00
whoisdomi f15a1974d5 Ioniq 6 turn blips 2026-09-19 21:01:03 -05:00
whoisdomi 08139a021a Car Date/Time fallback when gps/wifi not available 2026-09-19 21:01:02 -05:00
whoisdomi fbe982f47b Ioniq 6 Date/Time DBC Signal
Added date/time signal from Ioniq 6 can
2026-09-19 21:01:01 -05:00
firestarsdog 5e6e978438 Gen2 Bolt HSA Fix? Maybe?
0x315 : Mode 1 when not engaged, not Mode 9
2026-09-19 18:25:59 -04:00
firestarsdog b990a776b2 Curve radial menu corner gradient 2026-09-18 22:26:26 -04:00
firestarsdog 88cbf88756 TV Set 2026-09-18 22:17:55 -04:00
whoisdomi 373c411baa Model Stuff 2026-09-18 19:47:39 -05:00
Prabhaav Pillai 990e68804c fix recording and revert nav tab 2026-09-18 20:04:20 -04:00
firestarsdog b295a57281 mici ux 2026-09-18 18:12:53 -04:00
firestarsdog 673ca37396 Small UI UX 2026-09-18 03:42:24 -04:00
firestar5683 44beb5b778 EV6 2026-09-17 08:33:35 -05:00
firestar5683 00ac287223 EV6 2026-09-17 08:33:10 -05:00
firestar5683 09b53ccf9f hackathon 2026-09-16 13:12:42 -05:00
firestar5683 0fee545400 build 2026-09-16 12:20:32 -05:00
firestar5683 14370fe9cf dopa 2026-09-16 12:19:59 -05:00
Prabhaav Pillai 7316871e62 firefox friendly :) 2026-09-16 00:34:47 -04:00
firestar5683 239121b0e1 build 2026-09-15 20:25:19 -05:00
firestar5683 26de11932a In&Out 2026-09-15 20:23:01 -05:00
firestar5683 1f8b955a0f niro 2026-09-15 16:10:15 -05:00
firestar5683 b41b0ab95f build 2026-09-15 15:00:36 -05:00
firestar5683 a8d1f2318e whoopity scoop 2026-09-15 15:00:02 -05:00
firestar5683 dac7140410 fingerprint 2026-09-15 14:01:39 -05:00
firestar5683 0cf86c5c3b build 2026-09-15 13:07:18 -05:00
firestar5683 d3ec77b0b4 lunch time 2026-09-15 13:05:50 -05:00
firestar5683 814af739d0 Keep screen settings in Galaxy
Leave the existing UI-state consumer in place so Galaxy values still control the display, but remove the native settings pages and tests. Hide the new brightness and wake-choice controls behind Galaxy Developer Mode while preserving current defaults.

Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
2026-09-15 12:53:17 -05:00
AngusBell97 68b75fc51e Reuse the UI carState reader for standby button wake 2026-09-15 12:27:02 -05:00
AngusBell97 7ff3682aba Verify native screen timeout and toggle saves 2026-09-15 12:27:02 -05:00
AngusBell97 91052ea0e1 Make standby button wake optional and retain ignition wake 2026-09-15 12:27:02 -05:00
AngusBell97 20f38f3d8e Use general standby wakes and six event selections 2026-09-15 12:27:02 -05:00
AngusBell97 cdaf33529a Add configurable screen brightness and standby wakes 2026-09-15 12:26:49 -05:00
firestar5683 6e37c0917c build 2026-09-15 12:17:10 -05:00
firestar5683 1db1ff9b91 waffles 2026-09-15 12:15:34 -05:00
Prabhaav Pillai 3ba36ed4fc GalaxySelect refactor, update tests, rename components to be more accurate, Discord Support button 2026-09-15 01:06:17 -04:00
Prabhaav Pillai cbe6f39030 Refactor navigation components and enhance slider functionality with fine scrubbing feature 2026-09-15 00:16:49 -04:00
firestarsdog 6aa9abf046 Purple RainX 2026-09-14 21:10:16 -04:00
firestarsdog 9332886242 SLC: Man with a slow hand 2026-09-14 17:45:36 -04:00
firestar5683 c3e4ec630f fix 2026-09-14 15:41:25 -05:00
firestarsdog 65c8581db3 SLC: Fix ghost confirmation/simplify 2026-09-14 16:07:33 -04:00
firestar5683 9136e13fdf net 2026-09-14 14:58:55 -05:00
firestar5683 9e5a3e288b booty 2026-09-14 14:32:33 -05:00
firestar5683 2878d13c3d nav 2026-09-14 14:11:41 -05:00
firestar5683 16ec6bc5c9 backpack 2026-09-14 13:38:29 -05:00
firestar5683 1d6d0cb5ba yas 2026-09-14 13:20:38 -05:00
firestar5683 200ac08499 astrobot 2026-09-14 13:13:33 -05:00
firestar5683 71649a2ac1 multi comma 2026-09-14 13:03:17 -05:00
firestar5683 fc852fed06 nav 2026-09-14 12:21:59 -05:00
firestarsdog 7222b29a88 SLC : Raise Max with Higher Confirmations on 2026-09-14 13:11:43 -04:00
firestar5683 2d3f483f3a galaxy 2026-09-14 12:11:26 -05:00
firestar5683 b4dbc18a14 mario 2026-09-14 11:39:02 -05:00
Zikeji 8d73b7b679 Harden tethering NAT activation 2026-09-14 11:09:15 -05:00
Zikeji b640bbc20e Ensure WAN NAT for Wi-Fi tethering hotspot
AGNOS kernels (4.9, CONFIG_NF_TABLES not set — verified in upstream AGNOS
boot image) cannot run NetworkManager's shared-mode firewall rules, so
tethered clients get DHCP but no WAN access.

Idempotently apply masquerade/forward rules via iptables-legacy on every
hotspot activation path (UI toggle, autoconnect fallback, boot restore),
replacing a manually re-installed systemd service after each AGNOS update.
2026-09-14 11:07:05 -05:00
AngusBell97 9d0ab7a849 Align personality registry test with Dom defaults 2026-09-14 10:52:38 -05:00
AngusBell97 6f3d863ecd Align following presets with Dom defaults 2026-09-14 10:48:32 -05:00
AngusBell97 ab6c97fef8 Remember custom personality graphs and correct default resets 2026-09-14 10:48:32 -05:00
firestar5683 a51205e302 software 2026-09-14 10:45:36 -05:00
firestar5683 64f8b75551 ferd 2026-09-14 10:10:26 -05:00
firestar5683 497b906121 G70 2026-09-14 10:08:00 -05:00
whoisdomi 04ba07e706 C3/C3X Aggressive Fan Curve + Toggle
C3 and C3X Cooling Curve. 10 - 16 C cooler than stock curve.
2026-09-14 09:48:15 -05:00
firestar5683 ad1c970cdd fix maps 2026-09-14 09:45:49 -05:00
firestarsdog 7a7b391656 stop lying on my chestnut 2026-09-13 22:04:29 -05:00
firestar5683 688631b6bd cyanara 2026-09-13 21:57:33 -05:00
AngusBell97 9677a3bd78 Harden Galaxy version picker 2026-09-13 21:40:03 -05:00
AngusBell97 4d585dbcbc Preserve standard OS updates and rollback in version picker 2026-09-13 21:32:41 -05:00
AngusBell97 f88ef758ca Reduce history requests and protect hidden local edits 2026-09-13 21:32:41 -05:00
AngusBell97 c4f46c51b2 Add branch and historical version selection to Galaxy 2026-09-13 21:32:40 -05:00
firestar5683 6b8bb279d4 build 2026-09-13 17:58:26 -05:00
AngusBell97 c51b96879a Keep Tesla steering diagnostics in custom cereal
Move the fork-specific diagnostics out of the stock CarOutput schema and into StarPilot reserved messaging. Preserve the legacy saturation fallback when custom diagnostics are unavailable or stale.

Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
2026-09-13 17:49:18 -05:00
firestar5683 524cffa19c suburban cam 2026-09-13 17:23:07 -05:00
firestar5683 549c12cd1b build 2026-09-13 16:53:12 -05:00
AngusBell97 f6664f5466 Fix Tesla cooperative steering saturation warnings
(cherry picked from commit 9136f1cb62)
2026-09-13 16:46:46 -05:00
firestar5683 151b07462c new support 2026-09-13 16:24:10 -05:00
firestar5683 3506c2561b build 2026-09-13 15:40:54 -05:00
firestar5683 5bead81598 hercules 2026-09-13 15:34:21 -05:00
firestar5683 01ae511879 Clean up Galaxy model management integration
Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
2026-09-13 15:30:44 -05:00
AngusBell97 8ccaacb919 Improve model selection and downloads in Galaxy
(cherry picked from commit da099820c7)
2026-09-13 15:23:38 -05:00
AngusBell97 38c98cf9f7 Include supported settings in Galaxy diagnostic reports
(cherry picked from commit 0466650e4a)
2026-09-13 15:23:38 -05:00
Danny 737c8499eb Remove Short Route ID
(cherry picked from commit b9f23f8ce0)
2026-09-13 15:23:38 -05:00
Danny 12251b663a Show Recording Dates and Search
(cherry picked from commit 3b3d2649ae)
2026-09-13 15:23:38 -05:00
firestar5683 ed008e2bf3 buildy 2026-09-13 14:24:36 -05:00
firestar5683 024465323b ribbit 2026-09-13 14:23:08 -05:00
firestar5683 32381eb182 Reapply "joplin"
This reverts commit e517a83541.
2026-09-13 14:04:45 -05:00
firestar5683 3f4d1e4fcc Reapply "g70"
This reverts commit eb088bccfa.
2026-09-13 14:04:42 -05:00
firestarsdog 50a8d1abdb Prevent rejected SLC limit auto-application 2026-09-13 00:07:11 -04:00
firestarsdog 185c501809 Refactorious III 2026-09-12 00:16:28 -04:00
firestarsdog eb088bccfa Revert "g70"
This reverts commit c7b9a77782.
2026-09-11 22:30:34 -04:00
firestarsdog d0585de42d Revert "build"
This reverts commit 07dc300d5b.
2026-09-11 22:30:32 -04:00
firestarsdog e517a83541 Revert "joplin"
This reverts commit 59d3c4dd66.
2026-09-11 22:30:29 -04:00
firestar5683 c7b9a77782 g70 2026-09-11 18:14:08 -05:00
firestar5683 07dc300d5b build 2026-09-11 16:35:33 -05:00
firestar5683 59d3c4dd66 joplin 2026-09-11 16:34:46 -05:00
firestar5683 d6712e2a10 Roadhouse 2026-09-11 10:01:15 -05:00
firestar5683 be263a6fa1 ravbob 2026-09-10 22:26:20 -05:00
firestar5683 f1cb143bd7 neck is red 2026-09-10 22:22:56 -05:00
firestar5683 ecbd6362f7 kona 2026-09-10 21:28:03 -05:00
firestar5683 fbfadc65da move 2026-09-10 21:00:32 -05:00
firestar5683 334f32f5d8 Cabo 2026-09-10 20:49:06 -05:00
Prabhaav Pillai 52c61da75d tailscale anyone? 2026-09-10 19:52:14 -04:00
firestar5683 08a11c445c the bell 2026-09-10 17:39:45 -05:00
firestar5683 14b15022eb Glycogen Supercompensation 2026-09-10 17:12:08 -05:00
RiskyBiscuit-arc 340d225039 Honda: clean Alpha Long arbitration tests
Keep the imported Bosch arbitration focused on executable behavior and concise tests.
2026-09-10 16:55:15 -05:00
AngusBell97 a3d8c8948e Galaxy: harden system monitor and optional chime
Keep the model-ready sound opt-in and return a controlled error when memory totals are unavailable.
2026-09-10 16:55:11 -05:00
AngusBell97 1b1989f794 Galaxy: tighten shared action picker integration
Clean up imported picker commentary and update Galaxy tests for the unified favourites/controller catalogue.
2026-09-10 16:55:06 -05:00
AngusBell97 d8a4be7e98 Keep the new action picker in New Galaxy
(cherry picked from commit 396fee8904)
2026-09-10 16:43:52 -05:00
AngusBell97 b942e08f58 Unify Bluetooth and favourites with a searchable action picker
(cherry picked from commit e82b1f0f7a)
2026-09-10 16:43:48 -05:00
AngusBell97 8eb46987ff Add a live System Monitor to Galaxy
(cherry picked from commit aa042de324)
2026-09-10 16:43:25 -05:00
AngusBell97 46596218ba Add an optional GPU-model-ready chime
(cherry picked from commit 0a212734c1)
2026-09-10 16:41:45 -05:00
RiskyBiscuit-arc 202ea33690 fix(honda): arbitrate Alpha Long braking from compensated force
(cherry picked from commit 3a811e9622)
2026-09-10 16:41:28 -05:00
raadiphone0-sketch 23821bad24 Hyundai: add Korean Sonata DN8 fingerprints
Co-authored-by: raadiphone0-sketch <raadiphone0@gmail.com>
2026-09-10 16:30:51 -05:00
pharmacomaniac 49940935a9 Manager: cover forced road-state transitions
Co-authored-by: pharmacomaniac <blittle65@gmail.com>
2026-09-10 16:30:43 -05:00
pharmacomaniac 14da2ebbd9 Reset car initialization flags with ForceOnroad
Prevent stale flags from letting Panda apply the car's safety mode before card finishes initializing.

(cherry picked from commit f2987df137)
2026-09-10 16:26:49 -05:00
pharmacomaniac 29dbf8d084 Galaxy: fix turning Force Offroad back off
Only require Park when enabling.

(cherry picked from commit 2a01f17b46)
2026-09-10 16:26:49 -05:00
Danny b67bc26763 Bugs Be Ghosty
(cherry picked from commit e6df2aadb8)
2026-09-10 16:26:48 -05:00
Danny 943c739521 Leaky Frame Mems
(cherry picked from commit 2e8a158419)
2026-09-10 16:26:48 -05:00
Danny 85703712df Share your things and play nice
(cherry picked from commit 5a03ba4600)
2026-09-10 16:26:48 -05:00
Danny 88b3b3eeea Cache the Colorssss
(cherry picked from commit 29c408d5f9)
2026-09-10 16:26:48 -05:00
firestar5683 ab9011c825 range 2026-09-10 15:22:36 -05:00
firestar5683 91535cc086 Sports: It's in the game 2026-09-10 15:08:39 -05:00
firestar5683 b135a43d97 meb 2026-09-10 14:37:06 -05:00
firestar5683 0b8a9a503b bouild 2026-09-10 14:00:55 -05:00
firestar5683 bf00f88be4 POWAAA 2026-09-10 13:58:45 -05:00
AngusBell97 58d2b6838d Tesla: clarify Galaxy-only validation
Keep the wake-on-CAN test documentation aligned with the Galaxy-only integration.

Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
2026-09-10 13:54:48 -05:00
AngusBell97 9832c3de4f Panda: rebuild firmware for Tesla wake
Rebuild the tracked Panda application images from the credited Tesla wake implementation.

Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
2026-09-10 13:51:28 -05:00
AngusBell97 24b8789c79 Tesla: add Galaxy-controlled CAN wake
Bring in PR #134 for opt-in Tesla wake-on-CAN support. Keep the setting in Galaxy, omit native UI changes, and reject incompatible remote-start firmware selections.

Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
2026-09-10 13:49:04 -05:00
firestar5683 01dd14bfae timeout 2026-09-10 11:53:43 -05:00
firestar5683 bb04e93527 build 2026-09-10 11:03:53 -05:00
firestar5683 244aa67371 Blueberry Pancakes 2026-09-10 11:02:23 -05:00
firestarsdog dd607aa07d temp force dev until simple mode 2026-09-10 03:12:11 -04:00
firestarsdog 79d73d6ed4 big ui pulseglide fix 2026-09-10 00:48:31 -04:00
firestarsdog c48247ddb0 free the clusters 2026-09-10 00:07:15 -04: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
firestar5683 227b5cf82a gpu 2026-08-27 12:25:23 -05:00
firestar5683 adb5058cb8 build 2026-08-27 11:59:02 -05:00
firestar5683 3c655e79a9 the morrow 2026-08-27 11:57:40 -05:00
Prabhaav Pillai 032f085500 ui: add PiP side-camera orientation toggle 2026-08-27 11:36:58 -05:00
whoisdomi 5882bd23ba Force Stop: Fast Approach
Force Stop is now better at faster approach speeds. It has more authority on braking in general.

Force Stop Distance Offset: still the lever that moves the ending point sooner or later.

Radar bug fixed
2026-08-27 08:22:54 -05:00
firestarsdog 78aba03b4f Revert "tell me wtf is wrong"
This reverts commit 2c002755c7.
2026-08-27 03:19:39 -04:00
firestarsdog 2c002755c7 tell me wtf is wrong 2026-08-26 23:20:52 -04: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
firestar5683 81d20d304f gpu 2026-08-26 20:50:16 -05:00
firestar5683 d683da24ea lines 2026-08-26 16:28:11 -05:00
firestar5683 4509316877 model 2026-08-26 16:17:04 -05: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
whoisdomi 6a17743513 Force Stop Tweak
Ratched only down below 40m to prevent model jitter from overshooting.

Original PR author: whoisdomi <dcantu83@gmail.com>.
2026-08-26 10:24:29 -05:00
whoisdomi e20ea5d0d1 Remove Takeoff Twitch
Limit the curvature guard to a short pull-away window and reject invalid model plans.

Original PR author: whoisdomi <dcantu83@gmail.com>.
2026-08-26 10:24:25 -05:00
firestar5683 f20b256473 UI 2026-08-26 08:28:28 -05:00
whoisdomi a88da4071f Force Stop hold 2 sec 2026-08-26 08:12:05 -05:00
Prabhaav Pillai 9437efe93b Fix C4 traffic border layering on top 2026-08-26 02:29:36 -04:00
firestar5683 b7393ad04a gniht 2026-08-25 21:42:54 -05:00
firestar5683 1da3008676 GPS feed 2026-08-25 21:05:56 -05:00
firestar5683 7446b96939 honda: enable Bosch-A radar for CR-V 5G
Promote HONDA_CRV_5G after replaying a StarPilot route with active Bosch-A object frames across multiple segments. Add explicit CR-V DBC wiring and availability coverage while leaving every other unverified Bosch-A platform gated off.

Co-authored-by: JamesL787 <jamezelle1@gmail.com>
2026-08-25 20:07:57 -05:00
firestar5683 145bf39587 honda: keep Bosch-A radard timing Honda-scoped
Keep the Bosch-A lead probability filter cadence change limited to the verified Honda Bosch-A path. Preserve the existing radar timestep for every other platform and add regression coverage for the gate and both timing paths.

Co-authored-by: JamesL787 <jamezelle1@gmail.com>
2026-08-25 19:20:38 -05:00
JamesL787 31f00eaff7 honda: port Bosch-A-aware radard lead tracking from tested branch
is_bosch_a_radar_car() and its downstream handling in radard.py never
landed alongside the radar decoder (interface.py/values.py/radar_interface.py)
-- without it, the newly-decoded Bosch-A radar points feed into radard's
generic path, which assumes the wrong Kalman filter timestep and is missing
a real staleness/freshness gate this radar's actual cadence needs.

Ports (unmodified in behavior, only renamed to match this repo's
HONDA_BOSCH_A / HONDA_BOSCH_A_RADAR_VERIFIED naming instead of "civic_bosch"):
- Kalman filter dt selected from the radar's real ~15Hz cadence
  (HONDA_BOSCH_A_RADAR_TS), not the ~20Hz model-cycle default
- measurement_update gating so a duplicate liveTracks payload (radar runs
  slower than the model loop) isn't absorbed twice by the KF
- a "preferred track" continuity path: a previously-selected lead stays
  selected through ordinary model-probability fluctuations as long as it's
  still coherent, instead of re-arbitrating from scratch every cycle
- staleness detection with two independent triggers (a better-scoring
  challenger track sustained over several cycles, or gross absolute-range
  disagreement with the model sustained over several cycles) that drops a
  stale preferred track rather than holding it indefinitely
- honda_bosch_a_low_speed_radar_lead_sane(), requiring a few real sweeps
  before a radar-only low-speed takeover

Tested on the branch this was developed on (109/109 relevant tests pass);
this port is a mechanical, unmodified transplant onto this repo's current
radard.py -- worth a run through this repo's own test suite before merge.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 8d23733946)
2026-08-25 19:16:28 -05:00
firestar5683 b381cc0c4a Closer Follow 2026-08-25 19:08:46 -05:00
firestar5683 903631ee53 build 2026-08-25 18:10:34 -05:00
firestar5683 f8d47139a3 the day of the push 2026-08-25 17:57:36 -05:00
firestar5683 89753b6194 honda: enable only contributor-verified Bosch-A radar
Trust the contributor's plain Bosch Civic road validation while keeping every other Bosch-A variant unavailable until it has an accepted capture. Keep the internal kill switch default-on only behind the verified platform gate.

Co-authored-by: JamesL787 <jamezelle1@gmail.com>
2026-08-25 17:47:00 -05:00
firestar5683 0b9298e363 honda: gate Bosch-A radar by verified platform
Keep the decoder available for parser testing, but require an exact platform allowlist entry and an internal opt-in before radar can affect production lead tracking. Hide the control from user settings until real captures validate each platform.

Co-authored-by: JamesL787 <jamezelle1@gmail.com>
2026-08-25 17:44:43 -05:00
JamesL787 fcf4d75c76 honda: port Bosch-A 16-slot radar decoder, enable for full HONDA_BOSCH_A harness
Ports the 16-slot Bosch-A object-bank radar decoder (16 slots x 4 main frames
+ 1 aux frame each) and enables it behind a HondaBoschARadar toggle (default
on) for the full HONDA_BOSCH_A harness category: N-Box, Accord, Civic (incl.
Diesel), CR-V, CR-V Hybrid, Acura RDX, Insight, and Honda e -- 10 platforms
total, derived as HONDA_BOSCH - HONDA_BOSCH_RADARLESS - HONDA_BOSCH_CANFD -
HONDA_BOSCH_ALT_RADAR.

RX-only: never transmits on the radar bus, so it takes no CAN authority.
Factory AEB/CMBS/FCW stay live as long as stock longitudinal remains in
control; enabling openpilot longitudinal (alpha) disables them regardless
of this toggle, via the existing UDS CommunicationControl call in
CarInterface.init() -- documented consistently now via a new HondaCarDocs
Footnote.EXP_LONG and matching comments on all 10 platform configs.

86 new tests for the decoder (gate-open/closed coverage for every affected
platform) plus the existing 19 in test_honda.py all pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit b13dd7d75e)
2026-08-25 17:38:42 -05:00
inauner 4db69dae3b webrtc: fix livestream request compatibility
Fix StreamRequestBody initialization for legacy and multi-camera clients, ignore unknown request fields, and use the supported nonblocking Params API for keyframe requests. Add regression coverage for request normalization.

Based on PR #101 by inauner: https://github.com/firestar5683/StarPilot/pull/101
2026-08-25 17:05:17 -05:00
j3dok3do c82cc2ed9f build: refresh Panda artifacts for Accord control integration
Refresh tracked Panda version metadata for the scoped Accord 11G CAN-FD integration. Original Accord contribution by j3dok3do.
2026-08-25 16:51:37 -05:00
firestar5683 f30a8ea20e controls: gate Kona non-SCC AOL on steering release
Avoid EPS torque faults when entering always-on lateral while the driver is steering. The behavior is scoped to HYUNDAI_KONA_NON_SCC and leaves other vehicles on the shared lateral-active path.

Original contribution by firestar5683.
2026-08-25 16:47:48 -05:00
j3dok3do 1a3b243233 Honda Accord 11G: add scoped CAN-FD control integration
Port the Accord 11G CAN-FD radar handover, vehicle-specific actuator tuning, and Honda safety permissions from PR #93 while keeping StarPilot\x27s shared planner and lead policy. Scope the replacement radar messages and B/regen gear behavior to the Accord platform, preserve generic Honda CAN-FD behavior, and add regression coverage.

Source: https://github.com/firestar5683/StarPilot/pull/93
2026-08-25 16:45:58 -05:00
firestar5683 68cef2d360 stop the kachow 2026-08-25 15:49:03 -05:00
firestar5683 60aa4aad49 Add VOACC takeoff safety regression coverage
Co-authored-by: Robin Dittrich <r.dittrich@niverplast.com>
2026-08-25 15:37:25 -05:00
Robin Dittrich 5c4f6ff353 Remove vision-only low speed acceleration cap
(cherry picked from commit bf23312b65)
2026-08-25 15:31:59 -05:00
firestar5683 c1857824b7 bribery build 2026-08-25 15:27:41 -05:00
firestar5683 468ba323fc bribery 2026-08-25 15:26:31 -05:00
dellop dfd9b54bf5 Fix 2022-23 Bolt automatic fingerprint recovery
(cherry picked from commit a16491aa20)
2026-08-25 15:21:38 -05:00
inauner b00888c858 galaxy: enable threaded mode in Flask to prevent request blocking
(cherry picked from commit 02075dfbed)
2026-08-25 15:20:15 -05:00
dirwin31 0a89eaf255 Oops a Comma and RF Timeout
(cherry picked from commit 1f0b1e2cbc)
2026-08-25 15:20:13 -05:00
dirwin31 931877feb9 Update dashboard cache timestamp to use monotonic time
(cherry picked from commit 960d676fc8)
2026-08-25 15:20:12 -05:00
firestar5683 71f35601d7 blue 2026-08-25 10:51:35 -05:00
firestar5683 9eb5768d2a build 2026-08-25 10:02:22 -05:00
firestar5683 2ef545b987 gmornin 2026-08-25 10:00:34 -05:00
firestar5683 83cec26dea Add AGNOS download mirrors 2026-08-25 09:26:12 -05:00
Prabhaav Pillai 0122e4069b Enhance VASM inference with dual-threshold hysteresis and confidence hold-off
Cherry-picked Feature Request 6578897 from 2f9c2d95b6.

Preserve documented defaults when VASM parameters are missing during upgrades.

Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
2026-08-25 08:43:13 -05:00
firestar5683 f857a55f59 Reapply build 2026-08-24 22:11:36 -05:00
firestar5683 05140149fb Reapply Agnos 19.6.10 2026-08-24 22:11:35 -05:00
firestarsdog 6a64493512 Revert "Guards, arrest this man"
This reverts commit 9c1d39b859.
2026-08-24 19:25:05 -04:00
firestarsdog 9c1d39b859 Guards, arrest this man 2026-08-24 19:23:25 -04:00
firestarsdog ffab4a8321 A pitstop! 2026-08-24 19:11:30 -04:00
firestarsdog 71c1d79807 Radial corner betterness 2026-08-24 10:27:22 -04:00
firestar5683 36d0c074f1 Revert "agnosis"
This reverts commit 2db8d95905.
2026-08-23 14:51:08 -05:00
firestar5683 6dccb85b96 build 2026-08-23 14:38:57 -05:00
firestar5683 d11ecaf457 End The Suffering 2026-08-23 13:12:07 -05:00
firestar5683 b302637e4d build 2026-08-23 12:57:27 -05:00
firestar5683 e28f72c661 AHHHH 2026-08-23 12:54:45 -05:00
firestar5683 2db8d95905 agnosis 2026-08-23 11:29:46 -05:00
firestar5683 db5922db43 fix 2026-08-23 11:29:18 -05:00
firestar5683 96f5819c6a build 2026-08-23 10:41:01 -05:00
firestar5683 cfd221b89c fine day, Sunday. 2026-08-23 10:40:20 -05:00
firestar5683 013f8579d7 squeak squeak 2026-08-23 09:19:58 -05:00
firestarsdog 2b53b59bee Bready 2026-08-23 05:12:19 -04:00
firestarsdog 33f6d69a32 Sound panel blinky 2026-08-23 04:48:35 -04:00
firestarsdog 58e919c264 Breadcrumb 2026-08-23 04:28:04 -04:00
firestarsdog 17e5542b1b Model Manager 2026-08-23 03:59:15 -04:00
firestarsdog e1458d4723 Consistency 2026-08-23 03:38:38 -04:00
firestarsdog 06f9e43e4e Consistency 2026-08-23 02:30:57 -04:00
firestarsdog f8e03a9fc5 Tweaker of the Toggletiles 2026-08-23 02:15:00 -04:00
firestarsdog 7c6741a93e cleanup 2026-08-22 04:51:33 -04:00
firestar5683 4b3372d006 BUMP 2026-08-21 20:43:16 -05:00
firestar5683 9a9e7cd1e9 why no s-t-z 2026-08-21 20:42:46 -05:00
firestar5683 1d9fdc25ca toy 2026-08-21 19:16:28 -05:00
firestar5683 05db925e93 sentry 2026-08-21 18:44:11 -05:00
firestar5683 2bd080821b Zoom default boot logo 2026-08-21 18:37:21 -05:00
whoisdomi 651726bbb5 Joystick Mode Refresh
OP Long: Left stick horizontal steer
	            Right stick vertical gas/brake

AOL:  Left stick horizontal steer

Controller profiles added:
Google Stadia Controller added
Xbox Controller has its own profile
Dual Sense has its own profile

joystick_probe.py tool allows people to discover their own controllers.
2026-08-21 17:19:46 -05:00
firestar5683 6eabec436d Update starpilot_boot_logo.jpg 2026-08-21 14:43:55 -05:00
firestar5683 56d6a6ee47 leaf 2026-08-21 14:19:34 -05:00
firestar5683 e9f4c6316c b is for build 2026-08-21 14:04:30 -05:00
firestar5683 284dbddd8a book 2026-08-21 14:03:08 -05:00
firestar5683 0068d7a698 build 2026-08-21 13:23:15 -05:00
firestar5683 f63ee3375a Comover 2026-08-21 13:21:20 -05:00
firestar5683 05e9dfd5d0 Require matching blinkers for navigation turn desires
Cherry-picked Feature Request 1267242 from ab98ae54bb. Prevent sharp or ordinary navigation turn desires from being issued unless the corresponding turn signal is active, and add regression coverage for missing and opposite signals.

Co-Authored-By: Prabhaav Pillai <prabhaav@msu.edu>
2026-08-21 12:04:11 -05:00
firestarsdog 269562e73a Polish favorite picker 2026-08-21 04:54:35 -04:00
firestarsdog 3cefd789e5 Uppies 2026-08-21 01:24:53 -04:00
firestarsdog d477735c92 Radial Menu Squishfest 2026-08-21 01:24:53 -04:00
firestar5683 66893b27ae build 2026-08-21 00:13:48 -05:00
firestar5683 e0d9e05f9f sub 2 2026-08-21 00:12:30 -05:00
1202 changed files with 139972 additions and 17660 deletions
+56
View File
@@ -0,0 +1,56 @@
name: Fleet controller safety
on:
workflow_dispatch:
pull_request:
paths:
- 'opendbc_repo/**'
- 'selfdrive/car/**'
- 'starpilot/car/**'
- 'starpilot/controls/**'
- 'cereal/**'
- '.github/workflows/fleet_safety.yaml'
permissions:
contents: read
jobs:
harness:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: python -m pip install -r selfdrive/car/tests/fleet_requirements.txt
- name: Audit accounting and runner failures
run: >-
python -m pytest --noconftest -o addopts='' -q
selfdrive/car/tests/test_fleet_safety_core.py
selfdrive/car/tests/test_fleet_safety_runner.py
opendbc_repo/opendbc/safety/tests/safety_replay/test_replay_drive.py
recorded_fleet:
runs-on: ubuntu-24.04
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
shard: [0, 1, 2, 3, 4, 5, 6, 7]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: python -m pip install -r selfdrive/car/tests/fleet_requirements.txt
- name: Current controllers against freshly compiled release safety
run: >-
python -m selfdrive.car.tests.fleet_safety --all --release
--shard-count 8 --shard-index ${{ matrix.shard }}
--out selfdrive/car/tests/fleet_results/ci
- uses: actions/upload-artifact@v4
if: always()
with:
name: fleet-safety-${{ matrix.shard }}
path: |
selfdrive/car/tests/fleet_results/ci/**/*.json
selfdrive/car/tests/fleet_results/ci/**/*.log
+1
View File
@@ -81,6 +81,7 @@ selfdrive/modeld/models/*.pkl
# openpilot log files
*.bz2
*.zst
!selfdrive/modeld/firmware/amdgpu/*.zst
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) 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:
+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)
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).
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
## 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.
* 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.
+51 -4
View File
@@ -1,4 +1,5 @@
import os
import importlib
import shutil
import subprocess
import sys
@@ -128,6 +129,41 @@ elif arch == "aarch64" and AGNOS:
arch = "larch64"
assert arch in ["larch64", "aarch64", "x86_64", "Darwin"]
# AGNOS 19.6 ships native dependencies as versioned Python packages. Link
# Cap'n Proto statically from that managed package so release binaries don't
# depend on the removed libcapnp-1.0.2.so system library.
try:
capnproto = importlib.import_module("capnproto")
except ModuleNotFoundError:
capnproto = None
try:
ffmpeg = importlib.import_module("ffmpeg")
except ModuleNotFoundError:
ffmpeg = None
capnproto_include_dirs = [capnproto.INCLUDE_DIR] if capnproto is not None else []
capnproto_lib_dirs = [capnproto.LIB_DIR] if capnproto is not None else []
ffmpeg_include_dirs = [ffmpeg.INCLUDE_DIR] if ffmpeg is not None else []
ffmpeg_lib_dirs = [ffmpeg.LIB_DIR] if ffmpeg is not None else []
# Cross-builds install managed dependencies in /work/.venv-linux-arm64, but
# comma devices expose the same packages from /usr/local/venv. Never embed the
# host/container mount path in release binaries.
ffmpeg_runtime_lib_dirs = ffmpeg_lib_dirs
if arch == "larch64" and ffmpeg is not None:
ffmpeg_runtime_lib_dirs = [os.path.join("/usr/local/venv", os.path.relpath(ffmpeg.LIB_DIR, sys.prefix))]
# The managed native-dependency packages keep their tools inside the package
# instead of installing them into /usr/local/venv/bin. cereal invokes capnpc
# directly while SConscript files are evaluated, so make the packaged tools
# discoverable to both SCons actions and configure-time subprocesses.
dependency_bin_dirs = [
package.BIN_DIR for package in (capnproto, ffmpeg)
if package is not None and os.path.isdir(package.BIN_DIR)
]
if dependency_bin_dirs:
os.environ["PATH"] = os.pathsep.join([*dependency_bin_dirs, os.environ["PATH"]])
# Homebrew llvm can shadow Apple clang and break macOS SDK header resolution.
# Use the system toolchain explicitly on macOS for reliable local builds.
cc = '/usr/bin/clang' if arch == "Darwin" else 'clang'
@@ -269,7 +305,10 @@ env = Environment(
"-Wno-vla-cxx-extension",
] + cflags + ccflags,
CPPPATH=cpppath + [
# Managed dependencies must precede the compatibility sysroot. The sysroot
# can intentionally retain legacy libraries for C3 support, but new release
# binaries must link against the versions shipped in the managed venv.
CPPPATH=capnproto_include_dirs + ffmpeg_include_dirs + cpppath + [
"#",
"#third_party/acados/include",
"#third_party/acados/include/blasfeo/include",
@@ -288,11 +327,11 @@ env = Environment(
RANLIB=ranlib,
LINKFLAGS=ldflags,
RPATH=rpath,
RPATH=ffmpeg_runtime_lib_dirs + rpath,
CFLAGS=["-std=gnu11"] + cflags,
CXXFLAGS=["-std=c++1z"] + cxxflags,
LIBPATH=libpath + [
LIBPATH=capnproto_lib_dirs + ffmpeg_lib_dirs + libpath + [
"#msgq_repo",
"#third_party",
"#selfdrive/pandad",
@@ -371,7 +410,15 @@ SConscript(['opendbc_repo/SConscript'], exports={'env': env_swaglog})
SConscript(['cereal/SConscript'])
Import('socketmaster', 'msgq')
messaging = [socketmaster, msgq, 'capnp', 'kj',]
if capnproto is not None:
messaging = [
socketmaster,
msgq,
File(os.path.join(capnproto.LIB_DIR, "libcapnp.a")),
File(os.path.join(capnproto.LIB_DIR, "libkj.a")),
]
else:
messaging = [socketmaster, msgq, 'capnp', 'kj']
Export('messaging')
+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.
+4
View File
@@ -27,6 +27,10 @@ add_panda_targets() {
panda_h7_remote_can_ignition_only
panda_hkg_remote_can_ignition_only
panda_h7_hkg_remote_can_ignition_only
panda_tesla_wake
panda_h7_tesla_wake
panda_tesla_wake_can_ignition_only
panda_h7_tesla_wake_can_ignition_only
panda_jungle_h7
body_h7
)
+20
View File
@@ -14,6 +14,7 @@ using Car = import "car.capnp";
struct StarPilotCarControl @0x81c2f05a394cf4af {
hudControl @0 :HUDControl;
steeringLimitInfo @1 :SteeringLimitInfo;
struct HUDControl {
audibleAlert @0 :AudibleAlert;
@@ -49,6 +50,16 @@ struct StarPilotCarControl @0x81c2f05a394cf4af {
uwu @22;
}
}
struct SteeringLimitInfo {
valid @0 :Bool;
modelLimitErrorDeg @1 :Float32;
resumeLimitErrorDeg @2 :Float32;
cooperativeLimitErrorDeg @3 :Float32;
cooperativeOffsetDeg @4 :Float32;
monoTime @5 :UInt64;
combinedLimitErrorDeg @6 :Float32;
}
}
struct StarPilotCarParams @0xaedffd8f31e7b55d {
@@ -220,6 +231,14 @@ struct StarPilotPlan @0xf98d843bfd7004a3 {
disableThrottle @35 :Bool;
trackingLead @36 :Bool;
stopSignConfirmed @37 :Bool;
pulseGlideCoasting @38 :Bool; # developer-only P&G phase for on-road status UI
# Curve Speed Controller diagnostics, for tuning and rollout validation
cscOverridden @39 :Bool; # driver cancelled this curve with RES+
cscLearnedLatAccel @40 :Float32; # learned comfort at the current curvature, before margin
cscBindingDistance @41 :Float32; # distance to the horizon point setting the target, m
approachStopLength @42 :Float32; # pre-commit distance to a detected stop, m; 0 when off
slcPresentedSpeedLimitSource @43 :Text; # source of the shown accepted or pending posted limit
slcIsLimitingMaxSet @44 :Bool; # SLC target is below the configured Max Set
}
struct StarPilotRadarState @0xb86e6369214c01c8 {
@@ -264,6 +283,7 @@ struct StarPilotSelfdriveState @0xf416ec09499d9d19 {
alertSize @3 :AlertSize;
alertType @4 :Text;
alertSound @5 :Car.CarControl.HUDControl.AudibleAlert;
vEgo @6 :Float32;
enum AlertStatus {
normal @0;
Binary file not shown.
Binary file not shown.
+2
View File
@@ -774,6 +774,7 @@ struct ChestnutState {
pcieLtssm @7 :UInt8;
supplyVoltage @8 :UInt16; # mV
supplyCurrent @9 :Int16; # mA
supplyFault @10 :Bool;
}
struct RadarState @0x9a185389d6fdd05f {
@@ -2757,6 +2758,7 @@ struct Event {
userBookmark @93 :UserBookmark;
bookmarkButton @148 :UserBookmark;
audioFeedback @149 :AudioFeedback;
visionSpeedLimitBookmark @153 :UserBookmark;
lateralManeuverPlan @150 :LateralManeuverPlan;
# *********** debug ***********
+23 -4
View File
@@ -152,7 +152,8 @@ class FrequencyTracker:
class SubMaster:
def __init__(self, services: List[str], poll: Optional[str] = None,
ignore_alive: Optional[List[str]] = None, ignore_avg_freq: Optional[List[str]] = None,
ignore_valid: Optional[List[str]] = None, addr: str = "127.0.0.1", frequency: Optional[float] = None):
ignore_valid: Optional[list[str]] = None, addr: str = "127.0.0.1", frequency: Optional[float] = None,
drain_services: list[str] | None = None):
self.frame = -1
self.services = services
self.seen = {s: False for s in services}
@@ -160,6 +161,9 @@ class SubMaster:
self.recv_time = {s: 0. for s in services}
self.recv_frame = {s: 0 for s in services}
self.sock = {}
self.drained = {s: [] for s in (drain_services or [])}
if not self.drained.keys() <= set(services):
raise ValueError("Drained services must be subscribed")
self.data = {}
self.logMonoTime = {s: 0 for s in services}
@@ -187,7 +191,7 @@ class SubMaster:
for s in services:
p = self.poller if s not in self.non_polled_services else None
self.sock[s] = sub_sock(s, poller=p, addr=addr, conflate=True)
self.sock[s] = sub_sock(s, poller=p, addr=addr, conflate=s not in self.drained)
try:
data = new_message(s)
@@ -207,14 +211,28 @@ class SubMaster:
def _check_avg_freq(self, s: str) -> bool:
return SERVICE_LIST[s].frequency > 0.99 and (s not in self.ignore_average_freq) and (s not in self.ignore_alive)
def _recv_socket(self, sock):
message = recv_one_or_none(sock)
if not self.drained or message is None:
return message
# Native Poller returns fresh socket wrappers; identify the service by data.
service = message.which()
if service not in self.drained:
return message
# Preserve event edges for observers, but update state/frequency only once.
self.drained[service] = [message, *drain_sock(sock)]
return self.drained[service][-1]
def update(self, timeout: int = 100) -> None:
for service in self.drained:
self.drained[service] = []
msgs = []
for sock in self.poller.poll(timeout):
msgs.append(recv_one_or_none(sock))
msgs.append(self._recv_socket(sock))
# non-blocking receive for non-polled sockets
for s in self.non_polled_services:
msgs.append(recv_one_or_none(self.sock[s]))
msgs.append(self._recv_socket(self.sock[s]))
self.update_msgs(time.monotonic(), msgs)
def update_msgs(self, cur_time: float, msgs: List[capnp.lib.capnp._DynamicStructReader]) -> None:
@@ -262,6 +280,7 @@ class SubMaster:
ignore_valid=self.ignore_valid,
addr=self.addr,
frequency=None if self.poll is not None else self.update_freq,
drain_services=list(self.drained),
)
Binary file not shown.
@@ -1,5 +1,6 @@
import random
import time
import pytest
from typing import Sized, cast
import cereal.messaging as messaging
@@ -16,6 +17,29 @@ class TestSubMaster:
# sleep to prevent multiple publishers error between tests
zmq_sleep(3)
@pytest.mark.parametrize("poll", [None, "deviceState"])
def test_drain_preserves_short_events_with_native_socket_wrappers(self, poll):
pub = messaging.PubMaster(["carState", "deviceState"])
sm = messaging.SubMaster(["carState", "deviceState"], poll=poll, drain_services=["carState"])
zmq_sleep()
pressed = messaging.new_message("carState", valid=True)
button = pressed.carState.init("buttonEvents", 1)[0]
button.type, button.pressed = "accelCruise", True
pub.send("carState", pressed)
latest = messaging.new_message("carState", valid=True)
latest.carState.vEgo = 12.0
pub.send("carState", latest)
pub.send("deviceState", messaging.new_message("deviceState", valid=True))
sm.update(1000)
assert len(sm.drained["carState"]) == 2
assert sm.drained["carState"][0].carState.buttonEvents[0].pressed
assert sm["carState"].vEgo == 12.0 and not sm["carState"].buttonEvents
assert sm.logMonoTime["carState"] == latest.logMonoTime
assert sm.frame == 0 and all(sm.updated.values())
sm.update(0)
assert sm.drained["carState"] == []
assert sm.frame == 1 and not any(sm.updated.values())
def test_init(self):
sm = messaging.SubMaster(events)
for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive,
+1
View File
@@ -69,6 +69,7 @@ static std::map<std::string, service> services = {
{ "rawAudioData", {"rawAudioData", false, 20.000000, -1, 256000}},
{ "bookmarkButton", {"bookmarkButton", true, 0.000000, 1, 256000}},
{ "audioFeedback", {"audioFeedback", true, 0.000000, 1, 256000}},
{ "visionSpeedLimitBookmark", {"visionSpeedLimitBookmark", false, 0.000000, 1, 256000}},
{ "roadEncodeData", {"roadEncodeData", false, 20.000000, -1, 10485760}},
{ "driverEncodeData", {"driverEncodeData", false, 20.000000, -1, 10485760}},
{ "wideRoadEncodeData", {"wideRoadEncodeData", false, 20.000000, -1, 10485760}},
+1
View File
@@ -86,6 +86,7 @@ _services: dict[str, tuple] = {
"rawAudioData": (False, 20.),
"bookmarkButton": (True, 0., 1),
"audioFeedback": (True, 0., 1),
"visionSpeedLimitBookmark": (False, 0., 1),
"roadEncodeData": (False, 20., None, QueueSize.BIG),
"driverEncodeData": (False, 20., None, QueueSize.BIG),
"wideRoadEncodeData": (False, 20., None, QueueSize.BIG),
+1 -1
View File
@@ -2,7 +2,7 @@ from openpilot.common.params import Params
def get_gps_location_service(params: Params) -> str:
if params.get_bool("UbloxAvailable"):
if params.get_bool("UbloxAvailable") or params.get_bool("CarGpsAvailable"):
return "gpsLocationExternal"
else:
return "gpsLocation"
Binary file not shown.
+96 -10
View File
@@ -16,10 +16,15 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"AthenadUploadQueue", {PERSISTENT, JSON}},
{"AthenadRecentlyViewedRoutes", {PERSISTENT, STRING}},
{"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}},
{"CameraDebugExpGain", {CLEAR_ON_MANAGER_START, STRING}},
{"CameraDebugExpTime", {CLEAR_ON_MANAGER_START, STRING}},
{"CarBatteryCapacity", {PERSISTENT, INT}},
{"CarGpsAvailable", {CLEAR_ON_MANAGER_START, BOOL}},
{"CarParams", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BYTES}},
{"CarParamsCache", {CLEAR_ON_MANAGER_START, BYTES}},
{"CarParamsPersistent", {PERSISTENT, BYTES}},
@@ -62,6 +67,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"GsmRoaming", {PERSISTENT, BOOL}},
{"HardwareSerial", {PERSISTENT, STRING}},
{"HasAcceptedTerms", {PERSISTENT, STRING, "0"}},
// Internal kill switch; the platform verification allowlist remains the hard safety gate.
{"HondaBoschARadar", {PERSISTENT, BOOL, "1"}},
{"HondaGasFactorParams", {PERSISTENT, FLOAT}},
{"HondaLateralPidKiScale", {PERSISTENT, FLOAT, "1.0", "1.0", 3}},
{"HondaLateralPidKpScale", {PERSISTENT, FLOAT, "1.0", "1.0", 3}},
@@ -81,6 +88,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"IsTakingSnapshot", {CLEAR_ON_MANAGER_START, BOOL}},
{"IsTestedBranch", {CLEAR_ON_MANAGER_START, BOOL}},
{"JoystickDebugMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
{"JoystickControlDevice", {PERSISTENT, STRING}},
{"LanguageSetting", {PERSISTENT, STRING, "main_en"}},
{"LastAthenaPingTime", {CLEAR_ON_MANAGER_START, INT}},
{"LastGPSPosition", {PERSISTENT, STRING}},
@@ -102,10 +110,17 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"LocationFilterInitialState", {PERSISTENT, BYTES}},
{"LongitudinalManeuverMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
{"LongitudinalPersonality", {PERSISTENT, INT, std::to_string(static_cast<int>(cereal::LongitudinalPersonality::STANDARD))}},
{"LongitudinalPersonalityProfiles", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}},
{"NetworkMetered", {PERSISTENT, BOOL}},
{"ObdMultiplexingChanged", {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_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_ConnectivityNeededPrompt", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_ExcessiveActuation", {PERSISTENT, JSON}},
@@ -183,11 +198,12 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"AggressiveJerkSpeedDecrease", {PERSISTENT, FLOAT, "50.0", "50.0", 3}},
{"AlertVolumeControl", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"AllowImpossibleAcceleration", {PERSISTENT, BOOL, "0", "0", 3}},
{"AlwaysOnLateral", {PERSISTENT, BOOL, "1", "0", 0, SETTINGS_SIMPLE}},
{"AlwaysOnLateral", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
{"AlwaysOnLateralLKAS", {PERSISTENT, BOOL, "1", "0", 2}},
{"ApiCache_DriveStats", {PERSISTENT, JSON, "{}", "{}"}},
{"AutomaticallyDownloadModels", {PERSISTENT, BOOL, "1", "0", 1}},
{"AutomaticUpdates", {PERSISTENT, BOOL, "1", "1", 0}},
{"AllowGpuModelDownloadWithoutGpu", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
{"AvailableModelNames", {PERSISTENT, STRING, "", "", 1}},
{"AvailableModelSeries", {PERSISTENT, STRING, "", "", 1}},
{"AvailableModels", {PERSISTENT, STRING, "", "", 1}},
@@ -251,6 +267,32 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"CustomAccelProfile45MPH", {PERSISTENT, FLOAT, "1.0", "1.0", 3}},
{"CustomAccelProfile56MPH", {PERSISTENT, FLOAT, "0.8", "0.8", 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}},
{"CustomCruiseLong", {PERSISTENT, FLOAT, "5.0", "5.0", 2, SETTINGS_SIMPLE}},
{"CustomPersonalities", {PERSISTENT, BOOL, "0", "0", 2}},
@@ -276,6 +318,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"DeveloperSidebarMetric7", {PERSISTENT, INT, "7", "0", 3}},
{"DeveloperUI", {PERSISTENT, BOOL, "0", "0", 3}},
{"GalaxyDeveloperMode", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
{"GalaxyDeviceName", {PERSISTENT | DONT_LOG, STRING, "", "", 0}},
{"GalaxyMobileDefault", {PERSISTENT | DONT_LOG, BOOL, "1", "1", 0, SETTINGS_SIMPLE}},
{"DeveloperWidgets", {PERSISTENT, BOOL, "1", "0", 3}},
{"DeviceManagement", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
{"DeviceShutdown", {PERSISTENT, INT, "6", "6", 1, SETTINGS_SIMPLE}},
@@ -296,6 +340,12 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"DownloadAllModels", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
{"DownloadMaps", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
{"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}},
{"ModelVersion", {PERSISTENT, STRING, "v15", "v15", 1}},
{"DrivingModel", {PERSISTENT, STRING, "rdf43", "rdf43", 1}},
@@ -303,6 +353,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"DrivingModelVersion", {PERSISTENT, STRING, "v15", "v15", 1}},
{"DynamicPathWidth", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"DynamicPedalsOnUI", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
{"GpuModelReadySound", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"EngageVolume", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
{"EVTuning", {PERSISTENT, BOOL, "0", "0", 3}},
{"Fahrenheit", {PERSISTENT, BOOL, "0", "0", 3}},
@@ -313,6 +364,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"IgnoreIgnitionLine", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
{"LongPitch", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
{"RemoteStartBootsComma", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
{"TeslaWakeOnCAN", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
{"RemapCancelToDistance", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
{"NAPAdaptiveAccel", {PERSISTENT, BOOL, "1", "1", 0, SETTINGS_SIMPLE}},
{"NAPFollowDistance", {PERSISTENT, INT, "4", "4"}},
@@ -336,17 +388,15 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"ForceStops", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
{"ForceStopDistanceOffset", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
{"ForceStandstill", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"FordLKASButtonControlMigrated", {PERSISTENT, BOOL, "0", "0"}},
{"ForceTorqueController", {PERSISTENT, BOOL, "0", "0", 3}},
{"FordAngleBlend", {PERSISTENT, FLOAT, "0.5", "0.5", 2}},
{"FordAngleHighSpeedDamping", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
{"FordAngleHighSpeedFactor", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
{"FordAngleLaneChangeFactor", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
{"FordAngleLowSpeedFactor", {PERSISTENT, FLOAT, "1.0", "1.0", 2}},
// These Ford curvature tuning concepts descend from BluePilot bp-7.0. StarPilot's key names and
// settings integration are local; see /CREDITS.md and /THIRD_PARTY_NOTICES.md for provenance.
{"FordCurvatureBlendHigh", {PERSISTENT, FLOAT, "0.4", "0.4", 2}},
{"FordCurvatureBlendLow", {PERSISTENT, FLOAT, "0.4", "0.4", 2}},
{"FordCurvatureLaneChangeFactor", {PERSISTENT, FLOAT, "0.85", "0.85", 2}},
{"FordHandsFreeCluster", {PERSISTENT, BOOL, "0", "0", 2}},
{"FordHumanTurnDetection", {PERSISTENT, BOOL, "1", "1", 2}},
{"FordLateralMode", {PERSISTENT, INT, "1", "1", 2}},
{"FLMActiveOverrides", {PERSISTENT, JSON, "{}", "{}", 2}},
{"FLMActiveProfileId", {PERSISTENT, STRING, "", "", 2}},
{"FLMSubmittedTune", {CLEAR_ON_MANAGER_START, JSON, "{}", "{}"}},
@@ -359,6 +409,12 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"StarPilotCarParamsPersistent", {PERSISTENT, BYTES, "", ""}},
{"StarPilotDongleId", {PERSISTENT | DONT_LOG, STRING, "", "", 0}},
{"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, "{}", "{}"}},
{"StarPilotTogglesUpdated", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
{"GoatScream", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
@@ -389,6 +445,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"IncreaseFollowingRain", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
{"IncreaseFollowingRainStorm", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
{"IncreaseFollowingSnow", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
{"AggressiveCoolingEnabled", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"IncreaseThermalLimits", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"IssueReported", {CLEAR_ON_MANAGER_START, JSON, "{}", "{}"}},
{"KonikDongleId", {PERSISTENT, STRING, "", "", 0}},
@@ -412,7 +469,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"LeadDepartingAlert", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
{"LeadDetectionThreshold", {PERSISTENT, INT, "35", "50", 3}},
{"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}},
{"LockDoors", {PERSISTENT, BOOL, "1", "0", 0}},
{"LockDoorsTimer", {PERSISTENT, INT, "0", "0", 0}},
@@ -468,6 +526,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"ModeButtonControl", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
{"ModelDownloadProgress", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
{"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}},
{"ModelRandomizer", {PERSISTENT, BOOL, "0", "0", 2}},
{"LatSmoothSeconds", {PERSISTENT, FLOAT, "0.1", "0.1", 3}},
@@ -501,6 +562,11 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"FavoriteVirtualDecelCruiseCounter", {CLEAR_ON_MANAGER_START, INT, "0", "0"}},
{"FavoriteTrafficModeCounter", {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}},
{"OverpassRequests", {PERSISTENT, JSON, "{}", "{}"}},
{"PathColor", {PERSISTENT, STRING, "", "", 2, SETTINGS_SIMPLE}},
@@ -516,6 +582,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"PIPPreviewMask", {PERSISTENT, JSON, "{\"width\":1928,\"height\":1208,\"center_left\":[315,548],\"center_right\":[1571,539],\"crop_size\":580}", "{\"width\":1928,\"height\":1208,\"center_left\":[315,548],\"center_right\":[1571,539],\"crop_size\":580}", 2}},
{"PIPPreviewShowOnBlinker", {PERSISTENT, BOOL, "0", "0", 1}},
{"PIPPreviewShowOnBSM", {PERSISTENT, BOOL, "0", "0", 1}},
{"PIPPreviewInvert", {PERSISTENT, BOOL, "0", "0", 1}},
{"GalaxyPaired", {PERSISTENT, BOOL, "0", "0", 0}},
{"GalaxyUploadPending", {PERSISTENT, BOOL, "0", "0", 0}},
{"PreferredSchedule", {PERSISTENT, INT, "2", "0", 0}},
@@ -548,7 +615,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"RelaxedJerkDeceleration", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"RelaxedJerkSpeed", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"RelaxedJerkSpeedDecrease", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"ReverseCruise", {PERSISTENT, BOOL, "0", "0", 1}},
{"ReverseCruise", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
{"RivianAngleControl", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"RivianAngleSaturated", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL, "0", "0"}},
{"RivianToiRecoveryFailed", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL, "0", "0"}},
@@ -558,6 +625,10 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"RotatingWheel", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
{"ScreenBrightness", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
{"ScreenBrightnessOnroad", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
{"ScreenBrightnessManual", {PERSISTENT, INT, "100", "100", 2, SETTINGS_SIMPLE}},
{"ScreenBrightnessOffset", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
{"ScreenBrightnessOnroadManual", {PERSISTENT, INT, "100", "100", 2, SETTINGS_SIMPLE}},
{"ScreenBrightnessOnroadOffset", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
{"ScreenManagement", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
{"ScreenRecorder", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
{"ScreenTimeout", {PERSISTENT, INT, "30", "30", 2, SETTINGS_SIMPLE}},
@@ -567,6 +638,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"SafeModeBackup", {PERSISTENT, JSON, "{}", "{}"}},
{"SetSpeedLimit", {PERSISTENT, BOOL, "0", "0", 1}},
{"SetSpeedOffset", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
{"ShowBrakeStatus", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"ShowCEMStatus", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
{"ShowCCMStatus", {PERSISTENT, BOOL, "0", "0", 2}},
{"ShowCPU", {PERSISTENT, BOOL, "0", "0", 3}},
@@ -628,6 +700,15 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"StandardJerkSpeed", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"StandardJerkSpeedDecrease", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"StandbyMode", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
{"StandbyWakeButton", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"StandbyButtonPressTime", {CLEAR_ON_MANAGER_START | DONT_LOG, INT, "0", "0"}},
{"ScreenOffToggleCounter", {CLEAR_ON_MANAGER_START | DONT_LOG, INT, "0", "0"}},
{"StandbyWakeEngage", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
{"StandbyWakeDisengage", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
{"StandbyWakeInfoAlert", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
{"StandbyWakeWarningAlert", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
{"StandbyWakeCriticalAlert", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
{"StandbyWakeTurnSignal", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"StartAccel", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
{"StartAccelStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
{"StartupMessageBottom", {PERSISTENT, STRING, "Always keep hands on wheel and eyes on road", "Always keep hands on wheel and eyes on road", 0}},
@@ -659,7 +740,11 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"SwitchbackModeEnabled", {CLEAR_ON_OFFROAD_TRANSITION, BOOL, "0", "0"}},
{"SubaruSNG", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
{"SubaruSNGManualParkingBrake", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"SubaruStopStartOff", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"SubaruAvhStartup", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"SubaruRedneckCruise", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"TacoTune", {PERSISTENT, BOOL, "0", "0", 2}},
{"TeslaAOLDisengageOnBrake", {PERSISTENT, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
{"TeslaCoopSteering", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"TestAlert", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
{"TetheringEnabled", {PERSISTENT, INT, "0", "0", 0}},
@@ -679,6 +764,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"TuningLevel", {PERSISTENT, INT, "0", "0", 0}},
{"TuningLevelConfirmed", {PERSISTENT, BOOL, "0", "0", 0}},
{"TurnDesires", {PERSISTENT, BOOL, "0", "0", 2}},
{"TurnSteeringLimitMuteSpeed", {PERSISTENT, INT, "0", "0", 0}},
{"UnlockDoors", {PERSISTENT, BOOL, "1", "0", 0}},
{"Updated", {PERSISTENT, STRING, "0", "0"}},
{"UpdateSpeedLimits", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
@@ -692,7 +778,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"UserFavorites", {PERSISTENT, STRING, "", "", 1}},
{"UseVienna", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
{"VASMAnnotationConfig", {PERSISTENT, JSON, "{}", "{}", 2}},
{"VASMConfidenceThreshold", {PERSISTENT, FLOAT, "0.85", "0.85", 2}},
{"VASMConfidenceThreshold", {PERSISTENT, FLOAT, "0.94", "0.94", 2}},
{"VASMEnabled", {PERSISTENT, BOOL, "0", "0", 1}},
{"VASMLeftActive", {CLEAR_ON_MANAGER_START, STRING, "0", "0", 2}},
{"VASMLeftConfidence", {CLEAR_ON_MANAGER_START, STRING, "0.0", "0.0", 2}},
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 uuid
from openpilot.common.params import Params, ParamKeyFlag, UnknownKeyName
from openpilot.common.params import Params, ParamKeyFlag, ParamKeyType, UnknownKeyName
class TestParams:
def setup_method(self):
@@ -128,6 +128,31 @@ class TestParams:
assert self.params.get("LiveParameters") 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):
# json
self.params.put("ApiCache_DriveStats", {"a": 0})
Binary file not shown.
+179 -175
View File
@@ -4,36 +4,36 @@
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.
# 451 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|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|Acura|ADX 2025-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 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=Acura ADX 2025-26">Buy Here</a></sub></details>|||
|Acura|ADX 2025-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 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=Acura ADX 2025-26">Buy Here</a></sub></details>|||
|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 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=Acura ILX 2016-18">Buy Here</a></sub></details>|||
|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 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=Acura ILX 2019">Buy Here</a></sub></details>|||
|Acura|Integra 2023-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 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=Acura Integra 2023-26">Buy Here</a></sub></details>|||
|Acura|Integra 2023-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 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=Acura Integra 2023-26">Buy Here</a></sub></details>|||
|Acura|MDX 2014-16|Advance Package|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 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=Acura MDX 2014-16">Buy Here</a></sub></details>|||
|Acura|MDX 2017-19|All|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 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=Acura MDX 2017-19">Buy Here</a></sub></details>|||
|Acura|MDX 2020|All|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 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=Acura MDX 2020">Buy Here</a></sub></details>|||
|Acura|MDX 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|43 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2022-24">Buy Here</a></sub></details>|||
|Acura|MDX 2025|All except Type S|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2025">Buy Here</a></sub></details>|||
|Acura|MDX 2022-24|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|43 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2022-24">Buy Here</a></sub></details>|||
|Acura|MDX 2025|All except Type S|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2025">Buy Here</a></sub></details>|||
|Acura|MDX Hybrid 2017-19|All|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 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=Acura MDX Hybrid 2017-19">Buy Here</a></sub></details>|||
|Acura|MDX Hybrid 2020|All|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 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=Acura MDX Hybrid 2020">Buy Here</a></sub></details>|||
|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 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=Acura RDX 2016-18">Buy Here</a></sub></details>|||
|Acura|RDX 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 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=Acura RDX 2019-21">Buy Here</a></sub></details>|||
|Acura|RDX 2022-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|43 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 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=Acura RDX 2022-26">Buy Here</a></sub></details>|||
|Acura|RDX 2019-21|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 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=Acura RDX 2019-21">Buy Here</a></sub></details>|||
|Acura|RDX 2022-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|43 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 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=Acura RDX 2022-26">Buy Here</a></sub></details>|||
|Acura|TLX 2015-17|Advance Package|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 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=Acura TLX 2015-17">Buy Here</a></sub></details>|||
|Acura|TLX 2018-20|All|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 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=Acura TLX 2018-20">Buy Here</a></sub></details>|||
|Acura|TLX 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura TLX 2021">Buy Here</a></sub></details>|||
|Acura|TLX 2024-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura TLX 2024-25">Buy Here</a></sub></details>|||
|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 2014-19">Buy Here</a></sub></details>|||
|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 Sportback e-tron 2017-18">Buy Here</a></sub></details>|||
|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q2 2018">Buy Here</a></sub></details>|||
|Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q3 2019-24">Buy Here</a></sub></details>|||
|Audi|Q4 e-tron 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q4 e-tron 2021-23">Buy Here</a></sub></details>|||
|Audi|Q4 e-tron 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q4 e-tron 2024-25">Buy Here</a></sub></details>|||
|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi RS3 2018">Buy Here</a></sub></details>|||
|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi S3 2015-17">Buy Here</a></sub></details>|||
|Acura|TLX 2021|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura TLX 2021">Buy Here</a></sub></details>|||
|Acura|TLX 2024-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Acura TLX 2024-25">Buy Here</a></sub></details>|||
|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 2014-19">Buy Here</a></sub></details>|||
|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 Sportback e-tron 2017-18">Buy Here</a></sub></details>|||
|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q2 2018">Buy Here</a></sub></details>|||
|Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q3 2019-24">Buy Here</a></sub></details>|||
|Audi|Q4 e-tron 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q4 e-tron 2021-23">Buy Here</a></sub></details>|||
|Audi|Q4 e-tron 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q4 e-tron 2024-25">Buy Here</a></sub></details>|||
|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi RS3 2018">Buy Here</a></sub></details>|||
|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Audi S3 2015-17">Buy Here</a></sub></details>|||
|Buick|Baby Enclave 2020-23|Driver Assist Package|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=Buick Baby Enclave 2020-23">Buy Here</a></sub></details>|||
|Buick[<sup>3</sup>](#footnotes)|LaCrosse 2017-19|Driver Confidence Package 2|openpilot|18 mph|7 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 OBD-II connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Buick LaCrosse 2017-19">Buy Here</a></sub></details>|||
|Buick|LaCrosse ASCM Harness 2017-19|Adaptive Cruise Control (ACC)|Stock|3 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=Buick LaCrosse ASCM Harness 2017-19">Buy Here</a></sub></details>|||
@@ -76,55 +76,56 @@ 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 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 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 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 Hybrid 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 Hybrid 2017-18">Buy Here</a></sub></details>|||
|Chrysler|Pacifica Hybrid 2019-25|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 Hybrid 2019-25">Buy Here</a></sub></details>|||
|comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None|<a href="https://youtu.be/VT-i3yRsX2s?t=2736" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=CUPRA Ateca 2018-23">Buy Here</a></sub></details>|||
|CUPRA|Born 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=CUPRA Born 2021-23">Buy Here</a></sub></details>|||
|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=CUPRA Ateca 2018-23">Buy Here</a></sub></details>|||
|CUPRA|Born 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=CUPRA Born 2021-23">Buy Here</a></sub></details>|||
|Dodge|Durango 2020-21|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=Dodge Durango 2020-21">Buy Here</a></sub></details>|||
|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Bronco Sport 2021-24">Buy Here</a></sub></details>|||
|Ford|Edge 2022|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Edge 2022">Buy Here</a></sub></details>|||
|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Escape 2020-22">Buy Here</a></sub></details>|||
|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Escape 2023-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Escape Hybrid 2020-22">Buy Here</a></sub></details>|||
|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Escape Hybrid 2023-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Escape Plug-in Hybrid 2020-22">Buy Here</a></sub></details>|||
|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Escape Plug-in Hybrid 2023-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Expedition 2022-24|Co-Pilot360 Assist 2.0|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Expedition 2022-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Explorer 2020-24">Buy Here</a></sub></details>|||
|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Explorer Hybrid 2020-24">Buy Here</a></sub></details>|||
|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford F-150 2021-23">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford F-150 Hybrid 2021-23">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|F-150 Lightning 2022-25|Co-Pilot360 Assist 2.0|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford F-150 Lightning 2022-25">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Focus 2018-22[<sup>2</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Focus 2018-22">Buy Here</a></sub></details>|||
|Ford|Focus Hybrid 2018-22[<sup>2</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Focus Hybrid 2018-22">Buy Here</a></sub></details>|||
|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Kuga 2020-23">Buy Here</a></sub></details>|||
|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Kuga Hybrid 2020-23">Buy Here</a></sub></details>|||
|Ford|Kuga Hybrid 2024|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Kuga Hybrid 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Kuga Plug-in Hybrid 2020-23">Buy Here</a></sub></details>|||
|Ford|Kuga Plug-in Hybrid 2024|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Kuga Plug-in Hybrid 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Maverick 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Maverick 2022">Buy Here</a></sub></details>|||
|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Maverick 2023-24">Buy Here</a></sub></details>|||
|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Maverick Hybrid 2022">Buy Here</a></sub></details>|||
|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Maverick Hybrid 2023-24">Buy Here</a></sub></details>|||
|Ford|Mondeo 2014-22|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Mondeo 2014-22">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Mustang Mach-E 2021-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Mustang Mach-E 2021-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Ranger 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Transit 2025|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Transit 2025">Buy Here</a></sub></details>|||
|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Bronco Sport 2021-24">Buy Here</a></sub></details>|||
|Ford|Edge 2022|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Edge 2022">Buy Here</a></sub></details>|||
|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Escape 2020-22">Buy Here</a></sub></details>|||
|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Escape 2023-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Escape Hybrid 2020-22">Buy Here</a></sub></details>|||
|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Escape Hybrid 2023-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Escape Plug-in Hybrid 2020-22">Buy Here</a></sub></details>|||
|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Escape Plug-in Hybrid 2023-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Expedition 2022-24|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Expedition 2022-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Explorer 2020-24">Buy Here</a></sub></details>|||
|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Explorer Hybrid 2020-24">Buy Here</a></sub></details>|||
|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford F-150 2021-23">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford F-150 Hybrid 2021-23">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|F-150 Lightning 2022-25|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford F-150 Lightning 2022-25">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=MewJc9LYp9M" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Focus 2018-22[<sup>2</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Focus 2018-22">Buy Here</a></sub></details>|||
|Ford|Focus Hybrid 2018-22[<sup>2</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Focus Hybrid 2018-22">Buy Here</a></sub></details>|||
|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Kuga 2020-23">Buy Here</a></sub></details>|||
|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Kuga Hybrid 2020-23">Buy Here</a></sub></details>|||
|Ford|Kuga Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Kuga Hybrid 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Kuga Plug-in Hybrid 2020-23">Buy Here</a></sub></details>|||
|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Kuga Plug-in Hybrid 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Maverick 2022|LARIAT Luxury|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Maverick 2022">Buy Here</a></sub></details>|||
|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Maverick 2023-24">Buy Here</a></sub></details>|||
|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Maverick Hybrid 2022">Buy Here</a></sub></details>|||
|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Maverick Hybrid 2023-24">Buy Here</a></sub></details>|||
|Ford|Mondeo 2014-22|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Mondeo 2014-22">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Mustang Mach-E 2021-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Mustang Mach-E 2021-24">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 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=Ford Ranger 2024">Buy Here</a></sub></details>||<a href="https://www.youtube.com/watch?v=uUGkH6C_EQU" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Ford|Transit 2025|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Ford Transit 2025">Buy Here</a></sub></details>|||
|Genesis|G70 2018|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F 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=Genesis G70 2018">Buy Here</a></sub></details>|||
|Genesis|G70 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F 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=Genesis G70 2019-21">Buy Here</a></sub></details>|||
|Genesis|G70 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai 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=Genesis G70 2022-23">Buy Here</a></sub></details>|||
|Genesis|G80 2017|All|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai J connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis G80 2017">Buy Here</a></sub></details>|||
|Genesis|G80 2018-19|All|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=Genesis G80 2018-19">Buy Here</a></sub></details>|||
|Genesis|G80 2018-19|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 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=Genesis G80 2018-19">Buy Here</a></sub></details>|||
|Genesis|G80 (2.5T Advanced Trim, with HDA II) 2024|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis G80 (2.5T Advanced Trim, with HDA II) 2024">Buy Here</a></sub></details>|||
|Genesis|G90 2017-20|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 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=Genesis G90 2017-20">Buy Here</a></sub></details>|||
|Genesis|GV60 (Advanced Trim) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis GV60 (Advanced Trim) 2023">Buy Here</a></sub></details>|||
|Genesis|GV60 (Performance Trim) 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 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=Genesis GV60 (Performance Trim) 2022-23">Buy Here</a></sub></details>|||
|Genesis|GV70 (2.5T Trim, without HDA II) 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 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=Genesis GV70 (2.5T Trim, without HDA II) 2022-24">Buy Here</a></sub></details>|||
|Genesis|GV70 (3.5T Sport Prestige Trim, with HDA II & LFA2) 2026|Highway Driving Assist II & Lane Follow Assist 2|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M connector<br>- 1 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=Genesis GV70 (3.5T Sport Prestige Trim, with HDA II & LFA2) 2026">Buy Here</a></sub></details>|||
|Genesis|GV70 (3.5T Trim, without HDA II) 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M connector<br>- 1 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=Genesis GV70 (3.5T Trim, without HDA II) 2022-23">Buy Here</a></sub></details>|||
|Genesis|GV70 Electrified 2026|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M connector<br>- 1 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=Genesis GV70 Electrified 2026">Buy Here</a></sub></details>|||
|Genesis|GV70 Electrified (Australia Only) 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis GV70 Electrified (Australia Only) 2022">Buy Here</a></sub></details>|||
@@ -140,46 +141,46 @@ A supported vehicle is one that just works when you install a comma device. All
|GMC[<sup>3</sup>](#footnotes)|Yukon No-ACC 2019-20|Adaptive Cruise Control (ACC)|openpilot|24 mph|7 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 OBD-II connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=GMC Yukon No-ACC 2019-20">Buy Here</a></sub></details>|||
|Holden[<sup>3</sup>](#footnotes)|Astra 2017|Adaptive Cruise Control (ACC)|openpilot|18 mph|7 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 OBD-II connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Holden Astra 2017">Buy Here</a></sub></details>|||
|Honda|Accord 2016-17|Honda Sensing|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2016-17">Buy Here</a></sub></details>|||
|Honda|Accord 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2018-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=mrUwlj3Mi58" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Accord 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2023-25">Buy Here</a></sub></details>|||
|Honda|Accord 2018-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2018-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=mrUwlj3Mi58" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Accord 2023-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2023-25">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2017|All|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2017">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2018-22">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2023-25">Buy Here</a></sub></details>|||
|Honda|City (Brazil only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda City (Brazil only) 2023">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2018-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2018-22">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2023-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2023-25">Buy Here</a></sub></details>|||
|Honda|City (Brazil only) 2023|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda City (Brazil only) 2023">Buy Here</a></sub></details>|||
|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2016-18">Buy Here</a></sub></details>|<a href="https://youtu.be/-IkImTe1NYE" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|2 mph[<sup>4</sup>](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=4Iz1Mz5LGF8" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2017-18">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2019-21">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic Hatchback Hybrid 2025-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid 2025-26">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid (Europe only) 2023">Buy Here</a></sub></details>|||
|Honda|Civic Hybrid 2025-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hybrid 2025-26">Buy Here</a></sub></details>|||
|Honda|Civic 2019-21|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|2 mph[<sup>4</sup>](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=4Iz1Mz5LGF8" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic 2022-24|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2017-18">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2019-21|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2019-21">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2022-24|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic Hatchback Hybrid 2025-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid 2025-26">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid (Europe only) 2023">Buy Here</a></sub></details>|||
|Honda|Civic Hybrid 2025-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hybrid 2025-26">Buy Here</a></sub></details>|||
|Honda|Clarity 2018-21|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Clarity 2018-21">Buy Here</a></sub></details>|||
|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2015-16">Buy Here</a></sub></details>|||
|Honda|CR-V 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|15 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2017-22">Buy Here</a></sub></details>|||
|Honda|CR-V 2023-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2023-26">Buy Here</a></sub></details>|||
|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2017-22">Buy Here</a></sub></details>|||
|Honda|CR-V Hybrid 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2023-25">Buy Here</a></sub></details>|||
|Honda|e 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda e 2020">Buy Here</a></sub></details>|||
|Honda|CR-V 2017-22|Honda Sensing|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|15 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2017-22">Buy Here</a></sub></details>|||
|Honda|CR-V 2023-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2023-26">Buy Here</a></sub></details>|||
|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2017-22">Buy Here</a></sub></details>|||
|Honda|CR-V Hybrid 2023-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2023-25">Buy Here</a></sub></details>|||
|Honda|e 2020|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda e 2020">Buy Here</a></sub></details>|||
|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Fit 2018-20">Buy Here</a></sub></details>|||
|Honda|Fit (Taiwan) 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Fit (Taiwan) 2021">Buy Here</a></sub></details>|||
|Honda|Fit (Taiwan) 2024-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Fit (Taiwan) 2024-25">Buy Here</a></sub></details>|||
|Honda|Fit (Taiwan) 2021|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Fit (Taiwan) 2021">Buy Here</a></sub></details>|||
|Honda|Fit (Taiwan) 2024-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Fit (Taiwan) 2024-25">Buy Here</a></sub></details>|||
|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Freed 2020">Buy Here</a></sub></details>|||
|Honda|HR-V 2019-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda HR-V 2019-22">Buy Here</a></sub></details>|||
|Honda|HR-V 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda HR-V 2023-25">Buy Here</a></sub></details>|||
|Honda|Insight 2019-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Insight 2019-22">Buy Here</a></sub></details>|||
|Honda|Inspire 2018|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Inspire 2018">Buy Here</a></sub></details>|||
|Honda|N-Box 2018|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|11 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda N-Box 2018">Buy Here</a></sub></details>|||
|Honda|HR-V 2023-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda HR-V 2023-25">Buy Here</a></sub></details>|||
|Honda|Insight 2019-22|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Insight 2019-22">Buy Here</a></sub></details>|||
|Honda|Inspire 2018|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Inspire 2018">Buy Here</a></sub></details>|||
|Honda|N-Box 2018|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|11 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda N-Box 2018">Buy Here</a></sub></details>|||
|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2018-20">Buy Here</a></sub></details>|||
|Honda|Odyssey 2021-26|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|43 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2021-26">Buy Here</a></sub></details>|||
|Honda|Odyssey 2021-26|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|43 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2021-26">Buy Here</a></sub></details>|||
|Honda|Odyssey (Singapore) 2021|Honda Sensing|openpilot|19 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey (Singapore) 2021">Buy Here</a></sub></details>|||
|Honda|Odyssey (Taiwan) 2018-19|Honda Sensing|openpilot|19 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey (Taiwan) 2018-19">Buy Here</a></sub></details>|||
|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Passport 2019-25">Buy Here</a></sub></details>|||
|Honda|Passport 2026|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Passport 2026">Buy Here</a></sub></details>|||
|Honda|Passport 2026|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Passport 2026">Buy Here</a></sub></details>|||
|Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2016-22">Buy Here</a></sub></details>|||
|Honda|Pilot 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2023-25">Buy Here</a></sub></details>|||
|Honda|Prelude 2026|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Prelude 2026">Buy Here</a></sub></details>|||
|Honda|Pilot 2023-25|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2023-25">Buy Here</a></sub></details>|||
|Honda|Prelude 2026|All|openpilot available[<sup>1,5</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Prelude 2026">Buy Here</a></sub></details>|||
|Honda|Ridgeline 2017-25|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Ridgeline 2017-25">Buy Here</a></sub></details>|||
|Hyundai|Azera 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera 2022">Buy Here</a></sub></details>|||
|Hyundai|Azera Hybrid 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera Hybrid 2019">Buy Here</a></sub></details>|||
@@ -325,54 +326,54 @@ A supported vehicle is one that just works when you install a comma device. All
|Lexus|RX Hybrid 2017-19|All|Stock|0 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=Lexus RX Hybrid 2017-19">Buy Here</a></sub></details>|||
|Lexus|RX Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.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=Lexus RX Hybrid 2020-22">Buy Here</a></sub></details>|||
|Lexus|UX Hybrid 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.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=Lexus UX Hybrid 2019-24">Buy Here</a></sub></details>|||
|Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Lincoln Aviator 2020-24">Buy Here</a></sub></details>|||
|Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Lincoln Aviator Plug-in Hybrid 2020-24">Buy Here</a></sub></details>|||
|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=MAN eTGE 2020-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=MAN TGE 2017-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Lincoln Aviator 2020-24">Buy Here</a></sub></details>|||
|Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 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=Lincoln Aviator Plug-in Hybrid 2020-24">Buy Here</a></sub></details>|||
|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=MAN eTGE 2020-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=MAN TGE 2017-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Mazda 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=Mazda CX-5 2022-25">Buy Here</a></sub></details>|||
|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Mazda 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=Mazda CX-9 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/dA3duO4a0O4" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Nissan[<sup>5</sup>](#footnotes)|Altima 2019-20, 2024|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan B 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=Nissan Altima 2019-20, 2024">Buy Here</a></sub></details>|||
|Nissan[<sup>5</sup>](#footnotes)|Leaf 2018-25|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A 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=Nissan Leaf 2018-25">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Nissan[<sup>5</sup>](#footnotes)|Leaf Instrument Cluster 2018-25|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A 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=Nissan Leaf Instrument Cluster 2018-25">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Nissan[<sup>5</sup>](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A 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=Nissan Rogue 2018-20">Buy Here</a></sub></details>|||
|Nissan[<sup>5</sup>](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A 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=Nissan X-Trail 2017">Buy Here</a></sub></details>|||
|Nissan[<sup>6</sup>](#footnotes)|Altima 2019-20, 2024|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan B 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=Nissan Altima 2019-20, 2024">Buy Here</a></sub></details>|||
|Nissan[<sup>6</sup>](#footnotes)|Leaf 2018-25|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A 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=Nissan Leaf 2018-25">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Nissan[<sup>6</sup>](#footnotes)|Leaf Instrument Cluster 2018-25|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A 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=Nissan Leaf Instrument Cluster 2018-25">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Nissan[<sup>6</sup>](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A 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=Nissan Rogue 2018-20">Buy Here</a></sub></details>|||
|Nissan[<sup>6</sup>](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A 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=Nissan X-Trail 2017">Buy Here</a></sub></details>|||
|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|32 mph|1 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 Ram 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=Ram 1500 2019-24">Buy Here</a></sub></details>|||
|Rivian|R1S 2022-24|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<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=Rivian R1S 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Rivian|R1S 2025|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian B connector<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=Rivian R1S 2025">Buy Here</a></sub></details>|||
|Rivian|R1T 2022-24|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian A connector<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=Rivian R1T 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Rivian|R1T 2025|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Rivian B connector<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=Rivian R1T 2025">Buy Here</a></sub></details>|||
|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Ateca 2016-23">Buy Here</a></sub></details>|||
|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Leon 2014-20">Buy Here</a></sub></details>|||
|Subaru|Ascent 2019-21|All[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Ascent 2023-25|All[<sup>6</sup>](#footnotes)|Stock|0 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 Subaru D 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=Subaru Ascent 2023-25">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Crosstrek 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Crosstrek 2020-23">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Crosstrek 2025|All[<sup>6</sup>](#footnotes)|Stock|0 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 Subaru D 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=Subaru Crosstrek 2025">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Forester 2019-21|All[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Forester 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Forester 2022-24|All[<sup>6</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru C 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=Subaru Forester 2022-24">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Impreza 2017-19|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Impreza 2017-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Impreza 2020-22|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Impreza 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Legacy 2020-22|All[<sup>6</sup>](#footnotes)|Stock|0 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 Subaru B 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=Subaru Legacy 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Legacy 2025|All[<sup>6</sup>](#footnotes)|Stock|0 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 Subaru D 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=Subaru Legacy 2025">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Outback 2020-22|All[<sup>6</sup>](#footnotes)|Stock|0 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 Subaru B 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=Subaru Outback 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Outback 2023-24|All[<sup>6</sup>](#footnotes)|Stock|0 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 Subaru D 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=Subaru Outback 2023-24">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|XV 2018-19|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru XV 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Subaru|XV 2020-21|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru XV 2020-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Škoda|Enyaq 2021-23[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Enyaq 2021-23">Buy Here</a></sub></details>|||
|Škoda|Enyaq 2024-25[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Enyaq 2024-25">Buy Here</a></sub></details>|||
|Škoda|Fabia 2022-23[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Fabia 2022-23">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|Škoda|Kamiq 2021-23[<sup>11,13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Kamiq 2021-23">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|Škoda|Karoq 2019-23[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Karoq 2019-23">Buy Here</a></sub></details>|||
|Škoda|Kodiaq 2017-23[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Kodiaq 2017-23">Buy Here</a></sub></details>|||
|Škoda|Octavia 2015-19[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia 2015-19">Buy Here</a></sub></details>|||
|Škoda|Octavia RS 2016[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia RS 2016">Buy Here</a></sub></details>|||
|Škoda|Octavia Scout 2017-19[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia Scout 2017-19">Buy Here</a></sub></details>|||
|Škoda|Scala 2020-23[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Scala 2020-23">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|Škoda|Superb 2015-22[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Superb 2015-22">Buy Here</a></sub></details>|||
|Tesla[<sup>9</sup>](#footnotes)|Model 3 (with HW3) 2019-23[<sup>8</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW3) 2019-23">Buy Here</a></sub></details>|||
|Tesla[<sup>9</sup>](#footnotes)|Model 3 (with HW4) 2024-26[<sup>8</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW4) 2024-26">Buy Here</a></sub></details>|||
|Tesla[<sup>9</sup>](#footnotes)|Model Y (with HW3) 2020-23[<sup>8</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW3) 2020-23">Buy Here</a></sub></details>|||
|Tesla[<sup>9</sup>](#footnotes)|Model Y (with HW4) 2024-25[<sup>8</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW4) 2024-25">Buy Here</a></sub></details>|||
|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Ateca 2016-23">Buy Here</a></sub></details>|||
|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Leon 2014-20">Buy Here</a></sub></details>|||
|Subaru|Ascent 2019-21|All[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Ascent 2023-25|All[<sup>7</sup>](#footnotes)|Stock|0 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 Subaru D 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=Subaru Ascent 2023-25">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Crosstrek 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Crosstrek 2020-23">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Crosstrek 2025|All[<sup>7</sup>](#footnotes)|Stock|0 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 Subaru D 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=Subaru Crosstrek 2025">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Forester 2019-21|All[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Forester 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Forester 2022-24|All[<sup>7</sup>](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru C 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=Subaru Forester 2022-24">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Impreza 2017-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Impreza 2017-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Impreza 2020-22|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru Impreza 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Legacy 2020-22|All[<sup>7</sup>](#footnotes)|Stock|0 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 Subaru B 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=Subaru Legacy 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Legacy 2025|All[<sup>7</sup>](#footnotes)|Stock|0 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 Subaru D 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=Subaru Legacy 2025">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Outback 2020-22|All[<sup>7</sup>](#footnotes)|Stock|0 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 Subaru B 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=Subaru Outback 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|Outback 2023-24|All[<sup>7</sup>](#footnotes)|Stock|0 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 Subaru D 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=Subaru Outback 2023-24">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Subaru|XV 2018-19|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru XV 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Subaru|XV 2020-21|EyeSight Driver Assistance[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Subaru 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=Subaru XV 2020-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|Škoda|Enyaq 2021-23[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Enyaq 2021-23">Buy Here</a></sub></details>|||
|Škoda|Enyaq 2024-25[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Enyaq 2024-25">Buy Here</a></sub></details>|||
|Škoda|Fabia 2022-23[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Fabia 2022-23">Buy Here</a></sub></details>[<sup>16</sup>](#footnotes)|||
|Škoda|Kamiq 2021-23[<sup>12,14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Kamiq 2021-23">Buy Here</a></sub></details>[<sup>16</sup>](#footnotes)|||
|Škoda|Karoq 2019-23[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Karoq 2019-23">Buy Here</a></sub></details>|||
|Škoda|Kodiaq 2017-23[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Kodiaq 2017-23">Buy Here</a></sub></details>|||
|Škoda|Octavia 2015-19[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia 2015-19">Buy Here</a></sub></details>|||
|Škoda|Octavia RS 2016[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia RS 2016">Buy Here</a></sub></details>|||
|Škoda|Octavia Scout 2017-19[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia Scout 2017-19">Buy Here</a></sub></details>|||
|Škoda|Scala 2020-23[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Scala 2020-23">Buy Here</a></sub></details>[<sup>16</sup>](#footnotes)|||
|Škoda|Superb 2015-22[<sup>14</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Superb 2015-22">Buy Here</a></sub></details>|||
|Tesla[<sup>10</sup>](#footnotes)|Model 3 (with HW3) 2019-23[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW3) 2019-23">Buy Here</a></sub></details>|||
|Tesla[<sup>10</sup>](#footnotes)|Model 3 (with HW4) 2024-26[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW4) 2024-26">Buy Here</a></sub></details>|||
|Tesla[<sup>10</sup>](#footnotes)|Model Y (with HW3) 2020-23[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla A connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW3) 2020-23">Buy Here</a></sub></details>|||
|Tesla[<sup>10</sup>](#footnotes)|Model Y (with HW4) 2024-25[<sup>9</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW4) 2024-25">Buy Here</a></sub></details>|||
|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.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 Alphard 2019-20">Buy Here</a></sub></details>|||
|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.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 Alphard Hybrid 2021">Buy Here</a></sub></details>|||
|Toyota|Avalon 2016|Toyota Safety Sense P|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 Avalon 2016">Buy Here</a></sub></details>|||
@@ -385,8 +386,8 @@ A supported vehicle is one that just works when you install a comma device. All
|Toyota|C-HR 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 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 C-HR 2021">Buy Here</a></sub></details>|||
|Toyota|C-HR Hybrid 2017-20|All|Stock|0 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 C-HR Hybrid 2017-20">Buy Here</a></sub></details>|||
|Toyota|C-HR Hybrid 2021-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 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 C-HR Hybrid 2021-22">Buy Here</a></sub></details>|||
|Toyota|Camry 2018-20|All|Stock|0 mph[<sup>10</sup>](#footnotes)|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 Camry 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=fkcjviZY9CM" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Toyota|Camry 2021-24|All|openpilot|0 mph[<sup>10</sup>](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.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 Camry 2021-24">Buy Here</a></sub></details>|||
|Toyota|Camry 2018-20|All|Stock|0 mph[<sup>11</sup>](#footnotes)|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 Camry 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=fkcjviZY9CM" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Toyota|Camry 2021-24|All|openpilot|0 mph[<sup>11</sup>](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.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 Camry 2021-24">Buy Here</a></sub></details>|||
|Toyota|Camry Hybrid 2018-20|All|Stock|0 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 Camry Hybrid 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=Q2DYY0AWKgk" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.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 Camry Hybrid 2021-24">Buy Here</a></sub></details>|||
|Toyota|Corolla 2017-19|All|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 Corolla 2017-19">Buy Here</a></sub></details>|||
@@ -418,53 +419,53 @@ A supported vehicle is one that just works when you install a comma device. All
|Toyota|RAV4 Hybrid 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 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 RAV4 Hybrid 2022">Buy Here</a></sub></details>|<a href="https://youtu.be/U0nH9cnrFB0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 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 RAV4 Hybrid 2023-25">Buy Here</a></sub></details>|<a href="https://youtu.be/4eIsEq4L4Ng" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Toyota|Sienna 2018-20|All|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 Sienna 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=q1UPOo4Sh68" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon eHybrid 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon R 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon Shooting Brake 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Atlas 2018-23">Buy Here</a></sub></details>|||
|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Atlas Cross Sport 2020-22">Buy Here</a></sub></details>|||
|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen California 2021-23">Buy Here</a></sub></details>|||
|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Caravelle 2020">Buy Here</a></sub></details>|||
|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen CC 2018-22">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Crafter 2017-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen e-Crafter 2018-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen e-Golf 2014-20">Buy Here</a></sub></details>|||
|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 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 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf 2015-20">Buy Here</a></sub></details>|||
|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 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 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf Alltrack 2015-19">Buy Here</a></sub></details>|||
|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf GTD 2015-20">Buy Here</a></sub></details>|||
|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf GTE 2015-20">Buy Here</a></sub></details>|||
|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 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 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf GTI 2015-21">Buy Here</a></sub></details>|||
|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf R 2015-19">Buy Here</a></sub></details>|||
|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf SportsVan 2015-20">Buy Here</a></sub></details>|||
|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Grand California 2019-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|ID.3 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.3 2020-23">Buy Here</a></sub></details>|||
|Volkswagen|ID.3 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.3 2024-25">Buy Here</a></sub></details>|||
|Volkswagen|ID.4 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.4 2021-23">Buy Here</a></sub></details>|||
|Volkswagen|ID.4 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.4 2024-25">Buy Here</a></sub></details>|||
|Volkswagen|ID.5 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.5 2022-23">Buy Here</a></sub></details>|||
|Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Jetta 2018-23">Buy Here</a></sub></details>|||
|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Jetta GLI 2021-23">Buy Here</a></sub></details>|||
|Volkswagen|Passat 2015-22[<sup>12</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat 2015-22">Buy Here</a></sub></details>|||
|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat Alltrack 2015-22">Buy Here</a></sub></details>|||
|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat GTE 2015-22">Buy Here</a></sub></details>|||
|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Polo 2018-23">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Polo GTI 2018-23">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen T-Cross 2021">Buy Here</a></sub></details>[<sup>15</sup>](#footnotes)|||
|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen T-Roc 2018-23">Buy Here</a></sub></details>|||
|Volkswagen|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Taos 2022-24">Buy Here</a></sub></details>|||
|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Teramont 2018-22">Buy Here</a></sub></details>|||
|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Teramont Cross Sport 2021-22">Buy Here</a></sub></details>|||
|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Teramont X 2021-22">Buy Here</a></sub></details>|||
|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Tiguan 2018-24">Buy Here</a></sub></details>|||
|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Tiguan eHybrid 2021-23">Buy Here</a></sub></details>|||
|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,14</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Touran 2016-23">Buy Here</a></sub></details>|||
|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon eHybrid 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon R 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon Shooting Brake 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Atlas 2018-23">Buy Here</a></sub></details>|||
|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Atlas Cross Sport 2020-22">Buy Here</a></sub></details>|||
|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen California 2021-23">Buy Here</a></sub></details>|||
|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Caravelle 2020">Buy Here</a></sub></details>|||
|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen CC 2018-22">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Crafter 2017-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen e-Crafter 2018-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen e-Golf 2014-20">Buy Here</a></sub></details>|||
|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 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 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf 2015-20">Buy Here</a></sub></details>|||
|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 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 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf Alltrack 2015-19">Buy Here</a></sub></details>|||
|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf GTD 2015-20">Buy Here</a></sub></details>|||
|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf GTE 2015-20">Buy Here</a></sub></details>|||
|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 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 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf GTI 2015-21">Buy Here</a></sub></details>|||
|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf R 2015-19">Buy Here</a></sub></details>|||
|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Golf SportsVan 2015-20">Buy Here</a></sub></details>|||
|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Grand California 2019-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Volkswagen|ID.3 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.3 2020-23">Buy Here</a></sub></details>|||
|Volkswagen|ID.3 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.3 2024-25">Buy Here</a></sub></details>|||
|Volkswagen|ID.4 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.4 2021-23">Buy Here</a></sub></details>|||
|Volkswagen|ID.4 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.4 2024-25">Buy Here</a></sub></details>|||
|Volkswagen|ID.5 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen ID.5 2022-23">Buy Here</a></sub></details>|||
|Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Jetta 2018-23">Buy Here</a></sub></details>|||
|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Jetta GLI 2021-23">Buy Here</a></sub></details>|||
|Volkswagen|Passat 2015-22[<sup>13</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat 2015-22">Buy Here</a></sub></details>|||
|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat Alltrack 2015-22">Buy Here</a></sub></details>|||
|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Passat GTE 2015-22">Buy Here</a></sub></details>|||
|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Polo 2018-23">Buy Here</a></sub></details>[<sup>16</sup>](#footnotes)|||
|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Polo GTI 2018-23">Buy Here</a></sub></details>[<sup>16</sup>](#footnotes)|||
|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen T-Cross 2021">Buy Here</a></sub></details>[<sup>16</sup>](#footnotes)|||
|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen T-Roc 2018-23">Buy Here</a></sub></details>|||
|Volkswagen|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Taos 2022-24">Buy Here</a></sub></details>|||
|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Teramont 2018-22">Buy Here</a></sub></details>|||
|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Teramont Cross Sport 2021-22">Buy Here</a></sub></details>|||
|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Teramont X 2021-22">Buy Here</a></sub></details>|||
|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Tiguan 2018-24">Buy Here</a></sub></details>|||
|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Tiguan eHybrid 2021-23">Buy Here</a></sub></details>|||
|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,15</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma four<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Touran 2016-23">Buy Here</a></sub></details>|||
## StarPilot Community Cars
These additional vehicle ports are maintained by StarPilot rather than upstream openpilot.
# 15 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|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@@ -477,9 +478,11 @@ 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|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|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>|||
|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|Prius 2016-20 with TSS2 EPS retrofit|Custom retrofit|Stock|0 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 Prius 2016-20 with TSS2 EPS retrofit">Buy Here</a></sub></details>|||
|Toyota|RAV4 Prime 2021-23|All|openpilot|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 RAV4 Prime 2021-23">Buy Here</a></sub></details>|||
|Toyota|Sienna 2021-25|All|openpilot|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 Sienna 2021-25">Buy Here</a></sub></details>|||
|Toyota|Yaris (Non-US only) 2020, 2023|All|openpilot available[<sup>1</sup>](#footnotes)|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 Yaris (Non-US only) 2020, 2023">Buy Here</a></sub></details>|||
@@ -489,17 +492,18 @@ These additional vehicle ports are maintained by StarPilot rather than upstream
<sup>2</sup>Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia. <br />
<sup>3</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/gm" target="_blank">GM</a>. <br />
<sup>4</sup>2019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph. <br />
<sup>5</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/nissan" target="_blank">Nissan</a>. <br />
<sup>6</sup>In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance. <br />
<sup>7</sup>Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB. <br />
<sup>8</sup>Some 2023 model years have HW4. To check which hardware type your vehicle has, look for <b>Autopilot computer</b> under <b>Software -> Additional Vehicle Information</b> on your vehicle's touchscreen. See <a href="https://www.notateslaapp.com/news/2173/how-to-check-if-your-tesla-has-hardware-4-ai4-or-hardware-3">this page</a> for more information. <br />
<sup>9</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/tesla" target="_blank">Tesla</a>. <br />
<sup>10</sup>openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control. <br />
<sup>11</sup>Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform. <br />
<sup>12</sup>Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets. <br />
<sup>13</sup>Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma four functionality. <br />
<sup>14</sup>Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC. <br />
<sup>15</sup>Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store. <br />
<sup>5</sup>Enabling longitudinal control (alpha) will disable all CMBS functionality, including AEB and FCW. <br />
<sup>6</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/nissan" target="_blank">Nissan</a>. <br />
<sup>7</sup>In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance. <br />
<sup>8</sup>Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB. <br />
<sup>9</sup>Some 2023 model years have HW4. To check which hardware type your vehicle has, look for <b>Autopilot computer</b> under <b>Software -> Additional Vehicle Information</b> on your vehicle's touchscreen. See <a href="https://www.notateslaapp.com/news/2173/how-to-check-if-your-tesla-has-hardware-4-ai4-or-hardware-3">this page</a> for more information. <br />
<sup>10</sup>See more setup details for <a href="https://github.com/commaai/openpilot/wiki/tesla" target="_blank">Tesla</a>. <br />
<sup>11</sup>openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control. <br />
<sup>12</sup>Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform. <br />
<sup>13</sup>Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets. <br />
<sup>14</sup>Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma four functionality. <br />
<sup>15</sup>Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC. <br />
<sup>16</sup>Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store. <br />
# Don't see your car here?
@@ -542,4 +546,4 @@ openpilot does not yet support these Toyota models due to a new message authenti
* Toyota Camry 2025+
* Lexus NX 2022+
* Toyota bZ4x 2023+
* Subaru Solterra 2023+
* Subaru Solterra 2023+
+86
View File
@@ -0,0 +1,86 @@
# Fleet offline audit — September 21, 2026
This is a first-pass fault and coverage inventory, not fleet driving clearance.
No hardware was accessed and no controller or safety policy was changed by this
audit. Other work was concurrently modifying the checkout; per-case source and
library hashes identify the tested implementations.
The full debug-library run visited all 345 platforms. It evaluated both recorded
and AOL-main scenarios for 287 registered routes, plus 108 missing-route entries:
| Result | Cases |
|---|---:|
| Pass within the stated scope | 103 |
| Failed checks, requiring triage | 123 |
| Missing coverage | 288 |
| Could not evaluate | 168 |
| Total | 682 |
These are case counts, not numbers of unsafe cars. Missing coverage includes all
108 platforms without routes and segments without active transitions. Evaluation
errors include unavailable recordings and identities that do not match the
registered platform after the existing fingerprint migration. Old logs must be
normalized explicitly, not quietly substituted for another car.
Full local evidence is under
`selfdrive/car/tests/fleet_results/full_fleet/results.json`, with per-shard build,
case and worker logs. Generated evidence is ignored by Git.
The follow-up full release-library run also completed 682 cases: **102 pass,
153 failed, 289 uncovered, 138 evaluation errors**. Evidence is under
`selfdrive/car/tests/fleet_results/release_fleet_checked/results.json`.
The two batches had different download availability and ran against a changing
working tree; their count difference is not an isolated debug-versus-release
experiment. Both correctly exit nonzero. The release batch is not fleet clearance.
## Confirmed distinctions from failure triage
**Hyundai Custin — controller/safety capability mismatch.** The registered
segment `0bbe367c98fa1538/2023-09-16--00-16-49/2` contains 600 camera-bus
messages at 0x53e, all eight bytes, none six. `CarInterfaceBase.get_starpilot_params`
in `opendbc_repo/opendbc/car/interfaces.py` enables HAS_LKAS12 by address alone.
Hyundai `CarState.update` and `CarController.update` then produce six-byte LKAS12
replacements. In `opendbc_repo/opendbc/safety/modes/hyundai.h`,
`hyundai_rx_all_hook` only enables replacement after receiving a six-byte camera
message, and `hyundai_tx_hook` correctly rejects the unsolicited replacement.
Both scenarios reject 5,799 such packets. A fresh-library probe also reproduced
the six-byte/eight-byte distinction. Repair requires a controller capability and
parser regression test; do not broaden the safety allowlist to hide the mismatch.
**Honda Civic Bosch — incompatible historical control requests.** All 5,997
recorded requests in the inspected 2020 fixture have enabled/latActive/longActive
false but resume true; all 6,000 cruise-state CAN messages are disabled. Current
controller output is RES_ACCEL at 0x296, which current safety correctly blocks.
The AOL probe suppresses resume and passes. Investigate historical command/schema
semantics before calling this a current steering defect.
**Ford Escape — mid-segment initialization artifact.** The first cruise-enabled
0x165 enables controls, but the immediately following 0x202 reaches
`speed_mismatch_check` before safety has nonzero speed history. Controls are
revoked; cruise stays enabled for the entire segment so `pcm_cruise_check` sees
no new rising edge. Relay health remains good. This reproduces with both recorded
and default alternative experience. A two-second scoring warmup does not repair
the latch. This needs recorded preroll/initialization coverage, not force-setting
`controls_allowed` or changing vehicle safety.
## Tesla and AOL scope
In the full debug-library run, the Model 3 route and the second Model Y route
passed both scenarios. The first Model Y route lacked a lateral transition;
its AOL case also lacked requested steering under AOL-only safety permission.
Model X was uncovered as a current dashcam-only configuration. The Model S HW1
and Pre-AP entries have no registered routes. None of these findings reproduces
or disproves the exact hackathon oscillation without its trace.
The separate actual StarPilotCard synthetic-input suite passed 964 checks with
72 explicit active-sequence gaps. All 345 disabled configurations were checked;
309 platforms completed both active modes. These tests check state-machine gates
and stable sequences, not the entire selfdrived-to-Panda feedback loop.
The test-harness regressions pass 34 tests. They cover pre-hook AOL authorization,
strict configuration/bus routing, rejected active packets, expected negative
checks, empty activity, worker crashes, stale reports and build failures.
See `FLEET_SAFETY_TESTING.md` for commands, CI scope and limitations. The workflow
has been added locally but not published or run on GitHub, and branch protection
has not been changed. The full fleet is not green.
+112
View File
@@ -0,0 +1,112 @@
# Offline fleet controller and safety checks
The runner in `selfdrive/car/tests/fleet_safety.py` enumerates every platform in
the current checkout and uses `opendbc.car.tests.routes`. It runs current
CarInterface/CarState/CarController code against recorded CAN and actuator
requests, then checks each newly generated CAN packet with freshly compiled
current safety hooks. It never connects to a Panda or starts vehicle processes.
Run from the repository root with the repository Python environment:
```sh
python -m selfdrive.car.tests.fleet_safety --inventory
python -m selfdrive.car.tests.fleet_safety --platform TESLA_MODEL_Y --release
python -m selfdrive.car.tests.fleet_safety --all --release
```
An isolated dependency set is in `selfdrive/car/tests/fleet_requirements.txt`.
The runner needs a C compiler. It does not use a previously staged libsafety.
Without `--release`, the safety library enables ALLOW_DEBUG, like the existing
safety unit tests. Release checks are needed as well: a debug-only hook must not
be mistaken for an available production configuration. Release host builds retain
unused-variable warnings without treating that specific diagnostic as an error.
For parallel runs, use separate output directories:
```sh
python -m selfdrive.car.tests.fleet_safety --all --release \
--shard-count 8 --shard-index 0 --out selfdrive/car/tests/fleet_results/shard_0
```
Run indices 0 through 7. Each has its own library copies, worker processes,
parameter namespace, logs and results. `--local-log` allows a local rlog for one
explicitly selected platform. Route IDs and old fingerprint aliases must match;
the harness does not silently treat another vehicle's log as that platform.
## What is checked
- Recorded commands under the current default feature configuration.
- A separate AOL MAIN-availability controller/safety probe, using recorded
actuator values with longitudinal requests and cruise button requests off.
- Actual per-Panda safety model, CP/FPCP safety-param OR, alternative-experience
OR, and strict four-bus routing, matching production configuration assembly.
- Incoming CAN, current CarState validity and safety receive health.
- Every emitted TX, including inactive-state packets; unexpected rejection fails.
- Pre-hook normal/AOL/longitudinal permissions, so a rejection that revokes
authorization cannot disappear from the failure accounting.
- Active requests, accepted active TX, engagement transitions, and AOL-only
safety authorization coverage. Sparse commands and absent transitions cannot
qualify as complete coverage.
There is a two-second unscored fixture startup interval. Controller and safety
history still receive messages during it. The harness does not force safety
authorization or clear a relay fault to manufacture a passing result.
## Results are deliberately strict
`pass` means the case satisfied these specific checks and coverage requirements.
`failed` means a hook/health check failed and needs investigation. `uncovered`
means the scenario was not demonstrated, including missing routes, dashcam-only
interfaces and segments without transitions. `error` means the case could not be
evaluated, such as download failure or mismatched fixture identity. Anything
other than pass makes the command exit nonzero. Existing `non_tested_cars`
exemptions remain visible coverage gaps.
Reports include frame counters, bounded rejected packet evidence, source hashes,
effective safety configurations and build provenance. Worker results carry a
unique execution ID; a crash, stale result or inconsistent exit code cannot be
reused as a pass. JSON and logs live under the ignored `fleet_results` directory.
The AOL probe is **not** a complete simulation of StarPilotCard, selfdrived,
controls mismatch handling or a vehicle ECU. Recorded commands may also reflect
historical settings different from current defaults. A blocked historical resume
request is not automatically a steering bug. Investigate each failure before
changing code. Do not widen safety permissions to make tests green.
## Continuous integration and remaining coverage
`starpilot/controls/tests/test_fleet_aol.py` separately exercises the actual
StarPilotCard state machine with isolated synthetic inputs for every platform.
It tests feature-off behavior, steady engagement, AOL-only operation, brake
pause, native/StarPilot immediate-disable alerts, calibration and gear gates.
It uses empty firmware/fingerprint fixtures, so optional vehicle configurations
are not covered by these sequences. Run it with a compatible built host runtime:
```sh
python -m pytest --noconftest -o addopts='' -q starpilot/controls/tests/test_fleet_aol.py
```
The first run passed 964 checks and explicitly skipped 72 active sequences:
309 platforms exercised both active modes; 36 platforms had two gaps each
(30 dashcam-only, one notCar, four Volvo policy exclusions, one Pre-AP external
authorization dependency). All 345 feature-off checks passed. A separate
Pre-AP authorization input boundary test is synthetic, not proof of actual
Panda authorization. These tests were run against the current working tree,
including concurrent Pre-AP changes; they do not certify an earlier commit.
The lightweight CI workflow below does not build the native runtime required
by this separate state-machine suite.
`.github/workflows/fleet_safety.yaml` adds harness tests and eight release-mode
recorded-route shards on relevant pull requests and manual runs. Missing coverage
is not converted to a skip or allowed failure. The current fleet is not green;
this workflow will expose that fact. It has not been executed on GitHub from this
local task. Requiring it for merge also needs repository branch protection; a
workflow file alone does not change repository settings.
As of the first September 21 inventory there are 345 platforms, 287 registered
routes across 237 platforms, and 108 platforms with no registered route. A route
entry does not guarantee valid, downloadable logs or all necessary maneuvers.
Every optional harness, longitudinal mode, safety parameter, firmware generation
and AOL configuration still needs explicit coverage. Offline checks reduce
blind spots; they do not certify every physical vehicle or reproduce an incident
whose CAN trace was not retained.
+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`.
- 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.
## Release Contract
## 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
/Volumes/T5/StarPilot-Model-Rebuild-2026-06-22/
hf://buckets/StarPilot-Driving/StarPilot-Resources/onnx/<source-id>/
```
Important directories:
- `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
Stage one model at a time in `/data/openpilot/uncompiledmodels`; this avoids
filling the comma and prevents `./models` from selecting stale input files.
```bash
python3 scripts/model_rebuild_pipeline.py init
python3 scripts/model_rebuild_pipeline.py extract \
--base-manifest /path/to/model_names_v21.json
./models --<model-id> --version <behavior-version>
./models --<gpu-model-id> --version v16 --gpu
```
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.
To retry one source:
The default input is a single supercombo ONNX. For legacy sources use:
```bash
python3 scripts/model_rebuild_pipeline.py extract \
--model pop22 \
--base-manifest /path/to/model_names_v21.json
./models --<model-id> --input-format split --version <behavior-version>
```
The original catalog sources are defined in `scripts/model_source_map_v22.json`.
Recovered late-model and supercombo sources, including RDF2, are defined in
`scripts/model_source_map_v23.json`. The v23 map is intentionally separate so
adding a recovered iteration cannot alter the older model source history.
Every non-local release build emits an OOB artifact as native chunks and removes
the temporary full PKL. `./models --local-<id>` intentionally keeps one OOB PKL
for local use.
## Compile
Compile one model:
The resumable bulk helper is:
```bash
STAR_PILOT_MODEL_REMOTE=comma@192.168.3.110 \
python3 scripts/model_rebuild_pipeline.py compile \
--model pop22 \
--base-manifest /path/to/model_names_v21.json
--workspace /Volumes/T5/StarPilot-Model-Rebuild \
--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
python3 scripts/model_rebuild_pipeline.py compile \
--base-manifest /path/to/model_names_v21.json
```
## Driver Monitoring And Default
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.
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:
Driver monitoring is built once per tinygrad generation:
```bash
./models --dm \
@@ -148,61 +130,43 @@ Stage the current DM ONNX in `uncompiledmodels`, then run:
--output-dir /tmp/dm_artifacts
```
This builds:
Replace these four files together:
- `dmonitoring_model_tinygrad.pkl`
- `dmonitoring_model_metadata.pkl`
- `dm_warp_1928x1208_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
python3 scripts/model_rebuild_pipeline.py manifest \
--base-manifest /path/to/model_names_v21.json
./dev sync
./.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
python3 scripts/namespace_model_artifacts.py \
--workspace /Volumes/T5/StarPilot-Model-Rebuild-2026-06-22 \
--base-manifest /Volumes/T5/StarPilot-Model-Rebuild-2026-06-22/manifests/model_names_v22.json \
--manifest-version v23 --suffix 3
```
For representative v8, v11, v12, v15, v16, and GPU artifacts, validate both
camera resolutions on real QCOM and require finite plan, lane-line, road-edge,
lead, pose, and action outputs. Then start `modeld` and confirm stable
`modelV2` publication. Validate DM `driverStateV2` at both resolutions.
The namespace command changes IDs such as `tr1422` to `tr14223`, renames the
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.
## Device Migration
After importing newly compiled sources, normalize the release namespace before
copying files into either resource repository:
When `ModelManifestVersion` changes, the model manager retains the selected
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
python3 scripts/reconcile_v23_artifacts.py \
--workspace /Volumes/T5/StarPilot-Model-Rebuild-2026-06-22
```
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.
Test this explicitly before release by starting with a v24 selected model and
checking that no v24 driving artifact remains under `/data/models` after the
v25 manifest is applied.
+80
View File
@@ -0,0 +1,80 @@
# Custom personality graphs
Each personality keeps its own Custom acceleration, braking and following curve.
Selecting a named preset changes the active selection without deleting Custom
points. Selecting Custom again restores those points, including after a reload
or restart. If a category has never had Custom points, it is initialized from
the current selection, as before.
The existing **Reset to default** button, below each Custom graph's numeric
points in New Galaxy's Advanced section, replaces only that category's Custom
curve. It leaves the category set to Custom. The server resolves the reset
values; the dashed **Dom default** line uses the same resolver.
Defaults are Dom's configured base curves sampled at the editor's 10 mph
points. They include Traffic's dedicated acceleration and braking, following
settings, global tuning switches and powertrain overrides. Where gear mapping
is enabled, the reference uses normal gear. Live Eco/Sport gear, weather,
lead/stop and overspeed adjustments remain on the existing controller paths.
Sampling cannot reproduce every native breakpoint or between-point value;
resetting a Custom graph is not the same as delegating to the Dom-default
runtime path.
Dom-default points outside the ordinary editor range (such as Traffic braking
at 0.35 m/s², configured Traffic following at 0.5 seconds or truck acceleration
at 6 m/s²) remain visible and are preserved when another point is edited.
New point edits still use the existing authoring bounds. This does not expand
braking authority or change acceleration/braking preset definitions.
## Following presets
Named following presets now match Dom's factory following settings with custom
personalities enabled. Close follows Aggressive, Medium follows Standard and
Far follows Relaxed. The presets are available in every personality.
| Preset | Previous curve | Revised curve |
| --- | --- | --- |
| Close | 1.25 s at every speed | 1.25 s through 45 mph, falling to 1.0 s at 70 mph |
| Medium | 1.45 s at every speed | 1.45 s through 45 mph, falling to 1.2 s at 70 mph |
| Far | 1.75 s at every speed | 1.6 s through 45 mph, falling to 1.4 s at 70 mph |
| Traffic | No named preset | 0.75 s at rest, rising to 1.6 s at 25 m/s (55.92 mph) |
Interpolation is linear between the stated breakpoints and constant outside
them. Named presets use the exact native speed axes at runtime. First-use
Custom conversion samples them onto the existing 10 mph editor grid.
Existing v1/v2 Close, Medium and Far selections keep their old fixed headways
as `legacy_close`, `legacy_medium` and `legacy_far`. Both Galaxy pickers show
the selected compatibility entry as **Previous Close**, **Previous Medium**
or **Previous Far**. Explicitly selecting a current preset adopts its new curve.
The previous entry disappears when it is no longer selected.
Existing `dom_default` selections continue to inherit configured settings;
they are not silently converted to fixed named presets. Fresh profiles also
retain this inheritance. The named curves match untouched factory settings;
users' changed global following values can still differ from them.
Acceleration and braking presets are unchanged. Standard acceleration and Eco
braking match the normal factory defaults for Aggressive, Standard and Relaxed
when named-preset and global powertrain tuning agree. Named presets use detected
EV/truck tuning; the Dom-default resolver respects the global tuning switches,
so these can differ. Traffic retains its dedicated acceleration/braking defaults;
this change adds only its named following preset.
## Storage compatibility
Profile document version 3 retains `curve` and optional `legacyCurve` while
`preset` is a named preset or `dom_default`. These retained values are dormant;
only Custom uses them. An actual graph edit or reset retires preserved v1
interpolation for that category; a preset switch or unchanged submission does
not.
Valid v2 documents retain their runtime meaning and are upgraded on the next
normal write, including the fixed following compatibility names above.
Version 1 keeps its existing explicit, verified migration flow. Reads never
rewrite Params. Category conflict detection, off-road checks and atomic profile
document writes still apply to edits and resets.
Older builds do not understand v3 documents. Retain a compatible settings
backup before rolling back to one of those builds. Curves discarded before
this change cannot be recovered automatically.
+3 -94
View File
@@ -147,101 +147,10 @@ function launch {
while true; do sleep 1; done
fi
function prebuilt_runtime_compatible {
python3 - <<'PY'
import importlib
import os
from pathlib import Path
import sys
import time
from openpilot.common.file_chunker import get_existing_chunks
start = time.monotonic()
last = start
log_path = os.environ.get("SP_BOOT_TIMING_LOG")
def emit(line):
print(line, flush=True)
if log_path:
try:
with open(log_path, "a") as f:
f.write(line + "\n")
except OSError:
pass
def log_step(label):
global last
now = time.monotonic()
emit(f"SP_BOOT_TIMING prebuilt_compat {label} +{now - last:.3f}s total={now - start:.3f}s")
last = now
mods = [
"openpilot.common.params_pyx",
"msgq.ipc_pyx",
"msgq.visionipc.visionipc_pyx",
"openpilot.common.transformations.transformations",
"openpilot.selfdrive.pandad.pandad_api_impl",
"openpilot.selfdrive.controls.lib.lateral_mpc_lib.c_generated_code.acados_ocp_solver_pyx",
"openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.c_generated_code.acados_ocp_solver_pyx",
]
for mod in mods:
try:
importlib.import_module(mod)
except Exception as e:
print(f"Prebuilt compatibility failure in {mod}: {e}", file=sys.stderr)
raise
log_step(f"import:{mod}")
repo_root = Path.cwd().parents[1]
required_model_artifacts = [
repo_root / "selfdrive/modeld/models/driving_tinygrad.pkl",
repo_root / "selfdrive/modeld/models/dmonitoring_model_metadata.pkl",
repo_root / "selfdrive/modeld/models/dmonitoring_model_tinygrad.pkl",
repo_root / "selfdrive/modeld/models/dm_warp_1928x1208_tinygrad.pkl",
repo_root / "selfdrive/modeld/models/dm_warp_1344x760_tinygrad.pkl",
]
required_files = [
repo_root / "selfdrive/pandad/pandad_api_impl.so",
repo_root / "selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so",
repo_root / "selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/libacados_ocp_solver_lat.so",
repo_root / "selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so",
repo_root / "selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.so",
repo_root / "opendbc_repo/opendbc/dbc/gm_global_a_powertrain_generated.dbc",
]
for path in required_model_artifacts:
try:
artifact_paths = [Path(p) for p in get_existing_chunks(path)]
except Exception as e:
raise FileNotFoundError(f"Missing prebuilt runtime artifact: {path}") from e
missing_chunks = [p for p in artifact_paths if not p.is_file()]
if missing_chunks:
missing = ", ".join(str(p) for p in missing_chunks)
raise FileNotFoundError(f"Missing prebuilt runtime artifact chunks for {path}: {missing}")
log_step("required_model_artifacts")
for path in required_files:
if not path.is_file():
raise FileNotFoundError(f"Missing prebuilt runtime artifact: {path}")
log_step("required_files")
PY
}
USE_PREBUILT=1
if [ -f /data/params/d/UsePrebuilt ]; then
USE_PREBUILT=$(tr -d '\n' < /data/params/d/UsePrebuilt)
fi
sp_launch_timing "prebuilt_decision_done"
if [ "$USE_PREBUILT" = "1" ] && [ -f $DIR/prebuilt ] && ! prebuilt_runtime_compatible; then
echo "Prebuilt runtime artifacts are incompatible on this device; rebuilding locally."
USE_PREBUILT=0
fi
sp_launch_timing "prebuilt_compat_done"
if [ "$USE_PREBUILT" != "1" ] || [ ! -f $DIR/prebuilt ]; then
# Published trees carry this marker and must never compile on-device.
# Developers can remove it explicitly when working from a source tree.
if [ ! -f "$DIR/prebuilt" ]; then
sp_launch_timing "build_start"
./build.py
sp_launch_timing "build_done"
+2 -2
View File
@@ -21,11 +21,11 @@ fi
export QCOM_PRIORITY=12
if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="19.6.2"
export AGNOS_VERSION="19.8.1"
fi
if [ -z "$AGNOS_ACCEPTED_VERSIONS" ]; then
export AGNOS_ACCEPTED_VERSIONS="$AGNOS_VERSION"
export AGNOS_ACCEPTED_VERSIONS="19.8.1 19.8.2"
fi
export STAGING_ROOT="/data/safe_staging"
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" "$@"
Binary file not shown.
Binary file not shown.
+1
View File
@@ -1,3 +1,4 @@
include opendbc/car/car.capnp
include opendbc/car/include/c++.capnp
include opendbc/dbc/hyundai_kia_ray_pedal.dbc
recursive-include opendbc/safety *.h
+2 -2
View File
@@ -85,7 +85,7 @@
|Chevrolet|Volt 2019|Adaptive Cruise Control (ACC) & LKAS|[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 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 2019-20|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|Chrysler|Pacifica 2021-23|All|[Upstream](#upstream)|
@@ -578,4 +578,4 @@ Toyota, and the GM Global B platform.
All the cars that openpilot supports use a [CAN bus](https://en.wikipedia.org/wiki/CAN_bus) for communication between all the car's computers, however a
CAN bus isn't the only way that the computers in your car can communicate. Most, if not all, vehicles from the following
manufacturers use [FlexRay](https://en.wikipedia.org/wiki/FlexRay) instead of a CAN bus: **BMW, Mercedes, Audi, Land Rover, and some Volvo**. These cars
may one day be supported, but we have no immediate plans to support FlexRay.
may one day be supported, but we have no immediate plans to support FlexRay.
+4 -2
View File
@@ -13,7 +13,7 @@ from opendbc.car.subaru.subarucan import subaru_checksum
from opendbc.car.chrysler.chryslercan import chrysler_checksum, fca_giorgio_checksum
from opendbc.car.hyundai.hyundaicanfd import hkg_can_fd_checksum
from opendbc.car.volkswagen.mlbcan import volkswagen_mlb_checksum
from opendbc.car.volkswagen.mqbcan import volkswagen_mqb_meb_checksum, xor_checksum
from opendbc.car.volkswagen.mqbcan import volkswagen_meb_alt_crc_checksum, volkswagen_mqb_meb_checksum, xor_checksum
from opendbc.car.tesla.teslacan import tesla_checksum
from opendbc.car.body.bodycan import body_checksum
from opendbc.car.psa.psacan import psa_checksum
@@ -194,8 +194,10 @@ def get_checksum_state(dbc_name: str) -> ChecksumState | None:
return ChecksumState(4, 2, 3, 5, False, SignalType.HONDA_CHECKSUM, honda_checksum)
elif dbc_name.startswith(("toyota_", "lexus_")):
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)
elif dbc_name.startswith("vw_meb_2024"):
return ChecksumState(8, 4, 0, 0, True, SignalType.VOLKSWAGEN_MQB_MEB_CHECKSUM, volkswagen_meb_alt_crc_checksum)
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)
elif dbc_name.startswith("vw_mlb"):
+5
View File
@@ -132,6 +132,7 @@ class CANParser:
self.dbc: DBC = DBC(dbc_name)
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.ts_nanos: dict[int | str, dict[str, int]] = {}
self.addresses: set[int] = set()
@@ -166,6 +167,8 @@ class CANParser:
signals_dict = {s: 0.0 for s in signal_names}
dict.__setitem__(self.vl, msg.address, 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.name] = self.vl_all[msg.address]
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_all_addr[sig.name] = state.all_vals[i]
ts_addr[sig.name] = state.timestamps[-1]
self.vl_raw[address] = bytes(dat)
self.vl_raw[state.name] = bytes(dat)
if not bus_empty:
self.last_nonempty_nanos = t
@@ -1,5 +1,6 @@
import copy
from opendbc.can import CANPacker, CANParser
from opendbc.car.honda.hondacan import honda_checksum
class TestCanChecksums:
@@ -90,6 +91,13 @@ class TestCanChecksums:
assert parser.vl['LKAS_HUD']['CHECKSUM'] == std
assert parser.vl['LKAS_HUD_A']['CHECKSUM'] == ext
def test_honda_canfd_checksum_offset_is_scoped_to_mvl_messages(self):
# Existing high-ID Honda messages retain the legacy extended-ID offset;
# only the Accord MVL replacement-radar IDs use the CAN-FD offset.
payload = bytearray(8)
assert honda_checksum(0xF31AA54, None, payload) == 11 # existing radarless LKAS_HUD_2
assert honda_checksum(0xF31AA52, None, payload) == 4 # Accord MVL RADAR_LEAD2
def verify_volkswagen_mqb_crc(self, subtests, msg_name: str, msg_addr: int, test_messages: list[bytes], counter_field: str = 'COUNTER'):
"""Test AUTOSAR E2E Profile 2 CRCs"""
assert len(test_messages) == 16 # All counter values must be tested
+1
View File
@@ -90,6 +90,7 @@ class Bus(StrEnum):
main = auto()
party = auto()
ap_party = auto()
ap_pt = auto()
def rate_limit(new_value, last_value, dw_step, up_step):
+2
View File
@@ -644,6 +644,8 @@ struct CarParams {
fcaGiorgio @32;
rivian @33;
volkswagenMeb @34;
teslaPreAP @35;
volvo @36;
}
enum SteerControlType {
+61 -4
View File
@@ -10,6 +10,7 @@ from opendbc.car.carlog import carlog
from opendbc.car.structs import CarParams, CarParamsT
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.hyundai.values import kia_ray_ev_vin
from opendbc.car.mock.values import CAR as MOCK
from opendbc.car.toyota.values import ToyotaSafetyFlags
from opendbc.car.values import BRANDS
@@ -55,6 +56,20 @@ GM_CANDIDATE_PREFIXES = ("CHEVROLET_", "GMC_", "CADILLAC_", "BUICK_", "HOLDEN_")
GM_CORE_FINGERPRINT_MSGS = frozenset((190, 201, 209, 211, 241))
GM_CAMERA_BUS = 2
GM_VOLT_CAMERA_MSG = 0x320
GM_SUBURBAN_CAMERA_VIN_PREFIX = "1GNSKJKJ"
GM_SUBURBAN_CAMERA_PT_SIGNATURE = {
190: 6,
201: 8,
209: 7,
211: 2,
241: 6,
304: 1,
320: 3,
}
GM_CAMERA_DIAGNOSTIC_MESSAGES = {
0x24b: 8,
0x64b: 8,
}
def _normalize_forced_candidate(candidate: str | None) -> str | None:
@@ -66,7 +81,7 @@ def _normalize_forced_candidate(candidate: str | None) -> str | None:
return LEGACY_FORCED_CANDIDATE_MAP.get(candidate, candidate)
def _normalize_gm_bolt_candidate(candidate: str | None, fingerprints: dict[int, dict]) -> str | None:
def _normalize_gm_bolt_candidate(candidate: str | None, fingerprints: dict[int, dict], vin: str | None = None) -> str | None:
"""
Normalize ambiguous/mismatched Bolt candidates using robust PT message signatures.
This guards against occasional wrong variant selection causing persistent canError.
@@ -79,7 +94,22 @@ def _normalize_gm_bolt_candidate(candidate: str | None, fingerprints: dict[int,
msg_304_len = pt.get(304) # 8 on 2017 Gen1, 1 on 2018-2021 Gen1
has_pedal = 513 in pt
# The 2022-23 Bolt ACC, pedal, and CC variants share the same FPv1
# fingerprint. Recover an ambiguous match when the observed traffic
# matches the Bolt family and the VIN confirms model year 2022/2023.
bolt_2022_2023_signature = (
pt.get(190) == 7 and
msg_211_len == 3 and
pt.get(566) == 8 and
pt.get(458) == 5
)
vin_year = vin[9] if isinstance(vin, str) and len(vin) > 9 else None
# VIN model-year codes: N=2022, P=2023.
if candidate is None and bolt_2022_2023_signature and vin_year in ("N", "P"):
return "CHEVROLET_BOLT_ACC_2022_2023_PEDAL" if has_pedal else "CHEVROLET_BOLT_ACC_2022_2023"
# If detection failed entirely but the signature is clearly Bolt, recover to a sane default.
if candidate is None and 170 in pt and 188 in pt and msg_211_len in (2, 3):
if msg_211_len == 3:
return "CHEVROLET_BOLT_ACC_2022_2023_PEDAL" if has_pedal else "CHEVROLET_BOLT_ACC_2022_2023"
@@ -136,6 +166,24 @@ def _normalize_gm_volt_candidate(candidate: str | None, fingerprints: dict[int,
return candidate
def _normalize_gm_suburban_camera_candidate(candidate: str | None, fingerprints: dict[int, dict], vin: str | None) -> str | None:
"""Resolve the 2019 Suburban camera-harness variant when CAN is shared with Yukon."""
if candidate not in (None, "GMC_YUKON", "GMC_YUKON_CC"):
return candidate
if not isinstance(vin, str) or not vin.startswith(GM_SUBURBAN_CAMERA_VIN_PREFIX):
return candidate
powertrain = fingerprints.get(0, {})
camera = fingerprints.get(GM_CAMERA_BUS, {})
if not all(powertrain.get(address) == length for address, length in GM_SUBURBAN_CAMERA_PT_SIGNATURE.items()):
return candidate
if not all(camera.get(address) == length for address, length in GM_CAMERA_DIAGNOSTIC_MESSAGES.items()):
return candidate
return "CHEVROLET_SUBURBAN_CAMERA"
def _is_gm_candidate(candidate: str | None) -> bool:
return isinstance(candidate, str) and candidate.startswith(GM_CANDIDATE_PREFIXES)
@@ -231,8 +279,13 @@ def fingerprint(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_mu
set_obd_multiplexing(True)
# VIN query only reliably works through OBDII
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)
car_fw = get_fw_versions_ordered(can_recv, can_send, set_obd_multiplexing, vin, ecu_rx_addrs, num_pandas=num_pandas)
skip_fw_buses = {1} if kia_ray_ev_vin(vin) else set()
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
exact_fw_match, fw_candidates = match_fw_to_car(car_fw, vin)
@@ -279,13 +332,17 @@ def fingerprint(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_mu
def get_car(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, alpha_long_allowed: bool,
is_release: bool, params: Params, num_pandas: int = 1, cached_params: CarParamsT | None = None, starpilot_toggles: SimpleNamespace = None):
candidate, fingerprints, vin, car_fw, source, exact_match = fingerprint(can_recv, can_send, set_obd_multiplexing, num_pandas, cached_params)
candidate = _normalize_gm_bolt_candidate(candidate, fingerprints)
candidate = _normalize_gm_bolt_candidate(candidate, fingerprints, vin)
candidate = _normalize_gm_volt_candidate(candidate, fingerprints)
candidate = _normalize_forced_candidate(candidate)
fingerprinted_candidate = candidate
stored_candidate = _normalize_forced_candidate(params.get("CarModel"))
cached_candidate = _normalize_forced_candidate(getattr(cached_params, "carFingerprint", None))
if candidate is None and stored_candidate is None and cached_candidate is None:
candidate = _normalize_gm_suburban_camera_candidate(candidate, fingerprints, vin)
fingerprinted_candidate = candidate
if candidate is None:
gm_fallback_candidate = _get_gm_stored_candidate_fallback(fingerprints, stored_candidate, cached_candidate)
if gm_fallback_candidate is not None:
+1
View File
@@ -264,6 +264,7 @@ MIGRATION = {
"GENESIS G70 2020": HYUNDAI.GENESIS_G70_2020,
"GENESIS GV70 1ST GEN": HYUNDAI.GENESIS_GV70_1ST_GEN,
"GENESIS GV70 ELECTRIFIED 2026": HYUNDAI.GENESIS_GV70_ELECTRIFIED_2ND_GEN,
"GENESIS GV70 2026": HYUNDAI.GENESIS_GV70_2026,
"GENESIS G80 2017": HYUNDAI.GENESIS_G80,
"GENESIS G90 2017": HYUNDAI.GENESIS_G90,
"GENESIS GV80 2023": HYUNDAI.GENESIS_GV80,
+59 -79
View File
@@ -1,13 +1,15 @@
import math
import numpy as np
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.ford import fordcan
from opendbc.car.ford.values import CarControllerParams, FordFlags, CAR
from opendbc.car.ford.values import CAR, CarControllerParams, FordFlags
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.lateral import FordLateralController, FordLateralMode, FordLateralResult
from openpilot.starpilot.car.ford.lateral import FordLateralController, FordLateralResult
LongCtrlState = structs.CarControl.Actuators.LongControlState
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
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:
relative_angle = desired_angle_deg - current_angle_deg
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):
# No blending at low speed due to lack of torque wind-up and inaccurate current curvature
if v_ego_raw > 9:
@@ -58,7 +64,9 @@ def apply_ford_curvature_limits(apply_curvature, apply_curvature_last, current_c
return apply_curvature
def apply_creep_compensation(accel: float, v_ego: float) -> float:
def apply_creep_compensation(accel: float, v_ego: float, car_fingerprint: str, *, standstill: bool, stopping: bool) -> float:
if car_fingerprint == CAR.FORD_MUSTANG_MACH_E_MK1 and not (standstill and stopping):
return accel
creep_accel = np.interp(v_ego, [1., 3.], [0.6, 0.])
creep_accel = np.interp(accel, [0., 0.2], [creep_accel, 0.])
accel -= creep_accel
@@ -73,7 +81,6 @@ class CarController(CarControllerBase):
self.apply_curvature_last = 0
self.apply_angle_last = 0
self.anti_overshoot_curvature_last = 0
self.accel = 0.0
self.gas = 0.0
self.brake_request = False
@@ -83,8 +90,8 @@ class CarController(CarControllerBase):
self.lead_distance_bars_last = None
self.distance_bar_frame = 0
self.ford_lateral = None if CP.flags & FordFlags.LKA_STEERING else FordLateralController(CP)
self.ford_shadow_curvature = 0.0
self.ford_lateral_announced_mode = FordLateralMode.native
self.ford_extended_lateral_announced = False
self.stock_cruise_button = FordStockCruiseButton()
def update(self, CC, CS, now_nanos, starpilot_toggles):
can_sends = []
@@ -100,9 +107,23 @@ class CarController(CarControllerBase):
self.ford_lateral.update_inputs()
### 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:
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))
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:
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))
@@ -136,82 +157,37 @@ class CarController(CarControllerBase):
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))
else:
lateral_mode = self.ford_lateral.mode
lateral_mode_ready = lateral_mode == self.ford_lateral_announced_mode
# 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)
if (self.frame % CarControllerParams.STEER_STEP) == 0:
lateral = self.ford_lateral.update(CC, CS, actuators) \
if self.ford_extended_lateral_announced else FordLateralResult()
self.apply_curvature_last = lateral.curvature
self.ford_shadow_curvature = lateral.shadow_curvature
if self.CP.flags & FordFlags.CANFD:
counter = (self.frame // CarControllerParams.STEER_STEP) % 0x10
can_sends.append(starpilot_fordcan.create_lat_ctl2_msg(
self.packer, self.CAN, 1 if lateral.active else 0,
lateral.ramp_type, lateral.precision_type,
-lateral.path_offset, -lateral.path_angle,
-lateral.curvature, -lateral.curvature_rate, counter))
-lateral.curvature, -lateral.curvature_rate, counter, -lateral.path_angle))
else:
can_sends.append(starpilot_fordcan.create_lat_ctl_msg(
self.packer, self.CAN, lateral.active,
lateral.ramp_type, lateral.precision_type,
-lateral.path_offset, -lateral.path_angle,
-lateral.curvature, -lateral.curvature_rate))
if (self.frame % CarControllerParams.LKA_STEP) == 0:
if lateral_mode == FordLateralMode.native:
can_sends.append(fordcan.create_lka_msg(self.packer, self.CAN))
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
can_sends.append(starpilot_fordcan.create_lka_msg(self.packer, self.CAN))
self.ford_extended_lateral_announced = True
### longitudinal control ###
# send acc msg at 50Hz
if self.CP.openpilotLongitudinalControl and (self.frame % CarControllerParams.ACC_CONTROL_STEP) == 0:
accel = actuators.accel
gas = accel
stopping = actuators.longControlState == LongCtrlState.stopping
if CC.longActive:
# Compensate for engine creep at low speed.
# Either the ABS does not account for engine creep, or the correction is very slow
# TODO: verify this applies to EV/hybrid
accel = apply_creep_compensation(accel, CS.out.vEgo)
accel = apply_creep_compensation(accel, CS.out.vEgo, self.CP.carFingerprint,
standstill=CS.out.standstill, stopping=stopping)
# The stock system has been seen rate limiting the brake accel to 5 m/s^3,
# however even 3.5 m/s^3 causes some overshoot with a step response.
@@ -235,7 +211,6 @@ class CarController(CarControllerBase):
elif accel_pitch_compensated < 0.0:
self.brake_request = True
stopping = CC.actuators.longControlState == LongCtrlState.stopping
# TODO: look into using the actuators packet to send the desired speed
can_sends.append(fordcan.create_acc_msg(self.packer, self.CAN, CC.longActive, gas, accel, stopping, self.brake_request, v_ego_kph=V_CRUISE_MAX))
@@ -255,9 +230,14 @@ class CarController(CarControllerBase):
if (self.frame % CarControllerParams.ACC_UI_STEP) == 0 or send_ui:
show_distance_bars = self.frame - self.distance_bar_frame < 400
hands_free_cluster = bool(
self.ford_lateral is not None
and self.ford_extended_lateral_announced
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,
fcw_alert, CS.out.cruiseState.standstill, show_distance_bars,
hud_control, CS.acc_tja_status_stock_values))
hud_control, CS.acc_tja_status_stock_values,
hands_free_cluster))
self.main_on_last = main_on
self.lkas_enabled_last = CC.latActive
+110 -3
View File
@@ -1,9 +1,13 @@
# 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 opendbc.can import CANDefine, CANParser
from opendbc.car import Bus, create_button_events, structs
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.ford.fordcan import CanBus
from opendbc.car.ford.values import DBC, CarControllerParams, FordFlags
from opendbc.car.gps import get_car_gps_config
from opendbc.car.interfaces import CarStateBase
ButtonType = structs.CarState.ButtonEvent.Type
@@ -27,12 +31,40 @@ class CarState(CarStateBase):
self.lc_button = 0
self.lkas_available = False
self.lateral_motion_control = None
self.lateral_control_status = None
self.steering_angle_offset_deg = 0.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
def _update_car_gps(self, cp) -> 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
self.car_gps = gps
self._car_gps_timestamp_nanos = timestamp_nanos
def get_car_gps(self):
return self.car_gps
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
cp = can_parsers[Bus.pt]
cp_cam = can_parsers[Bus.cam]
self._update_car_gps(cp)
ret = structs.CarState()
if self.CP.flags & FordFlags.ALT_STEER_ANGLE:
@@ -81,7 +113,8 @@ class CarState(CarStateBase):
if self.CP.flags & FordFlags.CANFD:
# this signal is always 0 on non-CAN FD cars
ret.steerFaultTemporary |= cp.vl["Lane_Assist_Data3_FD1"]["LatCtlSte_D_Stat"] not in (1, 2, 3)
self.lateral_control_status = int(cp.vl["Lane_Assist_Data3_FD1"]["LatCtlSte_D_Stat"])
ret.steerFaultTemporary |= self.lateral_control_status not in (1, 2, 3)
# cruise state
is_metric = cp.vl["INSTRUMENT_PANEL"]["METRIC_UNITS"] == 1 if not self.CP.flags & FordFlags.CANFD else \
@@ -177,7 +210,81 @@ class CarState(CarStateBase):
@staticmethod
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 []
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 {
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus(CP).main),
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus(CP).camera),
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, CanBus(CP).main),
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."""
# 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.ford.values import CAR
+3 -1
View File
@@ -181,7 +181,7 @@ def create_acc_msg(packer, CAN: CanBus, long_active: bool, gas: float, accel: fl
def create_acc_ui_msg(packer, CAN: CanBus, CP, main_on: bool, enabled: bool, fcw_alert: bool, standstill: bool,
show_distance_bars: bool, hud_control, stock_values: dict):
show_distance_bars: bool, hud_control, stock_values: dict, hands_free_cluster: bool = False):
"""
Creates a CAN message for the Ford IPC adaptive cruise, forward collision warning and traffic jam
assist status.
@@ -197,6 +197,8 @@ def create_acc_ui_msg(packer, CAN: CanBus, CP, main_on: bool, enabled: bool, fcw
status = 3 # ActiveInterventionLeft
elif hud_control.rightLaneDepart:
status = 4 # ActiveInterventionRight
elif hands_free_cluster:
status = 7 # Hands-free assistance display
else:
status = 2 # Active
elif main_on:
+8 -4
View File
@@ -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
from opendbc.car import Bus, get_safety_config, structs
from opendbc.car.carlog import carlog
@@ -6,7 +8,7 @@ from opendbc.car.ford.carcontroller import CarController
from opendbc.car.ford.carstate import CarState
from opendbc.car.ford.fordcan import CanBus
from opendbc.car.ford.radar_interface import RadarInterface
from opendbc.car.ford.values import CarControllerParams, DBC, Ecu, FordFlags, RADAR, FordSafetyFlags
from opendbc.car.ford.values import CAR, CarControllerParams, DBC, Ecu, FordFlags, RADAR, FordSafetyFlags
from opendbc.car.interfaces import CarInterfaceBase
TransmissionType = structs.CarParams.TransmissionType
@@ -54,13 +56,15 @@ class CarInterface(CarInterfaceBase):
cfgs.insert(0, get_safety_config(structs.CarParams.SafetyModel.noOutput))
ret.safetyConfigs = cfgs
ret.alphaLongitudinalAvailable = ret.radarUnavailable
if alpha_long or not ret.radarUnavailable:
ret.alphaLongitudinalAvailable = True
ret.openpilotLongitudinalControl = bool(alpha_long)
if ret.openpilotLongitudinalControl:
ret.safetyConfigs[-1].safetyParam |= FordSafetyFlags.LONG_CONTROL.value
ret.openpilotLongitudinalControl = True
if ret.flags & FordFlags.CANFD:
ret.safetyConfigs[-1].safetyParam |= FordSafetyFlags.CANFD.value
if candidate == CAR.FORD_MUSTANG_MACH_E_MK1:
ret.safetyConfigs[-1].safetyParam |= FordSafetyFlags.MACH_E_CURVATURE.value
# TRON (SecOC) platforms are not supported
# LateralMotionControl2, ACCDATA are 16 bytes on these platforms
@@ -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
from collections import deque
from typing import cast
@@ -1,17 +1,54 @@
import random
from collections.abc import Iterable
from types import SimpleNamespace
from hypothesis import settings, given, strategies as st
from parameterized import parameterized
import pytest
from opendbc.car import Bus, gen_empty_fingerprint
from opendbc.can import CANPacker
from opendbc.car.ford import fordcan
from opendbc.car.ford.carcontroller import FordStockCruiseButton, apply_creep_compensation
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.fw_versions import build_fw_dict
from opendbc.car.ford.values import CAR, FW_QUERY_CONFIG, FW_PATTERN, get_platform_codes, match_vin_to_car
from opendbc.car.ford.interface import CarInterface
from opendbc.car.ford.values import CAR, FW_QUERY_CONFIG, FW_PATTERN, FordSafetyFlags, get_platform_codes, match_vin_to_car
from opendbc.car.ford.fingerprints import FW_VERSIONS
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)
def test_mach_e_does_not_apply_engine_creep_compensation():
for accel in (-1.0, -0.1, 0.0, 0.1):
assert apply_creep_compensation(accel, 0.5, CAR.FORD_MUSTANG_MACH_E_MK1,
standstill=False, stopping=False) == accel
assert apply_creep_compensation(0.0, 0.0, CAR.FORD_MUSTANG_MACH_E_MK1,
standstill=True, stopping=True) == -0.6
assert apply_creep_compensation(0.0, 0.5, CAR.FORD_F_150_MK14,
standstill=False, stopping=False) == -0.6
ECU_ADDRESSES = {
Ecu.eps: 0x730, # Power Steering Control Module (PSCM)
Ecu.abs: 0x760, # Anti-Lock Brake System (ABS)
@@ -154,3 +191,160 @@ class TestFordFW:
live_fw[(0x760, None)] = {b"M1MC-2D053-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"}
candidates = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fw, '', {expected_fingerprint: offline_fw})
assert len(candidates) == 0, "Should not match new model year hint"
def test_mach_e_longitudinal_toggle_controls_stock_acc_selection():
stock = CarInterface.get_params(
CAR.FORD_MUSTANG_MACH_E_MK1, gen_empty_fingerprint(), [], False, False, False, None)
enhanced = CarInterface.get_params(
CAR.FORD_MUSTANG_MACH_E_MK1, gen_empty_fingerprint(), [], True, False, False, None)
assert stock.alphaLongitudinalAvailable
assert not stock.openpilotLongitudinalControl
assert stock.pcmCruise
assert not (stock.safetyConfigs[-1].safetyParam & FordSafetyFlags.LONG_CONTROL)
assert stock.safetyConfigs[-1].safetyParam & FordSafetyFlags.MACH_E_CURVATURE
assert enhanced.alphaLongitudinalAvailable
assert enhanced.openpilotLongitudinalControl
assert enhanced.safetyConfigs[-1].safetyParam & FordSafetyFlags.LONG_CONTROL
assert enhanced.safetyConfigs[-1].safetyParam & FordSafetyFlags.MACH_E_CURVATURE
def test_mach_e_can_gps_decode():
nav1 = {
"GpsHsphLattSth_D_Actl": 2,
"GpsHsphLongEast_D_Actl": 2,
"GPS_Latitude_Degrees": 37,
"GPS_Latitude_Minutes": 57,
"GPS_Latitude_Min_dec": 0.8864,
"GPS_Longitude_Degrees": -121,
"GPS_Longitude_Minutes": 44,
"GPS_Longitude_Min_dec": 0.22,
}
nav2 = {
"GpsUtcYr_No_Actl": 2026,
"GpsUtcMnth_No_Actl": 8,
"GpsUtcDay_No_Actl": 26,
"GPS_UTC_hours": 0,
"GPS_UTC_minutes": 24,
"GPS_UTC_seconds": 38,
"Gps_B_Falt": 0,
}
nav3 = {
"GPS_dimension": 2,
"GPS_Hdop": 0.6,
"GPS_Vdop": 0.8,
"GPS_Sat_num_in_view": 31,
"GPS_MSL_altitude": 90,
"GPS_Speed": 10,
"GPS_Heading": 180,
}
gps = parse_ford_can_gps(nav1, nav2, nav3)
assert gps is not None
assert gps["latitude"] == 37.96477333333333
assert gps["longitude"] == -121.737
assert abs(gps["altitude"] - 27.432) < 1e-9
assert abs(gps["speed"] - 10 * 0.44704) < 1e-9
assert gps["hasFix"]
assert gps["satelliteCount"] == 0 # 31 is Ford's invalid sentinel.
def test_mach_e_can_gps_fault_invalidates_fix():
nav1 = {
"GpsHsphLattSth_D_Actl": 2,
"GpsHsphLongEast_D_Actl": 2,
"GPS_Latitude_Degrees": 37,
"GPS_Latitude_Minutes": 57,
"GPS_Latitude_Min_dec": 0.8864,
"GPS_Longitude_Degrees": -121,
"GPS_Longitude_Minutes": 44,
"GPS_Longitude_Min_dec": 0.22,
}
nav2 = {
"GpsUtcYr_No_Actl": 2026,
"GpsUtcMnth_No_Actl": 8,
"GpsUtcDay_No_Actl": 26,
"GPS_UTC_hours": 0,
"GPS_UTC_minutes": 24,
"GPS_UTC_seconds": 38,
"Gps_B_Falt": 1,
}
nav3 = {
"GPS_dimension": 2,
"GPS_Hdop": 0.6,
"GPS_Vdop": 0.8,
"GPS_Sat_num_in_view": 31,
"GPS_MSL_altitude": 90,
"GPS_Speed": 0,
"GPS_Heading": 180,
}
gps = parse_ford_can_gps(nav1, nav2, nav3)
assert gps is not None
assert not gps["hasFix"]
assert gps["latitude"] == 37.96477333333333
assert gps["altitude"] == 0.0
def test_mach_e_can_gps_messages_are_optional_main_bus_inputs():
cp = CarInterface.get_params(CAR.FORD_MUSTANG_MACH_E_MK1, gen_empty_fingerprint(), [], False, False, False, None)
parser = CarInterface.CarState.get_can_parsers(cp)[Bus.pt]
gps_config = get_car_gps_config(cp)
assert gps_config is not None
assert gps_config.messages == FORD_MACH_E_GPS_MESSAGES
assert gps_config.decoder is parse_ford_can_gps
assert set(parser.addresses) >= {0x462, 0x463, 0x464}
assert set(FORD_MACH_E_GPS_MESSAGES) == set(gps_config.messages) == {
parser.dbc.addr_to_msg[0x462].name,
parser.dbc.addr_to_msg[0x463].name,
parser.dbc.addr_to_msg[0x464].name,
}
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():
packer = CANPacker("ford_lincoln_base_pt")
CAN = SimpleNamespace(main=0)
CP = SimpleNamespace(openpilotLongitudinalControl=False)
hud = SimpleNamespace(leftLaneDepart=False, rightLaneDepart=False)
stock_values = dict.fromkeys([
"HaDsply_No_Cs", "HaDsply_No_Cnt", "AccStopStat_D_Dsply", "AccTrgDist2_D_Dsply",
"AccStopRes_B_Dsply", "TjaWarn_D_Rq", "TjaMsgTxt_D_Dsply", "IaccLamp_D_Rq",
"AccMsgTxt_D2_Rq", "FcwDeny_B_Dsply", "FcwMemStat_B_Actl", "AccTGap_B_Dsply",
"CadsAlignIncplt_B_Actl", "AccFllwMde_B_Dsply", "CadsRadrBlck_B_Actl",
"CmbbPostEvnt_B_Dsply", "AccStopMde_B_Dsply", "FcwMemSens_D_Actl",
"FcwMsgTxt_D_Rq", "AccWarn_D_Dsply", "FcwVisblWarn_B_Rq", "FcwAudioWarn_B_Rq",
"AccTGap_D_Dsply", "AccMemEnbl_B_RqDrv", "FdaMem_B_Stat",
], 0)
regular = fordcan.create_acc_ui_msg(
packer, CAN, CP, True, True, False, False, False, hud, stock_values)
hands_free = fordcan.create_acc_ui_msg(
packer, CAN, CP, True, True, False, False, False, hud, stock_values, True)
expected_regular = packer.make_can_msg("ACCDATA_3", 0, {"Tja_D_Stat": 2})
expected_hands_free = packer.make_can_msg("ACCDATA_3", 0, {"Tja_D_Stat": 7})
assert regular == expected_regular
assert hands_free == expected_hands_free
+3
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 re
from dataclasses import dataclass, field, replace
@@ -48,6 +50,7 @@ class FordSafetyFlags(IntFlag):
LONG_CONTROL = 1
CANFD = 2
LKA_STEERING = 4
MACH_E_CURVATURE = 8
class FordFlags(IntFlag):
+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()
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: dict[bool, list[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:
# 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
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,
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"""
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]:
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)
# 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,
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()
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 brand, config, r in requests:
# 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
# Toggle OBD multiplexing for each request
+26 -9
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.common.conversions import Conversions as CV
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,
)
from opendbc.car.interfaces import CarControllerBase
@@ -172,7 +172,7 @@ def should_send_cc_button_spam(CP, CC, CS):
return (
bool(CP.flags & GMFlags.CC_LONG.value) 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
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]:
if apply_brake <= 0:
return 0, False
@@ -298,7 +309,7 @@ def supports_volt_auto_hold(CP, auto_hold_enabled: bool):
auto_hold_enabled and
getattr(CP, "openpilotLongitudinalControl", False) 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
else:
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 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
else:
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
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:
self.apply_gas = self.params.INACTIVE_REGEN
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
else:
accel_due_to_pitch = 0.0
@@ -1048,6 +1058,7 @@ class CarController(CarControllerBase):
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 = limit_grade_feedforward(actuators.accel, accel_due_to_pitch)
accel_input = actuators.accel + accel_due_to_pitch
if truck_long_smoothing:
accel_input = shape_truck_positive_accel(
@@ -1148,7 +1159,13 @@ class CarController(CarControllerBase):
if should_send_cc_button_spam(self.CP, CC, CS):
if self.CP.carFingerprint != CAR.CADILLAC_XT4_CC:
# Using extend instead of append since the message is only sent intermittently
can_sends.extend(gmcan.create_gm_cc_spam_command(self.packer_pt, self, CS, actuators, starpilot_toggles))
longitudinal_adjustment_active = bool(getattr(
CS, "openpilot_longitudinal_adjustment_active", CC.hudControl.leadVisible,
))
can_sends.extend(gmcan.create_gm_cc_spam_command(
self.packer_pt, self, CS, actuators, starpilot_toggles,
longitudinal_adjustment_active=longitudinal_adjustment_active,
))
else:
if (CS.out.cruiseState.enabled and CC.enabled and self.frame % 52 == 0 and
CS.cruise_buttons == CruiseButtons.UNPRESS and CS.out.gasPressed and CS.out.cruiseState.speed < CS.out.vEgo < hud_v_cruise):
@@ -1180,7 +1197,7 @@ class CarController(CarControllerBase):
# cannot linger after a disengage or main-off event.
if should_send_bolt_acc_pedal_friction:
can_sends.append(gmcan.create_friction_brake_command(
self.packer_ch, friction_brake_bus, experiment_brake, idx, bolt_acc_pedal_friction_main_on,
self.packer_ch, friction_brake_bus, experiment_brake, idx, bolt_acc_pedal_friction_main_on and CC.longActive,
near_stop, at_full_stop, self.CP))
if self.CP.carFingerprint not in CC_ONLY_CAR:
friction_brake_bus = get_friction_brake_bus(self.CP)
+106 -2
View File
@@ -1,9 +1,11 @@
import copy
import math
from cereal import custom
from opendbc.can import CANDefine, CANParser
from opendbc.car import Bus, create_button_events, structs
from opendbc.car import DT_CTRL
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.gm.values import (
ALT_ACCS,
@@ -16,6 +18,7 @@ from opendbc.car.gm.values import (
AccState,
CanBus,
CruiseButtons,
GM_AUTO_HOLD_CARS,
GMFlags,
SDGM_CAR,
STEER_THRESHOLD,
@@ -30,6 +33,7 @@ STANDSTILL_THRESHOLD = 10 * 0.0311
VOLT_EBCM_BRAKE_PRESSED_THRESHOLD = 6 / 0xd0
AUTO_HOLD_MIN_DRIVE_TIME_S = 3.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,
CruiseButtons.MAIN: ButtonType.mainCruise, CruiseButtons.CANCEL: ButtonType.cancel}
@@ -65,6 +69,36 @@ def update_auto_hold_drive_timers(in_drive_for_hold: bool, moving_for_hold: bool
return auto_hold_drive_time, one_pedal_drive_time
def is_gm_auto_hold_active(car_fingerprint: str, auto_hold_engaged: bool, in_drive_for_hold: bool,
cruise_available: bool, standstill: bool, gas_pressed: bool) -> bool:
return (
auto_hold_engaged and
car_fingerprint in GM_AUTO_HOLD_CARS and
in_drive_for_hold and
cruise_available and
standstill and
not gas_pressed
)
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):
def __init__(self, CP, FPCP):
super().__init__(CP, FPCP)
@@ -103,7 +137,56 @@ class CarState(CarStateBase):
self.lkas_previously_enabled = 0
self.lkas_enabled = 0
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.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]):
if not self.CP.pcmCruise:
@@ -189,6 +272,8 @@ class CarState(CarStateBase):
ret.standstill = abs(pt_cp.vl["EBCMWheelSpdRear"]["RLWheelSpd"]) <= STANDSTILL_THRESHOLD and \
abs(pt_cp.vl["EBCMWheelSpdRear"]["RRWheelSpd"]) <= STANDSTILL_THRESHOLD
self._update_car_gps(pt_cp, ret.vEgo)
if pt_cp.vl["ECMPRDNL2"]["ManualMode"] == 1:
ret.gearShifter = self.parse_gear_shifter("T")
else:
@@ -299,8 +384,18 @@ class CarState(CarStateBase):
ret.cruiseState.available = pt_cp.vl["ECMEngineStatus"]["CruiseMainOn"] != 0
ret.espDisabled = pt_cp.vl["ESPStatus"]["TractionControlOn"] != 1
ret.accFaulted = (pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.FAULTED or
pt_cp.vl["EBCMFrictionBrakeStatus"]["FrictionBrakeUnavailable"] == 1)
acc_state = pt_cp.vl["AcceleratorPedal2"]["CruiseState"]
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.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL
@@ -349,6 +444,11 @@ class CarState(CarStateBase):
self.auto_hold_fault_suppression_timer = max(self.auto_hold_fault_suppression_timer - DT_CTRL, 0.0)
ret.accFaulted = False
ret.brakeHoldActive = is_gm_auto_hold_active(
self.CP.carFingerprint, self.auto_hold_engaged, in_drive_for_hold,
ret.cruiseState.available, ret.standstill, ret.gasPressed,
)
if self.CP.enableBsm and not sdgm_non_volt:
ret.leftBlindspot = pt_cp.vl["BCMBlindSpotMonitor"]["LeftBSM"] == 1
ret.rightBlindspot = pt_cp.vl["BCMBlindSpotMonitor"]["RightBSM"] == 1
@@ -428,6 +528,9 @@ class CarState(CarStateBase):
@staticmethod
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 = {
CAR.CHEVROLET_VOLT,
CAR.CHEVROLET_VOLT_2019,
@@ -451,6 +554,7 @@ class CarState(CarStateBase):
("PSCMSteeringAngle", 100),
("ECMAcceleratorPos", 80),
("SportMode", 0),
*gps_messages,
]
prndl2_rate = 10 if CP.carFingerprint in kaofui_state_cars else 40
+18 -15
View File
@@ -30,20 +30,20 @@ FINGERPRINTS = {
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
}],
# CAR.CHEVROLET_VOLT_CC: [
# FIXME: Need a message to distinguish flashed from non-flashed
# 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
# },
# {
# 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
# {
# 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.CHEVROLET_VOLT_CC: [
# Captured no-ACC Volt fingerprints for OBD-C/L&P harness installations
# 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
},
{
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
{
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: [{
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,12 +207,15 @@ FINGERPRINTS = {
FINGERPRINTS.update({
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_CC: FINGERPRINTS[CAR.CHEVROLET_VOLT],
CAR.GMC_ACADIA_ASCM: FINGERPRINTS[CAR.GMC_ACADIA],
CAR.CHEVROLET_MALIBU_ASCM: FINGERPRINTS[CAR.CHEVROLET_MALIBU],
CAR.CADILLAC_ESCALADE_ASCM: FINGERPRINTS[CAR.CADILLAC_ESCALADE],
CAR.CADILLAC_ESCALADE_ESV_2019_ASCM: [{**fp, SASCM_ADDRESS: 8} for fp in FINGERPRINTS[CAR.CADILLAC_ESCALADE_ESV_2019]],
CAR.CHEVROLET_SUBURBAN: FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CC],
CAR.CHEVROLET_SUBURBAN_ASCM: FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CC],
# The camera-harness Suburban shares the observed CAN map with the 2019 Yukon;
# VIN and camera-bus diagnostics disambiguate it during live fingerprinting.
CAR.CHEVROLET_SUBURBAN_CAMERA: FINGERPRINTS[CAR.GMC_YUKON],
CAR.GMC_YUKON_CC: FINGERPRINTS[CAR.GMC_YUKON],
CAR.CADILLAC_XT6: FINGERPRINTS[CAR.CHEVROLET_TRAVERSE],
CAR.CADILLAC_XT5: FINGERPRINTS[CAR.CHEVROLET_TRAVERSE],
+68 -11
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.common.conversions import Conversions as CV
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_REVERSE_CONFIRM_S = 0.6
BOLT_CC_DIRECTION_MEMORY_S = 1.5
VOLT_CC_CARS = {
CAR.CHEVROLET_VOLT_CC,
}
VOLT_CC_FREE_REQUEST_DEADBAND_MPH = 5.0
VOLT_CC_ACTIVE_REQUEST_DEADBAND_MPH = 2.0
def malibu_phase_map_for_button(button):
@@ -336,7 +341,54 @@ def stabilize_bolt_cc_button(controller, CP, requested_button):
return requested_button
def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggles):
def _create_volt_cc_spam_command(CS, actuators, ms_convert, longitudinal_adjustment_active):
accel = float(actuators.accel)
speed_setpoint = int(round(CS.out.cruiseState.speed * ms_convert))
ego_speed = CS.out.vEgo * ms_convert
requested_setpoint = (CS.out.vEgo * 1.01 + 3 * accel) * ms_convert
deadband_mph = (
VOLT_CC_ACTIVE_REQUEST_DEADBAND_MPH if longitudinal_adjustment_active
else VOLT_CC_FREE_REQUEST_DEADBAND_MPH
)
request_deadband = deadband_mph * (CV.MPH_TO_KPH if ms_convert == CV.MS_TO_KPH else 1.0)
target_setpoint = None
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 = int(round(v_cruise_kph if is_metric else v_cruise_kph * CV.KPH_TO_MPH))
moving_toward_target = target_setpoint is not None and (
(accel > 0.0 and speed_setpoint < target_setpoint) or
(accel < 0.0 and speed_setpoint > target_setpoint)
)
if target_setpoint is not None and accel > 0.0 and speed_setpoint >= target_setpoint:
return CruiseButtons.INIT, float("inf")
if (target_setpoint is not None and accel < 0.0 and speed_setpoint <= target_setpoint and
not longitudinal_adjustment_active):
return CruiseButtons.INIT, float("inf")
if not moving_toward_target and abs(requested_setpoint - speed_setpoint) <= request_deadband:
return CruiseButtons.INIT, float("inf")
if accel == 0.0:
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, longitudinal_adjustment_active=False):
accel = actuators.accel
v_ego = CS.out.vEgo
cruise_btn = CruiseButtons.INIT
@@ -350,12 +402,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
comparison_setpoint = projected_setpoint if bolt_cc else desired_setpoint
if CS.CP.minEnableSpeed - (desired_setpoint / ms_convert) > 3.25:
cruise_btn = CruiseButtons.CANCEL
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
if CS.CP.carFingerprint in VOLT_CC_CARS:
cruise_btn, rate = _create_volt_cc_spam_command(CS, actuators, ms_convert, longitudinal_adjustment_active)
else:
if CS.CP.minEnableSpeed - (desired_setpoint / ms_convert) > 3.25:
cruise_btn = CruiseButtons.CANCEL
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)
if cruise_btn == CruiseButtons.CANCEL:
@@ -420,9 +475,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
msgs = [create_buttons(packer, CanBus.POWERTRAIN, idx, cruise_btn)]
# Flashed camera-forward Volt CC installs also need the button spoof on the
# camera side. Removed-camera installs set NO_CAMERA and keep this PT-only.
if CS.CP.carFingerprint == CAR.CHEVROLET_VOLT_CC and not (CS.CP.flags & GMFlags.NO_CAMERA.value):
# A camera-forward Volt CC install needs the button spoof on both sides.
# The OBD-C/L&P gateway variant has no camera bus and remains PT-only.
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))
return msgs
else:
+22 -20
View File
@@ -15,6 +15,7 @@ from opendbc.car.gm.values import (
CC_ONLY_CAR,
CC_REGEN_PADDLE_CAR,
EV_CAR,
GM_AUTO_HOLD_CARS,
SDGM_CAR,
CarControllerParams,
CanBus,
@@ -273,7 +274,6 @@ class CarInterface(CarInterfaceBase):
kaofui_camera_cars = {
CAR.CHEVROLET_VOLT_CAMERA,
CAR.CHEVROLET_VOLT_CC,
CAR.CHEVROLET_MALIBU_HYBRID_CC,
}
bolt_cc_camera_cars = {
@@ -306,7 +306,7 @@ class CarInterface(CarInterfaceBase):
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.HW_CAM_LONG.value
elif is_camera_acc:
ret.alphaLongitudinalAvailable = (candidate not in CC_ONLY_CAR) and not ret.enableGasInterceptorDEPRECATED
ret.alphaLongitudinalAvailable = candidate not in (CC_ONLY_CAR | ALT_ACCS) and not ret.enableGasInterceptorDEPRECATED
ret.networkLocation = NetworkLocation.fwdCamera
ret.radarUnavailable = True
ret.pcmCruise = not ret.enableGasInterceptorDEPRECATED
@@ -409,7 +409,7 @@ class CarInterface(CarInterfaceBase):
ret.steerActuatorDelay = 0.1 # Default delay, not measured yet
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
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):
CarInterfaceBase.configure_torque_tune(CAR.BUICK_LACROSSE, ret.lateralTuning)
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:
ret.minEnableSpeed = -1. # engage speed is decided by pcm
@@ -501,9 +501,7 @@ class CarInterface(CarInterfaceBase):
ret.flags |= GMFlags.PEDAL_LONG.value
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
# 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
ret.minEnableSpeed = 0.
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate in (CAR.CHEVROLET_EQUINOX, CAR.CHEVROLET_EQUINOX_CC):
@@ -524,7 +522,7 @@ class CarInterface(CarInterfaceBase):
ret.steerActuatorDelay = 0.2
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate in (CAR.CHEVROLET_SUBURBAN, CAR.CHEVROLET_SUBURBAN_CC):
elif candidate in (CAR.CHEVROLET_SUBURBAN, CAR.CHEVROLET_SUBURBAN_ASCM, CAR.CHEVROLET_SUBURBAN_CAMERA, CAR.CHEVROLET_SUBURBAN_CC):
ret.steerActuatorDelay = 0.2
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
@@ -667,7 +665,8 @@ class CarInterface(CarInterfaceBase):
ret.alphaLongitudinalAvailable = False
ret.openpilotLongitudinalControl = not disable_openpilot_long
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.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_CC_LONG.value
@@ -685,6 +684,8 @@ class CarInterface(CarInterfaceBase):
if candidate in CC_ONLY_CAR:
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]:
ret.flags |= GMFlags.FORCE_BRAKE_C9.value
@@ -698,7 +699,7 @@ class CarInterface(CarInterfaceBase):
if ACCELERATOR_POS_MSG not in fingerprint[CanBus.POWERTRAIN]:
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.
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_NO_CAMERA.value
@@ -710,18 +711,19 @@ class CarInterface(CarInterfaceBase):
if remote_start_boots_comma:
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
(gm_auto_hold or volt_one_pedal_mode) and
candidate in {
CAR.CHEVROLET_VOLT,
CAR.CHEVROLET_VOLT_2019,
CAR.CHEVROLET_VOLT_ASCM,
CAR.CHEVROLET_VOLT_CAMERA,
}
(
(gm_auto_hold and candidate in GM_AUTO_HOLD_CARS) or
(volt_one_pedal_mode and candidate in {
CAR.CHEVROLET_VOLT,
CAR.CHEVROLET_VOLT_2019,
CAR.CHEVROLET_VOLT_ASCM,
CAR.CHEVROLET_VOLT_CAMERA,
})
)
)
if volt_stock_friction_brake_safety:
# Reuse the paddle-scheduler safety bit as a Volt stock friction-brake
if gm_stock_friction_brake_safety:
# 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
# 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_acc_dashboard_status_active,
get_stock_cc_active_for_cancel,
limit_grade_feedforward,
shape_bolt_acc_pedal_low_speed_friction,
shape_truck_friction_brake,
shape_truck_pitch_accel,
@@ -430,6 +431,15 @@ def test_volt_auto_hold_requires_toggle_supported_non_cc_only_volt_and_stock_saf
),
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(
SimpleNamespace(
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)
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():
assert shape_truck_friction_brake(14, -0.3, False, False) == (0, False)
+698 -2
View File
@@ -8,7 +8,13 @@ from opendbc.can import CANPacker, CANParser
from opendbc.car import Bus, DT_CTRL, structs
from opendbc.car.car_helpers import interfaces
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,
is_gm_auto_hold_active,
update_auto_hold_drive_timers,
update_startup_acc_fault_suppression,
)
from opendbc.car.gm.carcontroller import (
VisualAlert,
get_acc_dashboard_always_one,
@@ -20,8 +26,9 @@ from opendbc.car.gm.carcontroller import (
)
import opendbc.car.gm.interface as gm_interface
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.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 ALT_ACCS, ASCM_INT, CAMERA_ACC_CAR, CAR, CC_ONLY_CAR, DBC, GM_RX_OFFSET, CarControllerParams, CruiseButtons, GMFlags, GMSafetyFlags
from opendbc.safety import ALTERNATIVE_EXPERIENCE
from openpilot.common.params import Params
@@ -65,7 +72,317 @@ class TestGMFingerprint:
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:
@parameterized.expand([
(CAR.BUICK_LACROSSE, True, True, True, True, False, True),
(CAR.CHEVROLET_VOLT, True, True, True, True, False, True),
(CAR.CHEVROLET_BOLT_CC_2017, True, True, True, True, False, False),
(CAR.BUICK_LACROSSE, True, True, True, False, False, False),
(CAR.BUICK_LACROSSE, True, True, True, True, True, False),
])
def test_auto_hold_alert_state_requires_supported_complete_stop(self, car_fingerprint, engaged, in_drive,
cruise_available, standstill, gas_pressed, expected):
assert is_gm_auto_hold_active(
car_fingerprint, engaged, in_drive, cruise_available, standstill, gas_pressed,
) is expected
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:
def test_suburban_obd_and_ascm_integrations_remain_separate(self):
fingerprint = _empty_fingerprint()
fingerprint[0] = FINGERPRINTS[CAR.CHEVROLET_SUBURBAN][0].copy()
assert CAR.CHEVROLET_SUBURBAN_ASCM in ASCM_INT
assert FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_ASCM] == FINGERPRINTS[CAR.CHEVROLET_SUBURBAN]
obd_params = interfaces[CAR.CHEVROLET_SUBURBAN].get_params(
CAR.CHEVROLET_SUBURBAN,
fingerprint,
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
ascm_params = interfaces[CAR.CHEVROLET_SUBURBAN_ASCM].get_params(
CAR.CHEVROLET_SUBURBAN_ASCM,
fingerprint,
[],
alpha_long=True,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
assert obd_params.networkLocation == structs.CarParams.NetworkLocation.gateway
assert obd_params.openpilotLongitudinalControl
assert not obd_params.pcmCruise
assert obd_params.safetyConfigs[0].safetyParam == 0
assert ascm_params.networkLocation == structs.CarParams.NetworkLocation.fwdCamera
assert not ascm_params.flags & GMFlags.SASCM.value
assert not ascm_params.alphaLongitudinalAvailable
assert not ascm_params.openpilotLongitudinalControl
assert ascm_params.pcmCruise
assert ascm_params.safetyConfigs[0].safetyParam == GMSafetyFlags.HW_CAM.value | GMSafetyFlags.HW_ASCM_INT.value
assert ascm_params.lateralTuning.torque.latAccelFactor == pytest.approx(obd_params.lateralTuning.torque.latAccelFactor)
assert ascm_params.lateralTuning.torque.friction == pytest.approx(obd_params.lateralTuning.torque.friction)
def test_suburban_camera_harness_preserves_stock_acc(self):
fingerprint = _empty_fingerprint()
fingerprint[0] = FINGERPRINTS[CAR.CHEVROLET_SUBURBAN][0].copy()
fingerprint[2] = fingerprint[0].copy()
assert CAR.CHEVROLET_SUBURBAN_CAMERA in CAMERA_ACC_CAR
assert CAR.CHEVROLET_SUBURBAN_CAMERA in ALT_ACCS
assert CAR.CHEVROLET_SUBURBAN_CAMERA not in CC_ONLY_CAR
assert CAR.CHEVROLET_SUBURBAN_CAMERA not in ASCM_INT
assert all(fp[CAMERA_DIAGNOSTIC_ADDRESS] == 8 for fp in FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CAMERA])
assert all(fp[CAMERA_DIAGNOSTIC_ADDRESS + GM_RX_OFFSET] == 8 for fp in FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CAMERA])
camera_params = interfaces[CAR.CHEVROLET_SUBURBAN_CAMERA].get_params(
CAR.CHEVROLET_SUBURBAN_CAMERA,
fingerprint,
[],
alpha_long=True,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
assert camera_params.networkLocation == structs.CarParams.NetworkLocation.fwdCamera
assert camera_params.pcmCruise
assert not camera_params.alphaLongitudinalAvailable
assert not camera_params.openpilotLongitudinalControl
assert camera_params.safetyConfigs[0].safetyParam == GMSafetyFlags.HW_CAM.value
def test_suburban_cc_remains_no_acc_gateway_profile(self):
fingerprint = _empty_fingerprint()
fingerprint[0] = FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CC][0].copy()
cc_params = interfaces[CAR.CHEVROLET_SUBURBAN_CC].get_params(
CAR.CHEVROLET_SUBURBAN_CC,
fingerprint,
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
assert cc_params.networkLocation == structs.CarParams.NetworkLocation.gateway
assert cc_params.openpilotLongitudinalControl
assert not cc_params.pcmCruise
assert cc_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_CC_LONG.value
assert cc_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_ACC.value
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([
CAR.CHEVROLET_BOLT_CC_2017,
CAR.CHEVROLET_BOLT_CC_2018_2021,
@@ -152,6 +469,14 @@ class TestGMInterface:
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([
("interceptor", True),
("ascm_int", False),
@@ -201,6 +526,45 @@ class TestGMInterface:
assert car_params.flags & GMFlags.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):
CarInterface = interfaces[CAR.CHEVROLET_VOLT_ASCM]
fingerprint = {
@@ -226,11 +590,36 @@ class TestGMInterface:
assert car_params.openpilotLongitudinalControl
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.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.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):
CarInterface = interfaces[CAR.CHEVROLET_BLAZER]
fingerprint = _empty_fingerprint()
@@ -283,6 +672,43 @@ class TestGMInterface:
assert car_params.openpilotLongitudinalControl
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):
CarInterface = interfaces[CAR.CHEVROLET_VOLT_ASCM]
fingerprint = _empty_fingerprint()
@@ -526,6 +952,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=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):
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)
@@ -533,6 +966,7 @@ class TestGMCarController:
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=0,
networkLocation=structs.CarParams.NetworkLocation.fwdCamera,
minEnableSpeed=24 * CV.MPH_TO_MS,
),
buttons_counter=2,
@@ -547,6 +981,267 @@ class TestGMCarController:
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.2 / 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=1.0), SimpleNamespace(is_metric=True),
)
assert msgs == []
controller.frame = int(0.3 / DT_CTRL)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=1.0), SimpleNamespace(is_metric=True),
)
assert len(msgs) == 1
def test_volt_cc_redneck_does_not_raise_stock_setpoint_above_max(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=100.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 == 100
def test_volt_cc_redneck_tracks_max_inside_request_deadband(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(3.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=52.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=52.0 * CV.KPH_TO_MS),
vCruise=60.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=0.1), SimpleNamespace(is_metric=True),
)
assert len(msgs) == 1
assert controller.apply_speed == 53
def test_volt_cc_redneck_tracks_max_down_inside_request_deadband(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(3.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=68.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=68.0 * CV.KPH_TO_MS),
vCruise=60.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=-0.1), SimpleNamespace(is_metric=True),
)
assert len(msgs) == 1
assert controller.apply_speed == 67
def test_volt_cc_redneck_holds_small_decel_request_at_max(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(3.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=100.0 * CV.KPH_TO_MS),
vCruise=100.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=-0.1), SimpleNamespace(is_metric=True),
)
assert msgs == []
assert controller.apply_speed == 100
def test_volt_cc_redneck_holds_strong_decel_request_at_max_during_free_cruise(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(3.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=100.0 * CV.KPH_TO_MS),
vCruise=100.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=-1.2), SimpleNamespace(is_metric=True),
)
assert msgs == []
assert controller.apply_speed == 100
def test_volt_cc_redneck_brakes_for_active_lead_inside_free_road_deadband(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(3.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=100.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), longitudinal_adjustment_active=True,
)
assert len(msgs) == 1
assert controller.apply_speed == 99
def test_volt_cc_redneck_accelerates_when_pseudo_speed_request_exceeds_deadband(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(0.1 / 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=44.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=44.0 * CV.KPH_TO_MS),
vCruise=50.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=2.0), SimpleNamespace(is_metric=True),
)
assert msgs == []
controller.frame = int(0.3 / DT_CTRL)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=2.0), SimpleNamespace(is_metric=True),
)
assert len(msgs) == 1
assert controller.apply_speed == 45
def test_volt_cc_redneck_brakes_when_pseudo_speed_request_exceeds_deadband(self):
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)
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=50.7 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=49.0 * CV.KPH_TO_MS),
vCruise=49.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=-1.36), SimpleNamespace(is_metric=True), longitudinal_adjustment_active=True,
)
assert len(msgs) == 1
assert controller.apply_speed == 48
def test_volt_cc_no_camera_redneck_spam_stays_on_powertrain_bus(self):
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)
@@ -554,6 +1249,7 @@ class TestGMCarController:
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=24 * CV.MPH_TO_MS,
),
buttons_counter=2,
+22 -3
View File
@@ -175,6 +175,7 @@ class GMSafetyFlags(IntFlag):
FLAG_GM_REMOTE_START_BOOTS_COMMA = 8192
FLAG_GM_PANDA_3D1_SCHED = 16384
FLAG_GM_PANDA_PADDLE_SCHED = 32768
FLAG_GM_VOLT_CC_GATEWAY = 16384
class Footnote(Enum):
@@ -247,7 +248,7 @@ class CAR(Platforms):
dbc_dict=CHEVROLET_VOLT.dbc_dict,
)
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,
dbc_dict=CHEVROLET_VOLT.dbc_dict,
)
@@ -356,6 +357,14 @@ class CAR(Platforms):
[GMCarDocs("Chevrolet Suburban Premier 2016-20")],
CarSpecs(mass=2731, wheelbase=3.302, steerRatio=17.3, centerToFrontRatio=0.49),
)
CHEVROLET_SUBURBAN_ASCM = GMPlatformConfig(
[GMCarDocs("Chevrolet Suburban Premier ASCM Harness 2016-20", "Adaptive Cruise Control (ACC) & LKAS")],
CHEVROLET_SUBURBAN.specs,
)
CHEVROLET_SUBURBAN_CAMERA = GMPlatformConfig(
[GMCarDocs("Chevrolet Suburban Premier Camera Harness 2016-20", "Adaptive Cruise Control (ACC) & LKAS")],
CHEVROLET_SUBURBAN.specs,
)
GMC_YUKON_CC = GMPlatformConfig(
[GMCarDocs("GMC Yukon No-ACC 2019-20")],
CarSpecs(mass=2541, wheelbase=2.95, steerRatio=16.3, centerToFrontRatio=0.4),
@@ -532,12 +541,21 @@ EV_CAR = {
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)
CAMERA_ACC_CAR = {
CAR.CHEVROLET_BOLT_ACC_2022_2023,
CAR.CHEVROLET_SILVERADO,
CAR.CHEVROLET_EQUINOX,
CAR.CHEVROLET_TRAILBLAZER,
CAR.CHEVROLET_SUBURBAN_CAMERA,
CAR.CHEVROLET_VOLT_CAMERA,
CAR.CHEVROLET_BLAZER,
CAR.CHEVROLET_TRAX,
@@ -545,7 +563,7 @@ CAMERA_ACC_CAR = {
}
# Alt ASCMActiveCruiseControlStatus
ALT_ACCS = {CAR.GMC_YUKON, CAR.GMC_YUKON_CC}
ALT_ACCS = {CAR.CHEVROLET_SUBURBAN_CAMERA, CAR.GMC_YUKON, CAR.GMC_YUKON_CC}
# We're integrated at the Safety Data Gateway Module on these cars
SDGM_CAR = {
@@ -584,9 +602,10 @@ CC_REGEN_PADDLE_CAR = {
}
CAMERA_ACC_CAR.update(CC_ONLY_CAR)
# ASCM-INT paths are only enabled when SASCM (0x2FF) is detected at runtime
# ASCM-intercept variants preserve stock ACC. SASCM (0x2FF) enables alpha-long where supported.
ASCM_INT = {
CAR.CHEVROLET_VOLT_ASCM,
CAR.CHEVROLET_SUBURBAN_ASCM,
CAR.GMC_ACADIA_ASCM,
CAR.CHEVROLET_MALIBU_ASCM,
CAR.CADILLAC_ESCALADE_ASCM,
+166
View File
@@ -0,0 +1,166 @@
import math
from dataclasses import dataclass
from datetime import UTC, datetime
from collections.abc import Callable, Mapping
from typing import Any
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.ford.values import CAR as FORD_CAR
from opendbc.car.gm.values import CAR as GM_CAR
CarGpsSample = dict[str, Any]
CanGpsDecoder = Callable[..., CarGpsSample | None]
@dataclass(frozen=True)
class CarGpsConfig:
"""Vehicle-specific CAN GPS inputs and decoder."""
brand: str
messages: tuple[str, ...]
decoder: CanGpsDecoder
def _dop_accuracy(dop: float, default: float = 500.0) -> float:
"""Convert Ford's dimensionless DOP into a conservative meter estimate."""
return max(1.0, dop * 5.0) if 0.0 <= dop <= 5.8 else default
def parse_ford_can_gps(nav1: Mapping[str, float], nav2: Mapping[str, float], nav3: Mapping[str, float]) -> CarGpsSample | None:
"""Decode the Ford APIM GPS messages into the fields used by gpsLocationExternal."""
year = int(nav2["GpsUtcYr_No_Actl"])
month = int(nav2["GpsUtcMnth_No_Actl"])
day = int(nav2["GpsUtcDay_No_Actl"])
hour = int(nav2["GPS_UTC_hours"])
minute = int(nav2["GPS_UTC_minutes"])
second = int(nav2["GPS_UTC_seconds"])
try:
timestamp_ms = int(datetime(year, month, day, hour, minute, second, tzinfo=UTC).timestamp() * 1000)
except ValueError:
return None
latitude_direction = int(nav1["GpsHsphLattSth_D_Actl"])
longitude_direction = int(nav1["GpsHsphLongEast_D_Actl"])
latitude_degrees = abs(float(nav1["GPS_Latitude_Degrees"]))
longitude_degrees = abs(float(nav1["GPS_Longitude_Degrees"]))
latitude_minutes = float(nav1["GPS_Latitude_Minutes"]) + float(nav1["GPS_Latitude_Min_dec"])
longitude_minutes = float(nav1["GPS_Longitude_Minutes"]) + float(nav1["GPS_Longitude_Min_dec"])
coordinates_valid = (
latitude_direction in (1, 2) and longitude_direction in (1, 2) and
0.0 <= latitude_degrees <= 90.0 and 0.0 <= longitude_degrees <= 180.0 and
0.0 <= latitude_minutes < 60.0 and 0.0 <= longitude_minutes < 60.0
)
latitude = (1.0 if latitude_direction == 2 else -1.0) * (latitude_degrees + latitude_minutes / 60.0) if coordinates_valid else 0.0
longitude = (1.0 if longitude_direction == 1 else -1.0) * (longitude_degrees + longitude_minutes / 60.0) if coordinates_valid else 0.0
dimension = int(nav3["GPS_dimension"])
has_fix = coordinates_valid and dimension in (1, 2) and int(nav2["Gps_B_Falt"]) == 0
speed_mph = float(nav3["GPS_Speed"])
speed_mps = speed_mph * CV.MPH_TO_MS
heading = float(nav3["GPS_Heading"])
if speed_mph in (254.0, 255.0) or not math.isfinite(speed_mps) or not 0.0 <= speed_mps <= 200.0:
speed_mps = 0.0
if not math.isfinite(heading) or not 0.0 <= heading < 360.0:
heading = 0.0
vertical_accuracy = _dop_accuracy(float(nav3["GPS_Vdop"]))
horizontal_accuracy = _dop_accuracy(float(nav3["GPS_Hdop"]))
satellite_count = int(nav3["GPS_Sat_num_in_view"])
if not 0 <= satellite_count < 30: # 30 and 31 are Ford's unknown/invalid values.
satellite_count = 0
heading_rad = math.radians(heading)
return {
"latitude": latitude,
"longitude": longitude,
"altitude": (float(nav3["GPS_MSL_altitude"]) * 0.3048) if has_fix else 0.0,
"speed": speed_mps,
"bearingDeg": heading,
"horizontalAccuracy": horizontal_accuracy,
"unixTimestampMillis": timestamp_ms,
"verticalAccuracy": vertical_accuracy,
"bearingAccuracyDeg": max(5.0, horizontal_accuracy * 2.0) if speed_mps > 1.0 else 180.0,
"speedAccuracy": max(0.5, horizontal_accuracy),
"hasFix": has_fix,
"satelliteCount": satellite_count,
"vNED": [speed_mps * math.cos(heading_rad), speed_mps * math.sin(heading_rad), 0.0],
}
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 = (
"APIMGPS_Data_Nav_1_FD1",
"APIMGPS_Data_Nav_2_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] = {
FORD_CAR.FORD_MUSTANG_MACH_E_MK1: CarGpsConfig(
brand="ford",
messages=FORD_MACH_E_GPS_MESSAGES,
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
},
}
def get_car_gps_config(CP) -> CarGpsConfig | None:
config = CAR_GPS_CONFIGS.get(CP.carFingerprint)
return config if config is not None and config.brand == CP.brand else None
def car_gps_available(CP) -> bool:
return get_car_gps_config(CP) is not None
+145 -15
View File
@@ -7,6 +7,7 @@ from opendbc.car.honda import hondacan
from opendbc.car.honda.values import (
CAR,
CruiseButtons,
CruiseSettings,
HONDA_BOSCH,
HONDA_BOSCH_CANFD,
HONDA_BOSCH_RADARLESS,
@@ -16,11 +17,26 @@ from opendbc.car.honda.values import (
HondaFlags,
)
from opendbc.car.interfaces import CarControllerBase
from opendbc.car.common.pid import PIDController
from openpilot.common.params import Params
VisualAlert = structs.CarControl.HUDControl.VisualAlert
LongCtrlState = structs.CarControl.Actuators.LongControlState
BOSCH_BRAKE_FORCE_ON = -0.12
BOSCH_BRAKE_FORCE_RELEASE = -0.02
def update_honda_bosch_braking(braking: bool, gas_pedal_force: float, stopping: bool, long_active: bool) -> bool:
"""Select Bosch brake mode from the same road-load-adjusted force used for gas."""
if not long_active:
return False
if stopping:
return True
if braking:
return gas_pedal_force <= BOSCH_BRAKE_FORCE_RELEASE
return gas_pedal_force < BOSCH_BRAKE_FORCE_ON
def get_civic_bosch_modified_torque_lpf_tau(torque_cmd: float, prev_torque_cmd: float, v_ego: float) -> float:
torque_delta = abs(float(torque_cmd) - float(prev_torque_cmd))
@@ -211,6 +227,14 @@ class CarController(CarControllerBase):
self.CAN = hondacan.CanBus(CP)
self.tja_control = CP.carFingerprint in HONDA_BOSCH_TJA_CONTROL
# MVL CAN-FD ownership state. These do not affect non-CANFD vehicles.
self.radar_disable_counter = 0
self.radar_mux = 0
self.radar_hud_pulse = 0
self.last_acc_enabled = False
self.lkas_button_send_remaining = 0
self.last_lkas_button_frame = 0
self.braking = False
self.brake_steady = 0.0
self.brake_last = 0.0
@@ -228,12 +252,17 @@ class CarController(CarControllerBase):
self.steering_pressed_filter_s = 0.0
self.steering_pressed_robust_prev = False
self.bosch_last_gas = 0.0
self.bosch_braking = False
self.bosch_gas_factor = self.param_store.get_float("HondaGasFactorParams", default=1.0)
self.bosch_wind_factor = self.param_store.get_float("HondaWindFactorParams", default=1.0)
self.bosch_wind_factor_before_brake = self.bosch_wind_factor
self.bosch_gas_factor_before_gasmax = self.bosch_gas_factor
self.bosch_wind_factor_before_gasmax = self.bosch_wind_factor
self.pitch = 0.0
self.mvl_accord_mode = CP.carFingerprint == CAR.HONDA_ACCORD_11G
# MVL Bosch low-speed extra-brake integrator. Active only for Accord 11G MVL mode.
self.mvl_brake_pid = PIDController(k_p=0.0, k_i=1.0, pos_limit=0.0, neg_limit=-2.0, rate=50)
self.mvl_brake_pid.reset()
def _modified_civic_standard_active(self) -> bool:
return self.CP.carFingerprint == CAR.HONDA_CIVIC_BOSCH and bool(self.CP.flags & HondaFlags.EPS_MODIFIED)
@@ -255,6 +284,7 @@ class CarController(CarControllerBase):
hud_v_cruise = hud_control.setSpeed / CS.v_cruise_factor if hud_control.speedVisible else 255
pcm_cancel_cmd = CC.cruiseControl.cancel
gas_interceptor_command = 0.0
min_gas = self.params.BOSCH_GAS_LOOKUP_BP[0]
if len(CC.orientationNED) == 3:
self.pitch = CC.orientationNED[1]
hill_brake = math.sin(self.pitch) * ACCELERATION_DUE_TO_GRAVITY
@@ -308,11 +338,56 @@ class CarController(CarControllerBase):
# Send CAN commands
can_sends = []
# tester present - w/ no response (keeps radar disabled)
# Bosch radar ownership. On CAN-FD, leave the stock radar active until the comma relay is open,
# then enter extended diagnostics and disable radar RX/TX. This prevents a gap between stock
# ACC_CONTROL disappearing and panda permitting openpilot's replacement stream.
if self.CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) and self.CP.openpilotLongitudinalControl:
if self.frame % 10 == 0:
if self.mvl_accord_mode and CS.stock_acc_alive:
if CS.canfd_relay_open:
if self.radar_disable_counter % 50 == 0:
can_sends.append((0x18DAB0F1, b'\x02\x10\x03\x00\x00\x00\x00\x00', self.CAN.pt))
elif self.radar_disable_counter % 50 == 5:
can_sends.append((0x18DAB0F1, b'\x03\x28\x83\x03\x00\x00\x00\x00', self.CAN.pt))
self.radar_disable_counter += 1
elif self.frame % 10 == 0:
can_sends.append(make_tester_present_msg(0x18DAB0F1, self.CAN.pt, suppress_response=True))
# After the stock CAN-FD radar is silent, reproduce its low-rate/tick-aligned messages on both
# the PT and camera sides of the open relay. Pack once and mirror identical bytes so counters and
# checksums stay synchronized. v3 deliberately uses an idle lane/object payload first; model-based
# cluster rendering is deferred until the ownership/DTC handover is proven.
mvl_radar_owned = self.mvl_accord_mode and CS.canfd_relay_open and not CS.stock_acc_alive
if mvl_radar_owned and self.CP.openpilotLongitudinalControl:
if CC.enabled and not self.last_acc_enabled:
self.radar_hud_pulse = 30
self.last_acc_enabled = CC.enabled
radar_msgs = []
if CS.hud_tick:
radar_msgs.append(hondacan.create_radar_hud_canfd(self.packer, self.CAN.pt, CC.enabled, self.radar_hud_pulse > 0))
if self.radar_hud_pulse > 0:
self.radar_hud_pulse -= 1
if CS.supp_tick:
radar_msgs.append(hondacan.create_canfd_supplemental(self.packer, self.CAN.pt))
if CS.radar_50hz_tick:
if self.radar_mux >= 58:
self.radar_mux = 1
elif self.radar_mux == 10:
self.radar_mux = 17
elif self.radar_mux == 26:
self.radar_mux = 33
elif self.radar_mux == 42:
self.radar_mux = 49
else:
self.radar_mux += 1
radar_msgs.extend(hondacan.create_canfd_50hz_radar_messages(self.packer, self.CAN.pt, self.radar_mux))
if CS.radar_5hz_tick:
radar_msgs.extend(hondacan.create_canfd_5hz_radar_messages(self.packer, self.CAN.pt, CS.radar_ref_counter))
for addr, dat, _ in radar_msgs:
can_sends.append((addr, dat, self.CAN.pt))
can_sends.append((addr, dat, self.CAN.camera))
# Send steering command.
can_sends.append(hondacan.create_steering_control(self.packer, self.CAN, apply_torque, CC.latActive, self.tja_control))
@@ -352,23 +427,37 @@ class CarController(CarControllerBase):
ts = self.frame * DT_CTRL
if self.CP.carFingerprint in HONDA_BOSCH:
self.accel = float(np.clip(accel, self.params.BOSCH_ACCEL_MIN, self.params.BOSCH_ACCEL_MAX))
gas_pedal_force = self.accel + hill_brake
if self.mvl_accord_mode and (accel < min_gas) and (1e-3 < CS.out.vEgo < 3.0):
brake_addon = self.mvl_brake_pid.update(error=accel - CS.out.aEgo, speed=CS.out.vEgo)
target_accel = min(accel, accel + brake_addon)
else:
if self.mvl_accord_mode:
self.mvl_brake_pid.reset()
target_accel = accel
self.accel = float(np.clip(target_accel, self.params.BOSCH_ACCEL_MIN, self.params.BOSCH_ACCEL_MAX))
# MVL uses requested accel (not extra-brake-adjusted accel) to decide propulsion crossover.
gas_pedal_force = (accel if self.mvl_accord_mode else self.accel) + hill_brake
if self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS:
gas_pedal_force += wind_brake_mps2 * self.bosch_wind_factor
if actuators.longControlState == LongCtrlState.pid and not CS.out.gasPressed:
gas_error = self.accel - CS.out.aEgo
gas_error = (accel if self.mvl_accord_mode else self.accel) - CS.out.aEgo
if gas_error != 0.0 and gas_pedal_force > 0.0:
if gas_error != 0.0 and gas_pedal_force > min_gas:
if self.CP.carFingerprint == CAR.HONDA_INSIGHT:
gas_learn_speed = 150.0
elif self.CP.carFingerprint in (CAR.ACURA_RDX_3G, CAR.ACURA_RDX_3G_MMR):
gas_learn_speed = 300.0
else:
gas_learn_speed = 50.0
self.bosch_gas_factor = float(np.clip(self.bosch_gas_factor + gas_error / gas_learn_speed * gas_pedal_force, 0.1, 3.0))
gas_factor_floor = 0.01 if self.mvl_accord_mode else 0.1
gas_learn_force = (gas_pedal_force - min_gas) if self.mvl_accord_mode else gas_pedal_force
self.bosch_gas_factor = float(np.clip(
self.bosch_gas_factor + gas_error / gas_learn_speed * gas_learn_force,
gas_factor_floor, 3.0,
))
if gas_error != 0.0 and not CS.out.brakePressed and CS.out.vEgo > 0.0:
wind_learn_speed = 100.0 if self.CP.carFingerprint in (CAR.ACURA_RDX_3G, CAR.ACURA_RDX_3G_MMR) else 1000.0
@@ -378,7 +467,8 @@ class CarController(CarControllerBase):
else:
self.bosch_wind_factor = float(np.clip(self.bosch_wind_factor / wind_adjust, 0.1, 3.0))
if gas_pedal_force <= 0.0:
wind_brake_threshold = min_gas if self.mvl_accord_mode else 0.0
if gas_pedal_force <= wind_brake_threshold:
self.bosch_wind_factor = max(self.bosch_wind_factor, self.bosch_wind_factor_before_brake)
else:
self.bosch_wind_factor_before_brake = self.bosch_wind_factor
@@ -390,15 +480,25 @@ class CarController(CarControllerBase):
self.bosch_gas_factor_before_gasmax = self.bosch_gas_factor
self.bosch_wind_factor_before_gasmax = self.bosch_wind_factor
self.gas = float(np.interp(gas_pedal_force * self.bosch_gas_factor, self.params.BOSCH_GAS_LOOKUP_BP, self.params.BOSCH_GAS_LOOKUP_V))
gas_lookup_input = ((gas_pedal_force - min_gas) * self.bosch_gas_factor + min_gas) if self.mvl_accord_mode else \
gas_pedal_force * self.bosch_gas_factor
self.gas = float(np.interp(gas_lookup_input, self.params.BOSCH_GAS_LOOKUP_BP, self.params.BOSCH_GAS_LOOKUP_V))
self.gas = min(self.gas, max(60.0, self.bosch_last_gas + 60.0))
self.bosch_last_gas = self.gas
stopping = actuators.longControlState == LongCtrlState.stopping
bosch_braking = None
if not self.mvl_accord_mode:
self.bosch_braking = update_honda_bosch_braking(self.bosch_braking, gas_pedal_force, stopping, CC.longActive)
bosch_braking = self.bosch_braking
self.stopping_counter = self.stopping_counter + 1 if stopping else 0
can_sends.extend(
hondacan.create_acc_commands(self.packer, self.CAN, CC.enabled, CC.longActive, self.accel, self.gas, self.stopping_counter, self.CP.carFingerprint)
)
if not self.mvl_accord_mode or mvl_radar_owned:
can_sends.extend(
hondacan.create_acc_commands(
self.packer, self.CAN, CC.enabled, CC.longActive, self.accel, self.gas, self.stopping_counter, self.CP,
gas_force=gas_pedal_force, braking=bosch_braking,
)
)
else:
apply_brake = np.clip(self.brake_last - wind_brake, 0.0, 1.0)
apply_brake = int(np.clip(apply_brake * self.params.NIDEC_BRAKE_MAX, 0, self.params.NIDEC_BRAKE_MAX - 1))
@@ -440,9 +540,16 @@ class CarController(CarControllerBase):
idx = (self.frame // 2) % 0x10
can_sends.append(create_gas_interceptor_command(self.packer, gas_interceptor_command, idx))
# Send dashboard UI commands.
# Send dashboard UI commands. On CAN-FD, ACC_HUD is owned by the radar and must only start
# after the handover, aligned to the radar's 10 Hz HUD tick.
if (mvl_radar_owned and CS.hud_tick and self.CP.openpilotLongitudinalControl):
can_sends.append(
hondacan.create_acc_hud(self.packer, self.CAN.pt, self.CP, CC.enabled, pcm_speed, actuators.accel,
hud_control, hud_v_cruise, CS.is_metric, CS.acc_hud)
)
if self.frame % 10 == 0:
if self.CP.openpilotLongitudinalControl:
if self.CP.openpilotLongitudinalControl and not self.mvl_accord_mode:
# On Nidec, this also controls longitudinal positive acceleration
can_sends.append(
hondacan.create_acc_hud(self.packer, self.CAN.pt, self.CP, CC.enabled, pcm_speed, pcm_accel, hud_control, hud_v_cruise, CS.is_metric, CS.acc_hud)
@@ -458,7 +565,7 @@ class CarController(CarControllerBase):
if self.CP.openpilotLongitudinalControl:
# TODO: combining with create_acc_hud block above will change message order and will need replay logs regenerated
if self.CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS):
if self.CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS - {CAR.HONDA_ACCORD_11G}):
can_sends.append(hondacan.create_radar_hud(self.packer, self.CAN.pt))
if self.CP.carFingerprint == CAR.HONDA_CIVIC_BOSCH:
can_sends.append(hondacan.create_legacy_brake_command(self.packer, self.CAN.pt))
@@ -469,6 +576,29 @@ class CarController(CarControllerBase):
else:
self.gas = pcm_accel / self.params.NIDEC_GAS_MAX
# CAN-FD: while engaged, the camera still needs a valid SCM_BUTTONS stream behind the open relay.
# Take over that stream, echoing the live ambient-light byte. Also send a short LKAS-setting pulse
# when stock LKAS is ready, matching MVL's workaround for the Honda touch-steering-wheel timer.
if (self.mvl_accord_mode and CC.enabled and self.frame % 4 == 0
and not pcm_cancel_cmd and not CC.cruiseControl.resume):
if (self.lkas_button_send_remaining == 0 and CS.lkas_hud["LKAS_READY"]
and self.frame >= self.last_lkas_button_frame + 500):
self.lkas_button_send_remaining = 3
if self.lkas_button_send_remaining > 0:
self.last_lkas_button_frame = self.frame
self.lkas_button_send_remaining -= 1
cruise_setting = CruiseSettings.LKAS
elif CS.cruise_setting == CruiseSettings.LKAS:
cruise_setting = 0
else:
cruise_setting = CS.cruise_setting
can_sends.append(hondacan.spam_buttons_command(
self.packer, self.CAN, CS.cruise_buttons, self.CP.carFingerprint,
cruise_setting=cruise_setting, ambient_light=CS.scm_ambient_light, bus=self.CAN.camera,
))
if self.frame > 0 and self.frame % 6000 == 0:
self.param_store.put_float("HondaGasFactorParams", self.bosch_gas_factor)
self.param_store.put_float("HondaWindFactorParams", self.bosch_wind_factor)
+95 -2
View File
@@ -68,11 +68,33 @@ class CarState(CarStateBase):
self.initial_accFault_cleared = False
self.initial_accFault_cleared_timer = int(10 / DT_CTRL)
# CAN-FD deferred radar handover state. The stock radar remains responsible for ACC_CONTROL
# until the relay is open and the controller has silenced it; radar reference/tick messages are
# used to keep the replacement radar look-alikes on the stock cadence after the handover.
self.radar_ref_counter = 0
self.radar_5hz_tick_counter = 0
self.radar_5hz_tick = False
self.supp_tick_counter = 0
self.supp_tick = False
self.hud_tick_counter = 0
self.hud_tick = False
self.radar_50hz_tick_counter = 0
self.radar_50hz_tick = False
self.scm_ambient_light = 0
self.stock_acc_counter = 0
self.stock_acc_alive = False
self.camera_steer_counter = 0
self.camera_steer_seen = False
self.canfd_frames = 0
self.canfd_relay_open = False
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
cp = can_parsers[Bus.pt]
cp_cam = can_parsers[Bus.cam]
if self.CP.enableBsm:
cp_body = can_parsers[Bus.body]
if self.CP.carFingerprint == CAR.HONDA_ACCORD_11G:
cp_radar = can_parsers[Bus.radar]
ret = structs.CarState()
@@ -85,6 +107,10 @@ class CarState(CarStateBase):
prev_cruise_setting = self.cruise_setting
self.cruise_setting = cp.vl["SCM_BUTTONS"]["CRUISE_SETTING"]
self.cruise_buttons = cp.vl["SCM_BUTTONS"]["CRUISE_BUTTONS"]
if self.CP.carFingerprint in (HONDA_BOSCH_RADARLESS | {CAR.HONDA_ACCORD_11G}):
# The camera consumes this byte in SCM_BUTTONS too (adaptive high beam); preserve it when
# openpilot temporarily takes over SCM_BUTTONS toward the camera.
self.scm_ambient_light = cp.vl["SCM_BUTTONS"]["AMBIENT_LIGHT_MAYBE"]
# used for car hud message
self.is_metric = self.CP.carFingerprint in (CAR.HONDA_ODYSSEY_TWN,) or not cp.vl["CAR_SPEED"]["IMPERIAL_UNIT"]
@@ -116,8 +142,8 @@ class CarState(CarStateBase):
steer_status = self.steer_status_values[cp.vl["STEER_STATUS"]["STEER_STATUS"]]
ret.steerFaultPermanent = steer_status not in ("NORMAL", "NO_TORQUE_ALERT_1", "NO_TORQUE_ALERT_2", "LOW_SPEED_LOCKOUT", "TJA_LOW_SPEED_LOCKOUT",
"TMP_FAULT")
if self.CP.carFingerprint in HONDA_BOSCH_ALT_RADAR:
# TODO: See if this logic works for all other Honda
if self.CP.carFingerprint in (HONDA_BOSCH_ALT_RADAR | {CAR.HONDA_ACCORD_11G}):
# MVL treats CAN-FD low-speed lockout as an expected EPS state too.
min_steer_speed = max(CarControllerParams.STEER_GLOBAL_MIN_SPEED, self.CP.minSteerSpeed)
expected_low_speed_lockout = steer_status == "LOW_SPEED_LOCKOUT" and ret.vEgo < min_steer_speed
ret.steerFaultTemporary = steer_status != "NORMAL" and not expected_low_speed_lockout
@@ -249,6 +275,63 @@ class CarState(CarStateBase):
if self.CP.carFingerprint in (HONDA_BOSCH_RADARLESS | HONDA_BOSCH_CANFD):
self.lkas_hud = cp_cam.vl["LKAS_HUD"]
if self.CP.carFingerprint == CAR.HONDA_ACCORD_11G:
# Phase replacement radar messages from the stock radar's tick references. CarState reads the
# tick in one 100 Hz cycle and CarController transmits on the next, so pulse one frame before
# the expected next stock tick.
self.radar_ref_counter = cp.vl["RADAR_REFERENCE"]["COUNTER"]
ref_tick_vals = cp.vl_all.get("RADAR_REFERENCE", {}).get("COUNTER", [])
if len(ref_tick_vals) > 0:
self.radar_5hz_tick_counter = 0
else:
self.radar_5hz_tick_counter += 1
self.radar_5hz_tick = self.radar_5hz_tick_counter == 11
supp_tick_vals = cp_radar.vl_all.get("RADAR_SUPP_TICK_REFERENCE", {}).get("IGNORE", [])
if len(supp_tick_vals) > 0:
self.supp_tick_counter = 0
else:
self.supp_tick_counter += 1
self.supp_tick = self.supp_tick_counter == 99
hud_tick_vals = cp_radar.vl_all.get("RADAR_HUD_TICK_REFERENCE", {}).get("IGNORE", [])
if len(hud_tick_vals) > 0:
self.hud_tick_counter = 0
else:
self.hud_tick_counter += 1
self.hud_tick = self.hud_tick_counter == 9
tick_50hz_vals = cp_radar.vl_all.get("RADAR_50HZ_TICK_REFERENCE", {}).get("IGNORE", [])
if len(tick_50hz_vals) > 0:
self.radar_50hz_tick_counter = 0
else:
self.radar_50hz_tick_counter += 1
self.radar_50hz_tick = self.radar_50hz_tick_counter == 1
# ACC_CONTROL is normally present every 2 frames. Keep treating the stock radar as alive until
# four consecutive frames have passed without it, preventing overlap between stock and OP ACC.
self.canfd_frames += 1
if len(cp.vl_all.get("ACC_CONTROL", {}).get("COUNTER", [])) > 0:
self.stock_acc_counter = 0
else:
self.stock_acc_counter += 1
self.stock_acc_alive = self.stock_acc_counter < 4
# Before the relay opens, the camera's STEERING_CONTROL is physically visible on PT. Once the
# relay opens it disappears. Fallback to 5 seconds in case the camera frame was never observed.
if len(cp.vl_all.get("STEERING_CONTROL", {}).get("COUNTER", [])) > 0:
self.camera_steer_counter = 0
self.camera_steer_seen = True
else:
self.camera_steer_counter += 1
self.canfd_relay_open = (self.camera_steer_seen and self.camera_steer_counter >= 5) or self.canfd_frames >= 500
else:
self.supp_tick = False
self.hud_tick = False
self.radar_5hz_tick = False
self.radar_50hz_tick = False
if self.CP.enableBsm:
# BSM messages are on B-CAN, requires a panda forwarding B-CAN messages to CAN 0
# more info here: https://github.com/commaai/openpilot/pull/1867
@@ -271,6 +354,10 @@ class CarState(CarStateBase):
def get_can_parsers(self, CP):
pt_messages = [("GAS_SENSOR", 0)] if CP.enableGasInterceptorDEPRECATED else []
if CP.carFingerprint == CAR.HONDA_ACCORD_11G:
# Both deliberately go silent during the handover, so skip alive/timeout checks.
pt_messages += [("ACC_CONTROL", float("nan")), ("STEERING_CONTROL", float("nan"))]
pt_parser = CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, CanBus(CP).pt)
if CP.enableGasInterceptorDEPRECATED:
pt_parser.message_states[0x201].ignore_checksum = True
@@ -282,5 +369,11 @@ class CarState(CarStateBase):
}
if CP.enableBsm:
parsers[Bus.body] = CANParser(DBC[CP.carFingerprint][Bus.body], [], CanBus(CP).radar)
if CP.carFingerprint == CAR.HONDA_ACCORD_11G:
parsers[Bus.radar] = CANParser(DBC[CP.carFingerprint][Bus.radar], [
("RADAR_SUPP_TICK_REFERENCE", 0),
("RADAR_HUD_TICK_REFERENCE", 0),
("RADAR_50HZ_TICK_REFERENCE", 0),
], CanBus(CP).radar)
return parsers
@@ -1039,6 +1039,8 @@ FW_VERSIONS = {
(Ecu.fwdCamera, 0x18dab5f1, None): [
b'8S102-30A-A050\x00\x00',
b'8S102-30A-A060\x00\x00',
b'8S102-30A-A070\x00\x00',
b'8S102-30A-A080\x00\x00',
],
},
CAR.HONDA_CRV_6G: {
+114 -11
View File
@@ -2,6 +2,7 @@ from opendbc.car import CanBusBase
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.honda.values import (HondaFlags, HONDA_BOSCH, HONDA_BOSCH_ALT_RADAR, HONDA_BOSCH_RADARLESS,
HONDA_BOSCH_CANFD, CarControllerParams)
from opendbc.car.honda.values import CAR
# CAN bus layout with relay
# 0 = ACC-CAN - radar side
@@ -70,14 +71,18 @@ def create_brake_command(packer, CAN, apply_brake, pump_on, pcm_override, pcm_ca
return packer.make_can_msg("BRAKE_COMMAND", CAN.pt, values)
def create_acc_commands(packer, CAN, enabled, active, accel, gas, stopping_counter, car_fingerprint):
def create_acc_commands(packer, CAN, enabled, active, accel, gas, stopping_counter, CP, gas_force=None, braking=None):
commands = []
min_gas_accel = CarControllerParams.BOSCH_GAS_LOOKUP_BP[0]
control_on = 5 if enabled else 0
gas_command = gas if active and accel > min_gas_accel else -30000
if gas_force is None:
gas_force = accel
if braking is None:
braking = gas_force < min_gas_accel
braking = int(active and braking)
gas_command = gas if active and gas_force > min_gas_accel and not braking else -30000
accel_command = accel if active else 0
braking = 1 if active and accel < min_gas_accel else 0
standstill = 1 if active and stopping_counter > 0 else 0
standstill_release = 1 if active and stopping_counter == 0 else 0
@@ -87,7 +92,7 @@ def create_acc_commands(packer, CAN, enabled, active, accel, gas, stopping_count
'STANDSTILL': standstill,
}
if car_fingerprint in HONDA_BOSCH_RADARLESS:
if CP.carFingerprint in HONDA_BOSCH_RADARLESS:
acc_control_values.update({
"CONTROL_ON": enabled,
"IDLESTOP_ALLOW": stopping_counter > 200, # allow idle stop after 4 seconds (50 Hz)
@@ -147,10 +152,20 @@ def create_acc_hud(packer, bus, CP, enabled, pcm_speed, pcm_accel, hud_control,
'SET_ME_X01_2': 1,
}
if CP.carFingerprint == CAR.HONDA_ACCORD_11G:
# Stock CAN-FD radar toggles these together with ACC/lead/braking state. Keeping them low when
# neither lead nor braking is present matches MVL's observed CAN-FD payload shape.
set_me = int(enabled and (bool(acc_hud_values['HUD_LEAD']) or (pcm_accel < 0.2)))
acc_hud_values['SET_ME_X01'] = set_me
acc_hud_values['SET_ME_X01_2'] = set_me
if CP.carFingerprint in HONDA_BOSCH:
acc_hud_values['ACC_ON'] = int(enabled)
acc_hud_values['FCM_OFF'] = 1
acc_hud_values['FCM_OFF_2'] = 1
# Preserve StarPilot's existing Bosch HUD behavior outside CAN-FD. MVL's CAN-FD
# replacement radar expects FCW/CMBS enabled, while legacy Bosch keeps them off.
canfd = CP.carFingerprint == CAR.HONDA_ACCORD_11G
acc_hud_values['FCM_OFF'] = 0 if canfd else 1
acc_hud_values['FCM_OFF_2'] = 0 if canfd else 1
else:
# Shows the distance bars, TODO: stock camera shows updates temporarily while disabled
acc_hud_values['ACC_ON'] = int(enabled)
@@ -219,16 +234,104 @@ def create_legacy_brake_command(packer, bus):
return packer.make_can_msg("LEGACY_BRAKE_COMMAND", bus, {})
def spam_buttons_command(packer, CAN, button_val, car_fingerprint):
def spam_buttons_command(packer, CAN, button_val, car_fingerprint, cruise_setting=0, ambient_light=None, bus=None):
values = {
'CRUISE_BUTTONS': button_val,
'CRUISE_SETTING': 0,
'CRUISE_SETTING': cruise_setting,
}
# send buttons to camera on radarless (camera does ACC) cars
bus = CAN.camera if car_fingerprint in HONDA_BOSCH_RADARLESS else CAN.pt
# Existing StarPilot callers should retain their previous payload; only the CAN-FD camera
# takeover path explicitly echoes the live ambient-light byte.
if ambient_light is not None:
values['AMBIENT_LIGHT_MAYBE'] = ambient_light
if bus is None:
# send buttons to camera on radarless (camera does ACC) cars
bus = CAN.camera if car_fingerprint in HONDA_BOSCH_RADARLESS else CAN.pt
return packer.make_can_msg("SCM_BUTTONS", bus, values)
def create_radar_hud_canfd(packer, bus, acc, acc_pulse=False):
values = {
'CMBS_ENABLED_MAYBE': 1 if (acc and acc_pulse) else 0,
'ACC_ON': acc,
'SET_ME_X01': 0x01,
'SET_ME_X01_2': 0x01,
}
return packer.make_can_msg("RADAR_HUD_CANFD", bus, values)
def create_canfd_supplemental(packer, bus):
values = {
'SET_ME_X01': 0x01,
'SET_ME_X41': 0x41,
}
return packer.make_can_msg("BOSCH_SUPPLEMENTAL_CANFD", bus, values)
RADAR_MUX_BANK_STARTS = (1, 17, 33, 49)
PATH_OFFSET_INVALID = 2047
# These extended IDs use the CAN-FD checksum offset used by the Accord 11G
# replacement-radar stream. Keep the legacy Honda offset for existing high-ID
# messages on other Honda platforms.
HONDA_CANFD_MVL_CHECKSUM_IDS = frozenset((
0x6CD5558, 0x6CD5559, 0xF31AA52, 0xF31AA5C, 0x1A45AA4E,
))
def _lane_path_offsets(radar_mux):
pos = next((radar_mux - start for start in RADAR_MUX_BANK_STARTS if start <= radar_mux <= start + 9), 0)
if pos == 0:
return (0, 0, 0, 0)
if pos == 1:
return (0, 0, PATH_OFFSET_INVALID, PATH_OFFSET_INVALID)
return (PATH_OFFSET_INVALID,) * 4
def create_canfd_50hz_radar_messages(packer, bus, radar_mux):
offsets = _lane_path_offsets(radar_mux)
lane_path_values = {
'MUX': radar_mux,
'PATH_OFFSET_1': offsets[0],
'PATH_OFFSET_2': offsets[1],
'PATH_OFFSET_3': offsets[2],
'PATH_OFFSET_4': offsets[3],
}
hud_objects_values = {
'MUX': radar_mux,
'OBJECT_ID': 0,
'IS_LEAD_CAR': 0,
'CAR_TYPE': -1,
'ROTATION': -128,
'LONG_DIST': 196.9,
'LAT_DIST': 204.7,
}
return [
packer.make_can_msg('LANE_PATH', bus, lane_path_values),
packer.make_can_msg('HUD_OBJECTS', bus, hud_objects_values),
]
def create_canfd_5hz_radar_messages(packer, bus, radar_ref_cntr):
radar_lead_values = {
'CNTR_REF': radar_ref_cntr,
'SET_ME_X01': 0x01,
'TARGET_SPEED_MAYBE': 140,
# v3 initially sends MVL's stock-like idle lane state. Model-derived dash rendering can be added
# separately after the DTC/ownership handover is proven on-car.
'LEFT_LANE': 0,
'RIGHT_LANE': 0,
'LANE_PATH_LENGTH': 6,
}
radar_lead2_values = {
'SET_ME_X88': 136,
'SET_ME_X78': 120,
'LEAD_DISTANCE_MAYBE': 0,
}
return [
packer.make_can_msg('RADAR_LEAD', bus, radar_lead_values),
packer.make_can_msg('RADAR_LEAD2', bus, radar_lead2_values),
]
def honda_checksum(address: int, sig, d: bytearray) -> int:
s = 0
extended = address > 0x7FF
@@ -243,5 +346,5 @@ def honda_checksum(address: int, sig, d: bytearray) -> int:
s += (x & 0xF) + (x >> 4)
s = 8 - s
if extended:
s += 3
s += 10 if address in HONDA_CANFD_MVL_CHECKSUM_IDS else 3
return s & 0xF
+29 -5
View File
@@ -1,10 +1,11 @@
#!/usr/bin/env python3
import numpy as np
from openpilot.common.params import Params, UnknownKeyName
from opendbc.car import get_safety_config, structs, uds
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.disable_ecu import disable_ecu
from opendbc.car.honda.hondacan import CanBus
from opendbc.car.honda.values import CarControllerParams, HondaFlags, CAR, HONDA_BOSCH, HONDA_BOSCH_CANFD, \
from opendbc.car.honda.values import CarControllerParams, HondaFlags, CAR, HONDA_BOSCH, HONDA_BOSCH_A, HONDA_BOSCH_A_RADAR_VERIFIED, HONDA_BOSCH_CANFD, \
HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS, HondaSafetyFlags
from opendbc.car.honda.carcontroller import CarController
from opendbc.car.honda.carstate import CarState
@@ -44,7 +45,17 @@ class CarInterface(CarInterfaceBase):
cfgs.insert(0, get_safety_config(structs.CarParams.SafetyModel.noOutput))
ret.safetyConfigs = cfgs
ret.radarUnavailable = True
# HONDA_BOSCH_A describes the physical harness family. Radar remains unavailable until the
# exact platform is added to HONDA_BOSCH_A_RADAR_VERIFIED after real-capture validation.
try:
# The explicit default keeps the verified platform usable on installs that have not yet
# persisted the internal kill-switch parameter; the verified-platform set remains mandatory.
bosch_a_radar_tryout = not docs and Params().get_bool("HondaBoschARadar", default=True)
except UnknownKeyName:
bosch_a_radar_tryout = False
ret.radarUnavailable = not (candidate in HONDA_BOSCH_A and
candidate in HONDA_BOSCH_A_RADAR_VERIFIED and
bosch_a_radar_tryout)
# Disable the radar and let openpilot control longitudinal
# WARNING: THIS DISABLES AEB!
# If Bosch radarless, this blocks ACC messages from the camera
@@ -143,9 +154,13 @@ class CarInterface(CarInterfaceBase):
CarControllerParams.BOSCH_GAS_LOOKUP_BP = [-0.2, 2.0]
elif candidate == CAR.HONDA_ACCORD_11G:
ret.steerActuatorDelay = 0.22
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 5200], [0, 2560, 12747]]
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
# MVL Boston sp-honda-dev-202608 tuning (48211d6a63).
ret.longitudinalActuatorDelay = 0.05
ret.steerActuatorDelay = 0.3
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 12789], [0, 12789]]
ret.lateralTuning.pid.kf = 0.000035
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.115], [0.052]]
CarControllerParams.BOSCH_GAS_LOOKUP_BP = [0.0, 2.0]
elif candidate == CAR.ACURA_ILX:
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]] # TODO: determine if there is a dead zone at the top end
@@ -314,6 +329,8 @@ class CarInterface(CarInterfaceBase):
ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.RADARLESS.value
if candidate in HONDA_BOSCH_CANFD:
ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.BOSCH_CANFD.value
if candidate == CAR.HONDA_ACCORD_11G:
ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.BOSCH_CANFD_MVL.value
# min speed to enable ACC. if car can do stop and go, then set enabling speed
# to a negative value, so it won't matter. Otherwise, add 0.5 mph margin to not
@@ -345,6 +362,13 @@ class CarInterface(CarInterfaceBase):
@staticmethod
def init(CP, can_recv, can_send, communication_control=None):
if CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) and CP.openpilotLongitudinalControl:
# CAN-FD radar ownership is handed over only after the comma relay is confirmed open.
# Disabling the radar here can create a gap before panda enters the Honda safety mode and
# replacement ACC_CONTROL is permitted, which can latch CRUISE_FAULT/DTCs in the brake module.
# deinit() still passes an explicit enable request and therefore falls through to disable_ecu.
if CP.carFingerprint == CAR.HONDA_ACCORD_11G and communication_control is None:
return
# 0x80 silences response
if communication_control is None:
communication_control = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, 0x80 | uds.CONTROL_TYPE.DISABLE_RX_DISABLE_TX,
@@ -1,8 +1,13 @@
#!/usr/bin/env python3
import math
from collections import deque
from dataclasses import dataclass, field
from opendbc.can import CANParser
from opendbc.car import Bus, structs
from opendbc.car.interfaces import RadarInterfaceBase
from opendbc.car.honda.hondacan import CanBus
from opendbc.car.honda.values import DBC
from opendbc.car.interfaces import RadarInterfaceBase
def _create_nidec_can_parser(car_fingerprint):
@@ -11,39 +16,615 @@ def _create_nidec_can_parser(car_fingerprint):
return CANParser(DBC[car_fingerprint][Bus.radar], messages, 1)
# ============================================================================
# Honda Bosch-A 16-slot object bank
# ============================================================================
# 16 CAN-visible object slots. Each slot has 4 main frames (f0..f3, one CAN ID each -- NOT sub-frames
# muxed onto a shared ID) plus one synchronized auxiliary 5th frame. This supersedes any prior model of
# 0x280/0x284/0x288/0x28C as pieces of one object, or of 0x2C8/0x2C9 as a separate "coarse" list: it is
# ONE 16-slot bank with one auxiliary frame per slot. See honda_bosch_a_radar.dbc for the bit geometry.
BOSCH_A_DBC_NAME = 'honda_bosch_a_radar'
BOSCH_A_NUM_SLOTS = 16
def _bosch_a_main_base(slot: int) -> int:
return 0x280 + 4 * slot if slot < 4 else 0x2D0 + 4 * (slot - 4)
def _bosch_a_aux_id(slot: int) -> int:
return 0x2C8 + slot if slot < 8 else 0x290 + (slot - 8)
BOSCH_A_MAIN_IDS = [[_bosch_a_main_base(s) + i for i in range(4)] for s in range(BOSCH_A_NUM_SLOTS)]
BOSCH_A_AUX_IDS = [_bosch_a_aux_id(s) for s in range(BOSCH_A_NUM_SLOTS)]
BOSCH_A_ALL_IDS = [addr for ids in BOSCH_A_MAIN_IDS for addr in ids] + BOSCH_A_AUX_IDS
# Publish RadarPoints when the last MAIN object frame arrives. Passive captures prove that slot 15's
# companion frame, 0x297, follows 0x2FF and is the final observed object-family frame in a full sweep:
#
# ... 0x2FC, 0x2FD, 0x2FE, 0x2FF, 0x297
#
# Keep 0x2FF as the RadarPoint trigger while the companion data remains optional/debug-only. Making
# 0x297 the sole trigger would allow one dropped auxiliary frame to suppress an otherwise-valid point
# update, contrary to the parser's "aux never gates validity" contract.
BOSCH_A_TRIGGER_MSG = BOSCH_A_MAIN_IDS[BOSCH_A_NUM_SLOTS - 1][3]
BOSCH_A_SWEEP_END_MSG = BOSCH_A_AUX_IDS[BOSCH_A_NUM_SLOTS - 1] # 0x297
# Observed coherent Bosch-A sweep cadence is ~14.5-16 Hz in the available captures.
BOSCH_A_FREQ_HZ = 15
# Range: f0 raw_range (12-bit, B2:B3 high nibble) -> meters. Firmware q16 = 8*raw_range; physical
# calibration keeps slope/offset as named, replay-refinable constants (do not add a second radar/camera
# longitudinal offset on top of this).
BOSCH_A_RANGE_SCALE_M = 0.05712
BOSCH_A_RANGE_OFFSET_M = -3.0
# Azimuth: f0 raw_angle (11-bit, B4:B5 high 3 bits), offset-binary about 1024.
#
# The f3 angular-edge pair independently closes the exact center-angle scale:
# azimuth_rad = (raw_angle - 1024) / 2048
#
# This supersedes the older empirical 0.032 deg/count fit.
BOSCH_A_AZIMUTH_SCALE_RAD = 1.0 / 2048.0
BOSCH_A_AZIMUTH_CENTER = 1024
# Invalid sentinels (section 3/4/5/6 of the spec).
BOSCH_A_STATUS_INVALID = 0xF
BOSCH_A_RANGE_RAW_INVALID = 0xFFF
BOSCH_A_ANGLE_RAW_INVALID = 0x7FF
BOSCH_A_LIFE_INVALID = 0xFFF
BOSCH_A_TRACK_ID_MIN = 1
BOSCH_A_TRACK_ID_MAX = 0x3F
# AUX logical 0x00CA has an explicit 0x3FF invalid sentinel. Firmware proves the normalization below;
# captures strongly support interpreting the active value as previous/current range ratio.
BOSCH_A_RANGE_RATIO_INVALID = 0x3FF
BOSCH_A_LOGICAL_00CA_INVALID = BOSCH_A_RANGE_RATIO_INVALID # compatibility/debug alias
BOSCH_A_RANGE_RATIO_SCALE = 0.001
BOSCH_A_RANGE_RATIO_OFFSET = 0.5
# The synchronized AUX frame contains an 11-bit offset-binary velocity-like field and a 10-bit
# companion quality/uncertainty field. The byte locations are now decoded in the DBC. The exact
# Bosch descriptor name is still being verified, so keep the conversion constants isolated here.
# Capture validation shows active values in [0, 1728], with 0x7FE used as the inactive sentinel.
BOSCH_A_DIRECT_VREL_INVALID = 0x7FE
BOSCH_A_DIRECT_VREL_MIN_RAW = 0
BOSCH_A_DIRECT_VREL_MAX_RAW = 1728
BOSCH_A_DIRECT_VREL_CENTER_RAW = 864
BOSCH_A_DIRECT_VREL_SCALE_MPS = 1.0 / 64.0
# Empirical raw-quality policy. U11 error against an independent range-derivative reference rises
# with u10 in a graded way, not a cliff: replay evidence bins it roughly as 0-255 clean, 256-511 only
# mildly degraded, 512-767 clearly degraded, 768+ worst. 511 is set at that mild/clear boundary so
# U11 is still trusted directly through the mildly-degraded band; only u10 above this triggers the
# high-u10 coast path below. This is deliberately not presented as a Bosch physical unit or
# descriptor constant; it keeps saturated/high-uncertainty AUX values from becoming authoritative
# velocity measurements.
BOSCH_A_DIRECT_VREL_MAX_UNCERTAINTY_RAW = 511
# Measurement-authority policy. These are replay-derived safety/tuning gates, not recovered Bosch
# constants. Range innovation is measured from the previous accepted observation so a reset cannot
# become the baseline for following sweeps.
BOSCH_A_RANGE_SIGMA_DEGRADED_RAW = 4
BOSCH_A_RANGE_INNOVATION_MAX_M = 2.0
BOSCH_A_RANGE_INNOVATION_HARD_MAX_M = 5.0
BOSCH_A_FALLBACK_RANGE_RATE_MAX_MPS = 50.0
BOSCH_A_USE_TAN_LATERAL_PROJECTION = True
# Accepted-range history is retained for continuity and the adjacent two-point fallback. Rejected
# range resets never enter it, and it is not used for a multi-sample OLS velocity fit.
BOSCH_A_VREL_MAX_SAMPLES = 8
# Staleness gate -- TUNING constant, reused plumbing pattern (not a firmware fact). At the observed
# ~15 Hz cadence, 0.20 s is approximately three missed sweeps.
BOSCH_A_STALE_S = 0.20
@dataclass
class _BoschASlotState:
last_seen_nanos: int | None = None
# Firmware logical-ID companion data -- telemetry/debug only for now, never a RadarPoint validity
# gate. 0x00C9 has a firmware transform but no proven physical meaning; 0x00CA has an explicit
# invalid sentinel and its physical meaning remains unresolved.
logical_00c9_raw: float = float('nan')
logical_00ca_raw: float = float('nan')
direct_vrel_raw: int | None = None
direct_vrel_uncertainty_raw: int | None = None
def reset(self):
self.last_seen_nanos = None
self.logical_00c9_raw = float('nan')
self.logical_00ca_raw = float('nan')
self.direct_vrel_raw = None
self.direct_vrel_uncertainty_raw = None
@dataclass
class _BoschATrackState:
"""Persistent state for one Bosch CAN object identity, independent of wire slot."""
track_id: int
prev_frame_idx: int | None = None
prev_life: int | None = None
last_seen_nanos: int | None = None
wire_slot: int | None = None
samples: deque = field(default_factory=lambda: deque(maxlen=BOSCH_A_VREL_MAX_SAMPLES))
last_trusted_vrel: float | None = None
last_trusted_vrel_nanos: int | None = None
def _bosch_a_direct_vrel(raw_value: int | float | None,
uncertainty_raw: int | float | None = None) -> float | None:
"""Decode the capture-validated AUX relative-velocity candidate.
None means that AUX was absent/invalid and the caller should use the existing fallback policy. The [0, 1728]
active domain is deliberately enforced here because values above the observed +13.5 m/s rail have
not appeared on active objects; 0x7FE is the observed inactive sentinel.
"""
if raw_value is None:
return None
raw = int(raw_value)
if raw == BOSCH_A_DIRECT_VREL_INVALID:
return None
if not BOSCH_A_DIRECT_VREL_MIN_RAW <= raw <= BOSCH_A_DIRECT_VREL_MAX_RAW:
return None
# u10 is retained as a raw quality indicator because its physical units remain unresolved. The
# conservative threshold is evidence-backed tuning, not a recovered firmware validity rule.
if uncertainty_raw is not None and int(uncertainty_raw) > BOSCH_A_DIRECT_VREL_MAX_UNCERTAINTY_RAW:
return None
return (raw - BOSCH_A_DIRECT_VREL_CENTER_RAW) * BOSCH_A_DIRECT_VREL_SCALE_MPS
def _bosch_a_range_ratio(raw_value: int | float | None) -> float | None:
if raw_value is None:
return None
raw = int(raw_value)
if raw == BOSCH_A_RANGE_RATIO_INVALID or not 0 <= raw < BOSCH_A_RANGE_RATIO_INVALID:
return None
return BOSCH_A_RANGE_RATIO_OFFSET + BOSCH_A_RANGE_RATIO_SCALE * raw
def _bosch_a_range_ratio_vrel(raw_value: int | float | None, d_rel: float, dt: float) -> float | None:
"""Return the range rate implied by the empirical previous/current range ratio."""
ratio = _bosch_a_range_ratio(raw_value)
if ratio is None or dt <= 0.0 or not math.isfinite(d_rel):
return None
return d_rel * (1.0 - ratio) / dt
def _bosch_a_measurement_degraded(range_sigma_raw: int, existence_raw: int,
direct_vrel_uncertainty_raw: int | None) -> bool:
range_quality_bad = range_sigma_raw >= BOSCH_A_RANGE_SIGMA_DEGRADED_RAW or existence_raw in (0, 0x7F)
velocity_quality_bad = (direct_vrel_uncertainty_raw is not None and
direct_vrel_uncertainty_raw > BOSCH_A_DIRECT_VREL_MAX_UNCERTAINTY_RAW)
return range_quality_bad or velocity_quality_bad
def _create_bosch_a_can_parser(CP):
messages = [(addr, BOSCH_A_FREQ_HZ) for addr in BOSCH_A_ALL_IDS]
# Bus.radar selects the Bosch-A DBC; the object/fusion feed itself is
# physically on the camera-side ACC-CAN.
return CANParser(DBC[CP.carFingerprint][Bus.radar], messages, CanBus(CP).camera)
class RadarInterface(RadarInterfaceBase):
def __init__(self, CP):
super().__init__(CP)
self.track_id = 0
self.radar_fault = False
self.radar_wrong_config = False
self.radar_off_can = CP.radarUnavailable
self.bosch_a_radar = (not self.radar_off_can and Bus.radar in DBC[CP.carFingerprint] and
DBC[CP.carFingerprint][Bus.radar] == BOSCH_A_DBC_NAME)
# Nidec
if self.radar_off_can:
self.rcp = None
self.trigger_msg = 0x445
elif self.bosch_a_radar:
self.rcp = _create_bosch_a_can_parser(CP)
self.trigger_msg = BOSCH_A_TRIGGER_MSG
self._slots = [_BoschASlotState() for _ in range(BOSCH_A_NUM_SLOTS)]
self._tracks: dict[int, _BoschATrackState] = {}
self._slot_track_ids: list[int | None] = [None] * BOSCH_A_NUM_SLOTS
self._last_trigger_nanos = -1
else:
# Nidec
self.rcp = _create_nidec_can_parser(CP.carFingerprint)
self.trigger_msg = 0x445
self.trigger_msg = 0x445
self.track_id = 0
self.radar_fault = False
self.radar_wrong_config = False
self.updated_messages = set()
def update(self, can_strings):
# in Bosch radar and we are only steering for now, so sleep 0.05s to keep
# radard at 20Hz and return no points
if self.radar_off_can:
if self.radar_off_can or self.rcp is None:
return super().update(None)
vls = self.rcp.update(can_strings)
self.updated_messages.update(vls)
if self.trigger_msg not in self.updated_messages:
if self.bosch_a_radar and self._last_trigger_nanos >= 0:
now = self.rcp._last_update_nanos
if (now - self._last_trigger_nanos) * 1e-9 > BOSCH_A_STALE_S:
return self._bosch_a_stale_radardata()
return None
rr = self._update(self.updated_messages)
self.updated_messages.clear()
return rr
def _bosch_a_stale_radardata(self):
# Whole-bus silence: clear every live point/history and emit an EMPTY RadarData (not None) so
# radard drops any lead within a cycle instead of freezing a phantom. The next observation starts
# a fresh incarnation for its CAN identity.
self.pts.clear()
self._tracks.clear()
self._slot_track_ids = [None] * BOSCH_A_NUM_SLOTS
for slot_state in self._slots:
slot_state.reset()
self._last_trigger_nanos = -1
stale = structs.RadarData()
if not self.rcp.can_valid:
stale.errors.canError = True
stale.errors.radarUnavailableTemporary = True
return stale
def _bosch_a_retire_track(self, track_id: int):
self._tracks.pop(track_id, None)
self.pts.pop(track_id, None)
for slot, slot_track_id in enumerate(self._slot_track_ids):
if slot_track_id == track_id:
self._slot_track_ids[slot] = None
def _bosch_a_retire_stale_tracks(self, now: int):
for track_id, track in list(self._tracks.items()):
if track.last_seen_nanos is not None and (now - track.last_seen_nanos) * 1e-9 > BOSCH_A_STALE_S:
self._bosch_a_retire_track(track_id)
def _update(self, updated_messages):
if self.bosch_a_radar:
return self._update_bosch_a(updated_messages)
return self._update_nidec(updated_messages)
def _update_bosch_a(self, updated_messages):
ret = structs.RadarData()
if not self.rcp.can_valid:
ret.errors.canError = True
now = self.rcp._last_update_nanos
self._last_trigger_nanos = now
self._bosch_a_retire_stale_tracks(now)
observations = []
for slot in range(BOSCH_A_NUM_SLOTS):
f0, f1, f2, f3 = BOSCH_A_MAIN_IDS[slot]
aux = BOSCH_A_AUX_IDS[slot]
st = self._slots[slot]
if not (f0 in updated_messages and f1 in updated_messages and
f2 in updated_messages and f3 in updated_messages):
# Incomplete main-frame set: a missing CAN frame must not be treated as a lifecycle mismatch or
# death/replacement. Logical tracks are retired independently by their global staleness gate.
continue
v0 = self.rcp.vl[f0]
v1 = self.rcp.vl[f1]
v2 = self.rcp.vl[f2]
v3 = self.rcp.vl[f3]
idx0 = int(v0['FRAME_IDX'])
if not (idx0 == int(v1['FRAME_IDX']) == int(v2['FRAME_IDX']) == int(v3['FRAME_IDX'])):
# The four main frames don't share a common cycle index -- not a coherent observation this
# window. Only combine main-frame data from a coherent common frame index (section 2).
continue
status = int(v0['STATUS'])
range_raw = int(v0['RANGE_RAW'])
angle_raw = int(v0['AZIMUTH_RAW'])
range_sigma_raw = int(v0['RANGE_SIGMA_RAW'])
existence_raw = int(v1['OBJECT_EXISTENCE_PROBABILITY_RAW'])
life = int(v2['LIFECYCLE_RAW'])
track_id = int(v3['TRACK_ID'])
track_id_valid = BOSCH_A_TRACK_ID_MIN <= track_id <= BOSCH_A_TRACK_ID_MAX
st.last_seen_nanos = now
direct_vrel_raw = None
direct_vrel_uncertainty_raw = None
range_ratio_raw = None
# Attach synchronized companion data only when its cycle matches. Missing AUX never invalidates
# an otherwise coherent F0-F3 object; it only removes independent motion/quality evidence.
if aux in updated_messages:
av = self.rcp.vl[aux]
if int(av['FRAME_IDX']) == idx0:
direct_vrel_raw = int(av['REL_VELOCITY_RAW'])
direct_vrel_uncertainty_raw = int(av['REL_VELOCITY_UNCERTAINTY_RAW'])
range_ratio_raw = int(av['RANGE_RATIO_RAW'])
logical_00c9_raw = av['FW_LID_00C9_RAW']
logical_00ca_raw = av['FW_LID_00CA_RAW']
st.logical_00c9_raw = logical_00c9_raw
st.logical_00ca_raw = (
logical_00ca_raw if logical_00ca_raw != BOSCH_A_LOGICAL_00CA_INVALID else float('nan')
)
st.direct_vrel_raw = direct_vrel_raw
st.direct_vrel_uncertainty_raw = direct_vrel_uncertainty_raw
object_valid = (status != BOSCH_A_STATUS_INVALID and range_raw != BOSCH_A_RANGE_RAW_INVALID and
angle_raw != BOSCH_A_ANGLE_RAW_INVALID and life != BOSCH_A_LIFE_INVALID)
observations.append({
'slot': slot,
'frame_idx': idx0,
'life': life,
'track_id': track_id,
'track_id_valid': track_id_valid,
'object_valid': object_valid,
'range_sigma_raw': range_sigma_raw,
'existence_raw': existence_raw,
'direct_vrel_raw': direct_vrel_raw,
'direct_vrel_uncertainty_raw': direct_vrel_uncertainty_raw,
'range_ratio_raw': range_ratio_raw,
})
# First collapse duplicate wire observations of one CAN identity. The dictionary is also the
# output uniqueness boundary: one valid Bosch identity can never create two RadarPoints.
valid_by_id = {}
for observation in observations:
if not (observation['object_valid'] and observation['track_id_valid']):
# An invalid observation ends publication for the object currently occupying this wire slot,
# but it does not immediately destroy the persistent state. The object may be multiplexed to
# another slot or may return before the per-identity stale deadline; a later lifecycle break
# or staleness expiry will clear its derivative history.
ids_to_hide = {self._slot_track_ids[observation['slot']]}
if observation['track_id_valid']:
ids_to_hide.add(observation['track_id'])
for invalid_id in ids_to_hide - {None}:
self.pts.pop(invalid_id, None)
continue
track_id = observation['track_id']
current = valid_by_id.get(track_id)
if current is None:
valid_by_id[track_id] = observation
continue
# Prefer the wire slot currently associated with the persistent state. If neither candidate is
# preferred, retain the lower slot for deterministic handling of a malformed duplicate frame.
track = self._tracks.get(track_id)
current_score = (0 if track is not None and track.wire_slot == current['slot'] else 1, current['slot'])
candidate_score = (0 if track is not None and track.wire_slot == observation['slot'] else 1,
observation['slot'])
if candidate_score < current_score:
valid_by_id[track_id] = observation
valid_ids = set(valid_by_id)
for track_id, observation in sorted(valid_by_id.items(), key=lambda item: item[1]['slot']):
slot = observation['slot']
idx0 = observation['frame_idx']
life = observation['life']
# A wire-slot replacement ends publication for the old occupant, but not its persistent
# identity/history. If the old ID is still valid in another slot this sweep, keep its point;
# otherwise hide it until that ID returns or reaches its own stale deadline.
old_id = self._slot_track_ids[slot]
if old_id is not None and old_id != track_id and old_id not in valid_ids:
self.pts.pop(old_id, None)
track = self._tracks.get(track_id)
if track is None:
track = _BoschATrackState(track_id=track_id)
self._tracks[track_id] = track
same_incarnation = False
if track.prev_frame_idx is not None and track.prev_life is not None:
frame_delta = (idx0 - track.prev_frame_idx) & 0xF
life_delta = (life - track.prev_life) & 0xFFF
same_incarnation = life_delta == 2 * frame_delta
if not same_incarnation:
# The CAN identity remains the externally-visible key, but a lifecycle discontinuity starts a
# new incarnation and must not inherit the previous object's range-rate history.
track.samples.clear()
track.last_trusted_vrel = None
track.last_trusted_vrel_nanos = None
self.pts.pop(track_id, None)
v0 = self.rcp.vl[BOSCH_A_MAIN_IDS[slot][0]]
range_raw = int(v0['RANGE_RAW'])
angle_raw = int(v0['AZIMUTH_RAW'])
dRel = BOSCH_A_RANGE_SCALE_M * range_raw + BOSCH_A_RANGE_OFFSET_M
azimuth_rad = BOSCH_A_AZIMUTH_SCALE_RAD * (angle_raw - BOSCH_A_AZIMUTH_CENTER)
# The firmware's internal geometry path uses tan(angle) for a forward-axis distance. The
# Bosch object range is consumed as that forward-axis quantity here, so use the same projection
# rather than treating it as radial/slant range. Keep the switch explicit while the final
# output bridge remains under static review.
#
# Sign: AZIMUTH_RAW > center (positive azimuth_rad) is a LEFT-of-center detection in car frame's
# y axis (left is positive), matching the Nidec RadarPoint.yRel contract. Confirmed against real
# captures 2026-08-22: a stationary cluster of queued vehicles visible on the left in the road
# camera was rendering on the right in both the Qt and raylib radar-track overlays with the
# previously-flipped sign.
lateral_projection = math.tan if BOSCH_A_USE_TAN_LATERAL_PROJECTION else math.sin
yRel = dRel * lateral_projection(azimuth_rad)
now_s = now * 1e-9
direct_vrel_raw = observation['direct_vrel_raw']
direct_vrel_uncertainty_raw = observation['direct_vrel_uncertainty_raw']
direct_vrel = _bosch_a_direct_vrel(direct_vrel_raw, direct_vrel_uncertainty_raw)
live_direct_vrel = _bosch_a_direct_vrel(direct_vrel_raw)
range_ratio_raw = observation['range_ratio_raw']
# A live U11 rejected solely by the conservative U10 qualification threshold is neither an
# unavailable velocity nor permission to synthesize a one-sweep range derivative. It is also
# NOT permission to skip range-innovation checking below: u10 correlates with range_sigma in
# replay data, so a high-u10 sweep is exactly the condition where a bad/discontinuous range
# (slot migration, reset) is most likely, not less likely. Range acceptance is therefore
# decided on the same terms as every other sweep first; only once the range clears that gate
# does a high-u10 U11 fall back to coasting instead of publishing a synthesized derivative.
high_u10_live_vrel = (direct_vrel is None and live_direct_vrel is not None and
direct_vrel_uncertainty_raw is not None and
direct_vrel_uncertainty_raw > BOSCH_A_DIRECT_VREL_MAX_UNCERTAINTY_RAW)
# Validate against the previous accepted range. Qualified U11 and the range-ratio field are
# independent corroboration paths; high-U10 U11 is deliberately excluded from this decision.
previous_sample = track.samples[-1] if track.samples else None
fallback_vrel = 0.0
ratio_vrel = None
range_rejected = False
degraded = _bosch_a_measurement_degraded(
observation['range_sigma_raw'], observation['existence_raw'], direct_vrel_uncertainty_raw,
)
if previous_sample is not None:
previous_time, previous_range = previous_sample
dt = now_s - previous_time
if dt <= 0.0:
range_rejected = True
else:
fallback_vrel = (dRel - previous_range) / dt
ratio = _bosch_a_range_ratio(range_ratio_raw)
ratio_vrel = _bosch_a_range_ratio_vrel(range_ratio_raw, dRel, dt)
residuals_m = []
if direct_vrel is not None:
residuals_m.append(abs(dRel - (previous_range + direct_vrel * dt)))
if ratio is not None:
residuals_m.append(abs(previous_range - dRel * ratio))
if residuals_m:
innovation_m = min(residuals_m)
range_rejected = (
innovation_m > BOSCH_A_RANGE_INNOVATION_HARD_MAX_M or
(degraded and innovation_m > BOSCH_A_RANGE_INNOVATION_MAX_M)
)
else:
range_rejected = abs(fallback_vrel) > BOSCH_A_FALLBACK_RANGE_RATE_MAX_MPS
if range_rejected:
# Keep the last accepted point briefly as an unmeasured coast. The rejected geometry is not
# published and never becomes the baseline for a later derivative.
accepted_fresh = previous_sample is not None and now_s - previous_sample[0] <= BOSCH_A_STALE_S
point = self.pts.get(track_id)
if accepted_fresh and point is not None:
point.measured = False
else:
self.pts.pop(track_id, None)
track.prev_frame_idx = idx0
track.prev_life = life
track.last_seen_nanos = now
track.wire_slot = slot
for old_slot, old_id in enumerate(self._slot_track_ids):
if old_slot != slot and old_id == track_id:
self._slot_track_ids[old_slot] = None
self._slot_track_ids[slot] = track_id
continue
if high_u10_live_vrel:
# The range cleared innovation checking above, so geometry here is trustworthy; only vRel is
# in question. Preserve current geometry but coast only a recent authoritative motion
# estimate without a KF update, rather than publishing a one-sweep-derivative synthesis.
trusted_fresh = (track.last_trusted_vrel is not None and track.last_trusted_vrel_nanos is not None and
(now - track.last_trusted_vrel_nanos) * 1e-9 <= BOSCH_A_STALE_S)
if trusted_fresh:
point = self.pts.get(track_id)
if point is not None:
point.dRel = dRel
point.yRel = yRel
point.vRel = track.last_trusted_vrel
point.measured = False
else:
track.last_trusted_vrel = None
track.last_trusted_vrel_nanos = None
self.pts.pop(track_id, None)
# Do not let a coast-only range observation become a future derivative baseline.
track.prev_frame_idx = idx0
track.prev_life = life
track.last_seen_nanos = now
track.wire_slot = slot
for old_slot, old_id in enumerate(self._slot_track_ids):
if old_slot != slot and old_id == track_id:
self._slot_track_ids[old_slot] = None
self._slot_track_ids[slot] = track_id
continue
# Native U11 is unavailable here (sentinel/out-of-range -- the high-u10-but-live case above
# already returned before this point) and the range-ratio field is unusable or degraded too.
# The remaining option is the raw one-sweep (dRel-previous_range)/dt derivative, which is
# structurally the same hazard the high-u10 case guards against: a synthesized rate becoming an
# authoritative measurement. Coast a recent trusted velocity instead, on the same terms as above,
# rather than publish it.
# A true birth observation (no previous accepted range yet) can never mature into a published
# point this cycle regardless of vRel source -- `matured` below requires a second sample -- so
# only intercept once a fallback derivative would actually have something to poison.
u11_and_ratio_unavailable = (direct_vrel is None and (ratio_vrel is None or degraded) and
previous_sample is not None)
if u11_and_ratio_unavailable:
trusted_fresh = (track.last_trusted_vrel is not None and track.last_trusted_vrel_nanos is not None and
(now - track.last_trusted_vrel_nanos) * 1e-9 <= BOSCH_A_STALE_S)
if trusted_fresh:
point = self.pts.get(track_id)
if point is not None:
point.dRel = dRel
point.yRel = yRel
point.vRel = track.last_trusted_vrel
point.measured = False
else:
track.last_trusted_vrel = None
track.last_trusted_vrel_nanos = None
self.pts.pop(track_id, None)
# Do not let a coast-only range observation become a future derivative baseline.
track.prev_frame_idx = idx0
track.prev_life = life
track.last_seen_nanos = now
track.wire_slot = slot
for old_slot, old_id in enumerate(self._slot_track_ids):
if old_slot != slot and old_id == track_id:
self._slot_track_ids[old_slot] = None
self._slot_track_ids[slot] = track_id
continue
track.samples.append((now_s, dRel))
sample_count = len(track.samples)
# Prefer qualified native U11; otherwise the range-ratio field. The raw one-sweep derivative is
# never published as a measurement -- see the coast/drop branch above, which intercepts before
# this point whenever neither native U11 nor the ratio field is usable.
if direct_vrel is not None:
vRel = direct_vrel
else:
vRel = ratio_vrel
trustworthy_vrel = True
# A birth observation has no range-rate yet. Keep it as history, but do not publish a RadarPoint
# until a second coherent observation of the same CAN identity supplies a finite derivative.
matured = sample_count >= 2 and math.isfinite(vRel)
if trustworthy_vrel:
track.last_trusted_vrel = vRel
track.last_trusted_vrel_nanos = now
if matured and track_id not in self.pts:
self.pts[track_id] = structs.RadarData.RadarPoint()
self.pts[track_id].trackId = track_id
self.pts[track_id].aRel = float('nan')
self.pts[track_id].yvRel = float('nan')
if matured:
self.pts[track_id].dRel = dRel
self.pts[track_id].yRel = yRel
self.pts[track_id].vRel = vRel
self.pts[track_id].measured = True
else:
self.pts.pop(track_id, None)
track.prev_frame_idx = idx0
track.prev_life = life
track.last_seen_nanos = now
track.wire_slot = slot
for old_slot, old_id in enumerate(self._slot_track_ids):
if old_slot != slot and old_id == track_id:
self._slot_track_ids[old_slot] = None
self._slot_track_ids[slot] = track_id
ret.points = [self.pts[track_id] for track_id in sorted(self.pts)]
return ret
def _update_nidec(self, updated_messages):
ret = structs.RadarData()
for ii in sorted(updated_messages):
File diff suppressed because it is too large Load Diff
@@ -2,18 +2,21 @@ import re
from types import SimpleNamespace
import pytest
from opendbc.car import structs
from opendbc.car import Bus, structs
from opendbc.car.structs import CarParams
from opendbc.car import gen_empty_fingerprint
from opendbc.car.honda.interface import CarInterface
from opendbc.car.honda.carcontroller import (
BOSCH_BRAKE_FORCE_ON,
BOSCH_BRAKE_FORCE_RELEASE,
CarController,
get_civic_bosch_modified_steering_pressed,
get_civic_bosch_modified_torque_lpf_tau,
get_honda_bosch_wind_brake_mps2,
update_honda_bosch_braking,
update_honda_bosch_live_learning,
)
from opendbc.car.honda.hondacan import create_lkas_hud
from opendbc.car.honda.hondacan import create_acc_commands, create_lkas_hud
from opendbc.car.honda.fingerprints import FW_VERSIONS
from opendbc.car.honda.values import CAR, DBC, HONDA_BOSCH, HONDA_BOSCH_TJA_CONTROL, CarControllerParams, HondaFlags, HondaSafetyFlags, \
HondaStarPilotFlags
@@ -26,6 +29,67 @@ def get_test_toggles() -> SimpleNamespace:
class TestHondaFingerprint:
@staticmethod
def _acc_control_values(active, accel, gas=500, gas_force=0.5, braking=False):
class FakePacker:
@staticmethod
def make_can_msg(name, bus, values):
return name, bus, values
can = SimpleNamespace(pt=1)
cp = SimpleNamespace(carFingerprint=CAR.HONDA_CRV_5G)
commands = create_acc_commands(FakePacker(), can, True, active, accel, gas, 0, cp, gas_force, braking)
assert commands[-1][0] == "ACC_CONTROL"
return commands[-1][2]
def test_bosch_acc_commands_reject_fault_route_gas_brake_conflict(self):
braking = update_honda_bosch_braking(False, 0.2, False, True)
values = self._acc_control_values(True, -0.27, gas=160, gas_force=0.2, braking=braking)
assert values["GAS_COMMAND"] == 160
assert values["ACCEL_COMMAND"] == pytest.approx(-0.27)
assert values["BRAKE_REQUEST"] == 0
assert values["BRAKE_LIGHTS"] == 0
@pytest.mark.parametrize("active", [False, True])
@pytest.mark.parametrize("accel", [-3.5, -0.27, -0.2, -0.1, 0.0, 0.01, 2.0])
@pytest.mark.parametrize("gas_force", [-0.5, 0.0, 0.5])
@pytest.mark.parametrize("braking", [False, True])
def test_bosch_acc_commands_never_request_gas_and_braking_together(self, active, accel, gas_force, braking):
values = self._acc_control_values(active, accel, gas_force=gas_force, braking=braking)
assert not (values["GAS_COMMAND"] > 0 and values["BRAKE_REQUEST"] == 1)
assert not (values["GAS_COMMAND"] > 0 and values["BRAKE_LIGHTS"] == 1)
if values["GAS_COMMAND"] > 0:
assert active
def test_bosch_acc_commands_preserve_road_load_gas_above_brake_threshold(self):
values = self._acc_control_values(True, -0.27, gas=500, gas_force=0.3)
assert values["GAS_COMMAND"] == 500
assert values["ACCEL_COMMAND"] == pytest.approx(-0.27)
assert values["BRAKE_REQUEST"] == 0
assert values["BRAKE_LIGHTS"] == 0
def test_bosch_acc_commands_do_not_send_gas_without_positive_force(self):
values = self._acc_control_values(True, 0.2, gas=500, gas_force=-0.4)
assert values["GAS_COMMAND"] == -30000
def test_bosch_braking_uses_force_hysteresis(self):
braking = update_honda_bosch_braking(False, BOSCH_BRAKE_FORCE_ON - 0.01, False, True)
assert braking
braking = update_honda_bosch_braking(braking, -0.05, False, True)
assert braking
braking = update_honda_bosch_braking(braking, BOSCH_BRAKE_FORCE_RELEASE + 0.01, False, True)
assert not braking
def test_bosch_braking_preserves_stopping_and_resets_inactive(self):
assert update_honda_bosch_braking(False, 0.5, True, True)
assert not update_honda_bosch_braking(True, -1.0, False, False)
def test_honda_lkas_hud_shows_lane_lines_when_lateral_only_is_active(self):
class FakePacker:
@staticmethod
@@ -196,6 +260,16 @@ class TestHondaFingerprint:
assert CP.safetyConfigs[-1].safetyParam & HondaSafetyFlags.BOSCH_CANFD
assert CP.safetyConfigs[-1].safetyParam & HondaSafetyFlags.BOSCH_LONG
def test_mvl_handover_is_scoped_to_accord_11g(self):
toggles = get_test_toggles()
accord_cp = CarInterface.get_params(CAR.HONDA_ACCORD_11G, gen_empty_fingerprint(), [], True, False, False, toggles)
crv_cp = CarInterface.get_params(CAR.HONDA_CRV_6G, gen_empty_fingerprint(), [], True, False, False, toggles)
assert Bus.radar in DBC[accord_cp.carFingerprint]
assert Bus.radar not in DBC[crv_cp.carFingerprint]
assert accord_cp.safetyConfigs[-1].safetyParam & HondaSafetyFlags.BOSCH_CANFD_MVL
assert not crv_cp.safetyConfigs[-1].safetyParam & HondaSafetyFlags.BOSCH_CANFD_MVL
def test_nidec_pedal_detection_enables_interceptor_path(self):
toggles = get_test_toggles()
fingerprint = gen_empty_fingerprint()
+78 -12
View File
@@ -57,6 +57,9 @@ class HondaSafetyFlags(IntFlag):
RADARLESS = 8
BOSCH_CANFD = 16
GAS_INTERCEPTOR = 32
# Accord 11G MVL radar/camera handover messages. Keep the generic CAN-FD
# safety profile unchanged for other Honda CAN-FD platforms (for example CR-V 6G).
BOSCH_CANFD_MVL = 64
class HondaFlags(IntFlag):
@@ -117,11 +120,17 @@ class HondaCarDocs(CarDocs):
self.car_parts = CarParts.common([harness])
if CP.alphaLongitudinalAvailable:
self.footnotes.append(Footnote.EXP_LONG)
class Footnote(Enum):
CIVIC_DIESEL = CarFootnote(
"2019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.",
Column.FSR_STEERING)
EXP_LONG = CarFootnote(
"Enabling longitudinal control (alpha) will disable all CMBS functionality, including AEB and FCW.",
Column.LONGITUDINAL)
@dataclass
@@ -162,7 +171,12 @@ class CAR(Platforms):
HondaCarDocs("Honda N-Box 2018", "All", min_steer_speed=5.),
],
CarSpecs(mass=890., wheelbase=2.520, steerRatio=18.64),
{Bus.pt: 'acura_rdx_2020_can_generated'},
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
{Bus.pt: 'acura_rdx_2020_can_generated', Bus.radar: 'honda_bosch_a_radar'},
)
HONDA_ACCORD = HondaBoschPlatformConfig(
[
@@ -172,15 +186,21 @@ class CAR(Platforms):
],
# steerRatio: 11.82 is spec end-to-end
CarSpecs(mass=3279 * CV.LB_TO_KG, wheelbase=2.83, steerRatio=16.33, centerToFrontRatio=0.39, tireStiffnessFactor=0.8467),
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'},
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.radar: 'honda_bosch_a_radar'},
flags=HondaFlags.ALLOW_MANUAL_TRANS,
)
HONDA_ACCORD_11G = HondaBoschCANFDPlatformConfig(
[
HondaCarDocs("Honda Accord 2023-25", "All"),
HondaCarDocs("Honda Accord Hybrid 2023-25", "All"),
],
CarSpecs(mass=3477 * CV.LB_TO_KG, wheelbase=2.83, steerRatio=16.0, centerToFrontRatio=0.39),
],
CarSpecs(mass=3477 * CV.LB_TO_KG, wheelbase=2.83, steerRatio=16.7, centerToFrontRatio=0.39),
{Bus.pt: 'honda_common_canfd_generated', Bus.radar: 'honda_common_canfd_generated'},
)
HONDA_CIVIC_BOSCH = HondaBoschPlatformConfig(
[
@@ -190,12 +210,22 @@ class CAR(Platforms):
HondaCarDocs("Honda Civic Hatchback 2019-21", "All", min_steer_speed=12. * CV.MPH_TO_MS),
],
CarSpecs(mass=1326, wheelbase=2.7, steerRatio=15.38, centerToFrontRatio=0.4), # steerRatio: 10.93 is end-to-end spec
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'},
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.radar: 'honda_bosch_a_radar'},
)
HONDA_CIVIC_BOSCH_DIESEL = HondaBoschPlatformConfig(
[], # don't show in docs
HONDA_CIVIC_BOSCH.specs,
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'},
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.radar: 'honda_bosch_a_radar'},
)
HONDA_CIVIC_2022 = HondaBoschPlatformConfig(
[
@@ -214,7 +244,12 @@ class CAR(Platforms):
[HondaCarDocs("Honda CR-V 2017-22", min_steer_speed=15. * CV.MPH_TO_MS)],
# steerRatio: 12.3 is spec end-to-end
CarSpecs(mass=3410 * CV.LB_TO_KG, wheelbase=2.66, steerRatio=16.0, centerToFrontRatio=0.41, tireStiffnessFactor=0.677),
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.body: 'honda_crv_ex_2017_body_generated'},
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.body: 'honda_crv_ex_2017_body_generated', Bus.radar: 'honda_bosch_a_radar'},
flags=HondaFlags.BOSCH_ALT_BRAKE,
)
HONDA_CRV_6G = HondaBoschCANFDPlatformConfig(
@@ -228,7 +263,12 @@ class CAR(Platforms):
[HondaCarDocs("Honda CR-V Hybrid 2017-22", min_steer_speed=12. * CV.MPH_TO_MS)],
# mass: mean of 4 models in kg, steerRatio: 12.3 is spec end-to-end
CarSpecs(mass=1667, wheelbase=2.66, steerRatio=16, centerToFrontRatio=0.41, tireStiffnessFactor=0.677),
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'},
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.radar: 'honda_bosch_a_radar'},
)
HONDA_HRV_3G = HondaBoschPlatformConfig(
[HondaCarDocs("Honda HR-V 2023-25", "All")],
@@ -245,7 +285,12 @@ class CAR(Platforms):
ACURA_RDX_3G = HondaBoschPlatformConfig(
[HondaCarDocs("Acura RDX 2019-21", "All", min_steer_speed=3. * CV.MPH_TO_MS)],
CarSpecs(mass=4068 * CV.LB_TO_KG, wheelbase=2.75, steerRatio=11.95, centerToFrontRatio=0.41, tireStiffnessFactor=0.677), # as spec
{Bus.pt: 'acura_rdx_2020_can_generated'},
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
{Bus.pt: 'acura_rdx_2020_can_generated', Bus.radar: 'honda_bosch_a_radar'},
flags=HondaFlags.BOSCH_ALT_BRAKE,
)
ACURA_RDX_3G_MMR = HondaBoschPlatformConfig(
@@ -257,17 +302,32 @@ class CAR(Platforms):
HONDA_INSIGHT = HondaBoschPlatformConfig(
[HondaCarDocs("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS)],
CarSpecs(mass=2987 * CV.LB_TO_KG, wheelbase=2.7, steerRatio=15.0, centerToFrontRatio=0.39, tireStiffnessFactor=0.82), # as spec
{Bus.pt: 'honda_insight_ex_2019_can_generated'},
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
{Bus.pt: 'honda_insight_ex_2019_can_generated', Bus.radar: 'honda_bosch_a_radar'},
)
HONDA_E = HondaBoschPlatformConfig(
[HondaCarDocs("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS)],
CarSpecs(mass=3338.8 * CV.LB_TO_KG, wheelbase=2.5, centerToFrontRatio=0.5, steerRatio=16.71, tireStiffnessFactor=0.82),
{Bus.pt: 'acura_rdx_2020_can_generated'},
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
{Bus.pt: 'acura_rdx_2020_can_generated', Bus.radar: 'honda_bosch_a_radar'},
)
HONDA_E_ADVANCE = HondaBoschPlatformConfig(
[], # don't show in docs, base trim already in docs
CarSpecs(mass=1527, wheelbase=2.5, centerToFrontRatio=0.5, steerRatio=16.71, tireStiffnessFactor=0.82),
{Bus.pt: 'honda_e_advance_2020_can_generated'},
# Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode).
# RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as
# stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS
# functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS
# CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason.
{Bus.pt: 'honda_e_advance_2020_can_generated', Bus.radar: 'honda_bosch_a_radar'},
)
HONDA_PILOT_4G = HondaBoschCANFDPlatformConfig(
[HondaCarDocs("Honda Pilot 2023-25", "All")],
@@ -468,6 +528,12 @@ HONDA_BOSCH = CAR.with_flags(HondaFlags.BOSCH)
HONDA_BOSCH_RADARLESS = CAR.with_flags(HondaFlags.BOSCH_RADARLESS)
HONDA_BOSCH_CANFD = CAR.with_flags(HondaFlags.BOSCH_CANFD)
HONDA_BOSCH_ALT_RADAR = CAR.with_flags(HondaFlags.BOSCH_ALT_RADAR)
# Plain bosch_a harness: not CANFD, not radarless, not alt-radar. These platforms have a firmware-correct
# RadarInterface (16-slot Bosch-A object bank, RX-only, see radar_interface.py) available behind
# 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.
HONDA_BOSCH_A = HONDA_BOSCH - HONDA_BOSCH_RADARLESS - HONDA_BOSCH_CANFD - HONDA_BOSCH_ALT_RADAR
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_CAMERA_MESSAGE_CARS = {
CAR.HONDA_ACCORD,
+152 -39
View File
@@ -1,18 +1,24 @@
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
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, create_gas_interceptor_command, make_tester_present_msg, rate_limit, structs
from opendbc.car.common.filter_simple import FirstOrderFilter
from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_steer_angle_limits_vm, common_fault_avoidance, get_max_angle_delta_vm, get_max_angle_vm
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.hyundai import hyundaicanfd, hyundaican
from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, kia_ev6_gt_line_longitudinal_tuning
from opendbc.car.hyundai.lead_data import CanLeadDataState
from opendbc.car.hyundai.values import HyundaiFlags, HyundaiSafetyFlags, HyundaiStarPilotFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \
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
from opendbc.car.interfaces import CarControllerBase
from opendbc.car.vehicle_model import VehicleModel
from openpilot.common.params import Params
from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import get_hyundai_canfd_scc_jerk_limits, shape_hyundai_canfd_scc_accel
from openpilot.starpilot.common.testing_grounds import testing_ground
VisualAlert = structs.CarControl.HUDControl.VisualAlert
LongCtrlState = structs.CarControl.Actuators.LongControlState
@@ -22,6 +28,9 @@ LongCtrlState = structs.CarControl.Actuators.LongControlState
MAX_ANGLE = 85
MAX_ANGLE_FRAMES = 89
MAX_ANGLE_CONSECUTIVE_FRAMES = 2
CANCEL_BUTTON_DELAY_FRAMES = 10
CANFD_BLINDSPOT_STATUS_STALE_NS = 200_000_000
CANFD_CAMERA_LEAD_STALE_NS = 300_000_000
CANFD_LEAD_MIN_DISTANCE = 0.1
@@ -33,6 +42,11 @@ IONIQ_6_RESPONSE_MULTIPLIER = 1.2
IONIQ_6_CANFD_SCC_ACCEL_STEP = (6.0 / 50.0) * IONIQ_6_RESPONSE_MULTIPLIER
IONIQ_6_CANFD_SCC_DECEL_STEP = (15.0 / 50.0) * IONIQ_6_RESPONSE_MULTIPLIER
EV9_CANFD_SCC_DECEL_STEP = 10.0 / 50.0
RAY_PEDAL_COMMAND_CAP = 0.55
RAY_PEDAL_RATE_UP = 0.02
RAY_PEDAL_RATE_DOWN = 0.06
RAY_PEDAL_OVERSPEED_CUTOFF = 0.5
RAY_PEDAL_TAPER_BELOW_TARGET = 0.75
GENESIS_G90_STOP_HOLD_SPEED_BP = [0.0, 0.03, 0.08, 0.16, 0.3, 0.5, 0.8, 1.2, 2.0, 3.0]
GENESIS_G90_STOP_HOLD_ACCEL_V = [-0.10, -0.10, -0.12, -0.18, -0.30, -0.50, -0.75, -1.00, -1.40, -1.80]
GENESIS_G90_STOP_HOLD_RELAX_SPEED_BP = [0.0, 0.08, 0.16, 0.3, 0.5, 0.8, 1.2, 2.0, 3.0]
@@ -79,7 +93,10 @@ BLINDSPOT_WARNING_SOUND_SAMPLES = 36
def egmp_dynamic_longitudinal_tuning(CP) -> bool:
return CP.carFingerprint in (CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV9, CAR.HYUNDAI_IONIQ_5_PE) or \
kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, getattr(CP, "carVin", ""))
kia_ev6_gt_line_longitudinal_tuning(
CP.carFingerprint, getattr(CP, "carVin", ""),
testing_ground.use(KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID),
)
def get_canfd_scc_decel_step(CP) -> float:
@@ -87,7 +104,10 @@ def get_canfd_scc_decel_step(CP) -> float:
def should_reset_ev6_gt_line_longitudinal_tuning(CP, long_control_state: LongCtrlState) -> bool:
return kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, getattr(CP, "carVin", "")) and \
return kia_ev6_gt_line_longitudinal_tuning(
CP.carFingerprint, getattr(CP, "carVin", ""),
testing_ground.use(KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID),
) and \
long_control_state == LongCtrlState.off
@@ -440,6 +460,7 @@ class CarController(CarControllerBase):
self.apply_angle_last = 0.0
self.car_fingerprint = CP.carFingerprint
self.last_button_frame = 0
self.cancel_counter = 0
self.redneck_button_frame = 0
self.ecu_disable_failed = False
self._ecu_disable_checked = False
@@ -453,9 +474,19 @@ class CarController(CarControllerBase):
self._ioniq_6_lane_change_ui_frames = 0
self._ioniq_6_long_tuning = Ioniq6LongitudinalTuningState()
self._genesis_g90_long_tuning = GenesisG90LongitudinalTuningState()
self._can_lead_data = CanLeadDataState()
self._dash_lat_disengage_blink_frame = 0
self._dash_lat_disengage_init = 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
self._ray_pedal = CP.carFingerprint == CAR.KIA_RAY_EV and CP.enableGasInterceptorDEPRECATED
self._ray_pedal_packer = CANPacker("hyundai_kia_ray_pedal") if self._ray_pedal else None
self._ray_pedal_gas_last = 0.0
def _update_dash_icon_state(self, CC):
if CC.latActive:
@@ -476,7 +507,9 @@ class CarController(CarControllerBase):
return lka_icon, lfa_icon
def _get_canfd_scc_lead_state(self, CC, CS, now_nanos):
openpilot_lead_visible = bool(getattr(CS, "openpilot_lead_visible", False) or CC.hudControl.leadVisible)
openpilot_lead_visible = bool(
getattr(CS, "openpilot_lead_visible", False) or getattr(CC.hudControl, "leadVisible", False)
)
openpilot_lead_distance = float(np.clip(getattr(CS, "openpilot_lead_distance", 0.0), 0.0, 204.7))
openpilot_lead_rel_speed = float(np.clip(getattr(CS, "openpilot_lead_rel_speed", 0.0), -16.4, 34.7))
stock_camera_lead_fresh = now_nanos - getattr(CS, "stock_camera_lead_ts", 0) <= CANFD_CAMERA_LEAD_STALE_NS
@@ -631,7 +664,10 @@ class CarController(CarControllerBase):
use_egmp_dynamic_long_tuning = egmp_dynamic_longitudinal_tuning(self.CP) and self.long_active_ecu and \
actuators.longControlState in (LongCtrlState.starting, LongCtrlState.pid, LongCtrlState.stopping)
is_ev6_gt_line = kia_ev6_gt_line_longitudinal_tuning(self.CP.carFingerprint, getattr(self.CP, "carVin", ""))
is_ev6_gt_line = kia_ev6_gt_line_longitudinal_tuning(
self.CP.carFingerprint, getattr(self.CP, "carVin", ""),
testing_ground.use(KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID),
)
is_ccnc_angle_long = self.CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR
if is_ccnc_angle_long and (self._ev9_long_tuning.stop_request or not CC.enabled or CC.cruiseControl.override):
self._ioniq_6_long_tuning = reset_egmp_longitudinal_tuning(self._ioniq_6_long_tuning)
@@ -697,6 +733,8 @@ class CarController(CarControllerBase):
if self.CP.flags & HyundaiFlags.ENABLE_BLINKERS:
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 ***
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,
@@ -722,14 +760,25 @@ class CarController(CarControllerBase):
can_sends = []
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)
longitudinal_active = bool(self.long_active_ecu and getattr(CC, "longActive", False))
lead_visible = bool(getattr(CS, "openpilot_lead_visible", False) or getattr(hud_control, "leadVisible", False))
lead_distance = float(np.clip(getattr(CS, "openpilot_lead_distance", 0.0), 0.0, 204.7))
lead_rel_speed = float(np.clip(getattr(CS, "openpilot_lead_rel_speed", 0.0), -170.0, 239.5))
if lead_visible and lead_distance <= CANFD_LEAD_MIN_DISTANCE:
lead_distance = CANFD_FALLBACK_LEAD_DISTANCE
lead_rel_speed = 0.0
lead_data = self._can_lead_data.update(lead_distance, lead_rel_speed, lead_visible)
# HUD messages
sys_warning, sys_state, left_lane_warning, right_lane_warning = process_hud_alert(CC.enabled, self.car_fingerprint,
stinger_hud_enabled = CC.enabled or (self.CP.carFingerprint == CAR.KIA_STINGER_2022 and CC.latActive)
sys_warning, sys_state, left_lane_warning, right_lane_warning = process_hud_alert(stinger_hud_enabled, self.car_fingerprint,
hud_control)
if blended_hda2:
can_sends.extend(hyundaicanfd.create_steering_messages(
self.packer, self.CP, self.CAN, CC.enabled, apply_steer_req, apply_torque, 0.0,
lka_icon=lka_icon,
longitudinal_active=longitudinal_active,
))
if self.long_active_ecu:
can_sends.extend(hyundaican.create_lkas11_can_canfd_blended(
@@ -739,6 +788,7 @@ class CarController(CarControllerBase):
left_lane_warning, right_lane_warning, CS.msg_364,
include_alerts=False,
counter_mod=0xF,
fcw_opt_usm=2 if apply_steer_req or lka_icon == 3 else 1,
))
if self.frame % 5 == 0:
can_sends.append(hyundaicanfd.create_suppress_lfa(
@@ -750,16 +800,25 @@ class CarController(CarControllerBase):
hud_control.leftLaneVisible, hud_control.rightLaneVisible,
left_lane_warning, right_lane_warning, CS.msg_364))
else:
can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.CP, apply_torque, apply_steer_req,
torque_fault, CS.lkas11, sys_warning, sys_state, CC.enabled,
hud_control.leftLaneVisible, hud_control.rightLaneVisible,
left_lane_warning, right_lane_warning, lka_icon))
if self.CP.carFingerprint != CAR.KIA_RAY_EV or self._ray_lkas11_active:
can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.CP, apply_torque, apply_steer_req,
torque_fault, CS.lkas11, sys_warning, sys_state, CC.enabled,
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
if not self.long_active_ecu:
if CC.cruiseControl.cancel:
if self._ray_pedal and CC.enabled and CS.out.cruiseState.enabled:
if (self.frame - self.last_button_frame) * DT_CTRL > 0.04:
can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP))
self.last_button_frame = self.frame
elif self.cancel_counter > CANCEL_BUTTON_DELAY_FRAMES:
can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP))
elif CC.cruiseControl.resume:
elif CC.cruiseControl.resume and not self._ray_pedal:
# send resume at a max freq of 10Hz
if (self.frame - self.last_button_frame) * DT_CTRL > 0.1:
# send 25 messages at a time to increases the likelihood of resume being accepted
@@ -767,7 +826,39 @@ class CarController(CarControllerBase):
if (self.frame - self.last_button_frame) * DT_CTRL >= 0.15:
self.last_button_frame = self.frame
else:
can_sends.extend(self._create_can_redneck_button_messages(CS))
if not self._ray_pedal:
can_sends.extend(self._create_can_redneck_button_messages(CS))
if self._ray_pedal and self.frame % 4 == 0:
pedal_ready = CS.ray_pedal_valid and CS.ray_pedal_state == 0
pedal_active = (CC.longActive and pedal_ready and not CC.cruiseControl.override and
not CS.out.gasPressed and not CS.out.brakePressed)
if pedal_active:
set_speed = hud_control.setSpeed
if not np.isfinite(set_speed) or set_speed < 1.0:
self._ray_pedal_gas_last = 0.0
else:
speed_error = set_speed - CS.out.vEgo
if speed_error <= -RAY_PEDAL_OVERSPEED_CUTOFF:
self._ray_pedal_gas_last = 0.0
else:
pedal_offset = float(np.interp(CS.out.vEgo, [0., 2., 4., 8., 12., 20.],
[0.08, 0.13, 0.20, 0.32, 0.42, 0.48]))
pedal_gain = 2.0 if accel < 0.0 else 0.22
target = float(np.clip(pedal_offset + accel * pedal_gain, 0.0, RAY_PEDAL_COMMAND_CAP))
if speed_error < 0.0:
target *= float(np.clip(0.65 * (1.0 + speed_error / RAY_PEDAL_OVERSPEED_CUTOFF), 0.0, 1.0))
elif speed_error < RAY_PEDAL_TAPER_BELOW_TARGET:
target *= 0.65 + 0.35 * speed_error / RAY_PEDAL_TAPER_BELOW_TARGET
if target <= 0.001:
self._ray_pedal_gas_last = 0.0
else:
next_gas = rate_limit(target, self._ray_pedal_gas_last, -RAY_PEDAL_RATE_DOWN, RAY_PEDAL_RATE_UP)
self._ray_pedal_gas_last = min(next_gas, target)
else:
self._ray_pedal_gas_last = 0.0
can_sends.append(create_gas_interceptor_command(
self._ray_pedal_packer, self._ray_pedal_gas_last, (self.frame // 4) & 0xF))
if self.long_active_ecu and can_canfd_blended:
if blended_hda2:
@@ -778,6 +869,7 @@ class CarController(CarControllerBase):
# TODO: unclear if this is needed
jerk = 3.0 if actuators.longControlState == LongCtrlState.pid else 1.0
use_fca = self.CP.flags & HyundaiFlags.USE_FCA.value
main_cruise_enabled = getattr(CS, "main_cruise_on", False) if getattr(CS, "main_cruise_tracking", False) else True
if blended_hda2:
stopping = stopping and CS.out.vEgoRaw < 0.1
can_sends.extend(hyundaican.create_acc_commands_can_canfd_blended_hda2(
@@ -793,11 +885,15 @@ class CarController(CarControllerBase):
else:
can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled, accel, jerk, int(self.frame / 2),
hud_control, set_speed_in_units, stopping,
CC.cruiseControl.override, use_fca, self.CP))
CC.cruiseControl.override, use_fca, self.CP,
main_cruise_enabled, lead_data))
# 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)):
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
if self.frame % 20 == 0 and self.long_active_ecu and not can_canfd_blended:
@@ -814,7 +910,14 @@ class CarController(CarControllerBase):
can_sends = []
lka_steering = self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING
lka_steering_long = lka_steering and self.long_active_ecu
persistent_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 persistent_lfa_status_cars else self.long_active_ecu
lka_steering_long = lka_steering and lfa_longitudinal_active
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 \
CC.actuators.longControlState in (LongCtrlState.starting, LongCtrlState.pid, LongCtrlState.stopping)
@@ -825,9 +928,6 @@ class CarController(CarControllerBase):
)
# 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 \
not self.long_active_ecu and self.CP.carFingerprint != CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN and \
preserve_stock_canfd_lkas_status(self.CP.carFingerprint)
@@ -843,10 +943,10 @@ class CarController(CarControllerBase):
gear = getattr(getattr(CS, "out", None), "gearShifter", None)
drive_gear = gear == structs.CarState.GearShifter.drive
if angle_lkas_alt:
if angle_lkas_alt and self.CP.carFingerprint != CAR.KIA_SPORTAGE_HEV_2026:
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)
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))
)
preserve_stock_lfa_status = preserve_stock_canfd_lfa_status(self.CP.carFingerprint)
@@ -856,13 +956,7 @@ class CarController(CarControllerBase):
CS.stock_lfa_msg if preserve_stock_lfa_status else None,
CS.stock_lkas_msg if preserve_stock_lkas else None,
lka_icon=lka_icon,
send_lfa_status=self.ecu_disable_failed and
self.CP.carFingerprint == CAR.KIA_EV9))
elif self.ecu_disable_failed and self.CP.carFingerprint == CAR.KIA_EV9:
can_sends.extend(hyundaicanfd.create_steering_messages(
self.packer, self.CP, self.CAN, CC.enabled, False, 0.0, 0.0,
CS.stock_lfa_msg, lka_icon=lka_icon, send_lfa_status=True, lfa_only=True,
))
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
inactive_steering_angle = float(np.clip(CS.angle_steering_angle,
-self.params.ANGLE_LIMITS.STEER_ANGLE_MAX,
@@ -879,7 +973,7 @@ class CarController(CarControllerBase):
# prevent LFA from activating on LKA steering cars by sending "no lane lines detected" to ADAS ECU
suppress_lfa = bool(lka_steering)
if angle_lkas_alt:
if angle_lkas_alt and self.CP.carFingerprint != CAR.KIA_SPORTAGE_HEV_2026:
suppress_lfa = bool(lka_steering and drive_gear and (CC.latActive or (ccnc_angle_long and CC.enabled)))
if self.frame % 5 == 0 and suppress_lfa:
can_sends.append(hyundaicanfd.create_suppress_lfa(self.packer, self.CAN, CS.lfa_block_msg,
@@ -949,12 +1043,14 @@ class CarController(CarControllerBase):
left_sound_active=left_warning.sound_active, right_sound_active=right_warning.sound_active,
)
else:
adrv_messages = hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame)
adrv_messages = hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame,
car_fingerprint=self.CP.carFingerprint,
drive_gear=drive_gear)
can_sends.extend(adrv_messages)
# The front radar treats ADAS_DRV's 0x100 broadcast as its host heartbeat
# and stops publishing object tracks when it disappears.
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,
CS.out.brakePressed, CS.out.gasPressed,
self.CP.carFingerprint))
@@ -978,10 +1074,23 @@ class CarController(CarControllerBase):
CC.leftBlinker,
CC.rightBlinker))
if self.frame % 2 == 0:
lead_visible, lead_distance, lead_rel_speed = self._get_canfd_scc_lead_state(CC, CS, now_nanos)
if self.CP.carFingerprint == CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN:
acc_kwargs = {}
scc_jerk_limits = get_hyundai_canfd_scc_jerk_limits(self.CP, stopping, accel)
raw_accel = accel
accel = shape_hyundai_canfd_scc_accel(
self.CP, CC.enabled, CC.cruiseControl.override, stopping, accel, self.accel_last,
)
acc_kwargs = {
"direct_accel": True,
"raw_accel": raw_accel,
"jerk_upper": scc_jerk_limits[0],
"jerk_lower": scc_jerk_limits[1],
"lead_distance": lead_distance,
"lead_rel_speed": lead_rel_speed,
"lead_visible": lead_visible,
}
else:
lead_visible, lead_distance, lead_rel_speed = self._get_canfd_scc_lead_state(CC, CS, now_nanos)
acc_kwargs = {
"main_mode_acc": int(CS.out.cruiseState.available),
"direct_accel": True,
@@ -1029,16 +1138,20 @@ class CarController(CarControllerBase):
if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, self.CAN, CS.cruise_info))
self.last_button_frame = self.frame
else:
elif self.cancel_counter > CANCEL_BUTTON_DELAY_FRAMES:
for _ in range(20):
can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, CS.buttons_counter + 1, Buttons.CANCEL))
self.last_button_frame = self.frame
# cruise standstill resume
elif CC.cruiseControl.resume:
if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
# TODO: resume for alt button cars
pass
if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS and self.CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR:
for _ in range(20):
can_sends.append(hyundaicanfd.create_buttons(
self.packer, self.CP, self.CAN, (CS.buttons_counter + 1) % 0x100,
Buttons.RES_ACCEL, base_values=CS.cruise_buttons_msg,
))
self.last_button_frame = self.frame
else:
for _ in range(20):
can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, CS.buttons_counter + 1, Buttons.RES_ACCEL))
+72 -13
View File
@@ -2,6 +2,8 @@ from collections import deque
import copy
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 opendbc.can import CANDefine, CANParser
from opendbc.car import Bus, create_button_events, structs
@@ -9,6 +11,7 @@ from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.values import HyundaiFlags, HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags, CAR, DBC, Buttons, CarControllerParams, \
CANFD_ANGLE_LONGITUDINAL_CAR, CANFD_CORNER_RADAR_BSM_CAR, \
CANFD_ALT_BUTTONS_RESUME_CAR, \
hyundai_cancel_button_enables_cruise, ALT_BUS_LDA_BUTTON_CARS, ALT_BUS_LDA_BUTTON_SWL_STAT_CARS
from opendbc.car.interfaces import CarStateBase
@@ -35,6 +38,8 @@ CLASSIC_MEDIA_BUTTON_CARS = frozenset({
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:
return "LABEL11", "CC_React", "EMS12", "ACC_ACT", "E_EMS11", "Cruise_Limit_Target"
if CP.flags & HyundaiFlags.HYBRID:
@@ -132,6 +137,10 @@ class CarState(CarStateBase):
self.is_metric = False
self.buttons_counter = 0
self.main_cruise_on = False
self.main_cruise_tracking = bool(getattr(FPCP, "flags", 0) & HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING)
if CP.carFingerprint == CAR.KIA_RAY_EV:
self.ray_pedal_state = 5
self.ray_pedal_valid = False
self.cruise_info = {}
self.msg_161 = {}
@@ -140,6 +149,7 @@ class CarState(CarStateBase):
self.msg_364 = {}
self.lfa_block_msg = {}
self.stock_lkas_msg = {}
self.lkas12 = {}
self.stock_lfa_msg = {}
self.stock_lfahda_cluster_msg = {}
self.stock_camera_lead_visible = False
@@ -171,8 +181,10 @@ class CarState(CarStateBase):
# 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)
def update_main_cruise(self, ret: structs.CarState) -> bool:
if any(be.type == ButtonType.mainCruise and be.pressed for be in ret.buttonEvents):
def update_main_cruise(self, ret: structs.CarState,
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
return bool(ret.cruiseState.available and self.main_cruise_on)
@@ -238,10 +250,20 @@ class CarState(CarStateBase):
return button_events
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
elif self.CP.carFingerprint == CAR.HYUNDAI_SONATA_HYBRID:
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:
source_states = (
int(cp.vl["CLU13"]["CF_Clu_LdwsLkasSW"]) if cp.ts_nanos["CLU13"]["CF_Clu_LdwsLkasSW"] > 0 else 0,
@@ -281,6 +303,7 @@ class CarState(CarStateBase):
cp = can_parsers[Bus.pt]
cp_cam = can_parsers[Bus.cam]
cp_alt = can_parsers.get(Bus.alt)
cp_pedal = can_parsers.get(Bus.party)
if self.CP.flags & HyundaiFlags.CANFD:
return self.update_canfd(can_parsers)
@@ -325,6 +348,15 @@ class CarState(CarStateBase):
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
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
no_scc = bool(self.CP.flags & HyundaiFlags.NON_SCC)
if no_scc:
@@ -348,6 +380,9 @@ class CarState(CarStateBase):
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
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.CANFD_LKA_STEERING:
self.lfa_block_msg = copy.copy(cp_cam.vl["CAM_0x2a4"])
@@ -362,6 +397,11 @@ class CarState(CarStateBase):
ret.espDisabled = cp.vl["TCS11"]["TCS_PAS"] == 1
ret.espActive = cp.vl["TCS11"]["ABS_ACT"] == 1
ret.accFaulted = False if no_scc else cp.vl["TCS13"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED
if self.CP.carFingerprint == CAR.KIA_RAY_EV and self.CP.enableGasInterceptorDEPRECATED:
self.ray_pedal_valid = bool(cp_pedal is not None and cp_pedal.can_valid and
cp_pedal.ts_nanos["GAS_SENSOR"]["STATE"] > 0)
self.ray_pedal_state = int(cp_pedal.vl["GAS_SENSOR"]["STATE"]) if cp_pedal is not None else 5
ret.accFaulted = not self.ray_pedal_valid or self.ray_pedal_state != 0
if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV | HyundaiFlags.FCEV):
if self.CP.flags & HyundaiFlags.FCEV:
@@ -373,6 +413,12 @@ class CarState(CarStateBase):
else:
ret.gasPressed = bool(cp.vl["EMS16"]["CF_Ems_AclAct"])
if self.CP.carFingerprint == CAR.KIA_RAY_EV and self.CP.enableGasInterceptorDEPRECATED and self.ray_pedal_valid:
driver_pedal = cp_pedal.vl_raw["GAS_SENSOR"]
track1 = int.from_bytes(driver_pedal[:2], "big")
track2 = int.from_bytes(driver_pedal[2:4], "big")
ret.gasPressed = track1 > 272 or track2 > 513
# Gear Selection via Cluster - For those Kia/Hyundai which are not fully discovered, we can use the Cluster Indicator for Gear Selection,
# as this seems to be standard over all cars, but is not the preferred method.
if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV):
@@ -415,21 +461,22 @@ class CarState(CarStateBase):
self.lkas11 = {}
else:
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.steer_state = cp.vl["MDPS12"]["CF_Mdps_ToiActive"] # 0 NOT ACTIVE, 1 ACTIVE
prev_cruise_buttons = self.cruise_buttons[-1]
prev_main_buttons = self.main_buttons[-1]
prev_lda_button = self.lda_button
if not 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})
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:
lkas_button_events = self.create_alt_bus_lda_button_events(cp_alt)
else:
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),
*create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise}),
*main_button_events,
*lkas_button_events]
ret.blockPcmEnable = not self.recent_button_interaction()
@@ -558,7 +605,9 @@ class CarState(CarStateBase):
self.main_buttons.extend(cp.vl_all[self.cruise_btns_msg_canfd]["ADAPTIVE_CRUISE_MAIN_BTN"])
self.lda_button = cp.vl[self.cruise_btns_msg_canfd]["LDA_BTN"]
self.left_paddle = 0
if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6:
if self.CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR:
self.cruise_buttons_msg = copy.copy(cp.vl[self.cruise_btns_msg_canfd])
elif self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6:
self.cruise_buttons_msg = copy.copy(cp.vl["CRUISE_BUTTONS"])
self.left_paddle = cp.vl["CRUISE_BUTTONS"]["LEFT_PADDLE"]
self.buttons_counter = cp.vl[self.cruise_btns_msg_canfd]["COUNTER"]
@@ -591,7 +640,7 @@ class CarState(CarStateBase):
*create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise}),
*create_button_events(self.lda_button, prev_lda_button, {1: ButtonType.lkas}),
*create_button_events(self.left_paddle, prev_left_paddle, {1: ButtonType.altButton2})]
if self.CP.openpilotLongitudinalControl and self.CP.carFingerprint == CAR.KIA_EV9:
if self.CP.openpilotLongitudinalControl and (self.CP.carFingerprint == CAR.KIA_EV9 or self.main_cruise_tracking):
ret.cruiseState.available = self.update_main_cruise(ret)
ret.blockPcmEnable = not self.recent_button_interaction()
@@ -618,7 +667,9 @@ class CarState(CarStateBase):
def get_can_parsers_canfd(self, CP):
msgs = []
cam_msgs = []
if not (CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS):
if CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR:
msgs.append(("CRUISE_BUTTONS_ALT", 50))
elif not (CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS):
# The EV9 can stop publishing this during the non-ECU-disabled startup
# state. Keep decoding it when present without making CAN invalid.
msgs += [
@@ -694,6 +745,12 @@ class CarState(CarStateBase):
("BCM_PO_11", 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:
# Steering-wheel media switches are event-driven on the refresh Elantra.
msgs.append(("GW_SWRC_PE", 0))
@@ -702,8 +759,10 @@ class CarState(CarStateBase):
parsers = {
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:
parsers[Bus.alt] = CANParser(DBC[CP.carFingerprint][Bus.pt], [("CLU13", 0)], 1)
if CP.carFingerprint == CAR.KIA_RAY_EV and CP.enableGasInterceptorDEPRECATED:
parsers[Bus.party] = CANParser("hyundai_kia_ray_pedal", [("GAS_SENSOR", 50)], 0)
return parsers
@@ -1,4 +1,6 @@
""" 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.hyundai.values import CAR
@@ -183,6 +185,7 @@ FW_VERSIONS = {
],
(Ecu.abs, 0x7d1, None): [
b'\xf1\x00DN ESC \x01 102\x19\x04\x13 58910-L1300',
b'\xf1\x00DN ESC \x01 107 \x07\x03 58910-L1300',
b'\xf1\x00DN ESC \x03 100 \x08\x01 58910-L0300',
b'\xf1\x00DN ESC \x06 104\x19\x08\x01 58910-L0100',
b'\xf1\x00DN ESC \x06 106 \x07\x01 58910-L0100',
@@ -206,6 +209,7 @@ FW_VERSIONS = {
b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC102',
b'\xf1\x00DN8 MDPS C 1.00 1.03 56310-L1010 4DNDC103',
b'\xf1\x00DN8 MDPS C 1.00 1.03 56310-L1030 4DNDC103',
b'\xf1\x00DN8 MDPS C 1.00 1.03 56310-L1210 4DNDC103',
b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP100',
b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP101',
b'\xf1\x00DN8 MDPS R 1.00 1.02 57700-L1000 4DNDP105',
@@ -213,6 +217,7 @@ FW_VERSIONS = {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.02 99211-L1000 190422',
b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.04 99211-L1000 191016',
b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.06 99211-L1000 210325',
b'\xf1\x00DN8 MFC AT RUS LHD 1.00 1.03 99211-L1000 190705',
b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.00 99211-L0000 190716',
b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.01 99211-L0000 191016',
@@ -1402,6 +1407,14 @@ FW_VERSIONS = {
b'\xf1\x00JK__ RDR ----- 1.00 1.01 99110-DS500 ',
],
},
CAR.GENESIS_GV70_2026: {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00JK MFC AT USA LHD 1.00 1.11 99211-IY600 241125',
],
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00JK__ RDR ----- 1.00 1.01 99110-AR600 ',
],
},
CAR.GENESIS_GV80_2025: {
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00JX__ RDR ----- 1.00 1.03 99110-T6500 ',
@@ -1543,6 +1556,7 @@ FW_VERSIONS = {
},
CAR.HYUNDAI_STARIA_4TH_GEN: {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00US4 MFC AT AUS RHD 1.00 1.04 99211-CG000 210819',
b'\xf1\x00US4 MFC AT KOR LHD 1.00 1.06 99211-CG000 230524',
],
(Ecu.fwdRadar, 0x7d0, None): [
@@ -1688,4 +1702,9 @@ FW_VERSIONS = {
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',
],
},
}
+49 -13
View File
@@ -1,5 +1,6 @@
import crcmod
from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.lead_data import CanLeadData
from opendbc.car.hyundai.values import CAR, HyundaiFlags
hyundai_checksum = crcmod.mkCrcFun(0x11D, initCrc=0xFD, rev=False, xorOut=0xdf)
@@ -40,7 +41,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_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_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):
values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1)
values["CF_Lkas_LdwsOpt_USM"] = 2
@@ -51,7 +52,7 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
# FcwOpt_USM 2 = Green car + lanes
# FcwOpt_USM 1 = White car + lanes
# FcwOpt_USM 0 = No car + lanes
values["CF_Lkas_FcwOpt_USM"] = lka_icon if CP.carFingerprint == CAR.GENESIS_G70_2020 else 2 if enabled else 1
values["CF_Lkas_FcwOpt_USM"] = lka_icon
# SysWarning 4 = keep hands on wheel
# SysWarning 5 = keep hands on wheel (red)
@@ -60,7 +61,7 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0
# 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
values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0
@@ -68,7 +69,7 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
# SysState 1-2 = white car + lanes
# SysState 3 = green car + lanes, green steering wheel
# 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
# these have no effect
@@ -80,6 +81,14 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
# Genesis and Optima fault when forwarding while engaged
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]
if CP.flags & HyundaiFlags.CHECKSUM_CRC8:
@@ -98,6 +107,19 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
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):
dat = packer.make_can_msg(addr, bus, values)[1]
return hyundai_checksum(dat[1:8])
@@ -107,13 +129,13 @@ def create_lkas11_can_canfd_blended(packer, frame, CP, apply_steer, steer_req,
torque_fault, lkas11, sys_warning, sys_state, enabled,
left_lane, right_lane,
left_lane_depart, right_lane_depart, msg_364,
include_alerts=True, counter_mod=0x10):
include_alerts=True, counter_mod=0x10, fcw_opt_usm=None):
bus = CanBus(CP).ECAN
values = {
"CF_Lkas_LdwsActivemode": int(left_lane) + (int(right_lane) << 1),
"CF_Lkas_LdwsLHWarning": left_lane_depart,
"CF_Lkas_LdwsRHWarning": right_lane_depart,
"CF_Lkas_FcwOpt_USM": 2 if enabled else 1,
"CF_Lkas_FcwOpt_USM": (2 if enabled else 1) if fcw_opt_usm is None else fcw_opt_usm,
"CR_Lkas_StrToqReq": apply_steer,
"CF_Lkas_ActToi": steer_req,
"CF_Lkas_ToiFlt": torque_fault,
@@ -182,6 +204,17 @@ def create_lfahda_mfc(packer, enabled, frame=None, CP=None, lfa_icon=None):
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,
stopping, long_override, use_fca, CP):
commands = []
@@ -284,19 +317,21 @@ def create_acc_commands_can_canfd_blended_hda2(packer, enabled, accel, accel_las
return commands
def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed, stopping, long_override, use_fca, CP):
def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed, stopping, long_override, use_fca, CP,
main_cruise_enabled=True, lead_data: CanLeadData | None = None):
commands = []
lead_data = lead_data or CanLeadData()
scc11_values = {
"MainMode_ACC": 1,
"MainMode_ACC": int(bool(main_cruise_enabled)),
"TauGapSet": hud_control.leadDistanceBars,
"VSetDis": set_speed if enabled else 0,
"AliveCounterACC": idx % 0x10,
"ObjValid": 1, # close lead makes controls tighter
"ACC_ObjStatus": 1, # close lead makes controls tighter
"ObjValid": int(lead_data.lead_visible),
"ACC_ObjStatus": int(lead_data.lead_visible),
"ACC_ObjLatPos": 0,
"ACC_ObjRelSpd": 0,
"ACC_ObjDist": 1, # close lead makes controls tighter
"ACC_ObjRelSpd": lead_data.lead_rel_speed,
"ACC_ObjDist": int(lead_data.lead_distance),
}
commands.append(packer.make_can_msg("SCC11", 0, scc11_values))
@@ -324,7 +359,8 @@ def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, se
"JerkUpperLimit": upper_jerk, # stock usually is 1.0 but sometimes uses higher values
"JerkLowerLimit": 5.0, # stock usually is 0.5 but sometimes uses higher values
"ACCMode": 2 if enabled and long_override else 1 if enabled else 4, # stock will always be 4 instead of 0 after first disengage
"ObjGap": 2 if hud_control.leadVisible else 0, # 5: >30, m, 4: 25-30 m, 3: 20-25 m, 2: < 20 m, 0: no lead
"ObjGap": lead_data.object_gap, # 5: >30 m, 4: 25-30 m, 3: 20-25 m, 2: <20 m, 0: no lead
"ObjDistStat": lead_data.object_rel_gap,
}
commands.append(packer.make_can_msg("SCC14", 0, scc14_values))
@@ -1,9 +1,38 @@
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
from opendbc.car import CanBusBase, CanData
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.crc import CRC16_XMODEM
from opendbc.car.hyundai.values import HyundaiFlags, CAR
from opendbc.car.hyundai.values import HyundaiFlags, CAR, CANFD_ALT_BUTTONS_RESUME_CAR
_adrv_0x51_templates: dict[CAR, bytes] = {}
def cache_adrv_0x51_template(car_fingerprint: CAR, dat: bytes | None) -> None:
if car_fingerprint not in (CAR.KIA_EV6, CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN):
return
if dat is None:
_adrv_0x51_templates.pop(car_fingerprint, None)
elif len(dat) == 32 and any(dat[3:]):
_adrv_0x51_templates[car_fingerprint] = bytes(dat)
def create_adrv_0x51(packer, CAN, frame: int, car_fingerprint: CAR | None = None, drive_gear: bool = False):
template = _adrv_0x51_templates.get(car_fingerprint)
if template is None:
return packer.make_can_msg("ADRV_0x51", CAN.ACAN, {})
dat = bytearray(template)
dat[2] = (template[2] + frame + 1) & 0xFF
dat[3] = (dat[3] & ~0x1) | int(drive_gear)
crc = hkg_can_fd_checksum(0x51, None, dat)
dat[0] = crc & 0xFF
dat[1] = (crc >> 8) & 0xFF
return CanData(0x51, bytes(dat), CAN.ACAN)
def _set_value(msg: bytearray, sig, ival: int) -> None:
@@ -63,61 +92,6 @@ def _update_checksum(packer, address: int, dat: bytearray) -> None:
_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):
address = packer.dbc.name_to_msg["LFA"].address
dat = packer.pack(address, values)
@@ -153,18 +127,11 @@ 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,
lfa_base_values=None, lkas_base_values=None, lka_icon=None,
send_lfa_status=False, lfa_only=False):
longitudinal_active=None):
if lka_icon is None:
lka_icon = 2 if enabled else 1
if CP.carFingerprint == CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN and CP.flags & HyundaiFlags.CANFD_LKA_STEERING:
ret = []
if CP.openpilotLongitudinalControl or send_lfa_status:
ret.append(_create_gv70_lka_status_msg(packer, CAN, "LFA", CAN.ECAN, enabled, lat_active, apply_torque))
if lfa_only:
return ret
ret.append(_create_gv70_lka_status_msg(packer, CAN, "LKAS", CAN.ACAN, enabled, lat_active, apply_torque))
return ret
if longitudinal_active is None:
longitudinal_active = CP.openpilotLongitudinalControl
angle_lkas_alt = CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING and CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT
@@ -183,7 +150,12 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque,
else:
lkas_values = copy.copy(control_values)
lkas_values["LKA_AVAILABLE"] = 0
if CP.carFingerprint in (CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN):
if CP.carFingerprint in (
CAR.KIA_CARNIVAL_4TH_GEN,
CAR.KIA_CARNIVAL_2025,
CAR.KIA_CARNIVAL_HEV_4TH_GEN,
CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN,
):
lkas_values["DAMP_FACTOR"] = 100
if lfa_base_values:
@@ -202,7 +174,21 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque,
lkas_values["LKAS_ANGLE_ACTIVE"] = 2 if lat_active else 1
lkas_values["ADAS_ACIAnglTqRedcGainVal"] = apply_torque if lat_active else 0.0
if angle_lkas_alt:
if lat_active:
if CP.carFingerprint == CAR.KIA_SPORTAGE_HEV_2026:
lkas_values = {
"LKA_OptUsmSta": 0,
"LKA_SysIndReq": 2 if enabled else 1,
"StrTqReqVal": 0,
"LKA_SysWrn": 0,
"ActToiSta": 0,
"LKA_UsmMod": 0,
"LKA_RcgSta": 3 if lat_active else 0,
"Damping_Gain": 100,
"ADAS_StrAnglReqVal": apply_angle,
"LKAS_ANGLE_ACTIVE": 2 if lat_active else 1,
"ADAS_ACIAnglTqRedcGainVal": apply_torque if lat_active else 0.0,
}
elif lat_active:
lkas_values = {
"LKA_OptUsmSta": 0,
"LKA_RcgSta": 3,
@@ -258,10 +244,8 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque,
ret = []
if CP.flags & HyundaiFlags.CANFD_LKA_STEERING:
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) or send_lfa_status:
if longitudinal_active and not CP.flags & HyundaiFlags.CAN_CANFD_BLENDED:
ret.append(packer.make_can_msg("LFA", CAN.ECAN, lfa_values))
if lfa_only:
return ret
ret.append(packer.make_can_msg(lkas_msg, CAN.ACAN, lkas_values))
else:
if CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
@@ -311,16 +295,27 @@ def create_suppress_lfa(packer, CAN, lfa_block_msg, lka_steering_alt):
def create_buttons(packer, CP, CAN, cnt, btn=0, base_values=None, left_paddle=False, right_paddle=False):
values = {k: v for k, v in base_values.items() if k not in ("_CHECKSUM", "COUNTER")} if base_values else {}
values = {k: v for k, v in base_values.items() if k not in ("CHECKSUM", "_CHECKSUM", "COUNTER")} if base_values else {}
values.update({
"COUNTER": cnt,
"SET_ME_1": 1,
"CRUISE_BUTTONS": btn,
"LEFT_PADDLE": int(left_paddle),
"RIGHT_PADDLE": int(right_paddle),
})
if not (CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS and CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR):
values.update({
"LEFT_PADDLE": int(left_paddle),
"RIGHT_PADDLE": int(right_paddle),
})
bus = CAN.ECAN if CP.flags & HyundaiFlags.CANFD_LKA_STEERING else CAN.CAM
if CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS and CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR:
address, dat, bus = packer.make_can_msg("CRUISE_BUTTONS_ALT", bus, values)
dat = bytearray(dat)
checksum = hkg_can_fd_checksum(address, None, dat)
dat[0] = checksum & 0xFF
dat[1] = (checksum >> 8) & 0xFF
return address, bytes(dat), bus
return packer.make_can_msg("CRUISE_BUTTONS", bus, values)
@@ -736,13 +731,13 @@ def create_ioniq_6_cluster_lane_change_messages(CAN, frame, side=None):
def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_override, set_speed, hud_control,
main_mode_acc=1, jerk_lower=None, jerk_upper=None, direct_accel=False,
lead_distance=None, lead_rel_speed=None, lead_visible=None, cruise_info=None):
lead_distance=None, lead_rel_speed=None, lead_visible=None, cruise_info=None, raw_accel=None):
jerk = 5
jn = jerk / 50
if not enabled or gas_override:
a_val, a_raw = 0, 0
elif direct_accel:
a_raw = accel
a_raw = accel if raw_accel is None else raw_accel
a_val = accel
else:
a_raw = accel
@@ -820,15 +815,13 @@ def create_fca_warning_light(packer, CAN, frame):
return ret
def create_adrv_messages(packer, CAN, frame, blended_hda2=False):
def create_adrv_messages(packer, CAN, frame, blended_hda2=False, car_fingerprint=None, drive_gear=False):
# messages needed to car happy after disabling
# the ADAS Driving ECU to do longitudinal control
ret = []
values = {
}
ret.append(packer.make_can_msg("ADRV_0x51", CAN.ACAN, values))
ret.append(create_adrv_0x51(packer, CAN, frame, car_fingerprint, drive_gear))
if blended_hda2:
return ret
+56 -16
View File
@@ -1,35 +1,50 @@
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.hyundai import hyundaicanfd
from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.values import HyundaiFlags, CAR, CarControllerParams, \
CANFD_UNSUPPORTED_LONGITUDINAL_CAR, \
CANFD_SECURITYACCESS_CAR, \
CANFD_ANGLE_LONGITUDINAL_CAR, \
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, \
CANFD_RADAR_ECU_KEEPALIVE_CAR, \
RADAR_LIVE_LONGITUDINAL_CAR, \
UNSUPPORTED_LONGITUDINAL_CAR, HyundaiSafetyFlags, \
LEGACY_LONGITUDINAL_CAR, \
CAN_CANFD_BLENDED_HDA2_LONGITUDINAL_CAR, \
HyundaiStarPilotSafetyFlags, \
hyundai_cancel_button_enables_cruise, \
kia_ev6_gt_line_longitudinal_tuning
kia_ev6_gt_line_longitudinal_tuning, \
KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID
from opendbc.car.hyundai.radar_interface import get_radar_track_config, radar_tracks_available
from opendbc.car.interfaces import CarInterfaceBase, ACCEL_MIN
from opendbc.car.disable_ecu import disable_ecu, ecu_log
from opendbc.car.hyundai.carcontroller import CarController
from opendbc.car.hyundai.carstate import CarState
from opendbc.car.hyundai.radar_interface import RadarInterface
from openpilot.starpilot.common.testing_grounds import testing_ground
ButtonType = structs.CarState.ButtonEvent.Type
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
ENABLE_BUTTONS = (ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.cancel, ButtonType.mainCruise)
# Track when ECU disable happened - used to permanently suppress CAN errors from disabled ECU
ECU_DISABLE_TIMESTAMP = 0.0
KONA_NON_SCC_FCA_RADAR_ADDR = 0x602
KIA_EV9_ACCEL_MAX = 2.5
KIA_EV9_ACCEL_MAX = 2.2
RAY_PEDAL_SENSOR_ADDR = 0x201
def apply_platform_longitudinal_params(ret: structs.CarParams) -> None:
@@ -46,7 +61,7 @@ def apply_platform_longitudinal_params(ret: structs.CarParams) -> None:
def apply_kia_ev6_gt_line_longitudinal_params(ret: structs.CarParams) -> None:
ret.startAccel = 1.4
ret.longitudinalActuatorDelay = 0.35
ret.longitudinalActuatorDelay = 0.5
ret.vEgoStarting = 0.5
@@ -106,7 +121,8 @@ class CarInterface(CarInterfaceBase):
@staticmethod
def apply_post_fingerprint_params(CP: structs.CarParams, candidate, fingerprint, car_fw) -> None:
if kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin):
gt_line_testing_ground = testing_ground.use(KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID)
if kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin, gt_line_testing_ground):
apply_kia_ev6_gt_line_longitudinal_params(CP)
@staticmethod
@@ -185,6 +201,8 @@ class CarInterface(CarInterfaceBase):
if ret.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
ret.steerControlType = structs.CarParams.SteerControlType.angle
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.CANFD_ANGLE_STEERING.value
if candidate == CAR.KIA_SPORTAGE_HEV_2026:
ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.CANFD_NO_STOCK_LKA.value
if candidate == CAR.HYUNDAI_IONIQ_6:
# Keep lateral active through stops: zeroing torque at standstill dropped the
# stop-turn hold and forced a rate-limit re-ramp from zero on every pull-away
@@ -221,6 +239,9 @@ class CarInterface(CarInterfaceBase):
else:
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:
ret.safetyConfigs[0].safetyParam |= HyundaiSafetyFlags.CAMERA_SCC.value
if candidate in (CAR.HYUNDAI_ELANTRA_2024, CAR.HYUNDAI_ELANTRA_HEV_2024):
@@ -285,6 +306,18 @@ class CarInterface(CarInterfaceBase):
elif ret.flags & HyundaiFlags.FCEV:
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.FCEV_GAS.value
if (candidate == CAR.KIA_RAY_EV and fingerprint[0].get(RAY_PEDAL_SENSOR_ADDR) == 6 and
ret.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CAN_REFRESH_MSGS and
ret.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.HAS_LDA_BUTTON):
ret.enableGasInterceptorDEPRECATED = True
ret.alphaLongitudinalAvailable = True
ret.openpilotLongitudinalControl = True
ret.pcmCruise = False
ret.radarUnavailable = True
ret.autoResumeSng = False
ret.minEnableSpeed = -1.0
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.LONG.value
# Car specific configuration overrides
if candidate == CAR.GENESIS_G90:
@@ -300,8 +333,8 @@ class CarInterface(CarInterfaceBase):
if candidate == CAR.HYUNDAI_ELANTRA_2021:
ret.longitudinalActuatorDelay = 0.22
ret.stopAccel = -0.85
ret.stoppingDecelRate = 0.35
ret.stopAccel = -1.1
ret.stoppingDecelRate = 0.55
if candidate == CAR.HYUNDAI_ELANTRA_HEV_2024:
ret.longitudinalActuatorDelay = 0.22
@@ -345,14 +378,7 @@ class CarInterface(CarInterfaceBase):
params = Params()
if communication_control is None:
if CP.carFingerprint in CANFD_RADAR_LIVE_LONGITUDINAL_CAR:
# 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])
communication_control = get_communication_control_request(CP.carFingerprint)
ecu_log(f"=== init() called: opLong={CP.openpilotLongitudinalControl}, flags=0x{CP.flags:x}, safetyParam={CP.safetyConfigs[-1].safetyParam} ===")
@@ -368,11 +394,25 @@ class CarInterface(CarInterfaceBase):
skip_disable_ecu = True
if not skip_disable_ecu:
disable_can_recv = can_recv
if CP.carFingerprint in (CAR.KIA_EV6, CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN) and can_recv is not None:
hyundaicanfd.cache_adrv_0x51_template(CP.carFingerprint, None)
base_can_recv = can_recv
adrv_bus = CanBus(CP).ACAN
def disable_can_recv(*args, **kwargs):
packets = base_can_recv(*args, **kwargs)
for packet in packets or []:
for msg in packet:
if msg.src == adrv_bus and msg.address == 0x51:
hyundaicanfd.cache_adrv_0x51_template(CP.carFingerprint, msg.dat)
return packets
# Try ECU disable. If it succeeds (IGN-ON mode), enable longitudinal.
# If it fails (READY mode returns NRC 0x22, or timeout), strip LONG safety flag
# so panda forwards stock SCC messages normally (lateral-only mode).
ecu_log(f"=== ECU DISABLE attempt: addr=0x{addr:x}, bus={bus} ===")
ecu_disabled = disable_ecu(can_recv, can_send, bus=bus, addr=addr, com_cont_req=communication_control,
ecu_disabled = disable_ecu(disable_can_recv, can_send, bus=bus, addr=addr, com_cont_req=communication_control,
reset=bool(CP.flags & HyundaiFlags.CAN_CANFD_BLENDED))
if CP.carFingerprint in (CAR.HYUNDAI_IONIQ_6, CAR.HYUNDAI_IONIQ_5_PE):
@@ -0,0 +1,63 @@
from dataclasses import dataclass
@dataclass(frozen=True)
class CanLeadData:
object_gap: int = 0
lead_distance: float = 0.0
lead_rel_speed: float = 0.0
lead_visible: bool = False
@property
def object_rel_gap(self) -> int:
return 0 if self.lead_distance == 0 else 2 if self.lead_rel_speed < -0.2 else 1
def _hysteresis_update(current, new_value, counter, threshold):
if new_value == current:
return current, 0
counter += 1
return (new_value, 0) if counter >= threshold else (current, counter)
class CanLeadDataState:
LEAD_HYSTERESIS_FRAMES = 50
def __init__(self):
self._lead_on_counter = 0
self._lead_off_counter = 0
self._gap_counter = 0
self._lead_visible = False
self._object_gap = 0
@staticmethod
def _get_object_gap(lead_distance: float) -> int:
if lead_distance == 0:
return 0
if lead_distance < 20:
return 2
if lead_distance < 25:
return 3
if lead_distance < 30:
return 4
return 5
def update(self, lead_distance: float, lead_rel_speed: float, lead_visible: bool) -> CanLeadData:
counter = self._lead_on_counter if lead_visible else self._lead_off_counter
self._lead_visible, counter = _hysteresis_update(
self._lead_visible, lead_visible, counter, self.LEAD_HYSTERESIS_FRAMES,
)
if lead_visible:
self._lead_on_counter = counter
self._lead_off_counter = 0
else:
self._lead_off_counter = counter
self._lead_on_counter = 0
object_gap = self._get_object_gap(lead_distance)
self._object_gap, self._gap_counter = _hysteresis_update(
self._object_gap, object_gap, self._gap_counter, self.LEAD_HYSTERESIS_FRAMES,
)
return CanLeadData(self._object_gap, lead_distance, lead_rel_speed, self._lead_visible)
@@ -19,6 +19,9 @@ MRR30_RADAR_START_ADDR = 0x210
MRR30_RADAR_MSG_COUNT = 16
MRR35_RADAR_START_ADDR = 0x3A5
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)
@@ -30,6 +33,7 @@ class RadarTrackConfig:
frequency: int = 50
parser_msg_count: int | None = None
expected_length: int | None = None
dbc_name: str | None = None
@property
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:
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)
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)
@@ -65,6 +73,10 @@ def radar_tracks_available(radar_config: RadarTrackConfig | None, fingerprint) -
if radar_config is None:
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)
if msg_len is None:
return False
@@ -78,7 +90,8 @@ def get_radar_can_parser(CP, radar_config):
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)]
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):
@@ -223,6 +236,27 @@ class RadarInterface(RadarInterfaceBase):
del self.pts[track_key]
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":
for i in ("1", "2"):
track_key = addr * 2 + int(i) - 1
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,291 @@
from types import SimpleNamespace
import pytest
from opendbc.can import CANPacker, CANParser
from opendbc.car import Bus, gen_empty_fingerprint
from opendbc.car.hyundai.carcontroller import CarController
from opendbc.car.hyundai.carstate import CarState
from opendbc.car.hyundai.interface import CarInterface
from opendbc.car.hyundai.values import CAR, DBC, HyundaiSafetyFlags
from opendbc.car.structs import CarControl
def ray_fingerprint(sensor_length=6, lfa_length=8):
fingerprint = gen_empty_fingerprint()
fingerprint[0][0x201] = sensor_length
fingerprint[0][0x391] = 8
fingerprint[2][0x485] = lfa_length
return fingerprint
@pytest.mark.parametrize(("candidate", "fingerprint", "has_pedal"), [
(CAR.KIA_RAY_EV, ray_fingerprint(), True),
(CAR.KIA_RAY_EV, ray_fingerprint(sensor_length=8), False),
(CAR.KIA_RAY_EV, ray_fingerprint(lfa_length=4), False),
(CAR.HYUNDAI_KONA_EV_NON_SCC, ray_fingerprint(), False),
])
def test_ray_pedal_fingerprint_isolation(candidate, fingerprint, has_pedal):
CP = CarInterface.get_params(candidate, fingerprint, [], False, False, False, None)
assert CP.enableGasInterceptorDEPRECATED is has_pedal
assert CP.openpilotLongitudinalControl is has_pedal
if has_pedal:
assert not CP.pcmCruise
assert CP.safetyConfigs[-1].safetyParam == 0x9405
assert CP.minEnableSpeed == -1.0
assert not CP.autoResumeSng
FPCP = CarInterface.get_starpilot_params(candidate, fingerprint, [], CP, SimpleNamespace())
assert FPCP.canUsePedal
assert not FPCP.pcmCruiseSpeed
assert not FPCP.redneckCruiseAvailable
else:
assert CP.pcmCruise
assert not (CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.LONG)
def test_ray_pedal_safety_signature_is_unique_across_hyundai_platforms():
for candidate in CAR:
for alpha_long in (False, True):
CP = CarInterface.get_params(candidate, ray_fingerprint(), [],
alpha_long, False, False, None)
has_ray_signature = (CP.safetyConfigs[-1].safetyParam & ~(32 | 128 | 2048)) == 0x9405
assert has_ray_signature is (candidate == CAR.KIA_RAY_EV)
assert CP.enableGasInterceptorDEPRECATED is (candidate == CAR.KIA_RAY_EV)
def test_ray_pedal_parser_validates_actual_route_frames():
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(), [], False, False, False, None)
parser = CarState(CP, None).get_can_parsers(CP)[Bus.party]
assert parser.dbc_name == "hyundai_kia_ray_pedal"
# Consecutive bus-0 GAS_SENSOR frames from Sept. 15 Ray rlog segment 4.
samples = [bytes.fromhex(s) for s in (
"01f403d55de8", "01f603d55ef1", "01f403d55f51", "01f603d3503f",
"01f903d551ab", "01f903d552a4", "01f703d55370",
)]
for idx, dat in enumerate(samples):
parser.update([(1_000_000_000 + idx * 20_000_000, [(0x201, dat, 0)])])
assert parser.can_valid
assert parser.vl["GAS_SENSOR"]["STATE"] == 5 # FAULT_TIMEOUT: no 0x200 was sent
prior = parser.vl_raw["GAS_SENSOR"]
bad = bytearray(samples[-1])
bad[-1] ^= 1
parser.update([(1_160_000_000, [(0x201, bytes(bad), 0)])])
assert parser.vl_raw["GAS_SENSOR"] == prior
def test_ray_pedal_fault_clears_only_with_healthy_sensor_state():
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(), [], False, False, False, None)
state = CarState(CP, None)
parsers = state.get_can_parsers(CP)
packer = CANPacker("hyundai_kia_ray_pedal")
sensor = packer.make_can_msg("GAS_SENSOR", 0, {
"INTERCEPTOR_GAS": 0, "INTERCEPTOR_GAS2": 0,
"STATE": 0, "COUNTER_PEDAL": 1,
})
for parser in parsers.values():
parser.update([(1_000_000_000, [sensor])])
ret, _ = state.update(parsers, SimpleNamespace())
assert state.ray_pedal_valid
assert state.ray_pedal_state == 0
assert not ret.accFaulted
def test_ray_driver_override_uses_physical_interceptor_tracks():
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(), [], False, False, False, None)
state = CarState(CP, None)
parsers = state.get_can_parsers(CP)
native_gas = (0x371, bytes.fromhex("004e008000ae0700"), 0)
physical_rest = (0x201, bytes.fromhex("010801f30cef"), 0)
for parser in parsers.values():
parser.update([(1_000_000_000, [native_gas, physical_rest])])
ret, _ = state.update(parsers, SimpleNamespace())
assert state.ray_pedal_valid
assert not ret.gasPressed
packer = CANPacker("hyundai_kia_ray_pedal")
physical_press = packer.make_can_msg("GAS_SENSOR", 0, {
"INTERCEPTOR_GAS": (310 - 264) * 0.672,
"INTERCEPTOR_GAS2": (593 - 497) * 0.332,
"STATE": 0, "COUNTER_PEDAL": 13,
})
for parser in parsers.values():
parser.update([(1_020_000_000, [physical_press])])
ret, _ = state.update(parsers, SimpleNamespace())
assert ret.gasPressed
def test_ray_without_pedal_keeps_native_gas_detection():
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(sensor_length=8), [], False, False, False, None)
assert not CP.enableGasInterceptorDEPRECATED
state = CarState(CP, None)
parsers = state.get_can_parsers(CP)
assert Bus.party not in parsers
native_gas = (0x371, bytes.fromhex("004e008000ae0700"), 0)
for parser in parsers.values():
parser.update([(1_000_000_000, [native_gas])])
ret, _ = state.update(parsers, SimpleNamespace())
assert ret.gasPressed
@pytest.mark.parametrize("speed", [0.0, 0.1, 1.0, 4.9, 5.0, 12.0])
def test_ray_controller_heartbeats_and_only_actuates_when_ready(speed):
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(), [], False, False, False, None)
controller = CarController(DBC[CP.carFingerprint], CP)
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0), ("CLU11", 0)], 0)
CS = SimpleNamespace(
lkas11=parser.vl["LKAS11"], clu11=parser.vl["CLU11"],
out=SimpleNamespace(vEgo=speed, gasPressed=False, brakePressed=False,
cruiseState=SimpleNamespace(enabled=False)),
ray_pedal_valid=True, ray_pedal_state=5, is_metric=True,
)
CC = SimpleNamespace(
enabled=True, longActive=True, latActive=True,
cruiseControl=SimpleNamespace(cancel=False, resume=False, override=False),
)
hud = SimpleNamespace(
visualAlert=CarControl.HUDControl.VisualAlert.none,
setSpeed=20.0,
leftLaneVisible=True, rightLaneVisible=True,
leftLaneDepart=False, rightLaneDepart=False,
)
actuators = SimpleNamespace(longControlState=CarControl.Actuators.LongControlState.pid)
def pedal_msg(accel, frame):
controller.frame = frame
messages = controller.create_can_msgs(True, 0, False, 0.0, accel, False,
hud, actuators, CS, CC, 2, 0)
return next(dat for addr, dat, bus in messages if addr == 0x200 and bus == 0)
assert pedal_msg(2.0, 0)[:4] == bytes(4) # fault timeout: heartbeat only
CS.ray_pedal_state = 0
assert pedal_msg(2.0, 4)[:4] != bytes(4)
CS.out.gasPressed = True
assert pedal_msg(2.0, 8)[:4] == bytes(4)
CS.out.gasPressed = False
assert pedal_msg(-1.0, 12)[:4] == bytes(4) # decel = EV lift/regen, not gas
CS.out.cruiseState.enabled = True
controller.frame = 16
messages = controller.create_can_msgs(True, 0, False, 0.0, 2.0, False,
hud, actuators, CS, CC, 2, 0)
assert next(dat for addr, dat, bus in messages if addr == 0x200 and bus == 0)[4] & 0x80
assert any(addr == 0x4F1 and bus == 0 and dat[0] & 7 == 4 for addr, dat, bus in messages)
CS.out.cruiseState.enabled = False
CS.out.brakePressed = True
assert pedal_msg(2.0, 20)[:4] == bytes(4)
CS.out.brakePressed = False
assert pedal_msg(2.0, 24)[4] & 0x80
assert controller._ray_pedal_gas_last == pytest.approx(0.02)
assert pedal_msg(2.0, 28)[4] & 0x80
assert controller._ray_pedal_gas_last == pytest.approx(0.04)
CS.out.brakePressed = True
assert pedal_msg(2.0, 32)[:4] == bytes(4)
assert controller._ray_pedal_gas_last == 0.0
CS.out.brakePressed = False
assert pedal_msg(2.0, 36)[4] & 0x80
assert controller._ray_pedal_gas_last == pytest.approx(0.02)
CC.longActive = False
assert pedal_msg(2.0, 40)[:4] == bytes(4)
CC.longActive = True
CC.cruiseControl.override = True
assert pedal_msg(2.0, 44)[:4] == bytes(4)
CC.cruiseControl.override = False
CS.ray_pedal_valid = False
assert pedal_msg(2.0, 48)[:4] == bytes(4)
CS.ray_pedal_valid = True
for fault in range(1, 6):
CS.ray_pedal_state = fault
assert pedal_msg(2.0, 48 + 4 * fault)[:4] == bytes(4)
CS.ray_pedal_state = 0
CS.out.vEgo = 10.0
assert pedal_msg(0.0, 72)[4] & 0x80
assert pedal_msg(-1.0, 76)[:4] == bytes(4)
CS.out.vEgo = 12.0
for frame in range(80, 80 + 4 * 40, 4):
pedal_msg(1.5, frame)
assert controller._ray_pedal_gas_last == pytest.approx(0.55)
for frame in range(240, 240 + 4 * 12, 4):
dat = pedal_msg(-1.5, frame)
assert dat[:4] == bytes(4)
for frame in range(288, 288 + 4 * 40, 4):
pedal_msg(1.5, frame)
assert controller._ray_pedal_gas_last == pytest.approx(0.55)
hud.setSpeed = 12.0
pedal_msg(1.5, 448)
assert controller._ray_pedal_gas_last == pytest.approx(0.55 * 0.65)
hud.setSpeed = 11.8
dat = pedal_msg(1.5, 452)
assert controller._ray_pedal_gas_last == pytest.approx(0.55 * 0.65 * 0.6)
assert dat[4] & 0x80
hud.setSpeed = 11.4
assert pedal_msg(1.5, 456)[:4] == bytes(4)
hud.setSpeed = float('nan')
assert pedal_msg(1.5, 460)[:4] == bytes(4)
hud.setSpeed = 20.0
assert pedal_msg(-0.3, 464)[:4] == bytes(4)
CS.out.vEgo = 15.0
hud.setSpeed = 53.0 / 3.6
pedal_msg(-0.16, 468)
assert controller._ray_pedal_gas_last < 0.1
CS.out.vEgo = 12.0
hud.setSpeed = 8.0 / 3.6
assert pedal_msg(-0.3, 472)[:4] == bytes(4)
hud.setSpeed = 145.0 / 3.6
assert pedal_msg(1.5, 476)[4] & 0x80
assert controller._ray_pedal_gas_last == pytest.approx(0.02)
assert pedal_msg(1.5, 480)[4] & 0x80
assert controller._ray_pedal_gas_last == pytest.approx(0.04)
assert pedal_msg(-0.3, 484)[:4] == bytes(4)
@pytest.mark.parametrize("candidate", [CAR.KIA_RAY_EV, CAR.HYUNDAI_KONA_EV_NON_SCC])
def test_ray_stock_cruise_cancellation_survives_accelerator_override(candidate):
CP = CarInterface.get_params(candidate, ray_fingerprint(), [], False, False, False, None)
controller = CarController(DBC[CP.carFingerprint], CP)
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0), ("CLU11", 0)], 0)
CS = SimpleNamespace(
lkas11=parser.vl["LKAS11"], clu11=parser.vl["CLU11"],
out=SimpleNamespace(vEgo=12.0, gasPressed=True, brakePressed=False,
cruiseState=SimpleNamespace(enabled=True)),
ray_pedal_valid=True, ray_pedal_state=0, is_metric=True,
)
CC = SimpleNamespace(
enabled=True, longActive=False, latActive=True,
cruiseControl=SimpleNamespace(cancel=False, resume=False, override=True),
)
hud = SimpleNamespace(
visualAlert=CarControl.HUDControl.VisualAlert.none,
setSpeed=20.0,
leftLaneVisible=True, rightLaneVisible=True, leftLaneDepart=False, rightLaneDepart=False,
)
actuators = SimpleNamespace(longControlState=CarControl.Actuators.LongControlState.off)
controller._create_can_redneck_button_messages = lambda _: []
def messages(frame):
controller.frame = frame
return controller.create_can_msgs(True, 0, False, 0.0, 2.0, False,
hud, actuators, CS, CC, 2, 0)
def cancel_frames(msgs):
return [dat for addr, dat, bus in msgs if addr == 0x4F1 and bus == 0 and dat[0] & 7 == 4]
msgs = messages(20)
assert bool(cancel_frames(msgs)) is (candidate == CAR.KIA_RAY_EV)
if candidate == CAR.KIA_RAY_EV:
pedal = next(dat for addr, dat, bus in msgs if addr == 0x200 and bus == 0)
assert pedal[:4] == bytes(4)
assert not (pedal[4] & 0x80)
assert not cancel_frames(messages(24)) # retain the existing cancellation rate limit
assert cancel_frames(messages(25))
assert cancel_frames(messages(32))
CS.out.cruiseState.enabled = False
assert not cancel_frames(messages(44))
CS.out.cruiseState.enabled = True
CC.enabled = False
assert not cancel_frames(messages(56)) # AOL alone must not cancel native cruise
+52 -7
View File
@@ -2,6 +2,8 @@ import re
from dataclasses import dataclass, field
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.lateral import AngleSteeringLimits, ISO_LATERAL_ACCEL
from opendbc.car.common.conversions import Conversions as CV
@@ -45,8 +47,12 @@ class CarControllerParams:
self.STEER_DRIVER_MULTIPLIER = 2
self.STEER_THRESHOLD = 100
if vEgoRaw < 15.0: # below ~34 mph - more aggressive for tight turns
self.STEER_DELTA_UP = 10
self.STEER_DELTA_DOWN = 8
if CP.carFingerprint == CAR.KIA_CARNIVAL_HEV_4TH_GEN:
self.STEER_DELTA_UP = 2
self.STEER_DELTA_DOWN = 3
else:
self.STEER_DELTA_UP = 10
self.STEER_DELTA_DOWN = 8
else:
self.STEER_DELTA_UP = 2
self.STEER_DELTA_DOWN = 3
@@ -111,6 +117,9 @@ class HyundaiSafetyFlags(IntFlag):
class HyundaiStarPilotSafetyFlags(IntFlag):
CANFD_NO_STOCK_LKA = 4096 # CAN-FD only; classic CAN uses this bit for NON_SCC.
AOL_MAIN_LKAS_ON_ENGAGE = 128
AOL_MAIN_LKAS_SYNC = 32
HAS_LDA_BUTTON = 1024
AOL_LKAS_ON_ENGAGE = 2048
@@ -118,6 +127,7 @@ class HyundaiStarPilotSafetyFlags(IntFlag):
class HyundaiStarPilotFlags(IntFlag):
SPEED_LIMIT_AVAILABLE = 1
MAIN_CRUISE_STATE_TRACKING = 2 ** 2
HAS_LKAS12 = 2 ** 9
class HyundaiFlags(IntFlag):
@@ -871,6 +881,16 @@ class CAR(Platforms):
flags=HyundaiFlags.EV | HyundaiFlags.CANFD_ANGLE_STEERING,
radar_dbc=HYUNDAI_MRR35_RADAR_DBC,
)
GENESIS_GV70_2026 = HyundaiCanFDPlatformConfig(
[
HyundaiCarDocs("Genesis GV70 (3.5T Sport Prestige Trim, with HDA II & LFA2) 2026",
"Highway Driving Assist II & Lane Follow Assist 2",
car_parts=CarParts.common([CarHarness.hyundai_m])),
],
GENESIS_GV70_1ST_GEN.specs,
flags=HyundaiFlags.CANFD_ANGLE_STEERING,
radar_dbc=HYUNDAI_MRR35_RADAR_DBC,
)
GENESIS_G80 = HyundaiPlatformConfig(
[HyundaiCarDocs("Genesis G80 2018-19", "All", car_parts=CarParts.common([CarHarness.hyundai_h]))],
CarSpecs(mass=2060, wheelbase=3.01, steerRatio=16.5),
@@ -929,6 +949,11 @@ class CAR(Platforms):
HYUNDAI_KONA_EV.specs,
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(
[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),
@@ -974,6 +999,11 @@ CAN_CANFD_BLENDED_HDA2_LONGITUDINAL_CAR = frozenset({
KIA_EV6_GT_LINE_LONG_TUNING_VDS_PREFIXES = frozenset({
"C4DLC",
})
KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID = "5"
KIA_RAY_EV_VIN_VDS_PREFIXES = frozenset({
"CG81A",
})
ALT_BUS_LDA_BUTTON_CARS = frozenset()
@@ -984,9 +1014,13 @@ def hyundai_cancel_button_enables_cruise(car_fingerprint) -> bool:
return car_fingerprint in CANCEL_BUTTON_ENABLE_CARS
def kia_ev6_gt_line_longitudinal_tuning(car_fingerprint, vin: str) -> bool:
return car_fingerprint == CAR.KIA_EV6 and isinstance(vin, str) and \
len(vin) == 17 and vin[3:8] in KIA_EV6_GT_LINE_LONG_TUNING_VDS_PREFIXES
def kia_ev6_gt_line_longitudinal_tuning(car_fingerprint, vin: str, testing_ground_active: bool = False) -> bool:
vin_match = isinstance(vin, str) and len(vin) == 17 and vin[3:8] in KIA_EV6_GT_LINE_LONG_TUNING_VDS_PREFIXES
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]]:
@@ -1078,7 +1112,7 @@ PART_NUMBER_FW_PATTERN = re.compile(b'(?<=[0-9][.,][0-9]{2} )([0-9]{5}[-/]?[A-Z]
# We've seen both ICE and hybrid for these platforms, and they have hybrid descriptors (e.g. MQ4 vs MQ4H)
CANFD_FUZZY_WHITELIST = {CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KIA_K8_HEV_1ST_GEN,
CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN,
CAR.KIA_SORENTO_HEV_4TH_GEN_LFA2}
CAR.KIA_SORENTO_HEV_4TH_GEN_LFA2, CAR.GENESIS_GV70_2026}
# List of ECUs expected to have platform codes, camera and radar should exist on all cars
# TODO: use abs, it has the platform code and part number on many platforms
@@ -1181,9 +1215,17 @@ CANFD_SECURITYACCESS_CAR = {
}
CANFD_UNSUPPORTED_LONGITUDINAL_CAR = CAR.with_flags(HyundaiFlags.CANFD_NO_RADAR_DISABLE) - CANFD_SECURITYACCESS_CAR # TODO: merge with UNSUPPORTED_LONGITUDINAL_CAR
CANFD_ANGLE_LONGITUDINAL_CAR = {CAR.KIA_EV9, CAR.HYUNDAI_IONIQ_5_PE}
CANFD_ALT_BUTTONS_RESUME_CAR = {CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN}
CANFD_CORNER_RADAR_BSM_CAR = {CAR.HYUNDAI_IONIQ_6, CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9}
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.GENESIS_GV70_ELECTRIFIED_1ST_GEN,
}
CANFD_RADAR_ECU_KEEPALIVE_CAR = {
CAR.HYUNDAI_IONIQ_5_PE,
CAR.HYUNDAI_IONIQ_6,
CAR.KIA_EV9,
CAR.GENESIS_GV60_EV_1ST_GEN,
}
RADAR_LIVE_LONGITUDINAL_CAR = CANFD_RADAR_LIVE_LONGITUDINAL_CAR | {
CAR.HYUNDAI_IONIQ,
@@ -1211,6 +1253,9 @@ NON_SCC_CAR = CAR.with_flags(HyundaiFlags.NON_SCC)
# HyundaiFlags.CANFD_RADAR_SCC | HyundaiFlags.CANFD_NO_RADAR_DISABLE | )
UNSUPPORTED_LONGITUDINAL_CAR = CAR.with_flags(HyundaiFlags.LEGACY) | CAR.with_flags(HyundaiFlags.UNSUPPORTED_LONGITUDINAL)
LEGACY_LONGITUDINAL_CAR = {CAR.KIA_XCEED_PHEV}
LEGACY_LONGITUDINAL_CAR = {
CAR.GENESIS_G80,
CAR.KIA_XCEED_PHEV,
}
DBC = CAR.create_dbc_map()
+53 -5
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.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.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.values import PLATFORMS
from opendbc.can import CANParser
@@ -109,6 +109,7 @@ class RadarInterfaceBase(ABC):
self.CP = CP
self.rcp = None
self.pts: dict[int, structs.RadarData.RadarPoint] = {}
self.track_id: int = 0
self.frame = 0
def update(self, can_packets: list[tuple[int, list[CanData]]]) -> structs.RadarDataT | None:
@@ -232,6 +233,9 @@ class CarInterfaceBase(ABC):
fp_ret.flags |= int(HondaStarPilotFlags.HAS_CAMERA_MESSAGES)
elif platform in HYUNDAI:
if candidate == HYUNDAI.KIA_RAY_EV and CP.enableGasInterceptorDEPRECATED:
fp_ret.canUsePedal = True
fp_ret.pcmCruiseSpeed = False
if candidate in CANFD_CAR:
hda2 = Ecu.adas in [fw.ecu for fw in car_fw]
CAN = CanBus(None, fingerprint, bool(CP.flags & HyundaiFlags.CANFD_LKA_STEERING))
@@ -240,10 +244,31 @@ class CarInterfaceBase(ABC):
if 0x1FA in fingerprint[CAN.ECAN]:
fp_ret.flags |= HyundaiStarPilotFlags.SPEED_LIMIT_AVAILABLE.value
fp_ret.redneckCruiseAvailable = bool(CP.flags & HyundaiFlags.NON_SCC) and not bool(CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS)
if candidate != HYUNDAI.KIA_RAY_EV and not (CP.flags & HyundaiFlags.CANFD) and 0x53E in fingerprint[2] and \
(candidate != HYUNDAI.KIA_STINGER_2022 or fingerprint[2][0x53E] == 6):
fp_ret.flags |= HyundaiStarPilotFlags.HAS_LKAS12.value
sportage_stock_scc_buttons = (
candidate == HYUNDAI.KIA_SPORTAGE_HEV_2026 and
not CP.openpilotLongitudinalControl and
bool(CP.flags & HyundaiFlags.CANFD_LKA_STEERING) and
not bool(CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS) and
fingerprint[CAN.ECAN].get(0x1CF) == 8 and
0x1AA not in fingerprint[CAN.ECAN]
)
fp_ret.redneckCruiseAvailable = (
(bool(CP.flags & HyundaiFlags.NON_SCC) and
not bool(CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS) and
not (candidate == HYUNDAI.KIA_RAY_EV and CP.enableGasInterceptorDEPRECATED)) or
sportage_stock_scc_buttons
)
if fp_ret.redneckCruiseAvailable and params.get_bool("RedneckCruise"):
fp_ret.pcmCruiseSpeed = False
CP.openpilotLongitudinalControl = True
if CP.flags & HyundaiFlags.NON_SCC:
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 (
0x391 in fingerprint[0] or
@@ -257,10 +282,25 @@ class CarInterfaceBase(ABC):
if getattr(starpilot_toggles, "always_on_lateral_lkas", False):
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
# LKASButtonControl == 9 means BUTTON_FUNCTIONS["AOL_TOGGLE"] in starpilot_variables.
if params.get_bool("AlwaysOnLateral") and params.get_int("LKASButtonControl") == 9:
if candidate in (HYUNDAI.HYUNDAI_ELANTRA_HEV_2024, HYUNDAI.HYUNDAI_SONATA_HYBRID) and \
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
if candidate == HYUNDAI.KIA_STINGER_2022 and CP.openpilotLongitudinalControl:
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
if candidate == HYUNDAI.HYUNDAI_SONATA_HYBRID and getattr(starpilot_toggles, "always_on_lateral_lkas", False) and \
getattr(starpilot_toggles, "main_cruise_aol_toggle", False):
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_SYNC.value
elif platform in TOYOTA:
fp_ret.canUsePedal = not CP.autoResumeSng
fp_ret.canUseSDSU = candidate not in UNSUPPORTED_DSU_CAR and candidate not in TSS2_CAR
@@ -282,6 +322,14 @@ class CarInterfaceBase(ABC):
if getattr(starpilot_toggles, "subaru_sng", False):
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
@staticmethod
+7 -7
View File
@@ -15,19 +15,19 @@ LEAF_ADAS_ECU_BUS = 0
LEAF_ADAS_COMMAND_BUS = 1
LEAF_ADAS_COMMAND_ADDRS = frozenset((0x1C3, 0x2B0))
LEAF_2025_SV_PLUS_CAMERA_FW = b'6WK2CDB\x04\x18\x00\x00\x00\x00\x00R=1\x18\x99\x10\x00\x00\x00\x80'
LEAF_2025_SV_PLUS_ALPHA_LONG_ENABLED = False
# This Leaf camera uses KWP2000 rather than UDS for session management.
LEAF_KWP_DATA_MONITOR_REQUEST = b"\x10\xF0"
LEAF_KWP_DATA_MONITOR_RESPONSE = b"\x50\xF0"
LEAF_KWP_DISABLE_NORMAL_TX = b"\x28\x01"
LEAF_KWP_EXTENDED_REQUEST = b"\x10\xC0"
LEAF_KWP_EXTENDED_RESPONSE = b"\x50\xC0"
LEAF_KWP_DISABLE_NORMAL_TX_NO_RESPONSE = b"\x28\x02"
LEAF_KWP_TAKEOVER_SESSIONS = (
(LEAF_KWP_DATA_MONITOR_REQUEST, LEAF_KWP_DATA_MONITOR_RESPONSE),
(LEAF_KWP_EXTENDED_REQUEST, LEAF_KWP_EXTENDED_RESPONSE),
)
def is_leaf_2025_sv_plus_longitudinal(candidate, car_fw):
return candidate == CAR.NISSAN_LEAF and any(
return LEAF_2025_SV_PLUS_ALPHA_LONG_ENABLED and candidate == CAR.NISSAN_LEAF and any(
fw.address == LEAF_ADAS_ECU_ADDR and bytes(fw.fwVersion) == LEAF_2025_SV_PLUS_CAMERA_FW
for fw in car_fw
)
@@ -161,7 +161,7 @@ class CarInterface(CarInterfaceBase):
for diag_request, diag_response in LEAF_KWP_TAKEOVER_SESSIONS:
ecu_log(f"Nissan Leaf ADAS takeover using KWP session {diag_request.hex()}")
ecu_disabled = disable_ecu(can_recv, can_send, bus=LEAF_ADAS_ECU_BUS, addr=LEAF_ADAS_ECU_ADDR,
com_cont_req=LEAF_KWP_DISABLE_NORMAL_TX, require_response=True, retry=1,
com_cont_req=LEAF_KWP_DISABLE_NORMAL_TX_NO_RESPONSE, require_response=False, retry=1,
diag_request=diag_request, diag_response=diag_response, response_offset=NISSAN_RX_OFFSET)
if ecu_disabled:
break
@@ -4,6 +4,7 @@ import pytest
from opendbc.car import Bus, ButtonType, gen_empty_fingerprint, structs
from opendbc.car.can_definitions import CanData
from opendbc.car.nissan import interface as nissan_interface
from opendbc.car.nissan.carstate import CarState
from opendbc.car.nissan.interface import CarInterface, LEAF_2025_SV_PLUS_CAMERA_FW, leaf_adas_commands_present, \
leaf_adas_commands_silent, restore_leaf_adas_tx
@@ -18,6 +19,12 @@ SUPPORTED_LEAF_FW = [structs.CarParams.CarFw(
)]
@pytest.fixture
def experimental_leaf_long(monkeypatch):
"""Exercise the dormant implementation without making it available in production."""
monkeypatch.setattr(nissan_interface, "LEAF_2025_SV_PLUS_ALPHA_LONG_ENABLED", True)
def run_controller(alpha_long, accel=0.0, long_active=True, long_state=structs.CarControl.Actuators.LongControlState.pid):
CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, alpha_long, False, False, TEST_TOGGLES)
FPCP = CarInterface.get_starpilot_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, CP, TEST_TOGGLES)
@@ -33,7 +40,28 @@ def run_controller(alpha_long, accel=0.0, long_active=True, long_state=structs.C
return {msg[0]: msg for msg in can_sends}
def test_leaf_2025_sv_plus_alpha_long_params():
def test_leaf_2025_sv_plus_alpha_long_is_disabled(monkeypatch):
stock = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, False, False, False, None)
alpha_long = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, True, False, False, None)
assert not stock.alphaLongitudinalAvailable
assert not stock.openpilotLongitudinalControl
assert stock.pcmCruise
assert not (stock.safetyConfigs[-1].safetyParam & NissanSafetyFlags.LONG_CONTROL)
assert not alpha_long.alphaLongitudinalAvailable
assert not alpha_long.openpilotLongitudinalControl
assert alpha_long.pcmCruise
assert not alpha_long.autoResumeSng
assert not (alpha_long.safetyConfigs[-1].safetyParam & NissanSafetyFlags.LONG_CONTROL)
disable_calls = []
monkeypatch.setattr("opendbc.car.nissan.interface.disable_ecu", lambda *args, **kwargs: disable_calls.append((args, kwargs)))
CarInterface.init(alpha_long, None, None)
assert not disable_calls
def test_dormant_leaf_2025_sv_plus_alpha_long_params(experimental_leaf_long):
stock = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, False, False, False, None)
alpha_long = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, True, False, False, None)
@@ -79,7 +107,13 @@ def test_stock_controller_does_not_send_longitudinal_messages():
assert not ({0x2B0, 0x1C3, 0x707} & can_sends.keys())
def test_alpha_long_controller_sends_stock_shaped_commands_and_keepalive():
def test_disabled_alpha_long_controller_does_not_send_longitudinal_messages():
can_sends = run_controller(True)
assert not ({0x2B0, 0x1C3, 0x707} & can_sends.keys())
def test_alpha_long_controller_sends_stock_shaped_commands_and_keepalive(experimental_leaf_long):
can_sends = run_controller(True)
assert can_sends[0x2B0][1].hex() == "ff6090ac5b000e03"
@@ -89,13 +123,13 @@ def test_alpha_long_controller_sends_stock_shaped_commands_and_keepalive():
assert can_sends[0x707][2] == 0
def test_alpha_long_controller_clamps_to_panda_accel_limit():
def test_alpha_long_controller_clamps_to_panda_accel_limit(experimental_leaf_long):
can_sends = run_controller(True, accel=5.0)
assert can_sends[0x2B0][1].hex() == "007f8fac5b000e0c"
def test_alpha_long_controller_blends_friction_brake_below_regen_limit():
def test_alpha_long_controller_blends_friction_brake_below_regen_limit(experimental_leaf_long):
can_sends = run_controller(True, accel=-2.0)
assert can_sends[0x2B0][1].hex() == "a827d5ac5b000e09"
@@ -104,7 +138,7 @@ def test_alpha_long_controller_blends_friction_brake_below_regen_limit():
assert brake[5] & 0x84 == 0x84
def test_alpha_long_controller_sends_inactive_commands_when_disengaged():
def test_alpha_long_controller_sends_inactive_commands_when_disengaged(experimental_leaf_long):
can_sends = run_controller(True, accel=1.0, long_active=False)
assert can_sends[0x2B0][1].hex() == "dc53a2ac1b000e03"
@@ -113,7 +147,7 @@ def test_alpha_long_controller_sends_inactive_commands_when_disengaged():
@pytest.mark.parametrize(("signal", "button_type"), [("SET_BUTTON", ButtonType.decelCruise),
("RES_BUTTON", ButtonType.accelCruise)])
def test_leaf_set_resume_release_enables_alpha_long(signal, button_type):
def test_leaf_set_resume_release_enables_alpha_long(signal, button_type, experimental_leaf_long):
CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, True, False, False, TEST_TOGGLES)
FPCP = CarInterface.get_starpilot_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, CP, TEST_TOGGLES)
CS = CarState(CP, FPCP)
@@ -130,7 +164,7 @@ def test_leaf_set_resume_release_enables_alpha_long(signal, button_type):
@pytest.mark.parametrize("ecu_disabled", [False, True])
def test_leaf_ecu_disable_is_strict_and_falls_back(monkeypatch, ecu_disabled):
def test_leaf_ecu_disable_is_strict_and_falls_back(monkeypatch, ecu_disabled, experimental_leaf_long):
CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, True, False, False, None)
calls = []
@@ -148,17 +182,17 @@ def test_leaf_ecu_disable_is_strict_and_falls_back(monkeypatch, ecu_disabled):
assert calls[0]["addr"] == 0x707
assert calls[0]["bus"] == 0
assert calls[0]["response_offset"] == 0x20
assert calls[0]["require_response"] is True
assert calls[0]["diag_request"] == b"\x10\xf0"
assert calls[0]["diag_response"] == b"\x50\xf0"
assert calls[0]["com_cont_req"] == b"\x28\x01"
assert calls[0]["require_response"] is False
assert calls[0]["diag_request"] == b"\x10\xc0"
assert calls[0]["diag_response"] == b"\x50\xc0"
assert calls[0]["com_cont_req"] == b"\x28\x02"
assert calls[0]["retry"] == 1
assert CP.openpilotLongitudinalControl is ecu_disabled
assert CP.pcmCruise is not ecu_disabled
assert bool(CP.safetyConfigs[-1].safetyParam & NissanSafetyFlags.LONG_CONTROL) is ecu_disabled
def test_leaf_kwp_data_monitor_session_can_confirm_ecu_disable(monkeypatch):
def test_leaf_kwp_no_response_disable_can_confirm_ecu_silence(monkeypatch, experimental_leaf_long):
CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, True, False, False, None)
monkeypatch.setattr("opendbc.car.nissan.interface.disable_ecu", lambda *args, **kwargs: True)
@@ -171,7 +205,7 @@ def test_leaf_kwp_data_monitor_session_can_confirm_ecu_disable(monkeypatch):
assert CP.safetyConfigs[-1].safetyParam & NissanSafetyFlags.LONG_CONTROL
def test_leaf_positive_disable_response_without_command_silence_falls_back(monkeypatch):
def test_leaf_positive_disable_response_without_command_silence_falls_back(monkeypatch, experimental_leaf_long):
CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, True, False, False, None)
restore_calls = []
+95
View File
@@ -0,0 +1,95 @@
"""One bounded Legacy AVH ON request; 0x32B is status, never a TX command."""
AVH_REQUEST = 0x6BB
AVH_STATUS = 0x32B
INPUTS = (AVH_REQUEST, AVH_STATUS, 0x40, 0x48, 0x13A, 0x174)
def checksum(address, data):
return ((address & 0xFF) + (address >> 8) + sum(data[1:])) & 0xFF
def avh_request(template, step):
if len(template) != 8 or checksum(AVH_REQUEST, template) != template[0] or template[2] & 3 or step not in (1, 2):
raise ValueError("Invalid AVH template or counter step")
data = bytearray(template)
data[1] = (data[1] & 0xF0) | ((data[1] + step) & 0xF)
data[2] |= 2
data[0] = checksum(AVH_REQUEST, data)
return AVH_REQUEST, bytes(data), 1
class AvhStartup:
def __init__(self):
self.started = None
self.last_time = None
self.stable_since = None
self.frames = {}
self.done = False
self.followup = None
def update(self, now, frames, enabled, can_valid, controls_active):
if self.started is None:
self.started = now
if self.last_time is not None and now < self.last_time:
self.done = True
self.last_time = now
if self.done:
return []
if now - self.started > 30 or controls_active:
self.done = True
return []
for address, (timestamp, data) in frames.items():
if address not in INPUTS or timestamp <= 0:
continue
previous = self.frames.get(address)
if previous and timestamp == previous[0]:
continue
if len(data) != 8 or checksum(address, data) != data[0] or timestamp > now or (previous and timestamp < previous[0]):
self.done = True
return []
if (address == AVH_REQUEST and data[2] & 3) or (address == AVH_STATUS and data[5] & 0x20) or \
(address == 0x48 and data[3] != 4) or (address == 0x40 and data[4]) or \
(address == 0x13A and any((int.from_bytes(data, 'little') >> bit) & 0x1FFF for bit in (12, 25, 38, 51))):
self.done = True
return []
if previous and (data[1] & 15) == (previous[1][1] & 15):
continue # duplicate counters cannot refresh freshness
# Controller snapshots can skip 50/100 Hz samples between updates. Panda
# checks their full counter stream; require consecutive head-unit frames here.
sequential = bool(previous and (address not in (AVH_REQUEST, AVH_STATUS) or
(data[1] & 15) == ((previous[1][1] + 1) & 15)))
self.frames[address] = (timestamp, data, sequential)
fresh = all(a in self.frames and self.frames[a][2] and
0 <= now - self.frames[a][0] <= (1.5 if a == AVH_REQUEST else 0.3) for a in INPUTS)
if not enabled or not can_valid or not fresh:
self.stable_since = None
if self.followup is not None:
self.done = True
return []
throttle = self.frames[0x40][1]
rpm = int.from_bytes(throttle[2:4], 'little') & 0x1FFF
if rpm < 400 or not self.frames[0x174][1][2] & 8:
self.stable_since = None
if self.followup is not None:
self.done = True
return []
if self.stable_since is None:
self.stable_since = now
if self.followup is not None:
sent, timestamp, template = self.followup
if now - sent > 0.075 or self.frames[AVH_REQUEST][0] != timestamp:
self.done = True
elif now - sent >= 0.05:
self.done = True
return [avh_request(template, 2)]
return []
if now - self.started < 10 or now - self.stable_since < 3:
return []
timestamp, template, _ = self.frames[AVH_REQUEST]
if now - timestamp > 0.010:
return []
self.followup = (now, timestamp, template)
return [avh_request(template, 1)]
+186 -163
View File
@@ -4,7 +4,8 @@ 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.interfaces import CarControllerBase
from opendbc.car.subaru import subarucan
from opendbc.car.subaru.values import CAR, DBC, GLOBAL_ES_ADDR, CanBus, CarControllerParams, SubaruFlags
from opendbc.car.subaru.avh import AvhStartup
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
# FIXME: These limits aren't exact. The real limit is more than likely over a larger time period and
@@ -16,20 +17,19 @@ _SNG_ACC_MIN_DIST = 3
_SNG_ACC_MAX_DIST = 4.5
_LEGACY_2025_MADS_MIN_SPEED = 0.44704
_LEGACY_2025_MADS_MAX_STEER_ANGLE = 120.0
_LEGACY_2025_OVERRIDE_HOLD_FRAMES = 10
_LEGACY_2025_REENGAGE_SETTLE_FRAMES = 8
_LEGACY_2025_REENGAGE_MAX_STEER_RATE = 2.0
_LEGACY_2025_REENGAGE_MAX_ANGLE_DELTA = 1.0
_LEGACY_2025_RECLAIM_FRAMES = 36
_LEGACY_2025_RECLAIM_EXPONENT = 2.5
_ANGLE_OVERRIDE_HOLD_FRAMES = 10
_ANGLE_REENGAGE_SETTLE_FRAMES = 8
_ANGLE_OVERRIDE_CONFIRM_FRAMES = 2
_ANGLE_REENGAGE_MAX_STEER_RATE = 2.0
_ANGLE_REENGAGE_MAX_ANGLE_DELTA = 1.0
_ANGLE_RECLAIM_FRAMES = 36
_ANGLE_RECLAIM_EXPONENT = 2.5
_ANGLE_MADS_MIN_SPEED = 0.44704
_ANGLE_MADS_MAX_STEER_ANGLE = 120.0
_ASCENT_AOL_ARM_FRAMES = 30
_STOP_START_STARTUP_DELAY_FRAMES = 100
# 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_PERIOD_FRAMES = 5
_REDNECK_BUTTON_INTERVAL_FRAMES = 10
_REDNECK_BUTTON_COPIES = 2
def get_safety_CP():
@@ -43,20 +43,11 @@ class CarController(CarControllerBase):
self.apply_torque_last = 0
self.apply_steer_last = 0
self.driver_override = False
self.legacy_2025_lkas_active = False
self.legacy_2025_handoff_active = False
self.legacy_2025_override_hold_frames = 0
self.legacy_2025_reengage_settle_frames = 0
self.legacy_2025_reengage_reference_angle = 0.0
self.legacy_2025_reclaim_frames = 0
self.legacy_2025_reclaim_start_angle = 0.0
self.angle_override_confirm_frames = 0
self.angle_lkas_active = False
self.angle_handoff_active = False
self.angle_override_hold_frames = 0
self.angle_reengage_settle_frames = 0
self.angle_reengage_reference_angle = 0.0
self.angle_reclaim_frames = 0
self.angle_reclaim_start_angle = 0.0
self.ascent_angle_initialized = False
self.ascent_aol_arm_frames = 0
self.cruise_button_prev = 0
self.steer_rate_counter = 0
@@ -67,138 +58,128 @@ class CarController(CarControllerBase):
self.angle_bus = CanBus.angle_for_cp(CP)
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.prev_close_distance = 0
self.epb_resume_frames_remaining = -1
self.last_standstill_frame = 0
self.stop_start_attempted = False
self.stop_start_request_started = False
self.stop_start_request_frame = 0
self.stop_start_initial_state = None
self.stop_start_counter = 0
self.stop_start_acknowledged = False
self.last_redneck_button_frame = 0
self.avh_startup = AvhStartup()
def _reset_legacy_2025_handoff(self):
self.legacy_2025_handoff_active = False
self.legacy_2025_override_hold_frames = 0
self.legacy_2025_reengage_settle_frames = 0
self.legacy_2025_reengage_reference_angle = 0.0
self.legacy_2025_reclaim_frames = 0
self.legacy_2025_reclaim_start_angle = 0.0
def _stop_start_off_request(self, CC, CS, starpilot_toggles):
"""Send one bounded Subaru Stop/Start OFF request after ignition.
def _legacy_2025_manual_handoff(self, CS, lkas_available):
if not lkas_available:
self._reset_legacy_2025_handoff()
return False
This is intentionally opt-in and limited to a stationary vehicle in
Park/Neutral. A single ignition session gets at most one attempt.
"""
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:
return None
if CS.out.steeringPressed:
self.legacy_2025_handoff_active = True
self.legacy_2025_override_hold_frames = _LEGACY_2025_OVERRIDE_HOLD_FRAMES
self.legacy_2025_reengage_settle_frames = 0
self.legacy_2025_reengage_reference_angle = CS.out.steeringAngleDeg
self.legacy_2025_reclaim_frames = 0
return True
if self.frame > _STOP_START_STARTUP_DEADLINE_FRAMES or getattr(CC, "enabled", False):
self.stop_start_attempted = True
return None
if not self.legacy_2025_handoff_active and not self.legacy_2025_lkas_active and \
abs(CS.out.steeringRateDeg) > _LEGACY_2025_REENGAGE_MAX_STEER_RATE:
self.legacy_2025_handoff_active = True
self.legacy_2025_reengage_reference_angle = CS.out.steeringAngleDeg
if self.frame < _STOP_START_STARTUP_DELAY_FRAMES or not getattr(getattr(CS, "out", None), "canValid", True):
return None
if not self.legacy_2025_handoff_active:
return False
out = CS.out
if not getattr(out, "standstill", False) or out.gearShifter not in (
structs.CarState.GearShifter.park,
structs.CarState.GearShifter.neutral,
):
return None
if self.legacy_2025_override_hold_frames > 0:
self.legacy_2025_override_hold_frames -= 1
if self.legacy_2025_override_hold_frames == 0:
self.legacy_2025_reengage_reference_angle = CS.out.steeringAngleDeg
return True
dashlights_msg = getattr(CS, "dashlights_msg", None)
if not dashlights_msg:
return None
wheel_stable = abs(CS.out.steeringRateDeg) <= _LEGACY_2025_REENGAGE_MAX_STEER_RATE and \
abs(CS.out.steeringAngleDeg - self.legacy_2025_reengage_reference_angle) <= _LEGACY_2025_REENGAGE_MAX_ANGLE_DELTA
if wheel_stable:
self.legacy_2025_reengage_settle_frames += 1
else:
self.legacy_2025_reengage_settle_frames = 0
self.legacy_2025_reengage_reference_angle = CS.out.steeringAngleDeg
if not self.stop_start_request_started:
self.stop_start_request_started = True
self.stop_start_request_frame = self.frame
self.stop_start_initial_state = getattr(CS, "stop_start_state", None)
self.stop_start_counter = (int(dashlights_msg.get("COUNTER", 0)) + 1) % 0x10
if self.legacy_2025_reengage_settle_frames < _LEGACY_2025_REENGAGE_SETTLE_FRAMES:
return True
current_state = getattr(CS, "stop_start_state", None)
if self.stop_start_initial_state is not None and self.stop_start_initial_state != 3 and current_state == 3:
self.stop_start_attempted = True
self.stop_start_acknowledged = True
return None
self.legacy_2025_handoff_active = False
self.legacy_2025_reengage_settle_frames = 0
self.legacy_2025_reclaim_frames = _LEGACY_2025_RECLAIM_FRAMES
self.legacy_2025_reclaim_start_angle = CS.out.steeringAngleDeg
return True
elapsed = self.frame - self.stop_start_request_frame
if elapsed >= _STOP_START_PULSE_FRAMES:
self.stop_start_attempted = True
return None
def _legacy_2025_reclaim_target(self, target_angle):
if self.legacy_2025_reclaim_frames <= 0:
return target_angle
if elapsed % _STOP_START_PULSE_PERIOD_FRAMES != 0:
return None
progress = (_LEGACY_2025_RECLAIM_FRAMES - self.legacy_2025_reclaim_frames + 1) / _LEGACY_2025_RECLAIM_FRAMES
eased_progress = progress ** _LEGACY_2025_RECLAIM_EXPONENT
target_angle = self.legacy_2025_reclaim_start_angle + eased_progress * \
(target_angle - self.legacy_2025_reclaim_start_angle)
self.legacy_2025_reclaim_frames -= 1
return target_angle
msg = subarucan.create_stop_start_control(
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
return msg
def _reset_angle_handoff(self):
self.driver_override = False
self.angle_override_confirm_frames = 0
self.angle_handoff_active = False
self.angle_override_hold_frames = 0
self.angle_reengage_settle_frames = 0
self.angle_reengage_reference_angle = 0.0
self.angle_reclaim_frames = 0
self.angle_reclaim_start_angle = 0.0
def _angle_manual_handoff(self, CS, lat_active):
if not lat_active:
self._reset_angle_handoff()
return False
if CS.out.steeringPressed:
driver_override = self._update_angle_driver_override(CS)
steering_rate = abs(getattr(CS.out, "steeringRateDeg", 0.0))
if driver_override:
self.angle_handoff_active = True
self.angle_override_hold_frames = _ANGLE_OVERRIDE_HOLD_FRAMES
self.angle_reengage_settle_frames = 0
self.angle_reengage_reference_angle = CS.out.steeringAngleDeg
self.angle_reclaim_frames = 0
return True
if not self.angle_handoff_active and not self.angle_lkas_active and \
abs(CS.out.steeringRateDeg) > _ANGLE_REENGAGE_MAX_STEER_RATE:
self.angle_handoff_active = True
self.angle_reengage_reference_angle = CS.out.steeringAngleDeg
if self.angle_handoff_active:
if steering_rate > _ANGLE_REENGAGE_MAX_STEER_RATE:
return True
if not self.angle_handoff_active:
self.angle_handoff_active = False
return True
if not self.angle_lkas_active and steering_rate > _ANGLE_REENGAGE_MAX_STEER_RATE:
self.angle_handoff_active = True
return True
return False
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 _ascent_aol_ready(self, ready):
if not ready:
self.ascent_aol_arm_frames = 0
return False
if self.angle_override_hold_frames > 0:
self.angle_override_hold_frames -= 1
if self.angle_override_hold_frames == 0:
self.angle_reengage_reference_angle = CS.out.steeringAngleDeg
return True
wheel_stable = abs(CS.out.steeringRateDeg) <= _ANGLE_REENGAGE_MAX_STEER_RATE and \
abs(CS.out.steeringAngleDeg - self.angle_reengage_reference_angle) <= _ANGLE_REENGAGE_MAX_ANGLE_DELTA
if wheel_stable:
self.angle_reengage_settle_frames += 1
else:
self.angle_reengage_settle_frames = 0
self.angle_reengage_reference_angle = CS.out.steeringAngleDeg
if self.angle_reengage_settle_frames < _ANGLE_REENGAGE_SETTLE_FRAMES:
return True
self.angle_handoff_active = False
self.angle_reengage_settle_frames = 0
self.angle_reclaim_frames = _ANGLE_RECLAIM_FRAMES
self.angle_reclaim_start_angle = CS.out.steeringAngleDeg
return True
def _angle_reclaim_target(self, target_angle):
if self.angle_reclaim_frames <= 0:
return target_angle
progress = (_ANGLE_RECLAIM_FRAMES - self.angle_reclaim_frames + 1) / _ANGLE_RECLAIM_FRAMES
eased_progress = progress ** _ANGLE_RECLAIM_EXPONENT
target_angle = self.angle_reclaim_start_angle + eased_progress * \
(target_angle - self.angle_reclaim_start_angle)
self.angle_reclaim_frames -= 1
return target_angle
self.ascent_aol_arm_frames = min(self.ascent_aol_arm_frames + 1, _ASCENT_AOL_ARM_FRAMES)
return self.ascent_aol_arm_frames >= _ASCENT_AOL_ARM_FRAMES
def lateral_angle(self, CC, CS):
if self.CP.carFingerprint == CAR.SUBARU_LEGACY_2025:
@@ -208,12 +189,11 @@ class CarController(CarControllerBase):
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
manual_handoff = self._legacy_2025_manual_handoff(CS, lkas_available)
manual_handoff = self._angle_manual_handoff(CS, lkas_available)
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
apply_steer = apply_std_steer_angle_limits(
steer_target,
CC.actuators.steeringAngleDeg,
self.apply_steer_last,
CS.out.vEgoRaw,
CS.out.steeringAngleDeg,
@@ -221,53 +201,58 @@ class CarController(CarControllerBase):
self.p.LEGACY_2025_ANGLE_LIMITS,
)
self.apply_steer_last = apply_steer
self.legacy_2025_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)
if self.CP.carFingerprint in (CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023):
if self.CP.carFingerprint == CAR.SUBARU_ASCENT_2023 and not self.ascent_angle_initialized:
self.apply_steer_last = CS.out.steeringAngleDeg
self.ascent_angle_initialized = True
mads_only = CC.latActive and not CC.enabled
mads_only_ok = CS.out.vEgoRaw > _ANGLE_MADS_MIN_SPEED and \
abs(CS.out.steeringAngleDeg) < _ANGLE_MADS_MAX_STEER_ANGLE
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
if self.CP.carFingerprint == CAR.SUBARU_ASCENT_2023:
if mads_only:
cruise_available = getattr(getattr(CS.out, "cruiseState", None), "available", True)
lkas_available = self._ascent_aol_ready(lkas_available and cruise_available)
else:
self.ascent_aol_arm_frames = _ASCENT_AOL_ARM_FRAMES if lkas_available else 0
manual_handoff = self._angle_manual_handoff(CS, lkas_available)
if self.CP.carFingerprint == CAR.SUBARU_OUTBACK_2023:
manual_handoff = not self.angle_lkas_active and \
abs(getattr(CS.out, "steeringRateDeg", 0.0)) > _ANGLE_REENGAGE_MAX_STEER_RATE
else:
manual_handoff = self._angle_manual_handoff(CS, lkas_available)
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 and self.CP.carFingerprint != CAR.SUBARU_ASCENT_2023:
self.apply_steer_last = CS.out.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:
apply_steer = apply_std_steer_angle_limits(
steer_target,
self.apply_steer_last,
CS.out.vEgoRaw,
CS.out.steeringAngleDeg,
lkas_active,
self.p.FIXED_ANGLE_LIMITS,
)
else:
apply_steer = apply_steer_angle_limits_vm(
steer_target,
self.apply_steer_last,
CS.out.vEgoRaw,
CS.out.steeringAngleDeg,
lkas_active,
self.p,
self.VM,
)
apply_steer = apply_std_steer_angle_limits(
CC.actuators.steeringAngleDeg,
self.apply_steer_last,
CS.out.vEgoRaw,
CS.out.steeringAngleDeg,
lkas_active,
self.p.FIXED_ANGLE_LIMITS,
)
self.apply_steer_last = apply_steer
self.angle_lkas_active = lkas_active
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
lat_active = CC.latActive and not self.driver_override
mads_only = CC.latActive and not getattr(CC, "enabled", False)
mads_only_ok = CS.out.vEgoRaw > _ANGLE_MADS_MIN_SPEED and \
abs(CS.out.steeringAngleDeg) < _ANGLE_MADS_MAX_STEER_ANGLE
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 \
not getattr(CS.out, "standstill", False)
manual_handoff = self._angle_manual_handoff(CS, lkas_available)
lat_active = lkas_available and not self.driver_override and not manual_handoff
if lat_active and not self.angle_lkas_active:
self.apply_steer_last = CS.out.steeringAngleDeg
apply_steer = apply_steer_angle_limits_vm(
CC.actuators.steeringAngleDeg,
self.apply_steer_last,
@@ -282,6 +267,7 @@ class CarController(CarControllerBase):
apply_steer = CS.out.steeringAngleDeg
self.apply_steer_last = apply_steer
self.angle_lkas_active = lat_active
return subarucan.create_steering_control_angle(self.packer, apply_steer, lat_active, self.angle_bus)
def lateral_torque(self, CC, CS):
@@ -309,13 +295,33 @@ class CarController(CarControllerBase):
return subarucan.create_steering_control(self.packer, apply_torque, apply_steer_req)
def _lkas_status_active(self, CC):
if self.CP.carFingerprint in (CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023):
return self.angle_lkas_active
return CC.latActive
def update(self, CC, CS, now_nanos, starpilot_toggles):
actuators = CC.actuators
hud_control = CC.hudControl
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 = []
stop_start_msg = self._stop_start_off_request(CC, CS, starpilot_toggles)
if stop_start_msg is not None:
can_sends.append(stop_start_msg)
if self.CP.carFingerprint == CAR.SUBARU_LEGACY_2025:
can_sends.extend(self.avh_startup.update(
now_nanos / 1e9, getattr(CS, "avh_frames", {}),
getattr(starpilot_toggles, "subaru_avh_on", False), getattr(CS.out, "canValid", False),
CC.enabled or CC.latActive or CC.longActive,
))
# *** steering ***
if (self.frame % self.p.STEER_STEP) == 0:
if self.CP.flags & SubaruFlags.LKAS_ANGLE:
@@ -373,12 +379,15 @@ class CarController(CarControllerBase):
else:
if self.frame % 10 == 0:
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))
can_sends.append(subarucan.create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, CC.latActive, hud_control.visualAlert,
hud_control.leftLaneVisible, hud_control.rightLaneVisible,
hud_control.leftLaneDepart, hud_control.rightLaneDepart, self.status_bus))
can_sends.append(subarucan.create_es_lkas_state(
self.packer, self.frame // 10, CS.es_lkas_state_msg, self._lkas_status_active(CC), hud_control.visualAlert,
hud_control.leftLaneVisible, hud_control.rightLaneVisible,
hud_control.leftLaneDepart, hud_control.rightLaneDepart, self.status_bus,
))
if self.CP.flags & SubaruFlags.SEND_INFOTAINMENT:
can_sends.append(subarucan.create_es_infotainment(self.packer, self.frame // 10, CS.es_infotainment_msg,
@@ -391,7 +400,7 @@ class CarController(CarControllerBase):
can_sends.append(subarucan.create_brake_pedal(self.packer, self.frame // 2, CS.brake_pedal_msg,
speed_cmd, pcm_cancel_cmd))
if self.CP.openpilotLongitudinalControl:
if self.CP.openpilotLongitudinalControl and not subaru_redneck_cruise:
if self.frame % 5 == 0:
can_sends.append(subarucan.create_es_status(self.packer, self.frame // 5, CS.es_status_msg,
self.CP.openpilotLongitudinalControl, CC.longActive, cruise_rpm))
@@ -407,6 +416,20 @@ class CarController(CarControllerBase):
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))
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:
# Tester present (keeps eyesight disabled)
if self.frame % 100 == 0:
+45 -6
View File
@@ -1,11 +1,20 @@
import copy
from cereal import custom
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.interfaces import CarStateBase
from opendbc.car.subaru.values import DBC, CanBus, SubaruFlags
from opendbc.car.subaru.values import CAR, DBC, CanBus, SUBARU_REDNECK_CRUISE_CARS, SUBARU_STOP_START_CARS, SubaruFlags
from opendbc.car import CanSignalRateCalculator
from opendbc.car.subaru.avh import INPUTS as AVH_INPUTS
ButtonType = structs.CarState.ButtonEvent.Type
SUBARU_CRUISE_BUTTONS = {
"Main": ButtonType.mainCruise,
"Set": ButtonType.decelCruise,
"Resume": ButtonType.accelCruise,
}
class CarState(CarStateBase):
@@ -15,6 +24,12 @@ class CarState(CarStateBase):
self.shifter_values = can_define.dv["Transmission"]["Gear"]
self.angle_rate_calulator = CanSignalRateCalculator(50)
self.dashlights_msg = {}
self.dashlights_dat = b""
self.stop_start_state = 0
self.avh_frames = {}
self.cruise_buttons_msg = {}
self.cruise_buttons = {button: 0 for button in SUBARU_CRUISE_BUTTONS}
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
cp = can_parsers[Bus.pt]
@@ -24,6 +39,15 @@ class CarState(CarStateBase):
cp_angle = cp_main if self.CP.flags & SubaruFlags.D_PLATFORM else cp
ret = structs.CarState()
if self.CP.carFingerprint == CAR.SUBARU_LEGACY_2025:
self.avh_frames = {a: (cp_alt.ts_nanos[a]["CHECKSUM"] / 1e9, cp_alt.vl_raw[a]) for a in AVH_INPUTS}
if self.CP.carFingerprint in SUBARU_STOP_START_CARS:
stop_start_cp = cp_alt if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp
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"]
ret.gasPressed = throttle_msg["Throttle_Pedal"] > 1e-5
if self.CP.flags & SubaruFlags.PREGLOBAL:
@@ -66,14 +90,14 @@ class CarState(CarStateBase):
if self.CP.flags & SubaruFlags.LKAS_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:
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):
# 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.steeringTorqueEps = cp_angle.vl["Steering_Torque"]["Steer_Torque_Output"]
@@ -127,6 +151,17 @@ class CarState(CarStateBase):
self.es_status_msg = copy.copy(cp_es_brake.vl["ES_Status"])
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):
self.es_distance_msg = copy.copy(cp_es_distance.vl["ES_Distance"])
@@ -147,11 +182,15 @@ class CarState(CarStateBase):
@staticmethod
def get_can_parsers(CP):
avh_messages = [(a, 0) for a in (0x6BB, 0x32B, 0x40, 0x48)] if CP.carFingerprint == CAR.SUBARU_LEGACY_2025 else []
parsers = {
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.main_for_cp(CP)),
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.camera),
Bus.alt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.alt_for_cp(CP))
Bus.alt: CANParser(DBC[CP.carFingerprint][Bus.pt], avh_messages, CanBus.alt_for_cp(CP))
}
if CP.flags & SubaruFlags.D_PLATFORM:
parsers[Bus.main] = CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.main)
if CP.carFingerprint == CAR.SUBARU_LEGACY_2025:
for address in AVH_INPUTS:
parsers[Bus.alt].vl[address]
return parsers
+6 -2
View File
@@ -3,7 +3,7 @@ from opendbc.car.disable_ecu import disable_ecu
from opendbc.car.interfaces import CarInterfaceBase
from opendbc.car.subaru.carcontroller import CarController
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):
@@ -40,7 +40,11 @@ class CarInterface(CarInterfaceBase):
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.D_PLATFORM.value
if ret.flags & SubaruFlags.D_PLATFORM_CAMERA:
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.D_PLATFORM_CAMERA.value
if candidate in (CAR.SUBARU_LEGACY_2025, CAR.SUBARU_ASCENT_2023):
if candidate in SUBARU_STOP_START_CARS:
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.STOP_START_BUTTON.value
if candidate == CAR.SUBARU_LEGACY_2025:
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.AVH_STARTUP.value
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.steerLimitTimer = 0.4
@@ -3,6 +3,10 @@ from opendbc.car.subaru.values import CanBus
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):
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)
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,
bus=CanBus.main):
values = {s: es_lkas_state_msg[s] for s in [
@@ -182,6 +199,32 @@ def create_es_dashstatus(packer, frame, dashstatus_msg, enabled, long_enabled, l
return packer.make_can_msg("ES_DashStatus", bus, values)
def create_stop_start_control(packer, dashlights_msg, raw_dat=None, counter=None, bus=CanBus.alt):
"""Create the supported Subaru momentary Stop/Start button request.
Dashlights is a stock periodic message, so preserve the live frame and only
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)
if counter is None:
counter = (int(values.get("COUNTER", 0)) + 1) % 0x10
values["COUNTER"] = counter % 0x10
values["STOP_START"] = 1
return packer.make_can_msg("Dashlights", bus, values)
def create_es_brake(packer, frame, es_brake_msg, long_enabled, long_active, brake_value, bus=CanBus.main):
values = {s: es_brake_msg[s] for s in [
"CHECKSUM",
@@ -0,0 +1,112 @@
from types import SimpleNamespace
import pytest
from opendbc.car.subaru.avh import AVH_REQUEST, AVH_STATUS, INPUTS, AvhStartup, avh_request, checksum
from opendbc.car.subaru.carcontroller import CarController
from opendbc.car.subaru.interface import CarInterface
from opendbc.car.subaru.values import CAR, SubaruSafetyFlags
from opendbc.car import Bus
def sample(address, counter):
data = bytearray(8)
data[1] = counter & 15
if address == AVH_REQUEST:
data[3], data[5], data[6] = 1, 0x80, 0x0E # captured Legacy payload, not Outback constants
elif address == 0x40:
data[2:4] = (800).to_bytes(2, 'little')
elif address == 0x48:
data[3] = 4
elif address == 0x174:
data[2] = 8
data[0] = checksum(address, data)
return bytes(data)
def prepare(fast_counter_step=1):
policy = AvhStartup()
frames = {}
for tick in range(101):
now = 100 + tick / 10
for address in INPUTS:
if address != AVH_REQUEST or tick % 10 == 0:
counter = tick // 10 if address == AVH_REQUEST else tick * (1 if address == AVH_STATUS else fast_counter_step)
frames[address] = (now, sample(address, counter))
sent = policy.update(now, frames, True, True, False)
if tick < 100:
assert sent == []
assert sent == [avh_request(frames[AVH_REQUEST][1], 1)]
return policy, frames
def test_captured_legacy_press_bytes():
template = bytes.fromhex('5b0b000100800e00')
assert avh_request(template, 1) == (0x6BB, bytes.fromhex('5e0c020100800e00'), 1)
assert avh_request(template, 2) == (0x6BB, bytes.fromhex('5f0d020100800e00'), 1)
wrap = sample(AVH_REQUEST, 15)
assert avh_request(wrap, 1)[1][1] == 0
assert avh_request(wrap, 2)[1][1] == 1
def test_two_frames_only_and_no_retry():
policy, frames = prepare()
assert policy.update(110.04, frames, True, True, False) == []
assert policy.update(110.06, frames, True, True, False) == [avh_request(frames[AVH_REQUEST][1], 2)]
assert policy.update(110.07, frames, True, True, False) == []
assert policy.update(111, frames, True, True, False) == []
def test_controller_snapshots_may_skip_fast_can_samples():
policy, frames = prepare(fast_counter_step=2)
assert policy.update(110.06, frames, True, True, False) == [avh_request(frames[AVH_REQUEST][1], 2)]
@pytest.mark.parametrize('reason', ['late', 'new_template', 'manual', 'ack', 'moving', 'gas', 'gear', 'invalid', 'disabled', 'engaged', 'stale'])
def test_followup_aborts_permanently(reason):
policy, frames = prepare()
address, offset, value = {
'manual': (AVH_REQUEST, 2, 1), 'ack': (AVH_STATUS, 5, 32),
'moving': (0x13A, 2, 1), 'gas': (0x40, 4, 1), 'gear': (0x48, 3, 3),
'new_template': (AVH_REQUEST, 1, 11),
}.get(reason, (None, None, None))
if address is not None:
data = bytearray(frames[address][1])
data[1] = (data[1] + 1) & 15
data[offset] = value
data[0] = checksum(address, data)
frames[address] = (110.05, bytes(data))
if reason == 'stale':
frames[0x40] = (109, frames[0x40][1])
now = 110.08 if reason == 'late' else 110.06
assert policy.update(now, frames, reason != 'disabled', reason != 'invalid', reason == 'engaged') == []
assert policy.done
assert policy.update(111, frames, True, True, False) == []
def test_only_legacy_has_avh_safety_permission():
for car in CAR:
cp = CarInterface.get_non_essential_params(car)
assert bool(cp.safetyConfigs[0].safetyParam & SubaruSafetyFlags.AVH_STARTUP) == (car == CAR.SUBARU_LEGACY_2025)
def test_existing_required_messages_keep_alive_checks():
cp = CarInterface.get_non_essential_params(CAR.SUBARU_LEGACY_2025)
parser = CarInterface.CarState.get_can_parsers(cp)[Bus.alt]
assert not parser.message_states[0x13A].ignore_alive
assert not parser.message_states[0x174].ignore_alive
assert parser.message_states[AVH_REQUEST].ignore_alive
assert parser.message_states[AVH_STATUS].ignore_alive
def test_controller_sends_only_when_opted_in():
cp = CarInterface.get_non_essential_params(CAR.SUBARU_LEGACY_2025)
controller = CarController({}, cp)
cc = SimpleNamespace(enabled=False, latActive=False, longActive=False,
actuators=SimpleNamespace(as_builder=lambda: SimpleNamespace(steeringAngleDeg=0)),
hudControl=SimpleNamespace(leadVisible=False), cruiseControl=SimpleNamespace(cancel=False))
cs = SimpleNamespace(out=SimpleNamespace(canValid=True), avh_frames={})
toggles = SimpleNamespace(subaru_stop_start_off=False, subaru_avh_on=False, subaru_sng=False)
controller.frame = 1
_, sent = controller.update(cc, cs, 100_000_000_000, toggles)
assert not any(m[0] in (AVH_REQUEST, AVH_STATUS) for m in sent)
@@ -5,10 +5,10 @@ from types import SimpleNamespace
import pytest
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.subaru import subarucan
from opendbc.car.subaru.carcontroller import CarController
from opendbc.car.subaru.carcontroller import CarController, _ASCENT_AOL_ARM_FRAMES
from opendbc.car.subaru.carstate import CarState
from opendbc.car.subaru.fingerprints import FW_VERSIONS
from opendbc.car.fw_versions import match_fw_to_car
@@ -67,6 +67,56 @@ def test_preglobal_sng_does_not_send_standstill_keepalive_without_manual_toggle(
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:
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]
@@ -193,7 +243,8 @@ def test_outback_2023_uses_d_platform_bus_layout():
assert CP.flags & SubaruFlags.D_PLATFORM
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM
assert not (CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.LEGACY_2025_ANGLE_LIMITS)
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.STOP_START_BUTTON
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.FIXED_ANGLE_LIMITS
assert CanBus.main_for_cp(CP) == CanBus.alt
assert CanBus.angle_for_cp(CP) == CanBus.main
assert parsers[Bus.pt].bus == CanBus.alt
@@ -205,6 +256,76 @@ def test_outback_2023_uses_d_platform_bus_layout():
assert CP.lateralSmoothSeconds == pytest.approx(0.4)
@pytest.mark.parametrize("platform", [CAR.SUBARU_OUTBACK_2023, CAR.SUBARU_LEGACY_2025])
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.frame = start_frame
class TestActuators:
steeringAngleDeg = 0.0
def as_builder(self):
return SimpleNamespace(steeringAngleDeg=self.steeringAngleDeg)
CC = SimpleNamespace(
enabled=False,
latActive=False,
longActive=False,
actuators=TestActuators(),
hudControl=SimpleNamespace(leadVisible=False),
cruiseControl=SimpleNamespace(cancel=False),
)
CS = SimpleNamespace(
canValid=True,
dashlights_msg={"COUNTER": 6, "STOP_START": 0},
dashlights_dat=bytes.fromhex("13061407875a8100"),
stop_start_state=0,
out=SimpleNamespace(
standstill=True,
gearShifter=structs.CarState.GearShifter.park,
),
)
toggles = SimpleNamespace(subaru_stop_start_off=True, subaru_sng=False)
_, can_sends = controller.update(CC, CS, 0, toggles)
stop_start_msgs = [msg for msg in can_sends if msg[0] == 0x390]
assert len(stop_start_msgs) == 1
assert stop_start_msgs[0][2] == expected_bus
assert stop_start_msgs[0][1] == bytes.fromhex("57071407875ac100")
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"]["COUNTER"] == 7
controller.frame = 103
CS.stop_start_state = 3
_, can_sends = controller.update(CC, CS, 0, toggles)
assert not any(msg[0] == 0x390 for msg in can_sends)
assert controller.stop_start_acknowledged
def test_legacy_2025_uses_gen2_angle_bus_layout():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_LEGACY_2025)
parsers = CarState.get_can_parsers(CP)
@@ -215,6 +336,7 @@ def test_legacy_2025_uses_gen2_angle_bus_layout():
assert not (CP.flags & SubaruFlags.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.STOP_START_BUTTON
assert CanBus.main_for_cp(CP) == CanBus.main
assert CanBus.angle_for_cp(CP) == CanBus.main
assert parsers[Bus.pt].bus == CanBus.main
@@ -292,7 +414,7 @@ def test_legacy_2025_engagement_continues_from_last_sent_angle():
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(0.47)
def test_legacy_2025_waits_for_manual_steering_to_settle_before_reengaging():
def test_legacy_2025_reengages_immediately_after_manual_steering_stops():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_LEGACY_2025)
controller = CarController({}, CP)
CC = SimpleNamespace(
@@ -304,6 +426,7 @@ def test_legacy_2025_waits_for_manual_steering_to_settle_before_reengaging():
vEgoRaw=6.2,
steeringAngleDeg=-121.55,
steeringRateDeg=350.0,
steeringTorque=250.0,
steeringPressed=True,
gearShifter=structs.CarState.GearShifter.drive,
standstill=False,
@@ -312,47 +435,29 @@ def test_legacy_2025_waits_for_manual_steering_to_settle_before_reengaging():
msg = controller.lateral_angle(CC, CS)
parser.update([(1, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
CS.out.steeringPressed = False
CS.out.steeringAngleDeg = -113.78
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_Output"] == pytest.approx(CS.out.steeringAngleDeg)
for i in range(9):
CS.out.steeringAngleDeg += 0.5
CS.out.steeringRateDeg = 20.0
msg = controller.lateral_angle(CC, CS)
parser.update([(3 + i, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
for i in range(6):
if i % 2:
CS.out.steeringAngleDeg += 0.5
CS.out.steeringRateDeg = 0.0 if i % 2 == 0 else 20.0
msg = controller.lateral_angle(CC, CS)
parser.update([(12 + i, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
CS.out.steeringPressed = False
CS.out.steeringTorque = 0.0
CS.out.steeringAngleDeg = -113.78
CS.out.steeringRateDeg = 0.0
msg = controller.lateral_angle(CC, CS)
parser.update([(3, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
CS.out.steeringRateDeg = 0.0
for i in range(8):
msg = controller.lateral_angle(CC, CS)
parser.update([(18 + i, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
measured_angle = CS.out.steeringAngleDeg
msg = controller.lateral_angle(CC, CS)
parser.update([(26, [msg])])
parser.update([(4, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
assert abs(parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] - measured_angle) < 0.1
assert -113.78 < parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] < -100.0
def test_legacy_2025_manual_handoff_reclaim_is_gradual():
def test_legacy_2025_manual_handoff_reentry_uses_normal_angle_limits():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_LEGACY_2025)
controller = CarController({}, CP)
CC = SimpleNamespace(
@@ -364,6 +469,7 @@ def test_legacy_2025_manual_handoff_reclaim_is_gradual():
vEgoRaw=3.7,
steeringAngleDeg=2.5,
steeringRateDeg=-45.0,
steeringTorque=250.0,
steeringPressed=True,
gearShifter=structs.CarState.GearShifter.drive,
standstill=False,
@@ -372,26 +478,32 @@ def test_legacy_2025_manual_handoff_reclaim_is_gradual():
msg = controller.lateral_angle(CC, CS)
parser.update([(1, [msg])])
msg = controller.lateral_angle(CC, CS)
parser.update([(2, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
CS.out.steeringPressed = False
CS.out.steeringTorque = 0.0
CS.out.steeringRateDeg = 0.0
for i in range(19):
msg = controller.lateral_angle(CC, CS)
parser.update([(2 + i, [msg])])
msg = controller.lateral_angle(CC, CS)
parser.update([(3, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
msg = controller.lateral_angle(CC, CS)
parser.update([(4, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
first_reclaim_angle = parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"]
assert first_reclaim_angle == pytest.approx(CS.out.steeringAngleDeg, abs=0.1)
first_reentry_angle = parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"]
assert CC.actuators.steeringAngleDeg < first_reentry_angle < CS.out.steeringAngleDeg
reclaim_angles = []
reentry_angles = []
for i in range(6):
msg = controller.lateral_angle(CC, CS)
parser.update([(20 + i, [msg])])
reclaim_angles.append(parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"])
reentry_angles.append(parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"])
assert all(reclaim_angles[i] >= reclaim_angles[i + 1] for i in range(len(reclaim_angles) - 1))
assert reclaim_angles[-1] > CC.actuators.steeringAngleDeg
assert all(reentry_angles[i] >= reentry_angles[i + 1] for i in range(len(reentry_angles) - 1))
assert reentry_angles[-1] > CC.actuators.steeringAngleDeg
def test_ascent_2023_uses_gen2_angle_bus_layout():
@@ -414,6 +526,25 @@ def test_ascent_2023_uses_gen2_angle_bus_layout():
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():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_CROSSTREK_2025)
parsers = CarState.get_can_parsers(CP)
@@ -429,24 +560,77 @@ def test_angle_controller_tracks_driver_override():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_CROSSTREK_2025)
controller = CarController({}, CP)
CC = SimpleNamespace(latActive=True, actuators=SimpleNamespace(steeringAngleDeg=15.0))
CS = SimpleNamespace(out=SimpleNamespace(vEgoRaw=15.0, steeringAngleDeg=2.0, steeringTorque=250.0))
CS = SimpleNamespace(out=SimpleNamespace(vEgoRaw=15.0, steeringAngleDeg=2.0, steeringTorque=175.0))
msg = controller.lateral_angle(CC, CS)
assert not controller.driver_override
msg = controller.lateral_angle(CC, CS)
assert controller.driver_override
assert controller.p.STEER_OVERRIDE_TORQUE_HIGH == 150
assert controller.p.STEER_OVERRIDE_TORQUE_LOW == 100
assert controller.apply_steer_last == CS.out.steeringAngleDeg
assert msg[0] == 0x124
CS.out.steeringTorque = 125.0
controller.lateral_angle(CC, CS)
assert controller.driver_override
def test_ascent_angle_controller_uses_fixed_angle_rate_limits():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023)
CS.out.steeringTorque = 75.0
controller.lateral_angle(CC, CS)
assert not controller.driver_override
def test_angle_controller_blocks_low_speed_mads_engagement():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_CROSSTREK_2025)
controller = CarController({}, CP)
CC = SimpleNamespace(
enabled=False,
latActive=True,
actuators=SimpleNamespace(steeringAngleDeg=15.0),
)
CS = SimpleNamespace(out=SimpleNamespace(
vEgoRaw=0.3,
steeringAngleDeg=80.0,
steeringTorque=0.0,
gearShifter=structs.CarState.GearShifter.drive,
standstill=False,
))
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("ES_LKAS_ANGLE", 0)], CanBus.main)
msg = controller.lateral_angle(CC, CS)
parser.update([(1, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
CS.out.vEgoRaw = 1.0
CS.out.steeringAngleDeg = 130.0
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_Output"] == pytest.approx(CS.out.steeringAngleDeg)
CS.out.steeringAngleDeg = 0.0
msg = controller.lateral_angle(CC, CS)
parser.update([(3, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
@pytest.mark.parametrize("platform", (CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023))
def test_angle_controller_uses_fixed_angle_rate_limits(platform):
CP = CarInterface.get_non_essential_params(platform)
controller = CarController({}, CP)
CC = SimpleNamespace(enabled=True, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=-14.88))
CS = SimpleNamespace(out=SimpleNamespace(
vEgoRaw=21.66,
steeringAngleDeg=-25.77,
steeringRateDeg=0.0,
steeringTorque=-149.0,
steeringTorque=-250.0,
steeringPressed=False,
gearShifter=structs.CarState.GearShifter.drive,
standstill=False,
@@ -459,8 +643,8 @@ def test_ascent_angle_controller_uses_fixed_angle_rate_limits():
assert CS.out.steeringAngleDeg < parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] < -25.0
@pytest.mark.parametrize("platform", (CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023))
def test_angle_controller_yields_until_manual_steering_settles(platform):
def test_ascent_angle_controller_reengages_immediately_after_manual_steering_stops():
platform = CAR.SUBARU_ASCENT_2023
CP = CarInterface.get_non_essential_params(platform)
controller = CarController({}, CP)
CC = SimpleNamespace(enabled=True, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=-10.0))
@@ -468,7 +652,7 @@ def test_angle_controller_yields_until_manual_steering_settles(platform):
vEgoRaw=21.66,
steeringAngleDeg=-25.06,
steeringRateDeg=35.0,
steeringTorque=-149.0,
steeringTorque=-250.0,
steeringPressed=True,
gearShifter=structs.CarState.GearShifter.drive,
standstill=False,
@@ -477,24 +661,50 @@ def test_angle_controller_yields_until_manual_steering_settles(platform):
msg = controller.lateral_angle(CC, CS)
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_Output"] == pytest.approx(CS.out.steeringAngleDeg)
CS.out.steeringPressed = False
CS.out.steeringTorque = 0.0
CS.out.steeringAngleDeg = -17.91
CS.out.steeringRateDeg = 0.0
for i in range(18):
msg = controller.lateral_angle(CC, CS)
parser.update([(2 + i, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
msg = controller.lateral_angle(CC, CS)
parser.update([(3, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
msg = controller.lateral_angle(CC, CS)
parser.update([(20, [msg])])
parser.update([(4, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg, abs=0.1)
assert CS.out.steeringAngleDeg < parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] < CC.actuators.steeringAngleDeg
def test_ascent_angle_controller_blocks_parking_lot_aol_engagement():
def test_ascent_reentry_rate_uses_last_transmitted_angle():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023)
controller = CarController({}, CP)
controller.angle_handoff_active = True
CC = SimpleNamespace(enabled=True, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=-1.45))
CS = SimpleNamespace(out=SimpleNamespace(
vEgoRaw=30.3, steeringAngleDeg=0.78, steeringRateDeg=-1.5, steeringTorque=56.0,
gearShifter=structs.CarState.GearShifter.drive, standstill=False,
))
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("ES_LKAS_ANGLE", 0)], CanBus.main)
parser.update([(1, [controller.lateral_angle(CC, CS)])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(0.78)
CS.out.steeringAngleDeg = 0.74
CS.out.steeringRateDeg = -1.99
parser.update([(2, [controller.lateral_angle(CC, CS)])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(0.53, abs=0.01)
def test_ascent_angle_controller_waits_for_parking_lot_safety_envelope():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023)
controller = CarController({}, CP)
CC = SimpleNamespace(enabled=False, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=-206.12))
@@ -504,6 +714,7 @@ def test_ascent_angle_controller_blocks_parking_lot_aol_engagement():
steeringRateDeg=96.0,
steeringTorque=7.0,
steeringPressed=False,
cruiseState=SimpleNamespace(available=True),
gearShifter=structs.CarState.GearShifter.drive,
standstill=False,
))
@@ -516,21 +727,77 @@ def test_ascent_angle_controller_blocks_parking_lot_aol_engagement():
CS.out.steeringAngleDeg = -100.0
CS.out.steeringRateDeg = 0.0
for frame in range(2, _ASCENT_AOL_ARM_FRAMES + 1):
msg = controller.lateral_angle(CC, CS)
parser.update([(frame, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
msg = controller.lateral_angle(CC, CS)
parser.update([(2, [msg])])
parser.update([(_ASCENT_AOL_ARM_FRAMES + 1, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
CS.out.gearShifter = structs.CarState.GearShifter.reverse
msg = controller.lateral_angle(CC, CS)
parser.update([(3, [msg])])
parser.update([(_ASCENT_AOL_ARM_FRAMES + 2, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
def test_lkas_hud_state_uses_lateral_active():
def test_ascent_aol_does_not_arm_before_cruise_main_is_available():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023)
controller = CarController({}, CP)
CC = SimpleNamespace(enabled=False, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=5.0))
CS = SimpleNamespace(out=SimpleNamespace(
vEgoRaw=10.0,
steeringAngleDeg=0.0,
steeringRateDeg=0.0,
steeringTorque=0.0,
steeringPressed=False,
cruiseState=SimpleNamespace(available=False),
gearShifter=structs.CarState.GearShifter.drive,
standstill=False,
))
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("ES_LKAS_ANGLE", 0)], CanBus.main)
for frame in range(_ASCENT_AOL_ARM_FRAMES):
msg = controller.lateral_angle(CC, CS)
parser.update([(frame + 1, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
assert controller.ascent_aol_arm_frames == 0
CS.out.cruiseState.available = True
msg = controller.lateral_angle(CC, CS)
parser.update([(_ASCENT_AOL_ARM_FRAMES + 1, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
assert controller.ascent_aol_arm_frames == 1
def test_ascent_angle_controller_does_not_delay_normal_engagement():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023)
controller = CarController({}, CP)
CC = SimpleNamespace(enabled=True, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=5.0))
CS = SimpleNamespace(out=SimpleNamespace(
vEgoRaw=10.0,
steeringAngleDeg=0.0,
steeringRateDeg=0.0,
steeringTorque=0.0,
steeringPressed=False,
gearShifter=structs.CarState.GearShifter.drive,
standstill=False,
))
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("ES_LKAS_ANGLE", 0)], CanBus.main)
msg = controller.lateral_angle(CC, CS)
parser.update([(1, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
def test_lkas_hud_state_uses_angle_request_state():
update_source = inspect.getsource(CarController.update)
assert "create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, CC.latActive" in update_source
assert "CS.es_lkas_state_msg, self._lkas_status_active(CC), hud_control.visualAlert" in update_source
assert "create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, CC.enabled" not in update_source
@@ -548,3 +815,73 @@ def test_lkas_hud_active_bit_follows_lateral_state(enabled, expected):
assert parser.can_valid
assert parser.vl["ES_LKAS_State"]["LKAS_ACTIVE"] == expected
def test_outback_manual_steering_keeps_cooperative_angle_request():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_OUTBACK_2023)
controller = CarController({}, CP)
CC = SimpleNamespace(
enabled=False,
latActive=True,
actuators=SimpleNamespace(steeringAngleDeg=-225.0),
)
CS = SimpleNamespace(out=SimpleNamespace(
vEgoRaw=0.9,
steeringAngleDeg=-57.0,
steeringRateDeg=0.0,
steeringTorque=0.0,
steeringPressed=True,
gearShifter=structs.CarState.GearShifter.drive,
standstill=False,
))
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("ES_LKAS_ANGLE", 0)], CanBus.main)
for frame, steering_torque in enumerate((-79.0, -81.0, -170.0, -250.0, -250.0, 79.0, 81.0, 170.0, 250.0, 250.0), start=1):
CS.out.steeringRateDeg = 0.0 if frame == 1 else -45.0
CS.out.steeringTorque = steering_torque
CS.out.steeringPressed = abs(steering_torque) > 80.0
msg = controller.lateral_angle(CC, CS)
parser.update([(frame, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
assert CC.actuators.steeringAngleDeg < parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] < CS.out.steeringAngleDeg
assert controller._lkas_status_active(CC)
def test_outback_waits_for_manual_turn_to_settle_before_reentry():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_OUTBACK_2023)
controller = CarController({}, CP)
CC = SimpleNamespace(enabled=False, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=-80.0))
CS = SimpleNamespace(out=SimpleNamespace(
vEgoRaw=7.3, steeringAngleDeg=-121.47, steeringRateDeg=126.5,
gearShifter=structs.CarState.GearShifter.drive, standstill=False,
))
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("ES_LKAS_ANGLE", 0)], CanBus.main)
for frame, (angle, rate, active) in enumerate([
(-121.47, 126.5, False), (-117.96, 122.5, False), (-88.65, 112.0, False),
(-0.24, 0.0, True),
], start=1):
CS.out.steeringAngleDeg = angle
CS.out.steeringRateDeg = rate
parser.update([(frame, [controller.lateral_angle(CC, CS)])])
assert bool(parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"]) == active
if not active:
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(angle, abs=0.01)
def test_ascent_hud_waits_for_angle_request():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023)
controller = CarController({}, CP)
CC = SimpleNamespace(latActive=True)
assert not controller._lkas_status_active(CC)
controller.angle_lkas_active = True
assert controller._lkas_status_active(CC)
def test_other_angle_cars_keep_lateral_status_behavior():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_CROSSTREK_2025)
controller = CarController({}, CP)
controller.angle_lkas_active = False
assert controller._lkas_status_active(SimpleNamespace(latActive=True))
+17
View File
@@ -37,6 +37,11 @@ class CarControllerParams:
self.STEER_OVERRIDE_TORQUE_HIGH = 200
self.STEER_OVERRIDE_TORQUE_LOW = 150
# Crosstrek 2025 reports manual parking-lot inputs below the generic handoff threshold.
if CP.carFingerprint == CAR.SUBARU_CROSSTREK_2025:
self.STEER_OVERRIDE_TORQUE_HIGH = 150
self.STEER_OVERRIDE_TORQUE_LOW = 100
if CP.flags & SubaruFlags.GLOBAL_GEN2:
# TODO: lower rate limits, this reaches min/max in 0.5s which negatively affects tuning
self.STEER_MAX = 1500
@@ -83,6 +88,9 @@ class SubaruSafetyFlags(IntFlag):
D_PLATFORM = 32
D_PLATFORM_CAMERA = 64
FIXED_ANGLE_LIMITS = 128
STOP_START_BUTTON = 256
REDNECK_CRUISE = 512
AVH_STARTUP = 1024
LEGACY_2025_ANGLE_LIMITS = FIXED_ANGLE_LIMITS
@@ -264,6 +272,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]) + \
p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION)
SUBARU_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
@@ -5,9 +5,10 @@ from opendbc.car.lateral import apply_steer_angle_limits_vm
from opendbc.car.interfaces import CarControllerBase
from opendbc.car.tesla.coop_steering import CooperativeSteeringController
from opendbc.car.tesla.teslacan import TeslaCAN
from opendbc.car.tesla.teslacan_legacy import TeslaCANRaven
from opendbc.car.tesla.preap.carcontroller import PreAPLongController, init_preap_can
from opendbc.car.tesla.preap.stock_cc_spoofer import StockCCSpoofer
from opendbc.car.tesla.values import CANBUS, CAR, CarControllerParams, TeslaSafetyFlags
from opendbc.car.tesla.values import CANBUS, CAR, CarControllerParams, TeslaSafetyFlags, LEGACY_CARS
from opendbc.car.vehicle_model import VehicleModel
def get_safety_CP():
@@ -24,6 +25,7 @@ class CarController(CarControllerBase):
self.coop_enabled = CP.carFingerprint == CAR.TESLA_MODEL_3 and any(
config.safetyParam & TeslaSafetyFlags.COOP_STEERING.value for config in CP.safetyConfigs
)
self._clear_steering_limit_info()
self.packer = CANPacker(dbc_names[Bus.party])
self.tesla_can = TeslaCAN(self.packer)
self.preap_long = None
@@ -38,9 +40,37 @@ class CarController(CarControllerBase):
self.stock_cc = StockCCSpoofer()
from opendbc.car.tesla.interface import CarInterface
self.VM = VehicleModel(CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_PREAP))
elif CP.carFingerprint in LEGACY_CARS:
self.packers = {
CANBUS.party: CANPacker(dbc_names[Bus.party]),
}
self.tesla_can = TeslaCANRaven(self.packers)
from opendbc.car.tesla.interface import CarInterface
self.VM = VehicleModel(CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1))
def _clear_steering_limit_info(self):
self.steering_limit_info_valid = False
self.model_limit_error_deg = 0.0
self.resume_limit_error_deg = 0.0
self.cooperative_limit_error_deg = 0.0
self.cooperative_offset_deg = 0.0
self.steering_limit_mono_time = 0
self.combined_limit_error_deg = 0.0
def get_steering_limit_info(self) -> dict[str, bool | float | int]:
return {
"valid": self.steering_limit_info_valid,
"modelLimitErrorDeg": self.model_limit_error_deg,
"resumeLimitErrorDeg": self.resume_limit_error_deg,
"cooperativeLimitErrorDeg": self.cooperative_limit_error_deg,
"cooperativeOffsetDeg": self.cooperative_offset_deg,
"monoTime": self.steering_limit_mono_time,
"combinedLimitErrorDeg": self.combined_limit_error_deg,
}
def update(self, CC, CS, now_nanos, starpilot_toggles):
if self.CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
self._clear_steering_limit_info()
return self._update_preap(CC, CS)
actuators = CC.actuators
@@ -48,8 +78,12 @@ class CarController(CarControllerBase):
# Preserve the stock controller path unless cooperative steering is explicitly enabled.
lat_active = CC.latActive and (not CS.out.steeringDisengage if self.coop_enabled else CS.hands_on_level < 3)
if not (self.coop_enabled and lat_active):
self._clear_steering_limit_info()
if self.frame % 2 == 0:
requested_angle = actuators.steeringAngleDeg
# Angular rate limit based on speed
self.apply_angle_last = apply_steer_angle_limits_vm(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgoRaw, CS.out.steeringAngleDeg,
lat_active, CarControllerParams, self.VM)
@@ -57,9 +91,34 @@ class CarController(CarControllerBase):
self.apply_angle_command_last, lat_active = self.coop_steer.update(
self.apply_angle_last, lat_active, self.coop_enabled, CS, self.VM,
)
can_sends.append(self.tesla_can.create_steering_control(self.apply_angle_command_last, lat_active))
if self.frame % 10 == 0:
if self.coop_enabled and lat_active:
model_limit_error_deg = abs(requested_angle - self.apply_angle_last)
resume_limit_error_deg = self.coop_steer.resume_limit_error_deg
cooperative_limit_error_deg = self.coop_steer.cooperative_limit_error_deg
cooperative_offset_deg = self.coop_steer.cooperative_offset_deg
combined_limit_error_deg = abs(requested_angle + cooperative_offset_deg - self.apply_angle_command_last)
limit_values = (model_limit_error_deg, resume_limit_error_deg, cooperative_limit_error_deg,
cooperative_offset_deg, combined_limit_error_deg)
if all(np.isfinite(value) for value in limit_values):
self.steering_limit_info_valid = True
self.model_limit_error_deg = model_limit_error_deg
self.resume_limit_error_deg = resume_limit_error_deg
self.cooperative_limit_error_deg = cooperative_limit_error_deg
self.cooperative_offset_deg = cooperative_offset_deg
self.steering_limit_mono_time = now_nanos
self.combined_limit_error_deg = combined_limit_error_deg
else:
self._clear_steering_limit_info()
if self.CP.carFingerprint in LEGACY_CARS:
cntr = (self.frame // 2) % 16
can_sends.append(self.tesla_can.create_steering_control(cntr, self.apply_angle_command_last, lat_active))
else:
can_sends.append(self.tesla_can.create_steering_control(self.apply_angle_command_last, lat_active))
if self.frame % 10 == 0 and self.CP.carFingerprint not in LEGACY_CARS:
can_sends.append(self.tesla_can.create_steering_allowed())
# Longitudinal control
@@ -68,13 +127,21 @@ class CarController(CarControllerBase):
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))
cntr = (self.frame // 4) % 8
can_sends.append(self.tesla_can.create_longitudinal_command(state, accel, cntr, CS.out.vEgo, CC.longActive))
if self.CP.carFingerprint in LEGACY_CARS:
hw1_accel = accel if CC.longActive and not CC.cruiseControl.cancel else 0.
hw1_active = CC.longActive and not CC.cruiseControl.cancel
can_sends.append(self.tesla_can.create_longitudinal_command(state, hw1_accel, cntr, CS.out.vEgo, hw1_active, CS.out.gasPressed))
else:
can_sends.append(self.tesla_can.create_longitudinal_command(state, accel, cntr, self.frame, CS.out.vEgo, CS.out.gasPressed))
else:
# Increment counter so cancel is prioritized even without openpilot longitudinal
if CC.cruiseControl.cancel:
cntr = (CS.das_control["DAS_controlCounter"] + 1) % 8
can_sends.append(self.tesla_can.create_longitudinal_command(13, 0, cntr, CS.out.vEgo, False))
if self.CP.carFingerprint in LEGACY_CARS:
can_sends.append(self.tesla_can.create_longitudinal_command(13, 0, cntr, CS.out.vEgo, False, CS.out.gasPressed))
else:
can_sends.append(self.tesla_can.create_longitudinal_command(13, 0, cntr, self.frame, CS.out.vEgo, False))
# TODO: HUD control
new_actuators = actuators.as_builder()
@@ -86,7 +153,7 @@ class CarController(CarControllerBase):
def _update_preap(self, CC, CS):
actuators = CC.actuators
can_sends = []
lat_active = CC.latActive and CS.hands_on_level < 3
lat_active = CC.latActive and CS.hands_on_level < 3 and getattr(CS, "preap_lateral_authorized", False)
if CC.cruiseControl.cancel and CS.cruiseEnabled:
CS.cruiseEnabled = False
@@ -102,8 +169,10 @@ class CarController(CarControllerBase):
CS.engagement.pedal_speed_kph = 0.0
if self.frame % 2 == 0:
requested_angle = float(np.clip(actuators.steeringAngleDeg,
CS.out.steeringAngleDeg - 20., CS.out.steeringAngleDeg + 20.))
self.apply_angle_last = apply_steer_angle_limits_vm(
actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgoRaw, CS.out.steeringAngleDeg,
requested_angle, self.apply_angle_last, CS.out.vEgoRaw, CS.out.steeringAngleDeg,
lat_active, CarControllerParams, self.VM,
)
cntr = (self.frame // 2) % 16
+103 -3
View File
@@ -4,7 +4,10 @@ from opendbc.can import CANDefine, CANParser
from opendbc.car import Bus, structs
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.interfaces import CarStateBase
from opendbc.car.tesla.values import DBC, CANBUS, GEAR_MAP, STEER_DISENGAGE_THRESHOLD, STEER_THRESHOLD, TeslaSafetyFlags, CAR
from opendbc.car.tesla.values import (
DBC, CANBUS, GEAR_MAP, STEER_DISENGAGE_THRESHOLD, STEER_THRESHOLD, TeslaSafetyFlags,
CAR, LEGACY_CARS,
)
from opendbc.car.tesla.preap.carstate import get_preap_can_parsers, update_preap
from opendbc.car.tesla.preap.engagement import PreAPEngagement
from opendbc.car.tesla.preap.nap_conf import nap_conf
@@ -25,8 +28,19 @@ class CarState(CarStateBase):
def __init__(self, CP, FPCP):
super().__init__(CP, FPCP)
self.can_define = CANDefine(DBC[CP.carFingerprint][Bus.party])
self.shifter_values = self.can_define.dv["DI_systemStatus"]["DI_gear"] if CP.carFingerprint != CAR.TESLA_MODEL_S_PREAP else \
self.can_define.dv["DI_torque2"]["DI_gear"]
if CP.carFingerprint in LEGACY_CARS:
self.can_define_party = CANDefine(DBC[CP.carFingerprint][Bus.party])
self.can_define_pt = CANDefine(DBC[CP.carFingerprint][Bus.pt])
self.can_define_chassis = CANDefine(DBC[CP.carFingerprint][Bus.chassis])
self.can_defines = {
**self.can_define_party.dv,
**self.can_define_pt.dv,
**self.can_define_chassis.dv,
}
self.shifter_values = self.can_defines["DI_torque2"]["DI_gear"]
else:
self.shifter_values = self.can_define.dv["DI_systemStatus"]["DI_gear"] if CP.carFingerprint != CAR.TESLA_MODEL_S_PREAP else \
self.can_define.dv["DI_torque2"]["DI_gear"]
self.autopark = False
self.autopark_prev = False
@@ -75,6 +89,8 @@ class CarState(CarStateBase):
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
if self.CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
return update_preap(self, can_parsers)
if self.CP.carFingerprint in LEGACY_CARS:
return self.update_legacy(can_parsers)
cp_party = can_parsers[Bus.party]
cp_ap_party = can_parsers[Bus.ap_party]
@@ -173,10 +189,94 @@ class CarState(CarStateBase):
return ret, fp_ret
def update_legacy(self, can_parsers):
cp_party = can_parsers[Bus.party]
cp_ap_party = can_parsers[Bus.ap_party]
cp_pt = can_parsers[Bus.pt]
cp_ap_pt = can_parsers[Bus.ap_pt]
cp_chassis = can_parsers[Bus.chassis]
ret = structs.CarState()
fp_ret = custom.StarPilotCarState.new_message()
# Vehicle speed
ret.vEgoRaw = cp_chassis.vl["ESP_B"]["ESP_vehicleSpeed"] * CV.KPH_TO_MS
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
# Gas and brake
ret.gasPressed = cp_pt.vl["DI_torque1"]["DI_pedalPos"] > 0
ret.brake = 0
ret.brakePressed = cp_chassis.vl["BrakeMessage"]["driverBrakeStatus"] != 1
# Steering wheel and EPAS status
epas_status = cp_chassis.vl["EPAS_sysStatus"]
self.hands_on_level = epas_status["EPAS_handsOnLevel"]
ret.steeringAngleDeg = -epas_status["EPAS_internalSAS"]
ret.steeringRateDeg = -cp_chassis.vl["STW_ANGLHP_STAT"]["StW_AnglHP_Spd"]
ret.steeringTorque = -epas_status["EPAS_torsionBarTorque"]
ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > STEER_THRESHOLD, 5)
eac_status = self.can_defines["EPAS_sysStatus"]["EPAS_eacStatus"].get(int(epas_status["EPAS_eacStatus"]), None)
ret.steerFaultPermanent = eac_status == "EAC_FAULT"
ret.steerFaultTemporary = eac_status == "EAC_INHIBITED"
eac_error_code = self.can_defines["EPAS_sysStatus"]["EPAS_eacErrorCode"].get(int(epas_status["EPAS_eacErrorCode"]), None)
ret.steeringDisengage = self.hands_on_level >= 3 or (
eac_status == "EAC_INHIBITED" and eac_error_code == "EAC_ERROR_HIGH_ANGLE_RATE_SAFETY"
)
# Cruise
cruise_state = self.can_defines["DI_state"]["DI_cruiseState"].get(int(cp_chassis.vl["DI_state"]["DI_cruiseState"]), None)
speed_units = self.can_defines["DI_state"]["DI_speedUnits"].get(int(cp_chassis.vl["DI_state"]["DI_speedUnits"]), None)
cruise_enabled = cruise_state in ("ENABLED", "STANDSTILL", "OVERRIDE", "PRE_FAULT", "PRE_CANCEL")
ret.cruiseState.enabled = cruise_enabled
if speed_units == "KPH":
ret.cruiseState.speed = max(cp_chassis.vl["DI_state"]["DI_hw1CruiseSet"] * CV.KPH_TO_MS, 1e-3)
elif speed_units == "MPH":
ret.cruiseState.speed = max(cp_chassis.vl["DI_state"]["DI_hw1CruiseSet"] * CV.MPH_TO_MS, 1e-3)
ret.cruiseState.available = cruise_state == "STANDBY" or ret.cruiseState.enabled
ret.cruiseState.standstill = False
ret.standstill = ret.vEgoRaw < 0.1
ret.accFaulted = cruise_state == "FAULT"
# Gear, body state, and safety state
ret.gearShifter = GEAR_MAP[self.can_defines["DI_torque2"]["DI_gear"].get(
int(cp_chassis.vl["DI_torque2"]["DI_gear"]), "DI_GEAR_INVALID")]
doors = ("DOOR_STATE_FL", "DOOR_STATE_FR", "DOOR_STATE_RL", "DOOR_STATE_RR", "DOOR_STATE_FrontTrunk", "BOOT_STATE")
ret.doorOpen = any(
self.can_defines["GTW_carState"][door].get(int(cp_chassis.vl["GTW_carState"][door]), "OPEN") == "OPEN"
for door in doors
)
ret.leftBlinker = cp_chassis.vl["GTW_carState"]["BC_indicatorLStatus"] == 1
ret.rightBlinker = cp_chassis.vl["GTW_carState"]["BC_indicatorRStatus"] == 1
_ = cp_chassis.vl["SDM1"]
_ = cp_chassis.vl["RCM_status"]
sd_time = cp_chassis.ts_nanos["SDM1"]["SDM_bcklDrivStatus"]
rcm_time = cp_chassis.ts_nanos["RCM_status"]["RCM_buckleDriverStatus"]
if sd_time and cp_chassis._last_update_nanos - sd_time <= 1_000_000_000:
ret.seatbeltUnlatched = cp_chassis.vl["SDM1"]["SDM_bcklDrivStatus"] != 1
elif rcm_time and cp_chassis._last_update_nanos - rcm_time <= 1_000_000_000:
ret.seatbeltUnlatched = cp_chassis.vl["RCM_status"]["RCM_buckleDriverStatus"] != 1
else:
ret.seatbeltUnlatched = True
ret.stockAeb = cp_ap_pt.vl["DAS_control"]["DAS_aebEvent"] == 1
ret.stockLkas = cp_ap_party.vl["DAS_steeringControl"]["DAS_steeringControlType"] == 2
self.das_control = copy.copy(cp_ap_pt.vl["DAS_control"])
return ret, fp_ret
@staticmethod
def get_can_parsers(CP):
if CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
return get_preap_can_parsers(CP)
if CP.carFingerprint in LEGACY_CARS:
return {
Bus.party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.party),
Bus.ap_party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.autopilot_party),
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CANBUS.party),
Bus.ap_pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CANBUS.autopilot_party),
Bus.chassis: CANParser(DBC[CP.carFingerprint][Bus.chassis], [("SDM1", 0), ("RCM_status", 0)], CANBUS.party),
}
return {
Bus.party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.party),
Bus.ap_party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.autopilot_party)
@@ -62,6 +62,9 @@ class CooperativeSteeringController:
self.angle_override = 0.0
self.resume_rate_limiter_delta = SteerRateLimiter()
self.resume_rate_limiter = SteerRateLimiter()
self.resume_limit_error_deg = 0.0
self.cooperative_limit_error_deg = 0.0
self.cooperative_offset_deg = 0.0
def reset_override_state(self, apply_angle: float) -> None:
self.apply_angle_last = apply_angle
@@ -105,19 +108,25 @@ class CooperativeSteeringController:
return self.resume_rate_limiter.update(apply_angle, angle_rate_delta)
def update(self, apply_angle: float, lat_active: bool, enabled: bool, CS, VM: VehicleModel) -> tuple[float, bool]:
self.resume_limit_error_deg = 0.0
self.cooperative_limit_error_deg = 0.0
self.cooperative_offset_deg = 0.0
if not enabled:
self.reset_resume_state(apply_angle)
self.reset_override_state(apply_angle)
return apply_angle, lat_active
requested_angle = apply_angle
apply_angle = self.apply_resume_rate_limit(lat_active, apply_angle)
self.resume_limit_error_deg = abs(requested_angle - apply_angle)
if not lat_active:
self.reset_override_state(apply_angle)
return apply_angle, False
apply_angle_delta = apply_angle - self.apply_angle_last
self.apply_angle_last = apply_angle
apply_angle += self.update_override_angle(apply_angle_delta, CS.out.steeringTorque, CS.out.vEgo, VM)
self.cooperative_offset_deg = self.update_override_angle(apply_angle_delta, CS.out.steeringTorque, CS.out.vEgo, VM)
apply_angle += self.cooperative_offset_deg
limited_angle = apply_steer_angle_limits_vm(
apply_angle,
@@ -129,5 +138,6 @@ class CooperativeSteeringController:
VM,
)
self.coop_apply_angle_last = limited_angle
self.cooperative_limit_error_deg = abs(apply_angle - limited_angle)
self.unwind_override_angle(apply_angle - limited_angle)
return limited_angle, True
@@ -5,6 +5,12 @@ from opendbc.car.tesla.values import CAR
Ecu = CarParams.Ecu
FW_VERSIONS = {
CAR.TESLA_MODEL_S_HW1: {
(Ecu.eps, 0x730, None): [
b'1016704-00-HAA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x10\x00A',
],
},
CAR.TESLA_MODEL_3: {
(Ecu.eps, 0x730, None): [
b'TeM3_E014p10_0.0.0 (16),E014.17.00',
+17 -2
View File
@@ -1,9 +1,9 @@
from opendbc.car import get_safety_config, structs
from opendbc.car import Bus, get_safety_config, structs
from opendbc.car.interfaces import CarInterfaceBase
from opendbc.car.tesla.carcontroller import CarController
from opendbc.car.tesla.carstate import CarState
from opendbc.car.tesla.radar_interface import RadarInterface
from opendbc.car.tesla.values import TeslaSafetyFlags, CAR
from opendbc.car.tesla.values import TeslaSafetyFlags, CAR, DBC, LEGACY_CARS
from opendbc.car.tesla.preap.interface import get_preap_accel_limits, get_preap_params
@@ -32,6 +32,21 @@ class CarInterface(CarInterfaceBase):
if candidate == CAR.TESLA_MODEL_S_PREAP:
return get_preap_params(ret)
if candidate in LEGACY_CARS:
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.tesla, TeslaSafetyFlags.FLAG_HW1.value)]
ret.steerLimitTimer = 0.4
ret.steerActuatorDelay = 0.1
ret.steerAtStandstill = True
ret.steerControlType = structs.CarParams.SteerControlType.angle
ret.radarUnavailable = Bus.radar not in DBC[candidate]
ret.radarTimeStepDEPRECATED = 0.125
ret.alphaLongitudinalAvailable = True
if alpha_long:
ret.openpilotLongitudinalControl = True
ret.safetyConfigs[0].safetyParam |= TeslaSafetyFlags.LONG_CONTROL.value
return ret
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.tesla)]
ret.steerLimitTimer = 0.4
@@ -13,6 +13,8 @@ class PreAPEngagement:
self.enableDoublePull = double_pull_enabled
self.double_pull_window_ms = double_pull_window_ms
self.cruiseEnabled = False
self.lateralEnabled = False
self.lateralRearmRequired = False
self.enableLongControl = False
self.enableJustCC = False
self.pending_enable = False
@@ -28,6 +30,8 @@ class PreAPEngagement:
def handle_steering_disengage(self, steering_disengage: bool) -> None:
if steering_disengage and not self.prev_steering_disengage:
self.lateralEnabled = False
self.lateralRearmRequired = True
self.cruiseEnabled = False
self.enableLongControl = False
self.enableJustCC = False
@@ -45,6 +49,8 @@ class PreAPEngagement:
button_events: list[structs.CarState.ButtonEvent] = []
if cruise_buttons == CruiseButtons.MAIN and prev_cruise_buttons != CruiseButtons.MAIN:
self.lateralEnabled = True
self.lateralRearmRequired = False
if self.enableDoublePull:
self._handle_double_pull(curr_time_ms, v_ego, speed_units, use_pedal, pedal_long_allowed, long_control_allowed, di_cruise_state)
else:
@@ -75,6 +81,8 @@ class PreAPEngagement:
def check_can_engage(self, door_open: bool, gear_shifter, seatbelt_unlatched: bool) -> bool:
can_engage = not door_open and gear_shifter == structs.CarState.GearShifter.drive and not seatbelt_unlatched
if not can_engage:
self.lateralEnabled = False
self.lateralRearmRequired = True
self.cruiseEnabled = False
self.enableLongControl = False
self.enableJustCC = False
@@ -118,6 +126,8 @@ class PreAPEngagement:
((curr_time_ms - self.preap_last_cc_spoof_ms) < SPOOF_ECHO_WINDOW_MS)
be.type = ButtonType.unknown if is_echo else ButtonType.cancel
if not is_echo:
self.lateralEnabled = False
self.lateralRearmRequired = True
self.cruiseEnabled = False
self.enableLongControl = False
self.enableJustCC = False
@@ -145,4 +155,3 @@ class PreAPEngagement:
def _capture_target_speed(v_ego: float, speed_units: str) -> float:
speed_uom_kph = CV.MPH_TO_KPH if speed_units == "MPH" else 1.0
return max(int(v_ego * CV.MS_TO_KPH / speed_uom_kph + 0.5) * speed_uom_kph, 0.0)
@@ -0,0 +1,21 @@
from opendbc.car import structs
from opendbc.safety import ALTERNATIVE_EXPERIENCE
def preap_lateral_authorized(CP, CS, panda_states, panda_states_valid: bool) -> bool:
"""Match Pre-AP's existing safety authorization without treating software CC availability as ACC main."""
if not panda_states_valid or CS.out.gearShifter != structs.CarState.GearShifter.drive or CS.out.doorOpen or CS.out.steeringDisengage:
return False
if CS.engagement.lateralRearmRequired:
return False
config = CP.safetyConfigs[0]
matching = [p for p in panda_states if p.safetyModel == config.safetyModel and p.safetyParam == config.safetyParam]
if len(matching) != 1 or matching[0].safetyRxChecksInvalid:
return False
panda = matching[0]
# Physical cancel/override/gear changes clear this latch immediately, whereas
# Panda telemetry can lag. Longitudinal software cancellation leaves it intact.
stalk_authorized = CS.engagement.lateralEnabled and panda.controlsAllowed
stock_main = CS.di_cruise_state in ("STANDBY", "ENABLED", "STANDSTILL", "OVERRIDE", "PRE_FAULT", "PRE_CANCEL")
aol_authorized = bool(panda.alternativeExperience & ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL) and stock_main
return bool(stalk_authorized or aol_authorized)
@@ -88,8 +88,7 @@ class TeslaCANPreAP:
else:
values.update(_STW_DEFAULTS)
# Preserve the live stalk layout, but force VSL enable on engage/resume spoofs.
values["VSL_Enbl_Rq"] = 0 if button_to_press == 1 else 1
values["VSL_Enbl_Rq"] = 1
data = self.packer.make_can_msg("STW_ACTN_RQ", bus, values)[1]
values["CRC_STW_ACTN_RQ"] = _crc8_stw(data[:7])

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