Compare commits

..

12 Commits

Author SHA1 Message Date
eFini 61475e98f6 Add error handling for Params import
Handle ImportError for Params and set default value.
2025-12-03 22:20:02 +08:00
Rick Lan 06bcd25b48 translation 2025-11-29 19:21:23 +08:00
Rick Lan 31c5d72ab4 pre-build 2025-11-29 19:21:23 +08:00
Rick Lan e554c5132b fix: c4 lite detection 2025-11-29 19:21:10 +08:00
Rick Lan db4aa22794 feat(ui): C4 UI Mode 2025-11-26 16:55:10 +08:00
Rick Lan f68789f76e feat(ui): Torque Bar for c3 2025-11-26 16:46:53 +08:00
Rick Lan 8fb0289ff6 Fix: dp panel scroller type 2025-11-26 16:46:53 +08:00
Rick Lan aa4eeafa84 Fix: syntax error 2025-11-26 16:46:53 +08:00
Rick Lan 6ba90e3185 feat: Squash all pre-brand features into pre 2025-11-26 11:19:59 +08:00
Rick Lan c56902a616 feat: Squash all min-features into full 2025-11-26 11:10:07 +08:00
Rick Lan 1ebc3523c6 feat: Squash all core features into min 2025-11-26 10:25:16 +08:00
Vehicle Researcher 161005a0c1 openpilot v0.10.2 release
date: 2025-11-19T19:10:11
master commit: a29fdbd02407d41ecbcc69d151bb4837bfba3cbc
2025-11-19 19:10:16 -08:00
1753 changed files with 204286 additions and 282865 deletions
-19
View File
@@ -1,19 +0,0 @@
---
Checks: '
bugprone-*,
-bugprone-integer-division,
-bugprone-narrowing-conversions,
performance-*,
clang-analyzer-*,
misc-*,
-misc-unused-parameters,
modernize-*,
-modernize-avoid-c-arrays,
-modernize-deprecated-headers,
-modernize-use-auto,
-modernize-use-using,
-modernize-use-nullptr,
-modernize-use-trailing-return-type,
'
CheckOptions:
...
-21
View File
@@ -13,27 +13,6 @@
*.o-*
*.os
*.os-*
*.so
*.a
venv/
.venv/
notebooks
phone
massivemap
neos
installer
chffr/app2
chffr/backend/env
selfdrive/nav
selfdrive/baseui
selfdrive/test/simulator2
**/cache_data
xx/plus
xx/community
xx/projects
!xx/projects/eon_testing_master
!xx/projects/map3d
xx/ops
xx/junk
+1 -1
View File
@@ -12,7 +12,7 @@ simulation:
ui:
- changed-files:
- any-glob-to-all-files: '{selfdrive/ui/**,system/ui/**}'
- any-glob-to-all-files: '{selfdrive/assets/**,selfdrive/ui/**,system/ui/**}'
tools:
- changed-files:
+6 -10
View File
@@ -10,7 +10,6 @@ venv/
.overlay_init
.overlay_consistent
.sconsign.dblite
model2.png
a.out
.hypothesis
.cache/
@@ -37,30 +36,23 @@ a.out
*.class
*.pyxbldc
*.vcd
*.qm
*.mo
*_pyx.cpp
*.stats
config.json
clcache
compile_commands.json
compare_runtime*.html
persist
selfdrive/pandad/pandad
cereal/services.h
cereal/gen
cereal/messaging/bridge
selfdrive/mapd/default_speeds_by_region.json
system/proclogd/proclogd
selfdrive/ui/translations/tmp
selfdrive/test/longitudinal_maneuvers/out
selfdrive/car/tests/cars_dump
system/camerad/camerad
system/camerad/test/ae_gray_test
notebooks
hyperthneed
provisioning
.coverage*
coverage.xml
htmlcov
@@ -74,6 +66,7 @@ comma*.sh
selfdrive/modeld/models/*.pkl
# openpilot log files
*.bz2
*.zst
@@ -102,3 +95,6 @@ Pipfile
# Ignore all local history of files
.history
.ionide
# rick - keep panda_tici standalone
panda_tici/
+40 -1
View File
@@ -23,6 +23,11 @@
"id": "args",
"description": "Arguments to pass to the process",
"type": "promptString"
},
{
"id": "replayArg",
"type": "promptString",
"description": "Enter route or segment to replay."
}
],
"configurations": [
@@ -40,7 +45,41 @@
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/${input:cpp_process}",
"cwd": "${workspaceFolder}",
"cwd": "${workspaceFolder}"
},
{
"name": "Attach LLDB to Replay drive",
"type": "lldb",
"request": "attach",
"pid": "${command:pickMyProcess}",
"initCommands": [
"script import time; time.sleep(3)"
]
},
{
"name": "Replay drive",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/opendbc/safety/tests/safety_replay/replay_drive.py",
"args": [
"${input:replayArg}"
],
"console": "integratedTerminal",
"justMyCode": false,
"env": {
"PYTHONPATH": "${workspaceFolder}"
},
"subProcess": true,
"stopOnEntry": false
}
],
"compounds": [
{
"name": "Replay drive + Safety LLDB",
"configurations": [
"Replay drive",
"Attach LLDB to Replay drive"
]
}
]
}
+42 -28
View File
@@ -3,26 +3,29 @@ flowchart TD
B000["devel-staging"] ---> CORE["core"]
CORE ---> CORE_001["core-feat/params"]
CORE_001 ---> CORE_002["core-feat/panel"]
CORE_002 ---> CORE_003["core-feat/spin-box-ctrl"]
CORE_003 ---> MIN["min"]
MIN ---> MIN_001["min-feat/dev/o3"]
MIN ---> MIN_002["min-feat/lat/alka"]
MIN ---> MIN_003["min-feat/ui/display-mode"]
MIN ---> MIN_004["min-feat/dev/model-selector"]
MIN ---> MIN_005["min-feat/lat/lca"]
MIN ---> MIN_006["min-feat/dev/on-off-road"]
MIN ---> MIN_007["min-feat/ui/hide-hud"]
MIN ---> MIN_008["min-feat/lon/ext-radar"]
MIN ---> MIN_009["min-feat/lat/road-edge-detection"]
MIN ---> MIN_010["min-feat/ui/rainbow-path"]
MIN ---> MIN_011["min-feat/lon/acm"]
MIN ---> MIN_012["min-feat/lon/aem"]
MIN ---> MIN_013["min-feat/dev/alert-mode"]
MIN ---> MIN_014["min-feat/dev/auto-shutdown"]
MIN ---> MIN_015["min-feat/ui/radar-tracks"]
MIN ---> MIN_016["min-feat/ui/border-indicator"]
MIN ---> MIN_017["min-feat/dev/dashy"]
MIN ---> MIN_018["min-feat/dev/delay-loggerd"]
CORE_002 ---> MIN["min"]
MIN ---> MIN_001["min-feat/dev/c3"]
MIN ---> MIN_002["min-feat/dev/o3"]
MIN ---> MIN_003["min-feat/lat/alka"]
MIN ---> MIN_004["min-feat/ui/display-mode"]
MIN ---> MIN_005["min-feat/dev/model-selector"]
MIN ---> MIN_006["min-feat/lat/lca"]
MIN ---> MIN_007["min-feat/dev/on-off-road"]
MIN ---> MIN_008["min-feat/ui/hide-hud"]
MIN ---> MIN_009["min-feat/lon/ext-radar"]
MIN ---> MIN_010["min-feat/lat/road-edge-detection"]
MIN ---> MIN_011["min-feat/ui/rainbow-path"]
MIN ---> MIN_012["min-feat/lon/acm"]
MIN ---> MIN_013["min-feat/lon/aem"]
MIN ---> MIN_014["min-feat/lon/dtsc"]
MIN ---> MIN_015["min-feat/dev/alert-mode"]
MIN ---> MIN_016["min-feat/dev/auto-shutdown"]
MIN ---> MIN_017["min-feat/ui/lead-stats"]
MIN ---> MIN_018["min-feat/ui/border-indicator"]
MIN ---> MIN_019["min-feat/dev/dashy"]
MIN ---> MIN_010["min-feat/dev/delay-loggerd"]
MIN ---> MIN_021["min-feat/dev/disable-connect"]
MIN ---> MIN_022["min-feat/dev/tether-on-boot"]
MIN_001 ---> FULL["full"]
MIN_002 ---> FULL
MIN_003 ---> FULL
@@ -41,18 +44,25 @@ flowchart TD
MIN_016 ---> FULL
MIN_017 ---> FULL
MIN_018 ---> FULL
FULL ---> TOYOTA_001[brand/toyota/door-auto-lock-unlock]
FULL ---> TOYOTA_002[brand/toyota/tss1-sng]
FULL ---> TOYOTA_003[brand/toyota/long-filter-common]
MIN_019 ---> FULL
MIN_020 ---> FULL
MIN_021 ---> FULL
MIN_022 ---> FULL
FULL ---> TOYOTA_001[brand/toyota/safety-common]
FULL ---> TOYOTA_002[brand/toyota/door-auto-lock-unlock]
FULL ---> TOYOTA_003[brand/toyota/tss1-sng]
FULL ---> TOYOTA_004[brand/toyota/radar-filter]
FULL ---> TOYOTA_005[brand/toyota/sdsu]
FULL ---> TOYOTA_006[brand/toyota/zss]
FULL ---> TOYOTA_007[brand/toyota/stock-lon]
FULL ---> TOYOTA_006[brand/toyota/dsu-bypass]
FULL ---> TOYOTA_007[brand/toyota/zss]
FULL ---> TOYOTA_008[brand/toyota/stock-lon]
FULL ---> VAG_001[brand/vag/a0-sng]
FULL ---> VAG_002[brand/vag/pq-steering-patch]
FULL ---> VAG_003[brand/vag/pq-no-dashcam]
FULL ---> VAG_004[brand/vag/avoid-eps-lockout]
FULL ---> HKG_001[brand/hkg/smdps]
FULL ---> HONDA_001[brand/honda/eps-mod]
FULL ---> SUBARU_001[brand/subaru/torque-3071]
TOYOTA_001 ---> TOYOTA[pre-toyota]
TOYOTA_002 ---> TOYOTA
TOYOTA_003 ---> TOYOTA
@@ -60,16 +70,20 @@ flowchart TD
TOYOTA_005 ---> TOYOTA
TOYOTA_006 ---> TOYOTA
TOYOTA_007 ---> TOYOTA
TOYOTA_008 ---> TOYOTA
VAG_001 ---> VAG[pre-vag]
VAG_002 ---> VAG
VAG_003 ---> VAG
VAG_004 ---> VAG
HKG_001 ---> HKG[pre-hkg]
HONDA_001 ---> HONDA[pre-honda]
SUBARU_001 ---> SUBARU[pre-subaru]
TOYOTA ---> PRE[pre]
VAG ---> PRE
HKG ---> PRE
HONDA ---> PRE
SUBARU ---> PRE
PRE ---> PRE_PATCH[pre-patch]
PRE_PATCH ---> PRE_001[pre-build]
PRE_001 ---> PRERELEASE[pre-release]
PRERELEASE ---> RELEASE[x.x.x]
PRE_PATCH ---> PREBUILD[pre-build]
PREBUILD ---> VERSION[x.x.x]
```
+191 -1
View File
@@ -1,7 +1,197 @@
dragonpilot 0.10.2 r1 (2025-11-30)
=======================
* ✅ openpilot 0.10.2 (devel-staging) (2025-11-20)
* ✨ 🚧 UI: C4 UI Mode (2025-11-26)
* ✨ UI: Torque Bar from C4 UI (2025-11-26)
* ✅ UI: Extend screen timeout (2025-11-04)
* ✅ UI: Display Lead & Radar Stats (2025-10-30)
* ✅ UI: Border Indicators (2025-05-27)
* ✅ UI: Rainbow Path (2025-04-02)
* ✅ UI: Speed Based HUD (2025-04-01)
* ✅ UI: Display Mode (2025-03-14)
* ✅ LAT: Auto Lane Change (2025-04-14)
* ✅ LAT: Road Edge Detection (2025-04-01)
* ✅ LAT: LCA Speed Changer (2025-03-21)
* ✅ LAT: ALKA (2025-03-14)
* ✅ 🚧 LON: Dynamic Turn Speed Control (DTSC) (2025-11-04)
* ✅ 🚧 LON: Adaptive Experimental Mode (AEM) (2025-05-14)
* ✅ LON: Adaptive Coasting Mode (ACM) (2025-04-06)
* ✅ LON: Ext Radar Support (2025-03-31)
* ✅ MISC: Branch Selector (2025-11-14)
* ✅ MISC: Tether on boot (2025-11-04)
* ✅ MISC: Disable Comma Connect (2025-09-22)
* ✅ MISC: C3/C3X/O3 series Support (2025-09-15)
* ✅ MISC: Delay Starting Loggerd (2025-06-30)
* ✅ MISC: Auto Shutdown (2025-04-28)
* ✅ MISC: Increase Max Speed Allowed (2025-04-18)
* ✅ MISC: Audible Alert Mode (2025-04-17)
* ✅ MISC: Error Logs Btn (2025-04-08)
* ✅ MISC: Reset Conf Btn (2025-04-08)
* ✅ MISC: Model Selector (2025-03-14)
* ✅ Toyota: DSU Bypass Support by @cydia2020 (2025-09-09)
* ✅ Toyota: Stock Lon Mode (2025-04-01)
* ✅ Toyota: Door Auto Lock/Unlock (2025-03-14)
* ✅ Toyota: Radar Filter (2025-03-14)
* ✅ Toyota: TSS1 SnG Mod (2025-03-14)
* ✅ Toyota: SDSU Support (2025-03-14)
* ✅ Toyota: ZSS Support (2025-03-14)
* ✅ Honda: EPS Mod Support (2025-09-09)
* ✅ VAG: Avoid EPS Lockout Toggle (2025-04-17)
* ✅ VAG: Remove Dashcam for VAG PQ (2025-04-06)
* ✅ VAG: PQ Steering Patch (2025-04-02)
* ✅ VAG: MQB A0 SnG Mod (2025-03-20)
* ✅ HKG: SMDPS Support (2025-04-17)
* ✅ 🚧 Subaru: Increase torque limits for early Impreza/Crosstek by @ClockeNessMnstr (2025-09-25)
dragonpilot 0.10.1 r3 (2025-11-25)
=======================
* ✅ openpilot 0.10.1 (devel-staging) (2025-10-24)
* ✨ Various Bugfixes / Improvements (2025-11-25)
* ✨ UI: Back to Display Mode v1 (std., main+, main-, op+, op-) (2025-11-25)
* ✅ UI: Extend screen timeout (2025-11-04)
* ✅ UI: Display Lead & Radar Stats (2025-10-30)
* ✅ UI: Border Indicators (2025-05-27)
* ✅ UI: Rainbow Path (2025-04-02)
* ✅ UI: Speed Based HUD (2025-04-01)
* ✅ UI: Display Mode (2025-03-14)
* ✅ LAT: Auto Lane Change (2025-04-14)
* ✅ LAT: Road Edge Detection (2025-04-01)
* ✅ LAT: LCA Speed Changer (2025-03-21)
* ✅ LAT: ALKA (2025-03-14)
* ✅ 🚧 LON: Dynamic Turn Speed Control (DTSC) (2025-11-04)
* ✅ 🚧 LON: Adaptive Experimental Mode (AEM) (2025-05-14)
* ✅ LON: Adaptive Coasting Mode (ACM) (2025-04-06)
* ✅ LON: Ext Radar Support (2025-03-31)
* ✅ MISC: Branch Selector (2025-11-14)
* ✅ MISC: Tether on boot (2025-11-04)
* ✅ MISC: Disable Comma Connect (2025-09-22)
* ✅ MISC: C3/C3X/O3 series Support (2025-09-15)
* ✅ MISC: Delay Starting Loggerd (2025-06-30)
* ✅ MISC: Auto Shutdown (2025-04-28)
* ✅ MISC: Increase Max Speed Allowed (2025-04-18)
* ✅ MISC: Audible Alert Mode (2025-04-17)
* ✅ MISC: Error Logs Btn (2025-04-08)
* ✅ MISC: Reset Conf Btn (2025-04-08)
* ✅ MISC: Model Selector (2025-03-14)
* ✅ Toyota: DSU Bypass Support by @cydia2020 (2025-09-09)
* ✅ Toyota: Stock Lon Mode (2025-04-01)
* ✅ Toyota: Door Auto Lock/Unlock (2025-03-14)
* ✅ Toyota: Radar Filter (2025-03-14)
* ✅ Toyota: TSS1 SnG Mod (2025-03-14)
* ✅ Toyota: SDSU Support (2025-03-14)
* ✅ Toyota: ZSS Support (2025-03-14)
* ✅ Honda: EPS Mod Support (2025-09-09)
* ✅ VAG: Avoid EPS Lockout Toggle (2025-04-17)
* ✅ VAG: Remove Dashcam for VAG PQ (2025-04-06)
* ✅ VAG: PQ Steering Patch (2025-04-02)
* ✅ VAG: MQB A0 SnG Mod (2025-03-20)
* ✅ HKG: SMDPS Support (2025-04-17)
* ✅ 🚧 Subaru: Increase torque limits for early Impreza/Crosstek by @ClockeNessMnstr (2025-09-25)
*~~🚧 AGNOS 14.7 (2025-11-11)~~
*~~LON: No Gas Gating Mode (NoGG) Toggle (2025-04-19)~~
*~~MISC: FileServ (port 5000) (2025-05-28)~~
dragonpilot 0.10.1 r2 (2025-11-16)
=======================
* ✅ openpilot 0.10.1 (devel-staging) (2025-10-24)
* ✨ Various Bugfixes / Improvements (2025-11-14)
* ✨ UI: Display Mode v2 - (std., op+, op- ONLY) (2025-11-14)
* ✨ MISC: Branch Selector (2025-11-14)
* ✅ UI: Extend screen timeout (2025-11-04)
* ✅ UI: Display Lead & Radar Stats (2025-10-30)
* ✅ UI: Border Indicators (2025-05-27)
* ✅ UI: Rainbow Path (2025-04-02)
* ✅ UI: Speed Based HUD (2025-04-01)
* ✅ UI: Display Mode (2025-03-14)
* ✅ LAT: Auto Lane Change (2025-04-14)
* ✅ LAT: Road Edge Detection (2025-04-01)
* ✅ LAT: LCA Speed Changer (2025-03-21)
* ✅ LAT: ALKA (2025-03-14)
* ✅ 🚧 LON: Dynamic Turn Speed Control (DTSC) (2025-11-04)
* ✅ 🚧 LON: Adaptive Experimental Mode (AEM) (2025-05-14)
* ✅ LON: Adaptive Coasting Mode (ACM) (2025-04-06)
* ✅ LON: Ext Radar Support (2025-03-31)
* ✅ MISC: Tether on boot (2025-11-04)
* ✅ MISC: Disable Comma Connect (2025-09-22)
* ✅ MISC: C3/C3X/O3 series Support (2025-09-15)
* ✅ MISC: Delay Starting Loggerd (2025-06-30)
* ✅ MISC: Auto Shutdown (2025-04-28)
* ✅ MISC: Increase Max Speed Allowed (2025-04-18)
* ✅ MISC: Audible Alert Mode (2025-04-17)
* ✅ MISC: Error Logs Btn (2025-04-08)
* ✅ MISC: Reset Conf Btn (2025-04-08)
* ✅ MISC: Model Selector (2025-03-14)
* ✅ Toyota: DSU Bypass Support by @cydia2020 (2025-09-09)
* ✅ Toyota: Stock Lon Mode (2025-04-01)
* ✅ Toyota: Door Auto Lock/Unlock (2025-03-14)
* ✅ Toyota: Radar Filter (2025-03-14)
* ✅ Toyota: TSS1 SnG Mod (2025-03-14)
* ✅ Toyota: SDSU Support (2025-03-14)
* ✅ Toyota: ZSS Support (2025-03-14)
* ✅ Honda: EPS Mod Support (2025-09-09)
* ✅ VAG: Avoid EPS Lockout Toggle (2025-04-17)
* ✅ VAG: Remove Dashcam for VAG PQ (2025-04-06)
* ✅ VAG: PQ Steering Patch (2025-04-02)
* ✅ VAG: MQB A0 SnG Mod (2025-03-20)
* ✅ HKG: SMDPS Support (2025-04-17)
* ✅ 🚧 Subaru: Increase torque limits for early Impreza/Crosstek by @ClockeNessMnstr (2025-09-25)
*~~🚧 AGNOS 14.7 (2025-11-11)~~
*~~LON: No Gas Gating Mode (NoGG) Toggle (2025-04-19)~~
*~~MISC: FileServ (port 5000) (2025-05-28)~~
dragonpilot 0.10.1 r1 (2025-11-11)
=======================
* ✨ openpilot 0.10.1 (devel-staging) (2025-10-24)
* ✨ 🚧 AGNOS 14.7 (2025-11-11)
* ✨ UI: Extend screen timeout (2025-11-04)
* ✨ UI: Display Lead & Radar Stats (2025-10-30)
* ✨ 🚧 LON/AEM: Refactored w/ New Logic (2025-11-10)
* ✨ 🚧 LON: Dynamic Turn Speed Control (DTSC) (2025-11-04)
* ✨ 🚧 LON/ACM: Refactored w/ New Logic (2025-09-19)
* ✨ MISC: Tether on boot (2025-11-04)
* ✨ MISC: Disable Comma Connect (2025-09-22)
* ✨ MISC: C3/C3X/O3 series Support (2025-09-15)
* ✨ Toyota: sDSU jerk fix by @sunnyhaibin (2025-09-30)
* ✨ 🚧 Subaru: Increase torque limits for early Impreza/Crosstek by @ClockeNessMnstr (2025-09-25)
* ✅ UI: Border Indicators (2025-05-27)
* ✅ UI: Rainbow Path (2025-04-02)
* ✅ UI: Speed Based HUD (2025-04-01)
* ✅ UI: Display Mode (2025-03-14)
* ✅ LAT: Auto Lane Change (2025-04-14)
* ✅ LAT: Road Edge Detection (2025-04-01)
* ✅ LAT: LCA Speed Changer (2025-03-21)
* ✅ LAT: ALKA (2025-03-14)
* ✅ 🚧 LON: Adaptive Experimental Mode (AEM) (2025-05-14)
* ✅ LON: Adaptive Coasting Mode (ACM) (2025-04-06)
* ✅ LON: Ext Radar Support (2025-03-31)
* ✅ MISC: Delay Starting Loggerd (2025-06-30)
* ✅ MISC: Auto Shutdown (2025-04-28)
* ✅ MISC: Increase Max Speed Allowed (2025-04-18)
* ✅ MISC: Audible Alert Mode (2025-04-17)
* ✅ MISC: Error Logs Btn (2025-04-08)
* ✅ MISC: Reset Conf Btn (2025-04-08)
* ✅ MISC: Model Selector (2025-03-14)
* ✅ Toyota: DSU Bypass Support by @cydia2020 (2025-09-09)
* ✅ Toyota: Stock Lon Mode (2025-04-01)
* ✅ Toyota: Door Auto Lock/Unlock (2025-03-14)
* ✅ Toyota: Radar Filter (2025-03-14)
* ✅ Toyota: TSS1 SnG Mod (2025-03-14)
* ✅ Toyota: SDSU Support (2025-03-14)
* ✅ Toyota: ZSS Support (2025-03-14)
* ✅ Honda: EPS Mod Support (2025-09-09)
* ✅ VAG: Avoid EPS Lockout Toggle (2025-04-17)
* ✅ VAG: Remove Dashcam for VAG PQ (2025-04-06)
* ✅ VAG: PQ Steering Patch (2025-04-02)
* ✅ VAG: MQB A0 SnG Mod (2025-03-20)
* ✅ HKG: SMDPS Support (2025-04-17)
*~~LON: No Gas Gating Mode (NoGG) Toggle (2025-04-19)~~
*~~MISC: FileServ (port 5000) (2025-05-28)~~
dragonpilot 0.10.0 r2 (2025-09-15)
=======================
* ✅ openpilot 0.10.0 (devel-staging) (2025-08-06)
* ✨ 🚧 Toyota: DSU Bypass Support by cydia2020 (Disabled, Code Change Requred) (2025-09-09)
* ✨ 🚧 Toyota: DSU Bypass Support by @cydia2020 (Disabled, Code Change Requred) (2025-09-09)
* ✨ Honda: EPS Mod Support (2025-09-09)
* ✅ UI: Border Indicators (2025-05-27)
* ✅ UI: Display Radar Tracks (2025-05-14)
+3 -1
View File
@@ -9,4 +9,6 @@ WORKDIR ${OPENPILOT_PATH}
COPY . ${OPENPILOT_PATH}/
RUN scons --cache-readonly -j$(nproc)
ENV UV_BIN="/home/batman/.local/bin/"
ENV PATH="$UV_BIN:$PATH"
RUN UV_PROJECT_ENVIRONMENT=$VIRTUAL_ENV uv run scons --cache-readonly -j$(nproc)
Vendored
+11 -25
View File
@@ -167,7 +167,7 @@ node {
env.GIT_COMMIT = checkout(scm).GIT_COMMIT
def excludeBranches = ['__nightly', 'devel', 'devel-staging', 'release3', 'release3-staging',
'release-tici', 'testing-closet*', 'hotfix-*']
'release-tici', 'release-tizi', 'release-tizi-staging', 'testing-closet*', 'hotfix-*']
def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*')
if (env.BRANCH_NAME != 'master' && !env.BRANCH_NAME.contains('__jenkins_loop_')) {
@@ -178,20 +178,20 @@ node {
try {
if (env.BRANCH_NAME == 'devel-staging') {
deviceStage("build release3-staging", "tici-needs-can", [], [
step("build release3-staging", "RELEASE_BRANCH=release3-staging $SOURCE_DIR/release/build_release.sh"),
deviceStage("build release-tizi-staging", "tizi-needs-can", [], [
step("build release-tizi-staging", "RELEASE_BRANCH=release-tizi-staging $SOURCE_DIR/release/build_release.sh"),
])
}
if (env.BRANCH_NAME == '__nightly') {
parallel (
'nightly': {
deviceStage("build nightly", "tici-needs-can", [], [
deviceStage("build nightly", "tizi-needs-can", [], [
step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"),
])
},
'nightly-dev': {
deviceStage("build nightly-dev", "tici-needs-can", [], [
deviceStage("build nightly-dev", "tizi-needs-can", [], [
step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/release/build_release.sh"),
])
},
@@ -200,39 +200,30 @@ node {
if (!env.BRANCH_NAME.matches(excludeRegex)) {
parallel (
// tici tests
'onroad tests': {
deviceStage("onroad", "tici-needs-can", ["UNSAFE=1"], [
deviceStage("onroad", "tizi-needs-can", ["UNSAFE=1"], [
step("build openpilot", "cd system/manager && ./build.py"),
step("check dirty", "release/check-dirty.sh"),
step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]),
])
},
'HW + Unit Tests': {
deviceStage("tici-hardware", "tici-common", ["UNSAFE=1"], [
deviceStage("tizi-hardware", "tizi-common", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py"),
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"),
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py", [diffPaths: ["system/loggerd/"]]),
step("test pigeond", "pytest system/ubloxd/tests/test_pigeond.py", [diffPaths: ["system/ubloxd/"]]),
step("test manager", "pytest system/manager/test/test_manager.py"),
])
},
'loopback': {
deviceStage("loopback", "tici-loopback", ["UNSAFE=1"], [
deviceStage("loopback", "tizi-loopback", ["UNSAFE=1"], [
step("build openpilot", "cd system/manager && ./build.py"),
step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
])
},
'camerad AR0231': {
deviceStage("AR0231", "tici-ar0231", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py"),
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
])
},
'camerad OX03C10': {
deviceStage("OX03C10", "tici-ox03c10", ["UNSAFE=1"], [
deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py"),
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
@@ -246,17 +237,13 @@ node {
])
},
'sensord': {
deviceStage("LSM + MMC", "tici-lsmc", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py"),
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
])
deviceStage("BMX + LSM", "tici-bmx-lsm", ["UNSAFE=1"], [
deviceStage("LSM + MMC", "tizi-lsmc", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py"),
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
])
},
'replay': {
deviceStage("model-replay", "tici-replay", ["UNSAFE=1"], [
deviceStage("model-replay", "tizi-replay", ["UNSAFE=1"], [
step("build", "cd system/manager && ./build.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
step("model replay", "selfdrive/test/process_replay/model_replay.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
])
@@ -266,7 +253,6 @@ node {
step("build openpilot", "cd system/manager && ./build.py"),
step("test pandad loopback", "SINGLE_PANDA=1 pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"),
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
// TODO: enable once new AGNOS is available
// step("test esim", "pytest system/hardware/tici/tests/test_esim.py"),
+47 -81
View File
@@ -1,108 +1,74 @@
<div align="center" style="text-align: center;">
![](dragonpilot/selfdrive/assets/dragonpilot.png)
<h1>openpilot</h1>
[Read this in English](README_EN.md)
<p>
<b>openpilot is an operating system for robotics.</b>
<br>
Currently, it upgrades the driver assistance system in 300+ supported cars.
</p>
# **🐲 dragonpilot - 賦予您的愛車「龍」之魂**
<h3>
<a href="https://docs.comma.ai">Docs</a>
<span> · </span>
<a href="https://docs.comma.ai/contributing/roadmap/">Roadmap</a>
<span> · </span>
<a href="https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md">Contribute</a>
<span> · </span>
<a href="https://discord.comma.ai">Community</a>
<span> · </span>
<a href="https://comma.ai/shop">Try it on a comma 3X</a>
</h3>
**我們與您一同翱翔於更智慧、更貼心的駕駛旅程。**
Quick start: `bash <(curl -fsSL openpilot.comma.ai)`
## **👋 嘿, 朋友,歡迎您的到來!**
[![openpilot tests](https://github.com/commaai/openpilot/actions/workflows/selfdrive_tests.yaml/badge.svg)](https://github.com/commaai/openpilot/actions/workflows/selfdrive_tests.yaml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![X Follow](https://img.shields.io/twitter/follow/comma_ai)](https://x.com/comma_ai)
[![Discord](https://img.shields.io/discord/469524606043160576)](https://discord.comma.ai)
`dragonpilot` 誕生於 2019 年,由三位早期的 openpilot 華人玩家共同創立。初衷很簡單:為廣大的華人用戶、玩家們提供一個友善的交流環境、更簡便的設定協助,並加入更多適合在地使用的貼心功能。
</div>
我們深知在地化的重要性,特別是語言的親切感。因此,我們率先導入了完整的中文介面,讓 `dragonpilot` 迅速在華語地區累積了口碑,也讓華人的使用者數量在全球名列前茅。這份來自在地的支持,是我們持續前進的最大動力。
<table>
<tr>
<td><a href="https://youtu.be/NmBfgOanCyk" title="Video By Greer Viau"><img src="https://github.com/commaai/openpilot/assets/8762862/2f7112ae-f748-4f39-b617-fabd689c3772"></a></td>
<td><a href="https://youtu.be/VHKyqZ7t8Gw" title="Video By Logan LeGrand"><img src="https://github.com/commaai/openpilot/assets/8762862/92351544-2833-40d7-9e0b-7ef7ae37ec4c"></a></td>
<td><a href="https://youtu.be/SUIZYzxtMQs" title="A drive to Taco Bell"><img src="https://github.com/commaai/openpilot/assets/8762862/05ceefc5-2628-439c-a9b2-89ce77dc6f63"></a></td>
</tr>
</table>
我們以功能強大的 [openpilot](https://github.com/commaai/openpilot) 為基礎——這套據美國消費者報告評測優於市售車方案的開源輔助駕駛系統——融入了更多在地化的巧思與客製化的溫度,希望能打造出最符合您需求的駕駛夥伴。(您也可以參考我們 repo 中保留的 [openpilot 原始說明檔案](README_OPENPILOT.md))
取名 `dragonpilot`,是因為我們希望它能像神話中的「龍」一樣,既強大又充滿智慧,為您的行車安全保駕護航。龍,在我們華人文化中,更是吉祥與力量的象徵,也代表著我們的根源與驕傲。
Using openpilot in a car
------
## **✨ dragonpilot 的里程碑**
To use openpilot in a car, you need four things:
1. **Supported Device:** a comma 3/3X, available at [comma.ai/shop](https://comma.ai/shop/comma-3x).
2. **Software:** The setup procedure for the comma 3/3X allows users to enter a URL for custom software. Use the URL `openpilot.comma.ai` to install the release version.
3. **Supported Car:** Ensure that you have one of [the 275+ supported cars](docs/CARS.md).
4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma 3/3X to your car.
我們不僅保留了 openpilot 的核心優勢,更達成了許多從社群回饋中誕生的里程碑,這些是我們引以為傲的足跡:
We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup). Note that it's possible to run openpilot on [other hardware](https://blog.comma.ai/self-driving-car-for-free/), although it's not plug-and-play.
* **🚘 全時置中車道維持 (ALKA)**
### Branches
| branch | URL | description |
|------------------|----------------------------------------|-------------------------------------------------------------------------------------|
| `release3` | openpilot.comma.ai | This is openpilot's release branch. |
| `release3-staging` | openpilot-test.comma.ai | This is the staging branch for releases. Use it to get new releases slightly early. |
| `nightly` | openpilot-nightly.comma.ai | This is the bleeding edge development branch. Do not expect this to be stable. |
| `nightly-dev` | installer.comma.ai/commaai/nightly-dev | Same as nightly, but includes experimental development features for some cars. |
| `secretgoodopenpilot` | installer.comma.ai/commaai/secretgoodopenpilot | This is a preview branch from the autonomy team where new driving models get merged earlier than master. |
這不只是一個功能,更是 `dragonpilot` 的哲學。我們最早於 [0.6.2 版本](https://github.com/dragonpilot-community/dragonpilot/blob/2861467183d62151024320447ba04d18fc3fe1e6/selfdrive/car/toyota/carstate.py#L199) 時便實現了這個功能,其開發歷程始於 2017 Lexus IS300h,接著擴展至 Toyota 全車系,並逐步延伸到其他支援的品牌。它能溫柔地輔助您,讓車輛始終穩定地保持在車道中央,提供一份額外的安心與從容。
To start developing openpilot
------
* **🌐 率先導入多國語言介面**
openpilot is developed by [comma](https://comma.ai/) and by users like you. We welcome both pull requests and issues on [GitHub](http://github.com/commaai/openpilot).
在官方 openpilot 還未支援前,我們便已將多國語言介面實現。`dragonpilot` 完整支援繁體中文、簡體中文與英文,讓操作毫無隔閡。
* Join the [community Discord](https://discord.comma.ai)
* Check out [the contributing docs](docs/CONTRIBUTING.md)
* Check out the [openpilot tools](tools/)
* Code documentation lives at https://docs.comma.ai
* Information about running openpilot lives on the [community wiki](https://github.com/commaai/openpilot/wiki)
* **💻 唯一同時支援多硬體平台**
Want to get paid to work on openpilot? [comma is hiring](https://comma.ai/jobs#open-positions) and offers lots of [bounties](https://comma.ai/bounties) for external contributors.
我們是唯一曾致力於讓專案同時兼容 EON、comma two、comma 3 與 Jetson 平台的社群分支,這份努力是為了服務最廣大的玩家社群。
此外,在 comma.ai 團隊於 0.10.0 版本宣布停止支持 comma 3 後,我們仍是唯一一個完整同時支援 comma 3、comma 3X 以及 O3、O3L、O3XL(O3 系列為副廠硬體)的社群分支。
Safety and Testing
----
* **📜 曾榮獲官方認證第一大分支**
* openpilot observes [ISO26262](https://en.wikipedia.org/wiki/ISO_26262) guidelines, see [SAFETY.md](docs/SAFETY.md) for more details.
* openpilot has software-in-the-loop [tests](.github/workflows/selfdrive_tests.yaml) that run on every commit.
* The code enforcing the safety model lives in panda and is written in C, see [code rigor](https://github.com/commaai/panda#code-rigor) for more details.
* panda has software-in-the-loop [safety tests](https://github.com/commaai/panda/tree/master/tests/safety).
* Internally, we have a hardware-in-the-loop Jenkins test suite that builds and unit tests the various processes.
* panda has additional hardware-in-the-loop [tests](https://github.com/commaai/panda/blob/master/Jenkinsfile).
* We run the latest openpilot in a testing closet containing 10 comma devices continuously replaying routes.
基於活躍的社群與功能創新,`dragonpilot` 曾一度成長為 comma ai 官方認證的第一大 openpilot 分支,這份榮耀屬於每一位參與者。
<details>
<summary>MIT Licensed</summary>
## **🧑‍💻 設計理念 - 少即是多 (Less is More)**
openpilot is released under the MIT license. Some parts of the software are released under other licenses as specified.
隨著 openpilot 的 AI 模型日益強大,許多過去需要手動微調的功能,現在都已能透過更先進的模型來實現。因此,我們現在的開發重心回歸到 **「最小化修改」(minimal changes)** 的核心原則上。
Any user of this software shall indemnify and hold harmless Comma.ai, Inc. and its directors, officers, employees, agents, stockholders, affiliates, subcontractors and customers from and against all allegations, claims, actions, suits, demands, damages, liabilities, obligations, losses, settlements, judgments, costs and expenses (including without limitation attorneys fees and costs) which arise out of, relate to or result from any use of this software by user.
我們的目標是為您提供最純粹、最接近官方的 openpilot 駕駛感受,同時保留 `dragonpilot` 那些經過時間考驗、最受社群喜愛的經典功能。我們相信,在強大的 AI 基礎上,簡潔即是力量。
**THIS IS ALPHA QUALITY SOFTWARE FOR RESEARCH PURPOSES ONLY. THIS IS NOT A PRODUCT.
YOU ARE RESPONSIBLE FOR COMPLYING WITH LOCAL LAWS AND REGULATIONS.
NO WARRANTY EXPRESSED OR IMPLIED.**
</details>
## **🛠️ 硬件的足跡 - 一路走來的夥伴們**
<details>
<summary>User Data and comma Account</summary>
從最早的 **EON**,到官方的 **comma two / three (C2/C3/C3X)**,再到社群中各式各樣充滿智慧的**副廠機 (如 C1.5, O2, O3, O3L, O3XL 等)**,甚至我們也曾探索過在 [**Jetson Xavier NX**](https://github.com/eFiniLan/xnxpilot) 上的可能性。
By default, openpilot uploads the driving data to our servers. You can also access your data through [comma connect](https://connect.comma.ai/). We use your data to train better models and improve openpilot for everyone.
目前最新版本主要支援: comma3 / 3X 以及 O3 / O3L / O3XL 等社群硬體。
針對 EON / C1.5 / C2 等舊款硬體,最後支援的版本位於 [d2 分支](https://github.com/dragonpilot-community/dragonpilot/tree/d2)。
無論您手上是哪一款設備,都代表著您對開源駕駛輔助的一份熱情。
openpilot is open source software: the user is free to disable data collection if they wish to do so.
## **🫂 加入我們,成為「尋龍者」的一份子**
openpilot logs the road-facing cameras, CAN, GPS, IMU, magnetometer, thermal sensors, crashes, and operating system logs.
The driver-facing camera and microphone are only logged if you explicitly opt-in in settings.
`dragonpilot` 的成長,離不開每一位使用者的貢獻與回饋。我們是一個以**公開、透明**為原則的溫暖社群,希望在這裡能與所有對 openpilot / dragonpilot 有興趣的用戶分享、交流開發與使用上的經驗。
By using openpilot, you agree to [our Privacy Policy](https://comma.ai/privacy). You understand that use of this software or its related services will generate certain types of user data, which may be logged and stored at the sole discretion of comma. By accepting this agreement, you grant an irrevocable, perpetual, worldwide right to comma for the use of this data.
</details>
[**歡迎加入我們的 Facebook 社團進行交流!**](https://www.facebook.com/groups/930190251238639)
## **❤️ 特別感謝**
`dragonpilot` 從創立至今,從未打算透過 Patreon 等平台進行任何形式的募資。我們的初衷是建立一個讓大家能一起學習、一起成長的社群。It's all about fun, not money.
然而,我們仍要對那些自發性支持本專案的朋友們,致上最誠摯的感謝。正是因為有您們的鼓勵,我們才有更大的動力持續前進。
[**我們的贊助者名單**](SPONSORS.md)
### **安全聲明**
`dragonpilot` 是一種駕駛**輔助**系統,並非全自動駕駛。它旨在減輕您的駕駛疲勞,提升行車安全,但駕駛人仍需時刻保持專注,並隨時準備接管車輛。請務必遵守您所在地區的交通法規。
**最後,再次感謝您的到來。**
**期待與您一同在智慧駕駛的道路上,乘「龍」而行!**
+74
View File
@@ -0,0 +1,74 @@
![](dragonpilot/selfdrive/assets/dragonpilot.png)
[Read this in Chinese](README.md)
# **🐲 dragonpilot - Bringing the Spirit of the Dragon to Your Car**
**Join us on a smarter, more thoughtful driving journey.**
## **👋 Welcome, friend!**
`dragonpilot` was launched in 2019 by three early openpilot enthusiasts from the Chinese community. Our mission was simple: create a friendly space for users to share experiences, provide easier setup help, and add features tailored for local needs.
Localization has always been at the heart of what we do—starting with a fully Chinese interface. This made `dragonpilot` quickly popular in Chinese-speaking regions and helped our user base grow into one of the largest worldwide. That community support is what keeps us moving forward.
Built on top of the powerful [openpilot](https://github.com/commaai/openpilot)—an open-source driver assistance system rated by Consumer Reports as outperforming commercial offerings—we add localized refinements and user-focused features to create a driving companion that truly fits your needs. (You can also see the [original openpilot README](README_OPENPILOT.md) preserved in our repo.)
The name `dragonpilot` reflects our vision: like the dragon of mythology, it is strong and wise, guarding your safety on the road. In Chinese culture, the dragon is also a symbol of luck and strength, representing our roots and pride.
## **✨ Milestones**
Beyond carrying forward openpilot's core strengths, we've reached several milestones inspired by community feedback:
* **🚘 Always Lane Keep Assist (ALKA)**
More than a feature—it's part of the `dragonpilot` philosophy. Introduced as early as [version 0.6.2](https://github.com/dragonpilot-community/dragonpilot/blob/2861467183d62151024320447ba04d18fc3fe1e6/selfdrive/car/toyota/carstate.py#L199), first tested on a 2017 Lexus IS300h, then expanded to Toyota's lineup and beyond. ALKA helps keep your vehicle steadily centered, giving you extra confidence on the road.
* **🌐 First to add multilingual support**
Before openpilot officially supported it, we had already introduced multiple languages. `dragonpilot` fully supports Traditional Chinese, Simplified Chinese, and English.
* **💻 Only community fork to support multiple hardware platforms at once**
We uniquely worked to make the project run on EON, comma two, comma 3, and Jetson—serving the widest range of users possible.
Additionally, after the comma.ai team deprecated the comma 3 in version 0.10.0, we remain the only community fork to offer full, simultaneous support for the comma 3, comma 3X, and the O3, O3L, and O3XL (the O3 series being third-party hardware).
* **📜 Once recognized as the #1 openpilot fork**
Thanks to an active community and continuous innovation, `dragonpilot` was once the largest openpilot fork officially recognized by comma ai. This honor belongs to everyone who contributed.
## **🧑‍💻 Design Philosophy - Less is More**
As openpilot's AI grows stronger, many features that once required manual tuning are now handled by advanced models. That's why our focus has returned to **“minimal changes.”**
We aim to give you the purest, most official-like openpilot driving experience—while preserving `dragonpilot`'s classic, community-loved features. With a solid AI foundation, simplicity is strength.
## **🛠️ Hardware Journey**
From the early **EON**, to official devices like **comma two / three (C2/C3/C3X)**, to creative community builds (**C1.5, O2, O3, O3L, O3XL, etc.**), and even experiments with [**Jetson Xavier NX**](https://github.com/eFiniLan/xnxpilot).
Currently, the latest versions support: **comma3 / 3X** and community hardware like **O3 / O3L / O3XL**.
Older devices such as **EON / C1.5 / C2** are supported in the [d2 branch](https://github.com/dragonpilot-community/dragonpilot/tree/d2).
Whatever device you're on, it represents your passion for open-source driver assistance.
## **🫂 Join Us Become a “Dragon Seeker”**
`dragonpilot` thrives thanks to every user's contributions and feedback. We're an open, transparent, and welcoming community where enthusiasts can share experiences with openpilot and `dragonpilot`.
[**Join our Facebook group here!**](https://www.facebook.com/groups/930190251238639)
## **❤️ Special Thanks**
Since day one, `dragonpilot` has never asked for funding through Patreon or similar platforms. Our vision is a community where everyone learns and grows together. It's about fun, not money.
That said, we're deeply grateful to those who voluntarily supported the project. Your encouragement keeps us motivated to keep building.
[**See our sponsors**](SPONSORS.md)
### **Safety Notice**
`dragonpilot` is a driver **assistance** system, not full self-driving. It reduces fatigue and improves safety, but you must remain alert and ready to take control at all times. Always follow your local traffic laws.
**Thanks again for being here.**
**We look forward to riding the “dragon” with you on the road to smarter driving!**
+107
View File
@@ -0,0 +1,107 @@
<div align="center" style="text-align: center;">
<h1>openpilot</h1>
<p>
<b>openpilot is an operating system for robotics.</b>
<br>
Currently, it upgrades the driver assistance system in 300+ supported cars.
</p>
<h3>
<a href="https://docs.comma.ai">Docs</a>
<span> · </span>
<a href="https://docs.comma.ai/contributing/roadmap/">Roadmap</a>
<span> · </span>
<a href="https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md">Contribute</a>
<span> · </span>
<a href="https://discord.comma.ai">Community</a>
<span> · </span>
<a href="https://comma.ai/shop">Try it on a comma 3X</a>
</h3>
Quick start: `bash <(curl -fsSL openpilot.comma.ai)`
[![openpilot tests](https://github.com/commaai/openpilot/actions/workflows/tests.yaml/badge.svg)](https://github.com/commaai/openpilot/actions/workflows/tests.yaml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![X Follow](https://img.shields.io/twitter/follow/comma_ai)](https://x.com/comma_ai)
[![Discord](https://img.shields.io/discord/469524606043160576)](https://discord.comma.ai)
</div>
<table>
<tr>
<td><a href="https://youtu.be/NmBfgOanCyk" title="Video By Greer Viau"><img src="https://github.com/commaai/openpilot/assets/8762862/2f7112ae-f748-4f39-b617-fabd689c3772"></a></td>
<td><a href="https://youtu.be/VHKyqZ7t8Gw" title="Video By Logan LeGrand"><img src="https://github.com/commaai/openpilot/assets/8762862/92351544-2833-40d7-9e0b-7ef7ae37ec4c"></a></td>
<td><a href="https://youtu.be/SUIZYzxtMQs" title="A drive to Taco Bell"><img src="https://github.com/commaai/openpilot/assets/8762862/05ceefc5-2628-439c-a9b2-89ce77dc6f63"></a></td>
</tr>
</table>
Using openpilot in a car
------
To use openpilot in a car, you need four things:
1. **Supported Device:** a comma 3X, available at [comma.ai/shop](https://comma.ai/shop/comma-3x).
2. **Software:** The setup procedure for the comma 3X allows users to enter a URL for custom software. Use the URL `openpilot.comma.ai` to install the release version.
3. **Supported Car:** Ensure that you have one of [the 275+ supported cars](docs/CARS.md).
4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma 3X to your car.
We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup). Note that it's possible to run openpilot on [other hardware](https://blog.comma.ai/self-driving-car-for-free/), although it's not plug-and-play.
### Branches
| branch | URL | description |
|------------------|----------------------------------------|-------------------------------------------------------------------------------------|
| `release3` | openpilot.comma.ai | This is openpilot's release branch. |
| `release3-staging` | openpilot-test.comma.ai | This is the staging branch for releases. Use it to get new releases slightly early. |
| `nightly` | openpilot-nightly.comma.ai | This is the bleeding edge development branch. Do not expect this to be stable. |
| `nightly-dev` | installer.comma.ai/commaai/nightly-dev | Same as nightly, but includes experimental development features for some cars. |
To start developing openpilot
------
openpilot is developed by [comma](https://comma.ai/) and by users like you. We welcome both pull requests and issues on [GitHub](http://github.com/commaai/openpilot).
* Join the [community Discord](https://discord.comma.ai)
* Check out [the contributing docs](docs/CONTRIBUTING.md)
* Check out the [openpilot tools](tools/)
* Code documentation lives at https://docs.comma.ai
* Information about running openpilot lives on the [community wiki](https://github.com/commaai/openpilot/wiki)
Want to get paid to work on openpilot? [comma is hiring](https://comma.ai/jobs#open-positions) and offers lots of [bounties](https://comma.ai/bounties) for external contributors.
Safety and Testing
----
* openpilot observes [ISO26262](https://en.wikipedia.org/wiki/ISO_26262) guidelines, see [SAFETY.md](docs/SAFETY.md) for more details.
* openpilot has software-in-the-loop [tests](.github/workflows/tests.yaml) that run on every commit.
* The code enforcing the safety model lives in panda and is written in C, see [code rigor](https://github.com/commaai/panda#code-rigor) for more details.
* panda has software-in-the-loop [safety tests](https://github.com/commaai/panda/tree/master/tests/safety).
* Internally, we have a hardware-in-the-loop Jenkins test suite that builds and unit tests the various processes.
* panda has additional hardware-in-the-loop [tests](https://github.com/commaai/panda/blob/master/Jenkinsfile).
* We run the latest openpilot in a testing closet containing 10 comma devices continuously replaying routes.
<details>
<summary>MIT Licensed</summary>
openpilot is released under the MIT license. Some parts of the software are released under other licenses as specified.
Any user of this software shall indemnify and hold harmless Comma.ai, Inc. and its directors, officers, employees, agents, stockholders, affiliates, subcontractors and customers from and against all allegations, claims, actions, suits, demands, damages, liabilities, obligations, losses, settlements, judgments, costs and expenses (including without limitation attorneys fees and costs) which arise out of, relate to or result from any use of this software by user.
**THIS IS ALPHA QUALITY SOFTWARE FOR RESEARCH PURPOSES ONLY. THIS IS NOT A PRODUCT.
YOU ARE RESPONSIBLE FOR COMPLYING WITH LOCAL LAWS AND REGULATIONS.
NO WARRANTY EXPRESSED OR IMPLIED.**
</details>
<details>
<summary>User Data and comma Account</summary>
By default, openpilot uploads the driving data to our servers. You can also access your data through [comma connect](https://connect.comma.ai/). We use your data to train better models and improve openpilot for everyone.
openpilot is open source software: the user is free to disable data collection if they wish to do so.
openpilot logs the road-facing cameras, CAN, GPS, IMU, magnetometer, thermal sensors, crashes, and operating system logs.
The driver-facing camera and microphone are only logged if you explicitly opt-in in settings.
By using openpilot, you agree to [our Privacy Policy](https://comma.ai/privacy). You understand that use of this software or its related services will generate certain types of user data, which may be logged and stored at the sole discretion of comma. By accepting this agreement, you grant an irrevocable, perpetual, worldwide right to comma for the use of this data.
</details>
+19
View File
@@ -1,3 +1,22 @@
Version 0.10.2 (2025-11-19)
========================
* comma four support
Version 0.10.1 (2025-09-08)
========================
* New driving model #36276
* World Model: removed global localization inputs
* World Model: 2x the number of parameters
* World Model: trained on 4x the number of segments
* VAE Compression Model: new architecture and training objective
* Driving Vision Model: trained on 4x the number of segments
* New Driver Monitoring model #36198
* Acura TLX 2021 support thanks to MVL!
* Honda City 2023 support thanks to vanillagorillaa and drFritz!
* Honda N-Box 2018 support thanks to miettal!
* Honda Odyssey 2021-25 support thanks to csouers and MVL!
* Honda Passport 2026 support thanks to vanillagorillaa and MVL!
Version 0.10.0 (2025-08-05)
========================
* New driving model
+95 -233
View File
@@ -3,157 +3,52 @@ import subprocess
import sys
import sysconfig
import platform
import shlex
import numpy as np
import SCons.Errors
SCons.Warnings.warningAsException(True)
# pending upstream fix - https://github.com/SCons/scons/issues/4461
#SetOption('warn', 'all')
TICI = os.path.isfile('/TICI')
AGNOS = TICI
Decider('MD5-timestamp')
SetOption('num_jobs', max(1, int(os.cpu_count()/2)))
AddOption('--kaitai',
action='store_true',
help='Regenerate kaitai struct parsers')
AddOption('--asan',
action='store_true',
help='turn on ASAN')
AddOption('--ubsan',
action='store_true',
help='turn on UBSan')
AddOption('--coverage',
action='store_true',
help='build with test coverage options')
AddOption('--clazy',
action='store_true',
help='build with clazy')
AddOption('--ccflags',
action='store',
type='string',
default='',
help='pass arbitrary flags over the command line')
AddOption('--external-sconscript',
action='store',
metavar='FILE',
dest='external_sconscript',
help='add an external SConscript to the build')
AddOption('--mutation',
action='store_true',
help='generate mutation-ready code')
AddOption('--kaitai', action='store_true', help='Regenerate kaitai struct parsers')
AddOption('--asan', action='store_true', help='turn on ASAN')
AddOption('--ubsan', action='store_true', help='turn on UBSan')
AddOption('--mutation', action='store_true', help='generate mutation-ready code')
AddOption('--ccflags', action='store', type='string', default='', help='pass arbitrary flags over the command line')
AddOption('--minimal',
action='store_false',
dest='extras',
default=os.path.exists(File('#.lfsconfig').abspath), # minimal by default on release branch (where there's no LFS)
default=os.path.exists(File('#.gitattributes').abspath), # minimal by default on release branch (where there's no LFS)
help='the minimum build to run openpilot. no tests, tools, etc.')
## Architecture name breakdown (arch)
## - larch64: linux tici aarch64
## - aarch64: linux pc aarch64
## - x86_64: linux pc x64
## - Darwin: mac x64 or arm64
real_arch = arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
# Detect platform
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
if platform.system() == "Darwin":
arch = "Darwin"
brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip()
elif arch == "aarch64" and AGNOS:
elif arch == "aarch64" and os.path.isfile('/TICI'):
arch = "larch64"
assert arch in ["larch64", "aarch64", "x86_64", "Darwin"]
lenv = {
"PATH": os.environ['PATH'],
"PYTHONPATH": Dir("#").abspath + ':' + Dir(f"#third_party/acados").abspath,
"ACADOS_SOURCE_DIR": Dir("#third_party/acados").abspath,
"ACADOS_PYTHON_INTERFACE_PATH": Dir("#third_party/acados/acados_template").abspath,
"TERA_PATH": Dir("#").abspath + f"/third_party/acados/{arch}/t_renderer"
}
rpath = []
if arch == "larch64":
cpppath = [
"#third_party/opencl/include",
]
libpath = [
"/usr/local/lib",
"/system/vendor/lib64",
f"#third_party/acados/{arch}/lib",
]
libpath += [
"#third_party/libyuv/larch64/lib",
"/usr/lib/aarch64-linux-gnu"
]
cflags = ["-DQCOM2", "-mcpu=cortex-a57"]
cxxflags = ["-DQCOM2", "-mcpu=cortex-a57"]
rpath += ["/usr/local/lib"]
else:
cflags = []
cxxflags = []
cpppath = []
rpath += []
# MacOS
if arch == "Darwin":
libpath = [
f"#third_party/libyuv/{arch}/lib",
f"#third_party/acados/{arch}/lib",
f"{brew_prefix}/lib",
f"{brew_prefix}/opt/openssl@3.0/lib",
"/System/Library/Frameworks/OpenGL.framework/Libraries",
]
cflags += ["-DGL_SILENCE_DEPRECATION"]
cxxflags += ["-DGL_SILENCE_DEPRECATION"]
cpppath += [
f"{brew_prefix}/include",
f"{brew_prefix}/opt/openssl@3.0/include",
]
# Linux
else:
libpath = [
f"#third_party/acados/{arch}/lib",
f"#third_party/libyuv/{arch}/lib",
"/usr/lib",
"/usr/local/lib",
]
if GetOption('asan'):
ccflags = ["-fsanitize=address", "-fno-omit-frame-pointer"]
ldflags = ["-fsanitize=address"]
elif GetOption('ubsan'):
ccflags = ["-fsanitize=undefined"]
ldflags = ["-fsanitize=undefined"]
else:
ccflags = []
ldflags = []
# no --as-needed on mac linker
if arch != "Darwin":
ldflags += ["-Wl,--as-needed", "-Wl,--no-undefined"]
ccflags_option = GetOption('ccflags')
if ccflags_option:
ccflags += ccflags_option.split(' ')
assert arch in [
"larch64", # linux tici arm64
"aarch64", # linux pc arm64
"x86_64", # linux pc x64
"Darwin", # macOS arm64 (x86 not supported)
]
env = Environment(
ENV=lenv,
ENV={
"PATH": os.environ['PATH'],
"PYTHONPATH": Dir("#").abspath + ':' + Dir(f"#third_party/acados").abspath,
"ACADOS_SOURCE_DIR": Dir("#third_party/acados").abspath,
"ACADOS_PYTHON_INTERFACE_PATH": Dir("#third_party/acados/acados_template").abspath,
"TERA_PATH": Dir("#").abspath + f"/third_party/acados/{arch}/t_renderer"
},
CC='clang',
CXX='clang++',
CCFLAGS=[
"-g",
"-fPIC",
@@ -166,36 +61,31 @@ env = Environment(
"-Wno-c99-designator",
"-Wno-reorder-init-list",
"-Wno-vla-cxx-extension",
] + cflags + ccflags,
CPPPATH=cpppath + [
],
CFLAGS=["-std=gnu11"],
CXXFLAGS=["-std=c++1z"],
CPPPATH=[
"#",
"#msgq",
"#third_party",
"#third_party/json11",
"#third_party/linux/include",
"#third_party/acados/include",
"#third_party/acados/include/blasfeo/include",
"#third_party/acados/include/hpipm/include",
"#third_party/catch2/include",
"#third_party/libyuv/include",
"#third_party/json11",
"#third_party/linux/include",
"#third_party",
"#msgq",
],
CC='clang',
CXX='clang++',
LINKFLAGS=ldflags,
RPATH=rpath,
CFLAGS=["-std=gnu11"] + cflags,
CXXFLAGS=["-std=c++1z"] + cxxflags,
LIBPATH=libpath + [
LIBPATH=[
"#common",
"#msgq_repo",
"#third_party",
"#selfdrive/pandad",
"#common",
"#rednose/helpers",
f"#third_party/libyuv/{arch}/lib",
f"#third_party/acados/{arch}/lib",
],
RPATH=[],
CYTHONCFILESUFFIX=".cpp",
COMPILATIONDB_USE_ABSPATH=True,
REDNOSE_ROOT="#",
@@ -203,29 +93,63 @@ env = Environment(
toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
)
if arch == "Darwin":
# RPATH is not supported on macOS, instead use the linker flags
darwin_rpath_link_flags = [f"-Wl,-rpath,{path}" for path in env["RPATH"]]
env["LINKFLAGS"] += darwin_rpath_link_flags
# Arch-specific flags and paths
if arch == "larch64":
env.Append(CPPPATH=["#third_party/opencl/include"])
env.Append(LIBPATH=[
"/usr/local/lib",
"/system/vendor/lib64",
"/usr/lib/aarch64-linux-gnu",
])
arch_flags = ["-D__TICI__", "-mcpu=cortex-a57"]
env.Append(CCFLAGS=arch_flags)
env.Append(CXXFLAGS=arch_flags)
elif arch == "Darwin":
env.Append(LIBPATH=[
f"{brew_prefix}/lib",
f"{brew_prefix}/opt/openssl@3.0/lib",
f"{brew_prefix}/opt/llvm/lib/c++",
"/System/Library/Frameworks/OpenGL.framework/Libraries",
])
env.Append(CCFLAGS=["-DGL_SILENCE_DEPRECATION"])
env.Append(CXXFLAGS=["-DGL_SILENCE_DEPRECATION"])
env.Append(CPPPATH=[
f"{brew_prefix}/include",
f"{brew_prefix}/opt/openssl@3.0/include",
])
else:
env.Append(LIBPATH=[
"/usr/lib",
"/usr/local/lib",
])
env.CompilationDatabase('compile_commands.json')
# Sanitizers and extra CCFLAGS from CLI
if GetOption('asan'):
env.Append(CCFLAGS=["-fsanitize=address", "-fno-omit-frame-pointer"])
env.Append(LINKFLAGS=["-fsanitize=address"])
elif GetOption('ubsan'):
env.Append(CCFLAGS=["-fsanitize=undefined"])
env.Append(LINKFLAGS=["-fsanitize=undefined"])
# Setup cache dir
cache_dir = '/data/scons_cache' if AGNOS else '/tmp/scons_cache'
CacheDir(cache_dir)
Clean(["."], cache_dir)
_extra_cc = shlex.split(GetOption('ccflags') or '')
if _extra_cc:
env.Append(CCFLAGS=_extra_cc)
# no --as-needed on mac linker
if arch != "Darwin":
env.Append(LINKFLAGS=["-Wl,--as-needed", "-Wl,--no-undefined"])
# progress output
node_interval = 5
node_count = 0
def progress_function(node):
global node_count
node_count += node_interval
sys.stderr.write("progress: %d\n" % node_count)
if os.environ.get('SCONS_PROGRESS'):
Progress(progress_function, interval=node_interval)
# Cython build environment
# ********** Cython build environment **********
py_include = sysconfig.get_paths()['include']
envCython = env.Clone()
envCython["CPPPATH"] += [py_include, np.get_include()]
@@ -234,84 +158,27 @@ envCython["CCFLAGS"].remove("-Werror")
envCython["LIBS"] = []
if arch == "Darwin":
envCython["LINKFLAGS"] = ["-bundle", "-undefined", "dynamic_lookup"] + darwin_rpath_link_flags
envCython["LINKFLAGS"] = env["LINKFLAGS"] + ["-bundle", "-undefined", "dynamic_lookup"]
else:
envCython["LINKFLAGS"] = ["-pthread", "-shared"]
np_version = SCons.Script.Value(np.__version__)
Export('envCython', 'np_version')
# Qt build environment
qt_env = env.Clone()
qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "DBus", "Xml"]
Export('env', 'arch')
qt_libs = []
if arch == "Darwin":
qt_env['QTDIR'] = f"{brew_prefix}/opt/qt@5"
qt_dirs = [
os.path.join(qt_env['QTDIR'], "include"),
]
qt_dirs += [f"{qt_env['QTDIR']}/include/Qt{m}" for m in qt_modules]
qt_env["LINKFLAGS"] += ["-F" + os.path.join(qt_env['QTDIR'], "lib")]
qt_env["FRAMEWORKS"] += [f"Qt{m}" for m in qt_modules] + ["OpenGL"]
qt_env.AppendENVPath('PATH', os.path.join(qt_env['QTDIR'], "bin"))
else:
qt_install_prefix = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_PREFIX'], encoding='utf8').strip()
qt_install_headers = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_HEADERS'], encoding='utf8').strip()
# Setup cache dir
cache_dir = '/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache'
CacheDir(cache_dir)
Clean(["."], cache_dir)
qt_env['QTDIR'] = qt_install_prefix
qt_dirs = [
f"{qt_install_headers}",
]
qt_gui_path = os.path.join(qt_install_headers, "QtGui")
qt_gui_dirs = [d for d in os.listdir(qt_gui_path) if os.path.isdir(os.path.join(qt_gui_path, d))]
qt_dirs += [f"{qt_install_headers}/QtGui/{qt_gui_dirs[0]}/QtGui", ] if qt_gui_dirs else []
qt_dirs += [f"{qt_install_headers}/Qt{m}" for m in qt_modules]
qt_libs = [f"Qt5{m}" for m in qt_modules]
if arch == "larch64":
qt_libs += ["GLESv2", "wayland-client"]
qt_env.PrependENVPath('PATH', Dir("#third_party/qt5/larch64/bin/").abspath)
elif arch != "Darwin":
qt_libs += ["GL"]
qt_env['QT3DIR'] = qt_env['QTDIR']
qt_env.Tool('qt3')
qt_env['CPPPATH'] += qt_dirs + ["#third_party/qrcode"]
qt_flags = [
"-D_REENTRANT",
"-DQT_NO_DEBUG",
"-DQT_WIDGETS_LIB",
"-DQT_GUI_LIB",
"-DQT_CORE_LIB",
"-DQT_MESSAGELOGCONTEXT",
]
qt_env['CXXFLAGS'] += qt_flags
qt_env['LIBPATH'] += ['#selfdrive/ui', ]
qt_env['LIBS'] = qt_libs
if GetOption("clazy"):
checks = [
"level0",
"level1",
"no-range-loop",
"no-non-pod-global-static",
]
qt_env['CXX'] = 'clazy'
qt_env['ENV']['CLAZY_IGNORE_DIRS'] = qt_dirs[0]
qt_env['ENV']['CLAZY_CHECKS'] = ','.join(checks)
Export('env', 'qt_env', 'arch', 'real_arch')
# ********** start building stuff **********
# Build common module
SConscript(['common/SConscript'])
Import('_common', '_gpucommon')
Import('_common')
common = [_common, 'json11', 'zmq']
gpucommon = [_gpucommon]
Export('common', 'gpucommon')
Export('common')
# Build messaging (cereal + msgq + socketmaster + their dependencies)
# Enable swaglog include in submodules
@@ -329,6 +196,7 @@ Export('messaging')
# Build other submodules
SConscript(['panda/SConscript'])
SConscript(['panda_tici/SConscript'])
# Build rednose library
SConscript(['rednose/SConscript'])
@@ -338,11 +206,6 @@ SConscript([
'system/ubloxd/SConscript',
'system/loggerd/SConscript',
])
if arch != "Darwin":
SConscript([
'system/logcatd/SConscript',
'system/proclogd/SConscript',
])
if arch == "larch64":
SConscript(['system/camerad/SConscript'])
@@ -357,6 +220,5 @@ if Dir('#tools/cabana/').exists() and GetOption('extras'):
if arch != "larch64":
SConscript(['tools/cabana/SConscript'])
external_sconscript = GetOption('external_sconscript')
if external_sconscript:
SConscript([external_sconscript])
env.CompilationDatabase('compile_commands.json')
+26
View File
@@ -0,0 +1,26 @@
# Sponsors 贊助者
我們誠摯感謝以下贊助者提供的硬體資源,讓專案能夠在多種平台上進行測試與驗證。
We sincerely thank the following sponsors for providing hardware resources, which enable the project to be tested and validated across multiple platforms.
---
## 贊助者列表 Sponsors
| 贊助者 Sponsor | 設備 Deices | 備註 Notes |
| -------------- | ----------- | ---------- |
| BlueGood | <ul><li>Radar Filter x 2</li><li>sDSU x1</li></ul> | - |
| Chia Chun Lee | <ul><li>C3 Quick Mount x1</li></ul> | - |
| CloudJ | <ul><li>C3X x1</li></ul> | - |
| FareWay | <ul><li>EON Quick Mount x1</li><li>C2/C3 Quick Mount x1</li></ul> | Special thanks for fixing my good old EON |
| Fred Wang | <ul><li>Oneplus 3t x1</li></ul> | - |
| Saber Huang | <ul><li>O3L x1</li></ul> | - |
| [馬威 Mr. One](https://shop61532546.taobao.com/) | <ul><li>O3 x 1</li><li>O3 (Dev) x1</li><li>O3XL x1</li><li>Red Panda x1</li><li>Panda Jungle v1 x1</li></ul> | - |
| 門文梁 | <ul><li>C1.5 x1</li></ul> | - |
---
🙏 沒有你們的支持,我們無法讓專案在這麼多硬體平台上持續成長與驗證。
Without your support, this project could not continue to grow and be validated across so many hardware platforms.
+11 -7
View File
@@ -585,7 +585,6 @@ struct PandaState @0xa7649e2575e4591e {
heartbeatLost @22 :Bool;
interruptLoad @25 :Float32;
fanPower @28 :UInt8;
fanStallCount @34 :UInt8;
spiErrorCount @33 :UInt16;
@@ -714,6 +713,7 @@ struct PandaState @0xa7649e2575e4591e {
usbPowerModeDEPRECATED @12 :PeripheralState.UsbPowerModeDEPRECATED;
safetyParamDEPRECATED @20 :Int16;
safetyParam2DEPRECATED @26 :UInt32;
fanStallCountDEPRECATED @34 :UInt8;
}
struct PeripheralState {
@@ -918,6 +918,8 @@ struct ControlsState @0x97ff69c53601abf1 {
saturated @7 :Bool;
actualLateralAccel @9 :Float32;
desiredLateralAccel @10 :Float32;
desiredLateralJerk @11 :Float32;
version @12 :Int32;
}
struct LateralLQRState {
@@ -2146,13 +2148,10 @@ struct Joystick {
struct DriverStateV2 {
frameId @0 :UInt32;
modelExecutionTime @1 :Float32;
dspExecutionTimeDEPRECATED @2 :Float32;
gpuExecutionTime @8 :Float32;
rawPredictions @3 :Data;
poorVisionProb @4 :Float32;
wheelOnRightProb @5 :Float32;
leftDriverData @6 :DriverData;
rightDriverData @7 :DriverData;
@@ -2167,10 +2166,14 @@ struct DriverStateV2 {
leftBlinkProb @7 :Float32;
rightBlinkProb @8 :Float32;
sunglassesProb @9 :Float32;
occludedProb @10 :Float32;
readyProb @11 :List(Float32);
notReadyProb @12 :List(Float32);
phoneProb @13 :Float32;
notReadyProbDEPRECATED @12 :List(Float32);
occludedProbDEPRECATED @10 :Float32;
readyProbDEPRECATED @11 :List(Float32);
}
dspExecutionTimeDEPRECATED @2 :Float32;
poorVisionProbDEPRECATED @4 :Float32;
}
struct DriverStateDEPRECATED @0xb83c6cc593ed0a00 {
@@ -2222,6 +2225,7 @@ struct DriverMonitoringState @0xb83cda094a1da284 {
hiStdCount @14 :UInt32;
isActiveMode @16 :Bool;
isRHD @4 :Bool;
uncertainCount @19 :UInt32;
isPreviewDEPRECATED @15 :Bool;
rhdCheckedDEPRECATED @5 :Bool;
+1 -1
View File
@@ -33,7 +33,7 @@ MessageContext message_context;
struct SubMaster::SubMessage {
std::string name;
SubSocket *socket = nullptr;
int freq = 0;
float freq = 0.0f;
bool updated = false, alive = false, valid = false, ignore_alive;
uint64_t rcv_time = 0, rcv_frame = 0;
void *allocated_msg_reader = nullptr;
+2 -2
View File
@@ -111,12 +111,12 @@ def build_header():
h += "#include <map>\n"
h += "#include <string>\n"
h += "struct service { std::string name; bool should_log; int frequency; int decimation; };\n"
h += "struct service { std::string name; bool should_log; float frequency; int decimation; };\n"
h += "static std::map<std::string, service> services = {\n"
for k, v in SERVICE_LIST.items():
should_log = "true" if v.should_log else "false"
decimation = -1 if v.decimation is None else v.decimation
h += ' { "%s", {"%s", %s, %d, %d}},\n' % \
h += ' { "%s", {"%s", %s, %f, %d}},\n' % \
(k, k, should_log, v.frequency, decimation)
h += "};\n"
+3 -9
View File
@@ -4,18 +4,12 @@ common_libs = [
'params.cc',
'swaglog.cc',
'util.cc',
'watchdog.cc',
'ratekeeper.cc'
]
_common = env.Library('common', common_libs, LIBS="json11")
files = [
'ratekeeper.cc',
'clutil.cc',
]
_gpucommon = env.Library('gpucommon', files)
Export('_common', '_gpucommon')
_common = env.Library('common', common_libs, LIBS="json11")
Export('_common')
if GetOption('extras'):
env.Program('tests/test_common',
+16 -4
View File
@@ -7,11 +7,14 @@ from openpilot.system.version import get_version
API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com')
# name : jwt signature algorithm
KEYS = {"id_rsa" : "RS256",
"id_ecdsa" : "ES256"}
class Api:
def __init__(self, dongle_id):
self.dongle_id = dongle_id
with open(Paths.persist_root()+'/comma/id_rsa') as f:
self.private_key = f.read()
self.jwt_algorithm, self.private_key, _ = get_key_pair()
def get(self, *args, **kwargs):
return self.request('GET', *args, **kwargs)
@@ -22,7 +25,7 @@ class Api:
def request(self, method, endpoint, timeout=None, access_token=None, **params):
return api_get(endpoint, method=method, timeout=timeout, access_token=access_token, **params)
def get_token(self, expiry_hours=1):
def get_token(self, payload_extra=None, expiry_hours=1):
now = datetime.now(UTC).replace(tzinfo=None)
payload = {
'identity': self.dongle_id,
@@ -30,7 +33,9 @@ class Api:
'iat': now,
'exp': now + timedelta(hours=expiry_hours)
}
token = jwt.encode(payload, self.private_key, algorithm='RS256')
if payload_extra is not None:
payload.update(payload_extra)
token = jwt.encode(payload, self.private_key, algorithm=self.jwt_algorithm)
if isinstance(token, bytes):
token = token.decode('utf8')
return token
@@ -44,3 +49,10 @@ def api_get(endpoint, method='GET', timeout=None, access_token=None, **params):
headers['User-Agent'] = "openpilot-" + get_version()
return requests.request(method, API_HOST + "/" + endpoint, timeout=timeout, headers=headers, params=params)
def get_key_pair():
for key in KEYS:
if os.path.isfile(Paths.persist_root() + f'/comma/{key}') and os.path.isfile(Paths.persist_root() + f'/comma/{key}.pub'):
with open(Paths.persist_root() + f'/comma/{key}') as private, open(Paths.persist_root() + f'/comma/{key}.pub') as public:
return KEYS[key], private.read(), public.read()
return None, None, None
-9
View File
@@ -1,9 +0,0 @@
# remove all keys that end in DEPRECATED
def strip_deprecated_keys(d):
for k in list(d.keys()):
if isinstance(k, str):
if k.endswith('DEPRECATED'):
d.pop(k)
elif isinstance(d[k], dict):
strip_deprecated_keys(d[k])
return d
+17
View File
@@ -15,3 +15,20 @@ class FirstOrderFilter:
self.initialized = True
self.x = x
return self.x
class BounceFilter(FirstOrderFilter):
def __init__(self, x0, rc, dt, initialized=True, bounce=2):
self.velocity = FirstOrderFilter(0.0, 0.15, dt)
self.bounce = bounce
super().__init__(x0, rc, dt, initialized)
def update(self, x):
super().update(x)
scale = self.dt / (1.0 / 60.0) # tuned at 60 fps
self.velocity.x += (x - self.x) * self.bounce * scale * self.dt
self.velocity.update(0.0)
if abs(self.velocity.x) < 1e-5:
self.velocity.x = 0.0
self.x += self.velocity.x
return self.x
+1 -1
View File
@@ -1,6 +1,6 @@
from functools import cache
import subprocess
from openpilot.common.run import run_cmd, run_cmd_default
from openpilot.common.utils import run_cmd, run_cmd_default
@cache
+19 -17
View File
@@ -66,7 +66,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}},
{"LanguageSetting", {PERSISTENT, STRING, "main_en"}},
{"LanguageSetting", {PERSISTENT, STRING, "en"}},
{"LastAthenaPingTime", {CLEAR_ON_MANAGER_START, INT}},
{"LastGPSPosition", {PERSISTENT, STRING}},
{"LastManagerExitReason", {CLEAR_ON_MANAGER_START, STRING}},
@@ -94,10 +94,10 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"Offroad_NeosUpdate", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_NoFirmware", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_Recalibration", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"Offroad_StorageMissing", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_TemperatureTooHigh", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_UnregisteredHardware", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_UpdateFailed", {CLEAR_ON_MANAGER_START, JSON}},
{"Offroad_DriverMonitoringUncertain", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
{"OnroadCycleRequested", {CLEAR_ON_MANAGER_START, BOOL}},
{"OpenpilotEnabledToggle", {PERSISTENT, BOOL, "1"}},
{"PandaHeartbeatLost", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
@@ -109,11 +109,10 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"RecordFront", {PERSISTENT, BOOL}},
{"RecordFrontLock", {PERSISTENT, BOOL}}, // for the internal fleet
{"SecOCKey", {PERSISTENT | DONT_LOG, STRING}},
{"ShowDebugInfo", {PERSISTENT, BOOL}},
{"RouteCount", {PERSISTENT, INT, "0"}},
{"SnoozeUpdate", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
{"SshEnabled", {PERSISTENT, BOOL}},
{"TermsVersion", {PERSISTENT, STRING}},
{"TrainingVersion", {PERSISTENT, STRING}},
{"UbloxAvailable", {PERSISTENT, BOOL}},
{"UpdateAvailable", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
{"UpdateFailedCount", {CLEAR_ON_MANAGER_START, INT}},
@@ -129,34 +128,37 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"UptimeOffroad", {PERSISTENT, FLOAT, "0.0"}},
{"UptimeOnroad", {PERSISTENT, FLOAT, "0.0"}},
{"Version", {PERSISTENT, STRING}},
{"dp_device_last_log", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
{"dp_device_reset_conf", {CLEAR_ON_MANAGER_START, BOOL}},
{"dp_device_is_rhd", {PERSISTENT, BOOL, "0"}},
{"dp_device_monitoring_disabled", {PERSISTENT, BOOL, "0"}},
{"dp_device_beep", {PERSISTENT, BOOL, "0"}},
{"dp_dev_last_log", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
{"dp_dev_reset_conf", {CLEAR_ON_MANAGER_START, BOOL, "0"}},
{"dp_dev_is_rhd", {PERSISTENT, BOOL, "0"}},
{"dp_dev_monitoring_disabled", {PERSISTENT, BOOL, "0"}},
{"dp_dev_beep", {PERSISTENT, BOOL, "0"}},
{"dp_lat_alka", {PERSISTENT, BOOL, "0"}},
{"dp_ui_display_mode", {PERSISTENT, BOOL, "0"}},
{"dp_device_model_selected", {PERSISTENT, STRING}},
{"dp_device_model_list", {PERSISTENT, STRING}},
{"dp_ui_display_mode", {PERSISTENT, INT, "0"}},
{"dp_dev_model_selected", {PERSISTENT, STRING}},
{"dp_dev_model_list", {PERSISTENT, STRING}},
{"dp_lat_lca_speed", {PERSISTENT, INT, "20"}},
{"dp_lat_lca_auto_sec", {PERSISTENT, FLOAT, "0.0"}},
{"dp_device_go_off_road", {CLEAR_ON_MANAGER_START, BOOL}},
{"dp_dev_go_off_road", {CLEAR_ON_MANAGER_START, BOOL}},
{"dp_ui_hide_hud_speed_kph", {PERSISTENT, INT, "0"}},
{"dp_lon_ext_radar", {PERSISTENT, BOOL, "0"}},
{"dp_lat_road_edge_detection", {PERSISTENT, BOOL, "0"}},
{"dp_ui_rainbow", {PERSISTENT, BOOL, "0"}},
{"dp_lon_acm", {PERSISTENT, BOOL, "0"}},
{"dp_lon_acm_downhill", {PERSISTENT, BOOL, "0"}},
{"dp_lon_aem", {PERSISTENT, BOOL, "0"}},
{"dp_device_audible_alert_mode", {PERSISTENT, INT, "0"}},
{"dp_device_auto_shutdown_in", {PERSISTENT, INT, "-5"}},
{"dp_ui_radar_tracks", {PERSISTENT, BOOL, "0"}},
{"dp_lon_dtsc", {PERSISTENT, BOOL, "0"}},
{"dp_dev_audible_alert_mode", {PERSISTENT, INT, "0"}},
{"dp_dev_auto_shutdown_in", {PERSISTENT, INT, "-5"}},
{"dp_ui_lead", {PERSISTENT, INT, "0"}},
{"dp_dev_dashy", {PERSISTENT, INT, "0"}},
{"dp_dev_delay_loggerd", {PERSISTENT, INT, "0"}},
{"dp_dev_disable_connect", {PERSISTENT, BOOL, "0"}},
{"dp_dev_tethering", {PERSISTENT, BOOL, "0"}},
{"dp_toyota_door_auto_lock_unlock", {PERSISTENT, BOOL, "0"}},
{"dp_toyota_tss1_sng", {PERSISTENT, BOOL, "0"}},
{"dp_toyota_stock_lon", {PERSISTENT, BOOL, "0"}},
{"dp_vag_a0_sng", {PERSISTENT, BOOL, "0"}},
{"dp_vag_pq_steering_patch", {PERSISTENT, BOOL, "0"}},
{"dp_vag_avoid_eps_lockout", {PERSISTENT, BOOL, "0"}},
{"dp_ui_four", {PERSISTENT, BOOL, "0"}},
};
+18 -25
View File
@@ -2,11 +2,10 @@ import numpy as np
from numbers import Number
class PIDController:
def __init__(self, k_p, k_i, k_f=0., k_d=0., pos_limit=1e308, neg_limit=-1e308, rate=100):
def __init__(self, k_p, k_i, k_d=0., pos_limit=1e308, neg_limit=-1e308, rate=100):
self._k_p = k_p
self._k_i = k_i
self._k_d = k_d
self.k_f = k_f # feedforward gain
if isinstance(self._k_p, Number):
self._k_p = [[0], [self._k_p]]
if isinstance(self._k_i, Number):
@@ -14,11 +13,9 @@ class PIDController:
if isinstance(self._k_d, Number):
self._k_d = [[0], [self._k_d]]
self.pos_limit = pos_limit
self.neg_limit = neg_limit
self.set_limits(pos_limit, neg_limit)
self.i_unwind_rate = 0.3 / rate
self.i_rate = 1.0 / rate
self.i_dt = 1.0 / rate
self.speed = 0.0
self.reset()
@@ -35,10 +32,6 @@ class PIDController:
def k_d(self):
return np.interp(self.speed, self._k_d[0], self._k_d[1])
@property
def error_integral(self):
return self.i/self.k_i
def reset(self):
self.p = 0.0
self.i = 0.0
@@ -46,25 +39,25 @@ class PIDController:
self.f = 0.0
self.control = 0
def update(self, error, error_rate=0.0, speed=0.0, override=False, feedforward=0., freeze_integrator=False):
def set_limits(self, pos_limit, neg_limit):
self.pos_limit = pos_limit
self.neg_limit = neg_limit
def update(self, error, error_rate=0.0, speed=0.0, feedforward=0., freeze_integrator=False):
self.speed = speed
self.p = self.k_p * float(error)
self.d = self.k_d * error_rate
self.f = feedforward
self.p = float(error) * self.k_p
self.f = feedforward * self.k_f
self.d = error_rate * self.k_d
if not freeze_integrator:
i = self.i + self.k_i * self.i_dt * error
if override:
self.i -= self.i_unwind_rate * float(np.sign(self.i))
else:
if not freeze_integrator:
self.i = self.i + error * self.k_i * self.i_rate
# Clip i to prevent exceeding control limits
control_no_i = self.p + self.d + self.f
control_no_i = np.clip(control_no_i, self.neg_limit, self.pos_limit)
self.i = np.clip(self.i, self.neg_limit - control_no_i, self.pos_limit - control_no_i)
# Don't allow windup if already clipping
test_control = self.p + i + self.d + self.f
i_upperbound = self.i if test_control > self.pos_limit else self.pos_limit
i_lowerbound = self.i if test_control < self.neg_limit else self.neg_limit
self.i = np.clip(i, i_lowerbound, i_upperbound)
control = self.p + self.i + self.d + self.f
self.control = np.clip(control, self.neg_limit, self.pos_limit)
return self.control
-30
View File
@@ -1,30 +0,0 @@
import time
import functools
from openpilot.common.swaglog import cloudlog
def retry(attempts=3, delay=1.0, ignore_failure=False):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(attempts):
try:
return func(*args, **kwargs)
except Exception:
cloudlog.exception(f"{func.__name__} failed, trying again")
time.sleep(delay)
if ignore_failure:
cloudlog.error(f"{func.__name__} failed after retry")
else:
raise Exception(f"{func.__name__} failed after retry")
return wrapper
return decorator
if __name__ == "__main__":
@retry(attempts=10)
def abc():
raise ValueError("abc failed :(")
abc()
-28
View File
@@ -1,28 +0,0 @@
import subprocess
from contextlib import contextmanager
from subprocess import Popen, PIPE, TimeoutExpired
def run_cmd(cmd: list[str], cwd=None, env=None) -> str:
return subprocess.check_output(cmd, encoding='utf8', cwd=cwd, env=env).strip()
def run_cmd_default(cmd: list[str], default: str = "", cwd=None, env=None) -> str:
try:
return run_cmd(cmd, cwd=cwd, env=env)
except subprocess.CalledProcessError:
return default
@contextmanager
def managed_proc(cmd: list[str], env: dict[str, str]):
proc = Popen(cmd, env=env, stdout=PIPE, stderr=PIPE)
try:
yield proc
finally:
if proc.poll() is None:
proc.terminate()
try:
proc.wait(timeout=5)
except TimeoutExpired:
proc.kill()
+1 -1
View File
@@ -1,7 +1,7 @@
import os
from uuid import uuid4
from openpilot.common.file_helpers import atomic_write_in_dir
from openpilot.common.utils import atomic_write_in_dir
class TestFileHelpers:
+62 -2
View File
@@ -2,9 +2,14 @@ import io
import os
import tempfile
import contextlib
import subprocess
import time
import functools
from subprocess import Popen, PIPE, TimeoutExpired
import zstandard as zstd
from openpilot.common.swaglog import cloudlog
LOG_COMPRESSION_LEVEL = 10 # little benefit up to level 15. level ~17 is a small step change
LOG_COMPRESSION_LEVEL = 10 # little benefit up to level 15. level ~17 is a small step change
class CallbackReader:
@@ -27,7 +32,7 @@ class CallbackReader:
@contextlib.contextmanager
def atomic_write_in_dir(path: str, mode: str = 'w', buffering: int = -1, encoding: str = None, newline: str = None,
def atomic_write_in_dir(path: str, mode: str = 'w', buffering: int = -1, encoding: str | None = None, newline: str | None = None,
overwrite: bool = False):
"""Write to a file atomically using a temporary file in the same directory as the destination file."""
dir_name = os.path.dirname(path)
@@ -56,3 +61,58 @@ def get_upload_stream(filepath: str, should_compress: bool) -> tuple[io.Buffered
compressed_size = compressed_stream.tell()
compressed_stream.seek(0)
return compressed_stream, compressed_size
# remove all keys that end in DEPRECATED
def strip_deprecated_keys(d):
for k in list(d.keys()):
if isinstance(k, str):
if k.endswith('DEPRECATED'):
d.pop(k)
elif isinstance(d[k], dict):
strip_deprecated_keys(d[k])
return d
def run_cmd(cmd: list[str], cwd=None, env=None) -> str:
return subprocess.check_output(cmd, encoding='utf8', cwd=cwd, env=env).strip()
def run_cmd_default(cmd: list[str], default: str = "", cwd=None, env=None) -> str:
try:
return run_cmd(cmd, cwd=cwd, env=env)
except subprocess.CalledProcessError:
return default
@contextlib.contextmanager
def managed_proc(cmd: list[str], env: dict[str, str]):
proc = Popen(cmd, env=env, stdout=PIPE, stderr=PIPE)
try:
yield proc
finally:
if proc.poll() is None:
proc.terminate()
try:
proc.wait(timeout=5)
except TimeoutExpired:
proc.kill()
def retry(attempts=3, delay=1.0, ignore_failure=False):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(attempts):
try:
return func(*args, **kwargs)
except Exception:
cloudlog.exception(f"{func.__name__} failed, trying again")
time.sleep(delay)
if ignore_failure:
cloudlog.error(f"{func.__name__} failed after retry")
else:
raise Exception(f"{func.__name__} failed after retry")
return wrapper
return decorator
+1 -1
View File
@@ -1 +1 @@
#define COMMA_VERSION "0.10.0"
#define COMMA_VERSION "0.10.2"
-12
View File
@@ -1,12 +0,0 @@
#include <string>
#include "common/watchdog.h"
#include "common/util.h"
#include "system/hardware/hw.h"
const std::string watchdog_fn_prefix = Path::shm_path() + "/wd_"; // + <pid>
bool watchdog_kick(uint64_t ts) {
static std::string fn = watchdog_fn_prefix + std::to_string(getpid());
return util::write_file(fn.c_str(), &ts, sizeof(ts), O_WRONLY | O_CREAT) > 0;
}
-5
View File
@@ -1,5 +0,0 @@
#pragma once
#include <cstdint>
bool watchdog_kick(uint64_t ts);
-22
View File
@@ -1,22 +0,0 @@
import os
import time
import struct
from openpilot.system.hardware.hw import Paths
WATCHDOG_FN = f"{Paths.shm_path()}/wd_"
_LAST_KICK = 0.0
def kick_watchdog():
global _LAST_KICK
current_time = time.monotonic()
if current_time - _LAST_KICK < 1.0:
return
try:
with open(f"{WATCHDOG_FN}{os.getpid()}", 'wb') as f:
f.write(struct.pack('<Q', int(current_time * 1e9)))
f.flush()
_LAST_KICK = current_time
except OSError:
pass
+13 -7
View File
@@ -4,7 +4,7 @@
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
# 319 Supported Cars
# 325 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|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@@ -13,6 +13,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Acura|MDX 2025|All except Type S|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2025">Buy Here</a></sub></details>|||
|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2016-18">Buy Here</a></sub></details>|||
|Acura|RDX 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2019-21">Buy Here</a></sub></details>|||
|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 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura TLX 2021">Buy Here</a></sub></details>|||
|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 2014-19">Buy Here</a></sub></details>|||
|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 Sportback e-tron 2017-18">Buy Here</a></sub></details>|||
|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Audi Q2 2018">Buy Here</a></sub></details>|||
@@ -76,12 +77,14 @@ A supported vehicle is one that just works when you install a comma device. All
|Honda|Accord 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2023-25">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2018-22">Buy Here</a></sub></details>|||
|Honda|Accord Hybrid 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2023-25">Buy Here</a></sub></details>|||
|Honda|City (Brazil only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda City (Brazil only) 2023">Buy Here</a></sub></details>|||
|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2016-18">Buy Here</a></sub></details>|<a href="https://youtu.be/-IkImTe1NYE" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|2 mph[<sup>5</sup>](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=4Iz1Mz5LGF8" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2017-21">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2017-18">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2019-21">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic Hatchback Hybrid 2025|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid 2025">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback Hybrid 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 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid 2025-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 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid (Europe only) 2023">Buy Here</a></sub></details>|||
|Honda|Civic Hybrid 2025|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hybrid 2025">Buy Here</a></sub></details>|||
|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2015-16">Buy Here</a></sub></details>|||
@@ -96,8 +99,11 @@ A supported vehicle is one that just works when you install a comma device. All
|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 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda 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 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda 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 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda 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 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda 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 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2018-20">Buy Here</a></sub></details>|||
|Honda|Odyssey 2021-25|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 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2021-25">Buy Here</a></sub></details>|||
|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Passport 2019-25">Buy Here</a></sub></details>|||
|Honda|Passport 2026|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda 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 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2016-22">Buy Here</a></sub></details>|||
|Honda|Pilot 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2023-25">Buy Here</a></sub></details>|||
|Honda|Ridgeline 2017-25|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Ridgeline 2017-25">Buy Here</a></sub></details>|||
@@ -242,10 +248,10 @@ A supported vehicle is one that just works when you install a comma device. All
|Škoda|Octavia Scout 2017-19[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Octavia Scout 2017-19">Buy Here</a></sub></details>|||
|Škoda|Scala 2020-23[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Scala 2020-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|Škoda|Superb 2015-22[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Superb 2015-22">Buy Here</a></sub></details>|||
|Tesla[<sup>11</sup>](#footnotes)|Model 3 (with HW3) 2019-23[<sup>10</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 Tesla A connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW3) 2019-23">Buy Here</a></sub></details>|||
|Tesla[<sup>11</sup>](#footnotes)|Model 3 (with HW4) 2024-25[<sup>10</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 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW4) 2024-25">Buy Here</a></sub></details>|||
|Tesla[<sup>11</sup>](#footnotes)|Model Y (with HW3) 2020-23[<sup>10</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 Tesla A connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW3) 2020-23">Buy Here</a></sub></details>|||
|Tesla[<sup>11</sup>](#footnotes)|Model Y (with HW4) 2024[<sup>10</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 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW4) 2024">Buy Here</a></sub></details>|||
|Tesla[<sup>11</sup>](#footnotes)|Model 3 (with HW3) 2019-23[<sup>10</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 Tesla A connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW3) 2019-23">Buy Here</a></sub></details>|||
|Tesla[<sup>11</sup>](#footnotes)|Model 3 (with HW4) 2024-25[<sup>10</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 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model 3 (with HW4) 2024-25">Buy Here</a></sub></details>|||
|Tesla[<sup>11</sup>](#footnotes)|Model Y (with HW3) 2020-23[<sup>10</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 Tesla A connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Tesla Model Y (with HW3) 2020-23">Buy Here</a></sub></details>|||
|Tesla[<sup>11</sup>](#footnotes)|Model Y (with HW4) 2024-25[<sup>10</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 Tesla B connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=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 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota 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 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Alphard Hybrid 2021">Buy Here</a></sub></details>|||
|Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Avalon 2016">Buy Here</a></sub></details>|||
+1 -1
View File
@@ -39,7 +39,7 @@ All of these are examples of good PRs:
### First contribution
[Projects / openpilot bounties](https://github.com/orgs/commaai/projects/26/views/1?pane=info) is the best place to get started and goes in-depth on what's expected when working on a bounty.
There's lot of bounties that don't require a comma 3/3X or a car.
There's lot of bounties that don't require a comma 3X or a car.
## Pull Requests
+30
View File
@@ -0,0 +1,30 @@
# Debugging Panda Safety with Replay Drive + LLDB
## 1. Start the debugger in VS Code
* Select **Replay drive + Safety LLDB**.
* Enter the route or segment when prompted.
[<img src="https://github.com/user-attachments/assets/b0cc320a-083e-46a7-a9f8-ca775bbe5604">](https://github.com/user-attachments/assets/b0cc320a-083e-46a7-a9f8-ca775bbe5604)
## 2. Attach LLDB
* When prompted, pick the running **`replay_drive` process**.
* ⚠️ Attach quickly, or `replay_drive` will start consuming messages.
> [!TIP]
> Add a Python breakpoint at the start of `replay_drive.py` to pause execution and give yourself time to attach LLDB.
## 3. Set breakpoints in VS Code
Breakpoints can be set directly in `modes/xxx.h` (or any C file).
No extra LLDB commands are required — just place breakpoints in the editor.
## 4. Resume execution
Once attached, you can step through both Python (on the replay) and C safety code as CAN logs are replayed.
> [!NOTE]
> * Use short routes for quicker iteration.
> * Pause `replay_drive` early to avoid wasting log messages.
## Video
View a demo of this workflow on the PR that added it: https://github.com/commaai/openpilot/pull/36055#issue-3352911578
+14 -4
View File
@@ -16,7 +16,7 @@ industry standards of safety for Level 2 Driver Assistance Systems. In particula
ISO26262 guidelines, including those from [pertinent documents](https://www.nhtsa.gov/sites/nhtsa.dot.gov/files/documents/13498a_812_573_alcsystemreport.pdf)
released by NHTSA. In addition, we impose strict coding guidelines (like [MISRA C : 2012](https://www.misra.org.uk/what-is-misra/))
on parts of openpilot that are safety relevant. We also perform software-in-the-loop,
hardware-in-the-loop and in-vehicle tests before each software release.
hardware-in-the-loop, and in-vehicle tests before each software release.
Following Hazard and Risk Analysis and FMEA, at a very high level, we have designed openpilot
ensuring two main safety requirements.
@@ -29,8 +29,18 @@ ensuring two main safety requirements.
For additional safety implementation details, refer to [panda safety model](https://github.com/commaai/panda#safety-model). For vehicle specific implementation of the safety concept, refer to [opendbc/safety/safety](https://github.com/commaai/opendbc/tree/master/opendbc/safety/safety).
**Extra note**: comma.ai strongly discourages the use of openpilot forks with safety code either missing or
not fully meeting the above requirements.
[^1]: For these actuator limits we observe ISO11270 and ISO15622. Lateral limits described there translate to 0.9 seconds of maximum actuation to achieve a 1m lateral deviation.
[^1]: For these actuator limits we observe ISO11270 and ISO15622. Lateral limits described there translate to 0.9 seconds of maximum actuation to achieve a 1m lateral deviation.
---
### Forks of openpilot
* Do not disable or nerf [driver monitoring](https://github.com/commaai/openpilot/tree/master/selfdrive/monitoring)
* Do not disable or nerf [excessive actuation checks](https://github.com/commaai/openpilot/tree/master/selfdrive/selfdrived/helpers.py)
* If your fork modifies any of the code in `opendbc/safety/`:
* your fork cannot use the openpilot trademark
* your fork must preserve the full [safety test suite](https://github.com/commaai/opendbc/tree/master/opendbc/safety/tests) and all tests must pass, including any new coverage required by the fork's changes
Failure to comply with these standards will get you and your users banned from comma.ai servers.
**comma.ai strongly discourages the use of openpilot forks with safety code either missing or not fully meeting the above requirements.**
+65
View File
@@ -0,0 +1,65 @@
# CarState signals
## Required for basic lateral control
* `brakePressed`
* `cruiseState`
* `doorOpen`
* `espDisabled`
* `gasPressed`
* `gearShifter`
* `leftBlinker` / `rightBlinker`
* `seatbeltUnlatched`
* `standstill`
* `steeringAngleDeg`
* `steeringPressed`
* `steeringTorque`
* `steerFaultPermanent`
* `steerFaultTemporary`
* `vCruise`
* `wheelSpeeds.[fl|fr|rl|rr]`: Speed of each of the car's four wheels, in m/s. The car's CAN bus often broadcasts the
speed in kph, so the helper function `parse_wheel_speeds` performs this conversion by default.
## Recommended / Required for openpilot longitudinal control
* `accFaulted`
* `espActive`
* `parkingBrake`
## Application Dependent
* `blockPcmEnable`
* `buttonEnable`
* `brakeHoldActive`
* `carFaultedNonCritical`
* `invalidLkasSetting`
* `lowSpeedAlert`
* `regenBraking`
* `steeringAngleOffsetDeg`
* `steeringDisengage`
* `steeringTorqueEps`
* `stockLkas`
* `vCruiseCluster`
* `vEgoCluster`
* `vehicleSensorsInvalid`
## Automatically populated
* `buttonEvents`
These values are populated automatically by `parse_wheel_speeds`:
* `aEgo`: Acceleration of the ego vehicle, Kalman filtered derivative of `vEgo`.
* `vEgo`: Speed of the ego vehicle, Kalman filtered from `vEgoRaw`.
* `vEgoRaw`: Speed of the ego vehicle, based on the average of all four wheel speeds, unfiltered.
## Optional
* `brake`
* `charging`
* `fuelGauge`
* `leftBlindspot` / `rightBlindspot`
* `steeringRateDeg`
* `stockAeb`
* `stockFcw`
* `yawRate`
+85
View File
@@ -0,0 +1,85 @@
# Stimulus-Response Tests
These are example test drives that can help identify the CAN bus messaging necessary for ADAS control. Each scripted
test should be done in a separate route (ignition cycle). These tests are a guide, not necessarily exhaustive.
While testing, constant power to the comma device is highly recommended, using [comma power](https://comma.ai/shop/comma-power) if
necessary to make sure all test activity is fully captured and for ease of uploading. If constant power isn't
available, keep the ignition on for at least one minute after your test to make sure power loss doesn't result
in loss of the last minute of testing data.
## Stationary ignition-only tests, part 1
1. Ignition on, but don't start engine, remain in Park
2. Open and close each door in a defined order: driver, passenger, rear left, rear right
3. Re-enter the vehicle, close the driver's door, and fasten the driver's seatbelt
4. Slowly press and release the accelerator pedal 3 times
5. Slowly press and release the brake pedal 3 times
6. Hold the brake and move the gearshift to reverse, then neutral, then drive, then sport/eco/etc if applicable
7. Return to Park, ignition off
Brake-pressed information may show up in several messages and signals, both as on/off states and as a percentage or
pressure. It may reflect a switch on the driver's brake pedal, or a pressure-threshold state, or signals to turn on
the rear brake lights. Start by identifying all the potential signals, and confirm while driving with ACC later.
Locate signals for all four door states if possible, but some cars only expose the driver's door state on the ADAS bus.
Driver/passenger door signals may or may not change positions for LHD vs RHD cars. For cars where only the driver's
door signal is available, the same signal may follow the driver.
## Stationary ignition-only tests, part 2
1. Ignition on, but don't start engine, remain in Park
2. Press each ACC button in a defined order: main switch on/off, set, resume, cancel, accel, decel, gap adjust
3. Set the left turn signal for about five seconds
4. Operate the left turn signal one time in its touch-to-pass mode
5. Set the right turn signal for about five seconds
6. Operate the right turn signal one time in its touch-to-pass mode
7. Set the hazard / emergency indicator switch for about five seconds
8. Ignition off
Your vehicle may have a momentary-press main ACC switch or a physical toggle that remains set. Actual ACC engagement
isn't necessary for purposes of detecting the ACC button presses.
## Steering angle and steering torque tests
Power steering should be available. On ICE cars, engine RPM may be present.
1. Ignition on, start engine if applicable, remain in Park
2. Rotate the steering wheel as follows, with a few seconds pause between each step
* Start as close to exact center as possible
* Turn to 45 degrees right and hold
* Turn to 90 degrees right and hold
* Turn to 180 degrees right and hold
* Turn to full lock right and hold, with firm pressure against lock
* Release the wheel and allow it to bounce back slightly from lock
* Turn to 180 degrees left and hold
* Return to center and release
3. Ignition off
Performing the full test to the right, followed by an abbreviated test to the left, helps give additional confirmation
of signal scale, and sign/direction for both the steering wheel angle and driver input torque signals.
## Low speed / parking lot driving tests
Before this test, drive to a place like an empty parking lot where you are free to drive in a series of curves.
1. Ignition on, start engine if applicable, prepare to drive
2. Slowly (10-20mph at most) drive a figure-8 if possible, or at least one sharp left and one sharp right.
3. Come to a complete stop
4. When and where safe, drive in reverse for a short distance (10-15 feet)
5. Park the car in a safe place, ignition off
## High speed / highway driving tests
Select a place and time where you can safely set cruise control at normal travel speeds with little interference from
traffic ahead, and safely test the response of your factory lane guidance system.
1. Ignition on, start engine if applicable, prepare to drive
2. When safely able, engage adaptive cruise control below 50 mph
3. When safely able, use the ACC buttons to accelerate to 50mph, then 55mph, then 60mph
4. Disengage adaptive cruise
5. When safely able, allow your factory lane guidance to prevent lane departures, 2-3 times on both the left and right
The series of setpoints can be adjusted to local traffic regulations, and of course metric units. The specific cruise
setpoints are useful for locating the ACC HUD signals later, and confirming their precise scaling. When the car reaches
and holds the setpoint, that can also provide additional confirmation of wheel speed scaling.
+4 -4
View File
@@ -1,11 +1,11 @@
# connect to a comma 3/3X
# connect to a comma 3X
A comma 3/3X is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console).
A comma 3X is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console).
## Serial Console
On both the comma three and 3X, the serial console is accessible from the main OBD-C port.
Connect the comma 3/3X to your computer with a normal USB C cable, or use a [comma serial](https://comma.ai/shop/comma-serial) for steady 12V power.
Connect the comma 3X to your computer with a normal USB C cable, or use a [comma serial](https://comma.ai/shop/comma-serial) for steady 12V power.
On the comma three, the serial console is exposed through a UART-to-USB chip, and `tools/scripts/serial.sh` can be used to connect.
@@ -45,7 +45,7 @@ In order to use ADB on your device, you'll need to perform the following steps u
* Here's an example command for connecting to your device using its tethered connection: `adb connect 192.168.43.1:5555`
> [!NOTE]
> The default port for ADB is 5555 on the comma 3/3X.
> The default port for ADB is 5555 on the comma 3X.
For more info on ADB, see the [Android Debug Bridge (ADB) documentation](https://developer.android.com/tools/adb).
+1 -1
View File
@@ -8,7 +8,7 @@ Replaying is a critical tool for openpilot development and debugging.
Just run `tools/replay/replay --demo`.
## Replaying CAN data
*Hardware required: jungle and comma 3/3X*
*Hardware required: jungle and comma 3X*
1. Connect your PC to a jungle.
2.
+1 -1
View File
@@ -3,7 +3,7 @@
In 30 minutes, we'll get an openpilot development environment set up on your computer and make some changes to openpilot's UI.
And if you have a comma 3/3X, we'll deploy the change to your device for testing.
And if you have a comma 3X, we'll deploy the change to your device for testing.
## 1. Set up your development environment
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

+115 -39
View File
@@ -4,82 +4,158 @@ Copyright (c) 2025, Rick Lan
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, and/or sublicense,
to use, copy, modify, merge, publish, distribute, and/or sublicense,
for non-commercial purposes only, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
- Commercial use (e.g. use in a product, service, or activity intended to
generate revenue) is prohibited without explicit written permission from
- Commercial use (e.g. use in a product, service, or activity intended to
generate revenue) is prohibited without explicit written permission from
the copyright holder.
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.
"""
import time
import numpy as np
from openpilot.common.swaglog import cloudlog
SLOPE = -0.04
RATIO = 0.9
# Configuration parameters
SPEED_RATIO = 0.98 # Must be within 2% over cruise speed
TTC_THRESHOLD = 3.0 # seconds - disable ACM when lead is within this time
# Emergency thresholds - IMMEDIATELY disable ACM
EMERGENCY_TTC = 2.0 # seconds - emergency situation
EMERGENCY_RELATIVE_SPEED = 10.0 # m/s (~36 km/h closing speed - only for rapid closing)
EMERGENCY_DECEL_THRESHOLD = -1.5 # m/s² - if MPC wants this much braking, emergency disable
# Safety cooldown after lead detection
LEAD_COOLDOWN_TIME = 0.5 # seconds - brief cooldown to handle sensor glitches
# Speed-based distance scaling - more practical for real traffic
SPEED_BP = [0., 10., 20., 30.] # m/s (0, 36, 72, 108 km/h)
MIN_DIST_V = [15., 20., 25., 30.] # meters - closer to original 25m baseline
TTC = 3.5
TTC_BP = [TTC, 2.5]
MIN_BRAKE_ALLOW_VALS = [0., -0.5]
class ACM:
def __init__(self):
self.enabled = False
self.downhill_only = False
self._is_downhill = False
self._is_speed_over_cruise = False
self._has_lead = False
self._active_prev = False
self._last_lead_time = 0.0 # Track when we last saw a lead
self.active = False
self.just_disabled = False
self.allowed_brake_val = 0.
def update_states(self, cs, rs, user_ctrl_lon, v_ego, v_cruise):
self.lead_ttc = float('inf') # Default if no lead
def _check_emergency_conditions(self, lead, v_ego, current_time):
"""Check for emergency conditions that require immediate ACM disable."""
if not lead or not lead.status:
return False
if not self.enabled:
self.active = False
return
self.lead_ttc = lead.dRel / max(v_ego, 0.1)
relative_speed = v_ego - lead.vLead # Positive = closing
if len(cs.orientationNED) != 3:
self.active = False
return
# Speed-adaptive minimum distance
min_dist_for_speed = np.interp(v_ego, SPEED_BP, MIN_DIST_V)
pitch_rad = cs.orientationNED[1]
self._is_downhill = np.sin(pitch_rad) < SLOPE
self._is_speed_over_cruise = v_ego > (v_cruise * RATIO)
# Emergency disable conditions - only for truly dangerous situations
# Require BOTH close distance AND (fast closing OR very short TTC)
if lead.dRel < min_dist_for_speed and (
self.lead_ttc < EMERGENCY_TTC or
relative_speed > EMERGENCY_RELATIVE_SPEED):
lead = rs.leadOne
self._last_lead_time = current_time
if self.active: # Only log if we're actually disabling
cloudlog.warning(f"ACM emergency disable: dRel={lead.dRel:.1f}m, TTC={self.lead_ttc:.1f}s, relSpeed={relative_speed:.1f}m/s")
return True
return False
def _update_lead_status(self, lead, v_ego, current_time):
"""Update lead vehicle detection status."""
if lead and lead.status:
self.lead_ttc = lead.dRel / v_ego if v_ego > 0 else float('inf')
self._has_lead = self.lead_ttc < TTC
self.lead_ttc = lead.dRel / max(v_ego, 0.1)
if self.lead_ttc < TTC_THRESHOLD:
self._has_lead = True
self._last_lead_time = current_time
else:
self._has_lead = False
else:
self._has_lead = False
self.lead_ttc = float('inf')
self.active = not user_ctrl_lon and not self._has_lead and self._is_speed_over_cruise and (self._is_downhill if self.downhill_only else True)
def _check_cooldown(self, current_time):
"""Check if we're still in cooldown period after lead detection."""
time_since_lead = current_time - self._last_lead_time
return time_since_lead < LEAD_COOLDOWN_TIME
def _should_activate(self, user_ctrl_lon, v_ego, v_cruise, in_cooldown):
"""Determine if ACM should be active based on all conditions."""
self._is_speed_over_cruise = v_ego > (v_cruise * SPEED_RATIO)
return (not user_ctrl_lon and
not self._has_lead and
not in_cooldown and
self._is_speed_over_cruise)
def update_states(self, cc, rs, user_ctrl_lon, v_ego, v_cruise):
"""Update ACM state with multiple safety checks."""
# Basic validation
if not self.enabled or len(cc.orientationNED) != 3:
self.active = False
return
current_time = time.monotonic()
lead = rs.leadOne
# Check emergency conditions first (highest priority)
if self._check_emergency_conditions(lead, v_ego, current_time):
self.active = False
self._active_prev = self.active
return
# Update normal lead status
self._update_lead_status(lead, v_ego, current_time)
# Check cooldown period
in_cooldown = self._check_cooldown(current_time)
# Determine if ACM should be active
self.active = self._should_activate(user_ctrl_lon, v_ego, v_cruise, in_cooldown)
# Track state changes for logging
self.just_disabled = self._active_prev and not self.active
if self.active and not self._active_prev:
cloudlog.info(f"ACM activated: v_ego={v_ego*3.6:.1f} km/h, v_cruise={v_cruise*3.6:.1f} km/h")
elif self.just_disabled:
cloudlog.info("ACM deactivated")
self._active_prev = self.active
def update_a_desired_trajectory(self, a_desired_trajectory):
"""
Modify acceleration trajectory to allow coasting.
SAFETY: Check for any strong braking request and abort.
"""
if not self.active:
return a_desired_trajectory
# Suppress all braking to allow smooth coasting
for i in range(len(a_desired_trajectory)):
if a_desired_trajectory[i] < 0 and a_desired_trajectory[i] > self.allowed_brake_val:
a_desired_trajectory[i] = 0.0
return a_desired_trajectory
# SAFETY CHECK: If MPC wants significant braking, DON'T suppress it
min_accel = np.min(a_desired_trajectory)
if min_accel < EMERGENCY_DECEL_THRESHOLD:
cloudlog.warning(f"ACM aborting: MPC requested {min_accel:.2f} m/s² braking")
self.active = False # Immediately deactivate
return a_desired_trajectory # Return unmodified trajectory
def update_output_a_target(self, output_a_target):
if not self.active:
return output_a_target
# Only suppress very mild braking (> -1.0 m/s²)
# This allows coasting but preserves any meaningful braking
modified_trajectory = np.copy(a_desired_trajectory)
for i in range(len(modified_trajectory)):
if -1.0 < modified_trajectory[i] < 0:
# Only suppress very gentle braking for cruise control
modified_trajectory[i] = 0.0
# Any braking stronger than -1.0 m/s² is preserved!
# Suppress braking
if output_a_target < 0 and output_a_target > self.allowed_brake_val:
output_a_target = 0.0
return output_a_target
return modified_trajectory
+41 -360
View File
@@ -16,374 +16,55 @@ for non-commercial purposes only, subject to the following conditions:
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.
"""
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.realtime import DT_MDL
import time
import numpy as np
from cereal import log
from openpilot.selfdrive.modeld.constants import ModelConstants
AEM_COOLDOWN_TIME = 0.5 # seconds
SLOW_DOWN_BP = [0., 2.78, 5.56, 8.34, 11.12, 13.89, 15.28]
SLOW_DOWN_DIST = [10, 30., 50., 70., 80., 90., 120.]
# Allow throttle
# ALLOW_THROTTLE_THRESHOLD = 0.4
# MIN_ALLOW_THROTTLE_SPEED = 2.5
class AEM:
# --- Configuration Constants ---
# Speed thresholds (m/s)
SPEED_THRESHOLD_HIGHWAY = 22.23 # m/s (approx. 80 kph)
SPEED_THRESHOLD_CITY = 15.27 # m/s (approx. 55 kph)
SPEED_THRESHOLD_LOW = 5.56 # m/s (approx. 20 kph)
SPEED_THRESHOLD_CREEP = 2.23 # m/s (approx. 8 kph)
# Lead related thresholds
LEAD_TTC_CRITICAL = 1.75 # seconds, time to collision
LEAD_TTC_CAUTION = 3.0
LEAD_DIST_VERY_CLOSE = 10.0 # meters
LEAD_DIST_FAR_HIGHWAY = 85.0 # meters, for considering lead far enough on highway
LEAD_DIST_DEFAULT_NO_LEAD = 150.0 # Default distance for EMA when no lead
def __init__(self):
self._active = False
self._cooldown_end_time = 0.0
LEAD_ACCEL_HARD_BRAKE = -3.0 # m/s^2
LEAD_ACCEL_MILD_BRAKE = -2.0 # m/s^2
LEAD_ACCEL_PULLING_AWAY = 0.5 # m/s^2
def _perform_experimental_mode(self):
self._active = True
self._cooldown_end_time = time.monotonic() + AEM_COOLDOWN_TIME
# Steering thresholds
STEERING_ANGLE_ABS_HIGH_CURVATURE = 45.0 # degrees (EMA value)
def get_mode(self, mode):
# override mode
if time.monotonic() < self._cooldown_end_time:
mode = 'blended'
else:
self._active = False
return mode
# Hysteresis & Timers
HYSTERESIS_FRAMES_TO_SWITCH = 10 # Approx 0.5s at 20Hz
LEAD_LOST_FRAMES_TO_FALLBACK_BASE = 40 # Approx 2s at 20Hz
def update_states(self, model_msg, radar_msg, v_ego):
# EMA filter time constants (seconds) - THESE ARE DESIGN PARAMETERS
EMA_TC_V_EGO = 1.0
EMA_TC_LEAD_DREL = 0.5
EMA_TC_LEAD_V_ABS = 0.5 # Filters absolute lead speed
EMA_TC_LEAD_ALEAD = 0.5
EMA_TC_STEERING_ANGLE_ABS = 0.8
EMA_TC_V_MODEL_ERROR = 1.0
# Stop sign/light detection
if not self._active:
if len(model_msg.orientation.x) == len(model_msg.position.x) == ModelConstants.IDX_N and \
model_msg.position.x[ModelConstants.IDX_N - 1] < np.interp(v_ego, SLOW_DOWN_BP, SLOW_DOWN_DIST):
self._perform_experimental_mode()
# Model & Planner Related Thresholds
MODEL_VEL_ERROR_THRESHOLD = 2.0 # m/s (EMA value)
MIN_VISION_LEAD_PROB_ACTION = 0.5 # Min modelProb for acting on vision-only leads
# throttle prob is low and there is no lead ahead (prep for brake?)
# if not self._active:
# if len(model_msg.meta.disengagePredictions.gasPressProbs) > 1:
# throttle_prob = model_msg.meta.disengagePredictions.gasPressProbs[1]
# else:
# throttle_prob = 1.0
# Other
REL_SPEED_SIGNIFICANT_DIFFERENCE = 2.5 # m/s
# allow_throttle = throttle_prob > ALLOW_THROTTLE_THRESHOLD or v_ego <= MIN_ALLOW_THROTTLE_SPEED
# if not allow_throttle and not radar_msg.leadOne.status:
# self._perform_experimental_mode()
def __init__(self, debug=False):
self.enabled = False
self._current_mode = 'acc' # Default to ACC
self._target_mode_suggestion = None
self._mode_switch_counter = 0
self.debug = debug
self._lead_id_prev = -1
self._lead_absence_frames = 0
self.personality = log.LongitudinalPersonality.standard
# --- Calculate alpha values from time constants (tau) ---
def get_alpha(tau, dt):
"""Calculates EMA alpha from time constant and time step."""
# Ensure tau > 0 to avoid division by zero if dt=0 somehow
# Also handle potential case where dt is zero
return dt / (tau + dt) if tau > 1e-5 and dt > 1e-5 else 1.0 # Default to alpha=1 (no filtering) if tau or dt is invalid/zero
alpha_v_ego = get_alpha(AEM.EMA_TC_V_EGO, DT_MDL)
alpha_lead_drel = get_alpha(AEM.EMA_TC_LEAD_DREL, DT_MDL)
alpha_lead_v_abs = get_alpha(AEM.EMA_TC_LEAD_V_ABS, DT_MDL)
alpha_lead_alead = get_alpha(AEM.EMA_TC_LEAD_ALEAD, DT_MDL)
alpha_steering_angle_abs = get_alpha(AEM.EMA_TC_STEERING_ANGLE_ABS, DT_MDL)
alpha_v_model_error = get_alpha(AEM.EMA_TC_V_MODEL_ERROR, DT_MDL)
# --- Initialize EMA Filters using alpha ---
# Ensure FirstOrderFilter takes 'alpha' as keyword arg
try:
self._v_ego_ema = FirstOrderFilter(0.0, alpha=alpha_v_ego, dt=DT_MDL)
self._lead_drel_ema = FirstOrderFilter(AEM.LEAD_DIST_DEFAULT_NO_LEAD, alpha=alpha_lead_drel, dt=DT_MDL)
self._lead_v_ema = FirstOrderFilter(0.0, alpha=alpha_lead_v_abs, dt=DT_MDL) # Filters absolute V_lead
self._lead_alead_ema = FirstOrderFilter(0.0, alpha=alpha_lead_alead, dt=DT_MDL)
self._steering_angle_abs_ema = FirstOrderFilter(0.0, alpha=alpha_steering_angle_abs, dt=DT_MDL)
# self._v_model_error_ema = FirstOrderFilter(0.0, alpha=alpha_v_model_error, dt=DT_MDL)
except TypeError:
# Fallback if the specific FirstOrderFilter expects positional args differently
print("Warning: FirstOrderFilter init failed with keyword 'alpha', attempting positional.")
self._v_ego_ema = FirstOrderFilter(0.0, alpha_v_ego, DT_MDL)
self._lead_drel_ema = FirstOrderFilter(AEM.LEAD_DIST_DEFAULT_NO_LEAD, alpha_lead_drel, DT_MDL)
self._lead_v_ema = FirstOrderFilter(0.0, alpha_lead_v_abs, DT_MDL)
self._lead_alead_ema = FirstOrderFilter(0.0, alpha_lead_alead, DT_MDL)
self._steering_angle_abs_ema = FirstOrderFilter(0.0, alpha_steering_angle_abs, DT_MDL)
# self._v_model_error_ema = FirstOrderFilter(0.0, alpha_v_model_error, DT_MDL)
self._log(f"AEM Initialized. Alphas: v_ego={alpha_v_ego:.3f}, dRel={alpha_lead_drel:.3f}, vLead={alpha_lead_v_abs:.3f}, aLead={alpha_lead_alead:.3f}, steer={alpha_steering_angle_abs:.3f}, v_err={alpha_v_model_error:.3f}")
def _log(self, msg: str):
"""Logs debug messages if debug is enabled."""
if self.debug:
# Consider using cloudlog for persistent logs during testing
# from openpilot.common.swaglog import cloudlog
# cloudlog.debug(f"[AEM]: {msg}")
print(f"[AEM]: {msg}")
def _calculate_ttc(self, dist: float, ego_speed: float, lead_speed: float) -> float:
"""Calculates Time To Collision (TTC), returns infinity if collision is not imminent."""
relative_speed = ego_speed - lead_speed
if dist > 0.1 and relative_speed > 0.3: # Thresholds to avoid noise and division issues
# Avoid division by zero or very small numbers
return max(0.0, dist / relative_speed) # Return non-negative TTC
return float('inf') # Return infinity if no collision course or relative speed is too low
def _reset_lead_emas(self, d_lead_raw, v_lead_raw, a_lead_raw):
"""Helper to reset/initialize lead EMAs' state."""
# This assumes direct state access/setting via '.x' is the correct method
# for the FirstOrderFilter implementation being used.
try:
self._lead_drel_ema.x = float(d_lead_raw)
self._lead_v_ema.x = float(v_lead_raw)
self._lead_alead_ema.x = float(a_lead_raw)
# Ensure filter knows it's 'initialized' if it uses that flag internally
self._lead_drel_ema.initialized = True
self._lead_v_ema.initialized = True
self._lead_alead_ema.initialized = True
self._log(f"Reset lead EMAs with raw values: d={d_lead_raw:.2f}, v={v_lead_raw:.2f}, a={a_lead_raw:.2f}")
except AttributeError:
self._log("Warning: Could not directly set '.x' on FirstOrderFilter. Reset may not be immediate.")
# If direct access fails, update might be the only way, causing a slight delay in reset
self._lead_drel_ema.update(d_lead_raw)
self._lead_v_ema.update(v_lead_raw)
self._lead_alead_ema.update(a_lead_raw)
def set_personality(self, v_ego, personality):
self.personality = personality
if self.enabled:
self.personality = log.LongitudinalPersonality.aggressive if v_ego > 16.67 else self.personality
return self.personality
# Note: Removed 'model_predicted_max_curvature_upcoming_raw' from signature
def get_mode(self, v_ego_raw: float, lead_one_data_raw, steering_angle_deg_raw: float, standstill_raw: bool,
long_personality: int, allow_throttle_planner: bool,
model_path_plan_raw: dict, a_target_from_prev_cycle: float, model_predicts_stop_prev: bool,
fcw_active_prev: bool) -> str:
"""
Determines the appropriate MPC mode ('acc' or 'blended') based on current conditions.
ACC is primary, Blended is assist.
"""
if not self.enabled:
# Should not be called if not enabled, but return primary mode as safeguard
return 'acc'
# 0. Initialize suggested mode with AEM's current internal state
suggested_mode = self._current_mode
# 1. Update common EMA filters
self._v_ego_ema.update(v_ego_raw)
self._steering_angle_abs_ema.update(abs(steering_angle_deg_raw))
# Use absolute value of model error for thresholding
# self._v_model_error_ema.update(abs(v_model_error_raw))
# 2. Process Lead Data & Update Lead EMAs
lead_status = lead_one_data_raw.status
current_lead_id = lead_one_data_raw.radarTrackId if lead_status else -1
d_lead_raw = lead_one_data_raw.dRel if lead_status else AEM.LEAD_DIST_DEFAULT_NO_LEAD
v_lead_raw = lead_one_data_raw.vLead if lead_status else self._v_ego_ema.x # Default to filtered ego if no lead
a_lead_raw = lead_one_data_raw.aLeadK if lead_status else 0.0
model_prob_lead = lead_one_data_raw.modelProb if lead_status else 0.0
if lead_status:
# Reset EMA if lead ID changes significantly (from a valid previous ID)
if current_lead_id != self._lead_id_prev and self._lead_id_prev != -1:
self._log(f"Lead ID changed: {self._lead_id_prev} -> {current_lead_id}.")
self._reset_lead_emas(d_lead_raw, v_lead_raw, a_lead_raw)
# Reset EMA if acquiring lead from no-lead state
elif self._lead_id_prev == -1 and current_lead_id != -1:
self._log(f"Lead acquired: ID {current_lead_id}.")
self._reset_lead_emas(d_lead_raw, v_lead_raw, a_lead_raw)
# Update filters with current raw data
self._lead_drel_ema.update(d_lead_raw)
self._lead_v_ema.update(v_lead_raw)
self._lead_alead_ema.update(a_lead_raw)
self._lead_absence_frames = 0
else: # No lead currently
self._lead_absence_frames += 1
# If lead was just lost, reset EMAs to default values
if self._lead_id_prev != -1:
self._log(f"Lead lost (Prev ID: {self._lead_id_prev}). Resetting lead EMAs to defaults.")
# Reset using the helper function ensures consistency
self._reset_lead_emas(AEM.LEAD_DIST_DEFAULT_NO_LEAD, self._v_ego_ema.x, 0.0)
else: # Still no lead, continue updating towards defaults
self._lead_drel_ema.update(AEM.LEAD_DIST_DEFAULT_NO_LEAD)
self._lead_v_ema.update(self._v_ego_ema.x) # Update towards filtered ego speed
self._lead_alead_ema.update(0.0) # Update towards zero accel
# Update previous lead ID for next cycle comparison *after* using it
self._lead_id_prev = current_lead_id
# 3. Get Filtered Values for decision making
v_ego = self._v_ego_ema.x
d_lead = self._lead_drel_ema.x
v_lead = self._lead_v_ema.x # Filtered absolute lead speed
a_lead = self._lead_alead_ema.x
steering_angle_abs = self._steering_angle_abs_ema.x
# v_model_error = self._v_model_error_ema.x
ttc = self._calculate_ttc(d_lead, v_ego, v_lead)
is_lead_one_vision_only = lead_status and current_lead_id == -1
# 4. Infer Current Model Intentions from raw plan
raw_model_stop_intention_current_cycle = False
if model_path_plan_raw and 'v' in model_path_plan_raw:
model_v_traj = model_path_plan_raw['v']
if len(model_v_traj) >= 5:
avg_final_model_v = np.mean(model_v_traj[-5:])
final_model_v = model_v_traj[-1]
# Consider stop if avg end speed is near creep and final point is very low
raw_model_stop_intention_current_cycle = avg_final_model_v < AEM.SPEED_THRESHOLD_CREEP and \
final_model_v < AEM.SPEED_THRESHOLD_CREEP * 0.7
elif len(model_v_traj) > 0: # Fallback for shorter trajectories
raw_model_stop_intention_current_cycle = np.isclose(model_v_traj[-1], 0.0, atol=0.3)
# --- 5. Mode Selection Logic ---
min_prob_for_action = AEM.MIN_VISION_LEAD_PROB_ACTION if is_lead_one_vision_only else 0.0
# A. Evaluate conditions to switch TO BLENDED (from ACC)
if self._current_mode == 'acc':
needs_blended_assist = False
reason = "None" # Default reason
# Scenario 1: Emergency/Dangerous Lead Situation
if lead_status and ttc < AEM.LEAD_TTC_CRITICAL and v_ego > AEM.SPEED_THRESHOLD_LOW and model_prob_lead >= min_prob_for_action:
needs_blended_assist, reason = True, f"Critical TTC ({ttc:.2f}s)"
elif lead_status and a_lead < AEM.LEAD_ACCEL_HARD_BRAKE and d_lead < (v_ego * 2.5) and model_prob_lead >= min_prob_for_action:
needs_blended_assist, reason = True, f"Hard lead brake ({a_lead:.2f}), d={d_lead:.1f}"
elif fcw_active_prev:
needs_blended_assist, reason = True, "FCW previously active"
# Scenario 2: Sudden Lead Cut-in/Appearance
# Check only if not already triggered by emergency
if not needs_blended_assist and lead_status and current_lead_id != self._lead_id_prev and self._lead_id_prev != -1 and \
ttc < AEM.LEAD_TTC_CAUTION and d_lead < (AEM.LEAD_DIST_VERY_CLOSE * 2.5) and model_prob_lead >= min_prob_for_action:
needs_blended_assist, reason = True, f"Sudden cut-in (TTC={ttc:.2f}, d={d_lead:.1f})"
# Scenario 3: Low-Speed/Urban/Congestion (Lead)
if not needs_blended_assist and lead_status and v_ego < AEM.SPEED_THRESHOLD_LOW and \
d_lead < (AEM.LEAD_DIST_VERY_CLOSE * 1.8) and model_prob_lead >= min_prob_for_action:
needs_blended_assist, reason = True, f"Low speed ({v_ego:.1f}) close lead ({d_lead:.1f})"
# Scenario 4: Model Predicts Stop
if not needs_blended_assist and \
(raw_model_stop_intention_current_cycle or model_predicts_stop_prev) and \
v_ego > AEM.SPEED_THRESHOLD_CREEP:
needs_blended_assist, reason = True, f"Model predicts stop (curr={raw_model_stop_intention_current_cycle}, prev={model_predicts_stop_prev})"
# Scenario 5: High Curvature/Urban Turns (Curvature input removed)
if not needs_blended_assist and \
steering_angle_abs > AEM.STEERING_ANGLE_ABS_HIGH_CURVATURE and \
v_ego < AEM.SPEED_THRESHOLD_CITY:
needs_blended_assist, reason = True, f"High steering angle ({steering_angle_abs:.1f})"
# Scenario 6: Planner Already Braking (Prev Cycle)
if not needs_blended_assist and \
a_target_from_prev_cycle < AEM.LEAD_ACCEL_MILD_BRAKE and \
not standstill_raw and v_ego > AEM.SPEED_THRESHOLD_CREEP:
needs_blended_assist, reason = True, f"Planner prev brake ({a_target_from_prev_cycle:.2f})"
# Scenario 7: MPC Favored E2E (Prev Cycle) & still complex
# if not needs_blended_assist and mpc_source_prev == 'e2e':
# is_complex = (v_ego < AEM.SPEED_THRESHOLD_CITY or \
# (lead_status and ttc < AEM.LEAD_TTC_CAUTION and model_prob_lead >= min_prob_for_action) or \
# (steering_angle_abs > AEM.STEERING_ANGLE_ABS_HIGH_CURVATURE * 0.6)) # Removed curvature check
# if is_complex:
# needs_blended_assist, reason = True, "Prev E2E source & ongoing complexity"
# Scenario 8: High Gas Disengage Prob. (Model)
if not needs_blended_assist and not allow_throttle_planner and \
(not lead_status or (lead_status and v_ego < (v_lead + AEM.REL_SPEED_SIGNIFICANT_DIFFERENCE * 0.5))):
needs_blended_assist, reason = True, "Model advises against throttle"
if needs_blended_assist:
suggested_mode = 'blended'
self._log(f"ACC->BLENDED Trigger: {reason}")
# B. Evaluate conditions to switch TO ACC (from Blended)
elif self._current_mode == 'blended':
# Assume staying in blended unless conditions strongly favor returning to ACC
suggested_mode = 'blended'
# Check if any critical Blended-triggering condition is still fundamentally active
blended_condition_still_active = False
active_blended_reason = "None"
if lead_status and ttc < AEM.LEAD_TTC_CRITICAL and v_ego > AEM.SPEED_THRESHOLD_LOW and model_prob_lead >= min_prob_for_action: blended_condition_still_active, active_blended_reason = True, "Critical TTC"
elif lead_status and a_lead < AEM.LEAD_ACCEL_HARD_BRAKE and model_prob_lead >= min_prob_for_action: blended_condition_still_active, active_blended_reason = True, "Hard Lead Brake"
elif fcw_active_prev: blended_condition_still_active, active_blended_reason = True, "Prev FCW"
elif (raw_model_stop_intention_current_cycle or model_predicts_stop_prev) and v_ego > AEM.SPEED_THRESHOLD_CREEP: blended_condition_still_active, active_blended_reason = True, "Model Predicts Stop"
elif steering_angle_abs > AEM.STEERING_ANGLE_ABS_HIGH_CURVATURE and v_ego < AEM.SPEED_THRESHOLD_CITY: blended_condition_still_active, active_blended_reason = True, "High Steering Angle"
elif a_target_from_prev_cycle < AEM.LEAD_ACCEL_MILD_BRAKE and not standstill_raw and v_ego > AEM.SPEED_THRESHOLD_CREEP: blended_condition_still_active, active_blended_reason = True, "Prev Planner Brake"
elif not allow_throttle_planner and (not lead_status or (lead_status and v_ego < (v_lead + AEM.REL_SPEED_SIGNIFICANT_DIFFERENCE * 0.5))): blended_condition_still_active, active_blended_reason = True, "Gas Disengage Prob"
elif lead_status and v_ego < AEM.SPEED_THRESHOLD_LOW and \
d_lead < (AEM.LEAD_DIST_VERY_CLOSE * 1.8) and \
model_prob_lead >= min_prob_for_action: # Using filtered values (v_ego, d_lead) as in other checks
blended_condition_still_active, active_blended_reason = True, f"Persisting Low speed ({v_ego:.1f}) close lead ({d_lead:.1f})"
# Low speed congestion is implicitly handled by other checks usually involving TTC or stops
if not blended_condition_still_active:
safe_to_return_to_acc = False
reason = ""
# Scenario 10: Highway Cruising - Excellent Conditions
if v_ego > AEM.SPEED_THRESHOLD_HIGHWAY and \
steering_angle_abs < (AEM.STEERING_ANGLE_ABS_HIGH_CURVATURE * 0.3) and \
(not lead_status or d_lead > (AEM.LEAD_DIST_FAR_HIGHWAY * 0.8) or ttc > (AEM.LEAD_TTC_CAUTION * 1.5)):
safe_to_return_to_acc, reason = True, "Highway cruise, clear path"
# Scenario 11: Stable Following - Safe Distance Achieved
elif lead_status and v_ego > AEM.SPEED_THRESHOLD_LOW and \
ttc > AEM.LEAD_TTC_CAUTION and \
d_lead > (AEM.LEAD_DIST_VERY_CLOSE * 2.0) and \
abs(a_lead) < (AEM.LEAD_ACCEL_PULLING_AWAY * 0.8) and \
abs(v_ego - v_lead) < (AEM.REL_SPEED_SIGNIFICANT_DIFFERENCE * 0.75) and \
steering_angle_abs < (AEM.STEERING_ANGLE_ABS_HIGH_CURVATURE * 0.5):
safe_to_return_to_acc, reason = True, f"Stable following (TTC={ttc:.2f}, d={d_lead:.1f})"
# Scenario 12: Prolonged Lead Absence
elif not lead_status:
personality_factor = 1.3 if long_personality == 0 else (0.7 if long_personality == 2 else 1.0)
fallback_frames = AEM.LEAD_LOST_FRAMES_TO_FALLBACK_BASE * personality_factor
if self._lead_absence_frames > fallback_frames and v_ego > AEM.SPEED_THRESHOLD_LOW:
safe_to_return_to_acc, reason = True, f"Prolonged lead absence ({self._lead_absence_frames} frames)"
# Scenario 13: Persistent High Model Velocity Error
# elif v_model_error > AEM.MODEL_VEL_ERROR_THRESHOLD:
# safe_to_return_to_acc, reason = True, f"High model vel error ({v_model_error:.2f})"
# Default fallback if no active blended condition and no specific ACC condition met
elif not safe_to_return_to_acc:
safe_to_return_to_acc, reason = True, "No active Blended condition found"
if safe_to_return_to_acc:
suggested_mode = 'acc'
self._log(f"BLENDED->ACC Trigger: {reason}")
else:
self._log(f"Staying BLENDED due to active condition: {active_blended_reason}")
# --- 6. Apply Hysteresis ---
if suggested_mode != self._current_mode:
# If target mode changes, reset counter
if self._target_mode_suggestion != suggested_mode:
self._target_mode_suggestion = suggested_mode
self._mode_switch_counter = 1
self._log(f"Target mode suggestion: {self._target_mode_suggestion}. Counter: {self._mode_switch_counter}")
# If target mode remains the same, increment counter
else:
self._mode_switch_counter += 1
# Log counter increment for debugging flapping
# self._log(f"Target mode {self._target_mode_suggestion} persists. Counter: {self._mode_switch_counter}")
# Check if threshold is met to execute switch
if self._mode_switch_counter >= AEM.HYSTERESIS_FRAMES_TO_SWITCH:
self._log(f"\n-----------------------\nExecuting switch from {self._current_mode} to {self._target_mode_suggestion}\n-----------------------")
self._current_mode = self._target_mode_suggestion
# Reset hysteresis state after switch
self._mode_switch_counter = 0
self._target_mode_suggestion = None
# else: remain in _current_mode until counter threshold met
else: # Suggested mode is the same as current mode
# If there was a pending switch suggestion, cancel it
if self._target_mode_suggestion is not None:
self._log(f"Mode suggestion {suggested_mode} matches current mode {self._current_mode}, cancelling pending switch to {self._target_mode_suggestion}")
# Reset hysteresis state
self._target_mode_suggestion = None
self._mode_switch_counter = 0
# Return the potentially updated current mode
return self._current_mode
+145
View File
@@ -0,0 +1,145 @@
"""
Copyright (c) 2025, Rick Lan
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, and/or sublicense,
for non-commercial purposes only, 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.
- Commercial use (e.g. use in a product, service, or activity intended to
generate revenue) is prohibited without explicit written permission from
the copyright holder.
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.
"""
import numpy as np
from openpilot.selfdrive.modeld.constants import ModelConstants
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC
from openpilot.common.swaglog import cloudlog
# Physics constants
COMFORT_LAT_G = 0.2 # g units - universal human comfort threshold
BASE_LAT_ACC = COMFORT_LAT_G * 9.81 # ~2.0 m/s^2
SAFETY_FACTOR = 0.9 # 10% safety margin on calculated speeds
MIN_CURVE_DISTANCE = 5.0 # meters - minimum distance to react to curves
MAX_DECEL = -2.0 # m/s^2 - maximum comfortable deceleration
class DTSC:
"""
Dynamic Turn Speed Controller - Predictive curve speed management via MPC constraints.
Core physics: v_max = sqrt(lateral_acceleration / curvature) * safety_factor
Operation:
1. Scans predicted path for curvature (up to ~10 seconds ahead)
2. Calculates safe speed for each point using physics + comfort limits
3. Identifies critical points where current speed would exceed safe speed
4. Calculates required deceleration to reach safe speed at critical point
5. Provides deceleration as MPC constraint for smooth trajectory planning
"""
def __init__(self, aggressiveness=1.0):
"""
Initialize DTSC with user-adjustable aggressiveness.
Args:
aggressiveness: Factor to adjust lateral acceleration limit
0.7 = 30% more conservative (slower in curves)
1.0 = default balanced behavior
1.3 = 30% more aggressive (faster in curves)
"""
self.aggressiveness = np.clip(aggressiveness, 0.5, 1.5)
self.active = False
self.debug_msg = ""
cloudlog.info(f"DTSC: Initialized with aggressiveness {self.aggressiveness:.2f}")
def set_aggressiveness(self, value):
"""Update aggressiveness factor (0.5 to 1.5)."""
self.aggressiveness = np.clip(value, 0.5, 1.5)
cloudlog.info(f"DTSC: Aggressiveness updated to {self.aggressiveness:.2f}")
def get_mpc_constraints(self, model_msg, v_ego, base_a_min, base_a_max):
"""
Calculate MPC acceleration constraints based on predicted path curvature.
Args:
model_msg: ModelDataV2 containing predicted path
v_ego: Current vehicle speed (m/s)
base_a_min: Default minimum acceleration constraint
base_a_max: Default maximum acceleration constraint
Returns:
(a_min_array, a_max_array): Modified constraints for each MPC timestep
"""
# Initialize with base constraints
a_min = np.ones(len(T_IDXS_MPC)) * base_a_min
a_max = np.ones(len(T_IDXS_MPC)) * base_a_max
# Validate model data
if not self._is_model_data_valid(model_msg):
self.active = False
return a_min, a_max
# Extract predictions for MPC horizon
v_pred = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.velocity.x)
turn_rates = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.orientationRate.z)
positions = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.position.x)
# Calculate curvature (turn_rate / velocity)
curvatures = np.abs(turn_rates / np.clip(v_pred, 1.0, 100.0))
# Calculate safe speeds
lat_acc_limit = BASE_LAT_ACC * self.aggressiveness
safe_speeds = np.sqrt(lat_acc_limit / (curvatures + 1e-6)) * SAFETY_FACTOR
# Find speed violations
speed_excess = v_pred - safe_speeds
if np.all(speed_excess <= 0):
self._deactivate()
return a_min, a_max
# Find critical point (maximum speed excess)
critical_idx = np.argmax(speed_excess)
critical_distance = positions[critical_idx]
critical_safe_speed = safe_speeds[critical_idx]
# Only act if we have sufficient distance
if critical_distance <= MIN_CURVE_DISTANCE:
self._deactivate()
return a_min, a_max
# Calculate required deceleration: a = (v_f^2 - v_i^2) / (2*d)
required_decel = (critical_safe_speed**2 - v_ego**2) / (2 * critical_distance)
required_decel = max(required_decel, MAX_DECEL)
# Apply constraint progressively until critical point
for i in range(len(T_IDXS_MPC)):
t = T_IDXS_MPC[i]
distance_at_t = v_ego * t + 0.5 * required_decel * t**2
if distance_at_t < critical_distance:
a_max[i] = min(a_max[i], required_decel)
# Update status
self.active = True
self.debug_msg = f"Curve in {critical_distance:.0f}m → {critical_safe_speed*3.6:.0f} km/h"
cloudlog.info(f"DTSC: {self.debug_msg} (aggr={self.aggressiveness:.1f})")
return a_min, a_max
def _is_model_data_valid(self, model_msg):
"""Check if model message contains valid prediction data."""
return (len(model_msg.position.x) == ModelConstants.IDX_N and
len(model_msg.velocity.x) == ModelConstants.IDX_N and
len(model_msg.orientationRate.z) == ModelConstants.IDX_N)
def _deactivate(self):
"""Clear active state and debug message."""
self.active = False
self.debug_msg = ""
+31 -18
View File
@@ -5,13 +5,13 @@ Copyright (c) 2025, Rick Lan
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, and/or sublicense,
to use, copy, modify, merge, publish, distribute, and/or sublicense,
for non-commercial purposes only, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
- Commercial use (e.g. use in a product, service, or activity intended to
generate revenue) is prohibited without explicit written permission from
- Commercial use (e.g. use in a product, service, or activity intended to
generate revenue) is prohibited without explicit written permission from
the copyright holder.
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.
@@ -26,18 +26,23 @@ import threading
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
class Beepd:
def __init__(self):
def __init__(self, test=False):
self.current_alert = AudibleAlert.none
self._test = test
self.enable_gpio()
def enable_gpio(self):
try:
if self._test:
print("enabling GPIO")
subprocess.run("echo 42 | sudo tee /sys/class/gpio/export",
shell=True,
stderr=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
encoding='utf8')
except Exception:
if self._test:
print("GPIO failed to enable")
pass
subprocess.run("echo \"out\" | sudo tee /sys/class/gpio/gpio42/direction",
shell=True,
@@ -54,18 +59,24 @@ class Beepd:
encoding='utf8')
def engage(self):
if self._test:
print("beepd: engage")
self._beep(True)
time.sleep(0.05)
self._beep(False)
def disengage(self):
if self._test:
print("beepd: disengage")
for _ in range(2):
self._beep(True)
time.sleep(0.01)
self._beep(False)
time.sleep(0.01)
def warning(self):
def prompt(self):
if self._test:
print("beepd: prompt")
for _ in range(3):
self._beep(True)
time.sleep(0.01)
@@ -73,6 +84,8 @@ class Beepd:
time.sleep(0.01)
def warning_immediate(self):
if self._test:
print("beepd: warning_immediate")
for _ in range(5):
self._beep(True)
time.sleep(0.01)
@@ -86,10 +99,12 @@ class Beepd:
current_alert_played_once = self.current_alert == AudibleAlert.none
if self.current_alert != new_alert and (new_alert != AudibleAlert.none or current_alert_played_once):
self.current_alert = new_alert
# if new_alert == AudibleAlert.engage:
# self.dispatch_beep(self.engage)
# if new_alert == AudibleAlert.disengage:
# self.dispatch_beep(self.disengage)
if new_alert == AudibleAlert.engage:
self.dispatch_beep(self.engage)
if new_alert == AudibleAlert.disengage:
self.dispatch_beep(self.disengage)
if new_alert == AudibleAlert.prompt:
self.dispatch_beep(self.prompt)
if new_alert == AudibleAlert.warningImmediate:
self.dispatch_beep(self.warning_immediate)
@@ -111,17 +126,15 @@ class Beepd:
if frame == 60:
cs.selfdriveState.alertSound = AudibleAlert.prompt
if frame == 80:
cs.selfdriveState.alertSound = AudibleAlert.disengage
if frame == 85:
cs.selfdriveState.alertSound = AudibleAlert.prompt
cs.selfdriveState.alertSound = AudibleAlert.warningImmediate
pm.send("selfdriveState", cs)
frame += 1
rk.keep_time()
def beepd_thread(self, test=False):
if test:
threading.Thread(target=self.test_beepd_thread, daemon=True).start()
def beepd_thread(self):
if self._test:
threading.Thread(target=self.test_beepd_thread).start()
sm = messaging.SubMaster(['selfdriveState'])
rk = Ratekeeper(20)
@@ -132,8 +145,8 @@ class Beepd:
rk.keep_time()
def main():
s = Beepd()
s.beepd_thread(test=False)
s = Beepd(test=False)
s.beepd_thread()
if __name__ == "__main__":
main()
@@ -0,0 +1,307 @@
import os
from openpilot.system.ui.widgets import Widget, DialogResult
from openpilot.common.params import Params, UnknownKeyName
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.widgets.list_view import multiple_button_item, toggle_item, simple_item, button_item, spin_button_item, double_spin_button_item, text_spin_button_item
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dialog
LITE = os.getenv("LITE") is not None
class DragonpilotLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
self._scroller: Scroller | None = None
self._has_long_ctrl = False
self._has_radar_unavailable = False
self._toggles = {}
if ui_state.CP is not None:
self._has_long_ctrl = ui_state.CP.openpilotLongitudinalControl
self._has_radar_unavailable = ui_state.CP.radarUnavailable
match ui_state.CP.brand:
case "toyota":
self._toyota_toggles()
case "volkswagen":
self._vag_toggles()
case "mazda":
self._mazda_toggles()
self._lat_toggles()
self._lon_toggles()
self._ui_toggles()
self._device_toggles()
self._reset_dp_conf_btn = button_item(
lambda: tr("Reset DP Settings"),
lambda: tr("RESET"),
lambda: tr("Reset dragonpilot settings to default and restart the device."),
callback=self._reset_dp_conf)
self._toggles['btn_reset_dp_conf'] = self._reset_dp_conf_btn
self._scroller = Scroller(list(self._toggles.values()), line_separator=True, spacing=0)
def _toyota_toggles(self):
self._toggles["title_toyota"] = simple_item(title=lambda: tr("### Toyota / Lexus ###"))
self._toggles["dp_toyota_door_auto_lock_unlock"] = toggle_item(
title=lambda: tr("Door Auto Lock/Unlock"),
description=lambda: tr("Enable openpilot to auto-lock doors above 20 km/h and auto-unlock when shifting to Park."),
initial_state=self._params.get_bool("dp_toyota_door_auto_lock_unlock"),
callback=lambda val: self._params.put_bool("dp_toyota_door_auto_lock_unlock", val),
)
self._toggles["dp_toyota_tss1_sng"] = toggle_item(
title=lambda: tr("Enable TSS1 SnG Mod"),
description=lambda: "",
initial_state=self._params.get_bool("dp_toyota_tss1_sng"),
callback=lambda val: self._params.put_bool("dp_toyota_tss1_sng", val),
)
self._toggles["dp_toyota_stock_lon"] = toggle_item(
title=lambda: tr("Use Stock Longitudinal Control"),
description=lambda: "",
initial_state=self._params.get_bool("dp_toyota_stock_lon"),
callback=lambda val: self._params.put_bool("dp_toyota_stock_lon", val),
)
def _vag_toggles(self):
self._toggles["title_vag"] = simple_item(title=lambda: tr("### VAG ###"))
self._toggles["dp_vag_a0_sng"] = toggle_item(
title=lambda: tr("MQB A0 SnG Mod"),
description=lambda: "",
initial_state=self._params.get_bool("dp_vag_a0_sng"),
callback=lambda val: self._params.put_bool("dp_vag_a0_sng", val),
)
self._toggles["dp_vag_pq_steering_patch"] = toggle_item(
title=lambda: tr("PQ Steering Patch"),
description=lambda: "",
initial_state=self._params.get_bool("dp_vag_pq_steering_patch"),
callback=lambda val: self._params.put_bool("dp_vag_pq_steering_patch", val),
)
self._toggles["dp_vag_avoid_eps_lockout"] = toggle_item(
title=lambda: tr("Avoid EPS Lockout"),
description=lambda: "",
initial_state=self._params.get_bool("dp_vag_avoid_eps_lockout"),
callback=lambda val: self._params.put_bool("dp_vag_avoid_eps_lockout", val),
)
def _mazda_toggles(self):
self._toggles["title_mazda"] = simple_item(title=lambda: tr("### Mazda ###"))
def _lat_toggles(self):
self._toggles["title_lat"] = simple_item(title=lambda: tr("### Lateral ###"))
self._toggles["dp_lat_alka"] = toggle_item(
title=lambda: tr("Always-on Lane Keeping Assist (ALKA)"),
description=lambda: tr("Allows openpilot to always steer to keep the car in its lane."),
initial_state=self._params.get_bool("dp_lat_alka"),
callback=lambda val: self._params.put_bool("dp_lat_alka", val),
)
self._toggles['dp_lat_lca_speed'] = spin_button_item(
title=lambda: tr('Lane Change Assist At:'),
description=lambda: tr("Off = Disable LCA.<br>1 mph = 1.2 km/h."),
initial_value=int(self._params.get("dp_lat_lca_speed") or 20),
callback=self._on_dp_lat_lca_speed_change,
min_val=0,
max_val=100,
step=5,
suffix=tr(" mph"),
special_value_text=tr("Off"),
)
self._toggles['dp_lat_lca_auto_sec'] = double_spin_button_item(
title=lambda: "+ " + tr('Auto Lane Change after:'),
description=lambda: tr("Off = Disable Auto Lane Change."),
initial_value=int(self._params.get("dp_lat_lca_auto_sec") or 0.0),
callback=lambda val: self._params.put("dp_lat_lca_auto_sec", float(val)),
min_val=0.0,
max_val=5.0,
step=0.5,
suffix=tr(" sec"),
special_value_text=tr("Off"),
# enabled=int(self._params.get("dp_lat_lca_speed") or 20) > 0
)
self._toggles['dp_lat_road_edge_detection'] = toggle_item(
title=lambda: tr('Road Edge Detection (RED)'),
description=lambda: tr("Block lane change assist when the system detects the road edge.<br>NOTE: This will show 'Car Detected in Blindspot' warning."),
initial_state=self._params.get_bool("dp_lat_road_edge_detection"),
callback=lambda val: self._params.put_bool("dp_lat_road_edge_detection", val),
)
def _lon_toggles(self):
self._toggles["title_lon"] = simple_item(title=lambda: tr("### Longitudinal ###"))
self._toggles['dp_lon_ext_radar'] = toggle_item(
title=lambda: tr("Use External Radar"),
description=lambda: tr("See https://github.com/eFiniLan/openpilot-ext-radar-addon for more information."),
initial_state=self._params.get_bool("dp_lon_ext_radar"),
callback=lambda val: self._params.put_bool("dp_lon_ext_radar", val),
)
self._toggles["dp_lon_acm"] = toggle_item(
title=lambda: tr("Enable Adaptive Coasting Mode (ACM)"),
description=tr("Adaptive Coasting Mode (ACM) reduces braking to allow smoother coasting when appropriate."),
initial_state=self._params.get_bool("dp_lon_acm"),
callback=lambda val: self._params.put_bool("dp_lon_acm", val),
)
self._toggles["dp_lon_aem"] = toggle_item(
title=lambda: tr("Adaptive Experimental Mode (AEM)"),
description=lambda: tr("Adaptive mode switcher between ACC and Blended based on driving context."),
initial_state=self._params.get_bool("dp_lon_aem"),
callback=lambda val: self._params.put_bool("dp_lon_aem", val),
)
self._toggles["dp_lon_dtsc"] = toggle_item(
title=lambda: tr("Dynamic Turn Speed Control (DTSC)"),
description=lambda: tr("DTSC automatically adjusts the vehicle's predicted speed based on upcoming road curvature and grip conditions.<br>Originally from the openpilot TACO branch."),
initial_state=self._params.get_bool("dp_lon_dtsc"),
callback=lambda val: self._params.put_bool("dp_lon_dtsc", val),
)
def _ui_toggles(self):
self._toggles["title_ui"] = simple_item(title=lambda: tr("### UI ###"))
self._toggles["dp_ui_hide_hud_speed_kph"] = spin_button_item(
title=lambda: tr("Hide HUD When Moves above:"),
description=lambda: tr("To prevent screen burn-in, hide Speed, MAX Speed, and Steering/DM Icons when the car moves.<br>Off = Stock Behavior<br>1 km/h = 0.6 mph"),
initial_value=int(self._params.get("dp_ui_hide_hud_speed_kph") or 0),
callback=lambda val: self._params.put("dp_ui_hide_hud_speed_kph", val),
suffix=tr(" km/h"),
min_val=0,
max_val=120,
step=5,
special_value_text=tr("Off"),
)
self._toggles["dp_ui_rainbow"] = toggle_item(
title=lambda: tr("Rainbow Driving Path like Tesla"),
description=lambda: tr("Why not?"),
initial_state=self._params.get_bool("dp_ui_rainbow"),
callback=lambda val: self._params.put_bool("dp_ui_rainbow", val),
)
self._toggles["dp_ui_lead"] = text_spin_button_item(
title=lambda: tr("Display Lead Stats"),
description=lambda: tr("Display the statistics of lead car and/or radar tracking points.<br>Lead: Lead stats only<br>Radar: Radar tracking point stats only<br>All: Lead and Radar stats<br>NOTE: Radar option only works on certain vehicle models."),
initial_index=int(self._params.get("dp_ui_lead") or 0),
callback=lambda val: self._params.put("dp_ui_lead", val),
options=["Off", "Lead", "Radar", "All"]
)
def _device_toggles(self):
self._toggles["title_dev"] = simple_item(title=lambda: tr("### Device ###"))
if LITE:
self._toggles["dp_dev_is_rhd"] = toggle_item(
title=lambda: tr("Enable Right-Hand Drive Mode"),
description=lambda: tr("Allow openpilot to obey right-hand traffic conventions on right driver seat."),
initial_state=self._params.get_bool("dp_dev_is_rhd"),
callback=lambda val: self._params.put_bool("dp_dev_is_rhd", val),
)
self._toggles["dp_dev_monitoring_disabled"] = toggle_item(
title=lambda: tr("Disable Driver Monitoring"),
description=lambda: tr("USE AT YOUR OWN RISK."),
initial_state=self._params.get_bool("dp_dev_monitoring_disabled"),
callback=lambda val: self._params.put_bool("dp_dev_monitoring_disabled", val),
)
self._toggles["dp_dev_beep"] = toggle_item(
title=lambda: tr("Enable Beep (Warning)"),
description=lambda: tr("Use Buzzer for audiable alerts."),
initial_state=self._params.get_bool("dp_dev_beep"),
callback=lambda val: self._params.put_bool("dp_dev_beep", val),
)
self._toggles["dp_ui_display_mode"] = text_spin_button_item(
title=lambda: tr("Display Mode"),
callback=lambda val: self._params.put("dp_ui_display_mode", val),
options=["Std.", "MAIN+", "OP+", "MAIN-", "OP-"],
initial_index=int(self._params.get("dp_ui_display_mode") or 0),
description=lambda: tr("Std.: Stock behavior.<br>MAIN+: ACC MAIN on = Display ON.<br>OP+: OP enabled = Display ON.<br>MAIN-: ACC MAIN on = Display OFF<br>OP-: OP enabled = Display OFF."),
)
if "LITE" not in os.environ:
self._toggles["dp_dev_audible_alert_mode"] = text_spin_button_item(
title=lambda: tr("Audible Alert"),
description=lambda: tr("Std.: Stock behaviour.<br>Warning: Only emits sound when there is a warning.<br>Off: Does not emit any sound at all."),
initial_index=int(self._params.get("dp_dev_audible_alert_mode") or 0),
callback=lambda val: self._params.put("dp_dev_audible_alert_mode", val),
options=["Std.", "Warning", "Off"],
)
self._toggles["dp_dev_auto_shutdown_in"] = spin_button_item(
title=lambda: tr("Auto Shutdown After"),
description=lambda: tr("0 mins = Immediately"),
initial_value=int(self._params.get("dp_dev_auto_shutdown_in") or -5),
callback=lambda val: self._params.put("dp_dev_auto_shutdown_in", val),
min_val=-5,
max_val=300,
step=5,
suffix=tr(" mins"),
special_value_text=tr("Off"),
)
self._toggles["dp_dev_dashy"] = text_spin_button_item(
title=lambda: tr("dashy"),
description=lambda: tr("dashy - dragonpilot's all-in-one system hub for you.<br><br>Visit http://<device_ip>:5088 to access.<br><br>Off - Turn off dashy completely.<br>File: File Manager only.<br>All: File Manager + Live Stream."),
initial_index=int(self._params.get("dp_dev_dashy") or 0),
callback=lambda val: self._params.put("dp_dev_dashy", val),
options=[tr("Off"), tr("File"), tr("All")],
)
self._toggles["dp_dev_delay_loggerd"] = spin_button_item(
title=lambda: tr("Delay Starting Loggerd for:"),
description=lambda: tr("Delays the startup of loggerd and its related processes when the device goes on-road.<br>This prevents the initial moments of a drive from being recorded, protecting location privacy at the start of a trip."),
initial_value=int(self._params.get("dp_dev_delay_loggerd") or 0),
callback=lambda val: self._params.put("dp_dev_delay_loggerd", val),
min_val=0,
max_val=300,
step=5,
suffix=tr(" secs"),
special_value_text=tr("Off"),
)
self._toggles["dp_dev_disable_connect"] = toggle_item(
title=lambda: tr("Disable Comma Connect"),
description=lambda: tr("Disable Comma connect service if you do not wish to upload / being tracked by the service."),
initial_state=self._params.get_bool("dp_dev_disable_connect"),
callback=lambda val: self._params.put_bool("dp_dev_disable_connect", val),
)
def _reset_dp_conf(self):
def reset_dp_conf(result: int):
# Check engaged again in case it changed while the dialog was open
if result != DialogResult.CONFIRM:
return
self._params.put_bool("dp_dev_reset_conf", True)
self._params.put_bool("DoReboot", True)
dialog = ConfirmDialog(tr("Are you sure you want to reset ALL DP SETTINGS to default?"), tr("Reset"))
gui_app.set_modal_overlay(dialog, callback=reset_dp_conf)
def show_event(self):
self._scroller.show_event()
self._update_toggles()
def _update_toggles(self):
ui_state.update_params()
def _render(self, rect):
self._scroller.render(rect)
def _on_dp_lat_lca_speed_change(self, val):
self._params.put("dp_lat_lca_speed", int(val))
self._toggles['dp_lat_lca_auto_sec'].action_item.set_enabled(val > 0)
@@ -0,0 +1,133 @@
import numpy as np
import pyray as rl
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY
from openpilot.selfdrive.ui.mici.onroad import blend_colors
from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient
from openpilot.system.ui.widgets import Widget
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.selfdrive.ui.mici.onroad.torque_bar import arc_bar_pts
# TODO: arc_bar_pts doesn't consider rounded end caps part of the angle span
TORQUE_ANGLE_SPAN = 12.7
DEBUG = False
SCALE = 2.5
class TorqueBar(Widget):
def __init__(self, demo: bool = False):
super().__init__()
self._demo = demo
self._torque_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps)
self._torque_line_alpha_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps)
def update_filter(self, value: float):
"""Update the torque filter value (for demo mode)."""
self._torque_filter.update(value)
def _update_state(self):
if self._demo:
return
# torque line
if ui_state.sm['controlsState'].lateralControlState.which() == 'angleState':
controls_state = ui_state.sm['controlsState']
car_state = ui_state.sm['carState']
live_parameters = ui_state.sm['liveParameters']
lateral_acceleration = controls_state.curvature * car_state.vEgo ** 2 - live_parameters.roll * ACCELERATION_DUE_TO_GRAVITY
# TODO: pull from carparams
max_lateral_acceleration = 3
# from selfdrived
actual_lateral_accel = controls_state.curvature * car_state.vEgo ** 2
desired_lateral_accel = controls_state.desiredCurvature * car_state.vEgo ** 2
accel_diff = (desired_lateral_accel - actual_lateral_accel)
self._torque_filter.update(min(max(lateral_acceleration / max_lateral_acceleration + accel_diff, -1), 1))
else:
self._torque_filter.update(-ui_state.sm['carOutput'].actuatorsOutput.torque)
def _render(self, rect: rl.Rectangle) -> None:
# adjust y pos with torque
torque_line_offset = np.interp(abs(self._torque_filter.x), [0.5, 1], [22 * SCALE, 26 * SCALE])
torque_line_height = np.interp(abs(self._torque_filter.x), [0.5, 1], [14 * SCALE, 56 * SCALE])
# animate alpha and angle span
if not self._demo:
self._torque_line_alpha_filter.update(ui_state.status != UIStatus.DISENGAGED)
else:
self._torque_line_alpha_filter.update(1.0)
torque_line_bg_alpha = np.interp(abs(self._torque_filter.x), [0.5, 1.0], [0.25, 0.5])
torque_line_bg_color = rl.Color(255, 255, 255, int(255 * torque_line_bg_alpha * self._torque_line_alpha_filter.x))
if ui_state.status != UIStatus.ENGAGED and not self._demo:
torque_line_bg_color = rl.Color(255, 255, 255, int(255 * 0.15 * self._torque_line_alpha_filter.x))
# draw curved line polygon torque bar
torque_line_radius = 1200 * SCALE
top_angle = -90
torque_bg_angle_span = self._torque_line_alpha_filter.x * TORQUE_ANGLE_SPAN
torque_start_angle = top_angle - torque_bg_angle_span / 2
torque_end_angle = top_angle + torque_bg_angle_span / 2
# centerline radius & center
mid_r = torque_line_radius + torque_line_height / 2
cx = rect.x + rect.width / 2 + (8 * SCALE)
cy = rect.y + rect.height + torque_line_radius - torque_line_offset
# SCALED: pass cap_radius explicitly so the corners round properly
scaled_cap_radius = 7 * SCALE
# draw bg torque indicator line
bg_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, torque_start_angle, torque_end_angle,
cap_radius=scaled_cap_radius)
draw_polygon(rect, bg_pts, color=torque_line_bg_color)
# draw torque indicator line
a0s = top_angle
a1s = a0s + torque_bg_angle_span / 2 * self._torque_filter.x
sl_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, a0s, a1s,
cap_radius=scaled_cap_radius)
# draw beautiful gradient from center to 65% of the bg torque bar width
start_grad_pt = cx / rect.width
if self._torque_filter.x < 0:
end_grad_pt = (cx * (1 - 0.65) + (min(bg_pts[:, 0]) * 0.65)) / rect.width
else:
end_grad_pt = (cx * (1 - 0.65) + (max(bg_pts[:, 0]) * 0.65)) / rect.width
# fade to orange as we approach max torque
start_color = blend_colors(
rl.Color(255, 255, 255, int(255 * 0.9 * self._torque_line_alpha_filter.x)),
rl.Color(255, 200, 0, int(255 * self._torque_line_alpha_filter.x)), # yellow
max(0, abs(self._torque_filter.x) - 0.75) * 4,
)
end_color = blend_colors(
rl.Color(255, 255, 255, int(255 * 0.9 * self._torque_line_alpha_filter.x)),
rl.Color(255, 115, 0, int(255 * self._torque_line_alpha_filter.x)), # orange
max(0, abs(self._torque_filter.x) - 0.75) * 4,
)
if ui_state.status != UIStatus.ENGAGED and not self._demo:
start_color = end_color = rl.Color(255, 255, 255, int(255 * 0.35 * self._torque_line_alpha_filter.x))
gradient = Gradient(
start=(start_grad_pt, 0),
end=(end_grad_pt, 0),
colors=[
start_color,
end_color,
],
stops=[0.0, 1.0],
)
draw_polygon(rect, sl_pts, gradient=gradient)
# draw center torque bar dot
if abs(self._torque_filter.x) < 0.5:
dot_y = rect.y + rect.height - torque_line_offset - torque_line_height / 2
rl.draw_circle(int(cx), int(dot_y), int(10 * SCALE) // 2,
rl.Color(182, 182, 182, int(255 * 0.9 * self._torque_line_alpha_filter.x)))
+1 -1
View File
@@ -1 +1 @@
1508b2a6e31204c2fb41dd023412c90808236940
a29fdbd02407d41ecbcc69d151bb4837bfba3cbc
+1 -1
View File
@@ -1 +1 @@
1755620357 2025-08-19 09:19:17 -0700
1763595829 2025-11-19 15:43:49 -0800
+25 -14
View File
@@ -27,22 +27,32 @@ function agnos_init {
fi
}
no_amp() {
output=$(i2cget -y 0 0x10 0x00 2>/dev/null)
if [ -z "$output" ]; then
return 0
else
return 1
set_tici_hw() {
if grep -q "tici" /sys/firmware/devicetree/base/model 2>/dev/null; then
echo "Querying panda MCU type..."
MCU_OUTPUT=$(python -c "from panda_tici import Panda; p = Panda(cli=False); print(p.get_mcu_type()); p.close()" 2>/dev/null)
if [[ "$MCU_OUTPUT" == *"McuType.F4"* ]]; then
echo "TICI (DOS) detected"
elif [[ "$MCU_OUTPUT" == *"McuType.H7"* ]]; then
echo "TICI (TRES) detected"
export TICI_TRES=1
else
echo "TICI (UNKNOWN) detected"
fi
export TICI_HW=1
fi
}
function check_device_mode {
if no_amp; then
echo "O3 Lite Mode"
export LITE=1
export DISABLE_DRIVER=1
else
echo "C3 Mode"
set_lite_hw() {
if grep -q "tici" /sys/firmware/devicetree/base/model 2>/dev/null; then
output=$(i2cget -y 0 0x10 0x00 2>/dev/null)
if [ -z "$output" ]; then
echo "Lite HW"
export LITE=1
export DISABLE_DRIVER=1
fi
fi
}
@@ -90,7 +100,8 @@ function launch {
# hardware specific init
if [ -f /AGNOS ]; then
check_device_mode
set_tici_hw
set_lite_hw
agnos_init
fi
+10 -4
View File
@@ -5,12 +5,18 @@ export MKL_NUM_THREADS=1
export NUMEXPR_NUM_THREADS=1
export OPENBLAS_NUM_THREADS=1
export VECLIB_MAXIMUM_THREADS=1
if [ -s /data/params/d/dp_device_model_selected ]; then
export FINGERPRINT="$(cat /data/params/d/dp_device_model_selected)"
fi
# models get lower priority than ui
# - ui is ~5ms
# - modeld is 20ms
# - DM is 10ms
# in order to run ui at 60fps (16.67ms), we need to allow
# it to preempt the model workloads. we have enough
# headroom for this until ui is moved to the CPU.
export QCOM_PRIORITY=12
if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="12.8"
export AGNOS_VERSION="15.1"
fi
export STAGING_ROOT="/data/safe_staging"
+1 -1
View File
@@ -21,7 +21,7 @@ nav:
- What is openpilot?: getting-started/what-is-openpilot.md
- How-to:
- Turn the speed blue: how-to/turn-the-speed-blue.md
- Connect to a comma 3/3X: how-to/connect-to-comma.md
- Connect to a comma 3X: how-to/connect-to-comma.md
# - Make your first pull request: how-to/make-first-pr.md
#- Replay a drive: how-to/replay-a-drive.md
- Concepts:
-1
View File
@@ -1 +0,0 @@
.sconsign.dblite
+3 -2
View File
@@ -8,11 +8,12 @@ __pycache__
*.so
*.o
*.a
uv.lock
catch2/
test_runner
libmessaging.*
libmessaging_shared.*
services.h
.sconsign.dblite
.mypy_cache/
.mypy_cache/
+6 -5
View File
@@ -1,18 +1,18 @@
files: ^msgq/
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v6.0.0
hooks:
- id: check-ast
- id: check-yaml
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.9.0
rev: v1.17.1
hooks:
- id: mypy
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.5
rev: v0.12.12
hooks:
- id: ruff
- repo: local
@@ -30,8 +30,9 @@ repos:
- --force
- --quiet
- -j4
- --check-level=exhaustive
- repo: https://github.com/cpplint/cpplint
rev: 1.6.1
rev: 2.0.2
hooks:
- id: cpplint
args:
@@ -40,7 +41,7 @@ repos:
- --linelength=240
- --filter=-build,-legal,-readability,-runtime,-whitespace,+build/include_subdir,+build/forward_decl,+build/include_what_you_use,+build/deprecated,+whitespace/comma,+whitespace/line_length,+whitespace/empty_if_body,+whitespace/empty_loop_body,+whitespace/empty_conditional_body,+whitespace/forcolon,+whitespace/parens,+whitespace/semicolon,+whitespace/tab,+readability/braces
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
rev: v2.4.1
hooks:
- id: codespell
args:
-52
View File
@@ -1,52 +0,0 @@
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
autoconf \
build-essential \
ca-certificates \
capnproto \
clang \
cppcheck \
curl \
git \
libbz2-dev \
libcapnp-dev \
libclang-rt-dev \
libffi-dev \
liblzma-dev \
libncurses5-dev \
libncursesw5-dev \
libreadline-dev \
libsqlite3-dev \
libssl-dev \
libtool \
libzmq3-dev \
llvm \
make \
cmake \
ocl-icd-opencl-dev \
opencl-headers \
python3-dev \
python3-pip \
tk-dev \
wget \
xz-utils \
zlib1g-dev \
&& rm -rf /var/lib/apt/lists/*
RUN pip3 install --break-system-packages --no-cache-dir pyyaml Cython scons pycapnp pre-commit ruff parameterized coverage numpy pytest
WORKDIR /project/msgq
RUN cd /tmp/ && \
git clone -b v2.x --depth 1 https://github.com/catchorg/Catch2.git && \
cd Catch2 && \
mv single_include/* /project/msgq/ && \
cd .. \
rm -rf Catch2
ENV PYTHONPATH=/project/msgq
COPY . .
RUN ls && rm -rf .git && \
scons -c && scons -j$(nproc)
+1
View File
@@ -5,6 +5,7 @@
#include <string>
#include <exception>
#include <filesystem>
#include <vector>
#include <unistd.h>
#include <poll.h>
+1
View File
@@ -1,3 +1,4 @@
#include <vector>
#include "msgq/impl_fake.h"
void FakePoller::registerSocket(SubSocket *socket) {
+2
View File
@@ -3,6 +3,8 @@
#include <iostream>
#include <cstdlib>
#include <cerrno>
#include <string>
#include <vector>
#include "msgq/impl_msgq.h"
+2
View File
@@ -4,6 +4,8 @@
#include <cstdlib>
#include <cerrno>
#include <unistd.h>
#include <string>
#include <vector>
#include "msgq/impl_zmq.h"
+1
View File
@@ -1,5 +1,6 @@
#include <cassert>
#include <iostream>
#include <string>
#include "msgq/ipc.h"
#include "msgq/impl_zmq.h"
+9 -1
View File
@@ -2,6 +2,8 @@
#include <cassert>
#include <iostream>
#include <thread>
#include <string>
#include <set>
#include <unistd.h>
#include "msgq/visionipc/visionipc.h"
@@ -94,7 +96,13 @@ VisionBuf * VisionIpcClient::recv(VisionIpcBufExtra * extra, const int timeout_m
assert(r->getSize() == sizeof(VisionIpcPacket));
VisionIpcPacket *packet = (VisionIpcPacket*)r->getData();
assert(packet->idx < num_buffers);
// Check if packet index is out of bounds, indicating server has changed
if (packet->idx >= num_buffers) {
connected = false;
delete r;
return nullptr;
}
VisionBuf * buf = &buffers[packet->idx];
if (buf->server_id != packet->server_id){
@@ -3,6 +3,8 @@
#include <cassert>
#include <random>
#include <limits>
#include <string>
#include <vector>
#include <poll.h>
#include <sys/socket.h>
+26
View File
@@ -1,3 +1,29 @@
[project]
name = "msgq"
version = "0.0.1"
description = "Code powering the comma.ai panda"
readme = "README.md"
requires-python = ">=3.11,<3.13"
license = {text = "MIT"}
authors = [{name = "comma.ai"}]
classifiers = [
"Natural Language :: English",
"Programming Language :: Python :: 3",
"Topic :: System :: Hardware",
]
dependencies = [
"setuptools", # for distutils
"Cython",
"scons",
"pre-commit",
"ruff",
"parameterized",
"coverage",
"numpy",
"pytest",
"cppcheck",
]
# https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml
[tool.ruff]
lint.select = ["E", "F", "W", "PIE", "C4", "ISC", "RUF100", "A"]
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
set -e
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
cd $DIR
PLATFORM=$(uname -s)
echo "installing dependencies"
if [[ $PLATFORM == "Darwin" ]]; then
export ZMQ=1
export HOMEBREW_NO_AUTO_UPDATE=1
brew install python3 zeromq
elif [[ $PLATFORM == "Linux" ]]; then
# for AGNOS since we clear the apt lists
if [[ ! -d /"var/lib/apt/" ]]; then
sudo apt update
fi
sudo apt-get install -y --no-install-recommends \
curl ca-certificates \
libzmq3-dev \
ocl-icd-opencl-dev opencl-headers \
python3-dev python3-pip python3-venv
else
echo "WARNING: unsupported platform. skipping apt/brew install."
fi
# catch2
if [ ! -d $DIR/msgq/catch2/ ]; then
rm -rf /tmp/catch2/ $DIR/msgq/catch2/
git clone -b v2.x --depth 1 https://github.com/catchorg/Catch2.git /tmp/catch2
pushd /tmp/catch2
mv single_include/* $DIR/msgq/
popd
fi
if ! command -v uv &>/dev/null; then
echo "'uv' is not installed. Installing 'uv'..."
curl -LsSf https://astral.sh/uv/install.sh | sh
# doesn't require sourcing on all platforms
set +e
source $HOME/.local/bin/env
set -e
fi
export UV_PROJECT_ENVIRONMENT="$DIR/.venv"
uv sync --all-extras
source "$DIR/.venv/bin/activate"
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -e
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
cd $DIR
# *** env setup ***
source ./setup.sh
# *** build ***
scons -j8
# *** lint ***
#ruff check .
#mypy python/
pre-commit run --all-files
# *** test ***
pytest
+1 -1
View File
@@ -54,7 +54,7 @@ lefthook run lint # run the linter
[`examples/joystick.py`](examples/joystick.py) allows you to control a car with a joystick.
### Project Structure
* [`opendbc/dbc/`](opendbc/dbc/) is a repository of [DBC](https://en.wikipedia.org/wiki/CAN_bus#DBC) files
* [`opendbc/dbc/`](opendbc/dbc/) is a repository of [DBC](https://en.wikipedia.org/wiki/CAN_bus#DBC_(CAN_Database_Files)) files
* [`opendbc/can/`](opendbc/can/) is a library for parsing and building CAN messages from DBC files
* [`opendbc/car/`](opendbc/car/) is a high-level library for interfacing with cars using Python
* [`opendbc/safety/`](opendbc/safety/) is the functional safety for all the cars supported by `opendbc/car/`
+1 -3
View File
@@ -1,6 +1,4 @@
Import("env")
SConscript(['opendbc/dbc/SConscript'], exports={'env': env})
SConscript(['opendbc/dbc/SConscript'])
# test files
if GetOption('extras'):
-53
View File
@@ -1,64 +1,11 @@
import os
import subprocess
import platform
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
if platform.system() == "Darwin":
arch = "Darwin"
cpppath = [
'#',
'/usr/lib/include',
]
AddOption('--minimal',
action='store_false',
dest='extras',
default=True,
help='the minimum build. no tests, tools, etc.')
AddOption('--asan',
action='store_true',
help='turn on ASAN')
# safety options
AddOption('--ubsan',
action='store_true',
help='turn on UBSan')
AddOption('--mutation',
action='store_true',
help='generate mutation-ready code')
ccflags_asan = ["-fsanitize=address", "-fno-omit-frame-pointer"] if GetOption('asan') else []
ldflags_asan = ["-fsanitize=address"] if GetOption('asan') else []
env = Environment(
ENV=os.environ,
CC='gcc',
CXX='g++',
CCFLAGS=[
"-g",
"-fPIC",
"-O2",
"-Wunused",
"-Werror",
"-Wshadow",
"-Wno-vla-cxx-extension",
"-Wno-unknown-warning-option", # for compatibility across compiler versions
] + ccflags_asan,
LDFLAGS=ldflags_asan,
LINKFLAGS=ldflags_asan,
CFLAGS="-std=gnu11",
CXXFLAGS=["-std=c++1z"],
CPPPATH=cpppath,
tools=["default", "compilation_db"]
)
if arch != "Darwin":
env.Append(CCFLAGS=["-fmax-errors=1", ])
env.CompilationDatabase('compile_commands.json')
Export('env', 'arch')
SConscript(['SConscript'])
+14 -10
View File
@@ -1,6 +1,6 @@
<!--- AUTOGENERATED FROM selfdrive/car/CARS_template.md, DO NOT EDIT. --->
# Support Information for 377 Known Cars
# Support Information for 383 Known Cars
|Make|Model|Package|Support Level|
|---|---|---|:---:|
@@ -9,6 +9,7 @@
|Acura|Integra 2023-25|All|[Community](#community)|
|Acura|MDX 2015-16|Advance Package|[Community](#community)|
|Acura|MDX 2017-20|All|[Community](#community)|
|Acura|MDX 2022-24|All|[Community](#community)|
|Acura|MDX 2025|All except Type S|[Upstream](#upstream)|
|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|[Upstream](#upstream)|
|Acura|RDX 2019-21|All|[Upstream](#upstream)|
@@ -16,6 +17,8 @@
|Acura|RLX 2017|Advance Package or Technology Package|[Community](#community)|
|Acura|TLX 2015-17|Advance Package|[Community](#community)|
|Acura|TLX 2018-20|All|[Community](#community)|
|Acura|TLX 2021|All|[Upstream](#upstream)|
|Acura|TLX 2022-23|All|[Community](#community)|
|Acura|ZDX 2024|All|[Not compatible](#can-bus-security)|
|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
@@ -85,12 +88,14 @@
|Honda|Accord 2023-25|All|[Upstream](#upstream)|
|Honda|Accord Hybrid 2018-22|All|[Upstream](#upstream)|
|Honda|Accord Hybrid 2023-25|All|[Upstream](#upstream)|
|Honda|City (Brazil only) 2023|All|[Upstream](#upstream)|
|Honda|Civic 2016-18|Honda Sensing|[Upstream](#upstream)|
|Honda|Civic 2019-21|All|[Upstream](#upstream)|
|Honda|Civic 2022-24|All|[Upstream](#upstream)|
|Honda|Civic Hatchback 2017-21|Honda Sensing|[Upstream](#upstream)|
|Honda|Civic Hatchback 2017-18|Honda Sensing|[Upstream](#upstream)|
|Honda|Civic Hatchback 2019-21|All|[Upstream](#upstream)|
|Honda|Civic Hatchback 2022-24|All|[Upstream](#upstream)|
|Honda|Civic Hatchback Hybrid 2025|All|[Upstream](#upstream)|
|Honda|Civic Hatchback Hybrid 2025-26|All|[Upstream](#upstream)|
|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|[Upstream](#upstream)|
|Honda|Civic Hybrid 2025|All|[Upstream](#upstream)|
|Honda|Clarity 2018-21|All|[Community](#community)|
@@ -106,10 +111,11 @@
|Honda|HR-V 2023-25|All|[Upstream](#upstream)|
|Honda|Insight 2019-22|All|[Upstream](#upstream)|
|Honda|Inspire 2018|All|[Upstream](#upstream)|
|Honda|N-Box 2018|All|[Upstream](#upstream)|
|Honda|Odyssey 2018-20|Honda Sensing|[Upstream](#upstream)|
|Honda|Odyssey 2021-25|All|[Community](#community)|
|Honda|Odyssey 2021-25|All|[Upstream](#upstream)|
|Honda|Passport 2019-25|All|[Upstream](#upstream)|
|Honda|Passport 2026|All|[Community](#community)|
|Honda|Passport 2026|All|[Upstream](#upstream)|
|Honda|Pilot 2016-22|Honda Sensing|[Upstream](#upstream)|
|Honda|Pilot 2023-25|All|[Upstream](#upstream)|
|Honda|Prologue 2024-25|All|[Not compatible](#can-bus-security)|
@@ -236,7 +242,7 @@
|Mazda|CX-5 2022-25|All|[Upstream](#upstream)|
|Mazda|CX-9 2016-20|All|[Dashcam mode](#dashcam)|
|Mazda|CX-9 2021-23|All|[Upstream](#upstream)|
|Nissan|Altima 2019-20|ProPILOT Assist|[Upstream](#upstream)|
|Nissan|Altima 2019-20, 2024|ProPILOT Assist|[Upstream](#upstream)|
|Nissan|Leaf 2018-23|ProPILOT Assist|[Upstream](#upstream)|
|Nissan|Rogue 2018-20|ProPILOT Assist|[Upstream](#upstream)|
|Nissan|X-Trail 2017|ProPILOT Assist|[Upstream](#upstream)|
@@ -282,7 +288,7 @@
|Tesla|Model 3 (with HW4) 2024-25|All|[Upstream](#upstream)|
|Tesla|Model X (with HW4) 2024|All|[Dashcam mode](#dashcam)|
|Tesla|Model Y (with HW3) 2020-23|All|[Upstream](#upstream)|
|Tesla|Model Y (with HW4) 2024|All|[Upstream](#upstream)|
|Tesla|Model Y (with HW4) 2024-25|All|[Upstream](#upstream)|
|Toyota|Alphard 2019-20|All|[Upstream](#upstream)|
|Toyota|Alphard Hybrid 2021|All|[Upstream](#upstream)|
|Toyota|Avalon 2016|Toyota Safety Sense P|[Upstream](#upstream)|
@@ -420,9 +426,7 @@ Supported Models' section of each make [on our wiki](https://wiki.comma.ai/).
Some notable works-in-progress:
* Honda
* 2025 Acura MDX (CAN-FD), commaai/opendbc#2129
* 2022-25 Acura RDX, commaai/opendbc#1967
* 2021-25 Honda Odyssey, commaai/opendbc#2170
* 2022-24 Acura RDX, commaai/opendbc#1967
* Camera ACC stability improvements, commaai/opendbc#2192
* Alpha longitudinal stability improvements, commaai/opendbc#2347 and commaai/opendbc#2165
+11 -2
View File
@@ -43,6 +43,12 @@ class MessageState:
counter: int = 0
counter_fail: int = 0
first_seen_nanos: int = 0
last_warning_log_nanos: int = 0
def rate_limited_log(self, last_update_nanos: int, msg: str) -> None:
if (last_update_nanos - self.last_warning_log_nanos) >= 1_000_000_000:
carlog.warning(f"CANParser: {hex(self.address)} {self.name} {msg}")
self.last_warning_log_nanos = last_update_nanos
def parse(self, nanos: int, dat: bytes) -> bool:
tmp_vals: list[float] = [0.0] * len(self.signals)
@@ -58,8 +64,10 @@ class MessageState:
tmp -= ((tmp >> (sig.size - 1)) & 0x1) * (1 << sig.size)
if not self.ignore_checksum and sig.calc_checksum is not None:
if sig.calc_checksum(self.address, sig, bytearray(dat)) != tmp:
expected_checksum = sig.calc_checksum(self.address, sig, bytearray(dat))
if tmp != expected_checksum:
checksum_failed = True
self.rate_limited_log(nanos, f"checksum failed: received {hex(tmp)}, calculated {hex(expected_checksum)}")
if not self.ignore_counter and sig.type == 1: # COUNTER
if not self.update_counter(tmp, sig.size):
@@ -69,7 +77,6 @@ class MessageState:
# must have good counter and checksum to update data
if checksum_failed or counter_failed:
carlog.warning(f"{hex(self.address)} {self.name} checks failed, {checksum_failed=} {counter_failed=}")
return False
if not self.vals:
@@ -197,8 +204,10 @@ class CANParser:
for state in self.message_states.values():
if state.counter_fail >= MAX_BAD_COUNTER:
counters_valid = False
state.rate_limited_log(self._last_update_nanos, f"counter invalid, {state.counter_fail=} {MAX_BAD_COUNTER=}")
if not state.valid(self._last_update_nanos, bus_timeout):
valid = False
state.rate_limited_log(self._last_update_nanos, "not valid (timeout or missing)")
# TODO: probably only want to increment this once per update() call
self.can_invalid_cnt = 0 if valid else min(self.can_invalid_cnt + 1, CAN_INVALID_CNT)
@@ -63,13 +63,14 @@ class TestCanChecksums:
def test_honda_checksum(self):
"""Test checksums for Honda standard and extended CAN ids"""
# TODO: refactor to use self.verify_checksum()
dbc_file = "honda_accord_2018_can_generated"
dbc_file = "honda_civic_hatchback_ex_2017_can_generated"
msgs = [("LKAS_HUD", 0), ("LKAS_HUD_A", 0)]
parser = CANParser(dbc_file, msgs, 0)
packer = CANPacker(dbc_file)
values = {
'SET_ME_X41': 0x41,
'LKAS_READY': 1,
'LKAS_STATE_CHANGE': 1,
'STEERING_REQUIRED': 1,
'SOLID_LANES': 1,
'BEEP': 0,
@@ -16,6 +16,7 @@ class TestCANDefine:
4: 'NO_TORQUE_ALERT_2',
3: 'LOW_SPEED_LOCKOUT',
2: 'NO_TORQUE_ALERT_1',
1: 'DRIVER_STEERING',
0: 'NORMAL'}
}
+1 -3
View File
@@ -47,9 +47,7 @@ Supported Models' section of each make [on our wiki](https://wiki.comma.ai/).
Some notable works-in-progress:
* Honda
* 2025 Acura MDX (CAN-FD), commaai/opendbc#2129
* 2022-25 Acura RDX, commaai/opendbc#1967
* 2021-25 Honda Odyssey, commaai/opendbc#2170
* 2022-24 Acura RDX, commaai/opendbc#1967
* Camera ACC stability improvements, commaai/opendbc#2192
* Alpha longitudinal stability improvements, commaai/opendbc#2347 and commaai/opendbc#2165
+5 -4
View File
@@ -549,14 +549,15 @@ struct CarParams {
}
struct LateralTorqueTuning {
kp @1 :Float32;
ki @2 :Float32;
friction @3 :Float32;
kf @4 :Float32;
steeringAngleDeadzoneDeg @5 :Float32;
latAccelFactor @6 :Float32;
latAccelOffset @7 :Float32;
useSteeringAngleDEPRECATED @0 :Bool;
kpDEPRECATED @1 :Float32;
kiDEPRECATED @2 :Float32;
kfDEPRECATED @4 :Float32;
kdDEPRECATED @8 : Float32;
}
struct LongitudinalPIDTuning {
@@ -564,7 +565,7 @@ struct CarParams {
kpV @1 :List(Float32);
kiBP @2 :List(Float32);
kiV @3 :List(Float32);
kf @6 :Float32;
kfDEPRECATED @6 :Float32;
deadzoneBPDEPRECATED @4 :List(Float32);
deadzoneVDEPRECATED @5 :List(Float32);
}
+4 -4
View File
@@ -82,8 +82,8 @@ def can_fingerprint(can_recv: CanRecvCallable) -> tuple[str | None, dict[int, di
# **** for use live only ****
def fingerprint(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, num_pandas: int,
cached_params: CarParamsT | None) -> tuple[str | None, dict, str, list[CarParams.CarFw], CarParams.FingerprintSource, bool]:
fixed_fingerprint = os.environ.get('FINGERPRINT', "")
cached_params: CarParamsT | None, dp_fingerprint: str = "") -> tuple[str | None, dict, str, list[CarParams.CarFw], CarParams.FingerprintSource, bool]:
fixed_fingerprint = os.environ.get('FINGERPRINT', dp_fingerprint)
skip_fw_query = os.environ.get('SKIP_FW_QUERY', False)
disable_fw_cache = os.environ.get('DISABLE_FW_CACHE', False)
ecu_rx_addrs = set()
@@ -149,8 +149,8 @@ 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, num_pandas: int = 1, dp_params: int = 0, cached_params: CarParamsT | None = None):
candidate, fingerprints, vin, car_fw, source, exact_match = fingerprint(can_recv, can_send, set_obd_multiplexing, num_pandas, cached_params)
is_release: bool, num_pandas: int = 1, dp_params: int = 0, cached_params: CarParamsT | None = None, dp_fingerprint: str = ""):
candidate, fingerprints, vin, car_fw, source, exact_match = fingerprint(can_recv, can_send, set_obd_multiplexing, num_pandas, cached_params, dp_fingerprint=dp_fingerprint)
if candidate is None:
carlog.error({"event": "car doesn't match any fingerprints", "fingerprints": repr(fingerprints)})
@@ -351,6 +351,7 @@ FW_VERSIONS = {
b'68402707AB',
b'68402708AB',
b'68402714AB',
b'68402736AB',
b'68402971AD',
b'68454144AD',
b'68454145AB',
@@ -379,6 +380,7 @@ FW_VERSIONS = {
b'68417279AA',
b'68417280AA',
b'68417281AA',
b'68417283AA',
b'68453431AA',
b'68453433AA',
b'68453435AA',
@@ -391,6 +393,7 @@ FW_VERSIONS = {
b'05035674AB ',
b'68412635AE ',
b'68412635AG ',
b'68412635AH ',
b'68412660AD ',
b'68412660AF ',
b'68422860AB',
@@ -404,6 +407,7 @@ FW_VERSIONS = {
(Ecu.transmission, 0x7e1, None): [
b'05035707AA',
b'68419672AC',
b'68419675AC',
b'68419678AB',
b'68423905AB',
b'68449258AC',
@@ -2,17 +2,43 @@ class FirstOrderFilter:
# first order filter
def __init__(self, x0, rc, dt, initialized=True):
self.x = x0
self.dt = dt
self._dt = dt
self.update_alpha(rc)
self.initialized = initialized
def update_dt(self, dt):
self._dt = dt
self.update_alpha(self._rc)
def update_alpha(self, rc):
self.alpha = self.dt / (rc + self.dt)
self._rc = rc
self._alpha = self._dt / (self._rc + self._dt)
def update(self, x):
if self.initialized:
self.x = (1. - self.alpha) * self.x + self.alpha * x
self.x = (1. - self._alpha) * self.x + self._alpha * x
else:
self.initialized = True
self.x = x
return self.x
class HighPassFilter:
# technically a band-pass filter
def __init__(self, x0, rc1, rc2, dt, initialized=True):
self.x = x0
self._f1 = FirstOrderFilter(x0, rc1, dt, initialized)
self._f2 = FirstOrderFilter(x0, rc2, dt, initialized)
assert rc2 > rc1, "rc2 must be greater than rc1"
def update_dt(self, dt):
self._f1.update_dt(dt)
self._f2.update_dt(dt)
def update_alpha(self, rc1, rc2):
self._f1.update_alpha(rc1)
self._f2.update_alpha(rc2)
def update(self, x):
self.x = self._f1.update(x) - self._f2.update(x)
return self.x
+2 -9
View File
@@ -143,8 +143,8 @@ class CarHarness(EnumBase):
ford_q3 = BaseCarHarness("Ford Q3 connector")
ford_q4 = BaseCarHarness("Ford Q4 connector", parts=[Accessory.harness_box, Accessory.comma_power, Cable.long_obdc_cable, Cable.usbc_coupler])
rivian = BaseCarHarness("Rivian A connector", parts=[Accessory.harness_box, Accessory.comma_power, Cable.long_obdc_cable, Cable.usbc_coupler])
tesla_a = BaseCarHarness("Tesla A connector", parts=[Accessory.harness_box, Accessory.comma_power, Cable.long_obdc_cable, Cable.usbc_coupler])
tesla_b = BaseCarHarness("Tesla B connector", parts=[Accessory.harness_box, Accessory.comma_power, Cable.long_obdc_cable, Cable.usbc_coupler])
tesla_a = BaseCarHarness("Tesla A connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler])
tesla_b = BaseCarHarness("Tesla B connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler])
psa_a = BaseCarHarness("PSA A connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler])
@@ -152,12 +152,6 @@ class Device(EnumBase):
threex = BasePart("comma 3X", parts=[Mount.mount, Cable.right_angle_obd_c_cable_1_5ft])
# variant of comma 3X with angled mounts
threex_angled_mount = BasePart("comma 3X", parts=[Mount.angled_mount_8_degrees, Cable.right_angle_obd_c_cable_1_5ft])
red_panda = BasePart("red panda")
class Kit(EnumBase):
red_panda_kit = BasePart("CAN FD panda kit", parts=[Device.red_panda, Accessory.harness_box,
Cable.usb_a_2_a_cable, Cable.usbc_otg_cable, Cable.obd_c_cable_1_5ft])
class PartType(Enum):
@@ -165,7 +159,6 @@ class PartType(Enum):
cable = Cable
connector = CarHarness
device = Device
kit = Kit
mount = Mount
tool = Tool
+2 -2
View File
@@ -40,15 +40,15 @@ class CAR(Platforms):
CommunityCarDocs("Acura Integra 2023-25", "All"),
CommunityCarDocs("Acura MDX 2015-16", "Advance Package"),
CommunityCarDocs("Acura MDX 2017-20", "All"),
CommunityCarDocs("Acura MDX 2022-24", "All"),
CommunityCarDocs("Acura RDX 2022-25", "All"),
CommunityCarDocs("Acura RLX 2017", "Advance Package or Technology Package"),
CommunityCarDocs("Acura TLX 2015-17", "Advance Package"),
CommunityCarDocs("Acura TLX 2018-20", "All"),
CommunityCarDocs("Acura TLX 2022-23", "All"),
GMSecurityCarDocs("Acura ZDX 2024", "All"),
CommunityCarDocs("Honda Accord 2016-17", "Honda Sensing"),
CommunityCarDocs("Honda Clarity 2018-21", "All"),
CommunityCarDocs("Honda Odyssey 2021-25", "All"),
CommunityCarDocs("Honda Passport 2026", "All"),
GMSecurityCarDocs("Honda Prologue 2024-25", "All"),
],
)
+34 -34
View File
@@ -1,15 +1,14 @@
#!/usr/bin/env python3
import os
from math import fabs, exp
import numpy as np
from opendbc.car import get_safety_config, structs
from opendbc.car.common.basedir import BASEDIR
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.gm.carcontroller import CarController
from opendbc.car.gm.carstate import CarState
from opendbc.car.gm.radar_interface import RadarInterface, RADAR_HEADER_MSG, CAMERA_DATA_HEADER_MSG
from opendbc.car.gm.values import CAR, CarControllerParams, EV_CAR, CAMERA_ACC_CAR, SDGM_CAR, ALT_ACCS, CanBus, GMSafetyFlags
from opendbc.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, LatControlInputs, NanoFFModel
from opendbc.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, LateralAccelFromTorqueCallbackType
TransmissionType = structs.CarParams.TransmissionType
NetworkLocation = structs.CarParams.NetworkLocation
@@ -20,8 +19,6 @@ NON_LINEAR_TORQUE_PARAMS = {
CAR.CHEVROLET_SILVERADO: [3.29974374, 1.0, 0.25571356, 0.0465122]
}
NEURAL_PARAMS_PATH = os.path.join(BASEDIR, 'torque_data/neural_ff_weights.json')
class CarInterface(CarInterfaceBase):
CarState = CarState
@@ -45,42 +42,45 @@ class CarInterface(CarInterfaceBase):
else:
return CarInterfaceBase.get_steer_feedforward_default
def torque_from_lateral_accel_siglin(self, latcontrol_inputs: LatControlInputs, torque_params: structs.CarParams.LateralTorqueTuning,
gravity_adjusted: bool) -> float:
def sig(val):
# https://timvieira.github.io/blog/post/2014/02/11/exp-normalize-trick
if val >= 0:
return 1 / (1 + exp(-val)) - 0.5
else:
z = exp(val)
return z / (1 + z) - 0.5
def get_lataccel_torque_siglin(self) -> float:
# The "lat_accel vs torque" relationship is assumed to be the sum of "sigmoid + linear" curves
# An important thing to consider is that the slope at 0 should be > 0 (ideally >1)
# This has big effect on the stability about 0 (noise when going straight)
# ToDo: To generalize to other GMs, explore tanh function as the nonlinear
non_linear_torque_params = NON_LINEAR_TORQUE_PARAMS.get(self.CP.carFingerprint)
assert non_linear_torque_params, "The params are not defined"
a, b, c, _ = non_linear_torque_params
steer_torque = (sig(latcontrol_inputs.lateral_acceleration * a) * b) + (latcontrol_inputs.lateral_acceleration * c)
return float(steer_torque)
def torque_from_lateral_accel_siglin_func(lateral_acceleration: float) -> float:
# The "lat_accel vs torque" relationship is assumed to be the sum of "sigmoid + linear" curves
# An important thing to consider is that the slope at 0 should be > 0 (ideally >1)
# This has big effect on the stability about 0 (noise when going straight)
non_linear_torque_params = NON_LINEAR_TORQUE_PARAMS.get(self.CP.carFingerprint)
assert non_linear_torque_params, "The params are not defined"
a, b, c, _ = non_linear_torque_params
sig_input = a * lateral_acceleration
sig = np.sign(sig_input) * (1 / (1 + exp(-fabs(sig_input))) - 0.5)
steer_torque = (sig * b) + (lateral_acceleration * c)
return float(steer_torque)
def torque_from_lateral_accel_neural(self, latcontrol_inputs: LatControlInputs, torque_params: structs.CarParams.LateralTorqueTuning,
gravity_adjusted: bool) -> float:
inputs = list(latcontrol_inputs)
if gravity_adjusted:
inputs[0] += inputs[1]
return float(self.neural_ff_model.predict(inputs))
lataccel_values = np.arange(-5.0, 5.0, 0.01)
torque_values = [torque_from_lateral_accel_siglin_func(x) for x in lataccel_values]
assert min(torque_values) < -1 and max(torque_values) > 1, "The torque values should cover the range [-1, 1]"
return torque_values, lataccel_values
def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType:
if self.CP.carFingerprint == CAR.CHEVROLET_BOLT_EUV:
self.neural_ff_model = NanoFFModel(NEURAL_PARAMS_PATH, self.CP.carFingerprint)
return self.torque_from_lateral_accel_neural
elif self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS:
return self.torque_from_lateral_accel_siglin
if self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS:
torque_values, lataccel_values = self.get_lataccel_torque_siglin()
def torque_from_lateral_accel_siglin(lateral_acceleration: float, torque_params: structs.CarParams.LateralTorqueTuning):
return np.interp(lateral_acceleration, lataccel_values, torque_values)
return torque_from_lateral_accel_siglin
else:
return self.torque_from_lateral_accel_linear
def lateral_accel_from_torque(self) -> LateralAccelFromTorqueCallbackType:
if self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS:
torque_values, lataccel_values = self.get_lataccel_torque_siglin()
def lateral_accel_from_torque_siglin(torque: float, torque_params: structs.CarParams.LateralTorqueTuning):
return np.interp(torque, torque_values, lataccel_values)
return lateral_accel_from_torque_siglin
else:
return self.lateral_accel_from_torque_linear
@staticmethod
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, dp_params, docs) -> structs.CarParams:
ret.brand = "gm"
+31 -28
View File
@@ -1,11 +1,10 @@
import numpy as np
from collections import namedtuple
from opendbc.can import CANPacker
from opendbc.car import Bus, DT_CTRL, rate_limit, make_tester_present_msg, structs
from opendbc.car.honda import hondacan
from opendbc.car.honda.values import CruiseButtons, VISUAL_HUD, HONDA_BOSCH, HONDA_BOSCH_CANFD, HONDA_BOSCH_RADARLESS, \
HONDA_NIDEC_ALT_PCM_ACCEL, CarControllerParams
from opendbc.car.honda.values import CAR, CruiseButtons, HONDA_BOSCH, HONDA_BOSCH_CANFD, HONDA_BOSCH_RADARLESS, \
HONDA_BOSCH_TJA_CONTROL, HONDA_NIDEC_ALT_PCM_ACCEL, CarControllerParams
from opendbc.car.interfaces import CarControllerBase
VisualAlert = structs.CarControl.HUDControl.VisualAlert
@@ -76,25 +75,17 @@ def brake_pump_hysteresis(apply_brake, apply_brake_last, last_pump_ts, ts):
def process_hud_alert(hud_alert):
# initialize to no alert
fcw_display = 0
steer_required = 0
acc_alert = 0
alert_fcw = False
alert_steer_required = False
# priority is: FCW, steer required, all others
# Make sure FCW is prioritized over steering required
# TODO: implement separate available LDW alert
if hud_alert == VisualAlert.fcw:
fcw_display = VISUAL_HUD[hud_alert.raw]
alert_fcw = True
elif hud_alert in (VisualAlert.steerRequired, VisualAlert.ldw):
steer_required = VISUAL_HUD[hud_alert.raw]
else:
acc_alert = VISUAL_HUD[hud_alert.raw]
alert_steer_required = True
return fcw_display, steer_required, acc_alert
HUDData = namedtuple("HUDData",
["pcm_accel", "v_cruise", "lead_visible",
"lanes_visible", "fcw", "acc_alert", "steer_required", "lead_distance_bars"])
return alert_fcw, alert_steer_required
class CarController(CarControllerBase):
@@ -103,6 +94,7 @@ class CarController(CarControllerBase):
self.packer = CANPacker(dbc_names[Bus.pt])
self.params = CarControllerParams(CP)
self.CAN = hondacan.CanBus(CP)
self.tja_control = CP.carFingerprint in HONDA_BOSCH_TJA_CONTROL
self.braking = False
self.brake_steady = 0.
@@ -143,7 +135,7 @@ class CarController(CarControllerBase):
self.brake_last = rate_limit(pre_limit_brake, self.brake_last, -2., DT_CTRL)
# vehicle hud display, wait for one update from 10Hz 0x304 msg
fcw_display, steer_required, acc_alert = process_hud_alert(hud_control.visualAlert)
alert_fcw, alert_steer_required = process_hud_alert(hud_control.visualAlert)
# **** process the car messages ****
@@ -160,7 +152,7 @@ class CarController(CarControllerBase):
can_sends.append(make_tester_present_msg(0x18DAB0F1, 1, suppress_response=True))
# Send steering command.
can_sends.append(hondacan.create_steering_control(self.packer, self.CAN, apply_torque, CC.latActive))
can_sends.append(hondacan.create_steering_control(self.packer, self.CAN, apply_torque, CC.latActive, self.tja_control))
# wind brake from air resistance decel at high speed
wind_brake = np.interp(CS.out.vEgo, [0.0, 2.3, 35.0], [0.001, 0.002, 0.15])
@@ -220,21 +212,32 @@ class CarController(CarControllerBase):
pcm_override = True
can_sends.append(hondacan.create_brake_command(self.packer, self.CAN, apply_brake, pump_on,
pcm_override, pcm_cancel_cmd, fcw_display,
pcm_override, pcm_cancel_cmd, alert_fcw,
self.CP.carFingerprint, CS.stock_brake))
self.apply_brake_last = apply_brake
self.brake = apply_brake / self.params.NIDEC_BRAKE_MAX
# Send dashboard UI commands.
# On Nidec, this controls longitudinal positive acceleration
if self.frame % 10 == 0:
hud = HUDData(int(pcm_accel), int(round(hud_v_cruise)), hud_control.leadVisible,
hud_control.lanesVisible, fcw_display, acc_alert, steer_required, hud_control.leadDistanceBars)
can_sends.extend(hondacan.create_ui_commands(self.packer, self.CAN, self.CP, CC.enabled, pcm_speed, hud, CS.is_metric, CS.acc_hud, CS.lkas_hud))
if self.CP.openpilotLongitudinalControl:
# 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))
if self.CP.openpilotLongitudinalControl and self.CP.carFingerprint not in HONDA_BOSCH:
self.speed = pcm_speed
self.gas = pcm_accel / self.params.NIDEC_GAS_MAX
steering_available = CS.out.cruiseState.available and CS.out.vEgo > self.CP.minSteerSpeed
reduced_steering = CS.out.steeringPressed
can_sends.extend(hondacan.create_lkas_hud(self.packer, self.CAN.lkas, self.CP, hud_control, CC.latActive,
steering_available, reduced_steering, alert_steer_required, CS.lkas_hud))
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):
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))
if self.CP.carFingerprint not in HONDA_BOSCH:
self.speed = pcm_speed
self.gas = pcm_accel / self.params.NIDEC_GAS_MAX
new_actuators = actuators.as_builder()
new_actuators.speed = self.speed
+36 -22
View File
@@ -5,9 +5,9 @@ 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.honda.hondacan import CanBus
from opendbc.car.honda.values import CAR, DBC, STEER_THRESHOLD, HONDA_BOSCH, HONDA_BOSCH_CANFD, \
from opendbc.car.honda.values import CAR, DBC, STEER_THRESHOLD, HONDA_BOSCH, HONDA_BOSCH_ALT_RADAR, HONDA_BOSCH_CANFD, \
HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS, \
HondaFlags, CruiseButtons, CruiseSettings, GearShifter
HondaFlags, CruiseButtons, CruiseSettings, GearShifter, CarControllerParams
from opendbc.car.interfaces import CarStateBase
TransmissionType = structs.CarParams.TransmissionType
@@ -29,16 +29,19 @@ class CarState(CarStateBase):
self.gearbox_msg = "GEARBOX_CVT"
self.shifter_values = can_define.dv[self.gearbox_msg]["GEAR_SHIFTER"]
self.main_on_sig_msg = "SCM_FEEDBACK"
self.car_state_scm_msg = "SCM_FEEDBACK"
if CP.carFingerprint in HONDA_NIDEC_ALT_SCM_MESSAGES:
self.main_on_sig_msg = "SCM_BUTTONS"
self.car_state_scm_msg = "SCM_BUTTONS"
self.brake_error_msg = "HYBRID_BRAKE_ERROR" if CP.flags & HondaFlags.HYBRID else "STANDSTILL"
self.steer_status_values = defaultdict(lambda: "UNKNOWN", can_define.dv["STEER_STATUS"]["STEER_STATUS"])
self.brake_switch_prev = False
self.brake_switch_active = False
self.low_speed_alert = False
self.dynamic_v_cruise_units = self.CP.carFingerprint in (HONDA_BOSCH_RADARLESS | HONDA_BOSCH_CANFD)
self.dynamic_v_cruise_units = self.CP.carFingerprint in (HONDA_BOSCH_RADARLESS | HONDA_BOSCH_ALT_RADAR | HONDA_BOSCH_CANFD)
self.cruise_setting = 0
self.v_cruise_pcm_prev = 0
@@ -69,8 +72,13 @@ class CarState(CarStateBase):
self.v_cruise_factor = CV.MPH_TO_MS if self.dynamic_v_cruise_units and not self.is_metric else CV.KPH_TO_MS
# ******************* parse out can *******************
# blend in transmission speed at low speed, since it has more low speed accuracy
# STANDSTILL->WHEELS_MOVING bit can be noisy around zero, so use XMISSION_SPEED
# panda checks if the signal is non-zero
v_wheel = sum([cp.vl["WHEEL_SPEEDS"][f"WHEEL_SPEED_{s}"] for s in ("FL", "FR", "RL", "RR")]) / 4.0 * CV.KPH_TO_MS
v_weight = float(np.interp(v_wheel, v_weight_bp, v_weight_v))
ret.vEgoRaw = (1. - v_weight) * cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] * CV.KPH_TO_MS * self.CP.wheelSpeedFactor + v_weight * v_wheel
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
ret.standstill = cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] < 1e-5
# doorOpen is true if we can find any door open, but signal locations vary, and we may only see the driver's door
@@ -87,17 +95,31 @@ 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", "TMP_FAULT")
# LOW_SPEED_LOCKOUT is not worth a warning
# NO_TORQUE_ALERT_2 can be caused by bump or steering nudge from driver
ret.steerFaultTemporary = steer_status not in ("NORMAL", "LOW_SPEED_LOCKOUT", "NO_TORQUE_ALERT_2")
if self.CP.carFingerprint in HONDA_BOSCH_ALT_RADAR:
# TODO: See if this logic works for all other Honda
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
else:
# LOW_SPEED_LOCKOUT is not worth a warning
# NO_TORQUE_ALERT_2 can be caused by bump or steering nudge from driver
# FIXME: the stock camera stops steering on NO_TORQUE_ALERT_1
ret.steerFaultTemporary = steer_status not in ("NORMAL", "LOW_SPEED_LOCKOUT", "NO_TORQUE_ALERT_2")
# All Honda EPS cut off slightly above standstill, some much higher
# Don't alert in the near-standstill range, but alert for per-vehicle configured minimums above that
if CarControllerParams.STEER_GLOBAL_MIN_SPEED < ret.vEgo < (self.CP.minSteerSpeed + 0.5):
self.low_speed_alert = True
elif ret.vEgo > (self.CP.minSteerSpeed + 1.):
# TODO: better handle delayed steering enablement on ALT_RADAR cars
self.low_speed_alert = False
ret.lowSpeedAlert = self.low_speed_alert
if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS:
ret.accFaulted = bool(cp.vl["CRUISE_FAULT_STATUS"]["CRUISE_FAULT"])
else:
# On some cars, these two signals are always 1, this flag is masking a bug in release
# FIXME: find and set the ACC faulted signals on more platforms
if self.CP.openpilotLongitudinalControl:
ret.accFaulted = bool(cp.vl["STANDSTILL"]["BRAKE_ERROR_1"] or cp.vl["STANDSTILL"]["BRAKE_ERROR_2"])
ret.accFaulted = bool(cp.vl[self.brake_error_msg]["BRAKE_ERROR_1"] or cp.vl[self.brake_error_msg]["BRAKE_ERROR_2"])
# Log non-critical stock ACC/LKAS faults if Nidec (camera)
if self.CP.carFingerprint not in HONDA_BOSCH:
@@ -105,12 +127,6 @@ class CarState(CarStateBase):
ret.espDisabled = cp.vl["VSA_STATUS"]["ESP_DISABLED"] != 0
# blend in transmission speed at low speed, since it has more low speed accuracy
v_wheel = sum([cp.vl["WHEEL_SPEEDS"][f"WHEEL_SPEED_{s}"] for s in ("FL", "FR", "RL", "RR")]) / 4.0 * CV.KPH_TO_MS
v_weight = float(np.interp(v_wheel, v_weight_bp, v_weight_v))
ret.vEgoRaw = (1. - v_weight) * cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] * CV.KPH_TO_MS * self.CP.wheelSpeedFactor + v_weight * v_wheel
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
self.dash_speed_seen = self.dash_speed_seen or cp.vl["CAR_SPEED"]["ROUGH_CAR_SPEED_2"] > 1e-3
if self.dash_speed_seen:
conversion = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS
@@ -122,9 +138,7 @@ class CarState(CarStateBase):
ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_stalk(
250, cp.vl["SCM_FEEDBACK"]["LEFT_BLINKER"], cp.vl["SCM_FEEDBACK"]["RIGHT_BLINKER"])
ret.brakeHoldActive = cp.vl["VSA_STATUS"]["BRAKE_HOLD_ACTIVE"] == 1
if self.CP.flags & HondaFlags.HAS_EPB:
ret.parkingBrake = cp.vl["EPB_STATUS"]["EPB_STATE"] != 0
ret.parkingBrake = bool(cp.vl[self.car_state_scm_msg]["PARKING_BRAKE_ON"])
if self.CP.transmissionType == TransmissionType.manual:
ret.gearShifter = GearShifter.reverse if bool(cp.vl["SCM_FEEDBACK"]["REVERSE_LIGHT"]) else GearShifter.drive
@@ -171,7 +185,7 @@ class CarState(CarStateBase):
ret.brake = cp.vl["VSA_STATUS"]["USER_BRAKE"]
ret.cruiseState.enabled = cp.vl["POWERTRAIN_DATA"]["ACC_STATUS"] != 0
ret.cruiseState.available = bool(cp.vl[self.main_on_sig_msg]["MAIN_ON"])
ret.cruiseState.available = bool(cp.vl[self.car_state_scm_msg]["MAIN_ON"])
# Gets rid of Pedal Grinding noise when brake is pressed at slow speeds for some models
if self.CP.carFingerprint in (CAR.HONDA_PILOT, CAR.HONDA_RIDGELINE):
+99 -9
View File
@@ -11,6 +11,23 @@ Ecu = CarParams.Ecu
FW_VERSIONS = {
CAR.HONDA_NBOX_2G: {
(Ecu.vsa, 0x18da28f1, None): [
b'57114-TTA-J030\x00\x00',
],
(Ecu.eps, 0x18da30f1, None): [
b'39990-TTA-J040\x00\x00',
],
(Ecu.srs, 0x18da53f1, None): [
b'77959-TTA-N930\x00\x00',
],
(Ecu.fwdRadar, 0x18dab0f1, None): [
b'36802-TTA-J070\x00\x00',
],
(Ecu.fwdCamera, 0x18dab5f1, None): [
b'38897-TTA-J010\x00\x00',
],
},
CAR.HONDA_ACCORD: {
(Ecu.shiftByWire, 0x18da0bf1, None): [
b'54008-TVC-A910\x00\x00',
@@ -314,6 +331,7 @@ FW_VERSIONS = {
(Ecu.vsa, 0x18da28f1, None): [
b'57114-T1W-A230\x00\x00',
b'57114-T1W-A240\x00\x00',
b'57114-TFF-A930\x00\x00',
b'57114-TFF-A940\x00\x00',
],
(Ecu.srs, 0x18da53f1, None): [
@@ -559,6 +577,46 @@ FW_VERSIONS = {
b'54008-THR-A020\x00\x00',
],
},
CAR.HONDA_ODYSSEY_5G_MMR: {
(Ecu.vsa, 0x18da28f1, None): [
b'57114-THR-A240\x00\x00',
b'57114-THR-A520\x00\x00',
],
(Ecu.fwdRadar, 0x18dab0f1, None): [
b'36802-THR-A220\x00\x00',
],
(Ecu.fwdCamera, 0x18dab5f1, None): [
b'36161-THR-A220\x00\x00',
b'36161-THR-A230\x00\x00',
],
(Ecu.shiftByWire, 0x18da0bf1, None): [
b'54008-THR-A310\x00\x00',
],
(Ecu.transmission, 0x18da1ef1, None): [
b'28102-5MX-A100\x00\x00',
b'28102-5MX-A200\x00\x00',
b'28102-5MX-A410\x00\x00',
],
(Ecu.srs, 0x18da53f1, None): [
b'77959-THR-A220\x00\x00',
b'77959-THR-A230\x00\x00',
b'77959-THR-A320\x00\x00',
],
(Ecu.electricBrakeBooster, 0x18da2bf1, None): [
b'46114-THR-A530\x00\x00',
b'46114-THR-A540\x00\x00',
b'46114-THR-A720\x00\x00',
],
(Ecu.gateway, 0x18daeff1, None): [
b'38897-THR-A130\x00\x00',
b'38897-THR-A320\x00\x00',
b'38897-THR-A410\x00\x00',
],
(Ecu.eps, 0x18da30f1, None): [
b'39990-THR-A050\x00\x00',
b'39990-THR-A110\x00\x00',
],
},
CAR.HONDA_PILOT: {
(Ecu.shiftByWire, 0x18da0bf1, None): [
b'54008-TG7-A520\x00\x00',
@@ -581,6 +639,7 @@ FW_VERSIONS = {
b'28101-5EZ-A600\x00\x00',
b'28101-5EZ-A700\x00\x00',
b'28103-5EY-A110\x00\x00',
b'28103-5EZ-A010\x00\x00',
],
(Ecu.gateway, 0x18daeff1, None): [
b'38897-TG7-A030\x00\x00',
@@ -662,20 +721,16 @@ FW_VERSIONS = {
(Ecu.vsa, 0x18da28f1, None): [
b'57114-TJB-A030\x00\x00',
b'57114-TJB-A040\x00\x00',
b'57114-TJB-A120\x00\x00',
],
(Ecu.fwdRadar, 0x18dab0f1, None): [
b'36802-TJB-A040\x00\x00',
b'36802-TJB-A050\x00\x00',
b'36802-TJB-A540\x00\x00',
],
(Ecu.fwdCamera, 0x18dab5f1, None): [
b'36161-TJB-A040\x00\x00',
b'36161-TJB-A530\x00\x00',
],
(Ecu.shiftByWire, 0x18da0bf1, None): [
b'54008-TJB-A520\x00\x00',
b'54008-TJB-A530\x00\x00',
],
(Ecu.transmission, 0x18da1ef1, None): [
b'28102-5YK-A610\x00\x00',
@@ -683,32 +738,27 @@ FW_VERSIONS = {
b'28102-5YK-A630\x00\x00',
b'28102-5YK-A700\x00\x00',
b'28102-5YK-A711\x00\x00',
b'28102-5YK-A800\x00\x00',
b'28102-5YL-A620\x00\x00',
b'28102-5YL-A700\x00\x00',
b'28102-5YL-A711\x00\x00',
],
(Ecu.srs, 0x18da53f1, None): [
b'77959-TJB-A040\x00\x00',
b'77959-TJB-A120\x00\x00',
b'77959-TJB-A210\x00\x00',
],
(Ecu.electricBrakeBooster, 0x18da2bf1, None): [
b'46114-TJB-A040\x00\x00',
b'46114-TJB-A050\x00\x00',
b'46114-TJB-A060\x00\x00',
b'46114-TJB-A120\x00\x00',
],
(Ecu.gateway, 0x18daeff1, None): [
b'38897-TJB-A040\x00\x00',
b'38897-TJB-A110\x00\x00',
b'38897-TJB-A120\x00\x00',
b'38897-TJB-A220\x00\x00',
],
(Ecu.eps, 0x18da30f1, None): [
b'39990-TJB-A030\x00\x00',
b'39990-TJB-A040\x00\x00',
b'39990-TJB-A070\x00\x00',
b'39990-TJB-A130\x00\x00',
],
},
@@ -833,6 +883,7 @@ FW_VERSIONS = {
],
(Ecu.fwdRadar, 0x18dab0f1, None): [
b'36161-TV9-A140\x00\x00',
b'36161-TV9-C140\x00\x00',
b'36161-TX6-A030\x00\x00',
],
(Ecu.srs, 0x18da53f1, None): [
@@ -959,10 +1010,13 @@ FW_VERSIONS = {
},
CAR.HONDA_CRV_6G: {
(Ecu.fwdRadar, 0x18dab0f1, None): [
b'8S302-3A0-A060\x00\x00',
b'8S302-3C0-Q050\x00\x00',
b'8S302-3D4-A050\x00\x00',
],
(Ecu.fwdCamera, 0x18dab5f1, None): [
b'8S102-3A0-A090\x00\x00',
b'8S102-3A0-A110\x00\x00',
b'8S102-3C0-Q060\x00\x00',
b'8S102-3D4-A060\x00\x00',
b'8S102-3D4-A070\x00\x00',
@@ -970,4 +1024,40 @@ FW_VERSIONS = {
b'8S102-3D4-A090\x00\x00',
],
},
CAR.HONDA_CITY_7G: {
(Ecu.eps, 0x18da30f1, None): [
b'39990-T14-B030\x00\x00',
],
(Ecu.gateway, 0x18daeff1, None): [
b'38897-T14-M110\x00\x00',
],
(Ecu.srs, 0x18da53f1, None): [
b'77959-T00-B830\x00\x00',
],
(Ecu.fwdRadar, 0x18dab0f1, None): [
b'36161-T14-P050\x00\x00',
],
(Ecu.vsa, 0x18da28f1, None): [
b'57114-T14-B030\x00\x00',
],
(Ecu.transmission, 0x18da1ef1, None): [
b'28101-63B-M420\x00\x00',
],
},
CAR.HONDA_PASSPORT_4G: {
(Ecu.fwdRadar, 0x18dab0f1, None): [
b'8S302-3BM-A020\x00\x00',
],
(Ecu.fwdCamera, 0x18dab5f1, None): [
b'8S102-3BM-A020\x00\x00',
],
},
CAR.ACURA_TLX_2G: {
(Ecu.fwdRadar, 0x18dab0f1, None): [
b'36802-TGV-A060\x00\x00',
],
(Ecu.fwdCamera, 0x18dab5f1, None): [
b'36161-TGV-A030\x00\x00',
],
},
}
+67 -50
View File
@@ -1,6 +1,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_RADARLESS, HONDA_BOSCH_CANFD, CAR, CarControllerParams
from opendbc.car.honda.values import (HondaFlags, HONDA_BOSCH, HONDA_BOSCH_ALT_RADAR, HONDA_BOSCH_RADARLESS,
HONDA_BOSCH_CANFD, CarControllerParams)
# CAN bus layout with relay
# 0 = ACC-CAN - radar side
@@ -113,11 +114,15 @@ def create_acc_commands(packer, CAN, enabled, active, accel, gas, stopping_count
return commands
def create_steering_control(packer, CAN, apply_torque, lkas_active):
def create_steering_control(packer, CAN, apply_torque, lkas_active, tja_control):
values = {
"STEER_TORQUE": apply_torque if lkas_active else 0,
"STEER_TORQUE_REQUEST": lkas_active,
}
if tja_control:
values["STEER_DOWN_TO_ZERO"] = lkas_active
return packer.make_can_msg("STEERING_CONTROL", CAN.lkas, values)
@@ -131,47 +136,49 @@ def create_bosch_supplemental_1(packer, CAN):
return packer.make_can_msg("BOSCH_SUPPLEMENTAL_1", CAN.lkas, values)
def create_ui_commands(packer, CAN, CP, enabled, pcm_speed, hud, is_metric, acc_hud, lkas_hud):
def create_acc_hud(packer, bus, CP, enabled, pcm_speed, pcm_accel, hud_control, hud_v_cruise, is_metric, acc_hud):
acc_hud_values = {
'CRUISE_SPEED': hud_v_cruise,
'ENABLE_MINI_CAR': 1 if enabled else 0,
# only moves the lead car without ACC_ON
'HUD_DISTANCE': hud_control.leadDistanceBars, # wraps to 0 at 4 bars
'IMPERIAL_UNIT': int(not is_metric),
'HUD_LEAD': 2 if enabled and hud_control.leadVisible else 1 if enabled else 0,
'SET_ME_X01_2': 1,
}
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
else:
# Shows the distance bars, TODO: stock camera shows updates temporarily while disabled
acc_hud_values['ACC_ON'] = int(enabled)
acc_hud_values['PCM_SPEED'] = pcm_speed * CV.MS_TO_KPH
acc_hud_values['PCM_GAS'] = pcm_accel
acc_hud_values['SET_ME_X01'] = 1
acc_hud_values['FCM_OFF'] = acc_hud['FCM_OFF']
acc_hud_values['FCM_OFF_2'] = acc_hud['FCM_OFF_2']
acc_hud_values['FCM_PROBLEM'] = acc_hud['FCM_PROBLEM']
acc_hud_values['ICONS'] = acc_hud['ICONS']
return packer.make_can_msg("ACC_HUD", bus, acc_hud_values)
def create_lkas_hud(packer, bus, CP, hud_control, lat_active, steering_available, reduced_steering, alert_steer_required, lkas_hud):
commands = []
radar_disabled = CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) and CP.openpilotLongitudinalControl
if CP.openpilotLongitudinalControl:
acc_hud_values = {
'CRUISE_SPEED': hud.v_cruise,
'ENABLE_MINI_CAR': 1 if enabled else 0,
# only moves the lead car without ACC_ON
'HUD_DISTANCE': hud.lead_distance_bars, # wraps to 0 at 4 bars
'IMPERIAL_UNIT': int(not is_metric),
'HUD_LEAD': 2 if enabled and hud.lead_visible else 1 if enabled else 0,
'SET_ME_X01_2': 1,
}
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
else:
# Shows the distance bars, TODO: stock camera shows updates temporarily while disabled
acc_hud_values['ACC_ON'] = int(enabled)
acc_hud_values['PCM_SPEED'] = pcm_speed * CV.MS_TO_KPH
acc_hud_values['PCM_GAS'] = hud.pcm_accel
acc_hud_values['SET_ME_X01'] = 1
acc_hud_values['FCM_OFF'] = acc_hud['FCM_OFF']
acc_hud_values['FCM_OFF_2'] = acc_hud['FCM_OFF_2']
acc_hud_values['FCM_PROBLEM'] = acc_hud['FCM_PROBLEM']
acc_hud_values['ICONS'] = acc_hud['ICONS']
commands.append(packer.make_can_msg("ACC_HUD", CAN.pt, acc_hud_values))
lkas_hud_values = {
'SET_ME_X41': 0x41,
'STEERING_REQUIRED': hud.steer_required,
'SOLID_LANES': hud.lanes_visible,
'LKAS_READY': 1,
'LKAS_STATE_CHANGE': 1,
'STEERING_REQUIRED': alert_steer_required,
'SOLID_LANES': hud_control.lanesVisible,
'BEEP': 0,
}
if CP.carFingerprint in (HONDA_BOSCH_RADARLESS | HONDA_BOSCH_CANFD):
lkas_hud_values['LANE_LINES'] = 3
lkas_hud_values['DASHED_LANES'] = hud.lanes_visible
lkas_hud_values['DASHED_LANES'] = hud_control.lanesVisible
# car likely needs to see LKAS_PROBLEM fall within a specific time frame, so forward from camera
# TODO: needed for Bosch CAN FD?
@@ -179,28 +186,38 @@ def create_ui_commands(packer, CAN, CP, enabled, pcm_speed, hud, is_metric, acc_
lkas_hud_values['LKAS_PROBLEM'] = lkas_hud['LKAS_PROBLEM']
if not (CP.flags & HondaFlags.BOSCH_EXT_HUD):
lkas_hud_values['LDW_OFF'] = 1
lkas_hud_values['SET_ME_X1'] = 1
lkas_hud_values['RDM_OFF'] = 1
lkas_hud_values['LANE_ASSIST_BEEP_OFF'] = 1
# New HUD concept for selected Bosch cars, overwrites some of the above
# TODO: make global across all Honda if feedback is favorable
if CP.carFingerprint in HONDA_BOSCH_ALT_RADAR:
lkas_hud_values['DASHED_LANES'] = steering_available
lkas_hud_values['SOLID_LANES'] = lat_active
lkas_hud_values['LKAS_PROBLEM'] = lat_active and reduced_steering
if CP.flags & HondaFlags.BOSCH_EXT_HUD and not CP.openpilotLongitudinalControl:
commands.append(packer.make_can_msg('LKAS_HUD_A', CAN.lkas, lkas_hud_values))
commands.append(packer.make_can_msg('LKAS_HUD_B', CAN.lkas, lkas_hud_values))
commands.append(packer.make_can_msg('LKAS_HUD_A', bus, lkas_hud_values))
commands.append(packer.make_can_msg('LKAS_HUD_B', bus, lkas_hud_values))
else:
commands.append(packer.make_can_msg('LKAS_HUD', CAN.lkas, lkas_hud_values))
if radar_disabled:
radar_hud_values = {
'CMBS_OFF': 0x01,
'SET_TO_1': 0x01,
}
commands.append(packer.make_can_msg('RADAR_HUD', CAN.pt, radar_hud_values))
if CP.carFingerprint == CAR.HONDA_CIVIC_BOSCH:
commands.append(packer.make_can_msg("LEGACY_BRAKE_COMMAND", CAN.pt, {}))
commands.append(packer.make_can_msg('LKAS_HUD', bus, lkas_hud_values))
return commands
def create_radar_hud(packer, bus):
radar_hud_values = {
'CMBS_OFF': 0x01,
'SET_TO_1': 0x01,
}
return packer.make_can_msg('RADAR_HUD', bus, radar_hud_values)
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):
values = {
'CRUISE_BUTTONS': button_val,
+21 -34
View File
@@ -48,7 +48,7 @@ class CarInterface(CarInterfaceBase):
# If Bosch radarless, this blocks ACC messages from the camera
# TODO: get radar disable working on Bosch CANFD
ret.alphaLongitudinalAvailable = candidate not in HONDA_BOSCH_CANFD
ret.openpilotLongitudinalControl = alpha_long
ret.openpilotLongitudinalControl = alpha_long and (candidate not in HONDA_BOSCH_CANFD)
ret.pcmCruise = not ret.openpilotLongitudinalControl
else:
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.hondaNidec)]
@@ -63,8 +63,8 @@ class CarInterface(CarInterfaceBase):
if any(0x33DA in f for f in fingerprint.values()):
ret.flags |= HondaFlags.BOSCH_EXT_HUD.value
if 0x1C2 in fingerprint[CAN.pt]:
ret.flags |= HondaFlags.HAS_EPB.value
if 0x184 in fingerprint[CAN.pt]:
ret.flags |= HondaFlags.HYBRID.value
if ret.flags & HondaFlags.ALLOW_MANUAL_TRANS and all(msg not in fingerprint[CAN.pt] for msg in (0x191, 0x1A3)):
# Manual transmission support for allowlisted cars only, to prevent silent fall-through on auto-detection failures
@@ -76,10 +76,6 @@ class CarInterface(CarInterfaceBase):
# Traditional autos, direct-drive EVs and eCVTs, gearshift position in GEARBOX_AUTO
ret.transmissionType = TransmissionType.automatic
# Certain Hondas have an extra steering sensor at the bottom of the steering rack,
# which improves controls quality as it removes the steering column torsion from feedback.
# Tire stiffness factor fictitiously lower if it includes the steering column torsion effect.
# For modeling details, see p.198-200 in "The Science of Vehicle Dynamics (2014), M. Guiggiani"
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0], [0]]
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kf = 0.00006 # conservative feed-forward
@@ -118,6 +114,8 @@ class CarInterface(CarInterfaceBase):
elif candidate in (CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CIVIC_BOSCH_DIESEL):
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]]
if candidate == CAR.HONDA_CIVIC_BOSCH:
CarControllerParams.BOSCH_GAS_LOOKUP_V = [0, 750]
elif candidate == CAR.HONDA_CIVIC_2022:
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
@@ -131,11 +129,6 @@ class CarInterface(CarInterfaceBase):
else:
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]]
elif candidate == CAR.HONDA_ACCORD_11G:
ret.steerActuatorDelay = 0.22
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560], [0, 2560]]
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
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
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]]
@@ -162,11 +155,6 @@ class CarInterface(CarInterfaceBase):
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]]
ret.wheelSpeedFactor = 1.025
elif candidate in (CAR.HONDA_CRV_6G):
ret.steerActuatorDelay = 0.15
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560], [0, 2560]]
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate == CAR.HONDA_FIT:
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.05]]
@@ -199,21 +187,11 @@ class CarInterface(CarInterfaceBase):
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.38], [0.11]]
elif candidate == CAR.HONDA_PILOT_4G:
ret.steerActuatorDelay = 0.15
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560], [0, 2560]]
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate == CAR.ACURA_MDX_4G_MMR:
ret.steerActuatorDelay = 0.15
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560], [0, 2560]]
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate == CAR.HONDA_RIDGELINE:
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.38], [0.11]]
elif candidate == CAR.HONDA_INSIGHT:
elif candidate in (CAR.HONDA_INSIGHT, CAR.HONDA_NBOX_2G):
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]]
@@ -221,8 +199,22 @@ class CarInterface(CarInterfaceBase):
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] # TODO: can probably use some tuning
elif candidate == CAR.HONDA_ODYSSEY_5G_MMR:
# Stock camera sends up to 2560 during LKA operation and up to 3840 during RDM operation
# Steer motor torque does rise a little above 2560, but not linearly, RDM also applies one-sided brake drag
#ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 3072], [0, 2560, 3840]]
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560], [0, 2560]]
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
ret.steerActuatorDelay = 0.15
CarControllerParams.BOSCH_GAS_LOOKUP_V = [0, 2000]
if not ret.openpilotLongitudinalControl:
# When using stock ACC, the radar intercepts and filters steering commands the EPS would otherwise accept
ret.minSteerSpeed = 70. * CV.KPH_TO_MS
else:
raise ValueError(f"unsupported car {candidate}")
ret.steerActuatorDelay = 0.15
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560], [0, 2560]]
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
# These cars use alternate user brake msg (0x1BE)
if 0x1BE in fingerprint[CAN.pt] and candidate in (CAR.HONDA_ACCORD, CAR.HONDA_HRV_3G, *HONDA_BOSCH_CANFD):
@@ -230,17 +222,12 @@ class CarInterface(CarInterfaceBase):
if ret.flags & HondaFlags.BOSCH_ALT_BRAKE:
ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.ALT_BRAKE.value
# These cars use alternate SCM messages (SCM_FEEDBACK AND SCM_BUTTON)
if candidate in HONDA_NIDEC_ALT_SCM_MESSAGES:
ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.NIDEC_ALT.value
if ret.openpilotLongitudinalControl and candidate in HONDA_BOSCH:
ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.BOSCH_LONG.value
if candidate in HONDA_BOSCH_RADARLESS:
ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.RADARLESS.value
if candidate in HONDA_BOSCH_CANFD:
ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.BOSCH_CANFD.value
@@ -1,6 +1,7 @@
import re
from opendbc.car.honda.fingerprints import FW_VERSIONS
from opendbc.car.honda.values import HONDA_BOSCH, HONDA_BOSCH_TJA_CONTROL
HONDA_FW_VERSION_RE = br"[A-Z0-9]{5}-[A-Z0-9]{3}(-|,)[A-Z0-9]{4}(\x00){2}$"
@@ -12,3 +13,6 @@ class TestHondaFingerprint:
for fws in fw_by_ecu.values():
for fw in fws:
assert re.match(HONDA_FW_VERSION_RE, fw) is not None, fw
def test_tja_bosch_only(self):
assert set(HONDA_BOSCH_TJA_CONTROL).issubset(set(HONDA_BOSCH)), "Nidec car found in TJA control list"
+60 -29
View File
@@ -38,6 +38,7 @@ class CarControllerParams:
STEER_STEP = 1 # 100 Hz
STEER_DELTA_UP = 3 # min/max in 0.33s for all Honda
STEER_DELTA_DOWN = 3
STEER_GLOBAL_MIN_SPEED = 3 * CV.MPH_TO_MS
def __init__(self, CP):
self.STEER_MAX = CP.lateralParams.torqueBP[-1]
@@ -74,8 +75,10 @@ class HondaFlags(IntFlag):
BOSCH_CANFD = 128
HAS_ALL_DOOR_STATES = 256 # Some Hondas have all door states, others only driver door
HAS_EPB = 512
BOSCH_ALT_RADAR = 512
ALLOW_MANUAL_TRANS = 1024
HYBRID = 2048
BOSCH_TJA_CONTROL = 4096
# Car button codes
@@ -91,19 +94,6 @@ class CruiseSettings:
LKAS = 1
# See dbc files for info on values
VISUAL_HUD = {
VisualAlert.none: 0,
VisualAlert.fcw: 1,
VisualAlert.steerRequired: 1,
VisualAlert.ldw: 1,
VisualAlert.brakePressed: 10,
VisualAlert.wrongGear: 6,
VisualAlert.seatbeltUnbuckled: 5,
VisualAlert.speedTooHigh: 8
}
@dataclass
class HondaCarDocs(CarDocs):
package: str = "Honda Sensing"
@@ -156,8 +146,21 @@ def radar_dbc_dict(pt_dict):
return {Bus.pt: pt_dict, Bus.radar: 'acura_ilx_2016_nidec'}
# Certain Hondas have an extra steering sensor at the bottom of the steering rack,
# which improves controls quality as it removes the steering column torsion from feedback.
# Tire stiffness factor fictitiously lower if it includes the steering column torsion effect.
# For modeling details, see p.198-200 in "The Science of Vehicle Dynamics (2014), M. Guiggiani"
class CAR(Platforms):
# Bosch Cars
HONDA_NBOX_2G = HondaBoschPlatformConfig(
[
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'},
)
HONDA_ACCORD = HondaBoschPlatformConfig(
[
HondaCarDocs("Honda Accord 2018-22", "All", video="https://www.youtube.com/watch?v=mrUwlj3Mi58", min_steer_speed=3. * CV.MPH_TO_MS),
@@ -166,7 +169,7 @@ 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_accord_2018_can_generated'},
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'},
)
HONDA_ACCORD_11G = HondaBoschCANFDPlatformConfig(
[
@@ -179,7 +182,8 @@ class CAR(Platforms):
[
HondaCarDocs("Honda Civic 2019-21", "All", video="https://www.youtube.com/watch?v=4Iz1Mz5LGF8",
footnotes=[Footnote.CIVIC_DIESEL], min_steer_speed=2. * CV.MPH_TO_MS),
HondaCarDocs("Honda Civic Hatchback 2017-21", min_steer_speed=12. * CV.MPH_TO_MS),
HondaCarDocs("Honda Civic Hatchback 2017-18", min_steer_speed=12. * CV.MPH_TO_MS),
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'},
@@ -187,7 +191,7 @@ class CAR(Platforms):
HONDA_CIVIC_BOSCH_DIESEL = HondaBoschPlatformConfig(
[], # don't show in docs
HONDA_CIVIC_BOSCH.specs,
{Bus.pt: 'honda_accord_2018_can_generated'},
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'},
)
HONDA_CIVIC_2022 = HondaBoschPlatformConfig(
[
@@ -196,17 +200,17 @@ class CAR(Platforms):
HondaCarDocs("Honda Civic Hatchback 2022-24", "All", video="https://youtu.be/ytiOT5lcp6Q"),
HondaCarDocs("Honda Civic Hatchback Hybrid (Europe only) 2023", "All"),
# TODO: Confirm 2024
HondaCarDocs("Honda Civic Hatchback Hybrid 2025", "All"),
HondaCarDocs("Honda Civic Hatchback Hybrid 2025-26", "All"),
],
HONDA_CIVIC_BOSCH.specs,
{Bus.pt: 'honda_civic_ex_2022_can_generated'},
{Bus.pt: 'honda_bosch_radarless_generated'},
flags=HondaFlags.BOSCH_RADARLESS | HondaFlags.ALLOW_MANUAL_TRANS
)
HONDA_CRV_5G = HondaBoschPlatformConfig(
[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_crv_ex_2017_can_generated', Bus.body: 'honda_crv_ex_2017_body_generated'},
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.body: 'honda_crv_ex_2017_body_generated'},
flags=HondaFlags.BOSCH_ALT_BRAKE,
)
HONDA_CRV_6G = HondaBoschCANFDPlatformConfig(
@@ -220,12 +224,18 @@ 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_accord_2018_can_generated'},
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'},
)
HONDA_HRV_3G = HondaBoschPlatformConfig(
[HondaCarDocs("Honda HR-V 2023-25", "All")],
CarSpecs(mass=3125 * CV.LB_TO_KG, wheelbase=2.61, steerRatio=15.2, centerToFrontRatio=0.41, tireStiffnessFactor=0.5),
{Bus.pt: 'honda_civic_ex_2022_can_generated'},
{Bus.pt: 'honda_bosch_radarless_generated'},
flags=HondaFlags.BOSCH_RADARLESS,
)
HONDA_CITY_7G = HondaBoschPlatformConfig(
[HondaCarDocs("Honda City (Brazil only) 2023", "All")],
CarSpecs(mass=3125 * CV.LB_TO_KG, wheelbase=2.6, steerRatio=19.0, centerToFrontRatio=0.41, minSteerSpeed=23. * CV.KPH_TO_MS),
{Bus.pt: 'honda_bosch_radarless_generated'},
flags=HondaFlags.BOSCH_RADARLESS,
)
ACURA_RDX_3G = HondaBoschPlatformConfig(
@@ -248,11 +258,27 @@ class CAR(Platforms):
[HondaCarDocs("Honda Pilot 2023-25", "All")],
CarSpecs(mass=4660 * CV.LB_TO_KG, wheelbase=2.89, centerToFrontRatio=0.442, steerRatio=17.5),
)
HONDA_PASSPORT_4G = HondaBoschCANFDPlatformConfig(
[HondaCarDocs("Honda Passport 2026", "All")],
CarSpecs(mass=4620 * CV.LB_TO_KG, wheelbase=2.89, centerToFrontRatio=0.442, steerRatio=18.5),
)
# mid-model refresh
ACURA_MDX_4G_MMR = HondaBoschCANFDPlatformConfig(
[HondaCarDocs("Acura MDX 2025", "All except Type S")],
CarSpecs(mass=4544 * CV.LB_TO_KG, wheelbase=2.89, centerToFrontRatio=0.428, steerRatio=16.2),
)
HONDA_ODYSSEY_5G_MMR = HondaBoschPlatformConfig(
[HondaCarDocs("Honda Odyssey 2021-25", "All", min_steer_speed=70. * CV.KPH_TO_MS)],
CarSpecs(mass=4590 * CV.LB_TO_KG, wheelbase=3.00, steerRatio=19.4, centerToFrontRatio=0.41),
{Bus.pt: 'acura_rdx_2020_can_generated'},
flags=HondaFlags.BOSCH_ALT_BRAKE | HondaFlags.BOSCH_ALT_RADAR,
)
ACURA_TLX_2G = HondaBoschPlatformConfig(
[HondaCarDocs("Acura TLX 2021", "All")],
CarSpecs(mass=3982 * CV.LB_TO_KG, wheelbase=2.87, steerRatio=14.0, centerToFrontRatio=0.43),
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'},
flags=HondaFlags.BOSCH_ALT_RADAR,
)
# Nidec Cars
ACURA_ILX = HondaNidecPlatformConfig(
@@ -273,25 +299,25 @@ class CAR(Platforms):
HONDA_CRV_EU = HondaNidecPlatformConfig(
[], # Euro version of CRV Touring, don't show in docs
HONDA_CRV.specs,
radar_dbc_dict('honda_crv_executive_2016_can_generated'),
radar_dbc_dict('honda_crv_touring_2016_can_generated'),
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES | HondaFlags.HAS_ALL_DOOR_STATES,
)
HONDA_FIT = HondaNidecPlatformConfig(
[HondaCarDocs("Honda Fit 2018-20", min_steer_speed=12. * CV.MPH_TO_MS)],
CarSpecs(mass=2644 * CV.LB_TO_KG, wheelbase=2.53, steerRatio=13.06, centerToFrontRatio=0.39, tireStiffnessFactor=0.75),
radar_dbc_dict('honda_fit_ex_2018_can_generated'),
radar_dbc_dict('acura_ilx_2016_can_generated'),
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
)
HONDA_FREED = HondaNidecPlatformConfig(
[HondaCarDocs("Honda Freed 2020", min_steer_speed=12. * CV.MPH_TO_MS)],
CarSpecs(mass=3086. * CV.LB_TO_KG, wheelbase=2.74, steerRatio=13.06, centerToFrontRatio=0.39, tireStiffnessFactor=0.75), # mostly copied from FIT
radar_dbc_dict('honda_fit_ex_2018_can_generated'),
radar_dbc_dict('acura_ilx_2016_can_generated'),
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
)
HONDA_HRV = HondaNidecPlatformConfig(
[HondaCarDocs("Honda HR-V 2019-22", min_steer_speed=12. * CV.MPH_TO_MS)],
HONDA_HRV_3G.specs,
radar_dbc_dict('honda_fit_ex_2018_can_generated'),
radar_dbc_dict('acura_ilx_2016_can_generated'),
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
)
HONDA_ODYSSEY = HondaNidecPlatformConfig(
@@ -334,6 +360,8 @@ HONDA_NIDEC_ALT_SCM_MESSAGES = CAR.with_flags(HondaFlags.NIDEC_ALT_SCM_MESSAGES)
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)
HONDA_BOSCH_TJA_CONTROL = CAR.with_flags(HondaFlags.BOSCH_TJA_CONTROL)
DBC = CAR.create_dbc_map()
@@ -345,8 +373,11 @@ STEER_THRESHOLD = {
CAR.HONDA_CRV_EU: 400,
CAR.HONDA_ACCORD_11G: 600,
CAR.HONDA_PILOT_4G: 600,
CAR.HONDA_PASSPORT_4G: 600,
CAR.ACURA_MDX_4G_MMR: 600,
CAR.HONDA_CRV_6G: 600,
CAR.HONDA_CITY_7G: 600,
CAR.HONDA_NBOX_2G: 600,
}
@@ -391,9 +422,9 @@ FW_QUERY_CONFIG = FwQueryConfig(
# Note that we still attempt to match with them when they are present
# This is or'd with (ALL_ECUS - ESSENTIAL_ECUS) from fw_versions.py
non_essential_ecus={
Ecu.eps: [CAR.ACURA_RDX_3G, CAR.HONDA_ACCORD, CAR.HONDA_CIVIC_2022, CAR.HONDA_E, CAR.HONDA_HRV_3G, *HONDA_BOSCH_CANFD],
Ecu.vsa: [CAR.ACURA_RDX_3G, CAR.HONDA_ACCORD, CAR.HONDA_CIVIC, CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CIVIC_2022, CAR.HONDA_CRV_5G, CAR.HONDA_CRV_HYBRID,
CAR.HONDA_E, CAR.HONDA_HRV_3G, CAR.HONDA_INSIGHT, *HONDA_BOSCH_CANFD],
Ecu.eps: [CAR.ACURA_RDX_3G, CAR.HONDA_ACCORD, CAR.HONDA_E, *HONDA_BOSCH_ALT_RADAR, *HONDA_BOSCH_RADARLESS, *HONDA_BOSCH_CANFD],
Ecu.vsa: [CAR.ACURA_RDX_3G, CAR.HONDA_ACCORD, CAR.HONDA_CIVIC, CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CRV_5G, CAR.HONDA_CRV_HYBRID,
CAR.HONDA_E, CAR.HONDA_INSIGHT, CAR.HONDA_NBOX_2G, *HONDA_BOSCH_ALT_RADAR, *HONDA_BOSCH_RADARLESS, *HONDA_BOSCH_CANFD],
},
extra_ecus=[
(Ecu.combinationMeter, 0x18da60f1, None),
@@ -53,6 +53,7 @@ FW_VERSIONS = {
(Ecu.eps, 0x7d4, None): [
b'\xf1\x00AE MDPS C 1.00 1.03 56310/G2300 4AEHC103',
b'\xf1\x00AE MDPS C 1.00 1.03 56310G2300\x00 4AEHC103',
b'\xf1\x00AE MDPS C 1.00 1.04 56310G2550\x00 4AEHC104',
b'\xf1\x00AE MDPS C 1.00 1.05 56310/G2500 4AEHC105',
b'\xf1\x00AE MDPS C 1.00 1.05 56310/G2501 4AEHC105',
b'\xf1\x00AE MDPS C 1.00 1.07 56310/G2301 4AEHC107',
@@ -62,6 +63,7 @@ FW_VERSIONS = {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00AEH MFC AT EUR LHD 1.00 1.00 95740-G2400 180222',
b'\xf1\x00AEH MFC AT EUR LHD 1.00 1.00 95740-G7200 160418',
b'\xf1\x00AEH MFC AT EUR RHD 1.00 1.00 95740-G2200 161014',
b'\xf1\x00AEH MFC AT EUR RHD 1.00 1.00 95740-G2400 180222',
b'\xf1\x00AEH MFC AT USA LHD 1.00 1.00 95740-G2300 170703',
b'\xf1\x00AEH MFC AT USA LHD 1.00 1.00 95740-G2400 180222',
@@ -728,12 +730,14 @@ FW_VERSIONS = {
b'\xf1\x00DE MDPS C 1.00 1.04 56310Q4100\x00 4DEEC104',
b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4000\x00 4DEEC105',
b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4100\x00 4DEEC105',
b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4150\x00 4DEEC105',
b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4200\x00 4DEEC105',
],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.00 99211-Q4000 191211',
b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.00 99211-Q4100 200706',
b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.03 95740-Q4000 180821',
b'\xf1\x00DEE MFC AT EUR RHD 1.00 1.00 99211-Q4000 191211',
b'\xf1\x00DEE MFC AT KOR LHD 1.00 1.02 95740-Q4000 180705',
b'\xf1\x00DEE MFC AT KOR LHD 1.00 1.03 95740-Q4000 180821',
b'\xf1\x00DEE MFC AT USA LHD 1.00 1.00 99211-Q4000 191211',
@@ -917,6 +921,7 @@ FW_VERSIONS = {
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.06 99210-AA000 220111',
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.07 99210-AA000 220426',
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.08 99210-AA000 220728',
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.09 99210-AA000 221108',
],
(Ecu.abs, 0x7d1, None): [
b'\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800',
@@ -1120,6 +1125,7 @@ FW_VERSIONS = {
b'\xf1\x00JK1 MFC AT CAN LHD 1.00 1.04 99211-AR100 210204',
b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.01 99211-AR200 220125',
b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.01 99211-AR300 220125',
b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.02 99211-IY000 230627',
b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.04 99211-AR000 210204',
],
(Ecu.fwdRadar, 0x7d0, None): [
+2 -13
View File
@@ -1,11 +1,11 @@
import re
from dataclasses import dataclass, field
from enum import Enum, IntFlag
from enum import IntFlag
from opendbc.car import Bus, CarSpecs, DbcDict, PlatformConfig, Platforms, uds
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.structs import CarParams
from opendbc.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Column, Device
from opendbc.car.docs_definitions import CarHarness, CarDocs, CarParts, Device
from opendbc.car.fw_query_definitions import FwQueryConfig, Request, p16
Ecu = CarParams.Ecu
@@ -127,21 +127,10 @@ class HyundaiFlags(IntFlag):
ALT_LIMITS_2 = 2 ** 26
class Footnote(Enum):
CANFD = CarFootnote(
"Requires a <a href=\"https://comma.ai/shop/can-fd-panda-kit\" target=\"_blank\">CAN FD panda kit</a> if not using " +
"comma 3X for this <a href=\"https://en.wikipedia.org/wiki/CAN_FD\" target=\"_blank\">CAN FD car</a>.",
Column.MODEL)
@dataclass
class HyundaiCarDocs(CarDocs):
package: str = "Smart Cruise Control (SCC)"
def init_make(self, CP: CarParams):
if CP.flags & HyundaiFlags.CANFD:
self.footnotes.insert(0, Footnote.CANFD)
@dataclass
class HyundaiPlatformConfig(PlatformConfig):
+11 -50
View File
@@ -1,11 +1,10 @@
import json
import os
import numpy as np
import time
import tomllib
from abc import abstractmethod, ABC
from enum import StrEnum
from typing import Any, NamedTuple
from typing import Any
from collections.abc import Callable
from functools import cache
@@ -42,15 +41,8 @@ GEAR_SHIFTER_MAP: dict[str, structs.CarState.GearShifter] = {
'B': GearShifter.brake, 'BRAKE': GearShifter.brake,
}
class LatControlInputs(NamedTuple):
lateral_acceleration: float
roll_compensation: float
vego: float
aego: float
TorqueFromLateralAccelCallbackType = Callable[[LatControlInputs, structs.CarParams.LateralTorqueTuning, bool], float]
TorqueFromLateralAccelCallbackType = Callable[[float, structs.CarParams.LateralTorqueTuning, bool], float]
LateralAccelFromTorqueCallbackType = Callable[[float, structs.CarParams.LateralTorqueTuning, bool], float]
@cache
@@ -181,14 +173,19 @@ class CarInterfaceBase(ABC):
def get_steer_feedforward_function(self):
return self.get_steer_feedforward_default
def torque_from_lateral_accel_linear(self, latcontrol_inputs: LatControlInputs, torque_params: structs.CarParams.LateralTorqueTuning,
gravity_adjusted: bool) -> float:
def torque_from_lateral_accel_linear(self, lateral_acceleration: float, torque_params: structs.CarParams.LateralTorqueTuning) -> float:
# The default is a linear relationship between torque and lateral acceleration (accounting for road roll and steering friction)
return latcontrol_inputs.lateral_acceleration / float(torque_params.latAccelFactor)
return lateral_acceleration / float(torque_params.latAccelFactor)
def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType:
return self.torque_from_lateral_accel_linear
def lateral_accel_from_torque_linear(self, torque: float, torque_params: structs.CarParams.LateralTorqueTuning) -> float:
return torque * float(torque_params.latAccelFactor)
def lateral_accel_from_torque(self) -> LateralAccelFromTorqueCallbackType:
return self.lateral_accel_from_torque_linear
# returns a set of default params to avoid repetition in car specific params
@staticmethod
def get_std_params(candidate: str) -> structs.CarParams:
@@ -213,7 +210,6 @@ class CarInterfaceBase(ABC):
ret.stoppingDecelRate = 0.8 # brake_travel/s while trying to stop
ret.vEgoStopping = 0.5
ret.vEgoStarting = 0.5
ret.longitudinalTuning.kf = 1.
ret.longitudinalTuning.kpBP = [0.]
ret.longitudinalTuning.kpV = [0.]
ret.longitudinalTuning.kiBP = [0.]
@@ -228,9 +224,6 @@ class CarInterfaceBase(ABC):
params = get_torque_params()[candidate]
tune.init('torque')
tune.torque.kp = 1.0
tune.torque.kf = 1.0
tune.torque.ki = 0.3
tune.torque.friction = params['FRICTION']
tune.torque.latAccelFactor = params['LAT_ACCEL_FACTOR']
tune.torque.latAccelOffset = 0.0
@@ -408,35 +401,3 @@ def get_interface_attr(attr: str, combine_brands: bool = False, ignore_none: boo
pass
return result
class NanoFFModel:
def __init__(self, weights_loc: str, platform: str):
self.weights_loc = weights_loc
self.platform = platform
self.load_weights(platform)
def load_weights(self, platform: str):
with open(self.weights_loc) as fob:
self.weights = {k: np.array(v) for k, v in json.load(fob)[platform].items()}
def relu(self, x: np.ndarray):
return np.maximum(0.0, x)
def forward(self, x: np.ndarray):
assert x.ndim == 1
x = (x - self.weights['input_norm_mat'][:, 0]) / (self.weights['input_norm_mat'][:, 1] - self.weights['input_norm_mat'][:, 0])
x = self.relu(np.dot(x, self.weights['w_1']) + self.weights['b_1'])
x = self.relu(np.dot(x, self.weights['w_2']) + self.weights['b_2'])
x = self.relu(np.dot(x, self.weights['w_3']) + self.weights['b_3'])
x = np.dot(x, self.weights['w_4']) + self.weights['b_4']
return x
def predict(self, x: list[float], do_sample: bool = False):
x = self.forward(np.array(x))
if do_sample:
pred = np.random.laplace(x[0], np.exp(x[1]) / self.weights['temperature'])
else:
pred = x[0]
pred = pred * (self.weights['output_norm_mat'][1] - self.weights['output_norm_mat'][0]) + self.weights['output_norm_mat'][0]
return pred
+2 -1
View File
@@ -160,9 +160,10 @@ def apply_center_deadzone(error, deadzone):
def get_friction(lateral_accel_error: float, lateral_accel_deadzone: float, friction_threshold: float,
torque_params: structs.CarParams.LateralTorqueTuning) -> float:
# TODO torque params' friction should be in lataxel space, not torque space
friction_interp = np.interp(
apply_center_deadzone(lateral_accel_error, lateral_accel_deadzone),
[-friction_threshold, friction_threshold],
[-torque_params.friction, torque_params.friction]
[-torque_params.friction * torque_params.latAccelFactor, torque_params.friction * torque_params.latAccelFactor]
)
return float(friction_interp)
@@ -11,12 +11,15 @@ FW_VERSIONS = {
],
(Ecu.eps, 0x742, None): [
b'6CA2B\xa9A\x02\x02G8A89P90D6A\x00\x00\x01\x80',
b'6CA2C\xa9A\x02\x02G8A89P90D6A\x00\x00\x01\x80',
],
(Ecu.engine, 0x7e0, None): [
b'237106GU3B',
b'237109HE2B',
],
(Ecu.gateway, 0x18dad0f1, None): [
b'284U29HE0A',
b'284U29HF0A',
],
},
CAR.NISSAN_LEAF: {
@@ -56,6 +59,7 @@ FW_VERSIONS = {
b'5SK0ADB\x04\x18\x00\x00\x00\x00\x00_(5\x07\x9aQ\x00\x00\x00\x80',
],
(Ecu.abs, 0x740, None): [
b'476605SD2D',
b'476605SD2E',
b'476605SH1D',
b'476605SH7D',
@@ -66,13 +70,17 @@ FW_VERSIONS = {
b'5SH2A\x99A\x05\x02N123F\x15\x81\x00\x00\x00\x00\x00\x00\x00\x80',
b'5SH2A\xb7A\x05\x02N123F\x15\xa3\x00\x00\x00\x00\x00\x00\x00\x80',
b'5SH2C\xb7A\x05\x02N123F\x15\xa3\x00\x00\x00\x00\x00\x00\x00\x80',
b'5SH3A\x99A\x05\x02N123F\x15\x81\x00\x00\x00\x00\x00\x00\x00\x80',
b'5SH3C\xb7A\x05\x02N123F\x15\xa3\x00\x00\x00\x00\x00\x00\x00\x80',
b'5SK3A\x99A\x05\x02N123F\x15u\x00\x00\x00\x00\x00\x00\x00\x80',
],
(Ecu.gateway, 0x18dad0f1, None): [
b'284U25SF0C',
b'284U25SH3A',
b'284U25SH3B',
b'284U25SK2D',
b'284U25SR0B',
b'284U25SR0C',
],
},
CAR.NISSAN_XTRAIL: {
+1 -1
View File
@@ -72,7 +72,7 @@ class CAR(Platforms):
NissanCarSpecs(mass=1610, wheelbase=2.705)
)
NISSAN_ALTIMA = NissanPlatformConfig(
[NissanCarDocs("Nissan Altima 2019-20", car_parts=CarParts.common([CarHarness.nissan_b]))],
[NissanCarDocs("Nissan Altima 2019-20, 2024", car_parts=CarParts.common([CarHarness.nissan_b]))],
NissanCarSpecs(mass=1492, wheelbase=2.824)
)
+69 -80
View File
@@ -22,41 +22,36 @@ from opendbc.car.structs import RadarData
from typing import List, Tuple
# car head to radar
DREL_OFFSET = -1.35
DREL_OFFSET = -1.52
# typically max lane width is 3.7m
LANE_WIDTH = 3.8
LANE_WIDTH_HALF = LANE_WIDTH/2
LANE_CENTER_MIN_LAT = 0.
LANE_CENTER_MAX_LAT = LANE_WIDTH_HALF
LANE_CENTER_MIN_DIST = 5.
LANE_SIDE_MIN_LAT = LANE_WIDTH_HALF
LANE_SIDE_MAX_LAT = LANE_WIDTH_HALF + LANE_WIDTH
LANE_SIDE_MIN_DIST = 10.
# max object amount will process
MAX_OBJECTS = 100
# lat distance, typically max lane width is 3.7m
MAX_LAT_DIST = 3.6
MAX_LAT_DIST = 6.
# objects to ignore thats really close to the vehicle (after DREL_OFFSET applied)
MIN_DIST = 2.5
# when a object has really large negative v_rel means its stationary / standstill
# so with the values below (v_rel = -10, lat_dist = 2.), we are trying to ignore:
# when the ego vehicle is driving above 36 km/h (22.37 mph), we will ignore objects that lateral distance is above 2m on left or right.
STATIONARY_OBJ_VREL = -10.
STATIONARY_OBJ_LAT_DIST = 2.
# when we detect an object that's really closed to the ego vehicle
# we ignore the objects that's away from left or right
CLOSED_OBJ_DREL = 10
CLOSED_OBJ_YREL = 2.
# ignore objects that has small radar cross sections (-64 ~ 63.5)
MIN_RCS = -5.
MIN_DIST = 5.
# ignore oncoming objects
IGNORE_OBJ_STATE = 2
# ignore objects that we haven't seen for 5 secs
NOT_SEEN_INIT = 33*5
NOT_SEEN_INIT = 33
def _create_radar_parser():
messages = [("Status", float('nan')), ("ObjectData", float('nan'))]
messages += [(f"ObjectData_{i}", float('nan')) for i in range(MAX_OBJECTS)]
return CANParser('u_radar', messages, 1)
return CANParser('u_radar', [("Status", float('nan')), ("ObjectData", float('nan'))], 1)
class RadarInterface(RadarInterfaceBase):
def __init__(self, CP):
@@ -70,28 +65,6 @@ class RadarInterface(RadarInterfaceBase):
self._pts_not_seen = {key: 0 for key in range(255)}
self._should_clear_cache = False
def _create_parsable_object_can_strings(self, can_strings: List[Tuple]) -> Tuple[List[Tuple], int]:
"""Optimized object string parsing with minimal allocations."""
if not can_strings or not isinstance(can_strings[0], tuple) or len(can_strings[0]) < 2:
return [], 0
# Pre-allocate list with known maximum size
new_list = []
new_list_append = new_list.append # Local reference for faster access
records = can_strings[0][1]
id_num = 1
for record in records:
if id_num > MAX_OBJECTS:
break
if record[0] == 0x60B:
new_list_append((id_num + 383, record[1], record[2]))
id_num += 1
return [(can_strings[0][0], new_list)], len(new_list)
# called by card.py, 100hz
def update(self, can_strings):
vls = self.rcp.update(can_strings)
@@ -101,60 +74,76 @@ class RadarInterface(RadarInterfaceBase):
self._should_clear_cache = True
if 1547 in self.updated_messages:
parsable_can_string, size = self._create_parsable_object_can_strings(can_strings)
self.rcp.update(parsable_can_string)
all_objects = zip(
self.rcp.vl_all['ObjectData']['ID'],
self.rcp.vl_all['ObjectData']['DistLong'],
self.rcp.vl_all['ObjectData']['DistLat'],
self.rcp.vl_all['ObjectData']['VRelLong'],
self.rcp.vl_all['ObjectData']['VRelLat'],
self.rcp.vl_all['ObjectData']['DynProp'],
self.rcp.vl_all['ObjectData']['Class'],
self.rcp.vl_all['ObjectData']['RCS'],
)
# clean cache when we see a 0x60a then a 0x60b
if self._should_clear_cache:
self._pts_cache.clear()
self._should_clear_cache = False
for i in range(size):
cpt = self.rcp.vl[f'ObjectData_{i}']
track_id = int(cpt['ID'])
for track_id, dist_long, dist_lat, vrel_long, vrel_lat, dyn_prop, obj_class, rcs in all_objects:
d_rel = float(cpt['DistLong']) + DREL_OFFSET
y_rel = -float(cpt['DistLat'])
obj_class = int(cpt['Class'])
d_rel = dist_long + DREL_OFFSET
y_rel = -dist_lat
should_ignore = False
# ignore point (obj_class = 0)
if not should_ignore and int(obj_class) == 0:
should_ignore = True
# ignore oncoming objects
if int(cpt['DynProp']) == IGNORE_OBJ_STATE:
continue
# @todo remove this because it's always 0 ?
if not should_ignore and int(dyn_prop) == IGNORE_OBJ_STATE:
should_ignore = True
# only apply filters below when object is a point (0) not a vehicle (1)
if obj_class == 0:
# ignore really closed objects
if d_rel < MIN_DIST:
continue
# far away lane object, ignore
if not should_ignore and abs(y_rel) > LANE_SIDE_MAX_LAT:
should_ignore = True
# ignore objects with really small radar cross sections
if float(cpt['RCS']) < MIN_RCS:
continue
# close object, ignore, use vision
if not should_ignore and LANE_CENTER_MIN_LAT > abs(y_rel) > LANE_CENTER_MAX_LAT and d_rel < LANE_CENTER_MIN_DIST:
should_ignore = True
# ignore far left/right objects
if abs(y_rel) > MAX_LAT_DIST:
continue
# close object, ignore, use vision
if not should_ignore and LANE_SIDE_MIN_LAT > abs(y_rel) > LANE_SIDE_MAX_LAT and d_rel < LANE_SIDE_MIN_DIST:
should_ignore = True
# ignore closed left/right objects when closed
if d_rel < CLOSED_OBJ_DREL and abs(y_rel) > CLOSED_OBJ_YREL:
continue
# add to cache
if track_id not in self._pts_cache:
if not should_ignore and track_id not in self._pts_cache:
self._pts_cache[track_id] = RadarData.RadarPoint()
self._pts_cache[track_id].trackId = track_id
self._pts_not_seen[track_id] = NOT_SEEN_INIT
self._pts_cache[track_id].yvRel = float(cpt['VRelLat'])
self._pts_cache[track_id].dRel = d_rel
self._pts_cache[track_id].yRel = y_rel
self._pts_cache[track_id].vRel = float(cpt['VRelLong'])
self._pts_cache[track_id].aRel = float('nan')
self._pts_cache[track_id].measured = True
if should_ignore:
self._pts_not_seen[track_id] = -1
else:
self._pts_not_seen[track_id] = NOT_SEEN_INIT
# init cache
if track_id not in self._pts_cache:
self._pts_cache[track_id] = RadarData.RadarPoint()
self._pts_cache[track_id].trackId = track_id
# add/update to cache
self._pts_cache[track_id].dRel = d_rel
self._pts_cache[track_id].yRel = y_rel
self._pts_cache[track_id].vRel = float(vrel_long)
self._pts_cache[track_id].yvRel = float('nan')
self._pts_cache[track_id].aRel = float('nan')
self._pts_cache[track_id].measured = True
self.updated_messages.clear()
if self.frame % 2 == 0:
# publish to cereal
if self.frame % 3 == 0:
keys_to_remove = [key for key in self.pts if key not in self._pts_cache]
for key in keys_to_remove:
self._pts_not_seen[key] -= 1
+7 -4
View File
@@ -56,13 +56,16 @@ class CarState(CarStateBase):
ret.cruiseState.available = True # cp.vl["VDM_AdasSts"]["VDM_AdasInterfaceStatus"] == 1
ret.cruiseState.standstill = cp.vl["VDM_AdasSts"]["VDM_AdasVehicleHoldStatus"] == 1
# TODO: log ACM_Unkown2=3 as a fault. need to filter it at the start and end of routes though
# ACM_FaultStatus hasn't been seen yet
# ACM_Status->ACM_FaultSupervisorState normally 1, appears to go to 3 when either:
# 1. car in park/not in drive (normal)
# 2. something (message from another ECU) ACM relies on is faulty
# * ACM_FaultStatus will stay 0 since ACM itself isn't faulted
# TODO: ACM_FaultStatus hasn't been seen high yet, but log anyway
ret.accFaulted = (cp_cam.vl["ACM_Status"]["ACM_FaultStatus"] == 1 or
# VDM_AdasFaultStatus=Brk_Intv is the default for some reason
# VDM_AdasFaultStatus=Imps_Cmd was seen when sending it rapidly changing ACC enable commands
# VDM_AdasFaultStatus=Cntr_Fault isn't fully understood, but we've seen it in the wild
cp.vl["VDM_AdasSts"]["VDM_AdasFaultStatus"] in (3,)) # 3=Imps_Cmd
# VDM_AdasFaultStatus=Imps_Cmd was seen when sending it rapidly changing ACC enable commands, or when ACC command drops out
cp.vl["VDM_AdasSts"]["VDM_AdasFaultStatus"] in (2, 3)) # 2=Cntr_Fault, 3=Imps_Cmd
# Gear
ret.gearShifter = GEAR_MAP.get(int(cp.vl["VDM_PropStatus"]["VDM_Prndl_Status"]), GearShifter.unknown)

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