Merge remote-tracking branch 'upstream/master' into fix-alert-duration

pull/33478/head
Shane Smiskol 7 months ago
commit bcf8494694
  1. 1
      .gitattributes
  2. 5
      .github/workflows/selfdrive_tests.yaml
  3. 2
      .github/workflows/stale.yaml
  4. 32
      .github/workflows/tools_tests.yaml
  5. 166
      .github/workflows/ui_preview.yaml
  6. 3
      .gitignore
  7. 2
      .gitmodules
  8. 4
      Jenkinsfile
  9. 6
      RELEASES.md
  10. 4
      SConstruct
  11. 715
      cereal/car.capnp
  12. 1
      cereal/car.capnp
  13. 159
      cereal/log.capnp
  14. 23
      cereal/messaging/__init__.py
  15. 2
      cereal/messaging/socketmaster.cc
  16. 2
      cereal/services.py
  17. 2
      common/params.cc
  18. 18
      common/transformations/coordinates.cc
  19. 24
      common/transformations/coordinates.hpp
  20. 22
      common/transformations/orientation.cc
  21. 16
      common/transformations/orientation.hpp
  22. 34
      common/transformations/transformations.pxd
  23. 2
      common/util.h
  24. 13
      conftest.py
  25. 12
      docs/CARS.md
  26. 2
      docs/CONTRIBUTING.md
  27. 31
      docs/car-porting/what-is-a-car-port.md
  28. 2
      opendbc_repo
  29. 2
      panda
  30. 20
      pyproject.toml
  31. 2
      release/release_files.py
  32. 3
      selfdrive/assets/offroad/icon_openpilot.png
  33. 19
      selfdrive/car/README.md
  34. 101
      selfdrive/car/car_specific.py
  35. 104
      selfdrive/car/card.py
  36. 3
      selfdrive/car/cruise.py
  37. 72
      selfdrive/car/helpers.py
  38. 8
      selfdrive/car/tests/big_cars_test.sh
  39. 15
      selfdrive/car/tests/test_car_interfaces.py
  40. 33
      selfdrive/car/tests/test_models.py
  41. 748
      selfdrive/controls/controlsd.py
  42. 22
      selfdrive/controls/lib/drive_helpers.py
  43. 41
      selfdrive/controls/lib/ldw.py
  44. 32
      selfdrive/controls/lib/longitudinal_planner.py
  45. 2
      selfdrive/controls/lib/tests/test_latcontrol.py
  46. 3
      selfdrive/controls/lib/tests/test_vehicle_model.py
  47. 15
      selfdrive/controls/plannerd.py
  48. 58
      selfdrive/controls/radard.py
  49. 10
      selfdrive/controls/tests/test_leads.py
  50. 121
      selfdrive/controls/tests/test_startup.py
  51. 102
      selfdrive/controls/tests/test_state_machine.py
  52. 4
      selfdrive/debug/count_events.py
  53. 6
      selfdrive/debug/cycle_alerts.py
  54. 11
      selfdrive/debug/format_fingerprints.py
  55. 11
      selfdrive/locationd/locationd.py
  56. 4
      selfdrive/modeld/SConscript
  57. 23
      selfdrive/modeld/constants.py
  58. 10
      selfdrive/modeld/dmonitoringmodeld
  59. 63
      selfdrive/modeld/dmonitoringmodeld.py
  60. 26
      selfdrive/modeld/fill_model_msg.py
  61. 36
      selfdrive/modeld/modeld.py
  62. 30
      selfdrive/modeld/models/commonmodel.cc
  63. 10
      selfdrive/modeld/models/commonmodel.h
  64. 4
      selfdrive/modeld/models/commonmodel.pxd
  65. 8
      selfdrive/modeld/models/commonmodel_pyx.pyx
  66. 4
      selfdrive/modeld/models/dmonitoring_model.current
  67. 4
      selfdrive/modeld/models/dmonitoring_model.onnx
  68. 3
      selfdrive/modeld/models/dmonitoring_model_q.dlc
  69. 4
      selfdrive/modeld/models/supercombo.onnx
  70. 13
      selfdrive/modeld/parse_model_outputs.py
  71. 13
      selfdrive/modeld/runners/onnxmodel.py
  72. 3
      selfdrive/modeld/tests/test_modeld.py
  73. 22
      selfdrive/modeld/transforms/loadyuv.cc
  74. 24
      selfdrive/modeld/transforms/loadyuv.cl
  75. 6
      selfdrive/modeld/transforms/loadyuv.h
  76. 20
      selfdrive/monitoring/helpers.py
  77. 4
      selfdrive/monitoring/test_monitoring.py
  78. 6
      selfdrive/pandad/panda.cc
  79. 4
      selfdrive/pandad/panda.h
  80. 8
      selfdrive/pandad/panda_safety.cc
  81. 1
      selfdrive/pandad/pandad.h
  82. 15
      selfdrive/selfdrived/alertmanager.py
  83. 4
      selfdrive/selfdrived/alerts_offroad.json
  84. 93
      selfdrive/selfdrived/events.py
  85. 502
      selfdrive/selfdrived/selfdrived.py
  86. 98
      selfdrive/selfdrived/state.py
  87. 8
      selfdrive/selfdrived/tests/test_alertmanager.py
  88. 10
      selfdrive/selfdrived/tests/test_alerts.py
  89. 92
      selfdrive/selfdrived/tests/test_state_machine.py
  90. 3
      selfdrive/test/cpp_harness.py
  91. 16
      selfdrive/test/longitudinal_maneuvers/maneuver.py
  92. 13
      selfdrive/test/longitudinal_maneuvers/plant.py
  93. 38
      selfdrive/test/longitudinal_maneuvers/test_longitudinal.py
  94. 2
      selfdrive/test/process_replay/README.md
  95. 2
      selfdrive/test/process_replay/compare_logs.py
  96. 374
      selfdrive/test/process_replay/migration.py
  97. 34
      selfdrive/test/process_replay/model_replay.py
  98. 2
      selfdrive/test/process_replay/model_replay_ref_commit
  99. 55
      selfdrive/test/process_replay/process_replay.py
  100. 2
      selfdrive/test/process_replay/ref_commit
  101. Some files were not shown because too many files have changed in this diff Show More

1
.gitattributes vendored

@ -2,7 +2,6 @@
# to move existing files into LFS: # to move existing files into LFS:
# git add --renormalize . # git add --renormalize .
*.dlc filter=lfs diff=lfs merge=lfs -text
*.onnx filter=lfs diff=lfs merge=lfs -text *.onnx filter=lfs diff=lfs merge=lfs -text
*.svg filter=lfs diff=lfs merge=lfs -text *.svg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text *.png filter=lfs diff=lfs merge=lfs -text

@ -60,7 +60,7 @@ jobs:
run: | run: |
cd $STRIPPED_DIR cd $STRIPPED_DIR
${{ env.RUN }} "release/check-dirty.sh && \ ${{ env.RUN }} "release/check-dirty.sh && \
MAX_EXAMPLES=5 $PYTEST -m 'not slow' selfdrive/car" MAX_EXAMPLES=5 $PYTEST -m 'not slow' selfdrive/car system/manager"
- name: Static analysis - name: Static analysis
timeout-minutes: 1 timeout-minutes: 1
run: | run: |
@ -337,7 +337,6 @@ jobs:
# This job name needs to be the same as UI_JOB_NAME in ui_preview.yaml # This job name needs to be the same as UI_JOB_NAME in ui_preview.yaml
name: Create UI Report name: Create UI Report
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@ -353,5 +352,5 @@ jobs:
- name: Upload Test Report - name: Upload Test Report
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: report-${{ github.event.number }} name: report-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
path: selfdrive/ui/tests/test_ui/report_1/screenshots path: selfdrive/ui/tests/test_ui/report_1/screenshots

@ -21,7 +21,7 @@ jobs:
close-pr-message: 'This PR has been automatically closed due to inactivity. Feel free to re-open once activity resumes.' close-pr-message: 'This PR has been automatically closed due to inactivity. Feel free to re-open once activity resumes.'
stale-pr-label: stale stale-pr-label: stale
delete-branch: ${{ github.event.pull_request.head.repo.full_name == 'commaai/openpilot' }} # only delete branches on the main repo delete-branch: ${{ github.event.pull_request.head.repo.full_name == 'commaai/openpilot' }} # only delete branches on the main repo
exempt-pr-labels: "ignore stale,needs testing,car port,car" # if wip or it needs testing from the community, don't mark as stale exempt-pr-labels: "ignore stale,needs testing" # if wip or it needs testing from the community, don't mark as stale
days-before-pr-stale: ${{ env.DAYS_BEFORE_PR_STALE }} days-before-pr-stale: ${{ env.DAYS_BEFORE_PR_STALE }}
days-before-pr-close: ${{ env.DAYS_BEFORE_PR_CLOSE }} days-before-pr-close: ${{ env.DAYS_BEFORE_PR_CLOSE }}

@ -43,40 +43,10 @@ jobs:
source selfdrive/test/setup_vsound.sh && \ source selfdrive/test/setup_vsound.sh && \
CI=1 pytest tools/sim/tests/test_metadrive_bridge.py" CI=1 pytest tools/sim/tests/test_metadrive_bridge.py"
test_compatibility:
name: test 20.04 + 3.11
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Installing ubuntu dependencies
run: INSTALL_EXTRA_PACKAGES=no tools/install_ubuntu_dependencies.sh
- name: Installing python
uses: actions/setup-python@v5
with:
python-version: '3.11.4'
- name: Installing pip
run: pip install pip==24.0
- name: Installing uv
run: pip install uv
- name: git LFS
run: git lfs pull
- run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV
- name: Getting scons cache
uses: 'actions/cache@v4'
with:
path: /tmp/scons_cache
key: scons-${{ runner.arch }}-ubuntu2004-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
restore-keys: |
scons-${{ runner.arch }}-ubuntu2004-${{ env.CACHE_COMMIT_DATE }}
scons-${{ runner.arch }}-ubuntu2004
- name: Building openpilot
run: uv run scons -u -j$(nproc)
devcontainer: devcontainer:
name: devcontainer name: devcontainer
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: false # we can re-enable once this is faster
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:

@ -1,5 +1,8 @@
name: "ui preview" name: "ui preview"
on: on:
push:
branches:
- master
pull_request_target: pull_request_target:
types: [assigned, opened, synchronize, reopened, edited] types: [assigned, opened, synchronize, reopened, edited]
branches: branches:
@ -10,6 +13,9 @@ on:
env: env:
UI_JOB_NAME: "Create UI Report" UI_JOB_NAME: "Create UI Report"
REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.sha || github.event.pull_request.head.sha }}
BRANCH_NAME: "openpilot/pr-${{ github.event.number }}"
jobs: jobs:
preview: preview:
@ -22,87 +28,143 @@ jobs:
pull-requests: write pull-requests: write
actions: read actions: read
steps: steps:
- name: Waiting for ui test to start - name: Waiting for ui generation to start
run: sleep 30 run: sleep 30
- name: Wait for ui report - name: Waiting for ui generation to end
uses: lewagon/wait-on-check-action@v1.3.4 uses: lewagon/wait-on-check-action@v1.3.4
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ env.SHA }}
check-name: ${{ env.UI_JOB_NAME }} check-name: ${{ env.UI_JOB_NAME }}
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
allowed-conclusions: success allowed-conclusions: success
wait-interval: 20 wait-interval: 20
- name: Get workflow run ID - name: Getting workflow run ID
id: get_run_id id: get_run_id
run: | run: |
echo "run_id=$(curl https://api.github.com/repos/${{ github.repository }}/commits/${{ github.event.pull_request.head.sha }}/check-runs | jq -r '.check_runs[] | select(.name == "${{ env.UI_JOB_NAME }}") | .html_url | capture("(?<number>[0-9]+)") | .number')" >> $GITHUB_OUTPUT echo "run_id=$(curl https://api.github.com/repos/${{ github.repository }}/commits/${{ env.SHA }}/check-runs | jq -r '.check_runs[] | select(.name == "${{ env.UI_JOB_NAME }}") | .html_url | capture("(?<number>[0-9]+)") | .number')" >> $GITHUB_OUTPUT
- name: Checkout ci-artifacts
uses: actions/checkout@v4
with:
repository: commaai/ci-artifacts
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
path: ${{ github.workspace }}/ci-artifacts
ref: master
- name: Download artifact - name: Getting proposed ui
id: download-artifact id: download-artifact
uses: dawidd6/action-download-artifact@v6 uses: dawidd6/action-download-artifact@v6
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
run_id: ${{ steps.get_run_id.outputs.run_id }} run_id: ${{ steps.get_run_id.outputs.run_id }}
search_artifacts: true search_artifacts: true
name: report-${{ github.event.number }} name: report-${{ env.REPORT_NAME }}
path: ${{ github.workspace }}/ci-artifacts path: ${{ github.workspace }}/pr_ui
- name: Getting master ui
uses: actions/checkout@v4
with:
repository: commaai/ci-artifacts
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
path: ${{ github.workspace }}/master_ui
ref: openpilot_master_ui
- name: Saving new master ui
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
working-directory: ${{ github.workspace }}/master_ui
run: |
git checkout --orphan=new_master_ui
git rm -rf *
git branch -D openpilot_master_ui
git branch -m openpilot_master_ui
git config user.name "GitHub Actions Bot"
git config user.email "<>"
mv ${{ github.workspace }}/pr_ui/*.png .
git add .
git commit -m "screenshots for commit ${{ env.SHA }}"
git push origin openpilot_master_ui --force
- name: Finding diff
if: github.event_name == 'pull_request_target'
id: find_diff
run: >-
sudo apt-get install -y imagemagick
scenes="homescreen settings_device settings_toggles offroad_alert update_available prime onroad onroad_disengaged onroad_override onroad_sidebar onroad_wide onroad_wide_sidebar onroad_alert_small onroad_alert_mid onroad_alert_full driver_camera body keyboard"
A=($scenes)
DIFF=""
open=false
TABLE="<summary>All Screenshots</summary>"
TABLE="${TABLE}<table>"
for ((i=0; i<${#A[*]}; i=i+1));
do
if ! compare -fuzz 2% -highlight-color DeepSkyBlue1 -lowlight-color Black -compose Src ${{ github.workspace }}/master_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png; then
open=true
convert ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png -transparent black mask.png
composite mask.png ${{ github.workspace }}/master_ui/${A[$i]}.png composite_diff.png
convert -delay 20 ${{ github.workspace }}/master_ui/${A[$i]}.png composite_diff.png -loop 0 ${{ github.workspace }}/pr_ui/${A[$i]}_diff.gif
mv ${{ github.workspace }}/master_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_master_ref.png
DIFF="${DIFF}<details>"
DIFF="${DIFF}<summary>${A[$i]} : \$\${\\color{red}\\text{DIFFERENT}}\$\$</summary>"
DIFF="${DIFF}<table>"
DIFF="${DIFF}<tr>"
DIFF="${DIFF} <td> master <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_master_ref.png\"> </td>"
DIFF="${DIFF} <td> proposed <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}.png\"> </td>"
DIFF="${DIFF}</tr>"
DIFF="${DIFF}<tr>"
DIFF="${DIFF} <td> diff <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_diff.png\"> </td>"
DIFF="${DIFF} <td> composite diff <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_diff.gif\"> </td>"
DIFF="${DIFF}</tr>"
DIFF="${DIFF}</table>"
DIFF="${DIFF}</details>"
else
rm -f ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png
fi
INDEX=$(($i % 2))
if [[ $INDEX -eq 0 ]]; then
TABLE="${TABLE}<tr>"
fi
TABLE="${TABLE} <td> <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}.png\"> </td>"
if [[ $INDEX -eq 1 || $(($i + 1)) -eq ${#A[*]} ]]; then
TABLE="${TABLE}</tr>"
fi
done
TABLE="${TABLE}</table>"
TABLE="${TABLE}</details>"
if $open; then
TABLE="<details open>${TABLE}"
else
TABLE="<details>${TABLE}"
fi
echo "DIFF=$DIFF$TABLE" >> "$GITHUB_OUTPUT"
- name: Push Screenshots - name: Saving proposed ui
working-directory: ${{ github.workspace }}/ci-artifacts if: github.event_name == 'pull_request_target'
working-directory: ${{ github.workspace }}/master_ui
run: | run: |
git checkout -b openpilot/pr-${{ github.event.number }}
git config user.name "GitHub Actions Bot" git config user.name "GitHub Actions Bot"
git config user.email "<>" git config user.email "<>"
git add ${{ github.workspace }}/ci-artifacts/* git checkout --orphan=${{ env.BRANCH_NAME }}
git rm -rf *
mv ${{ github.workspace }}/pr_ui/* .
git add .
git commit -m "screenshots for PR #${{ github.event.number }}" git commit -m "screenshots for PR #${{ github.event.number }}"
git push origin openpilot/pr-${{ github.event.number }} --force git push origin ${{ env.BRANCH_NAME }} --force
- name: Comment Screenshots on PR - name: Comment Screenshots on PR
if: github.event_name == 'pull_request_target'
uses: thollander/actions-comment-pull-request@v2 uses: thollander/actions-comment-pull-request@v2
with: with:
message: | message: |
<!-- _(run_id_screenshots **${{ github.run_id }}**)_ --> <!-- _(run_id_screenshots **${{ github.run_id }}**)_ -->
## UI Screenshots ## UI Preview
<table> ${{ steps.find_diff.outputs.DIFF }}
<tr>
<td><img src="https://raw.githubusercontent.com/commaai/ci-artifacts/openpilot/pr-${{ github.event.number }}/homescreen.png"></td>
<td><img src="https://raw.githubusercontent.com/commaai/ci-artifacts/openpilot/pr-${{ github.event.number }}/settings_device.png"></td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/commaai/ci-artifacts/openpilot/pr-${{ github.event.number }}/offroad_alert.png"></td>
<td><img src="https://raw.githubusercontent.com/commaai/ci-artifacts/openpilot/pr-${{ github.event.number }}/update_available.png"></td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/commaai/ci-artifacts/openpilot/pr-${{ github.event.number }}/onroad.png"></td>
<td><img src="https://raw.githubusercontent.com/commaai/ci-artifacts/openpilot/pr-${{ github.event.number }}/onroad_sidebar.png"></td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/commaai/ci-artifacts/openpilot/pr-${{ github.event.number }}/onroad_wide.png"></td>
<td><img src="https://raw.githubusercontent.com/commaai/ci-artifacts/openpilot/pr-${{ github.event.number }}/onroad_wide_sidebar.png"></td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/commaai/ci-artifacts/openpilot/pr-${{ github.event.number }}/onroad_alert_small.png"></td>
<td><img src="https://raw.githubusercontent.com/commaai/ci-artifacts/openpilot/pr-${{ github.event.number }}/onroad_alert_mid.png"></td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/commaai/ci-artifacts/openpilot/pr-${{ github.event.number }}/onroad_alert_full.png"></td>
<td><img src="https://raw.githubusercontent.com/commaai/ci-artifacts/openpilot/pr-${{ github.event.number }}/driver_camera.png"></td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/commaai/ci-artifacts/openpilot/pr-${{ github.event.number }}/body.png"></td>
<td></td>
</tr>
</table>
comment_tag: run_id_screenshots comment_tag: run_id_screenshots
pr_number: ${{ github.event.number }} pr_number: ${{ github.event.number }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

3
.gitignore vendored

@ -55,9 +55,6 @@ selfdrive/test/longitudinal_maneuvers/out
selfdrive/car/tests/cars_dump selfdrive/car/tests/cars_dump
system/camerad/camerad system/camerad/camerad
system/camerad/test/ae_gray_test system/camerad/test/ae_gray_test
selfdrive/modeld/_modeld
selfdrive/modeld/_dmonitoringmodeld
/src/
notebooks notebooks
hyperthneed hyperthneed

2
.gitmodules vendored

@ -15,4 +15,4 @@
url = ../../commaai/teleoprtc url = ../../commaai/teleoprtc
[submodule "tinygrad"] [submodule "tinygrad"]
path = tinygrad_repo path = tinygrad_repo
url = https://github.com/tinygrad/tinygrad.git url = https://github.com/commaai/tinygrad.git

4
Jenkinsfile vendored

@ -84,6 +84,8 @@ def deviceStage(String stageName, String deviceType, List extra_env, def steps)
docker.image('ghcr.io/commaai/alpine-ssh').inside('--user=root') { docker.image('ghcr.io/commaai/alpine-ssh').inside('--user=root') {
timeout(time: 35, unit: 'MINUTES') { timeout(time: 35, unit: 'MINUTES') {
retry (3) { retry (3) {
def date = sh(script: 'date', returnStdout: true).trim();
device(device_ip, "set time", "date -s '" + date + "'")
device(device_ip, "git checkout", extra + "\n" + readFile("selfdrive/test/setup_device_ci.sh")) device(device_ip, "git checkout", extra + "\n" + readFile("selfdrive/test/setup_device_ci.sh"))
} }
steps.each { item -> steps.each { item ->
@ -147,7 +149,7 @@ node {
["build openpilot", "cd system/manager && ./build.py"], ["build openpilot", "cd system/manager && ./build.py"],
["check dirty", "release/check-dirty.sh"], ["check dirty", "release/check-dirty.sh"],
["onroad tests", "pytest selfdrive/test/test_onroad.py -s"], ["onroad tests", "pytest selfdrive/test/test_onroad.py -s"],
["time to onroad", "pytest selfdrive/test/test_time_to_onroad.py"], //["time to onroad", "pytest selfdrive/test/test_time_to_onroad.py"],
]) ])
}, },
'HW + Unit Tests': { 'HW + Unit Tests': {

@ -1,6 +1,12 @@
Version 0.9.8 (2024-XX-XX) Version 0.9.8 (2024-XX-XX)
======================== ========================
* New driving model
* Trained in brand new ML simulator
* Model now gates applying positive accel in Chill mode
* New driving monitoring model
* Reduced false positives related to passengers
* Added toggle to enable driver monitoring even when openpilot is not engaged * Added toggle to enable driver monitoring even when openpilot is not engaged
* New Toyota TSS2 longitudinal tune
Version 0.9.7 (2024-06-13) Version 0.9.7 (2024-06-13)
======================== ========================

@ -64,6 +64,10 @@ AddOption('--pc-thneed',
dest='pc_thneed', dest='pc_thneed',
help='use thneed on pc') help='use thneed on pc')
AddOption('--mutation',
action='store_true',
help='generate mutation-ready code')
AddOption('--minimal', AddOption('--minimal',
action='store_false', action='store_false',
dest='extras', dest='extras',

@ -1,715 +0,0 @@
using Cxx = import "./include/c++.capnp";
$Cxx.namespace("cereal");
@0x8e2af1e708af8b8d;
# ******* events causing controls state machine transition *******
# FIXME: OnroadEvent shouldn't be in car.capnp, but can't immediately
# move due to being referenced by structs in this file
struct OnroadEvent @0x9b1657f34caf3ad3 {
name @0 :EventName;
# event types
enable @1 :Bool;
noEntry @2 :Bool;
warning @3 :Bool; # alerts presented only when enabled or soft disabling
userDisable @4 :Bool;
softDisable @5 :Bool;
immediateDisable @6 :Bool;
preEnable @7 :Bool;
permanent @8 :Bool; # alerts presented regardless of openpilot state
overrideLateral @10 :Bool;
overrideLongitudinal @9 :Bool;
enum EventName @0xbaa8c5d505f727de {
canError @0;
steerUnavailable @1;
wrongGear @4;
doorOpen @5;
seatbeltNotLatched @6;
espDisabled @7;
wrongCarMode @8;
steerTempUnavailable @9;
reverseGear @10;
buttonCancel @11;
buttonEnable @12;
pedalPressed @13; # exits active state
preEnableStandstill @73; # added during pre-enable state with brake
gasPressedOverride @108; # added when user is pressing gas with no disengage on gas
steerOverride @114;
cruiseDisabled @14;
speedTooLow @17;
outOfSpace @18;
overheat @19;
calibrationIncomplete @20;
calibrationInvalid @21;
calibrationRecalibrating @117;
controlsMismatch @22;
pcmEnable @23;
pcmDisable @24;
radarFault @26;
brakeHold @28;
parkBrake @29;
manualRestart @30;
lowSpeedLockout @31;
joystickDebug @34;
steerTempUnavailableSilent @35;
resumeRequired @36;
preDriverDistracted @37;
promptDriverDistracted @38;
driverDistracted @39;
preDriverUnresponsive @43;
promptDriverUnresponsive @44;
driverUnresponsive @45;
belowSteerSpeed @46;
lowBattery @48;
accFaulted @51;
sensorDataInvalid @52;
commIssue @53;
commIssueAvgFreq @109;
tooDistracted @54;
posenetInvalid @55;
soundsUnavailable @56;
preLaneChangeLeft @57;
preLaneChangeRight @58;
laneChange @59;
lowMemory @63;
stockAeb @64;
ldw @65;
carUnrecognized @66;
invalidLkasSetting @69;
speedTooHigh @70;
laneChangeBlocked @71;
relayMalfunction @72;
stockFcw @74;
startup @75;
startupNoCar @76;
startupNoControl @77;
startupMaster @78;
startupNoFw @104;
fcw @79;
steerSaturated @80;
belowEngageSpeed @84;
noGps @85;
wrongCruiseMode @87;
modeldLagging @89;
deviceFalling @90;
fanMalfunction @91;
cameraMalfunction @92;
cameraFrameRate @110;
processNotRunning @95;
dashcamMode @96;
controlsInitializing @98;
usbError @99;
roadCameraError @100;
driverCameraError @101;
wideRoadCameraError @102;
highCpuUsage @105;
cruiseMismatch @106;
lkasDisabled @107;
canBusMissing @111;
controlsdLagging @112;
resumeBlocked @113;
steerTimeLimit @115;
vehicleSensorsInvalid @116;
locationdTemporaryError @103;
locationdPermanentError @118;
paramsdTemporaryError @50;
paramsdPermanentError @119;
actuatorsApiUnavailable @120;
espActive @121;
personalityChanged @122;
radarCanErrorDEPRECATED @15;
communityFeatureDisallowedDEPRECATED @62;
radarCommIssueDEPRECATED @67;
driverMonitorLowAccDEPRECATED @68;
gasUnavailableDEPRECATED @3;
dataNeededDEPRECATED @16;
modelCommIssueDEPRECATED @27;
ipasOverrideDEPRECATED @33;
geofenceDEPRECATED @40;
driverMonitorOnDEPRECATED @41;
driverMonitorOffDEPRECATED @42;
calibrationProgressDEPRECATED @47;
invalidGiraffeHondaDEPRECATED @49;
invalidGiraffeToyotaDEPRECATED @60;
internetConnectivityNeededDEPRECATED @61;
whitePandaUnsupportedDEPRECATED @81;
commIssueWarningDEPRECATED @83;
focusRecoverActiveDEPRECATED @86;
neosUpdateRequiredDEPRECATED @88;
modelLagWarningDEPRECATED @93;
startupOneplusDEPRECATED @82;
startupFuzzyFingerprintDEPRECATED @97;
noTargetDEPRECATED @25;
brakeUnavailableDEPRECATED @2;
plannerErrorDEPRECATED @32;
gpsMalfunctionDEPRECATED @94;
}
}
# ******* main car state @ 100hz *******
# all speeds in m/s
struct CarState {
events @13 :List(OnroadEvent);
# CAN health
canValid @26 :Bool; # invalid counter/checksums
canTimeout @40 :Bool; # CAN bus dropped out
canErrorCounter @48 :UInt32;
# car speed
vEgo @1 :Float32; # best estimate of speed
aEgo @16 :Float32; # best estimate of aCAN cceleration
vEgoRaw @17 :Float32; # unfiltered speed from wheel speed sensors
vEgoCluster @44 :Float32; # best estimate of speed shown on car's instrument cluster, used for UI
vCruise @53 :Float32; # actual set speed
vCruiseCluster @54 :Float32; # set speed to display in the UI
yawRate @22 :Float32; # best estimate of yaw rate
standstill @18 :Bool;
wheelSpeeds @2 :WheelSpeeds;
# gas pedal, 0.0-1.0
gas @3 :Float32; # this is user pedal only
gasPressed @4 :Bool; # this is user pedal only
engineRpm @46 :Float32;
# brake pedal, 0.0-1.0
brake @5 :Float32; # this is user pedal only
brakePressed @6 :Bool; # this is user pedal only
regenBraking @45 :Bool; # this is user pedal only
parkingBrake @39 :Bool;
brakeHoldActive @38 :Bool;
# steering wheel
steeringAngleDeg @7 :Float32;
steeringAngleOffsetDeg @37 :Float32; # Offset betweens sensors in case there multiple
steeringRateDeg @15 :Float32;
steeringTorque @8 :Float32; # TODO: standardize units
steeringTorqueEps @27 :Float32; # TODO: standardize units
steeringPressed @9 :Bool; # if the user is using the steering wheel
steerFaultTemporary @35 :Bool; # temporary EPS fault
steerFaultPermanent @36 :Bool; # permanent EPS fault
stockAeb @30 :Bool;
stockFcw @31 :Bool;
espDisabled @32 :Bool;
accFaulted @42 :Bool;
carFaultedNonCritical @47 :Bool; # some ECU is faulted, but car remains controllable
espActive @51 :Bool;
vehicleSensorsInvalid @52 :Bool; # invalid steering angle readings, etc.
# cruise state
cruiseState @10 :CruiseState;
# gear
gearShifter @14 :GearShifter;
# button presses
buttonEvents @11 :List(ButtonEvent);
leftBlinker @20 :Bool;
rightBlinker @21 :Bool;
genericToggle @23 :Bool;
# lock info
doorOpen @24 :Bool;
seatbeltUnlatched @25 :Bool;
# clutch (manual transmission only)
clutchPressed @28 :Bool;
# blindspot sensors
leftBlindspot @33 :Bool; # Is there something blocking the left lane change
rightBlindspot @34 :Bool; # Is there something blocking the right lane change
fuelGauge @41 :Float32; # battery or fuel tank level from 0.0 to 1.0
charging @43 :Bool;
# process meta
cumLagMs @50 :Float32;
struct WheelSpeeds {
# optional wheel speeds
fl @0 :Float32;
fr @1 :Float32;
rl @2 :Float32;
rr @3 :Float32;
}
struct CruiseState {
enabled @0 :Bool;
speed @1 :Float32;
speedCluster @6 :Float32; # Set speed as shown on instrument cluster
available @2 :Bool;
speedOffset @3 :Float32;
standstill @4 :Bool;
nonAdaptive @5 :Bool;
}
enum GearShifter {
unknown @0;
park @1;
drive @2;
neutral @3;
reverse @4;
sport @5;
low @6;
brake @7;
eco @8;
manumatic @9;
}
# send on change
struct ButtonEvent {
pressed @0 :Bool;
type @1 :Type;
enum Type {
unknown @0;
leftBlinker @1;
rightBlinker @2;
accelCruise @3;
decelCruise @4;
cancel @5;
altButton1 @6;
altButton2 @7;
altButton3 @8;
setCruise @9;
resumeCruise @10;
gapAdjustCruise @11;
}
}
# deprecated
errorsDEPRECATED @0 :List(OnroadEvent.EventName);
brakeLightsDEPRECATED @19 :Bool;
steeringRateLimitedDEPRECATED @29 :Bool;
canMonoTimesDEPRECATED @12: List(UInt64);
canRcvTimeoutDEPRECATED @49 :Bool;
}
# ******* radar state @ 20hz *******
struct RadarData @0x888ad6581cf0aacb {
errors @0 :List(Error);
points @1 :List(RadarPoint);
enum Error {
canError @0;
fault @1;
wrongConfig @2;
}
# similar to LiveTracks
# is one timestamp valid for all? I think so
struct RadarPoint {
trackId @0 :UInt64; # no trackId reuse
# these 3 are the minimum required
dRel @1 :Float32; # m from the front bumper of the car
yRel @2 :Float32; # m
vRel @3 :Float32; # m/s
# these are optional and valid if they are not NaN
aRel @4 :Float32; # m/s^2
yvRel @5 :Float32; # m/s
# some radars flag measurements VS estimates
measured @6 :Bool;
}
# deprecated
canMonoTimesDEPRECATED @2 :List(UInt64);
}
# ******* car controls @ 100hz *******
struct CarControl {
# must be true for any actuator commands to work
enabled @0 :Bool;
latActive @11: Bool;
longActive @12: Bool;
# Actuator commands as computed by controlsd
actuators @6 :Actuators;
# moved to CarOutput
actuatorsOutputDEPRECATED @10 :Actuators;
leftBlinker @15: Bool;
rightBlinker @16: Bool;
orientationNED @13 :List(Float32);
angularVelocity @14 :List(Float32);
cruiseControl @4 :CruiseControl;
hudControl @5 :HUDControl;
struct Actuators {
# range from 0.0 - 1.0
gas @0: Float32;
brake @1: Float32;
# range from -1.0 - 1.0
steer @2: Float32;
# value sent over can to the car
steerOutputCan @8: Float32;
steeringAngleDeg @3: Float32;
curvature @7: Float32;
speed @6: Float32; # m/s
accel @4: Float32; # m/s^2
longControlState @5: LongControlState;
enum LongControlState @0xe40f3a917d908282{
off @0;
pid @1;
stopping @2;
starting @3;
}
}
struct CruiseControl {
cancel @0: Bool;
resume @1: Bool;
override @4: Bool;
speedOverrideDEPRECATED @2: Float32;
accelOverrideDEPRECATED @3: Float32;
}
struct HUDControl {
speedVisible @0: Bool;
setSpeed @1: Float32;
lanesVisible @2: Bool;
leadVisible @3: Bool;
visualAlert @4: VisualAlert;
audibleAlert @5: AudibleAlert;
rightLaneVisible @6: Bool;
leftLaneVisible @7: Bool;
rightLaneDepart @8: Bool;
leftLaneDepart @9: Bool;
leadDistanceBars @10: Int8; # 1-3: 1 is closest, 3 is farthest. some ports may utilize 2-4 bars instead
enum VisualAlert {
# these are the choices from the Honda
# map as good as you can for your car
none @0;
fcw @1;
steerRequired @2;
brakePressed @3;
wrongGear @4;
seatbeltUnbuckled @5;
speedTooHigh @6;
ldw @7;
}
enum AudibleAlert {
none @0;
engage @1;
disengage @2;
refuse @3;
warningSoft @4;
warningImmediate @5;
prompt @6;
promptRepeat @7;
promptDistracted @8;
}
}
gasDEPRECATED @1 :Float32;
brakeDEPRECATED @2 :Float32;
steeringTorqueDEPRECATED @3 :Float32;
activeDEPRECATED @7 :Bool;
rollDEPRECATED @8 :Float32;
pitchDEPRECATED @9 :Float32;
}
struct CarOutput {
# Any car specific rate limits or quirks applied by
# the CarController are reflected in actuatorsOutput
# and matches what is sent to the car
actuatorsOutput @0 :CarControl.Actuators;
}
# ****** car param ******
struct CarParams {
carName @0 :Text;
carFingerprint @1 :Text;
fuzzyFingerprint @55 :Bool;
notCar @66 :Bool; # flag for non-car robotics platforms
pcmCruise @3 :Bool; # is openpilot's state tied to the PCM's cruise state?
enableDsu @5 :Bool; # driving support unit
enableBsm @56 :Bool; # blind spot monitoring
flags @64 :UInt32; # flags for car specific quirks
experimentalLongitudinalAvailable @71 :Bool;
minEnableSpeed @7 :Float32;
minSteerSpeed @8 :Float32;
safetyConfigs @62 :List(SafetyConfig);
alternativeExperience @65 :Int16; # panda flag for features like no disengage on gas
# Car docs fields
maxLateralAccel @68 :Float32;
autoResumeSng @69 :Bool; # describes whether car can resume from a stop automatically
# things about the car in the manual
mass @17 :Float32; # [kg] curb weight: all fluids no cargo
wheelbase @18 :Float32; # [m] distance from rear axle to front axle
centerToFront @19 :Float32; # [m] distance from center of mass to front axle
steerRatio @20 :Float32; # [] ratio of steering wheel angle to front wheel angle
steerRatioRear @21 :Float32; # [] ratio of steering wheel angle to rear wheel angle (usually 0)
# things we can derive
rotationalInertia @22 :Float32; # [kg*m2] body rotational inertia
tireStiffnessFactor @72 :Float32; # scaling factor used in calculating tireStiffness[Front,Rear]
tireStiffnessFront @23 :Float32; # [N/rad] front tire coeff of stiff
tireStiffnessRear @24 :Float32; # [N/rad] rear tire coeff of stiff
longitudinalTuning @25 :LongitudinalPIDTuning;
lateralParams @48 :LateralParams;
lateralTuning :union {
pid @26 :LateralPIDTuning;
indiDEPRECATED @27 :LateralINDITuning;
lqrDEPRECATED @40 :LateralLQRTuning;
torque @67 :LateralTorqueTuning;
}
steerLimitAlert @28 :Bool;
steerLimitTimer @47 :Float32; # time before steerLimitAlert is issued
vEgoStopping @29 :Float32; # Speed at which the car goes into stopping state
vEgoStarting @59 :Float32; # Speed at which the car goes into starting state
stoppingControl @31 :Bool; # Does the car allow full control even at lows speeds when stopping
steerControlType @34 :SteerControlType;
radarUnavailable @35 :Bool; # True when radar objects aren't visible on CAN or aren't parsed out
stopAccel @60 :Float32; # Required acceleration to keep vehicle stationary
stoppingDecelRate @52 :Float32; # m/s^2/s while trying to stop
startAccel @32 :Float32; # Required acceleration to get car moving
startingState @70 :Bool; # Does this car make use of special starting state
steerActuatorDelay @36 :Float32; # Steering wheel actuator delay in seconds
longitudinalActuatorDelay @58 :Float32; # Gas/Brake actuator delay in seconds
openpilotLongitudinalControl @37 :Bool; # is openpilot doing the longitudinal control?
carVin @38 :Text; # VIN number queried during fingerprinting
dashcamOnly @41: Bool;
passive @73: Bool; # is openpilot in control?
transmissionType @43 :TransmissionType;
carFw @44 :List(CarFw);
radarTimeStep @45: Float32 = 0.05; # time delta between radar updates, 20Hz is very standard
fingerprintSource @49: FingerprintSource;
networkLocation @50 :NetworkLocation; # Where Panda/C2 is integrated into the car's CAN network
wheelSpeedFactor @63 :Float32; # Multiplier on wheels speeds to computer actual speeds
struct SafetyConfig {
safetyModel @0 :SafetyModel;
safetyParam @3 :UInt16;
safetyParamDEPRECATED @1 :Int16;
safetyParam2DEPRECATED @2 :UInt32;
}
struct LateralParams {
torqueBP @0 :List(Int32);
torqueV @1 :List(Int32);
}
struct LateralPIDTuning {
kpBP @0 :List(Float32);
kpV @1 :List(Float32);
kiBP @2 :List(Float32);
kiV @3 :List(Float32);
kf @4 :Float32;
}
struct LateralTorqueTuning {
useSteeringAngle @0 :Bool;
kp @1 :Float32;
ki @2 :Float32;
friction @3 :Float32;
kf @4 :Float32;
steeringAngleDeadzoneDeg @5 :Float32;
latAccelFactor @6 :Float32;
latAccelOffset @7 :Float32;
}
struct LongitudinalPIDTuning {
kpBP @0 :List(Float32);
kpV @1 :List(Float32);
kiBP @2 :List(Float32);
kiV @3 :List(Float32);
kf @6 :Float32;
deadzoneBPDEPRECATED @4 :List(Float32);
deadzoneVDEPRECATED @5 :List(Float32);
}
struct LateralINDITuning {
outerLoopGainBP @4 :List(Float32);
outerLoopGainV @5 :List(Float32);
innerLoopGainBP @6 :List(Float32);
innerLoopGainV @7 :List(Float32);
timeConstantBP @8 :List(Float32);
timeConstantV @9 :List(Float32);
actuatorEffectivenessBP @10 :List(Float32);
actuatorEffectivenessV @11 :List(Float32);
outerLoopGainDEPRECATED @0 :Float32;
innerLoopGainDEPRECATED @1 :Float32;
timeConstantDEPRECATED @2 :Float32;
actuatorEffectivenessDEPRECATED @3 :Float32;
}
struct LateralLQRTuning {
scale @0 :Float32;
ki @1 :Float32;
dcGain @2 :Float32;
# State space system
a @3 :List(Float32);
b @4 :List(Float32);
c @5 :List(Float32);
k @6 :List(Float32); # LQR gain
l @7 :List(Float32); # Kalman gain
}
enum SafetyModel {
silent @0;
hondaNidec @1;
toyota @2;
elm327 @3;
gm @4;
hondaBoschGiraffe @5;
ford @6;
cadillac @7;
hyundai @8;
chrysler @9;
tesla @10;
subaru @11;
gmPassive @12;
mazda @13;
nissan @14;
volkswagen @15;
toyotaIpas @16;
allOutput @17;
gmAscm @18;
noOutput @19; # like silent but without silent CAN TXs
hondaBosch @20;
volkswagenPq @21;
subaruPreglobal @22; # pre-Global platform
hyundaiLegacy @23;
hyundaiCommunity @24;
volkswagenMlb @25;
hongqi @26;
body @27;
hyundaiCanfd @28;
volkswagenMqbEvo @29;
chryslerCusw @30;
psa @31;
}
enum SteerControlType {
torque @0;
angle @1;
curvatureDEPRECATED @2;
}
enum TransmissionType {
unknown @0;
automatic @1; # Traditional auto, including DSG
manual @2; # True "stick shift" only
direct @3; # Electric vehicle or other direct drive
cvt @4;
}
struct CarFw {
ecu @0 :Ecu;
fwVersion @1 :Data;
address @2 :UInt32;
subAddress @3 :UInt8;
responseAddress @4 :UInt32;
request @5 :List(Data);
brand @6 :Text;
bus @7 :UInt8;
logging @8 :Bool;
obdMultiplexing @9 :Bool;
}
enum Ecu {
eps @0;
abs @1;
fwdRadar @2;
fwdCamera @3;
engine @4;
unknown @5;
transmission @8; # Transmission Control Module
hybrid @18; # hybrid control unit, e.g. Chrysler's HCP, Honda's IMA Control Unit, Toyota's hybrid control computer
srs @9; # airbag
gateway @10; # can gateway
hud @11; # heads up display
combinationMeter @12; # instrument cluster
electricBrakeBooster @15;
shiftByWire @16;
adas @19;
cornerRadar @21;
hvac @20;
parkingAdas @7; # parking assist system ECU, e.g. Toyota's IPAS, Hyundai's RSPA, etc.
epb @22; # electronic parking brake
telematics @23;
body @24; # body control module
# Toyota only
dsu @6;
# Honda only
vsa @13; # Vehicle Stability Assist
programmedFuelInjection @14;
debug @17;
}
enum FingerprintSource {
can @0;
fw @1;
fixed @2;
}
enum NetworkLocation {
fwdCamera @0; # Standard/default integration at LKAS camera
gateway @1; # Integration at vehicle's CAN gateway
}
enableGasInterceptorDEPRECATED @2 :Bool;
enableCameraDEPRECATED @4 :Bool;
enableApgsDEPRECATED @6 :Bool;
steerRateCostDEPRECATED @33 :Float32;
isPandaBlackDEPRECATED @39 :Bool;
hasStockCameraDEPRECATED @57 :Bool;
safetyParamDEPRECATED @10 :Int16;
safetyModelDEPRECATED @9 :SafetyModel;
safetyModelPassiveDEPRECATED @42 :SafetyModel = silent;
minSpeedCanDEPRECATED @51 :Float32;
communityFeatureDEPRECATED @46: Bool;
startingAccelRateDEPRECATED @53 :Float32;
steerMaxBPDEPRECATED @11 :List(Float32);
steerMaxVDEPRECATED @12 :List(Float32);
gasMaxBPDEPRECATED @13 :List(Float32);
gasMaxVDEPRECATED @14 :List(Float32);
brakeMaxBPDEPRECATED @15 :List(Float32);
brakeMaxVDEPRECATED @16 :List(Float32);
directAccelControlDEPRECATED @30 :Bool;
maxSteeringAngleDegDEPRECATED @54 :Float32;
longitudinalActuatorDelayLowerBoundDEPRECATEDDEPRECATED @61 :Float32;
}

@ -0,0 +1 @@
../opendbc_repo/opendbc/car/car.capnp

@ -17,6 +17,118 @@ struct Map(Key, Value) {
} }
} }
struct OnroadEvent @0xc4fa6047f024e718 {
name @0 :EventName;
# event types
enable @1 :Bool;
noEntry @2 :Bool;
warning @3 :Bool; # alerts presented only when enabled or soft disabling
userDisable @4 :Bool;
softDisable @5 :Bool;
immediateDisable @6 :Bool;
preEnable @7 :Bool;
permanent @8 :Bool; # alerts presented regardless of openpilot state
overrideLateral @10 :Bool;
overrideLongitudinal @9 :Bool;
enum EventName @0x91f1992a1f77fb03 {
canError @0;
steerUnavailable @1;
wrongGear @2;
doorOpen @3;
seatbeltNotLatched @4;
espDisabled @5;
wrongCarMode @6;
steerTempUnavailable @7;
reverseGear @8;
buttonCancel @9;
buttonEnable @10;
pedalPressed @11; # exits active state
preEnableStandstill @12; # added during pre-enable state with brake
gasPressedOverride @13; # added when user is pressing gas with no disengage on gas
steerOverride @14;
cruiseDisabled @15;
speedTooLow @16;
outOfSpace @17;
overheat @18;
calibrationIncomplete @19;
calibrationInvalid @20;
calibrationRecalibrating @21;
controlsMismatch @22;
pcmEnable @23;
pcmDisable @24;
radarFault @25;
brakeHold @26;
parkBrake @27;
manualRestart @28;
joystickDebug @29;
longitudinalManeuver @30;
steerTempUnavailableSilent @31;
resumeRequired @32;
preDriverDistracted @33;
promptDriverDistracted @34;
driverDistracted @35;
preDriverUnresponsive @36;
promptDriverUnresponsive @37;
driverUnresponsive @38;
belowSteerSpeed @39;
lowBattery @40;
accFaulted @41;
sensorDataInvalid @42;
commIssue @43;
commIssueAvgFreq @44;
tooDistracted @45;
posenetInvalid @46;
soundsUnavailable @47;
preLaneChangeLeft @48;
preLaneChangeRight @49;
laneChange @50;
lowMemory @51;
stockAeb @52;
ldw @53;
carUnrecognized @54;
invalidLkasSetting @55;
speedTooHigh @56;
laneChangeBlocked @57;
relayMalfunction @58;
stockFcw @59;
startup @60;
startupNoCar @61;
startupNoControl @62;
startupNoSecOcKey @63;
startupMaster @64;
fcw @65;
steerSaturated @66;
belowEngageSpeed @67;
noGps @68;
wrongCruiseMode @69;
modeldLagging @70;
deviceFalling @71;
fanMalfunction @72;
cameraMalfunction @73;
cameraFrameRate @74;
processNotRunning @75;
dashcamMode @76;
selfdriveInitializing @77;
usbError @78;
cruiseMismatch @79;
canBusMissing @80;
selfdrivedLagging @81;
resumeBlocked @82;
steerTimeLimit @83;
vehicleSensorsInvalid @84;
locationdTemporaryError @85;
locationdPermanentError @86;
paramsdTemporaryError @87;
paramsdPermanentError @88;
actuatorsApiUnavailable @89;
espActive @90;
personalityChanged @91;
aeb @92;
}
}
enum LongitudinalPersonality { enum LongitudinalPersonality {
aggressive @0; aggressive @0;
standard @1; standard @1;
@ -611,7 +723,6 @@ struct RadarState @0x9a185389d6fdd05f {
leadOne @3 :LeadData; leadOne @3 :LeadData;
leadTwo @4 :LeadData; leadTwo @4 :LeadData;
cumLagMs @5 :Float32;
struct LeadData { struct LeadData {
dRel @0 :Float32; dRel @0 :Float32;
@ -641,6 +752,7 @@ struct RadarState @0x9a185389d6fdd05f {
calCycleDEPRECATED @8 :Int32; calCycleDEPRECATED @8 :Int32;
calPercDEPRECATED @9 :Int8; calPercDEPRECATED @9 :Int8;
canMonoTimesDEPRECATED @10 :List(UInt64); canMonoTimesDEPRECATED @10 :List(UInt64);
cumLagMsDEPRECATED @5 :Float32;
} }
struct LiveCalibrationData { struct LiveCalibrationData {
@ -671,7 +783,7 @@ struct LiveCalibrationData {
} }
} }
struct LiveTracks { struct LiveTracksDEPRECATED {
trackId @0 :Int32; trackId @0 :Int32;
dRel @1 :Float32; dRel @1 :Float32;
yRel @2 :Float32; yRel @2 :Float32;
@ -698,6 +810,7 @@ struct SelfdriveState {
alertSize @6 :AlertSize; alertSize @6 :AlertSize;
alertType @7 :Text; alertType @7 :Text;
alertSound @8 :Car.CarControl.HUDControl.AudibleAlert; alertSound @8 :Car.CarControl.HUDControl.AudibleAlert;
alertHudVisual @12 :Car.CarControl.HUDControl.VisualAlert;
# configurable driving settings # configurable driving settings
experimentalMode @10 :Bool; experimentalMode @10 :Bool;
@ -726,7 +839,6 @@ struct SelfdriveState {
} }
struct ControlsState @0x97ff69c53601abf1 { struct ControlsState @0x97ff69c53601abf1 {
cumLagMs @15 :Float32;
longitudinalPlanMonoTime @28 :UInt64; longitudinalPlanMonoTime @28 :UInt64;
lateralPlanMonoTime @50 :UInt64; lateralPlanMonoTime @50 :UInt64;
@ -735,7 +847,6 @@ struct ControlsState @0x97ff69c53601abf1 {
upAccelCmd @4 :Float32; upAccelCmd @4 :Float32;
uiAccelCmd @5 :Float32; uiAccelCmd @5 :Float32;
ufAccelCmd @33 :Float32; ufAccelCmd @33 :Float32;
aTarget @35 :Float32;
curvature @37 :Float32; # path curvature from vehicle model curvature @37 :Float32; # path curvature from vehicle model
desiredCurvature @61 :Float32; # lag adjusted curvatures used by lateral controllers desiredCurvature @61 :Float32; # lag adjusted curvatures used by lateral controllers
forceDecel @51 :Bool; forceDecel @51 :Bool;
@ -880,12 +991,15 @@ struct ControlsState @0x97ff69c53601abf1 {
vCruiseDEPRECATED @22 :Float32; # actual set speed vCruiseDEPRECATED @22 :Float32; # actual set speed
vCruiseClusterDEPRECATED @63 :Float32; # set speed to display in the UI vCruiseClusterDEPRECATED @63 :Float32; # set speed to display in the UI
startMonoTimeDEPRECATED @48 :UInt64; startMonoTimeDEPRECATED @48 :UInt64;
cumLagMsDEPRECATED @15 :Float32;
aTargetDEPRECATED @35 :Float32;
} }
struct DrivingModelData { struct DrivingModelData {
frameId @0 :UInt32; frameId @0 :UInt32;
frameIdExtra @1 :UInt32; frameIdExtra @1 :UInt32;
frameDropPerc @6 :Float32; frameDropPerc @6 :Float32;
modelExecutionTime @7 :Float32;
action @2 :ModelDataV2.Action; action @2 :ModelDataV2.Action;
@ -931,7 +1045,6 @@ struct ModelDataV2 {
frameDropPerc @2 :Float32; frameDropPerc @2 :Float32;
timestampEof @3 :UInt64; timestampEof @3 :UInt64;
modelExecutionTime @15 :Float32; modelExecutionTime @15 :Float32;
gpuExecutionTime @17 :Float32;
rawPredictions @16 :Data; rawPredictions @16 :Data;
# predicted future position, orientation, etc.. # predicted future position, orientation, etc..
@ -958,12 +1071,13 @@ struct ModelDataV2 {
# Model perceived motion # Model perceived motion
temporalPose @21 :Pose; temporalPose @21 :Pose;
# e2e lateral planner
action @26: Action;
gpuExecutionTimeDEPRECATED @17 :Float32;
navEnabledDEPRECATED @22 :Bool; navEnabledDEPRECATED @22 :Bool;
locationMonoTimeDEPRECATED @24 :UInt64; locationMonoTimeDEPRECATED @24 :UInt64;
# e2e lateral planner
lateralPlannerSolutionDEPRECATED @25: LateralPlannerSolution; lateralPlannerSolutionDEPRECATED @25: LateralPlannerSolution;
action @26: Action;
struct LeadDataV2 { struct LeadDataV2 {
prob @0 :Float32; # probability that car is your lead at time t prob @0 :Float32; # probability that car is your lead at time t
@ -1096,6 +1210,14 @@ struct AndroidLogEntry {
message @6 :Text; message @6 :Text;
} }
struct DriverAssistance {
# Lane Departure Warnings
leftLaneDeparture @0 :Bool;
rightLaneDeparture @1 :Bool;
# FCW, AEB, etc. will go here
}
struct LongitudinalPlan @0xe00b5b3eba12876c { struct LongitudinalPlan @0xe00b5b3eba12876c {
modelMonoTime @9 :UInt64; modelMonoTime @9 :UInt64;
hasLead @7 :Bool; hasLead @7 :Bool;
@ -1147,7 +1269,7 @@ struct LongitudinalPlan @0xe00b5b3eba12876c {
radarValidDEPRECATED @28 :Bool; radarValidDEPRECATED @28 :Bool;
radarCanErrorDEPRECATED @30 :Bool; radarCanErrorDEPRECATED @30 :Bool;
commIssueDEPRECATED @31 :Bool; commIssueDEPRECATED @31 :Bool;
eventsDEPRECATED @13 :List(Car.OnroadEvent); eventsDEPRECATED @13 :List(Car.OnroadEventDEPRECATED);
gpsTrajectoryDEPRECATED @12 :GpsTrajectory; gpsTrajectoryDEPRECATED @12 :GpsTrajectory;
gpsPlannerActiveDEPRECATED @19 :Bool; gpsPlannerActiveDEPRECATED @19 :Bool;
personalityDEPRECATED @36 :LongitudinalPersonality; personalityDEPRECATED @36 :LongitudinalPersonality;
@ -2002,7 +2124,8 @@ struct Joystick {
struct DriverStateV2 { struct DriverStateV2 {
frameId @0 :UInt32; frameId @0 :UInt32;
modelExecutionTime @1 :Float32; modelExecutionTime @1 :Float32;
dspExecutionTime @2 :Float32; dspExecutionTimeDEPRECATED @2 :Float32;
gpuExecutionTime @8 :Float32;
rawPredictions @3 :Data; rawPredictions @3 :Data;
poorVisionProb @4 :Float32; poorVisionProb @4 :Float32;
@ -2061,7 +2184,7 @@ struct DriverStateDEPRECATED @0xb83c6cc593ed0a00 {
} }
struct DriverMonitoringState @0xb83cda094a1da284 { struct DriverMonitoringState @0xb83cda094a1da284 {
events @0 :List(Car.OnroadEvent); events @18 :List(OnroadEvent);
faceDetected @1 :Bool; faceDetected @1 :Bool;
isDistracted @2 :Bool; isDistracted @2 :Bool;
distractedType @17 :UInt32; distractedType @17 :UInt32;
@ -2080,6 +2203,7 @@ struct DriverMonitoringState @0xb83cda094a1da284 {
isPreviewDEPRECATED @15 :Bool; isPreviewDEPRECATED @15 :Bool;
rhdCheckedDEPRECATED @5 :Bool; rhdCheckedDEPRECATED @5 :Bool;
eventsDEPRECATED @0 :List(Car.OnroadEventDEPRECATED);
} }
struct Boot { struct Boot {
@ -2295,6 +2419,11 @@ struct EncodeData {
height @5 :UInt32; height @5 :UInt32;
} }
struct DebugAlert {
alertText1 @0 :Text;
alertText2 @1 :Text;
}
struct UserFlag { struct UserFlag {
} }
@ -2335,13 +2464,14 @@ struct Event {
pandaStates @81 :List(PandaState); pandaStates @81 :List(PandaState);
peripheralState @80 :PeripheralState; peripheralState @80 :PeripheralState;
radarState @13 :RadarState; radarState @13 :RadarState;
liveTracks @16 :List(LiveTracks); liveTracks @131 :Car.RadarData;
sendcan @17 :List(CanData); sendcan @17 :List(CanData);
liveCalibration @19 :LiveCalibrationData; liveCalibration @19 :LiveCalibrationData;
carState @22 :Car.CarState; carState @22 :Car.CarState;
carControl @23 :Car.CarControl; carControl @23 :Car.CarControl;
carOutput @127 :Car.CarOutput; carOutput @127 :Car.CarOutput;
longitudinalPlan @24 :LongitudinalPlan; longitudinalPlan @24 :LongitudinalPlan;
driverAssistance @132 :DriverAssistance;
ubloxGnss @34 :UbloxGnss; ubloxGnss @34 :UbloxGnss;
ubloxRaw @39 :Data; ubloxRaw @39 :Data;
qcomGnss @31 :QcomGnss; qcomGnss @31 :QcomGnss;
@ -2352,7 +2482,7 @@ struct Event {
liveTorqueParameters @94 :LiveTorqueParametersData; liveTorqueParameters @94 :LiveTorqueParametersData;
cameraOdometry @63 :CameraOdometry; cameraOdometry @63 :CameraOdometry;
thumbnail @66: Thumbnail; thumbnail @66: Thumbnail;
onroadEvents @68: List(Car.OnroadEvent); onroadEvents @134: List(OnroadEvent);
carParams @69: Car.CarParams; carParams @69: Car.CarParams;
driverMonitoringState @71: DriverMonitoringState; driverMonitoringState @71: DriverMonitoringState;
livePose @129 :LivePose; livePose @129 :LivePose;
@ -2402,6 +2532,7 @@ struct Event {
driverEncodeData @87 :EncodeData; driverEncodeData @87 :EncodeData;
wideRoadEncodeData @88 :EncodeData; wideRoadEncodeData @88 :EncodeData;
qRoadEncodeData @89 :EncodeData; qRoadEncodeData @89 :EncodeData;
alertDebug @133 :DebugAlert;
livestreamRoadEncodeData @120 :EncodeData; livestreamRoadEncodeData @120 :EncodeData;
livestreamWideRoadEncodeData @121 :EncodeData; livestreamWideRoadEncodeData @121 :EncodeData;
@ -2465,5 +2596,7 @@ struct Event {
navModelDEPRECATED @104 :NavModelData; navModelDEPRECATED @104 :NavModelData;
uiPlanDEPRECATED @106 :UiPlan; uiPlanDEPRECATED @106 :UiPlan;
liveLocationKalmanDEPRECATED @72 :LiveLocationKalman; liveLocationKalmanDEPRECATED @72 :LiveLocationKalman;
liveTracksDEPRECATED @16 :List(LiveTracksDEPRECATED);
onroadEventsDEPRECATED @68: List(Car.OnroadEventDEPRECATED);
} }
} }

@ -109,12 +109,26 @@ class FrequencyTracker:
self.min_freq = min_freq * 0.8 self.min_freq = min_freq * 0.8
self.max_freq = max_freq * 1.2 self.max_freq = max_freq * 1.2
self.recv_dts: Deque[float] = deque(maxlen=int(10 * freq)) self.recv_dts: Deque[float] = deque(maxlen=int(10 * freq))
self.recv_dts_sum = 0.0
self.recent_recv_dts: Deque[float] = deque(maxlen=int(freq))
self.recent_recv_dts_sum = 0.0
self.prev_time = 0.0 self.prev_time = 0.0
def record_recv_time(self, cur_time: float) -> None: def record_recv_time(self, cur_time: float) -> None:
# TODO: Handle case where cur_time is less than prev_time # TODO: Handle case where cur_time is less than prev_time
if self.prev_time > 1e-5: if self.prev_time > 1e-5:
self.recv_dts.append(cur_time - self.prev_time) dt = cur_time - self.prev_time
if len(self.recv_dts) == self.recv_dts.maxlen:
self.recv_dts_sum -= self.recv_dts[0]
self.recv_dts.append(dt)
self.recv_dts_sum += dt
if len(self.recent_recv_dts) == self.recent_recv_dts.maxlen:
self.recent_recv_dts_sum -= self.recent_recv_dts[0]
self.recent_recv_dts.append(dt)
self.recent_recv_dts_sum += dt
self.prev_time = cur_time self.prev_time = cur_time
@property @property
@ -122,12 +136,11 @@ class FrequencyTracker:
if not self.recv_dts: if not self.recv_dts:
return False return False
avg_freq = len(self.recv_dts) / sum(self.recv_dts) avg_freq = len(self.recv_dts) / self.recv_dts_sum
if self.min_freq <= avg_freq <= self.max_freq: if self.min_freq <= avg_freq <= self.max_freq:
return True return True
recent_dts = list(self.recv_dts)[-int(self.recv_dts.maxlen / 10):] avg_freq_recent = len(self.recent_recv_dts) / self.recent_recv_dts_sum
avg_freq_recent = len(recent_dts) / sum(recent_dts)
return self.min_freq <= avg_freq_recent <= self.max_freq return self.min_freq <= avg_freq_recent <= self.max_freq
@ -174,7 +187,7 @@ class SubMaster:
self.data[s] = getattr(data.as_reader(), s) self.data[s] = getattr(data.as_reader(), s)
self.logMonoTime[s] = 0 self.logMonoTime[s] = 0
self.valid[s] = True # FIXME: this should default to False self.valid[s] = False
self.freq_tracker[s] = FrequencyTracker(SERVICE_LIST[s].frequency, self.update_freq, s == poll) self.freq_tracker[s] = FrequencyTracker(SERVICE_LIST[s].frequency, self.update_freq, s == poll)
def __getitem__(self, s: str) -> capnp.lib.capnp._DynamicStructReader: def __getitem__(self, s: str) -> capnp.lib.capnp._DynamicStructReader:

@ -34,7 +34,7 @@ struct SubMaster::SubMessage {
std::string name; std::string name;
SubSocket *socket = nullptr; SubSocket *socket = nullptr;
int freq = 0; int freq = 0;
bool updated = false, alive = false, valid = true, ignore_alive; bool updated = false, alive = false, valid = false, ignore_alive;
uint64_t rcv_time = 0, rcv_frame = 0; uint64_t rcv_time = 0, rcv_frame = 0;
void *allocated_msg_reader = nullptr; void *allocated_msg_reader = nullptr;
bool is_polled = false; bool is_polled = false;

@ -40,6 +40,7 @@ _services: dict[str, tuple] = {
"carControl": (True, 100., 10), "carControl": (True, 100., 10),
"carOutput": (True, 100., 10), "carOutput": (True, 100., 10),
"longitudinalPlan": (True, 20., 10), "longitudinalPlan": (True, 20., 10),
"driverAssistance": (True, 20., 20),
"procLog": (True, 0.5, 15), "procLog": (True, 0.5, 15),
"gpsLocationExternal": (True, 10., 10), "gpsLocationExternal": (True, 10., 10),
"gpsLocation": (True, 1., 1), "gpsLocation": (True, 1., 1),
@ -75,6 +76,7 @@ _services: dict[str, tuple] = {
# debug # debug
"uiDebug": (True, 0., 1), "uiDebug": (True, 0., 1),
"testJoystick": (True, 0.), "testJoystick": (True, 0.),
"alertDebug": (True, 20., 5),
"roadEncodeData": (False, 20.), "roadEncodeData": (False, 20.),
"driverEncodeData": (False, 20.), "driverEncodeData": (False, 20.),
"wideRoadEncodeData": (False, 20.), "wideRoadEncodeData": (False, 20.),

@ -158,6 +158,7 @@ std::unordered_map<std::string, uint32_t> keys = {
{"LiveParameters", PERSISTENT}, {"LiveParameters", PERSISTENT},
{"LiveTorqueParameters", PERSISTENT | DONT_LOG}, {"LiveTorqueParameters", PERSISTENT | DONT_LOG},
{"LocationFilterInitialState", PERSISTENT}, {"LocationFilterInitialState", PERSISTENT},
{"LongitudinalManeuverMode", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION},
{"LongitudinalPersonality", PERSISTENT}, {"LongitudinalPersonality", PERSISTENT},
{"NetworkMetered", PERSISTENT}, {"NetworkMetered", PERSISTENT},
{"ObdMultiplexingChanged", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"ObdMultiplexingChanged", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
@ -181,6 +182,7 @@ std::unordered_map<std::string, uint32_t> keys = {
{"PrimeType", PERSISTENT}, {"PrimeType", PERSISTENT},
{"RecordFront", PERSISTENT}, {"RecordFront", PERSISTENT},
{"RecordFrontLock", PERSISTENT}, // for the internal fleet {"RecordFrontLock", PERSISTENT}, // for the internal fleet
{"SecOCKey", PERSISTENT | DONT_LOG},
{"RouteCount", PERSISTENT}, {"RouteCount", PERSISTENT},
{"SnoozeUpdate", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"SnoozeUpdate", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION},
{"SshEnabled", PERSISTENT}, {"SshEnabled", PERSISTENT},

@ -25,8 +25,8 @@ static Geodetic to_radians(Geodetic geodetic){
} }
ECEF geodetic2ecef(Geodetic g){ ECEF geodetic2ecef(const Geodetic &geodetic) {
g = to_radians(g); auto g = to_radians(geodetic);
double xi = sqrt(1.0 - esq * pow(sin(g.lat), 2)); double xi = sqrt(1.0 - esq * pow(sin(g.lat), 2));
double x = (a / xi + g.alt) * cos(g.lat) * cos(g.lon); double x = (a / xi + g.alt) * cos(g.lat) * cos(g.lon);
double y = (a / xi + g.alt) * cos(g.lat) * sin(g.lon); double y = (a / xi + g.alt) * cos(g.lat) * sin(g.lon);
@ -34,7 +34,7 @@ ECEF geodetic2ecef(Geodetic g){
return {x, y, z}; return {x, y, z};
} }
Geodetic ecef2geodetic(ECEF e){ Geodetic ecef2geodetic(const ECEF &e) {
// Convert from ECEF to geodetic using Ferrari's methods // Convert from ECEF to geodetic using Ferrari's methods
// https://en.wikipedia.org/wiki/Geographic_coordinate_conversion#Ferrari.27s_solution // https://en.wikipedia.org/wiki/Geographic_coordinate_conversion#Ferrari.27s_solution
double x = e.x; double x = e.x;
@ -61,10 +61,10 @@ Geodetic ecef2geodetic(ECEF e){
return to_degrees({lat, lon, h}); return to_degrees({lat, lon, h});
} }
LocalCoord::LocalCoord(Geodetic g, ECEF e){ LocalCoord::LocalCoord(const Geodetic &geodetic, const ECEF &e) {
init_ecef << e.x, e.y, e.z; init_ecef << e.x, e.y, e.z;
g = to_radians(g); auto g = to_radians(geodetic);
ned2ecef_matrix << ned2ecef_matrix <<
-sin(g.lat)*cos(g.lon), -sin(g.lon), -cos(g.lat)*cos(g.lon), -sin(g.lat)*cos(g.lon), -sin(g.lon), -cos(g.lat)*cos(g.lon),
@ -73,7 +73,7 @@ LocalCoord::LocalCoord(Geodetic g, ECEF e){
ecef2ned_matrix = ned2ecef_matrix.transpose(); ecef2ned_matrix = ned2ecef_matrix.transpose();
} }
NED LocalCoord::ecef2ned(ECEF e) { NED LocalCoord::ecef2ned(const ECEF &e) {
Eigen::Vector3d ecef; Eigen::Vector3d ecef;
ecef << e.x, e.y, e.z; ecef << e.x, e.y, e.z;
@ -81,7 +81,7 @@ NED LocalCoord::ecef2ned(ECEF e) {
return {ned[0], ned[1], ned[2]}; return {ned[0], ned[1], ned[2]};
} }
ECEF LocalCoord::ned2ecef(NED n) { ECEF LocalCoord::ned2ecef(const NED &n) {
Eigen::Vector3d ned; Eigen::Vector3d ned;
ned << n.n, n.e, n.d; ned << n.n, n.e, n.d;
@ -89,12 +89,12 @@ ECEF LocalCoord::ned2ecef(NED n) {
return {ecef[0], ecef[1], ecef[2]}; return {ecef[0], ecef[1], ecef[2]};
} }
NED LocalCoord::geodetic2ned(Geodetic g) { NED LocalCoord::geodetic2ned(const Geodetic &g) {
ECEF e = ::geodetic2ecef(g); ECEF e = ::geodetic2ecef(g);
return ecef2ned(e); return ecef2ned(e);
} }
Geodetic LocalCoord::ned2geodetic(NED n){ Geodetic LocalCoord::ned2geodetic(const NED &n) {
ECEF e = ned2ecef(n); ECEF e = ned2ecef(n);
return ::ecef2geodetic(e); return ::ecef2geodetic(e);
} }

@ -7,14 +7,14 @@
struct ECEF { struct ECEF {
double x, y, z; double x, y, z;
Eigen::Vector3d to_vector(){ Eigen::Vector3d to_vector() const {
return Eigen::Vector3d(x, y, z); return Eigen::Vector3d(x, y, z);
} }
}; };
struct NED { struct NED {
double n, e, d; double n, e, d;
Eigen::Vector3d to_vector(){ Eigen::Vector3d to_vector() const {
return Eigen::Vector3d(n, e, d); return Eigen::Vector3d(n, e, d);
} }
}; };
@ -24,20 +24,20 @@ struct Geodetic {
bool radians=false; bool radians=false;
}; };
ECEF geodetic2ecef(Geodetic g); ECEF geodetic2ecef(const Geodetic &g);
Geodetic ecef2geodetic(ECEF e); Geodetic ecef2geodetic(const ECEF &e);
class LocalCoord { class LocalCoord {
public: public:
Eigen::Matrix3d ned2ecef_matrix; Eigen::Matrix3d ned2ecef_matrix;
Eigen::Matrix3d ecef2ned_matrix; Eigen::Matrix3d ecef2ned_matrix;
Eigen::Vector3d init_ecef; Eigen::Vector3d init_ecef;
LocalCoord(Geodetic g, ECEF e); LocalCoord(const Geodetic &g, const ECEF &e);
LocalCoord(Geodetic g) : LocalCoord(g, ::geodetic2ecef(g)) {} LocalCoord(const Geodetic &g) : LocalCoord(g, ::geodetic2ecef(g)) {}
LocalCoord(ECEF e) : LocalCoord(::ecef2geodetic(e), e) {} LocalCoord(const ECEF &e) : LocalCoord(::ecef2geodetic(e), e) {}
NED ecef2ned(ECEF e); NED ecef2ned(const ECEF &e);
ECEF ned2ecef(NED n); ECEF ned2ecef(const NED &n);
NED geodetic2ned(Geodetic g); NED geodetic2ned(const Geodetic &g);
Geodetic ned2geodetic(NED n); Geodetic ned2geodetic(const NED &n);
}; };

@ -7,7 +7,7 @@
#include "common/transformations/orientation.hpp" #include "common/transformations/orientation.hpp"
#include "common/transformations/coordinates.hpp" #include "common/transformations/coordinates.hpp"
Eigen::Quaterniond ensure_unique(Eigen::Quaterniond quat){ Eigen::Quaterniond ensure_unique(const Eigen::Quaterniond &quat) {
if (quat.w() > 0){ if (quat.w() > 0){
return quat; return quat;
} else { } else {
@ -15,7 +15,7 @@ Eigen::Quaterniond ensure_unique(Eigen::Quaterniond quat){
} }
} }
Eigen::Quaterniond euler2quat(Eigen::Vector3d euler){ Eigen::Quaterniond euler2quat(const Eigen::Vector3d &euler) {
Eigen::Quaterniond q; Eigen::Quaterniond q;
q = Eigen::AngleAxisd(euler(2), Eigen::Vector3d::UnitZ()) q = Eigen::AngleAxisd(euler(2), Eigen::Vector3d::UnitZ())
@ -25,7 +25,7 @@ Eigen::Quaterniond euler2quat(Eigen::Vector3d euler){
} }
Eigen::Vector3d quat2euler(Eigen::Quaterniond quat){ Eigen::Vector3d quat2euler(const Eigen::Quaterniond &quat) {
// TODO: switch to eigen implementation if the range of the Euler angles doesn't matter anymore // TODO: switch to eigen implementation if the range of the Euler angles doesn't matter anymore
// Eigen::Vector3d euler = quat.toRotationMatrix().eulerAngles(2, 1, 0); // Eigen::Vector3d euler = quat.toRotationMatrix().eulerAngles(2, 1, 0);
// return {euler(2), euler(1), euler(0)}; // return {euler(2), euler(1), euler(0)};
@ -36,34 +36,34 @@ Eigen::Vector3d quat2euler(Eigen::Quaterniond quat){
return {gamma, theta, psi}; return {gamma, theta, psi};
} }
Eigen::Matrix3d quat2rot(Eigen::Quaterniond quat){ Eigen::Matrix3d quat2rot(const Eigen::Quaterniond &quat) {
return quat.toRotationMatrix(); return quat.toRotationMatrix();
} }
Eigen::Quaterniond rot2quat(const Eigen::Matrix3d &rot){ Eigen::Quaterniond rot2quat(const Eigen::Matrix3d &rot) {
return ensure_unique(Eigen::Quaterniond(rot)); return ensure_unique(Eigen::Quaterniond(rot));
} }
Eigen::Matrix3d euler2rot(Eigen::Vector3d euler){ Eigen::Matrix3d euler2rot(const Eigen::Vector3d &euler) {
return quat2rot(euler2quat(euler)); return quat2rot(euler2quat(euler));
} }
Eigen::Vector3d rot2euler(const Eigen::Matrix3d &rot){ Eigen::Vector3d rot2euler(const Eigen::Matrix3d &rot) {
return quat2euler(rot2quat(rot)); return quat2euler(rot2quat(rot));
} }
Eigen::Matrix3d rot_matrix(double roll, double pitch, double yaw){ Eigen::Matrix3d rot_matrix(double roll, double pitch, double yaw) {
return euler2rot({roll, pitch, yaw}); return euler2rot({roll, pitch, yaw});
} }
Eigen::Matrix3d rot(Eigen::Vector3d axis, double angle){ Eigen::Matrix3d rot(const Eigen::Vector3d &axis, double angle) {
Eigen::Quaterniond q; Eigen::Quaterniond q;
q = Eigen::AngleAxisd(angle, axis); q = Eigen::AngleAxisd(angle, axis);
return q.toRotationMatrix(); return q.toRotationMatrix();
} }
Eigen::Vector3d ecef_euler_from_ned(ECEF ecef_init, Eigen::Vector3d ned_pose) { Eigen::Vector3d ecef_euler_from_ned(const ECEF &ecef_init, const Eigen::Vector3d &ned_pose) {
/* /*
Using Rotations to Build Aerospace Coordinate Systems Using Rotations to Build Aerospace Coordinate Systems
Don Koks Don Koks
@ -103,7 +103,7 @@ Eigen::Vector3d ecef_euler_from_ned(ECEF ecef_init, Eigen::Vector3d ned_pose) {
return {phi, theta, psi}; return {phi, theta, psi};
} }
Eigen::Vector3d ned_euler_from_ecef(ECEF ecef_init, Eigen::Vector3d ecef_pose){ Eigen::Vector3d ned_euler_from_ecef(const ECEF &ecef_init, const Eigen::Vector3d &ecef_pose) {
/* /*
Using Rotations to Build Aerospace Coordinate Systems Using Rotations to Build Aerospace Coordinate Systems
Don Koks Don Koks

@ -3,15 +3,15 @@
#include "common/transformations/coordinates.hpp" #include "common/transformations/coordinates.hpp"
Eigen::Quaterniond ensure_unique(Eigen::Quaterniond quat); Eigen::Quaterniond ensure_unique(const Eigen::Quaterniond &quat);
Eigen::Quaterniond euler2quat(Eigen::Vector3d euler); Eigen::Quaterniond euler2quat(const Eigen::Vector3d &euler);
Eigen::Vector3d quat2euler(Eigen::Quaterniond quat); Eigen::Vector3d quat2euler(const Eigen::Quaterniond &quat);
Eigen::Matrix3d quat2rot(Eigen::Quaterniond quat); Eigen::Matrix3d quat2rot(const Eigen::Quaterniond &quat);
Eigen::Quaterniond rot2quat(const Eigen::Matrix3d &rot); Eigen::Quaterniond rot2quat(const Eigen::Matrix3d &rot);
Eigen::Matrix3d euler2rot(Eigen::Vector3d euler); Eigen::Matrix3d euler2rot(const Eigen::Vector3d &euler);
Eigen::Vector3d rot2euler(const Eigen::Matrix3d &rot); Eigen::Vector3d rot2euler(const Eigen::Matrix3d &rot);
Eigen::Matrix3d rot_matrix(double roll, double pitch, double yaw); Eigen::Matrix3d rot_matrix(double roll, double pitch, double yaw);
Eigen::Matrix3d rot(Eigen::Vector3d axis, double angle); Eigen::Matrix3d rot(const Eigen::Vector3d &axis, double angle);
Eigen::Vector3d ecef_euler_from_ned(ECEF ecef_init, Eigen::Vector3d ned_pose); Eigen::Vector3d ecef_euler_from_ned(const ECEF &ecef_init, const Eigen::Vector3d &ned_pose);
Eigen::Vector3d ned_euler_from_ecef(ECEF ecef_init, Eigen::Vector3d ecef_pose); Eigen::Vector3d ned_euler_from_ecef(const ECEF &ecef_init, const Eigen::Vector3d &ecef_pose);

@ -24,15 +24,15 @@ cdef extern from "orientation.hpp":
double operator()(int, int) double operator()(int, int)
Quaternion euler2quat(Vector3) Quaternion euler2quat(const Vector3 &)
Vector3 quat2euler(Quaternion) Vector3 quat2euler(const Quaternion &)
Matrix3 quat2rot(Quaternion) Matrix3 quat2rot(const Quaternion &)
Quaternion rot2quat(Matrix3) Quaternion rot2quat(const Matrix3 &)
Vector3 rot2euler(Matrix3) Vector3 rot2euler(const Matrix3 &)
Matrix3 euler2rot(Vector3) Matrix3 euler2rot(const Vector3 &)
Matrix3 rot_matrix(double, double, double) Matrix3 rot_matrix(double, double, double)
Vector3 ecef_euler_from_ned(ECEF, Vector3) Vector3 ecef_euler_from_ned(const ECEF &, const Vector3 &)
Vector3 ned_euler_from_ecef(ECEF, Vector3) Vector3 ned_euler_from_ecef(const ECEF &, const Vector3 &)
cdef extern from "coordinates.cc": cdef extern from "coordinates.cc":
@ -52,21 +52,21 @@ cdef extern from "coordinates.cc":
double alt double alt
bool radians bool radians
ECEF geodetic2ecef(Geodetic) ECEF geodetic2ecef(const Geodetic &)
Geodetic ecef2geodetic(ECEF) Geodetic ecef2geodetic(const ECEF &)
cdef cppclass LocalCoord_c "LocalCoord": cdef cppclass LocalCoord_c "LocalCoord":
Matrix3 ned2ecef_matrix Matrix3 ned2ecef_matrix
Matrix3 ecef2ned_matrix Matrix3 ecef2ned_matrix
LocalCoord_c(Geodetic, ECEF) LocalCoord_c(const Geodetic &, const ECEF &)
LocalCoord_c(Geodetic) LocalCoord_c(const Geodetic &)
LocalCoord_c(ECEF) LocalCoord_c(const ECEF &)
NED ecef2ned(ECEF) NED ecef2ned(const ECEF &)
ECEF ned2ecef(NED) ECEF ned2ecef(const NED &)
NED geodetic2ned(Geodetic) NED geodetic2ned(const Geodetic &)
Geodetic ned2geodetic(NED) Geodetic ned2geodetic(const NED &)
cdef extern from "coordinates.hpp": cdef extern from "coordinates.hpp":
pass pass

@ -37,6 +37,8 @@ const double MS_TO_MPH = MS_TO_KPH * KM_TO_MILE;
const double METER_TO_MILE = KM_TO_MILE / 1000.0; const double METER_TO_MILE = KM_TO_MILE / 1000.0;
const double METER_TO_FOOT = 3.28084; const double METER_TO_FOOT = 3.28084;
#define ALIGNED_SIZE(x, align) (((x) + (align)-1) & ~((align)-1))
namespace util { namespace util {
void set_thread_name(const char* name); void set_thread_name(const char* name);

@ -8,6 +8,19 @@ from openpilot.common.prefix import OpenpilotPrefix
from openpilot.system.manager import manager from openpilot.system.manager import manager
from openpilot.system.hardware import TICI, HARDWARE from openpilot.system.hardware import TICI, HARDWARE
# TODO: pytest-cpp doesn't support FAIL, and we need to create test translations in sessionstart
# pending https://github.com/pytest-dev/pytest-cpp/pull/147
collect_ignore = [
"selfdrive/ui/tests/test_translations",
"selfdrive/test/process_replay/test_processes.py",
"selfdrive/test/process_replay/test_regen.py",
"selfdrive/test/test_time_to_onroad.py",
]
collect_ignore_glob = [
"selfdrive/debug/*.py",
"selfdrive/modeld/*.py",
]
def pytest_sessionstart(session): def pytest_sessionstart(session):
# TODO: fix tests and enable test order randomization # TODO: fix tests and enable test order randomization

@ -4,7 +4,7 @@
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
# 287 Supported Cars # 289 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| |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|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@ -50,11 +50,13 @@ A supported vehicle is one that just works when you install a comma device. All
|Genesis|G70 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G70 2022-23">Buy Here</a></sub></details>|| |Genesis|G70 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G70 2022-23">Buy Here</a></sub></details>||
|Genesis|G80 2017|All|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai J connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G80 2017">Buy Here</a></sub></details>|| |Genesis|G80 2017|All|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai J connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G80 2017">Buy Here</a></sub></details>||
|Genesis|G80 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G80 2018-19">Buy Here</a></sub></details>|| |Genesis|G80 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G80 2018-19">Buy Here</a></sub></details>||
|Genesis|G80 (2.5T Advanced Trim, with HDA II) 2024[<sup>5</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G80 (2.5T Advanced Trim, with HDA II) 2024">Buy Here</a></sub></details>||
|Genesis|G90 2017-20|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G90 2017-20">Buy Here</a></sub></details>|| |Genesis|G90 2017-20|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G90 2017-20">Buy Here</a></sub></details>||
|Genesis|GV60 (Advanced Trim) 2023[<sup>5</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 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=GV60 (Advanced Trim) 2023">Buy Here</a></sub></details>|| |Genesis|GV60 (Advanced Trim) 2023[<sup>5</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 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=GV60 (Advanced Trim) 2023">Buy Here</a></sub></details>||
|Genesis|GV60 (Performance Trim) 2023[<sup>5</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 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=GV60 (Performance Trim) 2023">Buy Here</a></sub></details>|| |Genesis|GV60 (Performance Trim) 2022-23[<sup>5</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 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=GV60 (Performance Trim) 2022-23">Buy Here</a></sub></details>||
|Genesis|GV70 (2.5T Trim, without HDA II) 2022-23[<sup>5</sup>](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=GV70 (2.5T Trim, without HDA II) 2022-23">Buy Here</a></sub></details>|| |Genesis|GV70 (2.5T Trim, without HDA II) 2022-23[<sup>5</sup>](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=GV70 (2.5T Trim, without HDA II) 2022-23">Buy Here</a></sub></details>||
|Genesis|GV70 (3.5T Trim, without HDA II) 2022-23[<sup>5</sup>](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=GV70 (3.5T Trim, without HDA II) 2022-23">Buy Here</a></sub></details>|| |Genesis|GV70 (3.5T Trim, without HDA II) 2022-23[<sup>5</sup>](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=GV70 (3.5T Trim, without HDA II) 2022-23">Buy Here</a></sub></details>||
|Genesis|GV70 Electrified (with HDA II) 2023[<sup>5</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=GV70 Electrified (with HDA II) 2023">Buy Here</a></sub></details>||
|Genesis|GV80 2023[<sup>5</sup>](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=GV80 2023">Buy Here</a></sub></details>|| |Genesis|GV80 2023[<sup>5</sup>](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=GV80 2023">Buy Here</a></sub></details>||
|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=GMC&model=Sierra 1500 2020-21">Buy Here</a></sub></details>|<a href="https://youtu.be/5HbNoBLzRwE" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=GMC&model=Sierra 1500 2020-21">Buy Here</a></sub></details>|<a href="https://youtu.be/5HbNoBLzRwE" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Honda|Accord 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Accord 2018-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=mrUwlj3Mi58" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Honda|Accord 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 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Accord 2018-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=mrUwlj3Mi58" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
@ -126,7 +128,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Kia|Carnival 2022-24[<sup>5</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Carnival 2022-24">Buy Here</a></sub></details>|| |Kia|Carnival 2022-24[<sup>5</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Carnival 2022-24">Buy Here</a></sub></details>||
|Kia|Carnival (China only) 2023[<sup>5</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Carnival (China only) 2023">Buy Here</a></sub></details>|| |Kia|Carnival (China only) 2023[<sup>5</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Carnival (China only) 2023">Buy Here</a></sub></details>||
|Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Ceed 2019">Buy Here</a></sub></details>|| |Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Ceed 2019">Buy Here</a></sub></details>||
|Kia|EV6 (Non-US only) 2022-24[<sup>5</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 Hyundai P connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=EV6 (Non-US only) 2022-24">Buy Here</a></sub></details>|| |Kia|EV6 (Southeast Asia only) 2022-24[<sup>5</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 Hyundai P connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=EV6 (Southeast Asia only) 2022-24">Buy Here</a></sub></details>||
|Kia|EV6 (with HDA II) 2022-24[<sup>5</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=EV6 (with HDA II) 2022-24">Buy Here</a></sub></details>|| |Kia|EV6 (with HDA II) 2022-24[<sup>5</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=EV6 (with HDA II) 2022-24">Buy Here</a></sub></details>||
|Kia|EV6 (without HDA II) 2022-24[<sup>5</sup>](#footnotes)|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=EV6 (without HDA II) 2022-24">Buy Here</a></sub></details>|| |Kia|EV6 (without HDA II) 2022-24[<sup>5</sup>](#footnotes)|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=EV6 (without HDA II) 2022-24">Buy Here</a></sub></details>||
|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Forte 2019-21">Buy Here</a></sub></details>|| |Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Forte 2019-21">Buy Here</a></sub></details>||
@ -279,8 +281,8 @@ A supported vehicle is one that just works when you install a comma device. All
|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</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<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Golf R 2015-19">Buy Here</a></sub></details>|| |Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</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<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Golf R 2015-19">Buy Here</a></sub></details>||
|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</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<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Golf SportsVan 2015-20">Buy Here</a></sub></details>|| |Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</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<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Golf SportsVan 2015-20">Buy Here</a></sub></details>||
|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|31 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 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Grand California 2019-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|31 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 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Grand California 2019-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Volkswagen|Jetta 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</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<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Jetta 2018-24">Buy Here</a></sub></details>|| |Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</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<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Jetta 2018-23">Buy Here</a></sub></details>||
|Volkswagen|Jetta GLI 2021-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</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<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Jetta GLI 2021-24">Buy Here</a></sub></details>|| |Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</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<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Jetta GLI 2021-23">Buy Here</a></sub></details>||
|Volkswagen|Passat 2015-22[<sup>10</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</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<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Passat 2015-22">Buy Here</a></sub></details>|| |Volkswagen|Passat 2015-22[<sup>10</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</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<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Passat 2015-22">Buy Here</a></sub></details>||
|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</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<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Passat Alltrack 2015-22">Buy Here</a></sub></details>|| |Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</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<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Passat Alltrack 2015-22">Buy Here</a></sub></details>||
|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</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<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Passat GTE 2015-22">Buy Here</a></sub></details>|| |Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</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<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Passat GTE 2015-22">Buy Here</a></sub></details>||

@ -39,7 +39,7 @@ All of these are examples of good PRs:
### First contribution ### First contribution
[Bounties](https://comma.ai/bounties) are the best place to get started. [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 3/3X or a car.
## Pull Requests ## Pull Requests

@ -1,22 +1,39 @@
# What is a car port? # What is a car port?
A car port enables openpilot support on a particular car. Each car model openpilot supports needs to be individually ported. All car ports live in `openpilot/selfdrive/car/car_specific.py` and `opendbc_repo/opendbc/car`. A car port enables openpilot support on a particular car. Each car model openpilot supports needs to be individually ported. The complexity of a car port varies depending on many factors including:
The complexity of a car port varies depending on many factors including:
* existing openpilot support for similar cars * existing openpilot support for similar cars
* architecture and APIs available in the car * architecture and APIs available in the car
# Structure of a car port # Structure of a car port
Virtually all car-specific code is contained in two other repositories: [opendbc](https://github.com/commaai/opendbc) and [panda](https://github.com/commaai/panda).
## opendbc
Each car brand is supported by a standard interface structure in `opendbc/car/[brand]`:
* `interface.py`: Interface for the car, defines the CarInterface class * `interface.py`: Interface for the car, defines the CarInterface class
* `carstate.py`: Reads CAN from car and builds openpilot CarState message * `carstate.py`: Reads CAN messages from the car and builds openpilot CarState messages
* `carcontroller.py`: Builds CAN messages to send to car * `carcontroller.py`: Control logic for executing openpilot CarControl actions on the car
* `[brand]can.py`: Composes CAN messages for carcontroller to send
* `values.py`: Limits for actuation, general constants for cars, and supported car documentation * `values.py`: Limits for actuation, general constants for cars, and supported car documentation
* `radar_interface.py`: Interface for parsing radar points from the car * `radar_interface.py`: Interface for parsing radar points from the car, if applicable
## panda
* `board/safety/safety_[brand].h`: Brand-specific safety logic
* `tests/safety/test_[brand].py`: Brand-specific safety CI tests
## openpilot
For historical reasons, openpilot still contains a small amount of car-specific logic. This will eventually be migrated to opendbc or otherwise removed.
* `selfdrive/car/car_specific.py`: Brand-specific event logic
# Overiew # Overview
[Jason Young](https://github.com/jyoung8607) gave a talk at COMMA_CON with an overview of the car porting process. The talk is available on YouTube: [Jason Young](https://github.com/jyoung8607) gave a talk at COMMA_CON with an overview of the car porting process. The talk is available on YouTube:
https://youtu.be/KcfzEHB6ms4?si=5szh1PX6TksOCKmM https://www.youtube.com/watch?v=XxPS5TpTUnI

@ -1 +1 @@
Subproject commit ff198e9af1cea1ab93d24b01beeb04ab3f2de742 Subproject commit 739117d561b265de5caf843c3f117490a9de57e6

@ -1 +1 @@
Subproject commit aac60b8a7938d4d32c0c02608058022e4d876156 Subproject commit 08c95bf47ba6d0c5d11a7616e42e10b8dbde19eb

@ -55,6 +55,9 @@ dependencies = [
"casadi >=3.6.6", # 3.12 fixed in 3.6.6 "casadi >=3.6.6", # 3.12 fixed in 3.6.6
"future-fstrings", "future-fstrings",
# joystickd
"inputs",
# these should be removed # these should be removed
"psutil", "psutil",
"pycryptodome", # used in updated/casync, panda, body, and a test "pycryptodome", # used in updated/casync, panda, body, and a test
@ -96,10 +99,8 @@ dev = [
"azure-storage-blob", "azure-storage-blob",
"dictdiffer", "dictdiffer",
"flaky", "flaky",
"inputs",
"lru-dict", "lru-dict",
"matplotlib", "matplotlib",
"metadrive-simulator @ https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal/metadrive_simulator-0.4.2.3-py3-none-any.whl ; (platform_machine != 'aarch64')",
"parameterized >=0.8, <0.9", "parameterized >=0.8, <0.9",
#"pprofile", #"pprofile",
"pyautogui", "pyautogui",
@ -107,7 +108,6 @@ dev = [
"pytools < 2024.1.11; platform_machine != 'aarch64'", # pyopencl use a broken version "pytools < 2024.1.11; platform_machine != 'aarch64'", # pyopencl use a broken version
"pywinctl", "pywinctl",
"pyprof2calltree", "pyprof2calltree",
"rerun-sdk >= 0.18",
"tabulate", "tabulate",
"types-requests", "types-requests",
"types-tabulate", "types-tabulate",
@ -116,6 +116,11 @@ dev = [
"pyqt5 ==5.15.2; platform_machine == 'x86_64'", # no aarch64 wheels for macOS/linux "pyqt5 ==5.15.2; platform_machine == 'x86_64'", # no aarch64 wheels for macOS/linux
] ]
tools = [
"metadrive-simulator @ https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal/metadrive_simulator-0.4.2.3-py3-none-any.whl ; (platform_machine != 'aarch64')",
"rerun-sdk >= 0.18",
]
[project.urls] [project.urls]
Homepage = "https://comma.ai" Homepage = "https://comma.ai"
@ -143,14 +148,7 @@ markers = [
] ]
testpaths = [ testpaths = [
"common", "common",
"selfdrive/pandad", "selfdrive",
"selfdrive/car",
"selfdrive/opcar",
"selfdrive/controls",
"selfdrive/locationd",
"selfdrive/monitoring",
"selfdrive/test/longitudinal_maneuvers",
"selfdrive/test/process_replay/test_fuzzy.py",
"system/updated", "system/updated",
"system/athena", "system/athena",
"system/camerad", "system/camerad",

@ -52,6 +52,8 @@ blacklist = [
whitelist = [ whitelist = [
"tools/lib/", "tools/lib/",
"tools/bodyteleop/", "tools/bodyteleop/",
"tools/joystick/",
"tools/longitudinal_maneuvers/",
"tinygrad_repo/openpilot/compile2.py", "tinygrad_repo/openpilot/compile2.py",
"tinygrad_repo/extra/onnx.py", "tinygrad_repo/extra/onnx.py",

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a3cd3b91673eded1282e2082be0efa8e54ed477b5feb3580e521d08078e18ed1
size 42640

@ -1,19 +0,0 @@
## Car port structure
### interface.py
Generic interface to send and receive messages from CAN (controlsd uses this to communicate with car)
### fingerprints.py
Fingerprints for matching to a specific car
### carcontroller.py
Builds CAN messages to send to car
##### carstate.py
Reads CAN from car and builds openpilot CarState message
##### values.py
Limits for actuation, general constants for cars, and supported car documentation
##### radar_interface.py
Interface for parsing radar points from the car

@ -1,15 +1,17 @@
from cereal import car from collections import deque
from cereal import car, log
import cereal.messaging as messaging import cereal.messaging as messaging
from opendbc.car import DT_CTRL, structs from opendbc.car import DT_CTRL, structs
from opendbc.car.interfaces import MAX_CTRL_SPEED, CarStateBase, CarControllerBase from opendbc.car.interfaces import MAX_CTRL_SPEED
from opendbc.car.volkswagen.values import CarControllerParams as VWCarControllerParams from opendbc.car.volkswagen.values import CarControllerParams as VWCarControllerParams
from opendbc.car.hyundai.interface import ENABLE_BUTTONS as HYUNDAI_ENABLE_BUTTONS from opendbc.car.hyundai.interface import ENABLE_BUTTONS as HYUNDAI_ENABLE_BUTTONS
from opendbc.car.hyundai.carstate import PREV_BUTTON_SAMPLES as HYUNDAI_PREV_BUTTON_SAMPLES
from openpilot.selfdrive.controls.lib.events import Events from openpilot.selfdrive.selfdrived.events import Events
ButtonType = structs.CarState.ButtonEvent.Type ButtonType = structs.CarState.ButtonEvent.Type
GearShifter = structs.CarState.GearShifter GearShifter = structs.CarState.GearShifter
EventName = car.OnroadEvent.EventName EventName = log.OnroadEvent.EventName
NetworkLocation = structs.CarParams.NetworkLocation NetworkLocation = structs.CarParams.NetworkLocation
@ -37,132 +39,121 @@ class CarSpecificEvents:
self.no_steer_warning = False self.no_steer_warning = False
self.silent_steer_warning = True self.silent_steer_warning = True
def update(self, CS: CarStateBase, CS_prev: car.CarState, CC: CarControllerBase, CC_prev: car.CarControl): self.cruise_buttons: deque = deque([], maxlen=HYUNDAI_PREV_BUTTON_SAMPLES)
def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl):
if self.CP.carName in ('body', 'mock'): if self.CP.carName in ('body', 'mock'):
events = Events() events = Events()
elif self.CP.carName == 'subaru': elif self.CP.carName in ('subaru', 'mazda'):
events = self.create_common_events(CS.out, CS_prev) events = self.create_common_events(CS, CS_prev)
elif self.CP.carName == 'ford': elif self.CP.carName == 'ford':
events = self.create_common_events(CS.out, CS_prev, extra_gears=[GearShifter.manumatic]) events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.manumatic])
elif self.CP.carName == 'nissan': elif self.CP.carName == 'nissan':
events = self.create_common_events(CS.out, CS_prev, extra_gears=[GearShifter.brake]) events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.brake])
if CS.lkas_enabled: # type: ignore[attr-defined]
events.add(EventName.invalidLkasSetting)
elif self.CP.carName == 'mazda':
events = self.create_common_events(CS.out, CS_prev)
if CS.lkas_disabled: # type: ignore[attr-defined]
events.add(EventName.lkasDisabled)
elif CS.low_speed_alert: # type: ignore[attr-defined]
events.add(EventName.belowSteerSpeed)
elif self.CP.carName == 'chrysler': elif self.CP.carName == 'chrysler':
events = self.create_common_events(CS.out, CS_prev, extra_gears=[GearShifter.low]) events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.low])
# Low speed steer alert hysteresis logic # Low speed steer alert hysteresis logic
if self.CP.minSteerSpeed > 0. and CS.out.vEgo < (self.CP.minSteerSpeed + 0.5): if self.CP.minSteerSpeed > 0. and CS.vEgo < (self.CP.minSteerSpeed + 0.5):
self.low_speed_alert = True self.low_speed_alert = True
elif CS.out.vEgo > (self.CP.minSteerSpeed + 1.): elif CS.vEgo > (self.CP.minSteerSpeed + 1.):
self.low_speed_alert = False self.low_speed_alert = False
if self.low_speed_alert: if self.low_speed_alert:
events.add(EventName.belowSteerSpeed) events.add(EventName.belowSteerSpeed)
elif self.CP.carName == 'honda': elif self.CP.carName == 'honda':
events = self.create_common_events(CS.out, CS_prev, pcm_enable=False) events = self.create_common_events(CS, CS_prev, pcm_enable=False)
if self.CP.pcmCruise and CS.out.vEgo < self.CP.minEnableSpeed: if self.CP.pcmCruise and CS.vEgo < self.CP.minEnableSpeed:
events.add(EventName.belowEngageSpeed) events.add(EventName.belowEngageSpeed)
if self.CP.pcmCruise: if self.CP.pcmCruise:
# we engage when pcm is active (rising edge) # we engage when pcm is active (rising edge)
if CS.out.cruiseState.enabled and not CS_prev.cruiseState.enabled: if CS.cruiseState.enabled and not CS_prev.cruiseState.enabled:
events.add(EventName.pcmEnable) events.add(EventName.pcmEnable)
elif not CS.out.cruiseState.enabled and (CC_prev.actuators.accel >= 0. or not self.CP.openpilotLongitudinalControl): elif not CS.cruiseState.enabled and (CC.actuators.accel >= 0. or not self.CP.openpilotLongitudinalControl):
# it can happen that car cruise disables while comma system is enabled: need to # it can happen that car cruise disables while comma system is enabled: need to
# keep braking if needed or if the speed is very low # keep braking if needed or if the speed is very low
if CS.out.vEgo < self.CP.minEnableSpeed + 2.: if CS.vEgo < self.CP.minEnableSpeed + 2.:
# non loud alert if cruise disables below 25mph as expected (+ a little margin) # non loud alert if cruise disables below 25mph as expected (+ a little margin)
events.add(EventName.speedTooLow) events.add(EventName.speedTooLow)
else: else:
events.add(EventName.cruiseDisabled) events.add(EventName.cruiseDisabled)
if self.CP.minEnableSpeed > 0 and CS.out.vEgo < 0.001: if self.CP.minEnableSpeed > 0 and CS.vEgo < 0.001:
events.add(EventName.manualRestart) events.add(EventName.manualRestart)
elif self.CP.carName == 'toyota': elif self.CP.carName == 'toyota':
events = self.create_common_events(CS.out, CS_prev) events = self.create_common_events(CS, CS_prev)
if self.CP.openpilotLongitudinalControl: if self.CP.openpilotLongitudinalControl:
if CS.out.cruiseState.standstill and not CS.out.brakePressed: if CS.cruiseState.standstill and not CS.brakePressed:
events.add(EventName.resumeRequired) events.add(EventName.resumeRequired)
if CS.low_speed_lockout: # type: ignore[attr-defined] if CS.vEgo < self.CP.minEnableSpeed:
events.add(EventName.lowSpeedLockout)
if CS.out.vEgo < self.CP.minEnableSpeed:
events.add(EventName.belowEngageSpeed) events.add(EventName.belowEngageSpeed)
if CC_prev.actuators.accel > 0.3: if CC.actuators.accel > 0.3:
# some margin on the actuator to not false trigger cancellation while stopping # some margin on the actuator to not false trigger cancellation while stopping
events.add(EventName.speedTooLow) events.add(EventName.speedTooLow)
if CS.out.vEgo < 0.001: if CS.vEgo < 0.001:
# while in standstill, send a user alert # while in standstill, send a user alert
events.add(EventName.manualRestart) events.add(EventName.manualRestart)
elif self.CP.carName == 'gm': elif self.CP.carName == 'gm':
# The ECM allows enabling on falling edge of set, but only rising edge of resume # The ECM allows enabling on falling edge of set, but only rising edge of resume
events = self.create_common_events(CS.out, CS_prev, extra_gears=[GearShifter.sport, GearShifter.low, events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.sport, GearShifter.low,
GearShifter.eco, GearShifter.manumatic], GearShifter.eco, GearShifter.manumatic],
pcm_enable=self.CP.pcmCruise, enable_buttons=(ButtonType.decelCruise,)) pcm_enable=self.CP.pcmCruise, enable_buttons=(ButtonType.decelCruise,))
if not self.CP.pcmCruise: if not self.CP.pcmCruise:
if any(b.type == ButtonType.accelCruise and b.pressed for b in CS.out.buttonEvents): if any(b.type == ButtonType.accelCruise and b.pressed for b in CS.buttonEvents):
events.add(EventName.buttonEnable) events.add(EventName.buttonEnable)
# Enabling at a standstill with brake is allowed # Enabling at a standstill with brake is allowed
# TODO: verify 17 Volt can enable for the first time at a stop and allow for all GMs # TODO: verify 17 Volt can enable for the first time at a stop and allow for all GMs
below_min_enable_speed = CS.out.vEgo < self.CP.minEnableSpeed or CS.moving_backward # type: ignore[attr-defined] if CS.vEgo < self.CP.minEnableSpeed and not (CS.standstill and CS.brake >= 20 and
if below_min_enable_speed and not (CS.out.standstill and CS.out.brake >= 20 and
self.CP.networkLocation == NetworkLocation.fwdCamera): self.CP.networkLocation == NetworkLocation.fwdCamera):
events.add(EventName.belowEngageSpeed) events.add(EventName.belowEngageSpeed)
if CS.out.cruiseState.standstill: if CS.cruiseState.standstill:
events.add(EventName.resumeRequired) events.add(EventName.resumeRequired)
if CS.out.vEgo < self.CP.minSteerSpeed: if CS.vEgo < self.CP.minSteerSpeed:
events.add(EventName.belowSteerSpeed) events.add(EventName.belowSteerSpeed)
elif self.CP.carName == 'volkswagen': elif self.CP.carName == 'volkswagen':
events = self.create_common_events(CS.out, CS_prev, extra_gears=[GearShifter.eco, GearShifter.sport, GearShifter.manumatic], events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.eco, GearShifter.sport, GearShifter.manumatic],
pcm_enable=not self.CP.openpilotLongitudinalControl, pcm_enable=not self.CP.openpilotLongitudinalControl,
enable_buttons=(ButtonType.setCruise, ButtonType.resumeCruise)) enable_buttons=(ButtonType.setCruise, ButtonType.resumeCruise))
# Low speed steer alert hysteresis logic # Low speed steer alert hysteresis logic
if (self.CP.minSteerSpeed - 1e-3) > VWCarControllerParams.DEFAULT_MIN_STEER_SPEED and CS.out.vEgo < (self.CP.minSteerSpeed + 1.): if (self.CP.minSteerSpeed - 1e-3) > VWCarControllerParams.DEFAULT_MIN_STEER_SPEED and CS.vEgo < (self.CP.minSteerSpeed + 1.):
self.low_speed_alert = True self.low_speed_alert = True
elif CS.out.vEgo > (self.CP.minSteerSpeed + 2.): elif CS.vEgo > (self.CP.minSteerSpeed + 2.):
self.low_speed_alert = False self.low_speed_alert = False
if self.low_speed_alert: if self.low_speed_alert:
events.add(EventName.belowSteerSpeed) events.add(EventName.belowSteerSpeed)
if self.CP.openpilotLongitudinalControl: if self.CP.openpilotLongitudinalControl:
if CS.out.vEgo < self.CP.minEnableSpeed + 0.5: if CS.vEgo < self.CP.minEnableSpeed + 0.5:
events.add(EventName.belowEngageSpeed) events.add(EventName.belowEngageSpeed)
if CC_prev.enabled and CS.out.vEgo < self.CP.minEnableSpeed: if CC.enabled and CS.vEgo < self.CP.minEnableSpeed:
events.add(EventName.speedTooLow) events.add(EventName.speedTooLow)
if CC.eps_timer_soft_disable_alert: # type: ignore[attr-defined] # TODO: this needs to be implemented generically in carState struct
events.add(EventName.steerTimeLimit) # if CC.eps_timer_soft_disable_alert: # type: ignore[attr-defined]
# events.add(EventName.steerTimeLimit)
elif self.CP.carName == 'hyundai': elif self.CP.carName == 'hyundai':
# On some newer model years, the CANCEL button acts as a pause/resume button based on the PCM state # On some newer model years, the CANCEL button acts as a pause/resume button based on the PCM state
# To avoid re-engaging when openpilot cancels, check user engagement intention via buttons # To avoid re-engaging when openpilot cancels, check user engagement intention via buttons
# Main button also can trigger an engagement on these cars # Main button also can trigger an engagement on these cars
allow_enable = any(btn in HYUNDAI_ENABLE_BUTTONS for btn in CS.cruise_buttons) or any(CS.main_buttons) # type: ignore[attr-defined] self.cruise_buttons.append(any(ev.type in HYUNDAI_ENABLE_BUTTONS for ev in CS.buttonEvents))
events = self.create_common_events(CS.out, CS_prev, pcm_enable=self.CP.pcmCruise, allow_enable=allow_enable) events = self.create_common_events(CS, CS_prev, pcm_enable=self.CP.pcmCruise, allow_enable=any(self.cruise_buttons))
# low speed steer alert hysteresis logic (only for cars with steer cut off above 10 m/s) # low speed steer alert hysteresis logic (only for cars with steer cut off above 10 m/s)
if CS.out.vEgo < (self.CP.minSteerSpeed + 2.) and self.CP.minSteerSpeed > 10.: if CS.vEgo < (self.CP.minSteerSpeed + 2.) and self.CP.minSteerSpeed > 10.:
self.low_speed_alert = True self.low_speed_alert = True
if CS.out.vEgo > (self.CP.minSteerSpeed + 4.): if CS.vEgo > (self.CP.minSteerSpeed + 4.):
self.low_speed_alert = False self.low_speed_alert = False
if self.low_speed_alert: if self.low_speed_alert:
events.add(EventName.belowSteerSpeed) events.add(EventName.belowSteerSpeed)
@ -213,6 +204,10 @@ class CarSpecificEvents:
events.add(EventName.gasPressedOverride) events.add(EventName.gasPressedOverride)
if CS.vehicleSensorsInvalid: if CS.vehicleSensorsInvalid:
events.add(EventName.vehicleSensorsInvalid) events.add(EventName.vehicleSensorsInvalid)
if CS.invalidLkasSetting:
events.add(EventName.invalidLkasSetting)
if CS.lowSpeedAlert:
events.add(EventName.belowSteerSpeed)
# Handle button presses # Handle button presses
for b in CS.buttonEvents: for b in CS.buttonEvents:

@ -5,7 +5,7 @@ import threading
import cereal.messaging as messaging import cereal.messaging as messaging
from cereal import car from cereal import car, log
from panda import ALTERNATIVE_EXPERIENCE from panda import ALTERNATIVE_EXPERIENCE
@ -16,17 +16,15 @@ from openpilot.common.swaglog import cloudlog, ForwardingHandler
from opendbc.car import DT_CTRL, carlog, structs from opendbc.car import DT_CTRL, carlog, structs
from opendbc.car.can_definitions import CanData, CanRecvCallable, CanSendCallable from opendbc.car.can_definitions import CanData, CanRecvCallable, CanSendCallable
from opendbc.car.fw_versions import ObdCallback from opendbc.car.fw_versions import ObdCallback
from opendbc.car.car_helpers import get_car from opendbc.car.car_helpers import get_car, get_radar_interface
from opendbc.car.interfaces import CarInterfaceBase from opendbc.car.interfaces import CarInterfaceBase, RadarInterfaceBase
from openpilot.selfdrive.pandad import can_capnp_to_list, can_list_to_can_capnp from openpilot.selfdrive.pandad import can_capnp_to_list, can_list_to_can_capnp
from openpilot.selfdrive.car.cruise import VCruiseHelper from openpilot.selfdrive.car.cruise import VCruiseHelper
from openpilot.selfdrive.car.car_specific import CarSpecificEvents, MockCarState from openpilot.selfdrive.car.car_specific import MockCarState
from openpilot.selfdrive.car.helpers import convert_carControl, convert_to_capnp
from openpilot.selfdrive.controls.lib.events import Events, ET
REPLAY = "REPLAY" in os.environ REPLAY = "REPLAY" in os.environ
EventName = car.OnroadEvent.EventName EventName = log.OnroadEvent.EventName
# forward # forward
carlog.addHandler(ForwardingHandler(cloudlog)) carlog.addHandler(ForwardingHandler(cloudlog))
@ -63,13 +61,13 @@ def can_comm_callbacks(logcan: messaging.SubSocket, sendcan: messaging.PubSocket
class Car: class Car:
CI: CarInterfaceBase CI: CarInterfaceBase
CP: structs.CarParams RI: RadarInterfaceBase
CP_capnp: car.CarParams CP: car.CarParams
def __init__(self, CI=None) -> None: def __init__(self, CI=None, RI=None) -> None:
self.can_sock = messaging.sub_sock('can', timeout=20) self.can_sock = messaging.sub_sock('can', timeout=20)
self.sm = messaging.SubMaster(['pandaStates', 'carControl', 'onroadEvents']) self.sm = messaging.SubMaster(['pandaStates', 'carControl', 'onroadEvents'])
self.pm = messaging.PubMaster(['sendcan', 'carState', 'carParams', 'carOutput']) self.pm = messaging.PubMaster(['sendcan', 'carState', 'carParams', 'carOutput', 'liveTracks'])
self.can_rcv_cum_timeout_counter = 0 self.can_rcv_cum_timeout_counter = 0
@ -98,20 +96,22 @@ class Car:
cached_params_raw = self.params.get("CarParamsCache") cached_params_raw = self.params.get("CarParamsCache")
if cached_params_raw is not None: if cached_params_raw is not None:
with car.CarParams.from_bytes(cached_params_raw) as _cached_params: with car.CarParams.from_bytes(cached_params_raw) as _cached_params:
cached_params = structs.CarParams(carName=_cached_params.carName, carFw=_cached_params.carFw, carVin=_cached_params.carVin) cached_params = _cached_params
self.CI = get_car(*self.can_callbacks, obd_callback(self.params), experimental_long_allowed, num_pandas, cached_params) self.CI = get_car(*self.can_callbacks, obd_callback(self.params), experimental_long_allowed, num_pandas, cached_params)
self.RI = get_radar_interface(self.CI.CP)
self.CP = self.CI.CP self.CP = self.CI.CP
# continue onto next fingerprinting step in pandad # continue onto next fingerprinting step in pandad
self.params.put_bool("FirmwareQueryDone", True) self.params.put_bool("FirmwareQueryDone", True)
else: else:
self.CI, self.CP = CI, CI.CP self.CI, self.CP = CI, CI.CP
self.RI = RI
# set alternative experiences from parameters # set alternative experiences from parameters
self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
self.CP.alternativeExperience = 0 self.CP.alternativeExperience = 0
if not self.disengage_on_accelerator: if not disengage_on_accelerator:
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
openpilot_enabled_toggle = self.params.get_bool("OpenpilotEnabledToggle") openpilot_enabled_toggle = self.params.get_bool("OpenpilotEnabledToggle")
@ -124,22 +124,29 @@ class Car:
safety_config.safetyModel = structs.CarParams.SafetyModel.noOutput safety_config.safetyModel = structs.CarParams.SafetyModel.noOutput
self.CP.safetyConfigs = [safety_config] self.CP.safetyConfigs = [safety_config]
if self.CP.secOcRequired and not self.params.get_bool("IsReleaseBranch"):
secoc_key = self.params.get("SecOCKey", encoding='utf8')
if secoc_key is not None:
saved_secoc_key = bytes.fromhex(secoc_key.strip())
if len(saved_secoc_key) == 16:
self.CP.secOcKeyAvailable = True
self.CI.CS.secoc_key = saved_secoc_key
if controller_available:
self.CI.CC.secoc_key = saved_secoc_key
else:
cloudlog.warning("Saved SecOC key is invalid")
# Write previous route's CarParams # Write previous route's CarParams
prev_cp = self.params.get("CarParamsPersistent") prev_cp = self.params.get("CarParamsPersistent")
if prev_cp is not None: if prev_cp is not None:
self.params.put("CarParamsPrevRoute", prev_cp) self.params.put("CarParamsPrevRoute", prev_cp)
# Write CarParams for controls and radard # Write CarParams for controls and radard
# convert to pycapnp representation for caching and logging cp_bytes = self.CP.to_bytes()
self.CP_capnp = convert_to_capnp(self.CP)
cp_bytes = self.CP_capnp.to_bytes()
self.params.put("CarParams", cp_bytes) self.params.put("CarParams", cp_bytes)
self.params.put_nonblocking("CarParamsCache", cp_bytes) self.params.put_nonblocking("CarParamsCache", cp_bytes)
self.params.put_nonblocking("CarParamsPersistent", cp_bytes) self.params.put_nonblocking("CarParamsPersistent", cp_bytes)
self.events = Events()
self.car_events = CarSpecificEvents(self.CP)
self.mock_carstate = MockCarState() self.mock_carstate = MockCarState()
self.v_cruise_helper = VCruiseHelper(self.CP) self.v_cruise_helper = VCruiseHelper(self.CP)
@ -149,16 +156,20 @@ class Car:
# card is driven by can recv, expected at 100Hz # card is driven by can recv, expected at 100Hz
self.rk = Ratekeeper(100, print_delay_threshold=None) self.rk = Ratekeeper(100, print_delay_threshold=None)
def state_update(self) -> car.CarState: def state_update(self) -> tuple[car.CarState, structs.RadarDataT | None]:
"""carState update loop, driven by can""" """carState update loop, driven by can"""
# Update carState from CAN
can_strs = messaging.drain_sock_raw(self.can_sock, wait_for_one=True) can_strs = messaging.drain_sock_raw(self.can_sock, wait_for_one=True)
CS = convert_to_capnp(self.CI.update(can_capnp_to_list(can_strs))) can_list = can_capnp_to_list(can_strs)
# Update carState from CAN
CS = self.CI.update(can_list)
if self.CP.carName == 'mock': if self.CP.carName == 'mock':
CS = self.mock_carstate.update(CS) CS = self.mock_carstate.update(CS)
# Update radar tracks from CAN
RD: structs.RadarDataT | None = self.RI.update(can_list)
self.sm.update(0) self.sm.update(0)
can_rcv_valid = len(can_strs) > 0 can_rcv_valid = len(can_strs) > 0
@ -175,43 +186,22 @@ class Car:
CS.vCruise = float(self.v_cruise_helper.v_cruise_kph) CS.vCruise = float(self.v_cruise_helper.v_cruise_kph)
CS.vCruiseCluster = float(self.v_cruise_helper.v_cruise_cluster_kph) CS.vCruiseCluster = float(self.v_cruise_helper.v_cruise_cluster_kph)
return CS return CS, RD
def update_events(self, CS: car.CarState):
self.events.clear()
CS.events = self.car_events.update(self.CI.CS, self.CS_prev, self.CI.CC, self.CC_prev).to_msg() def state_publish(self, CS: car.CarState, RD: structs.RadarDataT | None):
self.events.add_from_msg(CS.events)
if self.CP.notCar:
# wait for everything to init first
if self.sm.frame > int(5. / DT_CTRL) and self.initialized_prev:
# body always wants to enable
self.events.add(EventName.pcmEnable)
# Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0
if (CS.gasPressed and not self.CS_prev.gasPressed and self.disengage_on_accelerator) or \
(CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or \
(CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)):
self.events.add(EventName.pedalPressed)
CS.events = self.events.to_msg()
def state_publish(self, CS: car.CarState):
"""carState and carParams publish loop""" """carState and carParams publish loop"""
# carParams - logged every 50 seconds (> 1 per segment) # carParams - logged every 50 seconds (> 1 per segment)
if self.sm.frame % int(50. / DT_CTRL) == 0: if self.sm.frame % int(50. / DT_CTRL) == 0:
cp_send = messaging.new_message('carParams') cp_send = messaging.new_message('carParams')
cp_send.valid = True cp_send.valid = True
cp_send.carParams = self.CP_capnp cp_send.carParams = self.CP
self.pm.send('carParams', cp_send) self.pm.send('carParams', cp_send)
# publish new carOutput # publish new carOutput
co_send = messaging.new_message('carOutput') co_send = messaging.new_message('carOutput')
co_send.valid = self.sm.all_checks(['carControl']) co_send.valid = self.sm.all_checks(['carControl'])
co_send.carOutput.actuatorsOutput = convert_to_capnp(self.last_actuators_output) co_send.carOutput.actuatorsOutput = self.last_actuators_output
self.pm.send('carOutput', co_send) self.pm.send('carOutput', co_send)
# kick off controlsd step while we actuate the latest carControl packet # kick off controlsd step while we actuate the latest carControl packet
@ -222,6 +212,12 @@ class Car:
cs_send.carState.cumLagMs = -self.rk.remaining * 1000. cs_send.carState.cumLagMs = -self.rk.remaining * 1000.
self.pm.send('carState', cs_send) self.pm.send('carState', cs_send)
if RD is not None:
tracks_msg = messaging.new_message('liveTracks')
tracks_msg.valid = len(RD.errors) == 0
tracks_msg.liveTracks = RD
self.pm.send('liveTracks', tracks_msg)
def controls_update(self, CS: car.CarState, CC: car.CarControl): def controls_update(self, CS: car.CarState, CC: car.CarControl):
"""control update loop, driven by carControl""" """control update loop, driven by carControl"""
@ -235,22 +231,20 @@ class Car:
if self.sm.all_alive(['carControl']): if self.sm.all_alive(['carControl']):
# send car controls over can # send car controls over can
now_nanos = self.can_log_mono_time if REPLAY else int(time.monotonic() * 1e9) now_nanos = self.can_log_mono_time if REPLAY else int(time.monotonic() * 1e9)
self.last_actuators_output, can_sends = self.CI.apply(convert_carControl(CC), now_nanos) self.last_actuators_output, can_sends = self.CI.apply(CC, now_nanos)
self.pm.send('sendcan', can_list_to_can_capnp(can_sends, msgtype='sendcan', valid=CS.canValid)) self.pm.send('sendcan', can_list_to_can_capnp(can_sends, msgtype='sendcan', valid=CS.canValid))
self.CC_prev = CC self.CC_prev = CC
def step(self): def step(self):
CS = self.state_update() CS, RD = self.state_update()
self.update_events(CS)
if not self.sm['carControl'].enabled and self.events.contains(ET.ENABLE): if self.sm['carControl'].enabled and not self.CC_prev.enabled:
self.v_cruise_helper.initialize_v_cruise(CS, self.experimental_mode) self.v_cruise_helper.initialize_v_cruise(CS, self.experimental_mode)
self.state_publish(CS) self.state_publish(CS, RD)
initialized = (not any(e.name == EventName.controlsInitializing for e in self.sm['onroadEvents']) and initialized = (not any(e.name == EventName.selfdriveInitializing for e in self.sm['onroadEvents']) and
self.sm.seen['onroadEvents']) self.sm.seen['onroadEvents'])
if not self.CP.passive and initialized: if not self.CP.passive and initialized:
self.controls_update(CS, self.sm['carControl']) self.controls_update(CS, self.sm['carControl'])

@ -53,6 +53,9 @@ class VCruiseHelper:
else: else:
self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH
self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH
if CS.cruiseState.speed == 0:
self.v_cruise_kph = V_CRUISE_UNSET
self.v_cruise_cluster_kph = V_CRUISE_UNSET
else: else:
self.v_cruise_kph = V_CRUISE_UNSET self.v_cruise_kph = V_CRUISE_UNSET
self.v_cruise_cluster_kph = V_CRUISE_UNSET self.v_cruise_cluster_kph = V_CRUISE_UNSET

@ -1,72 +0,0 @@
import capnp
from typing import Any
from cereal import car
from opendbc.car import structs
_FIELDS = '__dataclass_fields__' # copy of dataclasses._FIELDS
def is_dataclass(obj):
"""Similar to dataclasses.is_dataclass without instance type check checking"""
return hasattr(obj, _FIELDS)
def _asdictref_inner(obj) -> dict[str, Any] | Any:
if is_dataclass(obj):
ret = {}
for field in getattr(obj, _FIELDS): # similar to dataclasses.fields()
ret[field] = _asdictref_inner(getattr(obj, field))
return ret
elif isinstance(obj, (tuple, list)):
return type(obj)(_asdictref_inner(v) for v in obj)
else:
return obj
def asdictref(obj) -> dict[str, Any]:
"""
Similar to dataclasses.asdict without recursive type checking and copy.deepcopy
Note that the resulting dict will contain references to the original struct as a result
"""
if not is_dataclass(obj):
raise TypeError("asdictref() should be called on dataclass instances")
return _asdictref_inner(obj)
def convert_to_capnp(struct: structs.CarParams | structs.CarState | structs.CarControl.Actuators) -> capnp.lib.capnp._DynamicStructBuilder:
struct_dict = asdictref(struct)
if isinstance(struct, structs.CarParams):
del struct_dict['lateralTuning']
struct_capnp = car.CarParams.new_message(**struct_dict)
# this is the only union, special handling
which = struct.lateralTuning.which()
struct_capnp.lateralTuning.init(which)
lateralTuning_dict = asdictref(getattr(struct.lateralTuning, which))
setattr(struct_capnp.lateralTuning, which, lateralTuning_dict)
elif isinstance(struct, structs.CarState):
struct_capnp = car.CarState.new_message(**struct_dict)
elif isinstance(struct, structs.CarControl.Actuators):
struct_capnp = car.CarControl.Actuators.new_message(**struct_dict)
else:
raise ValueError(f"Unsupported struct type: {type(struct)}")
return struct_capnp
def convert_carControl(struct: capnp.lib.capnp._DynamicStructReader) -> structs.CarControl:
# TODO: recursively handle any car struct as needed
def remove_deprecated(s: dict) -> dict:
return {k: v for k, v in s.items() if not k.endswith('DEPRECATED')}
struct_dict = struct.to_dict()
struct_dataclass = structs.CarControl(**remove_deprecated({k: v for k, v in struct_dict.items() if not isinstance(k, dict)}))
struct_dataclass.actuators = structs.CarControl.Actuators(**remove_deprecated(struct_dict.get('actuators', {})))
struct_dataclass.cruiseControl = structs.CarControl.CruiseControl(**remove_deprecated(struct_dict.get('cruiseControl', {})))
struct_dataclass.hudControl = structs.CarControl.HUDControl(**remove_deprecated(struct_dict.get('hudControl', {})))
return struct_dataclass

@ -4,9 +4,9 @@ SCRIPT_DIR=$(dirname "$0")
BASEDIR=$(realpath "$SCRIPT_DIR/../../../") BASEDIR=$(realpath "$SCRIPT_DIR/../../../")
cd $BASEDIR cd $BASEDIR
MAX_EXAMPLES=300 export MAX_EXAMPLES=300
INTERNAL_SEG_CNT=300 export INTERNAL_SEG_CNT=300
FILEREADER_CACHE=1 export FILEREADER_CACHE=1
INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt export INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt
cd selfdrive/car/tests && pytest test_models.py test_car_interfaces.py cd selfdrive/car/tests && pytest test_models.py test_car_interfaces.py

@ -12,7 +12,6 @@ from opendbc.car.tests.test_car_interfaces import get_fuzzy_car_interface_args
from opendbc.car.fingerprints import all_known_cars from opendbc.car.fingerprints import all_known_cars
from opendbc.car.fw_versions import FW_VERSIONS, FW_QUERY_CONFIGS from opendbc.car.fw_versions import FW_VERSIONS, FW_QUERY_CONFIGS
from opendbc.car.mock.values import CAR as MOCK from opendbc.car.mock.values import CAR as MOCK
from openpilot.selfdrive.car.card import convert_carControl, convert_to_capnp
from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle
from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
@ -41,6 +40,7 @@ class TestCarInterfaces:
car_params = CarInterface.get_params(car_name, args['fingerprints'], args['car_fw'], car_params = CarInterface.get_params(car_name, args['fingerprints'], args['car_fw'],
experimental_long=args['experimental_long'], docs=False) experimental_long=args['experimental_long'], docs=False)
car_params = car_params.as_reader()
car_interface = CarInterface(car_params, CarController, CarState) car_interface = CarInterface(car_params, CarController, CarState)
assert car_params assert car_params
assert car_interface assert car_interface
@ -72,7 +72,7 @@ class TestCarInterfaces:
# Run car interface # Run car interface
now_nanos = 0 now_nanos = 0
CC = car.CarControl.new_message(**cc_msg) CC = car.CarControl.new_message(**cc_msg)
CC = convert_carControl(CC.as_reader()) CC = CC.as_reader()
for _ in range(10): for _ in range(10):
car_interface.update([]) car_interface.update([])
car_interface.apply(CC, now_nanos) car_interface.apply(CC, now_nanos)
@ -80,7 +80,7 @@ class TestCarInterfaces:
CC = car.CarControl.new_message(**cc_msg) CC = car.CarControl.new_message(**cc_msg)
CC.enabled = True CC.enabled = True
CC = convert_carControl(CC.as_reader()) CC = CC.as_reader()
for _ in range(10): for _ in range(10):
car_interface.update([]) car_interface.update([])
car_interface.apply(CC, now_nanos) car_interface.apply(CC, now_nanos)
@ -89,11 +89,10 @@ class TestCarInterfaces:
# Test controller initialization # Test controller initialization
# TODO: wait until card refactor is merged to run controller a few times, # TODO: wait until card refactor is merged to run controller a few times,
# hypothesis also slows down significantly with just one more message draw # hypothesis also slows down significantly with just one more message draw
car_params_capnp = convert_to_capnp(car_params).as_reader() LongControl(car_params)
LongControl(car_params_capnp)
if car_params.steerControlType == CarParams.SteerControlType.angle: if car_params.steerControlType == CarParams.SteerControlType.angle:
LatControlAngle(car_params_capnp, car_interface) LatControlAngle(car_params, car_interface)
elif car_params.lateralTuning.which() == 'pid': elif car_params.lateralTuning.which() == 'pid':
LatControlPID(car_params_capnp, car_interface) LatControlPID(car_params, car_interface)
elif car_params.lateralTuning.which() == 'torque': elif car_params.lateralTuning.which() == 'torque':
LatControlTorque(car_params_capnp, car_interface) LatControlTorque(car_params, car_interface)

@ -1,6 +1,4 @@
import capnp import capnp
import copy
import dataclasses
import os import os
import pytest import pytest
import random import random
@ -20,7 +18,8 @@ from opendbc.car.car_helpers import FRAME_FINGERPRINT, interfaces
from opendbc.car.honda.values import CAR as HONDA, HondaFlags from opendbc.car.honda.values import CAR as HONDA, HondaFlags
from opendbc.car.values import Platform from opendbc.car.values import Platform
from opendbc.car.tests.routes import non_tested_cars, routes, CarTestRoute from opendbc.car.tests.routes import non_tested_cars, routes, CarTestRoute
from openpilot.selfdrive.car.card import Car, convert_to_capnp from openpilot.selfdrive.selfdrived.events import ET
from openpilot.selfdrive.selfdrived.selfdrived import SelfdriveD
from openpilot.selfdrive.pandad import can_capnp_to_list from openpilot.selfdrive.pandad import can_capnp_to_list
from openpilot.selfdrive.test.helpers import read_segment_list from openpilot.selfdrive.test.helpers import read_segment_list
from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT
@ -29,7 +28,7 @@ from openpilot.tools.lib.route import SegmentName
from panda.tests.libpanda import libpanda_py from panda.tests.libpanda import libpanda_py
EventName = car.OnroadEvent.EventName EventName = log.OnroadEvent.EventName
PandaType = log.PandaState.PandaType PandaType = log.PandaState.PandaType
SafetyModel = car.CarParams.SafetyModel SafetyModel = car.CarParams.SafetyModel
@ -57,6 +56,7 @@ def get_test_cases() -> list[tuple[str, CarTestRoute | None]]:
segment_list = read_segment_list(os.path.join(BASEDIR, INTERNAL_SEG_LIST)) segment_list = read_segment_list(os.path.join(BASEDIR, INTERNAL_SEG_LIST))
segment_list = random.sample(segment_list, INTERNAL_SEG_CNT or len(segment_list)) segment_list = random.sample(segment_list, INTERNAL_SEG_CNT or len(segment_list))
for platform, segment in segment_list: for platform, segment in segment_list:
platform = MIGRATION.get(platform, platform)
segment_name = SegmentName(segment) segment_name = SegmentName(segment)
test_cases.append((platform, CarTestRoute(segment_name.route_name.canonical_name, platform, test_cases.append((platform, CarTestRoute(segment_name.route_name.canonical_name, platform,
segment=segment_name.segment_num))) segment=segment_name.segment_num)))
@ -184,7 +184,7 @@ class TestCarModelBase(unittest.TestCase):
del cls.can_msgs del cls.can_msgs
def setUp(self): def setUp(self):
self.CI = self.CarInterface(copy.deepcopy(self.CP), self.CarController, self.CarState) self.CI = self.CarInterface(self.CP.copy(), self.CarController, self.CarState)
assert self.CI assert self.CI
Params().put_bool("OpenpilotEnabledToggle", self.openpilot_enabled) Params().put_bool("OpenpilotEnabledToggle", self.openpilot_enabled)
@ -192,7 +192,7 @@ class TestCarModelBase(unittest.TestCase):
# TODO: check safetyModel is in release panda build # TODO: check safetyModel is in release panda build
self.safety = libpanda_py.libpanda self.safety = libpanda_py.libpanda
cfg = car.CarParams.SafetyConfig(**dataclasses.asdict(self.CP.safetyConfigs[-1])) cfg = self.CP.safetyConfigs[-1]
set_status = self.safety.set_safety_hooks(cfg.safetyModel.raw, cfg.safetyParam) set_status = self.safety.set_safety_hooks(cfg.safetyModel.raw, cfg.safetyParam)
self.assertEqual(0, set_status, f"failed to set safetyModel {cfg}") self.assertEqual(0, set_status, f"failed to set safetyModel {cfg}")
self.safety.init_tests() self.safety.init_tests()
@ -217,7 +217,7 @@ class TestCarModelBase(unittest.TestCase):
# TODO: also check for checksum violations from can parser # TODO: also check for checksum violations from can parser
can_invalid_cnt = 0 can_invalid_cnt = 0
can_valid = False can_valid = False
CC = structs.CarControl() CC = structs.CarControl().as_reader()
for i, msg in enumerate(self.can_msgs): for i, msg in enumerate(self.can_msgs):
CS = self.CI.update(can_capnp_to_list((msg.as_builder().to_bytes(),))) CS = self.CI.update(can_capnp_to_list((msg.as_builder().to_bytes(),)))
@ -309,17 +309,17 @@ class TestCarModelBase(unittest.TestCase):
# Make sure we can send all messages while inactive # Make sure we can send all messages while inactive
CC = structs.CarControl() CC = structs.CarControl()
test_car_controller(CC) test_car_controller(CC.as_reader())
# Test cancel + general messages (controls_allowed=False & cruise_engaged=True) # Test cancel + general messages (controls_allowed=False & cruise_engaged=True)
self.safety.set_cruise_engaged_prev(True) self.safety.set_cruise_engaged_prev(True)
CC = structs.CarControl(cruiseControl=structs.CarControl.CruiseControl(cancel=True)) CC = structs.CarControl(cruiseControl=structs.CarControl.CruiseControl(cancel=True))
test_car_controller(CC) test_car_controller(CC.as_reader())
# Test resume + general messages (controls_allowed=True & cruise_engaged=True) # Test resume + general messages (controls_allowed=True & cruise_engaged=True)
self.safety.set_controls_allowed(True) self.safety.set_controls_allowed(True)
CC = structs.CarControl(cruiseControl=structs.CarControl.CruiseControl(resume=True)) CC = structs.CarControl(cruiseControl=structs.CarControl.CruiseControl(resume=True))
test_car_controller(CC) test_car_controller(CC.as_reader())
# Skip stdout/stderr capture with pytest, causes elevated memory usage # Skip stdout/stderr capture with pytest, causes elevated memory usage
@pytest.mark.nocapture @pytest.mark.nocapture
@ -403,9 +403,10 @@ class TestCarModelBase(unittest.TestCase):
controls_allowed_prev = False controls_allowed_prev = False
CS_prev = car.CarState.new_message() CS_prev = car.CarState.new_message()
checks = defaultdict(int) checks = defaultdict(int)
card = Car(CI=self.CI) selfdrived = SelfdriveD(CP=self.CP)
selfdrived.initialized = True
for idx, can in enumerate(self.can_msgs): for idx, can in enumerate(self.can_msgs):
CS = convert_to_capnp(self.CI.update(can_capnp_to_list((can.as_builder().to_bytes(), )))) CS = self.CI.update(can_capnp_to_list((can.as_builder().to_bytes(), ))).as_reader()
for msg in filter(lambda m: m.src in range(64), can.can): for msg in filter(lambda m: m.src in range(64), can.can):
to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat) to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat)
ret = self.safety.safety_rx_hook(to_send) ret = self.safety.safety_rx_hook(to_send)
@ -448,10 +449,10 @@ class TestCarModelBase(unittest.TestCase):
checks['cruiseState'] += CS.cruiseState.enabled != self.safety.get_cruise_engaged_prev() checks['cruiseState'] += CS.cruiseState.enabled != self.safety.get_cruise_engaged_prev()
else: else:
# Check for enable events on rising edge of controls allowed # Check for enable events on rising edge of controls allowed
card.update_events(CS) selfdrived.update_events(CS)
card.CS_prev = CS selfdrived.CS_prev = CS
button_enable = (any(evt.enable for evt in CS.events) and button_enable = (selfdrived.events.contains(ET.ENABLE) and
not any(evt == EventName.pedalPressed for evt in card.events.names)) EventName.pedalPressed not in selfdrived.events.names)
mismatch = button_enable != (self.safety.get_controls_allowed() and not controls_allowed_prev) mismatch = button_enable != (self.safety.get_controls_allowed() and not controls_allowed_prev)
checks['controlsAllowed'] += mismatch checks['controlsAllowed'] += mismatch
controls_allowed_prev = self.safety.get_controls_allowed() controls_allowed_prev = self.safety.get_controls_allowed()

@ -1,28 +1,16 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os
import math import math
import time
import threading
from typing import SupportsFloat from typing import SupportsFloat
import cereal.messaging as messaging
from cereal import car, log from cereal import car, log
from msgq.visionipc import VisionIpcClient, VisionStreamType import cereal.messaging as messaging
from openpilot.common.conversions import Conversions as CV from openpilot.common.conversions import Conversions as CV
from openpilot.common.git import get_short_branch
from openpilot.common.numpy_fast import clip
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper, DT_CTRL from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper
from openpilot.common.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.common.gps import get_gps_location_service
from opendbc.car.car_helpers import get_car_interface from opendbc.car.car_helpers import get_car_interface
from openpilot.selfdrive.controls.lib.alertmanager import AlertManager, set_offroad_alert from openpilot.selfdrive.controls.lib.drive_helpers import clip_curvature
from openpilot.selfdrive.controls.lib.drive_helpers import clip_curvature, get_startup_event
from openpilot.selfdrive.controls.lib.events import Events, ET
from openpilot.selfdrive.controls.lib.latcontrol import LatControl, MIN_LATERAL_CONTROL_SPEED from openpilot.selfdrive.controls.lib.latcontrol import LatControl, MIN_LATERAL_CONTROL_SPEED
from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle, STEER_ANGLE_SATURATION_THRESHOLD from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle, STEER_ANGLE_SATURATION_THRESHOLD
@ -31,107 +19,35 @@ from openpilot.selfdrive.controls.lib.longcontrol import LongControl
from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel
from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose
from openpilot.system.hardware import HARDWARE
SOFT_DISABLE_TIME = 3 # seconds
LDW_MIN_SPEED = 31 * CV.MPH_TO_MS
LANE_DEPARTURE_THRESHOLD = 0.1
CAMERA_OFFSET = 0.04
JOYSTICK_MAX_LAT_ACCEL = 2.5 # m/s^2
REPLAY = "REPLAY" in os.environ
SIMULATION = "SIMULATION" in os.environ
TESTING_CLOSET = "TESTING_CLOSET" in os.environ
IGNORE_PROCESSES = {"loggerd", "encoderd", "statsd"}
ThermalStatus = log.DeviceState.ThermalStatus
State = log.SelfdriveState.OpenpilotState State = log.SelfdriveState.OpenpilotState
PandaType = log.PandaState.PandaType
Desire = log.Desire
LaneChangeState = log.LaneChangeState LaneChangeState = log.LaneChangeState
LaneChangeDirection = log.LaneChangeDirection LaneChangeDirection = log.LaneChangeDirection
EventName = car.OnroadEvent.EventName
ButtonType = car.CarState.ButtonEvent.Type
SafetyModel = car.CarParams.SafetyModel
IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
CSID_MAP = {"1": EventName.roadCameraError, "2": EventName.wideRoadCameraError, "0": EventName.driverCameraError}
ACTUATOR_FIELDS = tuple(car.CarControl.Actuators.schema.fields.keys()) ACTUATOR_FIELDS = tuple(car.CarControl.Actuators.schema.fields.keys())
ACTIVE_STATES = (State.enabled, State.softDisabling, State.overriding)
ENABLED_STATES = (State.preEnabled, *ACTIVE_STATES)
class Controls: class Controls:
def __init__(self, CI=None): def __init__(self) -> None:
self.params = Params() self.params = Params()
if CI is None:
cloudlog.info("controlsd is waiting for CarParams") cloudlog.info("controlsd is waiting for CarParams")
self.CP = messaging.log_from_bytes(self.params.get("CarParams", block=True), car.CarParams) self.CP = messaging.log_from_bytes(self.params.get("CarParams", block=True), car.CarParams)
cloudlog.info("controlsd got CarParams") cloudlog.info("controlsd got CarParams")
# Uses car interface helper functions, altering state won't be considered by card for actuation
self.CI = get_car_interface(self.CP) self.CI = get_car_interface(self.CP)
else:
self.CI, self.CP = CI, CI.CP
# Ensure the current branch is cached, otherwise the first iteration of controlsd lags
self.branch = get_short_branch()
# Setup sockets
self.pm = messaging.PubMaster(['selfdriveState', 'controlsState', 'carControl', 'onroadEvents'])
self.gps_location_service = get_gps_location_service(self.params)
self.gps_packets = [self.gps_location_service]
self.sensor_packets = ["accelerometer", "gyroscope"]
self.camera_packets = ["roadCameraState", "driverCameraState", "wideRoadCameraState"]
self.log_sock = messaging.sub_sock('androidLog')
# TODO: de-couple controlsd with card/conflate on carState without introducing controls mismatches
self.car_state_sock = messaging.sub_sock('carState', timeout=20)
ignore = self.sensor_packets + self.gps_packets + ['testJoystick']
if SIMULATION:
ignore += ['driverCameraState', 'managerState']
if REPLAY:
# no vipc in replay will make them ignored anyways
ignore += ['roadCameraState', 'wideRoadCameraState']
self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration',
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose',
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
'testJoystick'] + self.camera_packets + self.sensor_packets + self.gps_packets,
ignore_alive=ignore, ignore_avg_freq=ignore+['radarState', 'testJoystick'], ignore_valid=['testJoystick', ],
frequency=int(1/DT_CTRL))
self.joystick_mode = False # self.params.get_bool("JoystickDebugMode")
# read params
self.is_metric = self.params.get_bool("IsMetric")
self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled")
# detect sound card presence and ensure successful init self.sm = messaging.SubMaster(['liveParameters', 'liveTorqueParameters', 'modelV2', 'selfdriveState',
sounds_available = HARDWARE.get_sound_card_online() 'liveCalibration', 'livePose', 'longitudinalPlan', 'carState', 'carOutput',
'driverMonitoringState', 'onroadEvents', 'driverAssistance'], poll='selfdriveState')
self.pm = messaging.PubMaster(['carControl', 'controlsState'])
car_recognized = self.CP.carName != 'mock' self.steer_limited = False
self.desired_curvature = 0.0
# cleanup old params
if not self.CP.experimentalLongitudinalAvailable:
self.params.remove("ExperimentalLongitudinalEnabled")
if not self.CP.openpilotLongitudinalControl:
self.params.remove("ExperimentalMode")
self.CS_prev = car.CarState.new_message()
self.AM = AlertManager()
self.events = Events()
self.pose_calibrator = PoseCalibrator() self.pose_calibrator = PoseCalibrator()
self.calibrated_pose: Pose|None = None self.calibrated_pose: Pose|None = None
self.LoC = LongControl(self.CP) self.LoC = LongControl(self.CP)
self.VM = VehicleModel(self.CP) self.VM = VehicleModel(self.CP)
self.LaC: LatControl self.LaC: LatControl
if self.CP.steerControlType == car.CarParams.SteerControlType.angle: if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
self.LaC = LatControlAngle(self.CP, self.CI) self.LaC = LatControlAngle(self.CP, self.CI)
@ -140,390 +56,16 @@ class Controls:
elif self.CP.lateralTuning.which() == 'torque': elif self.CP.lateralTuning.which() == 'torque':
self.LaC = LatControlTorque(self.CP, self.CI) self.LaC = LatControlTorque(self.CP, self.CI)
self.initialized = False def update(self):
self.state = State.disabled self.sm.update(15)
self.enabled = False
self.active = False
self.soft_disable_timer = 0
self.mismatch_counter = 0
self.cruise_mismatch_counter = 0
self.last_blinker_frame = 0
self.last_steering_pressed_frame = 0
self.distance_traveled = 0
self.last_functional_fan_frame = 0
self.events_prev = []
self.current_alert_types = [ET.PERMANENT]
self.logged_comm_issue = None
self.not_running_prev = None
self.steer_limited = False
self.desired_curvature = 0.0
self.experimental_mode = False
self.personality = self.read_personality_param()
self.recalibrating_seen = False
self.can_log_mono_time = 0
self.startup_event = get_startup_event(car_recognized, not self.CP.passive, len(self.CP.carFw) > 0)
if not sounds_available:
self.events.add(EventName.soundsUnavailable, static=True)
if not car_recognized:
self.events.add(EventName.carUnrecognized, static=True)
if len(self.CP.carFw) > 0:
set_offroad_alert("Offroad_CarUnrecognized", True)
else:
set_offroad_alert("Offroad_NoFirmware", True)
elif self.CP.passive:
self.events.add(EventName.dashcamMode, static=True)
# controlsd is driven by carState, expected at 100Hz
self.rk = Ratekeeper(100, print_delay_threshold=None)
def set_initial_state(self):
if REPLAY and any(ps.controlsAllowed for ps in self.sm['pandaStates']):
self.state = State.enabled
def update_events(self, CS):
"""Compute onroadEvents from carState"""
self.events.clear()
# Add joystick event, static on cars, dynamic on nonCars
if self.joystick_mode:
self.events.add(EventName.joystickDebug)
self.startup_event = None
# Add startup event
if self.startup_event is not None:
self.events.add(self.startup_event)
self.startup_event = None
# Don't add any more events if not initialized
if not self.initialized:
self.events.add(EventName.controlsInitializing)
return
# no more events while in dashcam mode
if self.CP.passive:
return
# Block resume if cruise never previously enabled
resume_pressed = any(be.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for be in CS.buttonEvents)
if not self.CP.pcmCruise and CS.vCruise > 250 and resume_pressed:
self.events.add(EventName.resumeBlocked)
if not self.CP.notCar:
self.events.add_from_msg(self.sm['driverMonitoringState'].events)
# Add car events, ignore if CAN isn't valid
if CS.canValid:
self.events.add_from_msg(CS.events)
# Create events for temperature, disk space, and memory
if self.sm['deviceState'].thermalStatus >= ThermalStatus.red:
self.events.add(EventName.overheat)
if self.sm['deviceState'].freeSpacePercent < 7 and not SIMULATION:
# under 7% of space free no enable allowed
self.events.add(EventName.outOfSpace)
if self.sm['deviceState'].memoryUsagePercent > 90 and not SIMULATION:
self.events.add(EventName.lowMemory)
# TODO: enable this once loggerd CPU usage is more reasonable
#cpus = list(self.sm['deviceState'].cpuUsagePercent)
#if max(cpus, default=0) > 95 and not SIMULATION:
# self.events.add(EventName.highCpuUsage)
# Alert if fan isn't spinning for 5 seconds
if self.sm['peripheralState'].pandaType != log.PandaState.PandaType.unknown:
if self.sm['peripheralState'].fanSpeedRpm < 500 and self.sm['deviceState'].fanSpeedPercentDesired > 50:
# allow enough time for the fan controller in the panda to recover from stalls
if (self.sm.frame - self.last_functional_fan_frame) * DT_CTRL > 15.0:
self.events.add(EventName.fanMalfunction)
else:
self.last_functional_fan_frame = self.sm.frame
# Handle calibration status
cal_status = self.sm['liveCalibration'].calStatus
if cal_status != log.LiveCalibrationData.Status.calibrated:
if cal_status == log.LiveCalibrationData.Status.uncalibrated:
self.events.add(EventName.calibrationIncomplete)
elif cal_status == log.LiveCalibrationData.Status.recalibrating:
if not self.recalibrating_seen:
set_offroad_alert("Offroad_Recalibration", True)
self.recalibrating_seen = True
self.events.add(EventName.calibrationRecalibrating)
else:
self.events.add(EventName.calibrationInvalid)
# Handle lane change
if self.sm['modelV2'].meta.laneChangeState == LaneChangeState.preLaneChange:
direction = self.sm['modelV2'].meta.laneChangeDirection
if (CS.leftBlindspot and direction == LaneChangeDirection.left) or \
(CS.rightBlindspot and direction == LaneChangeDirection.right):
self.events.add(EventName.laneChangeBlocked)
else:
if direction == LaneChangeDirection.left:
self.events.add(EventName.preLaneChangeLeft)
else:
self.events.add(EventName.preLaneChangeRight)
elif self.sm['modelV2'].meta.laneChangeState in (LaneChangeState.laneChangeStarting,
LaneChangeState.laneChangeFinishing):
self.events.add(EventName.laneChange)
for i, pandaState in enumerate(self.sm['pandaStates']):
# All pandas must match the list of safetyConfigs, and if outside this list, must be silent or noOutput
if i < len(self.CP.safetyConfigs):
safety_mismatch = pandaState.safetyModel != self.CP.safetyConfigs[i].safetyModel or \
pandaState.safetyParam != self.CP.safetyConfigs[i].safetyParam or \
pandaState.alternativeExperience != self.CP.alternativeExperience
else:
safety_mismatch = pandaState.safetyModel not in IGNORED_SAFETY_MODES
# safety mismatch allows some time for pandad to set the safety mode and publish it back from panda
if (safety_mismatch and self.sm.frame*DT_CTRL > 10.) or pandaState.safetyRxChecksInvalid or self.mismatch_counter >= 200:
self.events.add(EventName.controlsMismatch)
if log.PandaState.FaultType.relayMalfunction in pandaState.faults:
self.events.add(EventName.relayMalfunction)
# Handle HW and system malfunctions
# Order is very intentional here. Be careful when modifying this.
# All events here should at least have NO_ENTRY and SOFT_DISABLE.
num_events = len(self.events)
not_running = {p.name for p in self.sm['managerState'].processes if not p.running and p.shouldBeRunning}
if self.sm.recv_frame['managerState'] and (not_running - IGNORE_PROCESSES):
self.events.add(EventName.processNotRunning)
if not_running != self.not_running_prev:
cloudlog.event("process_not_running", not_running=not_running, error=True)
self.not_running_prev = not_running
else:
if not SIMULATION and not self.rk.lagging:
if not self.sm.all_alive(self.camera_packets):
self.events.add(EventName.cameraMalfunction)
elif not self.sm.all_freq_ok(self.camera_packets):
self.events.add(EventName.cameraFrameRate)
if not REPLAY and self.rk.lagging:
self.events.add(EventName.controlsdLagging)
if len(self.sm['radarState'].radarErrors) or ((not self.rk.lagging or REPLAY) and not self.sm.all_checks(['radarState'])):
self.events.add(EventName.radarFault)
if not self.sm.valid['pandaStates']:
self.events.add(EventName.usbError)
if CS.canTimeout:
self.events.add(EventName.canBusMissing)
elif not CS.canValid:
self.events.add(EventName.canError)
# generic catch-all. ideally, a more specific event should be added above instead
has_disable_events = self.events.contains(ET.NO_ENTRY) and (self.events.contains(ET.SOFT_DISABLE) or self.events.contains(ET.IMMEDIATE_DISABLE))
no_system_errors = (not has_disable_events) or (len(self.events) == num_events)
if not self.sm.all_checks() and no_system_errors:
if not self.sm.all_alive():
self.events.add(EventName.commIssue)
elif not self.sm.all_freq_ok():
self.events.add(EventName.commIssueAvgFreq)
else:
self.events.add(EventName.commIssue)
logs = {
'invalid': [s for s, valid in self.sm.valid.items() if not valid],
'not_alive': [s for s, alive in self.sm.alive.items() if not alive],
'not_freq_ok': [s for s, freq_ok in self.sm.freq_ok.items() if not freq_ok],
}
if logs != self.logged_comm_issue:
cloudlog.event("commIssue", error=True, **logs)
self.logged_comm_issue = logs
else:
self.logged_comm_issue = None
if not (self.CP.notCar and self.joystick_mode):
if not self.sm['livePose'].posenetOK:
self.events.add(EventName.posenetInvalid)
if not self.sm['livePose'].inputsOK:
self.events.add(EventName.locationdTemporaryError)
if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY):
self.events.add(EventName.paramsdTemporaryError)
# conservative HW alert. if the data or frequency are off, locationd will throw an error
if any((self.sm.frame - self.sm.recv_frame[s])*DT_CTRL > 10. for s in self.sensor_packets):
self.events.add(EventName.sensorDataInvalid)
if not REPLAY:
# Check for mismatch between openpilot and car's PCM
cruise_mismatch = CS.cruiseState.enabled and (not self.enabled or not self.CP.pcmCruise)
self.cruise_mismatch_counter = self.cruise_mismatch_counter + 1 if cruise_mismatch else 0
if self.cruise_mismatch_counter > int(6. / DT_CTRL):
self.events.add(EventName.cruiseMismatch)
# Check for FCW
stock_long_is_braking = self.enabled and not self.CP.openpilotLongitudinalControl and CS.aEgo < -1.25
model_fcw = self.sm['modelV2'].meta.hardBrakePredicted and not CS.brakePressed and not stock_long_is_braking
planner_fcw = self.sm['longitudinalPlan'].fcw and self.enabled
if (planner_fcw or model_fcw) and not (self.CP.notCar and self.joystick_mode):
self.events.add(EventName.fcw)
for m in messaging.drain_sock(self.log_sock, wait_for_one=False):
try:
msg = m.androidLog.message
if any(err in msg for err in ("ERROR_CRC", "ERROR_ECC", "ERROR_STREAM_UNDERFLOW", "APPLY FAILED")):
csid = msg.split("CSID:")[-1].split(" ")[0]
evt = CSID_MAP.get(csid, None)
if evt is not None:
self.events.add(evt)
except UnicodeDecodeError:
pass
# TODO: fix simulator
if not SIMULATION or REPLAY:
# Not show in first 1.5 km to allow for driving out of garage. This event shows after 5 minutes
gps_ok = self.sm.recv_frame[self.gps_location_service] > 0 and (self.sm.frame - self.sm.recv_frame[self.gps_location_service]) * DT_CTRL < 2.0
if not gps_ok and self.sm['livePose'].inputsOK and (self.distance_traveled > 1500):
self.events.add(EventName.noGps)
if gps_ok:
self.distance_traveled = 0
self.distance_traveled += CS.vEgo * DT_CTRL
if self.sm['modelV2'].frameDropPerc > 20:
self.events.add(EventName.modeldLagging)
def data_sample(self):
"""Receive data from sockets"""
car_state = messaging.recv_one(self.car_state_sock)
CS = car_state.carState if car_state else self.CS_prev
self.sm.update(0)
if not self.initialized:
all_valid = CS.canValid and self.sm.all_checks()
timed_out = self.sm.frame * DT_CTRL > 6.
if all_valid or timed_out or (SIMULATION and not REPLAY):
available_streams = VisionIpcClient.available_streams("camerad", block=False)
if VisionStreamType.VISION_STREAM_ROAD not in available_streams:
self.sm.ignore_alive.append('roadCameraState')
if VisionStreamType.VISION_STREAM_WIDE_ROAD not in available_streams:
self.sm.ignore_alive.append('wideRoadCameraState')
self.initialized = True
self.set_initial_state()
cloudlog.event(
"controlsd.initialized",
dt=self.sm.frame*DT_CTRL,
timeout=timed_out,
canValid=CS.canValid,
invalid=[s for s, valid in self.sm.valid.items() if not valid],
not_alive=[s for s, alive in self.sm.alive.items() if not alive],
not_freq_ok=[s for s, freq_ok in self.sm.freq_ok.items() if not freq_ok],
error=True,
)
# When the panda and controlsd do not agree on controls_allowed
# we want to disengage openpilot. However the status from the panda goes through
# another socket other than the CAN messages and one can arrive earlier than the other.
# Therefore we allow a mismatch for two samples, then we trigger the disengagement.
if not self.enabled:
self.mismatch_counter = 0
# All pandas not in silent mode must have controlsAllowed when openpilot is enabled
if self.enabled and any(not ps.controlsAllowed for ps in self.sm['pandaStates']
if ps.safetyModel not in IGNORED_SAFETY_MODES):
self.mismatch_counter += 1
# calibrate the live pose and save it for later use
if self.sm.updated["liveCalibration"]: if self.sm.updated["liveCalibration"]:
self.pose_calibrator.feed_live_calib(self.sm['liveCalibration']) self.pose_calibrator.feed_live_calib(self.sm['liveCalibration'])
if self.sm.updated["livePose"]: if self.sm.updated["livePose"]:
device_pose = Pose.from_live_pose(self.sm['livePose']) device_pose = Pose.from_live_pose(self.sm['livePose'])
self.calibrated_pose = self.pose_calibrator.build_calibrated_pose(device_pose) self.calibrated_pose = self.pose_calibrator.build_calibrated_pose(device_pose)
return CS def state_control(self):
CS = self.sm['carState']
def state_transition(self, CS):
"""Compute conditional state transitions and execute actions on state transitions"""
# decrement the soft disable timer at every step, as it's reset on
# entrance in SOFT_DISABLING state
self.soft_disable_timer = max(0, self.soft_disable_timer - 1)
self.current_alert_types = [ET.PERMANENT]
# ENABLED, SOFT DISABLING, PRE ENABLING, OVERRIDING
if self.state != State.disabled:
# user and immediate disable always have priority in a non-disabled state
if self.events.contains(ET.USER_DISABLE):
self.state = State.disabled
self.current_alert_types.append(ET.USER_DISABLE)
elif self.events.contains(ET.IMMEDIATE_DISABLE):
self.state = State.disabled
self.current_alert_types.append(ET.IMMEDIATE_DISABLE)
else:
# ENABLED
if self.state == State.enabled:
if self.events.contains(ET.SOFT_DISABLE):
self.state = State.softDisabling
self.soft_disable_timer = int(SOFT_DISABLE_TIME / DT_CTRL)
self.current_alert_types.append(ET.SOFT_DISABLE)
elif self.events.contains(ET.OVERRIDE_LATERAL) or self.events.contains(ET.OVERRIDE_LONGITUDINAL):
self.state = State.overriding
self.current_alert_types += [ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL]
# SOFT DISABLING
elif self.state == State.softDisabling:
if not self.events.contains(ET.SOFT_DISABLE):
# no more soft disabling condition, so go back to ENABLED
self.state = State.enabled
elif self.soft_disable_timer > 0:
self.current_alert_types.append(ET.SOFT_DISABLE)
elif self.soft_disable_timer <= 0:
self.state = State.disabled
# PRE ENABLING
elif self.state == State.preEnabled:
if not self.events.contains(ET.PRE_ENABLE):
self.state = State.enabled
else:
self.current_alert_types.append(ET.PRE_ENABLE)
# OVERRIDING
elif self.state == State.overriding:
if self.events.contains(ET.SOFT_DISABLE):
self.state = State.softDisabling
self.soft_disable_timer = int(SOFT_DISABLE_TIME / DT_CTRL)
self.current_alert_types.append(ET.SOFT_DISABLE)
elif not (self.events.contains(ET.OVERRIDE_LATERAL) or self.events.contains(ET.OVERRIDE_LONGITUDINAL)):
self.state = State.enabled
else:
self.current_alert_types += [ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL]
# DISABLED
elif self.state == State.disabled:
if self.events.contains(ET.ENABLE):
if self.events.contains(ET.NO_ENTRY):
self.current_alert_types.append(ET.NO_ENTRY)
else:
if self.events.contains(ET.PRE_ENABLE):
self.state = State.preEnabled
elif self.events.contains(ET.OVERRIDE_LATERAL) or self.events.contains(ET.OVERRIDE_LONGITUDINAL):
self.state = State.overriding
else:
self.state = State.enabled
self.current_alert_types.append(ET.ENABLE)
# Check if openpilot is engaged and actuators are enabled
self.enabled = self.state in ENABLED_STATES
self.active = self.state in ACTIVE_STATES
if self.active:
self.current_alert_types.append(ET.WARNING)
def state_control(self, CS):
"""Given the state, this function returns a CarControl packet"""
# Update VehicleModel # Update VehicleModel
lp = self.sm['liveParameters'] lp = self.sm['liveParameters']
@ -542,13 +84,12 @@ class Controls:
model_v2 = self.sm['modelV2'] model_v2 = self.sm['modelV2']
CC = car.CarControl.new_message() CC = car.CarControl.new_message()
CC.enabled = self.enabled CC.enabled = self.sm['selfdriveState'].enabled
# Check which actuators can be enabled # Check which actuators can be enabled
standstill = CS.vEgo <= max(self.CP.minSteerSpeed, MIN_LATERAL_CONTROL_SPEED) or CS.standstill standstill = abs(CS.vEgo) <= max(self.CP.minSteerSpeed, MIN_LATERAL_CONTROL_SPEED) or CS.standstill
CC.latActive = self.active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \ CC.latActive = self.sm['selfdriveState'].active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and not standstill
(not standstill or self.joystick_mode) CC.longActive = CC.enabled and not any(e.overrideLongitudinal for e in self.sm['onroadEvents']) and self.CP.openpilotLongitudinalControl
CC.longActive = self.enabled and not self.events.contains(ET.OVERRIDE_LONGITUDINAL) and self.CP.openpilotLongitudinalControl
actuators = CC.actuators actuators = CC.actuators
actuators.longControlState = self.LoC.long_control_state actuators.longControlState = self.LoC.long_control_state
@ -558,17 +99,11 @@ class Controls:
CC.leftBlinker = model_v2.meta.laneChangeDirection == LaneChangeDirection.left CC.leftBlinker = model_v2.meta.laneChangeDirection == LaneChangeDirection.left
CC.rightBlinker = model_v2.meta.laneChangeDirection == LaneChangeDirection.right CC.rightBlinker = model_v2.meta.laneChangeDirection == LaneChangeDirection.right
if CS.leftBlinker or CS.rightBlinker:
self.last_blinker_frame = self.sm.frame
# State specific actions
if not CC.latActive: if not CC.latActive:
self.LaC.reset() self.LaC.reset()
if not CC.longActive: if not CC.longActive:
self.LoC.reset() self.LoC.reset()
if not self.joystick_mode:
# accel PID loop # accel PID loop
pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, CS.vCruise * CV.KPH_TO_MS) pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, CS.vCruise * CV.KPH_TO_MS)
actuators.accel = self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits) actuators.accel = self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits)
@ -579,60 +114,6 @@ class Controls:
actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp, actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp,
self.steer_limited, self.desired_curvature, self.steer_limited, self.desired_curvature,
self.calibrated_pose) # TODO what if not available self.calibrated_pose) # TODO what if not available
else:
lac_log = log.ControlsState.LateralDebugState.new_message()
if self.sm.recv_frame['testJoystick'] > 0:
# reset joystick if it hasn't been received in a while
should_reset_joystick = (self.sm.frame - self.sm.recv_frame['testJoystick'])*DT_CTRL > 0.2
if not should_reset_joystick:
joystick_axes = self.sm['testJoystick'].axes
else:
joystick_axes = [0.0, 0.0]
if CC.longActive:
actuators.accel = 4.0*clip(joystick_axes[0], -1, 1)
if CC.latActive:
max_curvature = JOYSTICK_MAX_LAT_ACCEL / max(CS.vEgo ** 2, 1)
max_angle = math.degrees(self.VM.get_steer_from_curvature(max_curvature, CS.vEgo, lp.roll))
steer = clip(joystick_axes[1], -1, 1)
actuators.steer, actuators.steeringAngleDeg, actuators.curvature = steer, steer * max_angle, steer * -max_curvature
lac_log.active = self.active
lac_log.steeringAngleDeg = CS.steeringAngleDeg
lac_log.output = actuators.steer
lac_log.saturated = abs(actuators.steer) >= 0.9
if CS.steeringPressed:
self.last_steering_pressed_frame = self.sm.frame
recent_steer_pressed = (self.sm.frame - self.last_steering_pressed_frame)*DT_CTRL < 2.0
# Send a "steering required alert" if saturation count has reached the limit
if lac_log.active and not recent_steer_pressed and not self.CP.notCar:
if self.CP.lateralTuning.which() == 'torque' and not self.joystick_mode:
undershooting = abs(lac_log.desiredLateralAccel) / abs(1e-3 + lac_log.actualLateralAccel) > 1.2
turning = abs(lac_log.desiredLateralAccel) > 1.0
good_speed = CS.vEgo > 5
max_torque = abs(self.sm['carOutput'].actuatorsOutput.steer) > 0.99
if undershooting and turning and good_speed and max_torque:
lac_log.active and self.events.add(EventName.steerSaturated)
elif lac_log.saturated:
# TODO probably should not use dpath_points but curvature
dpath_points = model_v2.position.y
if len(dpath_points):
# Check if we deviated from the path
# TODO use desired vs actual curvature
if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
steering_value = actuators.steeringAngleDeg
else:
steering_value = actuators.steer
left_deviation = steering_value > 0 and dpath_points[0] < -0.20
right_deviation = steering_value < 0 and dpath_points[0] > 0.20
if left_deviation or right_deviation:
self.events.add(EventName.steerSaturated)
# Ensure no NaNs/Infs # Ensure no NaNs/Infs
for p in ACTUATOR_FIELDS: for p in ACTUATOR_FIELDS:
@ -644,17 +125,10 @@ class Controls:
cloudlog.error(f"actuators.{p} not finite {actuators.to_dict()}") cloudlog.error(f"actuators.{p} not finite {actuators.to_dict()}")
setattr(actuators, p, 0.0) setattr(actuators, p, 0.0)
# decrement personality on distance button press
if self.CP.openpilotLongitudinalControl:
if any(not be.pressed and be.type == ButtonType.gapAdjustCruise for be in CS.buttonEvents):
self.personality = (self.personality - 1) % 3
self.params.put_nonblocking('LongitudinalPersonality', str(self.personality))
self.events.add(EventName.personalityChanged)
return CC, lac_log return CC, lac_log
def publish_logs(self, CS, CC, lac_log): def publish(self, CC, lac_log):
"""Send actuators and hud commands to the car, send controlsstate and MPC logging""" CS = self.sm['carState']
# Orientation and angle rates can be useful for carcontroller # Orientation and angle rates can be useful for carcontroller
# Only calibrated (car) frame is relevant for the carcontroller # Only calibrated (car) frame is relevant for the carcontroller
@ -662,61 +136,28 @@ class Controls:
CC.orientationNED = self.calibrated_pose.orientation.xyz.tolist() CC.orientationNED = self.calibrated_pose.orientation.xyz.tolist()
CC.angularVelocity = self.calibrated_pose.angular_velocity.xyz.tolist() CC.angularVelocity = self.calibrated_pose.angular_velocity.xyz.tolist()
CC.cruiseControl.override = self.enabled and not CC.longActive and self.CP.openpilotLongitudinalControl CC.cruiseControl.override = CC.enabled and not CC.longActive and self.CP.openpilotLongitudinalControl
CC.cruiseControl.cancel = CS.cruiseState.enabled and (not self.enabled or not self.CP.pcmCruise) CC.cruiseControl.cancel = CS.cruiseState.enabled and (not CC.enabled or not self.CP.pcmCruise)
if self.joystick_mode and self.sm.recv_frame['testJoystick'] > 0 and self.sm['testJoystick'].buttons[0]:
CC.cruiseControl.cancel = True
speeds = self.sm['longitudinalPlan'].speeds speeds = self.sm['longitudinalPlan'].speeds
if len(speeds): if len(speeds):
CC.cruiseControl.resume = self.enabled and CS.cruiseState.standstill and speeds[-1] > 0.1 CC.cruiseControl.resume = CC.enabled and CS.cruiseState.standstill and speeds[-1] > 0.1
hudControl = CC.hudControl hudControl = CC.hudControl
hudControl.setSpeed = float(CS.vCruiseCluster * CV.KPH_TO_MS) hudControl.setSpeed = float(CS.vCruiseCluster * CV.KPH_TO_MS)
hudControl.speedVisible = self.enabled hudControl.speedVisible = CC.enabled
hudControl.lanesVisible = self.enabled hudControl.lanesVisible = CC.enabled
hudControl.leadVisible = self.sm['longitudinalPlan'].hasLead hudControl.leadVisible = self.sm['longitudinalPlan'].hasLead
hudControl.leadDistanceBars = self.personality + 1 hudControl.leadDistanceBars = self.sm['selfdriveState'].personality.raw + 1
hudControl.visualAlert = self.sm['selfdriveState'].alertHudVisual
hudControl.rightLaneVisible = True hudControl.rightLaneVisible = True
hudControl.leftLaneVisible = True hudControl.leftLaneVisible = True
if self.sm.valid['driverAssistance']:
hudControl.leftLaneDepart = self.sm['driverAssistance'].leftLaneDeparture
hudControl.rightLaneDepart = self.sm['driverAssistance'].rightLaneDeparture
recent_blinker = (self.sm.frame - self.last_blinker_frame) * DT_CTRL < 5.0 # 5s blinker cooldown if self.sm['selfdriveState'].active:
ldw_allowed = self.is_ldw_enabled and CS.vEgo > LDW_MIN_SPEED and not recent_blinker \
and not CC.latActive and self.sm['liveCalibration'].calStatus == log.LiveCalibrationData.Status.calibrated
model_v2 = self.sm['modelV2']
desire_prediction = model_v2.meta.desirePrediction
if len(desire_prediction) and ldw_allowed:
right_lane_visible = model_v2.laneLineProbs[2] > 0.5
left_lane_visible = model_v2.laneLineProbs[1] > 0.5
l_lane_change_prob = desire_prediction[Desire.laneChangeLeft]
r_lane_change_prob = desire_prediction[Desire.laneChangeRight]
lane_lines = model_v2.laneLines
l_lane_close = left_lane_visible and (lane_lines[1].y[0] > -(1.08 + CAMERA_OFFSET))
r_lane_close = right_lane_visible and (lane_lines[2].y[0] < (1.08 - CAMERA_OFFSET))
hudControl.leftLaneDepart = bool(l_lane_change_prob > LANE_DEPARTURE_THRESHOLD and l_lane_close)
hudControl.rightLaneDepart = bool(r_lane_change_prob > LANE_DEPARTURE_THRESHOLD and r_lane_close)
if hudControl.rightLaneDepart or hudControl.leftLaneDepart:
self.events.add(EventName.ldw)
clear_event_types = set()
if ET.WARNING not in self.current_alert_types:
clear_event_types.add(ET.WARNING)
if self.enabled:
clear_event_types.add(ET.NO_ENTRY)
pers = {v: k for k, v in log.LongitudinalPersonality.schema.enumerants.items()}[self.personality]
alerts = self.events.create_alerts(self.current_alert_types, [self.CP, CS, self.sm, self.is_metric, self.soft_disable_timer, pers])
self.AM.add_many(self.sm.frame, alerts)
current_alert = self.AM.process_alerts(self.sm.frame, clear_event_types)
if current_alert:
hudControl.visualAlert = current_alert.visual_alert
if not self.CP.passive and self.initialized:
CO = self.sm['carOutput'] CO = self.sm['carOutput']
if self.CP.steerControlType == car.CarParams.SteerControlType.angle: if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
self.steer_limited = abs(CC.actuators.steeringAngleDeg - CO.actuatorsOutput.steeringAngleDeg) > \ self.steer_limited = abs(CC.actuators.steeringAngleDeg - CO.actuatorsOutput.steeringAngleDeg) > \
@ -724,127 +165,56 @@ class Controls:
else: else:
self.steer_limited = abs(CC.actuators.steer - CO.actuatorsOutput.steer) > 1e-2 self.steer_limited = abs(CC.actuators.steer - CO.actuatorsOutput.steer) > 1e-2
force_decel = (self.sm['driverMonitoringState'].awarenessStatus < 0.) or \ # TODO: both controlsState and carControl valids should be set by
(self.state == State.softDisabling) # sm.all_checks(), but this creates a circular dependency
# Curvature & Steering angle
lp = self.sm['liveParameters']
steer_angle_without_offset = math.radians(CS.steeringAngleDeg - lp.angleOffsetDeg)
curvature = -self.VM.calc_curvature(steer_angle_without_offset, CS.vEgo, lp.roll)
# controlsState # controlsState
dat = messaging.new_message('controlsState') dat = messaging.new_message('controlsState')
dat.valid = CS.canValid dat.valid = CS.canValid
controlsState = dat.controlsState cs = dat.controlsState
controlsState.longitudinalPlanMonoTime = self.sm.logMonoTime['longitudinalPlan']
controlsState.lateralPlanMonoTime = self.sm.logMonoTime['modelV2'] lp = self.sm['liveParameters']
controlsState.curvature = curvature steer_angle_without_offset = math.radians(CS.steeringAngleDeg - lp.angleOffsetDeg)
controlsState.desiredCurvature = self.desired_curvature cs.curvature = -self.VM.calc_curvature(steer_angle_without_offset, CS.vEgo, lp.roll)
controlsState.longControlState = self.LoC.long_control_state
controlsState.upAccelCmd = float(self.LoC.pid.p) cs.longitudinalPlanMonoTime = self.sm.logMonoTime['longitudinalPlan']
controlsState.uiAccelCmd = float(self.LoC.pid.i) cs.lateralPlanMonoTime = self.sm.logMonoTime['modelV2']
controlsState.ufAccelCmd = float(self.LoC.pid.f) cs.desiredCurvature = self.desired_curvature
controlsState.cumLagMs = -self.rk.remaining * 1000. cs.longControlState = self.LoC.long_control_state
controlsState.forceDecel = bool(force_decel) cs.upAccelCmd = float(self.LoC.pid.p)
cs.uiAccelCmd = float(self.LoC.pid.i)
cs.ufAccelCmd = float(self.LoC.pid.f)
cs.forceDecel = bool((self.sm['driverMonitoringState'].awarenessStatus < 0.) or
(self.sm['selfdriveState'].state == State.softDisabling))
lat_tuning = self.CP.lateralTuning.which() lat_tuning = self.CP.lateralTuning.which()
if self.joystick_mode: if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
controlsState.lateralControlState.debugState = lac_log cs.lateralControlState.angleState = lac_log
elif self.CP.steerControlType == car.CarParams.SteerControlType.angle:
controlsState.lateralControlState.angleState = lac_log
elif lat_tuning == 'pid': elif lat_tuning == 'pid':
controlsState.lateralControlState.pidState = lac_log cs.lateralControlState.pidState = lac_log
elif lat_tuning == 'torque': elif lat_tuning == 'torque':
controlsState.lateralControlState.torqueState = lac_log cs.lateralControlState.torqueState = lac_log
self.pm.send('controlsState', dat) self.pm.send('controlsState', dat)
# selfdriveState
ss_msg = messaging.new_message('selfdriveState')
ss_msg.valid = CS.canValid
ss = ss_msg.selfdriveState
if current_alert:
ss.alertText1 = current_alert.alert_text_1
ss.alertText2 = current_alert.alert_text_2
ss.alertSize = current_alert.alert_size
ss.alertStatus = current_alert.alert_status
ss.alertType = current_alert.alert_type
ss.alertSound = current_alert.audible_alert
ss.enabled = self.enabled
ss.active = self.active
ss.state = self.state
ss.engageable = not self.events.contains(ET.NO_ENTRY)
ss.experimentalMode = self.experimental_mode
ss.personality = self.personality
self.pm.send('selfdriveState', ss_msg)
# onroadEvents - logged every second or on change
if (self.sm.frame % int(1. / DT_CTRL) == 0) or (self.events.names != self.events_prev):
ce_send = messaging.new_message('onroadEvents', len(self.events))
ce_send.valid = True
ce_send.onroadEvents = self.events.to_msg()
self.pm.send('onroadEvents', ce_send)
self.events_prev = self.events.names.copy()
# carControl # carControl
cc_send = messaging.new_message('carControl') cc_send = messaging.new_message('carControl')
cc_send.valid = CS.canValid cc_send.valid = CS.canValid
cc_send.carControl = CC cc_send.carControl = CC
self.pm.send('carControl', cc_send) self.pm.send('carControl', cc_send)
def step(self): def run(self):
# Sample data from sockets and get a carState rk = Ratekeeper(100, print_delay_threshold=None)
CS = self.data_sample()
cloudlog.timestamp("Data sampled")
self.update_events(CS)
cloudlog.timestamp("Events updated")
if not self.CP.passive and self.initialized:
# Update control state
self.state_transition(CS)
# Compute actuators (runs PID loops and lateral MPC)
CC, lac_log = self.state_control(CS)
# Publish data
self.publish_logs(CS, CC, lac_log)
self.CS_prev = CS
def read_personality_param(self):
try:
return int(self.params.get('LongitudinalPersonality'))
except (ValueError, TypeError):
return log.LongitudinalPersonality.standard
def params_thread(self, evt):
while not evt.is_set():
self.is_metric = self.params.get_bool("IsMetric")
self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl
self.personality = self.read_personality_param()
if self.CP.notCar:
self.joystick_mode = self.params.get_bool("JoystickDebugMode")
time.sleep(0.1)
def controlsd_thread(self):
e = threading.Event()
t = threading.Thread(target=self.params_thread, args=(e, ))
try:
t.start()
while True: while True:
self.step() self.update()
self.rk.monitor_time() CC, lac_log = self.state_control()
finally: self.publish(CC, lac_log)
e.set() rk.keep_time()
t.join()
def main(): def main():
config_realtime_process(4, Priority.CTRL_HIGH) config_realtime_process(4, Priority.CTRL_HIGH)
controls = Controls() controls = Controls()
controls.controlsd_thread() controls.run()
if __name__ == "__main__": if __name__ == "__main__":

@ -1,9 +1,6 @@
from cereal import car, log from cereal import log
from openpilot.common.numpy_fast import clip from openpilot.common.numpy_fast import clip
from openpilot.common.realtime import DT_CTRL from openpilot.common.realtime import DT_CTRL
from openpilot.system.version import get_build_metadata
EventName = car.OnroadEvent.EventName
MIN_SPEED = 1.0 MIN_SPEED = 1.0
CONTROL_N = 17 CONTROL_N = 17
@ -29,20 +26,3 @@ def get_speed_error(modelV2: log.ModelDataV2, v_ego: float) -> float:
vel_err = clip(modelV2.temporalPose.trans[0] - v_ego, -MAX_VEL_ERR, MAX_VEL_ERR) vel_err = clip(modelV2.temporalPose.trans[0] - v_ego, -MAX_VEL_ERR, MAX_VEL_ERR)
return float(vel_err) return float(vel_err)
return 0.0 return 0.0
def get_startup_event(car_recognized, controller_available, fw_seen):
build_metadata = get_build_metadata()
if build_metadata.openpilot.comma_remote and build_metadata.tested_channel:
event = EventName.startup
else:
event = EventName.startupMaster
if not car_recognized:
if fw_seen:
event = EventName.startupNoCar
else:
event = EventName.startupNoFw
elif car_recognized and not controller_available:
event = EventName.startupNoControl
return event

@ -0,0 +1,41 @@
from cereal import log
from openpilot.common.realtime import DT_CTRL
from openpilot.common.conversions import Conversions as CV
CAMERA_OFFSET = 0.04
LDW_MIN_SPEED = 31 * CV.MPH_TO_MS
LANE_DEPARTURE_THRESHOLD = 0.1
class LaneDepartureWarning:
def __init__(self):
self.left = False
self.right = False
self.last_blinker_frame = 0
def update(self, frame, modelV2, CS, CC):
if CS.leftBlinker or CS.rightBlinker:
self.last_blinker_frame = frame
recent_blinker = (frame - self.last_blinker_frame) * DT_CTRL < 5.0 # 5s blinker cooldown
ldw_allowed = CS.vEgo > LDW_MIN_SPEED and not recent_blinker and not CC.latActive
desire_prediction = modelV2.meta.desirePrediction
if len(desire_prediction) and ldw_allowed:
right_lane_visible = modelV2.laneLineProbs[2] > 0.5
left_lane_visible = modelV2.laneLineProbs[1] > 0.5
l_lane_change_prob = desire_prediction[log.Desire.laneChangeLeft]
r_lane_change_prob = desire_prediction[log.Desire.laneChangeRight]
lane_lines = modelV2.laneLines
l_lane_close = left_lane_visible and (lane_lines[1].y[0] > -(1.08 + CAMERA_OFFSET))
r_lane_close = right_lane_visible and (lane_lines[2].y[0] < (1.08 - CAMERA_OFFSET))
self.left = bool(l_lane_change_prob > LANE_DEPARTURE_THRESHOLD and l_lane_close)
self.right = bool(r_lane_change_prob > LANE_DEPARTURE_THRESHOLD and r_lane_close)
else:
self.left, self.right = False, False
@property
def warning(self) -> bool:
return bool(self.left or self.right)

@ -13,7 +13,7 @@ from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC
from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_speed_error from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_speed_error
from openpilot.selfdrive.car.cruise import V_CRUISE_MAX from openpilot.selfdrive.car.cruise import V_CRUISE_MAX, V_CRUISE_UNSET
from openpilot.common.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
LON_MPC_STEP = 0.2 # first step is 0.2s LON_MPC_STEP = 0.2 # first step is 0.2s
@ -21,6 +21,8 @@ A_CRUISE_MIN = -1.2
A_CRUISE_MAX_VALS = [1.6, 1.2, 0.8, 0.6] A_CRUISE_MAX_VALS = [1.6, 1.2, 0.8, 0.6]
A_CRUISE_MAX_BP = [0., 10.0, 25., 40.] A_CRUISE_MAX_BP = [0., 10.0, 25., 40.]
CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N] CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
ALLOW_THROTTLE_THRESHOLD = 0.5
ACCEL_LIMIT_MARGIN = 0.05
# Lookup table for turns # Lookup table for turns
_A_TOTAL_MAX_V = [1.7, 3.2] _A_TOTAL_MAX_V = [1.7, 3.2]
@ -30,6 +32,9 @@ _A_TOTAL_MAX_BP = [20., 40.]
def get_max_accel(v_ego): def get_max_accel(v_ego):
return interp(v_ego, A_CRUISE_MAX_BP, A_CRUISE_MAX_VALS) return interp(v_ego, A_CRUISE_MAX_BP, A_CRUISE_MAX_VALS)
def get_coast_accel(pitch):
return np.sin(pitch) * -5.65 - 0.3 # fitted from data using xx/projects/allow_throttle/compute_coast_accel.py
def limit_accel_in_turns(v_ego, angle_steers, a_target, CP): def limit_accel_in_turns(v_ego, angle_steers, a_target, CP):
""" """
@ -69,6 +74,7 @@ class LongitudinalPlanner:
self.mpc = LongitudinalMpc(dt=dt) self.mpc = LongitudinalMpc(dt=dt)
self.fcw = False self.fcw = False
self.dt = dt self.dt = dt
self.allow_throttle = True
self.a_desired = init_a self.a_desired = init_a
self.v_desired_filter = FirstOrderFilter(init_v, 2.0, self.dt) self.v_desired_filter = FirstOrderFilter(init_v, 2.0, self.dt)
@ -93,20 +99,32 @@ class LongitudinalPlanner:
v = np.zeros(len(T_IDXS_MPC)) v = np.zeros(len(T_IDXS_MPC))
a = np.zeros(len(T_IDXS_MPC)) a = np.zeros(len(T_IDXS_MPC))
j = np.zeros(len(T_IDXS_MPC)) j = np.zeros(len(T_IDXS_MPC))
return x, v, a, j if len(model_msg.meta.disengagePredictions.gasPressProbs) > 1:
throttle_prob = model_msg.meta.disengagePredictions.gasPressProbs[1]
else:
throttle_prob = 1.0
return x, v, a, j, throttle_prob
def update(self, sm): def update(self, sm):
self.mpc.mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc' self.mpc.mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc'
if len(sm['carControl'].orientationNED) == 3:
accel_coast = get_coast_accel(sm['carControl'].orientationNED[1])
else:
accel_coast = ACCEL_MAX
v_ego = sm['carState'].vEgo v_ego = sm['carState'].vEgo
v_cruise_kph = min(sm['carState'].vCruise, V_CRUISE_MAX) v_cruise_kph = min(sm['carState'].vCruise, V_CRUISE_MAX)
v_cruise = v_cruise_kph * CV.KPH_TO_MS v_cruise = v_cruise_kph * CV.KPH_TO_MS
v_cruise_initialized = sm['carState'].vCruise != V_CRUISE_UNSET
long_control_off = sm['controlsState'].longControlState == LongCtrlState.off long_control_off = sm['controlsState'].longControlState == LongCtrlState.off
force_slow_decel = sm['controlsState'].forceDecel force_slow_decel = sm['controlsState'].forceDecel
# Reset current state when not engaged, or user is controlling the speed # Reset current state when not engaged, or user is controlling the speed
reset_state = long_control_off if self.CP.openpilotLongitudinalControl else not sm['selfdriveState'].enabled reset_state = long_control_off if self.CP.openpilotLongitudinalControl else not sm['selfdriveState'].enabled
# PCM cruise speed may be updated a few cycles later, check if initialized
reset_state = reset_state or not v_cruise_initialized
# No change cost when user is controlling the speed, or when standstill # No change cost when user is controlling the speed, or when standstill
prev_accel_constraint = not (reset_state or sm['carState'].standstill) prev_accel_constraint = not (reset_state or sm['carState'].standstill)
@ -127,6 +145,13 @@ class LongitudinalPlanner:
self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego)) self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego))
# Compute model v_ego error # Compute model v_ego error
self.v_model_error = get_speed_error(sm['modelV2'], v_ego) self.v_model_error = get_speed_error(sm['modelV2'], v_ego)
x, v, a, j, throttle_prob = self.parse_model(sm['modelV2'], self.v_model_error)
self.allow_throttle = throttle_prob > ALLOW_THROTTLE_THRESHOLD
if not self.allow_throttle and v_ego > 5.0: # Don't clip at low speeds since throttle_prob doesn't account for creep
# MPC breaks when accel limits would cause negative velocity within the MPC horizon, so we clip the max accel limit at vEgo/T_MAX plus a bit of margin
clipped_accel_coast = max(accel_coast, accel_limits_turns[0], -v_ego / T_IDXS_MPC[-1] + ACCEL_LIMIT_MARGIN)
accel_limits_turns[1] = min(accel_limits_turns[1], clipped_accel_coast)
if force_slow_decel: if force_slow_decel:
v_cruise = 0.0 v_cruise = 0.0
@ -137,7 +162,6 @@ class LongitudinalPlanner:
self.mpc.set_weights(prev_accel_constraint, personality=sm['selfdriveState'].personality) self.mpc.set_weights(prev_accel_constraint, personality=sm['selfdriveState'].personality)
self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1])
self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired)
x, v, a, j = self.parse_model(sm['modelV2'], self.v_model_error)
self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=sm['selfdriveState'].personality) self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=sm['selfdriveState'].personality)
self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution) self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution)
@ -176,6 +200,6 @@ class LongitudinalPlanner:
longitudinalPlan.aTarget = a_target longitudinalPlan.aTarget = a_target
longitudinalPlan.shouldStop = should_stop longitudinalPlan.shouldStop = should_stop
longitudinalPlan.allowBrake = True longitudinalPlan.allowBrake = True
longitudinalPlan.allowThrottle = True longitudinalPlan.allowThrottle = self.allow_throttle
pm.send('longitudinalPlan', plan_send) pm.send('longitudinalPlan', plan_send)

@ -5,7 +5,6 @@ from opendbc.car.car_helpers import interfaces
from opendbc.car.honda.values import CAR as HONDA from opendbc.car.honda.values import CAR as HONDA
from opendbc.car.toyota.values import CAR as TOYOTA from opendbc.car.toyota.values import CAR as TOYOTA
from opendbc.car.nissan.values import CAR as NISSAN from opendbc.car.nissan.values import CAR as NISSAN
from openpilot.selfdrive.car.card import convert_to_capnp
from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle
@ -21,7 +20,6 @@ class TestLatControl:
CarInterface, CarController, CarState, RadarInterface = interfaces[car_name] CarInterface, CarController, CarState, RadarInterface = interfaces[car_name]
CP = CarInterface.get_non_essential_params(car_name) CP = CarInterface.get_non_essential_params(car_name)
CI = CarInterface(CP, CarController, CarState) CI = CarInterface(CP, CarController, CarState)
CP = convert_to_capnp(CP)
VM = VehicleModel(CP) VM = VehicleModel(CP)
controller = controller(CP.as_reader(), CI) controller = controller(CP.as_reader(), CI)

@ -5,14 +5,13 @@ import numpy as np
from opendbc.car.honda.interface import CarInterface from opendbc.car.honda.interface import CarInterface
from opendbc.car.honda.values import CAR from opendbc.car.honda.values import CAR
from openpilot.selfdrive.car.card import convert_to_capnp
from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel, dyn_ss_sol, create_dyn_state_matrices from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel, dyn_ss_sol, create_dyn_state_matrices
class TestVehicleModel: class TestVehicleModel:
def setup_method(self): def setup_method(self):
CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC) CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC)
self.VM = VehicleModel(convert_to_capnp(CP)) self.VM = VehicleModel(CP)
def test_round_trip_yaw_rate(self): def test_round_trip_yaw_rate(self):
# TODO: fix VM to work at zero speed # TODO: fix VM to work at zero speed

@ -3,11 +3,12 @@ from cereal import car
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.realtime import Priority, config_realtime_process from openpilot.common.realtime import Priority, config_realtime_process
from openpilot.common.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.controls.lib.ldw import LaneDepartureWarning
from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner
import cereal.messaging as messaging import cereal.messaging as messaging
def plannerd_thread(): def main():
config_realtime_process(5, Priority.CTRL_LOW) config_realtime_process(5, Priority.CTRL_LOW)
cloudlog.info("plannerd is waiting for CarParams") cloudlog.info("plannerd is waiting for CarParams")
@ -15,8 +16,9 @@ def plannerd_thread():
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
cloudlog.info("plannerd got CarParams: %s", CP.carName) cloudlog.info("plannerd got CarParams: %s", CP.carName)
ldw = LaneDepartureWarning()
longitudinal_planner = LongitudinalPlanner(CP) longitudinal_planner = LongitudinalPlanner(CP)
pm = messaging.PubMaster(['longitudinalPlan']) pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance'])
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'radarState', 'modelV2', 'selfdriveState'], sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'radarState', 'modelV2', 'selfdriveState'],
poll='modelV2', ignore_avg_freq=['radarState']) poll='modelV2', ignore_avg_freq=['radarState'])
@ -26,9 +28,12 @@ def plannerd_thread():
longitudinal_planner.update(sm) longitudinal_planner.update(sm)
longitudinal_planner.publish(sm, pm) longitudinal_planner.publish(sm, pm)
ldw.update(sm.frame, sm['modelV2'], sm['carState'], sm['carControl'])
def main(): msg = messaging.new_message('driverAssistance')
plannerd_thread() msg.valid = sm.all_checks(['carState', 'carControl', 'modelV2'])
msg.driverAssistance.leftLaneDeparture = ldw.left
msg.driverAssistance.rightLaneDeparture = ldw.right
pm.send('driverAssistance', msg)
if __name__ == "__main__": if __name__ == "__main__":

@ -1,18 +1,15 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import importlib
import math import math
from collections import deque from collections import deque
from typing import Any from typing import Any
import capnp import capnp
from cereal import messaging, log, car from cereal import messaging, log, car
from opendbc.car import structs
from openpilot.common.numpy_fast import interp from openpilot.common.numpy_fast import interp
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.realtime import DT_CTRL, Ratekeeper, Priority, config_realtime_process from openpilot.common.realtime import DT_MDL, Priority, config_realtime_process
from openpilot.common.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.common.simple_kalman import KF1D from openpilot.common.simple_kalman import KF1D
from openpilot.selfdrive.pandad import can_capnp_to_list
# Default lead acceleration decay set to 50% at 1s # Default lead acceleration decay set to 50% at 1s
@ -194,14 +191,14 @@ def get_lead(v_ego: float, ready: bool, tracks: dict[int, Track], lead_msg: capn
class RadarD: class RadarD:
def __init__(self, radar_ts: float, delay: int = 0): def __init__(self, delay: float = 0.0):
self.current_time = 0.0 self.current_time = 0.0
self.tracks: dict[int, Track] = {} self.tracks: dict[int, Track] = {}
self.kalman_params = KalmanParams(radar_ts) self.kalman_params = KalmanParams(DT_MDL)
self.v_ego = 0.0 self.v_ego = 0.0
self.v_ego_hist = deque([0.0], maxlen=delay+1) self.v_ego_hist = deque([0.0], maxlen=int(round(delay / DT_MDL))+1)
self.last_v_ego_frame = -1 self.last_v_ego_frame = -1
self.radar_state: capnp._DynamicStructBuilder | None = None self.radar_state: capnp._DynamicStructBuilder | None = None
@ -209,7 +206,7 @@ class RadarD:
self.ready = False self.ready = False
def update(self, sm: messaging.SubMaster, rr: structs.RadarData): def update(self, sm: messaging.SubMaster, rr: car.RadarData):
self.ready = sm.seen['modelV2'] self.ready = sm.seen['modelV2']
self.current_time = 1e-9*max(sm.logMonoTime.values()) self.current_time = 1e-9*max(sm.logMonoTime.values())
@ -246,8 +243,8 @@ class RadarD:
self.radar_state.radarErrors = list(rr.errors) self.radar_state.radarErrors = list(rr.errors)
self.radar_state.carStateMonoTime = sm.logMonoTime['carState'] self.radar_state.carStateMonoTime = sm.logMonoTime['carState']
if len(sm['modelV2'].temporalPose.trans): if len(sm['modelV2'].velocity.x):
model_v_ego = sm['modelV2'].temporalPose.trans[0] model_v_ego = sm['modelV2'].velocity.x[0]
else: else:
model_v_ego = self.v_ego model_v_ego = self.v_ego
leads_v3 = sm['modelV2'].leadsV3 leads_v3 = sm['modelV2'].leadsV3
@ -255,27 +252,14 @@ class RadarD:
self.radar_state.leadOne = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[0], model_v_ego, low_speed_override=True) self.radar_state.leadOne = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[0], model_v_ego, low_speed_override=True)
self.radar_state.leadTwo = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[1], model_v_ego, low_speed_override=False) self.radar_state.leadTwo = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[1], model_v_ego, low_speed_override=False)
def publish(self, pm: messaging.PubMaster, lag_ms: float): def publish(self, pm: messaging.PubMaster):
assert self.radar_state is not None assert self.radar_state is not None
radar_msg = messaging.new_message("radarState") radar_msg = messaging.new_message("radarState")
radar_msg.valid = self.radar_state_valid radar_msg.valid = self.radar_state_valid
radar_msg.radarState = self.radar_state radar_msg.radarState = self.radar_state
radar_msg.radarState.cumLagMs = lag_ms
pm.send("radarState", radar_msg) pm.send("radarState", radar_msg)
# publish tracks for UI debugging (keep last)
tracks_msg = messaging.new_message('liveTracks', len(self.tracks))
tracks_msg.valid = self.radar_state_valid
for index, tid in enumerate(sorted(self.tracks.keys())):
tracks_msg.liveTracks[index] = {
"trackId": tid,
"dRel": float(self.tracks[tid].dRel),
"yRel": float(self.tracks[tid].yRel),
"vRel": float(self.tracks[tid].vRel),
}
pm.send('liveTracks', tracks_msg)
# fuses camera and radar data for best lead detection # fuses camera and radar data for best lead detection
def main() -> None: def main() -> None:
@ -286,31 +270,17 @@ def main() -> None:
CP = messaging.log_from_bytes(Params().get("CarParams", block=True), car.CarParams) CP = messaging.log_from_bytes(Params().get("CarParams", block=True), car.CarParams)
cloudlog.info("radard got CarParams") cloudlog.info("radard got CarParams")
# import the radar from the fingerprint
cloudlog.info("radard is importing %s", CP.carName)
RadarInterface = importlib.import_module(f'opendbc.car.{CP.carName}.radar_interface').RadarInterface
# *** setup messaging # *** setup messaging
can_sock = messaging.sub_sock('can') sm = messaging.SubMaster(['modelV2', 'carState', 'liveTracks'], poll='modelV2')
sm = messaging.SubMaster(['modelV2', 'carState'], frequency=int(1./DT_CTRL)) pm = messaging.PubMaster(['radarState'])
pm = messaging.PubMaster(['radarState', 'liveTracks'])
RI = RadarInterface(CP) RD = RadarD(CP.radarDelay)
rk = Ratekeeper(1.0 / CP.radarTimeStep, print_delay_threshold=None)
RD = RadarD(CP.radarTimeStep, RI.delay)
while 1: while 1:
can_strings = messaging.drain_sock_raw(can_sock, wait_for_one=True) sm.update()
rr: structs.RadarData | None = RI.update(can_capnp_to_list(can_strings))
sm.update(0)
if rr is None:
continue
RD.update(sm, rr)
RD.publish(pm, -rk.remaining*1000.0)
rk.monitor_time() RD.update(sm, sm['liveTracks'])
RD.publish(pm)
if __name__ == "__main__": if __name__ == "__main__":

@ -11,19 +11,21 @@ class TestLeads:
def single_iter_pkg(): def single_iter_pkg():
# single iter package, with meaningless cans and empty carState/modelV2 # single iter package, with meaningless cans and empty carState/modelV2
msgs = [] msgs = []
for _ in range(5): for _ in range(500):
can = messaging.new_message("can", 1) can = messaging.new_message("can", 1)
cs = messaging.new_message("carState") cs = messaging.new_message("carState")
cp = messaging.new_message("carParams")
msgs.append(can.as_reader()) msgs.append(can.as_reader())
msgs.append(cs.as_reader()) msgs.append(cs.as_reader())
msgs.append(cp.as_reader())
model = messaging.new_message("modelV2") model = messaging.new_message("modelV2")
msgs.append(model.as_reader()) msgs.append(model.as_reader())
return msgs return msgs
msgs = [m for _ in range(3) for m in single_iter_pkg()] msgs = [m for _ in range(3) for m in single_iter_pkg()]
out = replay_process_with_name("radard", msgs, fingerprint=TOYOTA.TOYOTA_COROLLA_TSS2) out = replay_process_with_name("card", msgs, fingerprint=TOYOTA.TOYOTA_COROLLA_TSS2)
states = [m for m in out if m.which() == "radarState"] states = [m for m in out if m.which() == "liveTracks"]
failures = [not state.valid and len(state.radarState.radarErrors) for state in states] failures = [not state.valid and len(state.liveTracks.errors) for state in states]
assert len(states) == 0 or all(failures) assert len(states) == 0 or all(failures)

@ -1,121 +0,0 @@
import os
from parameterized import parameterized
from cereal import log, car
import cereal.messaging as messaging
from opendbc.car.fingerprints import _FINGERPRINTS
from opendbc.car.toyota.values import CAR as TOYOTA
from opendbc.car.mazda.values import CAR as MAZDA
from openpilot.common.params import Params
from openpilot.selfdrive.pandad.pandad_api_impl import can_list_to_can_capnp
from openpilot.selfdrive.controls.lib.events import EVENT_NAME
from openpilot.system.manager.process_config import managed_processes
EventName = car.OnroadEvent.EventName
Ecu = car.CarParams.Ecu
COROLLA_FW_VERSIONS = [
(Ecu.engine, 0x7e0, None, b'\x0230ZC2000\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00'),
(Ecu.abs, 0x7b0, None, b'F152602190\x00\x00\x00\x00\x00\x00'),
(Ecu.eps, 0x7a1, None, b'8965B02181\x00\x00\x00\x00\x00\x00'),
(Ecu.fwdRadar, 0x750, 0xf, b'8821F4702100\x00\x00\x00\x00'),
(Ecu.fwdCamera, 0x750, 0x6d, b'8646F0201101\x00\x00\x00\x00'),
(Ecu.dsu, 0x791, None, b'881510201100\x00\x00\x00\x00'),
]
COROLLA_FW_VERSIONS_FUZZY = COROLLA_FW_VERSIONS[:-1] + [(Ecu.dsu, 0x791, None, b'xxxxxx')]
COROLLA_FW_VERSIONS_NO_DSU = COROLLA_FW_VERSIONS[:-1]
CX5_FW_VERSIONS = [
(Ecu.engine, 0x7e0, None, b'PYNF-188K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'),
(Ecu.abs, 0x760, None, b'K123-437K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'),
(Ecu.eps, 0x730, None, b'KJ01-3210X-G-00\x00\x00\x00\x00\x00\x00\x00\x00\x00'),
(Ecu.fwdRadar, 0x764, None, b'K123-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'),
(Ecu.fwdCamera, 0x706, None, b'B61L-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'),
(Ecu.transmission, 0x7e1, None, b'PYNC-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'),
]
@parameterized.expand([
# TODO: test EventName.startup for release branches
# officially supported car
(EventName.startupMaster, TOYOTA.TOYOTA_COROLLA, COROLLA_FW_VERSIONS, "toyota"),
(EventName.startupMaster, TOYOTA.TOYOTA_COROLLA, COROLLA_FW_VERSIONS, "toyota"),
# dashcamOnly car
(EventName.startupNoControl, MAZDA.MAZDA_CX5, CX5_FW_VERSIONS, "mazda"),
(EventName.startupNoControl, MAZDA.MAZDA_CX5, CX5_FW_VERSIONS, "mazda"),
# unrecognized car with no fw
(EventName.startupNoFw, None, None, ""),
(EventName.startupNoFw, None, None, ""),
# unrecognized car
(EventName.startupNoCar, None, COROLLA_FW_VERSIONS[:1], "toyota"),
(EventName.startupNoCar, None, COROLLA_FW_VERSIONS[:1], "toyota"),
# fuzzy match
(EventName.startupMaster, TOYOTA.TOYOTA_COROLLA, COROLLA_FW_VERSIONS_FUZZY, "toyota"),
(EventName.startupMaster, TOYOTA.TOYOTA_COROLLA, COROLLA_FW_VERSIONS_FUZZY, "toyota"),
])
def test_startup_alert(expected_event, car_model, fw_versions, brand):
controls_sock = messaging.sub_sock("selfdriveState")
pm = messaging.PubMaster(['can', 'pandaStates'])
params = Params()
params.put_bool("OpenpilotEnabledToggle", True)
# Build capnn version of FW array
if fw_versions is not None:
car_fw = []
cp = car.CarParams.new_message()
for ecu, addr, subaddress, version in fw_versions:
f = car.CarParams.CarFw.new_message()
f.ecu = ecu
f.address = addr
f.fwVersion = version
f.brand = brand
if subaddress is not None:
f.subAddress = subaddress
car_fw.append(f)
cp.carVin = "1" * 17
cp.carFw = car_fw
params.put("CarParamsCache", cp.to_bytes())
else:
os.environ['SKIP_FW_QUERY'] = '1'
managed_processes['controlsd'].start()
managed_processes['card'].start()
assert pm.wait_for_readers_to_update('can', 5)
pm.send('can', can_list_to_can_capnp([[0, b"", 0]]))
assert pm.wait_for_readers_to_update('pandaStates', 5)
msg = messaging.new_message('pandaStates', 1)
msg.pandaStates[0].pandaType = log.PandaState.PandaType.uno
pm.send('pandaStates', msg)
# fingerprint
if (car_model is None) or (fw_versions is not None):
finger = {addr: 1 for addr in range(1, 100)}
else:
finger = _FINGERPRINTS[car_model][0]
msgs = [[addr, b'\x00'*length, 0] for addr, length in finger.items()]
for _ in range(1000):
# card waits for pandad to echo back that it has changed the multiplexing mode
if not params.get_bool("ObdMultiplexingChanged"):
params.put_bool("ObdMultiplexingChanged", True)
pm.send('can', can_list_to_can_capnp(msgs))
assert pm.wait_for_readers_to_update('can', 5, dt=0.001), f"step: {_}"
ctrls = messaging.drain_sock(controls_sock)
if len(ctrls):
event_name = ctrls[0].selfdriveState.alertType.split("/")[0]
assert EVENT_NAME[expected_event] == event_name, f"expected {EVENT_NAME[expected_event]} for '{car_model}', got {event_name}"
break
else:
raise Exception(f"failed to fingerprint {car_model}")

@ -1,102 +0,0 @@
from cereal import car, log
from opendbc.car.car_helpers import interfaces
from opendbc.car.mock.values import CAR as MOCK
from openpilot.common.realtime import DT_CTRL
from openpilot.selfdrive.controls.controlsd import Controls, SOFT_DISABLE_TIME
from openpilot.selfdrive.controls.lib.events import Events, ET, Alert, Priority, AlertSize, AlertStatus, VisualAlert, \
AudibleAlert, EVENTS
State = log.SelfdriveState.OpenpilotState
# The event types that maintain the current state
MAINTAIN_STATES = {State.enabled: (None,), State.disabled: (None,), State.softDisabling: (ET.SOFT_DISABLE,),
State.preEnabled: (ET.PRE_ENABLE,), State.overriding: (ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL)}
ALL_STATES = tuple(State.schema.enumerants.values())
# The event types checked in DISABLED section of state machine
ENABLE_EVENT_TYPES = (ET.ENABLE, ET.PRE_ENABLE, ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL)
def make_event(event_types):
event = {}
for ev in event_types:
event[ev] = Alert("", "", AlertStatus.normal, AlertSize.small, Priority.LOW,
VisualAlert.none, AudibleAlert.none, 1.)
EVENTS[0] = event
return 0
class TestStateMachine:
def setup_method(self):
CarInterface, CarController, CarState, RadarInterface = interfaces[MOCK.MOCK]
CP = CarInterface.get_non_essential_params(MOCK.MOCK)
CI = CarInterface(CP, CarController, CarState)
self.controlsd = Controls(CI=CI)
self.controlsd.events = Events()
self.controlsd.soft_disable_timer = int(SOFT_DISABLE_TIME / DT_CTRL)
self.CS = car.CarState()
def test_immediate_disable(self):
for state in ALL_STATES:
for et in MAINTAIN_STATES[state]:
self.controlsd.events.add(make_event([et, ET.IMMEDIATE_DISABLE]))
self.controlsd.state = state
self.controlsd.state_transition(self.CS)
assert State.disabled == self.controlsd.state
self.controlsd.events.clear()
def test_user_disable(self):
for state in ALL_STATES:
for et in MAINTAIN_STATES[state]:
self.controlsd.events.add(make_event([et, ET.USER_DISABLE]))
self.controlsd.state = state
self.controlsd.state_transition(self.CS)
assert State.disabled == self.controlsd.state
self.controlsd.events.clear()
def test_soft_disable(self):
for state in ALL_STATES:
if state == State.preEnabled: # preEnabled considers NO_ENTRY instead
continue
for et in MAINTAIN_STATES[state]:
self.controlsd.events.add(make_event([et, ET.SOFT_DISABLE]))
self.controlsd.state = state
self.controlsd.state_transition(self.CS)
assert self.controlsd.state == State.disabled if state == State.disabled else State.softDisabling
self.controlsd.events.clear()
def test_soft_disable_timer(self):
self.controlsd.state = State.enabled
self.controlsd.events.add(make_event([ET.SOFT_DISABLE]))
self.controlsd.state_transition(self.CS)
for _ in range(int(SOFT_DISABLE_TIME / DT_CTRL)):
assert self.controlsd.state == State.softDisabling
self.controlsd.state_transition(self.CS)
assert self.controlsd.state == State.disabled
def test_no_entry(self):
# Make sure noEntry keeps us disabled
for et in ENABLE_EVENT_TYPES:
self.controlsd.events.add(make_event([ET.NO_ENTRY, et]))
self.controlsd.state_transition(self.CS)
assert self.controlsd.state == State.disabled
self.controlsd.events.clear()
def test_no_entry_pre_enable(self):
# preEnabled with noEntry event
self.controlsd.state = State.preEnabled
self.controlsd.events.add(make_event([ET.NO_ENTRY, ET.PRE_ENABLE]))
self.controlsd.state_transition(self.CS)
assert self.controlsd.state == State.preEnabled
def test_maintain_states(self):
# Given current state's event type, we should maintain state
for state in ALL_STATES:
for et in MAINTAIN_STATES[state]:
self.controlsd.state = state
self.controlsd.events.add(make_event([et]))
self.controlsd.state_transition(self.CS)
assert self.controlsd.state == state
self.controlsd.events.clear()

@ -33,8 +33,8 @@ if __name__ == "__main__":
if len(events) == 0 or ae != events[-1][1]: if len(events) == 0 or ae != events[-1][1]:
events.append((t, ae)) events.append((t, ae))
elif msg.which() == 'controlsState': elif msg.which() == 'selfdriveState':
at = msg.controlsState.alertType at = msg.selfdriveState.alertType
if "/override" not in at or "lanechange" in at.lower(): if "/override" not in at or "lanechange" in at.lower():
if len(alerts) == 0 or alerts[-1][1] != at: if len(alerts) == 0 or alerts[-1][1] != at:
alerts.append((t, at)) alerts.append((t, at))

@ -6,11 +6,11 @@ from cereal import car, log
import cereal.messaging as messaging import cereal.messaging as messaging
from opendbc.car.honda.interface import CarInterface from opendbc.car.honda.interface import CarInterface
from openpilot.common.realtime import DT_CTRL from openpilot.common.realtime import DT_CTRL
from openpilot.selfdrive.controls.lib.events import ET, Events from openpilot.selfdrive.selfdrived.events import ET, Events
from openpilot.selfdrive.controls.lib.alertmanager import AlertManager from openpilot.selfdrive.selfdrived.alertmanager import AlertManager
from openpilot.system.manager.process_config import managed_processes from openpilot.system.manager.process_config import managed_processes
EventName = car.OnroadEvent.EventName EventName = log.OnroadEvent.EventName
def randperc() -> float: def randperc() -> float:
return 100. * random.random() return 100. * random.random()

@ -2,12 +2,16 @@
import jinja2 import jinja2
import os import os
from cereal import car
from openpilot.common.basedir import BASEDIR from openpilot.common.basedir import BASEDIR
from opendbc.car.interfaces import get_interface_attr from opendbc.car.interfaces import get_interface_attr
Ecu = car.CarParams.Ecu
CARS = get_interface_attr('CAR') CARS = get_interface_attr('CAR')
FW_VERSIONS = get_interface_attr('FW_VERSIONS') FW_VERSIONS = get_interface_attr('FW_VERSIONS')
FINGERPRINTS = get_interface_attr('FINGERPRINTS') FINGERPRINTS = get_interface_attr('FINGERPRINTS')
ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()}
FINGERPRINTS_PY_TEMPLATE = jinja2.Template(""" FINGERPRINTS_PY_TEMPLATE = jinja2.Template("""
{%- if FINGERPRINTS[brand] %} {%- if FINGERPRINTS[brand] %}
@ -45,7 +49,7 @@ FW_VERSIONS{% if not FW_VERSIONS[brand] %}: dict[str, dict[tuple, list[bytes]]]{
{% for car, _ in FW_VERSIONS[brand].items() %} {% for car, _ in FW_VERSIONS[brand].items() %}
CAR.{{car.name}}: { CAR.{{car.name}}: {
{% for key, fw_versions in FW_VERSIONS[brand][car].items() %} {% for key, fw_versions in FW_VERSIONS[brand][car].items() %}
(Ecu.{{key[0]}}, 0x{{"%0x" | format(key[1] | int)}}, \ (Ecu.{{ECU_NAME[key[0]]}}, 0x{{"%0x" | format(key[1] | int)}}, \
{% if key[2] %}0x{{"%0x" | format(key[2] | int)}}{% else %}{{key[2]}}{% endif %}): [ {% if key[2] %}0x{{"%0x" | format(key[2] | int)}}{% else %}{{key[2]}}{% endif %}): [
{% for fw_version in (fw_versions + extra_fw_versions.get(car, {}).get(key, [])) | unique | sort %} {% for fw_version in (fw_versions + extra_fw_versions.get(car, {}).get(key, [])) | unique | sort %}
{{fw_version}}, {{fw_version}},
@ -67,8 +71,9 @@ def format_brand_fw_versions(brand, extra_fw_versions: None | dict[str, dict[tup
comments = [line for line in f.readlines() if line.startswith("#") and "noqa" not in line] comments = [line for line in f.readlines() if line.startswith("#") and "noqa" not in line]
with open(fingerprints_file, "w") as f: with open(fingerprints_file, "w") as f:
f.write(FINGERPRINTS_PY_TEMPLATE.render(brand=brand, comments=comments, FINGERPRINTS=FINGERPRINTS, f.write(FINGERPRINTS_PY_TEMPLATE.render(brand=brand, comments=comments, ECU_NAME=ECU_NAME,
FW_VERSIONS=FW_VERSIONS, extra_fw_versions=extra_fw_versions)) FINGERPRINTS=FINGERPRINTS, FW_VERSIONS=FW_VERSIONS,
extra_fw_versions=extra_fw_versions))
if __name__ == "__main__": if __name__ == "__main__":

@ -31,9 +31,8 @@ POSENET_STD_HIST_HALF = 20
def init_xyz_measurement(measurement: capnp._DynamicStructBuilder, values: np.ndarray, stds: np.ndarray, valid: bool): def init_xyz_measurement(measurement: capnp._DynamicStructBuilder, values: np.ndarray, stds: np.ndarray, valid: bool):
assert len(values) == len(stds) == 3 assert len(values) == len(stds) == 3
for i, d in enumerate(("x", "y", "z")): measurement.x, measurement.y, measurement.z = map(float, values)
setattr(measurement, d, float(values[i])) measurement.xStd, measurement.yStd, measurement.zStd = map(float, stds)
setattr(measurement, f"{d}Std", float(stds[i]))
measurement.valid = valid measurement.valid = valid
@ -50,7 +49,7 @@ class LocationEstimator:
self.debug = debug self.debug = debug
self.posenet_stds = [POSENET_STD_INITIAL_VALUE] * (POSENET_STD_HIST_HALF * 2) self.posenet_stds = np.array([POSENET_STD_INITIAL_VALUE] * (POSENET_STD_HIST_HALF * 2))
self.car_speed = 0.0 self.car_speed = 0.0
self.camodo_yawrate_distribution = np.array([0.0, 10.0]) # mean, std self.camodo_yawrate_distribution = np.array([0.0, 10.0]) # mean, std
self.device_from_calib = np.eye(3) self.device_from_calib = np.eye(3)
@ -169,8 +168,8 @@ class LocationEstimator:
if np.linalg.norm(rot_calib_std) > 10 * ROTATION_SANITY_CHECK or np.linalg.norm(trans_calib_std) > 10 * TRANS_SANITY_CHECK: if np.linalg.norm(rot_calib_std) > 10 * ROTATION_SANITY_CHECK or np.linalg.norm(trans_calib_std) > 10 * TRANS_SANITY_CHECK:
return HandleLogResult.INPUT_INVALID return HandleLogResult.INPUT_INVALID
self.posenet_stds.pop(0) self.posenet_stds = np.roll(self.posenet_stds, -1)
self.posenet_stds.append(trans_calib_std[0]) self.posenet_stds[-1] = trans_calib_std[0]
# Multiply by N to avoid to high certainty in kalman filter because of temporally correlated noise # Multiply by N to avoid to high certainty in kalman filter because of temporally correlated noise
rot_calib_std *= 10 rot_calib_std *= 10

@ -69,6 +69,10 @@ if arch == "larch64" or GetOption('pc_thneed'):
lenv.Command(fn + ".thneed", [fn + ".onnx"] + tinygrad_files, cmd) lenv.Command(fn + ".thneed", [fn + ".onnx"] + tinygrad_files, cmd)
fn_dm = File("models/dmonitoring_model").abspath
cmd = f"cd {Dir('#').abspath}/tinygrad_repo && " + ' '.join(tinygrad_opts) + f" python3 openpilot/compile2.py {fn_dm}.onnx {fn_dm}.thneed"
lenv.Command(fn_dm + ".thneed", [fn_dm + ".onnx"] + tinygrad_files, cmd)
thneed_lib = env.SharedLibrary('thneed', thneed_src, LIBS=[gpucommon, common, 'OpenCL', 'dl']) thneed_lib = env.SharedLibrary('thneed', thneed_src, LIBS=[gpucommon, common, 'OpenCL', 'dl'])
thneedmodel_lib = env.Library('thneedmodel', ['runners/thneedmodel.cc']) thneedmodel_lib = env.Library('thneedmodel', ['runners/thneedmodel.cc'])
lenvCython.Program('runners/thneedmodel_pyx.so', 'runners/thneedmodel_pyx.pyx', LIBS=envCython["LIBS"]+[thneedmodel_lib, thneed_lib, gpucommon, common, 'dl', 'OpenCL']) lenvCython.Program('runners/thneedmodel_pyx.so', 'runners/thneedmodel_pyx.pyx', LIBS=envCython["LIBS"]+[thneedmodel_lib, thneed_lib, gpucommon, common, 'dl', 'OpenCL'])

@ -15,7 +15,8 @@ class ModelConstants:
# model inputs constants # model inputs constants
MODEL_FREQ = 20 MODEL_FREQ = 20
FEATURE_LEN = 512 FEATURE_LEN = 512
HISTORY_BUFFER_LEN = 99 FULL_HISTORY_BUFFER_LEN = 99
HISTORY_BUFFER_LEN = 24
DESIRE_LEN = 8 DESIRE_LEN = 8
TRAFFIC_CONVENTION_LEN = 2 TRAFFIC_CONVENTION_LEN = 2
LAT_PLANNER_STATE_LEN = 4 LAT_PLANNER_STATE_LEN = 4
@ -72,14 +73,14 @@ class Plan:
class Meta: class Meta:
ENGAGED = slice(0, 1) ENGAGED = slice(0, 1)
# next 2, 4, 6, 8, 10 seconds # next 2, 4, 6, 8, 10 seconds
GAS_DISENGAGE = slice(1, 41, 8) GAS_DISENGAGE = slice(1, 31, 6)
BRAKE_DISENGAGE = slice(2, 41, 8) BRAKE_DISENGAGE = slice(2, 31, 6)
STEER_OVERRIDE = slice(3, 41, 8) STEER_OVERRIDE = slice(3, 31, 6)
HARD_BRAKE_3 = slice(4, 41, 8) HARD_BRAKE_3 = slice(4, 31, 6)
HARD_BRAKE_4 = slice(5, 41, 8) HARD_BRAKE_4 = slice(5, 31, 6)
HARD_BRAKE_5 = slice(6, 41, 8) HARD_BRAKE_5 = slice(6, 31, 6)
GAS_PRESS = slice(7, 41, 8)
BRAKE_PRESS = slice(8, 41, 8)
# next 0, 2, 4, 6, 8, 10 seconds # next 0, 2, 4, 6, 8, 10 seconds
LEFT_BLINKER = slice(41, 53, 2) GAS_PRESS = slice(31, 55, 4)
RIGHT_BLINKER = slice(42, 53, 2) BRAKE_PRESS = slice(32, 55, 4)
LEFT_BLINKER = slice(33, 55, 4)
RIGHT_BLINKER = slice(34, 55, 4)

@ -0,0 +1,10 @@
#!/usr/bin/env bash
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
cd "$DIR/../../"
if [ -f "$DIR/libthneed.so" ]; then
export LD_PRELOAD="$DIR/libthneed.so"
fi
exec "$DIR/dmonitoringmodeld.py" "$@"

@ -6,6 +6,7 @@ import time
import ctypes import ctypes
import numpy as np import numpy as np
from pathlib import Path from pathlib import Path
from setproctitle import setproctitle
from cereal import messaging from cereal import messaging
from cereal.messaging import PubMaster, SubMaster from cereal.messaging import PubMaster, SubMaster
@ -14,16 +15,19 @@ from openpilot.common.swaglog import cloudlog
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.realtime import set_realtime_priority from openpilot.common.realtime import set_realtime_priority
from openpilot.selfdrive.modeld.runners import ModelRunner, Runtime from openpilot.selfdrive.modeld.runners import ModelRunner, Runtime
from openpilot.selfdrive.modeld.models.commonmodel_pyx import sigmoid from openpilot.selfdrive.modeld.models.commonmodel_pyx import CLContext
from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid
CALIB_LEN = 3 CALIB_LEN = 3
REG_SCALE = 0.25
MODEL_WIDTH = 1440 MODEL_WIDTH = 1440
MODEL_HEIGHT = 960 MODEL_HEIGHT = 960
OUTPUT_SIZE = 84 FEATURE_LEN = 512
OUTPUT_SIZE = 84 + FEATURE_LEN
PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld"
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
MODEL_PATHS = { MODEL_PATHS = {
ModelRunner.SNPE: Path(__file__).parent / 'models/dmonitoring_model_q.dlc', ModelRunner.THNEED: Path(__file__).parent / 'models/dmonitoring_model.thneed',
ModelRunner.ONNX: Path(__file__).parent / 'models/dmonitoring_model.onnx'} ModelRunner.ONNX: Path(__file__).parent / 'models/dmonitoring_model.onnx'}
class DriverStateResult(ctypes.Structure): class DriverStateResult(ctypes.Structure):
@ -49,21 +53,22 @@ class DMonitoringModelResult(ctypes.Structure):
("driver_state_lhd", DriverStateResult), ("driver_state_lhd", DriverStateResult),
("driver_state_rhd", DriverStateResult), ("driver_state_rhd", DriverStateResult),
("poor_vision_prob", ctypes.c_float), ("poor_vision_prob", ctypes.c_float),
("wheel_on_right_prob", ctypes.c_float)] ("wheel_on_right_prob", ctypes.c_float),
("features", ctypes.c_float*FEATURE_LEN)]
class ModelState: class ModelState:
inputs: dict[str, np.ndarray] inputs: dict[str, np.ndarray]
output: np.ndarray output: np.ndarray
model: ModelRunner model: ModelRunner
def __init__(self): def __init__(self, cl_ctx):
assert ctypes.sizeof(DMonitoringModelResult) == OUTPUT_SIZE * ctypes.sizeof(ctypes.c_float) assert ctypes.sizeof(DMonitoringModelResult) == OUTPUT_SIZE * ctypes.sizeof(ctypes.c_float)
self.output = np.zeros(OUTPUT_SIZE, dtype=np.float32) self.output = np.zeros(OUTPUT_SIZE, dtype=np.float32)
self.inputs = { self.inputs = {
'input_img': np.zeros(MODEL_HEIGHT * MODEL_WIDTH, dtype=np.uint8), 'input_img': np.zeros(MODEL_HEIGHT * MODEL_WIDTH, dtype=np.uint8),
'calib': np.zeros(CALIB_LEN, dtype=np.float32)} 'calib': np.zeros(CALIB_LEN, dtype=np.float32)}
self.model = ModelRunner(MODEL_PATHS, self.output, Runtime.DSP, True, None) self.model = ModelRunner(MODEL_PATHS, self.output, Runtime.GPU, False, cl_ctx)
self.model.addInput("input_img", None) self.model.addInput("input_img", None)
self.model.addInput("calib", self.inputs['calib']) self.model.addInput("calib", self.inputs['calib'])
@ -76,37 +81,37 @@ class ModelState:
input_data = self.inputs['input_img'].reshape(MODEL_HEIGHT, MODEL_WIDTH) input_data = self.inputs['input_img'].reshape(MODEL_HEIGHT, MODEL_WIDTH)
input_data[:] = buf_data[v_offset:v_offset+MODEL_HEIGHT, h_offset:h_offset+MODEL_WIDTH] input_data[:] = buf_data[v_offset:v_offset+MODEL_HEIGHT, h_offset:h_offset+MODEL_WIDTH]
t1 = time.perf_counter()
self.model.setInputBuffer("input_img", self.inputs['input_img'].view(np.float32)) self.model.setInputBuffer("input_img", self.inputs['input_img'].view(np.float32))
t1 = time.perf_counter()
self.model.execute() self.model.execute()
t2 = time.perf_counter() t2 = time.perf_counter()
return self.output, t2 - t1 return self.output, t2 - t1
def fill_driver_state(msg, ds_result: DriverStateResult): def fill_driver_state(msg, ds_result: DriverStateResult):
msg.faceOrientation = [x * REG_SCALE for x in ds_result.face_orientation] msg.faceOrientation = list(ds_result.face_orientation)
msg.faceOrientationStd = [math.exp(x) for x in ds_result.face_orientation_std] msg.faceOrientationStd = [math.exp(x) for x in ds_result.face_orientation_std]
msg.facePosition = [x * REG_SCALE for x in ds_result.face_position[:2]] msg.facePosition = list(ds_result.face_position[:2])
msg.facePositionStd = [math.exp(x) for x in ds_result.face_position_std[:2]] msg.facePositionStd = [math.exp(x) for x in ds_result.face_position_std[:2]]
msg.faceProb = sigmoid(ds_result.face_prob) msg.faceProb = float(sigmoid(ds_result.face_prob))
msg.leftEyeProb = sigmoid(ds_result.left_eye_prob) msg.leftEyeProb = float(sigmoid(ds_result.left_eye_prob))
msg.rightEyeProb = sigmoid(ds_result.right_eye_prob) msg.rightEyeProb = float(sigmoid(ds_result.right_eye_prob))
msg.leftBlinkProb = sigmoid(ds_result.left_blink_prob) msg.leftBlinkProb = float(sigmoid(ds_result.left_blink_prob))
msg.rightBlinkProb = sigmoid(ds_result.right_blink_prob) msg.rightBlinkProb = float(sigmoid(ds_result.right_blink_prob))
msg.sunglassesProb = sigmoid(ds_result.sunglasses_prob) msg.sunglassesProb = float(sigmoid(ds_result.sunglasses_prob))
msg.occludedProb = sigmoid(ds_result.occluded_prob) msg.occludedProb = float(sigmoid(ds_result.occluded_prob))
msg.readyProb = [sigmoid(x) for x in ds_result.ready_prob] msg.readyProb = [float(sigmoid(x)) for x in ds_result.ready_prob]
msg.notReadyProb = [sigmoid(x) for x in ds_result.not_ready_prob] msg.notReadyProb = [float(sigmoid(x)) for x in ds_result.not_ready_prob]
def get_driverstate_packet(model_output: np.ndarray, frame_id: int, location_ts: int, execution_time: float, dsp_execution_time: float): def get_driverstate_packet(model_output: np.ndarray, frame_id: int, location_ts: int, execution_time: float, gpu_execution_time: float):
model_result = ctypes.cast(model_output.ctypes.data, ctypes.POINTER(DMonitoringModelResult)).contents model_result = ctypes.cast(model_output.ctypes.data, ctypes.POINTER(DMonitoringModelResult)).contents
msg = messaging.new_message('driverStateV2', valid=True) msg = messaging.new_message('driverStateV2', valid=True)
ds = msg.driverStateV2 ds = msg.driverStateV2
ds.frameId = frame_id ds.frameId = frame_id
ds.modelExecutionTime = execution_time ds.modelExecutionTime = execution_time
ds.dspExecutionTime = dsp_execution_time ds.gpuExecutionTime = gpu_execution_time
ds.poorVisionProb = sigmoid(model_result.poor_vision_prob) ds.poorVisionProb = float(sigmoid(model_result.poor_vision_prob))
ds.wheelOnRightProb = sigmoid(model_result.wheel_on_right_prob) ds.wheelOnRightProb = float(sigmoid(model_result.wheel_on_right_prob))
ds.rawPredictions = model_output.tobytes() if SEND_RAW_PRED else b'' ds.rawPredictions = model_output.tobytes() if SEND_RAW_PRED else b''
fill_driver_state(ds.leftDriverData, model_result.driver_state_lhd) fill_driver_state(ds.leftDriverData, model_result.driver_state_lhd)
fill_driver_state(ds.rightDriverData, model_result.driver_state_rhd) fill_driver_state(ds.rightDriverData, model_result.driver_state_rhd)
@ -115,14 +120,16 @@ def get_driverstate_packet(model_output: np.ndarray, frame_id: int, location_ts:
def main(): def main():
gc.disable() gc.disable()
setproctitle(PROCESS_NAME)
set_realtime_priority(1) set_realtime_priority(1)
model = ModelState() cl_context = CLContext()
model = ModelState(cl_context)
cloudlog.warning("models loaded, dmonitoringmodeld starting") cloudlog.warning("models loaded, dmonitoringmodeld starting")
Params().put_bool("DmModelInitialized", True) Params().put_bool("DmModelInitialized", True)
cloudlog.warning("connecting to driver stream") cloudlog.warning("connecting to driver stream")
vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True) vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True, cl_context)
while not vipc_client.connect(False): while not vipc_client.connect(False):
time.sleep(0.1) time.sleep(0.1)
assert vipc_client.is_connected() assert vipc_client.is_connected()
@ -144,10 +151,10 @@ def main():
calib[:] = np.array(sm["liveCalibration"].rpyCalib) calib[:] = np.array(sm["liveCalibration"].rpyCalib)
t1 = time.perf_counter() t1 = time.perf_counter()
model_output, dsp_execution_time = model.run(buf, calib) model_output, gpu_execution_time = model.run(buf, calib)
t2 = time.perf_counter() t2 = time.perf_counter()
pm.send("driverStateV2", get_driverstate_packet(model_output, vipc_client.frame_id, vipc_client.timestamp_sof, t2 - t1, dsp_execution_time)) pm.send("driverStateV2", get_driverstate_packet(model_output, vipc_client.frame_id, vipc_client.timestamp_sof, t2 - t1, gpu_execution_time))
# print("dmonitoring process: %.2fms, from last %.2fms\n" % (t2 - t1, t1 - last)) # print("dmonitoring process: %.2fms, from last %.2fms\n" % (t2 - t1, t1 - last))
# last = t1 # last = t1

@ -48,6 +48,12 @@ def fill_xyz_poly(builder, degree, x, y, z):
builder.yCoefficients = coeffs[:, 1].tolist() builder.yCoefficients = coeffs[:, 1].tolist()
builder.zCoefficients = coeffs[:, 2].tolist() builder.zCoefficients = coeffs[:, 2].tolist()
def fill_lane_line_meta(builder, lane_lines, lane_line_probs):
builder.leftY = lane_lines[1].y[0]
builder.leftProb = lane_line_probs[1]
builder.rightY = lane_lines[2].y[0]
builder.rightProb = lane_line_probs[2]
def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._DynamicStructBuilder, def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._DynamicStructBuilder,
net_output_data: dict[str, np.ndarray], publish_state: PublishState, net_output_data: dict[str, np.ndarray], publish_state: PublishState,
vipc_frame_id: int, vipc_frame_id_extra: int, frame_id: int, frame_drop: float, vipc_frame_id: int, vipc_frame_id_extra: int, frame_id: int, frame_drop: float,
@ -62,6 +68,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
driving_model_data.frameId = vipc_frame_id driving_model_data.frameId = vipc_frame_id
driving_model_data.frameIdExtra = vipc_frame_id_extra driving_model_data.frameIdExtra = vipc_frame_id_extra
driving_model_data.frameDropPerc = frame_drop_perc driving_model_data.frameDropPerc = frame_drop_perc
driving_model_data.modelExecutionTime = model_execution_time
action = driving_model_data.action action = driving_model_data.action
action.desiredCurvature = float(net_output_data['desired_curvature'][0,0]) action.desiredCurvature = float(net_output_data['desired_curvature'][0,0])
@ -86,6 +93,13 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
orientation_rate = modelV2.orientationRate orientation_rate = modelV2.orientationRate
fill_xyzt(orientation_rate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T) fill_xyzt(orientation_rate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T)
# temporal pose
temporal_pose = modelV2.temporalPose
temporal_pose.trans = net_output_data['plan'][0,0,Plan.VELOCITY].tolist()
temporal_pose.transStd = net_output_data['plan_stds'][0,0,Plan.VELOCITY].tolist()
temporal_pose.rot = net_output_data['plan'][0,0,Plan.ORIENTATION_RATE].tolist()
temporal_pose.rotStd = net_output_data['plan_stds'][0,0,Plan.ORIENTATION_RATE].tolist()
# poly path # poly path
poly_path = driving_model_data.path poly_path = driving_model_data.path
fill_xyz_poly(poly_path, ModelConstants.POLY_PATH_DEGREE, *net_output_data['plan'][0,:,Plan.POSITION].T) fill_xyz_poly(poly_path, ModelConstants.POLY_PATH_DEGREE, *net_output_data['plan'][0,:,Plan.POSITION].T)
@ -122,10 +136,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
modelV2.laneLineProbs = net_output_data['lane_lines_prob'][0,1::2].tolist() modelV2.laneLineProbs = net_output_data['lane_lines_prob'][0,1::2].tolist()
lane_line_meta = driving_model_data.laneLineMeta lane_line_meta = driving_model_data.laneLineMeta
lane_line_meta.leftY = modelV2.laneLines[1].y[0] fill_lane_line_meta(lane_line_meta, modelV2.laneLines, modelV2.laneLineProbs)
lane_line_meta.leftProb = modelV2.laneLineProbs[1]
lane_line_meta.rightY = modelV2.laneLines[2].y[0]
lane_line_meta.rightProb = modelV2.laneLineProbs[2]
# road edges # road edges
modelV2.init('roadEdges', 2) modelV2.init('roadEdges', 2)
@ -167,13 +178,6 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
(publish_state.prev_brake_3ms2_probs > ModelConstants.FCW_THRESHOLDS_3MS2).all() (publish_state.prev_brake_3ms2_probs > ModelConstants.FCW_THRESHOLDS_3MS2).all()
meta.hardBrakePredicted = hard_brake_predicted.item() meta.hardBrakePredicted = hard_brake_predicted.item()
# temporal pose
temporal_pose = modelV2.temporalPose
temporal_pose.trans = net_output_data['sim_pose'][0,:3].tolist()
temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:3].tolist()
temporal_pose.rot = net_output_data['sim_pose'][0,3:].tolist()
temporal_pose.rotStd = net_output_data['sim_pose_stds'][0,3:].tolist()
# confidence # confidence
if vipc_frame_id % (2*ModelConstants.MODEL_FREQ) == 0: if vipc_frame_id % (2*ModelConstants.MODEL_FREQ) == 0:
# any disengage prob # any disengage prob

@ -17,7 +17,6 @@ from openpilot.common.realtime import config_realtime_process
from openpilot.common.transformations.camera import DEVICE_CAMERAS from openpilot.common.transformations.camera import DEVICE_CAMERAS
from openpilot.common.transformations.model import get_warp_matrix from openpilot.common.transformations.model import get_warp_matrix
from openpilot.system import sentry from openpilot.system import sentry
from openpilot.selfdrive.car.card import convert_to_capnp
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
from openpilot.selfdrive.modeld.runners import ModelRunner, Runtime from openpilot.selfdrive.modeld.runners import ModelRunner, Runtime
from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.parse_model_outputs import Parser
@ -34,6 +33,7 @@ MODEL_PATHS = {
METADATA_PATH = Path(__file__).parent / 'models/supercombo_metadata.pkl' METADATA_PATH = Path(__file__).parent / 'models/supercombo_metadata.pkl'
class FrameMeta: class FrameMeta:
frame_id: int = 0 frame_id: int = 0
timestamp_sof: int = 0 timestamp_sof: int = 0
@ -55,6 +55,11 @@ class ModelState:
self.frame = ModelFrame(context) self.frame = ModelFrame(context)
self.wide_frame = ModelFrame(context) self.wide_frame = ModelFrame(context)
self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
self.full_features_20Hz = np.zeros((ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32)
self.desire_20Hz = np.zeros((ModelConstants.FULL_HISTORY_BUFFER_LEN + 1, ModelConstants.DESIRE_LEN), dtype=np.float32)
self.prev_desired_curv_20hz = np.zeros((ModelConstants.FULL_HISTORY_BUFFER_LEN + 1, ModelConstants.PREV_DESIRED_CURV_LEN), dtype=np.float32)
# img buffers are managed in openCL transform code
self.inputs = { self.inputs = {
'desire': np.zeros(ModelConstants.DESIRE_LEN * (ModelConstants.HISTORY_BUFFER_LEN+1), dtype=np.float32), 'desire': np.zeros(ModelConstants.DESIRE_LEN * (ModelConstants.HISTORY_BUFFER_LEN+1), dtype=np.float32),
'traffic_convention': np.zeros(ModelConstants.TRAFFIC_CONVENTION_LEN, dtype=np.float32), 'traffic_convention': np.zeros(ModelConstants.TRAFFIC_CONVENTION_LEN, dtype=np.float32),
@ -87,16 +92,17 @@ class ModelState:
inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None: inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None:
# Model decides when action is completed, so desire input is just a pulse triggered on rising edge # Model decides when action is completed, so desire input is just a pulse triggered on rising edge
inputs['desire'][0] = 0 inputs['desire'][0] = 0
self.inputs['desire'][:-ModelConstants.DESIRE_LEN] = self.inputs['desire'][ModelConstants.DESIRE_LEN:] new_desire = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0)
self.inputs['desire'][-ModelConstants.DESIRE_LEN:] = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0)
self.prev_desire[:] = inputs['desire'] self.prev_desire[:] = inputs['desire']
self.desire_20Hz[:-1] = self.desire_20Hz[1:]
self.desire_20Hz[-1] = new_desire
self.inputs['desire'][:] = self.desire_20Hz.reshape((25,4,-1)).max(axis=1).flatten()
self.inputs['traffic_convention'][:] = inputs['traffic_convention'] self.inputs['traffic_convention'][:] = inputs['traffic_convention']
self.inputs['lateral_control_params'][:] = inputs['lateral_control_params'] self.inputs['lateral_control_params'][:] = inputs['lateral_control_params']
# if getCLBuffer is not None, frame will be None
self.model.setInputBuffer("input_imgs", self.frame.prepare(buf, transform.flatten(), self.model.getCLBuffer("input_imgs"))) self.model.setInputBuffer("input_imgs", self.frame.prepare(buf, transform.flatten(), self.model.getCLBuffer("input_imgs")))
if wbuf is not None:
self.model.setInputBuffer("big_input_imgs", self.wide_frame.prepare(wbuf, transform_wide.flatten(), self.model.getCLBuffer("big_input_imgs"))) self.model.setInputBuffer("big_input_imgs", self.wide_frame.prepare(wbuf, transform_wide.flatten(), self.model.getCLBuffer("big_input_imgs")))
if prepare_only: if prepare_only:
@ -105,10 +111,16 @@ class ModelState:
self.model.execute() self.model.execute()
outputs = self.parser.parse_outputs(self.slice_outputs(self.output)) outputs = self.parser.parse_outputs(self.slice_outputs(self.output))
self.inputs['features_buffer'][:-ModelConstants.FEATURE_LEN] = self.inputs['features_buffer'][ModelConstants.FEATURE_LEN:] self.full_features_20Hz[:-1] = self.full_features_20Hz[1:]
self.inputs['features_buffer'][-ModelConstants.FEATURE_LEN:] = outputs['hidden_state'][0, :] self.full_features_20Hz[-1] = outputs['hidden_state'][0, :]
self.inputs['prev_desired_curv'][:-ModelConstants.PREV_DESIRED_CURV_LEN] = self.inputs['prev_desired_curv'][ModelConstants.PREV_DESIRED_CURV_LEN:]
self.inputs['prev_desired_curv'][-ModelConstants.PREV_DESIRED_CURV_LEN:] = outputs['desired_curvature'][0, :] self.prev_desired_curv_20hz[:-1] = self.prev_desired_curv_20hz[1:]
self.prev_desired_curv_20hz[-1] = outputs['desired_curvature'][0, :]
idxs = np.arange(-4,-100,-4)[::-1]
self.inputs['features_buffer'][:] = self.full_features_20Hz[idxs].flatten()
# TODO model only uses last value now, once that changes we need to input strided action history buffer
self.inputs['prev_desired_curv'][-ModelConstants.PREV_DESIRED_CURV_LEN:] = 0. * self.prev_desired_curv_20hz[-4, :]
return outputs return outputs
@ -171,10 +183,9 @@ def main(demo=False):
if demo: if demo:
CP = convert_to_capnp(get_demo_car_params()) CP = get_demo_car_params()
else: else:
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
cloudlog.info("modeld got CarParams: %s", CP.carName) cloudlog.info("modeld got CarParams: %s", CP.carName)
# TODO this needs more thought, use .2s extra for now to estimate other delays # TODO this needs more thought, use .2s extra for now to estimate other delays
@ -219,7 +230,8 @@ def main(demo=False):
desire = DH.desire desire = DH.desire
is_rhd = sm["driverMonitoringState"].isRHD is_rhd = sm["driverMonitoringState"].isRHD
frame_id = sm["roadCameraState"].frameId frame_id = sm["roadCameraState"].frameId
lateral_control_params = np.array([sm["carState"].vEgo, steer_delay], dtype=np.float32) v_ego = max(sm["carState"].vEgo, 0.)
lateral_control_params = np.array([v_ego, steer_delay], dtype=np.float32)
if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']: if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']:
device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32)
dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))] dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))]

@ -7,32 +7,39 @@
#include "common/clutil.h" #include "common/clutil.h"
ModelFrame::ModelFrame(cl_device_id device_id, cl_context context) { ModelFrame::ModelFrame(cl_device_id device_id, cl_context context) {
input_frames = std::make_unique<float[]>(buf_size); input_frames = std::make_unique<uint8_t[]>(buf_size);
q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err)); q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err));
y_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, MODEL_WIDTH * MODEL_HEIGHT, NULL, &err)); y_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, MODEL_WIDTH * MODEL_HEIGHT, NULL, &err));
u_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (MODEL_WIDTH / 2) * (MODEL_HEIGHT / 2), NULL, &err)); u_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (MODEL_WIDTH / 2) * (MODEL_HEIGHT / 2), NULL, &err));
v_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (MODEL_WIDTH / 2) * (MODEL_HEIGHT / 2), NULL, &err)); v_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (MODEL_WIDTH / 2) * (MODEL_HEIGHT / 2), NULL, &err));
net_input_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, MODEL_FRAME_SIZE * sizeof(float), NULL, &err)); img_buffer_20hz_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, 5*frame_size_bytes, NULL, &err));
region.origin = 4 * frame_size_bytes;
region.size = frame_size_bytes;
last_img_cl = CL_CHECK_ERR(clCreateSubBuffer(img_buffer_20hz_cl, CL_MEM_READ_WRITE, CL_BUFFER_CREATE_TYPE_REGION, &region, &err));
transform_init(&transform, context, device_id); transform_init(&transform, context, device_id);
loadyuv_init(&loadyuv, context, device_id, MODEL_WIDTH, MODEL_HEIGHT); loadyuv_init(&loadyuv, context, device_id, MODEL_WIDTH, MODEL_HEIGHT);
} }
float* ModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3 &projection, cl_mem *output) { uint8_t* ModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3 &projection, cl_mem *output) {
transform_queue(&this->transform, q, transform_queue(&this->transform, q,
yuv_cl, frame_width, frame_height, frame_stride, frame_uv_offset, yuv_cl, frame_width, frame_height, frame_stride, frame_uv_offset,
y_cl, u_cl, v_cl, MODEL_WIDTH, MODEL_HEIGHT, projection); y_cl, u_cl, v_cl, MODEL_WIDTH, MODEL_HEIGHT, projection);
for (int i = 0; i < 4; i++) {
CL_CHECK(clEnqueueCopyBuffer(q, img_buffer_20hz_cl, img_buffer_20hz_cl, (i+1)*frame_size_bytes, i*frame_size_bytes, frame_size_bytes, 0, nullptr, nullptr));
}
loadyuv_queue(&loadyuv, q, y_cl, u_cl, v_cl, last_img_cl);
if (output == NULL) { if (output == NULL) {
loadyuv_queue(&loadyuv, q, y_cl, u_cl, v_cl, net_input_cl); CL_CHECK(clEnqueueReadBuffer(q, img_buffer_20hz_cl, CL_TRUE, 0, frame_size_bytes, &input_frames[0], 0, nullptr, nullptr));
CL_CHECK(clEnqueueReadBuffer(q, last_img_cl, CL_TRUE, 0, frame_size_bytes, &input_frames[MODEL_FRAME_SIZE], 0, nullptr, nullptr));
std::memmove(&input_frames[0], &input_frames[MODEL_FRAME_SIZE], sizeof(float) * MODEL_FRAME_SIZE);
CL_CHECK(clEnqueueReadBuffer(q, net_input_cl, CL_TRUE, 0, MODEL_FRAME_SIZE * sizeof(float), &input_frames[MODEL_FRAME_SIZE], 0, nullptr, nullptr));
clFinish(q); clFinish(q);
return &input_frames[0]; return &input_frames[0];
} else { } else {
loadyuv_queue(&loadyuv, q, y_cl, u_cl, v_cl, *output, true); copy_queue(&loadyuv, q, img_buffer_20hz_cl, *output, 0, 0, frame_size_bytes);
copy_queue(&loadyuv, q, last_img_cl, *output, 0, frame_size_bytes, frame_size_bytes);
// NOTE: Since thneed is using a different command queue, this clFinish is needed to ensure the image is ready. // NOTE: Since thneed is using a different command queue, this clFinish is needed to ensure the image is ready.
clFinish(q); clFinish(q);
return NULL; return NULL;
@ -42,13 +49,10 @@ float* ModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int
ModelFrame::~ModelFrame() { ModelFrame::~ModelFrame() {
transform_destroy(&transform); transform_destroy(&transform);
loadyuv_destroy(&loadyuv); loadyuv_destroy(&loadyuv);
CL_CHECK(clReleaseMemObject(net_input_cl)); CL_CHECK(clReleaseMemObject(img_buffer_20hz_cl));
CL_CHECK(clReleaseMemObject(last_img_cl));
CL_CHECK(clReleaseMemObject(v_cl)); CL_CHECK(clReleaseMemObject(v_cl));
CL_CHECK(clReleaseMemObject(u_cl)); CL_CHECK(clReleaseMemObject(u_cl));
CL_CHECK(clReleaseMemObject(y_cl)); CL_CHECK(clReleaseMemObject(y_cl));
CL_CHECK(clReleaseCommandQueue(q)); CL_CHECK(clReleaseCommandQueue(q));
} }
float sigmoid(float input) {
return 1 / (1 + expf(-input));
}

@ -16,23 +16,23 @@
#include "selfdrive/modeld/transforms/loadyuv.h" #include "selfdrive/modeld/transforms/loadyuv.h"
#include "selfdrive/modeld/transforms/transform.h" #include "selfdrive/modeld/transforms/transform.h"
float sigmoid(float input);
class ModelFrame { class ModelFrame {
public: public:
ModelFrame(cl_device_id device_id, cl_context context); ModelFrame(cl_device_id device_id, cl_context context);
~ModelFrame(); ~ModelFrame();
float* prepare(cl_mem yuv_cl, int width, int height, int frame_stride, int frame_uv_offset, const mat3& transform, cl_mem *output); uint8_t* prepare(cl_mem yuv_cl, int width, int height, int frame_stride, int frame_uv_offset, const mat3& transform, cl_mem *output);
const int MODEL_WIDTH = 512; const int MODEL_WIDTH = 512;
const int MODEL_HEIGHT = 256; const int MODEL_HEIGHT = 256;
const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2;
const int buf_size = MODEL_FRAME_SIZE * 2; const int buf_size = MODEL_FRAME_SIZE * 2;
const size_t frame_size_bytes = MODEL_FRAME_SIZE * sizeof(uint8_t);
private: private:
Transform transform; Transform transform;
LoadYUVState loadyuv; LoadYUVState loadyuv;
cl_command_queue q; cl_command_queue q;
cl_mem y_cl, u_cl, v_cl, net_input_cl; cl_mem y_cl, u_cl, v_cl, img_buffer_20hz_cl, last_img_cl;
std::unique_ptr<float[]> input_frames; cl_buffer_region region;
std::unique_ptr<uint8_t[]> input_frames;
}; };

@ -12,9 +12,7 @@ cdef extern from "common/clutil.h":
cl_context cl_create_context(cl_device_id) cl_context cl_create_context(cl_device_id)
cdef extern from "selfdrive/modeld/models/commonmodel.h": cdef extern from "selfdrive/modeld/models/commonmodel.h":
float sigmoid(float)
cppclass ModelFrame: cppclass ModelFrame:
int buf_size int buf_size
ModelFrame(cl_device_id, cl_context) ModelFrame(cl_device_id, cl_context)
float * prepare(cl_mem, int, int, int, int, mat3, cl_mem*) unsigned char * prepare(cl_mem, int, int, int, int, mat3, cl_mem*)

@ -8,10 +8,8 @@ from libc.string cimport memcpy
from msgq.visionipc.visionipc cimport cl_mem from msgq.visionipc.visionipc cimport cl_mem
from msgq.visionipc.visionipc_pyx cimport VisionBuf, CLContext as BaseCLContext from msgq.visionipc.visionipc_pyx cimport VisionBuf, CLContext as BaseCLContext
from .commonmodel cimport CL_DEVICE_TYPE_DEFAULT, cl_get_device_id, cl_create_context from .commonmodel cimport CL_DEVICE_TYPE_DEFAULT, cl_get_device_id, cl_create_context
from .commonmodel cimport mat3, sigmoid as cppSigmoid, ModelFrame as cppModelFrame from .commonmodel cimport mat3, ModelFrame as cppModelFrame
def sigmoid(x):
return cppSigmoid(x)
cdef class CLContext(BaseCLContext): cdef class CLContext(BaseCLContext):
def __cinit__(self): def __cinit__(self):
@ -37,11 +35,11 @@ cdef class ModelFrame:
def prepare(self, VisionBuf buf, float[:] projection, CLMem output): def prepare(self, VisionBuf buf, float[:] projection, CLMem output):
cdef mat3 cprojection cdef mat3 cprojection
memcpy(cprojection.v, &projection[0], 9*sizeof(float)) memcpy(cprojection.v, &projection[0], 9*sizeof(float))
cdef float * data cdef unsigned char * data
if output is None: if output is None:
data = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection, NULL) data = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection, NULL)
else: else:
data = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection, output.mem) data = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection, output.mem)
if not data: if not data:
return None return None
return np.asarray(<cnp.float32_t[:self.frame.buf_size]> data) return np.asarray(<cnp.uint8_t[:self.frame.buf_size]> data)

@ -1,2 +1,2 @@
5ec97a39-0095-4cea-adfa-6d72b1966cc1 fa69be01-b430-4504-9d72-7dcb058eb6dd
26cac7a9757a27c783a365403040a1bd27ccdaea d9fb22d1c4fa3ca3d201dbc8edf1d0f0918e53e6

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:3dd3982940d823c4fbb0429b733a0b78b0688d7d67aa76ff7b754a3e2f3d8683 oid sha256:50efe6451a3fb3fa04b6bb0e846544533329bd46ecefe9e657e91214dee2aaeb
size 16132780 size 7196502

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7c26f13816b143f5bb29ac2980f8557bd5687a75729e4d895313fb9a5a1f0f46
size 4488449

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:39786068cae1ed8c0dc34ef80c281dfcc67ed18a50e06b90765c49bcfdbf7db4 oid sha256:2431f40b8ca9926629e461e06316f9bbba984c821ebbc11e6449ca0c96c42d95
size 51453312 size 50309976

@ -1,15 +1,19 @@
import numpy as np import numpy as np
from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.modeld.constants import ModelConstants
def safe_exp(x, out=None):
# -11 is around 10**14, more causes float16 overflow
return np.exp(np.clip(x, -np.inf, 11), out=out)
def sigmoid(x): def sigmoid(x):
return 1. / (1. + np.exp(-x)) return 1. / (1. + safe_exp(-x))
def softmax(x, axis=-1): def softmax(x, axis=-1):
x -= np.max(x, axis=axis, keepdims=True) x -= np.max(x, axis=axis, keepdims=True)
if x.dtype == np.float32 or x.dtype == np.float64: if x.dtype == np.float32 or x.dtype == np.float64:
np.exp(x, out=x) safe_exp(x, out=x)
else: else:
x = np.exp(x) x = safe_exp(x)
x /= np.sum(x, axis=axis, keepdims=True) x /= np.sum(x, axis=axis, keepdims=True)
return x return x
@ -44,7 +48,7 @@ class Parser:
n_values = (raw.shape[2] - out_N)//2 n_values = (raw.shape[2] - out_N)//2
pred_mu = raw[:,:,:n_values] pred_mu = raw[:,:,:n_values]
pred_std = np.exp(raw[:,:,n_values: 2*n_values]) pred_std = safe_exp(raw[:,:,n_values: 2*n_values])
if in_N > 1: if in_N > 1:
weights = np.zeros((raw.shape[0], in_N, out_N), dtype=raw.dtype) weights = np.zeros((raw.shape[0], in_N, out_N), dtype=raw.dtype)
@ -87,7 +91,6 @@ class Parser:
self.parse_mdn('road_edges', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_ROAD_EDGES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) self.parse_mdn('road_edges', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_ROAD_EDGES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH))
self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,))
self.parse_mdn('road_transform', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) self.parse_mdn('road_transform', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,))
self.parse_mdn('sim_pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,))
self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,)) self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,))
self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION, self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION,
out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH)) out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH))

@ -14,8 +14,12 @@ def attributeproto_fp16_to_fp32(attr):
attr.data_type = 1 attr.data_type = 1
attr.raw_data = float32_list.astype(np.float32).tobytes() attr.raw_data = float32_list.astype(np.float32).tobytes()
def convert_fp16_to_fp32(path): def convert_fp16_to_fp32(onnx_path_or_bytes):
model = onnx.load(path) if isinstance(onnx_path_or_bytes, bytes):
model = onnx.load_from_string(onnx_path_or_bytes)
elif isinstance(onnx_path_or_bytes, str):
model = onnx.load(onnx_path_or_bytes)
for i in model.graph.initializer: for i in model.graph.initializer:
if i.data_type == 10: if i.data_type == 10:
attributeproto_fp16_to_fp32(i) attributeproto_fp16_to_fp32(i)
@ -23,6 +27,8 @@ def convert_fp16_to_fp32(path):
if i.type.tensor_type.elem_type == 10: if i.type.tensor_type.elem_type == 10:
i.type.tensor_type.elem_type = 1 i.type.tensor_type.elem_type = 1
for i in model.graph.node: for i in model.graph.node:
if i.op_type == 'Cast' and i.attribute[0].i == 10:
i.attribute[0].i = 1
for a in i.attribute: for a in i.attribute:
if hasattr(a, 't'): if hasattr(a, 't'):
if a.t.data_type == 10: if a.t.data_type == 10:
@ -61,7 +67,6 @@ class ONNXModel(RunModel):
def __init__(self, path, output, runtime, use_tf8, cl_context): def __init__(self, path, output, runtime, use_tf8, cl_context):
self.inputs = {} self.inputs = {}
self.output = output self.output = output
self.use_tf8 = use_tf8
self.session = create_ort_session(path, fp16_to_fp32=True) self.session = create_ort_session(path, fp16_to_fp32=True)
self.input_names = [x.name for x in self.session.get_inputs()] self.input_names = [x.name for x in self.session.get_inputs()]
@ -85,7 +90,7 @@ class ONNXModel(RunModel):
return None return None
def execute(self): def execute(self):
inputs = {k: (v.view(np.uint8) / 255. if self.use_tf8 and k == 'input_img' else v) for k,v in self.inputs.items()} inputs = {k: v.view(self.input_dtypes[k]) for k,v in self.inputs.items()}
inputs = {k: v.reshape(self.input_shapes[k]).astype(self.input_dtypes[k]) for k,v in inputs.items()} inputs = {k: v.reshape(self.input_shapes[k]).astype(self.input_dtypes[k]) for k,v in inputs.items()}
outputs = self.session.run(None, inputs) outputs = self.session.run(None, inputs)
assert len(outputs) == 1, "Only single model outputs are supported" assert len(outputs) == 1, "Only single model outputs are supported"

@ -7,7 +7,6 @@ from opendbc.car.car_helpers import get_demo_car_params
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.transformations.camera import DEVICE_CAMERAS from openpilot.common.transformations.camera import DEVICE_CAMERAS
from openpilot.common.realtime import DT_MDL from openpilot.common.realtime import DT_MDL
from openpilot.selfdrive.car.card import convert_to_capnp
from openpilot.system.manager.process_config import managed_processes from openpilot.system.manager.process_config import managed_processes
from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state
@ -24,7 +23,7 @@ class TestModeld:
self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_DRIVER, 40, False, CAM.width, CAM.height) self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_DRIVER, 40, False, CAM.width, CAM.height)
self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 40, False, CAM.width, CAM.height) self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 40, False, CAM.width, CAM.height)
self.vipc_server.start_listener() self.vipc_server.start_listener()
Params().put("CarParams", convert_to_capnp(get_demo_car_params()).to_bytes()) Params().put("CarParams", get_demo_car_params().to_bytes())
self.sm = messaging.SubMaster(['modelV2', 'cameraOdometry']) self.sm = messaging.SubMaster(['modelV2', 'cameraOdometry'])
self.pm = messaging.PubMaster(['roadCameraState', 'wideRoadCameraState', 'liveCalibration']) self.pm = messaging.PubMaster(['roadCameraState', 'wideRoadCameraState', 'liveCalibration'])

@ -33,17 +33,8 @@ void loadyuv_destroy(LoadYUVState* s) {
void loadyuv_queue(LoadYUVState* s, cl_command_queue q, void loadyuv_queue(LoadYUVState* s, cl_command_queue q,
cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, cl_mem y_cl, cl_mem u_cl, cl_mem v_cl,
cl_mem out_cl, bool do_shift) { cl_mem out_cl) {
cl_int global_out_off = 0; cl_int global_out_off = 0;
if (do_shift) {
// shift the image in slot 1 to slot 0, then place the new image in slot 1
global_out_off += (s->width*s->height) + (s->width/2)*(s->height/2)*2;
CL_CHECK(clSetKernelArg(s->copy_krnl, 0, sizeof(cl_mem), &out_cl));
CL_CHECK(clSetKernelArg(s->copy_krnl, 1, sizeof(cl_int), &global_out_off));
const size_t copy_work_size = global_out_off/8;
CL_CHECK(clEnqueueNDRangeKernel(q, s->copy_krnl, 1, NULL,
&copy_work_size, NULL, 0, 0, NULL));
}
CL_CHECK(clSetKernelArg(s->loadys_krnl, 0, sizeof(cl_mem), &y_cl)); CL_CHECK(clSetKernelArg(s->loadys_krnl, 0, sizeof(cl_mem), &y_cl));
CL_CHECK(clSetKernelArg(s->loadys_krnl, 1, sizeof(cl_mem), &out_cl)); CL_CHECK(clSetKernelArg(s->loadys_krnl, 1, sizeof(cl_mem), &out_cl));
@ -72,3 +63,14 @@ void loadyuv_queue(LoadYUVState* s, cl_command_queue q,
CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL,
&loaduv_work_size, NULL, 0, 0, NULL)); &loaduv_work_size, NULL, 0, 0, NULL));
} }
void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst,
size_t src_offset, size_t dst_offset, size_t size) {
CL_CHECK(clSetKernelArg(s->copy_krnl, 0, sizeof(cl_mem), &src));
CL_CHECK(clSetKernelArg(s->copy_krnl, 1, sizeof(cl_mem), &dst));
CL_CHECK(clSetKernelArg(s->copy_krnl, 2, sizeof(cl_int), &src_offset));
CL_CHECK(clSetKernelArg(s->copy_krnl, 3, sizeof(cl_int), &dst_offset));
const size_t copy_work_size = size/8;
CL_CHECK(clEnqueueNDRangeKernel(q, s->copy_krnl, 1, NULL,
&copy_work_size, NULL, 0, 0, NULL));
}

@ -1,7 +1,7 @@
#define UV_SIZE ((TRANSFORMED_WIDTH/2)*(TRANSFORMED_HEIGHT/2)) #define UV_SIZE ((TRANSFORMED_WIDTH/2)*(TRANSFORMED_HEIGHT/2))
__kernel void loadys(__global uchar8 const * const Y, __kernel void loadys(__global uchar8 const * const Y,
__global float * out, __global uchar * out,
int out_offset) int out_offset)
{ {
const int gid = get_global_id(0); const int gid = get_global_id(0);
@ -10,13 +10,12 @@ __kernel void loadys(__global uchar8 const * const Y,
const int ox = ois % TRANSFORMED_WIDTH; const int ox = ois % TRANSFORMED_WIDTH;
const uchar8 ys = Y[gid]; const uchar8 ys = Y[gid];
const float8 ysf = convert_float8(ys);
// 02 // 02
// 13 // 13
__global float* outy0; __global uchar* outy0;
__global float* outy1; __global uchar* outy1;
if ((oy & 1) == 0) { if ((oy & 1) == 0) {
outy0 = out + out_offset; //y0 outy0 = out + out_offset; //y0
outy1 = out + out_offset + UV_SIZE*2; //y2 outy1 = out + out_offset + UV_SIZE*2; //y2
@ -25,23 +24,24 @@ __kernel void loadys(__global uchar8 const * const Y,
outy1 = out + out_offset + UV_SIZE*3; //y3 outy1 = out + out_offset + UV_SIZE*3; //y3
} }
vstore4(ysf.s0246, 0, outy0 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); vstore4(ys.s0246, 0, outy0 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2);
vstore4(ysf.s1357, 0, outy1 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); vstore4(ys.s1357, 0, outy1 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2);
} }
__kernel void loaduv(__global uchar8 const * const in, __kernel void loaduv(__global uchar8 const * const in,
__global float8 * out, __global uchar8 * out,
int out_offset) int out_offset)
{ {
const int gid = get_global_id(0); const int gid = get_global_id(0);
const uchar8 inv = in[gid]; const uchar8 inv = in[gid];
const float8 outv = convert_float8(inv); out[gid + out_offset / 8] = inv;
out[gid + out_offset / 8] = outv;
} }
__kernel void copy(__global float8 * inout, __kernel void copy(__global uchar8 * in,
int in_offset) __global uchar8 * out,
int in_offset,
int out_offset)
{ {
const int gid = get_global_id(0); const int gid = get_global_id(0);
inout[gid] = inout[gid + in_offset / 8]; out[gid + out_offset / 8] = in[gid + in_offset / 8];
} }

@ -13,4 +13,8 @@ void loadyuv_destroy(LoadYUVState* s);
void loadyuv_queue(LoadYUVState* s, cl_command_queue q, void loadyuv_queue(LoadYUVState* s, cl_command_queue q,
cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, cl_mem y_cl, cl_mem u_cl, cl_mem v_cl,
cl_mem out_cl, bool do_shift = false); cl_mem out_cl);
void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst,
size_t src_offset, size_t dst_offset, size_t size);

@ -1,15 +1,15 @@
from math import atan2 from math import atan2
from cereal import car from cereal import car, log
import cereal.messaging as messaging import cereal.messaging as messaging
from openpilot.selfdrive.controls.lib.events import Events from openpilot.selfdrive.selfdrived.events import Events
from openpilot.common.numpy_fast import interp from openpilot.common.numpy_fast import interp
from openpilot.common.realtime import DT_DMON from openpilot.common.realtime import DT_DMON
from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.stat_live import RunningStatFilter from openpilot.common.stat_live import RunningStatFilter
from openpilot.common.transformations.camera import DEVICE_CAMERAS from openpilot.common.transformations.camera import DEVICE_CAMERAS
EventName = car.OnroadEvent.EventName EventName = log.OnroadEvent.EventName
# ****************************************************************************************** # ******************************************************************************************
# NOTE: To fork maintainers. # NOTE: To fork maintainers.
@ -33,8 +33,8 @@ class DRIVER_MONITOR_SETTINGS:
self._SG_THRESHOLD = 0.9 self._SG_THRESHOLD = 0.9
self._BLINK_THRESHOLD = 0.865 self._BLINK_THRESHOLD = 0.865
self._EE_THRESH11 = 0.25 self._EE_THRESH11 = 0.4
self._EE_THRESH12 = 7.5 self._EE_THRESH12 = 15.0
self._EE_MAX_OFFSET1 = 0.06 self._EE_MAX_OFFSET1 = 0.06
self._EE_MIN_OFFSET1 = 0.025 self._EE_MIN_OFFSET1 = 0.025
self._EE_THRESH21 = 0.01 self._EE_THRESH21 = 0.01
@ -57,7 +57,7 @@ class DRIVER_MONITOR_SETTINGS:
self._POSESTD_THRESHOLD = 0.3 self._POSESTD_THRESHOLD = 0.3
self._HI_STD_FALLBACK_TIME = int(10 / self._DT_DMON) # fall back to wheel touch if model is uncertain for 10s self._HI_STD_FALLBACK_TIME = int(10 / self._DT_DMON) # fall back to wheel touch if model is uncertain for 10s
self._DISTRACTED_FILTER_TS = 0.25 # 0.6Hz self._DISTRACTED_FILTER_TS = 0.25 # 0.6Hz
self._ALWAYS_ON_ALERT_MIN_SPEED = 7 self._ALWAYS_ON_ALERT_MIN_SPEED = 11
self._POSE_CALIB_MIN_SPEED = 13 # 30 mph self._POSE_CALIB_MIN_SPEED = 13 # 30 mph
self._POSE_OFFSET_MIN_COUNT = int(60 / self._DT_DMON) # valid data counts before calibration completes, 1min cumulative self._POSE_OFFSET_MIN_COUNT = int(60 / self._DT_DMON) # valid data counts before calibration completes, 1min cumulative
@ -337,9 +337,9 @@ class DriverMonitoring:
_reaching_audible = self.awareness - self.step_change <= self.threshold_prompt _reaching_audible = self.awareness - self.step_change <= self.threshold_prompt
_reaching_terminal = self.awareness - self.step_change <= 0 _reaching_terminal = self.awareness - self.step_change <= 0
standstill_exemption = standstill and _reaching_audible standstill_orange_exemption = standstill and _reaching_audible
always_on_red_exemption = always_on_valid and not op_engaged and _reaching_terminal always_on_red_exemption = always_on_valid and not op_engaged and _reaching_terminal
always_on_lowspeed_exemption = always_on_valid and not op_engaged and car_speed < self.settings._ALWAYS_ON_ALERT_MIN_SPEED and _reaching_audible always_on_lowspeed_exemption = always_on_valid and not op_engaged and car_speed < self.settings._ALWAYS_ON_ALERT_MIN_SPEED
certainly_distracted = self.driver_distraction_filter.x > 0.63 and self.driver_distracted and self.face_detected certainly_distracted = self.driver_distraction_filter.x > 0.63 and self.driver_distracted and self.face_detected
maybe_distracted = self.hi_stds > self.settings._HI_STD_FALLBACK_TIME or not self.face_detected maybe_distracted = self.hi_stds > self.settings._HI_STD_FALLBACK_TIME or not self.face_detected
@ -347,7 +347,7 @@ class DriverMonitoring:
if certainly_distracted or maybe_distracted: if certainly_distracted or maybe_distracted:
# should always be counting if distracted unless at standstill (lowspeed for always-on) and reaching orange # should always be counting if distracted unless at standstill (lowspeed for always-on) and reaching orange
# also will not be reaching 0 if DM is active when not engaged # also will not be reaching 0 if DM is active when not engaged
if not (standstill_exemption or always_on_red_exemption or always_on_lowspeed_exemption): if not (standstill_orange_exemption or always_on_red_exemption or (always_on_lowspeed_exemption and _reaching_audible)):
self.awareness = max(self.awareness - self.step_change, -0.1) self.awareness = max(self.awareness - self.step_change, -0.1)
alert = None alert = None
@ -360,7 +360,7 @@ class DriverMonitoring:
elif self.awareness <= self.threshold_prompt: elif self.awareness <= self.threshold_prompt:
# prompt orange alert # prompt orange alert
alert = EventName.promptDriverDistracted if self.active_monitoring_mode else EventName.promptDriverUnresponsive alert = EventName.promptDriverDistracted if self.active_monitoring_mode else EventName.promptDriverUnresponsive
elif self.awareness <= self.threshold_pre: elif self.awareness <= self.threshold_pre and not always_on_lowspeed_exemption:
# pre green alert # pre green alert
alert = EventName.preDriverDistracted if self.active_monitoring_mode else EventName.preDriverUnresponsive alert = EventName.preDriverDistracted if self.active_monitoring_mode else EventName.preDriverUnresponsive

@ -1,10 +1,10 @@
import numpy as np import numpy as np
from cereal import car, log from cereal import log
from openpilot.common.realtime import DT_DMON from openpilot.common.realtime import DT_DMON
from openpilot.selfdrive.monitoring.helpers import DriverMonitoring, DRIVER_MONITOR_SETTINGS from openpilot.selfdrive.monitoring.helpers import DriverMonitoring, DRIVER_MONITOR_SETTINGS
EventName = car.OnroadEvent.EventName EventName = log.OnroadEvent.EventName
dm_settings = DRIVER_MONITOR_SETTINGS() dm_settings = DRIVER_MONITOR_SETTINGS()
TEST_TIMESPAN = 120 # seconds TEST_TIMESPAN = 120 # seconds

@ -47,7 +47,7 @@ std::vector<std::string> Panda::list(bool usb_only) {
#ifndef __APPLE__ #ifndef __APPLE__
if (!usb_only) { if (!usb_only) {
for (auto s : PandaSpiHandle::list()) { for (const auto &s : PandaSpiHandle::list()) {
if (std::find(serials.begin(), serials.end(), s) == serials.end()) { if (std::find(serials.begin(), serials.end(), s) == serials.end()) {
serials.push_back(s); serials.push_back(s);
} }
@ -169,7 +169,7 @@ void Panda::pack_can_buffer(const capnp::List<cereal::CanData>::Reader &can_data
int32_t pos = 0; int32_t pos = 0;
uint8_t send_buf[2 * USB_TX_SOFT_LIMIT]; uint8_t send_buf[2 * USB_TX_SOFT_LIMIT];
for (auto cmsg : can_data_list) { for (const auto &cmsg : can_data_list) {
// check if the message is intended for this panda // check if the message is intended for this panda
uint8_t bus = cmsg.getSrc(); uint8_t bus = cmsg.getSrc();
if (bus < bus_offset || bus >= (bus_offset + PANDA_BUS_OFFSET)) { if (bus < bus_offset || bus >= (bus_offset + PANDA_BUS_OFFSET)) {
@ -206,7 +206,7 @@ void Panda::pack_can_buffer(const capnp::List<cereal::CanData>::Reader &can_data
if (pos > 0) write_func(send_buf, pos); if (pos > 0) write_func(send_buf, pos);
} }
void Panda::can_send(capnp::List<cereal::CanData>::Reader can_data_list) { void Panda::can_send(const capnp::List<cereal::CanData>::Reader &can_data_list) {
pack_can_buffer(can_data_list, [=](uint8_t* data, size_t size) { pack_can_buffer(can_data_list, [=](uint8_t* data, size_t size) {
handle->bulk_write(3, data, size, 5); handle->bulk_write(3, data, size, 5);
}); });

@ -12,7 +12,7 @@
#include "cereal/gen/cpp/car.capnp.h" #include "cereal/gen/cpp/car.capnp.h"
#include "cereal/gen/cpp/log.capnp.h" #include "cereal/gen/cpp/log.capnp.h"
#include "panda/board/health.h" #include "panda/board/health.h"
#include "panda/board/can_definitions.h" #include "panda/board/can.h"
#include "selfdrive/pandad/panda_comms.h" #include "selfdrive/pandad/panda_comms.h"
#define USB_TX_SOFT_LIMIT (0x100U) #define USB_TX_SOFT_LIMIT (0x100U)
@ -79,7 +79,7 @@ public:
void set_can_speed_kbps(uint16_t bus, uint16_t speed); void set_can_speed_kbps(uint16_t bus, uint16_t speed);
void set_data_speed_kbps(uint16_t bus, uint16_t speed); void set_data_speed_kbps(uint16_t bus, uint16_t speed);
void set_canfd_non_iso(uint16_t bus, bool non_iso); void set_canfd_non_iso(uint16_t bus, bool non_iso);
void can_send(capnp::List<cereal::CanData>::Reader can_data_list); void can_send(const capnp::List<cereal::CanData>::Reader &can_data_list);
bool can_receive(std::vector<can_frame>& out_vec); bool can_receive(std::vector<can_frame>& out_vec);
void can_reset_communications(); void can_reset_communications();

@ -17,6 +17,7 @@ void PandaSafety::configureSafetyMode() {
} else if (!is_onroad) { } else if (!is_onroad) {
initialized_ = false; initialized_ = false;
safety_configured_ = false; safety_configured_ = false;
log_once_ = false;
} }
} }
@ -46,9 +47,12 @@ std::string PandaSafety::fetchCarParams() {
if (!params_.getBool("FirmwareQueryDone")) { if (!params_.getBool("FirmwareQueryDone")) {
return {}; return {};
} }
LOGW("Finished FW query");
LOGW("Waiting for params to set safety model"); if (!log_once_) {
LOGW("Finished FW query, Waiting for params to set safety model");
log_once_ = true;
}
if (!params_.getBool("ControlsReady")) { if (!params_.getBool("ControlsReady")) {
return {}; return {};
} }

@ -19,6 +19,7 @@ private:
void setSafetyMode(const std::string &params_string); void setSafetyMode(const std::string &params_string);
bool initialized_ = false; bool initialized_ = false;
bool log_once_ = false;
bool safety_configured_ = false; bool safety_configured_ = false;
bool prev_obd_multiplexing_ = false; bool prev_obd_multiplexing_ = false;
std::vector<Panda *> pandas_; std::vector<Panda *> pandas_;

@ -6,10 +6,10 @@ from dataclasses import dataclass
from openpilot.common.basedir import BASEDIR from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.selfdrive.controls.lib.events import Alert from openpilot.selfdrive.selfdrived.events import Alert, EmptyAlert
with open(os.path.join(BASEDIR, "selfdrive/controls/lib/alerts_offroad.json")) as f: with open(os.path.join(BASEDIR, "selfdrive/selfdrived/alerts_offroad.json")) as f:
OFFROAD_ALERTS = json.load(f) OFFROAD_ALERTS = json.load(f)
@ -38,6 +38,7 @@ class AlertEntry:
class AlertManager: class AlertManager:
def __init__(self): def __init__(self):
self.alerts: dict[str, AlertEntry] = defaultdict(AlertEntry) self.alerts: dict[str, AlertEntry] = defaultdict(AlertEntry)
self.current_alert = EmptyAlert
def add_many(self, frame: int, alerts: list[Alert]) -> None: def add_many(self, frame: int, alerts: list[Alert]) -> None:
for alert in alerts: for alert in alerts:
@ -49,8 +50,8 @@ class AlertManager:
entry.end_frame = max(frame + 1, min_end_frame) entry.end_frame = max(frame + 1, min_end_frame)
entry.last_frame = frame entry.last_frame = frame
def process_alerts(self, frame: int, clear_event_types: set) -> Alert | None: def process_alerts(self, frame: int, clear_event_types: set):
current_alert = AlertEntry() ae = AlertEntry()
for v in self.alerts.values(): for v in self.alerts.values():
if not v.alert: if not v.alert:
continue continue
@ -59,8 +60,8 @@ class AlertManager:
v.end_frame = -1 v.end_frame = -1
# sort by priority first and then by start_frame # sort by priority first and then by start_frame
greater = current_alert.alert is None or (v.alert.priority, v.start_frame) > (current_alert.alert.priority, current_alert.start_frame) greater = ae.alert is None or (v.alert.priority, v.start_frame) > (ae.alert.priority, ae.start_frame)
if v.active(frame) and greater: if v.active(frame) and greater:
current_alert = v ae = v
return current_alert.alert self.current_alert = ae.alert if ae.alert is not None else EmptyAlert

@ -41,10 +41,6 @@
"text": "openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai.", "text": "openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai.",
"severity": 0 "severity": 0
}, },
"Offroad_NoFirmware": {
"text": "openpilot was unable to identify your car. Check integrity of cables and ensure all connections are secure, particularly that the comma power is fully inserted in the OBD-II port of the vehicle. Need help? Join discord.comma.ai.",
"severity": 0
},
"Offroad_Recalibration": { "Offroad_Recalibration": {
"text": "openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield.", "text": "openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield.",
"severity": 0 "severity": 0

@ -16,7 +16,7 @@ AlertSize = log.SelfdriveState.AlertSize
AlertStatus = log.SelfdriveState.AlertStatus AlertStatus = log.SelfdriveState.AlertStatus
VisualAlert = car.CarControl.HUDControl.VisualAlert VisualAlert = car.CarControl.HUDControl.VisualAlert
AudibleAlert = car.CarControl.HUDControl.AudibleAlert AudibleAlert = car.CarControl.HUDControl.AudibleAlert
EventName = car.OnroadEvent.EventName EventName = log.OnroadEvent.EventName
# Alert priorities # Alert priorities
@ -98,7 +98,7 @@ class Events:
def to_msg(self): def to_msg(self):
ret = [] ret = []
for event_name in self.events: for event_name in self.events:
event = car.OnroadEvent.new_message() event = log.OnroadEvent.new_message()
event.name = event_name event.name = event_name
for event_type in EVENTS.get(event_name, {}): for event_type in EVENTS.get(event_name, {}):
setattr(event, event_type, True) setattr(event, event_type, True)
@ -141,6 +141,8 @@ class Alert:
return False return False
return self.priority > alert2.priority return self.priority > alert2.priority
EmptyAlert = Alert("" , "", AlertStatus.normal, AlertSize.none, Priority.LOWEST,
VisualAlert.none, AudibleAlert.none, 0)
class NoEntryAlert(Alert): class NoEntryAlert(Alert):
def __init__(self, alert_text_2: str, def __init__(self, alert_text_2: str,
@ -318,11 +320,22 @@ def wrong_car_mode_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubM
def joystick_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: def joystick_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
axes = sm['testJoystick'].axes gb = sm['carControl'].actuators.accel / 4.
gb, steer = list(axes)[:2] if len(axes) else (0., 0.) steer = sm['carControl'].actuators.steer
vals = f"Gas: {round(gb * 100.)}%, Steer: {round(steer * 100.)}%" vals = f"Gas: {round(gb * 100.)}%, Steer: {round(steer * 100.)}%"
return NormalPermanentAlert("Joystick Mode", vals) return NormalPermanentAlert("Joystick Mode", vals)
def longitudinal_maneuver_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
ad = sm['alertDebug']
audible_alert = AudibleAlert.prompt if 'Active' in ad.alertText1 else AudibleAlert.none
alert_status = AlertStatus.userPrompt if 'Active' in ad.alertText1 else AlertStatus.normal
alert_size = AlertSize.mid if ad.alertText2 else AlertSize.small
return Alert(ad.alertText1, ad.alertText2,
alert_status, alert_size,
Priority.LOW, VisualAlert.none, audible_alert, 0.2)
def personality_changed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: def personality_changed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
personality = str(personality).title() personality = str(personality).title()
return NormalPermanentAlert(f"Driving Personality: {personality}", duration=1.5) return NormalPermanentAlert(f"Driving Personality: {personality}", duration=1.5)
@ -342,7 +355,13 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
ET.PERMANENT: NormalPermanentAlert("Joystick Mode"), ET.PERMANENT: NormalPermanentAlert("Joystick Mode"),
}, },
EventName.controlsInitializing: { EventName.longitudinalManeuver: {
ET.WARNING: longitudinal_maneuver_alert,
ET.PERMANENT: NormalPermanentAlert("Longitudinal Maneuver Mode",
"Ensure road ahead is clear"),
},
EventName.selfdriveInitializing: {
ET.NO_ENTRY: NoEntryAlert("System Initializing"), ET.NO_ENTRY: NoEntryAlert("System Initializing"),
}, },
@ -354,21 +373,19 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
ET.PERMANENT: startup_master_alert, ET.PERMANENT: startup_master_alert,
}, },
# Car is recognized, but marked as dashcam only
EventName.startupNoControl: { EventName.startupNoControl: {
ET.PERMANENT: StartupAlert("Dashcam mode"), ET.PERMANENT: StartupAlert("Dashcam mode"),
ET.NO_ENTRY: NoEntryAlert("Dashcam mode"), ET.NO_ENTRY: NoEntryAlert("Dashcam mode"),
}, },
# Car is not recognized
EventName.startupNoCar: { EventName.startupNoCar: {
ET.PERMANENT: StartupAlert("Dashcam mode for unsupported car"), ET.PERMANENT: StartupAlert("Dashcam mode for unsupported car"),
}, },
EventName.startupNoFw: { EventName.startupNoSecOcKey: {
ET.PERMANENT: StartupAlert("Car Unrecognized", ET.PERMANENT: NormalPermanentAlert("Dashcam Mode",
"Check comma power connections", "Security Key Not Available",
alert_status=AlertStatus.userPrompt), priority=Priority.HIGH),
}, },
EventName.dashcamMode: { EventName.dashcamMode: {
@ -377,8 +394,9 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
}, },
EventName.invalidLkasSetting: { EventName.invalidLkasSetting: {
ET.PERMANENT: NormalPermanentAlert("Stock LKAS is on", ET.PERMANENT: NormalPermanentAlert("Invalid LKAS setting",
"Turn off stock LKAS to engage"), "Toggle stock LKAS on or off to engage"),
ET.NO_ENTRY: NoEntryAlert("Invalid LKAS setting"),
}, },
EventName.cruiseMismatch: { EventName.cruiseMismatch: {
@ -394,6 +412,15 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
priority=Priority.LOWEST), priority=Priority.LOWEST),
}, },
EventName.aeb: {
ET.PERMANENT: Alert(
"BRAKE!",
"Emergency Braking: Risk of Collision",
AlertStatus.critical, AlertSize.full,
Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.none, 2.),
ET.NO_ENTRY: NoEntryAlert("AEB: Risk of Collision"),
},
EventName.stockAeb: { EventName.stockAeb: {
ET.PERMANENT: Alert( ET.PERMANENT: Alert(
"BRAKE!", "BRAKE!",
@ -773,9 +800,9 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
ET.NO_ENTRY: NoEntryAlert("Low Communication Rate Between Processes"), ET.NO_ENTRY: NoEntryAlert("Low Communication Rate Between Processes"),
}, },
EventName.controlsdLagging: { EventName.selfdrivedLagging: {
ET.SOFT_DISABLE: soft_disable_alert("Controls Lagging"), ET.SOFT_DISABLE: soft_disable_alert("System Lagging"),
ET.NO_ENTRY: NoEntryAlert("Controls Process Lagging: Reboot Your Device"), ET.NO_ENTRY: NoEntryAlert("Selfdrive Process Lagging: Reboot Your Device"),
}, },
# Thrown when manager detects a service exited unexpectedly while driving # Thrown when manager detects a service exited unexpectedly while driving
@ -821,12 +848,6 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
ET.NO_ENTRY: NoEntryAlert("Low Memory: Reboot Your Device"), ET.NO_ENTRY: NoEntryAlert("Low Memory: Reboot Your Device"),
}, },
EventName.highCpuUsage: {
#ET.SOFT_DISABLE: soft_disable_alert("System Malfunction: Reboot Your Device"),
#ET.PERMANENT: NormalPermanentAlert("System Malfunction", "Reboot your Device"),
ET.NO_ENTRY: high_cpu_usage_alert,
},
EventName.accFaulted: { EventName.accFaulted: {
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Cruise Fault: Restart the Car"), ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Cruise Fault: Restart the Car"),
ET.PERMANENT: NormalPermanentAlert("Cruise Fault: Restart the car to engage"), ET.PERMANENT: NormalPermanentAlert("Cruise Fault: Restart the car to engage"),
@ -843,24 +864,6 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
ET.NO_ENTRY: NoEntryAlert("Controls Mismatch"), ET.NO_ENTRY: NoEntryAlert("Controls Mismatch"),
}, },
EventName.roadCameraError: {
ET.PERMANENT: NormalPermanentAlert("Camera CRC Error - Road",
duration=1.,
creation_delay=30.),
},
EventName.wideRoadCameraError: {
ET.PERMANENT: NormalPermanentAlert("Camera CRC Error - Road Fisheye",
duration=1.,
creation_delay=30.),
},
EventName.driverCameraError: {
ET.PERMANENT: NormalPermanentAlert("Camera CRC Error - Driver",
duration=1.,
creation_delay=30.),
},
# Sometimes the USB stack on the device can get into a bad state # Sometimes the USB stack on the device can get into a bad state
# causing the connection to the panda to be lost # causing the connection to the panda to be lost
EventName.usbError: { EventName.usbError: {
@ -943,16 +946,6 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
ET.NO_ENTRY: NoEntryAlert("Slow down to engage"), ET.NO_ENTRY: NoEntryAlert("Slow down to engage"),
}, },
EventName.lowSpeedLockout: {
ET.PERMANENT: NormalPermanentAlert("Cruise Fault: Restart the car to engage"),
ET.NO_ENTRY: NoEntryAlert("Cruise Fault: Restart the Car"),
},
EventName.lkasDisabled: {
ET.PERMANENT: NormalPermanentAlert("LKAS Disabled: Enable LKAS to engage"),
ET.NO_ENTRY: NoEntryAlert("LKAS Disabled"),
},
EventName.vehicleSensorsInvalid: { EventName.vehicleSensorsInvalid: {
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Vehicle Sensors Invalid"), ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Vehicle Sensors Invalid"),
ET.PERMANENT: NormalPermanentAlert("Vehicle Sensors Calibrating", "Drive to Calibrate"), ET.PERMANENT: NormalPermanentAlert("Vehicle Sensors Calibrating", "Drive to Calibrate"),

@ -0,0 +1,502 @@
#!/usr/bin/env python3
import os
import time
import threading
import cereal.messaging as messaging
from cereal import car, log
from msgq.visionipc import VisionIpcClient, VisionStreamType
from panda import ALTERNATIVE_EXPERIENCE
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper, DT_CTRL
from openpilot.common.swaglog import cloudlog
from openpilot.common.gps import get_gps_location_service
from openpilot.selfdrive.car.car_specific import CarSpecificEvents
from openpilot.selfdrive.selfdrived.events import Events, ET
from openpilot.selfdrive.selfdrived.state import StateMachine
from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroad_alert
from openpilot.selfdrive.controls.lib.latcontrol import MIN_LATERAL_CONTROL_SPEED
from openpilot.system.hardware import HARDWARE
from openpilot.system.version import get_build_metadata
REPLAY = "REPLAY" in os.environ
SIMULATION = "SIMULATION" in os.environ
TESTING_CLOSET = "TESTING_CLOSET" in os.environ
IGNORE_PROCESSES = {"loggerd", "encoderd", "statsd"}
LONGITUDINAL_PERSONALITY_MAP = {v: k for k, v in log.LongitudinalPersonality.schema.enumerants.items()}
ThermalStatus = log.DeviceState.ThermalStatus
State = log.SelfdriveState.OpenpilotState
PandaType = log.PandaState.PandaType
LaneChangeState = log.LaneChangeState
LaneChangeDirection = log.LaneChangeDirection
EventName = log.OnroadEvent.EventName
ButtonType = car.CarState.ButtonEvent.Type
SafetyModel = car.CarParams.SafetyModel
IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
class SelfdriveD:
def __init__(self, CP=None):
self.params = Params()
# Ensure the current branch is cached, otherwise the first cycle lags
build_metadata = get_build_metadata()
if CP is None:
cloudlog.info("selfdrived is waiting for CarParams")
self.CP = messaging.log_from_bytes(self.params.get("CarParams", block=True), car.CarParams)
cloudlog.info("selfdrived got CarParams")
else:
self.CP = CP
self.car_events = CarSpecificEvents(self.CP)
self.disengage_on_accelerator = not (self.CP.alternativeExperience & ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS)
# Setup sockets
self.pm = messaging.PubMaster(['selfdriveState', 'onroadEvents'])
self.gps_location_service = get_gps_location_service(self.params)
self.gps_packets = [self.gps_location_service]
self.sensor_packets = ["accelerometer", "gyroscope"]
self.camera_packets = ["roadCameraState", "driverCameraState", "wideRoadCameraState"]
# TODO: de-couple selfdrived with card/conflate on carState without introducing controls mismatches
self.car_state_sock = messaging.sub_sock('carState', timeout=20)
ignore = self.sensor_packets + self.gps_packets + ['alertDebug']
if SIMULATION:
ignore += ['driverCameraState', 'managerState']
if REPLAY:
# no vipc in replay will make them ignored anyways
ignore += ['roadCameraState', 'wideRoadCameraState']
self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration',
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose',
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
'controlsState', 'carControl', 'driverAssistance', 'alertDebug'] + \
self.camera_packets + self.sensor_packets + self.gps_packets,
ignore_alive=ignore, ignore_avg_freq=ignore+['radarState',],
ignore_valid=ignore, frequency=int(1/DT_CTRL))
# read params
self.is_metric = self.params.get_bool("IsMetric")
self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled")
# detect sound card presence and ensure successful init
sounds_available = HARDWARE.get_sound_card_online()
car_recognized = self.CP.carName != 'mock'
# cleanup old params
if not self.CP.experimentalLongitudinalAvailable:
self.params.remove("ExperimentalLongitudinalEnabled")
if not self.CP.openpilotLongitudinalControl:
self.params.remove("ExperimentalMode")
self.CS_prev = car.CarState.new_message()
self.AM = AlertManager()
self.events = Events()
self.initialized = False
self.enabled = False
self.active = False
self.mismatch_counter = 0
self.cruise_mismatch_counter = 0
self.last_steering_pressed_frame = 0
self.distance_traveled = 0
self.last_functional_fan_frame = 0
self.events_prev = []
self.logged_comm_issue = None
self.not_running_prev = None
self.experimental_mode = False
self.personality = self.read_personality_param()
self.recalibrating_seen = False
self.state_machine = StateMachine()
self.rk = Ratekeeper(100, print_delay_threshold=None)
# Determine startup event
self.startup_event = EventName.startup if build_metadata.openpilot.comma_remote and build_metadata.tested_channel else EventName.startupMaster
if not car_recognized:
self.startup_event = EventName.startupNoCar
elif car_recognized and self.CP.passive:
self.startup_event = EventName.startupNoControl
elif self.CP.secOcRequired and not self.CP.secOcKeyAvailable:
self.startup_event = EventName.startupNoSecOcKey
if not sounds_available:
self.events.add(EventName.soundsUnavailable, static=True)
if not car_recognized:
self.events.add(EventName.carUnrecognized, static=True)
set_offroad_alert("Offroad_CarUnrecognized", True)
elif self.CP.passive:
self.events.add(EventName.dashcamMode, static=True)
def update_events(self, CS):
"""Compute onroadEvents from carState"""
self.events.clear()
if self.sm['controlsState'].lateralControlState.which() == 'debugState':
self.events.add(EventName.joystickDebug)
self.startup_event = None
if self.sm.recv_frame['alertDebug'] > 0:
self.events.add(EventName.longitudinalManeuver)
self.startup_event = None
# Add startup event
if self.startup_event is not None:
self.events.add(self.startup_event)
self.startup_event = None
# Don't add any more events if not initialized
if not self.initialized:
self.events.add(EventName.selfdriveInitializing)
return
# no more events while in dashcam mode
if self.CP.passive:
return
# Block resume if cruise never previously enabled
resume_pressed = any(be.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for be in CS.buttonEvents)
if not self.CP.pcmCruise and CS.vCruise > 250 and resume_pressed:
self.events.add(EventName.resumeBlocked)
if not self.CP.notCar:
self.events.add_from_msg(self.sm['driverMonitoringState'].events)
# Add car events, ignore if CAN isn't valid
if CS.canValid:
car_events = self.car_events.update(CS, self.CS_prev, self.sm['carControl']).to_msg()
self.events.add_from_msg(car_events)
if self.CP.notCar:
# wait for everything to init first
if self.sm.frame > int(5. / DT_CTRL) and self.initialized:
# body always wants to enable
self.events.add(EventName.pcmEnable)
# Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0
if (CS.gasPressed and not self.CS_prev.gasPressed and self.disengage_on_accelerator) or \
(CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or \
(CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)):
self.events.add(EventName.pedalPressed)
# Create events for temperature, disk space, and memory
if self.sm['deviceState'].thermalStatus >= ThermalStatus.red:
self.events.add(EventName.overheat)
if self.sm['deviceState'].freeSpacePercent < 7 and not SIMULATION:
self.events.add(EventName.outOfSpace)
if self.sm['deviceState'].memoryUsagePercent > 90 and not SIMULATION:
self.events.add(EventName.lowMemory)
# Alert if fan isn't spinning for 5 seconds
if self.sm['peripheralState'].pandaType != log.PandaState.PandaType.unknown:
if self.sm['peripheralState'].fanSpeedRpm < 500 and self.sm['deviceState'].fanSpeedPercentDesired > 50:
# allow enough time for the fan controller in the panda to recover from stalls
if (self.sm.frame - self.last_functional_fan_frame) * DT_CTRL > 15.0:
self.events.add(EventName.fanMalfunction)
else:
self.last_functional_fan_frame = self.sm.frame
# Handle calibration status
cal_status = self.sm['liveCalibration'].calStatus
if cal_status != log.LiveCalibrationData.Status.calibrated:
if cal_status == log.LiveCalibrationData.Status.uncalibrated:
self.events.add(EventName.calibrationIncomplete)
elif cal_status == log.LiveCalibrationData.Status.recalibrating:
if not self.recalibrating_seen:
set_offroad_alert("Offroad_Recalibration", True)
self.recalibrating_seen = True
self.events.add(EventName.calibrationRecalibrating)
else:
self.events.add(EventName.calibrationInvalid)
# Lane departure warning
if self.is_ldw_enabled and self.sm.valid['driverAssistance']:
if self.sm['driverAssistance'].leftLaneDeparture or self.sm['driverAssistance'].rightLaneDeparture:
self.events.add(EventName.ldw)
# Handle lane change
if self.sm['modelV2'].meta.laneChangeState == LaneChangeState.preLaneChange:
direction = self.sm['modelV2'].meta.laneChangeDirection
if (CS.leftBlindspot and direction == LaneChangeDirection.left) or \
(CS.rightBlindspot and direction == LaneChangeDirection.right):
self.events.add(EventName.laneChangeBlocked)
else:
if direction == LaneChangeDirection.left:
self.events.add(EventName.preLaneChangeLeft)
else:
self.events.add(EventName.preLaneChangeRight)
elif self.sm['modelV2'].meta.laneChangeState in (LaneChangeState.laneChangeStarting,
LaneChangeState.laneChangeFinishing):
self.events.add(EventName.laneChange)
for i, pandaState in enumerate(self.sm['pandaStates']):
# All pandas must match the list of safetyConfigs, and if outside this list, must be silent or noOutput
if i < len(self.CP.safetyConfigs):
safety_mismatch = pandaState.safetyModel != self.CP.safetyConfigs[i].safetyModel or \
pandaState.safetyParam != self.CP.safetyConfigs[i].safetyParam or \
pandaState.alternativeExperience != self.CP.alternativeExperience
else:
safety_mismatch = pandaState.safetyModel not in IGNORED_SAFETY_MODES
# safety mismatch allows some time for pandad to set the safety mode and publish it back from panda
if (safety_mismatch and self.sm.frame*DT_CTRL > 10.) or pandaState.safetyRxChecksInvalid or self.mismatch_counter >= 200:
self.events.add(EventName.controlsMismatch)
if log.PandaState.FaultType.relayMalfunction in pandaState.faults:
self.events.add(EventName.relayMalfunction)
# Handle HW and system malfunctions
# Order is very intentional here. Be careful when modifying this.
# All events here should at least have NO_ENTRY and SOFT_DISABLE.
num_events = len(self.events)
not_running = {p.name for p in self.sm['managerState'].processes if not p.running and p.shouldBeRunning}
if self.sm.recv_frame['managerState'] and (not_running - IGNORE_PROCESSES):
self.events.add(EventName.processNotRunning)
if not_running != self.not_running_prev:
cloudlog.event("process_not_running", not_running=not_running, error=True)
self.not_running_prev = not_running
else:
if not SIMULATION and not self.rk.lagging:
if not self.sm.all_alive(self.camera_packets):
self.events.add(EventName.cameraMalfunction)
elif not self.sm.all_freq_ok(self.camera_packets):
self.events.add(EventName.cameraFrameRate)
if not REPLAY and self.rk.lagging:
self.events.add(EventName.selfdrivedLagging)
if len(self.sm['radarState'].radarErrors) or ((not self.rk.lagging or REPLAY) and not self.sm.all_checks(['radarState'])):
self.events.add(EventName.radarFault)
if not self.sm.valid['pandaStates']:
self.events.add(EventName.usbError)
if CS.canTimeout:
self.events.add(EventName.canBusMissing)
elif not CS.canValid:
self.events.add(EventName.canError)
# generic catch-all. ideally, a more specific event should be added above instead
has_disable_events = self.events.contains(ET.NO_ENTRY) and (self.events.contains(ET.SOFT_DISABLE) or self.events.contains(ET.IMMEDIATE_DISABLE))
no_system_errors = (not has_disable_events) or (len(self.events) == num_events)
if not self.sm.all_checks() and no_system_errors:
if not self.sm.all_alive():
self.events.add(EventName.commIssue)
elif not self.sm.all_freq_ok():
self.events.add(EventName.commIssueAvgFreq)
else:
self.events.add(EventName.commIssue)
logs = {
'invalid': [s for s, valid in self.sm.valid.items() if not valid],
'not_alive': [s for s, alive in self.sm.alive.items() if not alive],
'not_freq_ok': [s for s, freq_ok in self.sm.freq_ok.items() if not freq_ok],
}
if logs != self.logged_comm_issue:
cloudlog.event("commIssue", error=True, **logs)
self.logged_comm_issue = logs
else:
self.logged_comm_issue = None
if not self.CP.notCar:
if not self.sm['livePose'].posenetOK:
self.events.add(EventName.posenetInvalid)
if not self.sm['livePose'].inputsOK:
self.events.add(EventName.locationdTemporaryError)
if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY):
self.events.add(EventName.paramsdTemporaryError)
# conservative HW alert. if the data or frequency are off, locationd will throw an error
if any((self.sm.frame - self.sm.recv_frame[s])*DT_CTRL > 10. for s in self.sensor_packets):
self.events.add(EventName.sensorDataInvalid)
if not REPLAY:
# Check for mismatch between openpilot and car's PCM
cruise_mismatch = CS.cruiseState.enabled and (not self.enabled or not self.CP.pcmCruise)
self.cruise_mismatch_counter = self.cruise_mismatch_counter + 1 if cruise_mismatch else 0
if self.cruise_mismatch_counter > int(6. / DT_CTRL):
self.events.add(EventName.cruiseMismatch)
# Send a "steering required alert" if saturation count has reached the limit
if CS.steeringPressed:
self.last_steering_pressed_frame = self.sm.frame
recent_steer_pressed = (self.sm.frame - self.last_steering_pressed_frame)*DT_CTRL < 2.0
controlstate = self.sm['controlsState']
lac = getattr(controlstate.lateralControlState, controlstate.lateralControlState.which())
if lac.active and not recent_steer_pressed and not self.CP.notCar:
clipped_speed = max(CS.vEgo, MIN_LATERAL_CONTROL_SPEED)
actual_lateral_accel = controlstate.curvature * (clipped_speed**2)
desired_lateral_accel = controlstate.desiredCurvature * (clipped_speed**2)
undershooting = abs(desired_lateral_accel) / abs(1e-3 + actual_lateral_accel) > 1.2
turning = abs(desired_lateral_accel) > 1.0
good_speed = CS.vEgo > 5
if undershooting and turning and good_speed and lac.saturated:
self.events.add(EventName.steerSaturated)
# Check for FCW
stock_long_is_braking = self.enabled and not self.CP.openpilotLongitudinalControl and CS.aEgo < -1.25
model_fcw = self.sm['modelV2'].meta.hardBrakePredicted and not CS.brakePressed and not stock_long_is_braking
planner_fcw = self.sm['longitudinalPlan'].fcw and self.enabled
if (planner_fcw or model_fcw) and not self.CP.notCar:
self.events.add(EventName.fcw)
# TODO: fix simulator
if not SIMULATION or REPLAY:
# Not show in first 1.5 km to allow for driving out of garage. This event shows after 5 minutes
gps_ok = self.sm.recv_frame[self.gps_location_service] > 0 and (self.sm.frame - self.sm.recv_frame[self.gps_location_service]) * DT_CTRL < 2.0
if not gps_ok and self.sm['livePose'].inputsOK and (self.distance_traveled > 1500):
self.events.add(EventName.noGps)
if gps_ok:
self.distance_traveled = 0
self.distance_traveled += abs(CS.vEgo) * DT_CTRL
if self.sm['modelV2'].frameDropPerc > 20:
self.events.add(EventName.modeldLagging)
# decrement personality on distance button press
if self.CP.openpilotLongitudinalControl:
if any(not be.pressed and be.type == ButtonType.gapAdjustCruise for be in CS.buttonEvents):
self.personality = (self.personality - 1) % 3
self.params.put_nonblocking('LongitudinalPersonality', str(self.personality))
self.events.add(EventName.personalityChanged)
def data_sample(self):
car_state = messaging.recv_one(self.car_state_sock)
CS = car_state.carState if car_state else self.CS_prev
self.sm.update(0)
if not self.initialized:
all_valid = CS.canValid and self.sm.all_checks()
timed_out = self.sm.frame * DT_CTRL > 6.
if all_valid or timed_out or (SIMULATION and not REPLAY):
available_streams = VisionIpcClient.available_streams("camerad", block=False)
if VisionStreamType.VISION_STREAM_ROAD not in available_streams:
self.sm.ignore_alive.append('roadCameraState')
self.sm.ignore_valid.append('roadCameraState')
if VisionStreamType.VISION_STREAM_WIDE_ROAD not in available_streams:
self.sm.ignore_alive.append('wideRoadCameraState')
self.sm.ignore_valid.append('wideRoadCameraState')
if REPLAY and any(ps.controlsAllowed for ps in self.sm['pandaStates']):
self.state_machine.state = State.enabled
self.initialized = True
cloudlog.event(
"selfdrived.initialized",
dt=self.sm.frame*DT_CTRL,
timeout=timed_out,
canValid=CS.canValid,
invalid=[s for s, valid in self.sm.valid.items() if not valid],
not_alive=[s for s, alive in self.sm.alive.items() if not alive],
not_freq_ok=[s for s, freq_ok in self.sm.freq_ok.items() if not freq_ok],
error=True,
)
# When the panda and selfdrived do not agree on controls_allowed
# we want to disengage openpilot. However the status from the panda goes through
# another socket other than the CAN messages and one can arrive earlier than the other.
# Therefore we allow a mismatch for two samples, then we trigger the disengagement.
if not self.enabled:
self.mismatch_counter = 0
# All pandas not in silent mode must have controlsAllowed when openpilot is enabled
if self.enabled and any(not ps.controlsAllowed for ps in self.sm['pandaStates']
if ps.safetyModel not in IGNORED_SAFETY_MODES):
self.mismatch_counter += 1
return CS
def update_alerts(self, CS):
clear_event_types = set()
if ET.WARNING not in self.state_machine.current_alert_types:
clear_event_types.add(ET.WARNING)
if self.enabled:
clear_event_types.add(ET.NO_ENTRY)
pers = LONGITUDINAL_PERSONALITY_MAP[self.personality]
alerts = self.events.create_alerts(self.state_machine.current_alert_types, [self.CP, CS, self.sm, self.is_metric,
self.state_machine.soft_disable_timer, pers])
self.AM.add_many(self.sm.frame, alerts)
self.AM.process_alerts(self.sm.frame, clear_event_types)
def publish_selfdriveState(self, CS):
# selfdriveState
ss_msg = messaging.new_message('selfdriveState')
ss_msg.valid = True
ss = ss_msg.selfdriveState
ss.enabled = self.enabled
ss.active = self.active
ss.state = self.state_machine.state
ss.engageable = not self.events.contains(ET.NO_ENTRY)
ss.experimentalMode = self.experimental_mode
ss.personality = self.personality
ss.alertText1 = self.AM.current_alert.alert_text_1
ss.alertText2 = self.AM.current_alert.alert_text_2
ss.alertSize = self.AM.current_alert.alert_size
ss.alertStatus = self.AM.current_alert.alert_status
ss.alertType = self.AM.current_alert.alert_type
ss.alertSound = self.AM.current_alert.audible_alert
self.pm.send('selfdriveState', ss_msg)
# onroadEvents - logged every second or on change
if (self.sm.frame % int(1. / DT_CTRL) == 0) or (self.events.names != self.events_prev):
ce_send = messaging.new_message('onroadEvents', len(self.events))
ce_send.valid = True
ce_send.onroadEvents = self.events.to_msg()
self.pm.send('onroadEvents', ce_send)
self.events_prev = self.events.names.copy()
def step(self):
CS = self.data_sample()
self.update_events(CS)
if not self.CP.passive and self.initialized:
self.enabled, self.active = self.state_machine.update(self.events)
self.update_alerts(CS)
self.publish_selfdriveState(CS)
self.CS_prev = CS
def read_personality_param(self):
try:
return int(self.params.get('LongitudinalPersonality'))
except (ValueError, TypeError):
return log.LongitudinalPersonality.standard
def params_thread(self, evt):
while not evt.is_set():
self.is_metric = self.params.get_bool("IsMetric")
self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl
self.personality = self.read_personality_param()
time.sleep(0.1)
def run(self):
e = threading.Event()
t = threading.Thread(target=self.params_thread, args=(e, ))
try:
t.start()
while True:
self.step()
self.rk.monitor_time()
finally:
e.set()
t.join()
def main():
config_realtime_process(4, Priority.CTRL_HIGH)
s = SelfdriveD()
s.run()
if __name__ == "__main__":
main()

@ -0,0 +1,98 @@
from cereal import log
from openpilot.selfdrive.selfdrived.events import Events, ET
from openpilot.common.realtime import DT_CTRL
State = log.SelfdriveState.OpenpilotState
SOFT_DISABLE_TIME = 3 # seconds
ACTIVE_STATES = (State.enabled, State.softDisabling, State.overriding)
ENABLED_STATES = (State.preEnabled, *ACTIVE_STATES)
class StateMachine:
def __init__(self):
self.current_alert_types = [ET.PERMANENT]
self.state = State.disabled
self.soft_disable_timer = 0
def update(self, events: Events):
# decrement the soft disable timer at every step, as it's reset on
# entrance in SOFT_DISABLING state
self.soft_disable_timer = max(0, self.soft_disable_timer - 1)
self.current_alert_types = [ET.PERMANENT]
# ENABLED, SOFT DISABLING, PRE ENABLING, OVERRIDING
if self.state != State.disabled:
# user and immediate disable always have priority in a non-disabled state
if events.contains(ET.USER_DISABLE):
self.state = State.disabled
self.current_alert_types.append(ET.USER_DISABLE)
elif events.contains(ET.IMMEDIATE_DISABLE):
self.state = State.disabled
self.current_alert_types.append(ET.IMMEDIATE_DISABLE)
else:
# ENABLED
if self.state == State.enabled:
if events.contains(ET.SOFT_DISABLE):
self.state = State.softDisabling
self.soft_disable_timer = int(SOFT_DISABLE_TIME / DT_CTRL)
self.current_alert_types.append(ET.SOFT_DISABLE)
elif events.contains(ET.OVERRIDE_LATERAL) or events.contains(ET.OVERRIDE_LONGITUDINAL):
self.state = State.overriding
self.current_alert_types += [ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL]
# SOFT DISABLING
elif self.state == State.softDisabling:
if not events.contains(ET.SOFT_DISABLE):
# no more soft disabling condition, so go back to ENABLED
self.state = State.enabled
elif self.soft_disable_timer > 0:
self.current_alert_types.append(ET.SOFT_DISABLE)
elif self.soft_disable_timer <= 0:
self.state = State.disabled
# PRE ENABLING
elif self.state == State.preEnabled:
if not events.contains(ET.PRE_ENABLE):
self.state = State.enabled
else:
self.current_alert_types.append(ET.PRE_ENABLE)
# OVERRIDING
elif self.state == State.overriding:
if events.contains(ET.SOFT_DISABLE):
self.state = State.softDisabling
self.soft_disable_timer = int(SOFT_DISABLE_TIME / DT_CTRL)
self.current_alert_types.append(ET.SOFT_DISABLE)
elif not (events.contains(ET.OVERRIDE_LATERAL) or events.contains(ET.OVERRIDE_LONGITUDINAL)):
self.state = State.enabled
else:
self.current_alert_types += [ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL]
# DISABLED
elif self.state == State.disabled:
if events.contains(ET.ENABLE):
if events.contains(ET.NO_ENTRY):
self.current_alert_types.append(ET.NO_ENTRY)
else:
if events.contains(ET.PRE_ENABLE):
self.state = State.preEnabled
elif events.contains(ET.OVERRIDE_LATERAL) or events.contains(ET.OVERRIDE_LONGITUDINAL):
self.state = State.overriding
else:
self.state = State.enabled
self.current_alert_types.append(ET.ENABLE)
# Check if openpilot is engaged and actuators are enabled
enabled = self.state in ENABLED_STATES
active = self.state in ACTIVE_STATES
if active:
self.current_alert_types.append(ET.WARNING)
return enabled, active

@ -1,7 +1,7 @@
import random import random
from openpilot.selfdrive.controls.lib.events import Alert, EVENTS from openpilot.selfdrive.selfdrived.events import Alert, EmptyAlert, EVENTS
from openpilot.selfdrive.controls.lib.alertmanager import AlertManager from openpilot.selfdrive.selfdrived.alertmanager import AlertManager
class TestAlertManager: class TestAlertManager:
@ -32,8 +32,8 @@ class TestAlertManager:
for frame in range(duration+10): for frame in range(duration+10):
if frame < add_duration: if frame < add_duration:
AM.add_many(frame, [alert, ]) AM.add_many(frame, [alert, ])
current_alert = AM.process_alerts(frame, {}) AM.process_alerts(frame, {})
shown = current_alert is not None shown = AM.current_alert != EmptyAlert
should_show = frame <= show_duration should_show = frame <= show_duration
assert shown == should_show, f"{frame=} {add_duration=} {duration=}" assert shown == should_show, f"{frame=} {add_duration=} {duration=}"

@ -8,13 +8,13 @@ from cereal import log, car
from cereal.messaging import SubMaster from cereal.messaging import SubMaster
from openpilot.common.basedir import BASEDIR from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.selfdrive.controls.lib.events import Alert, EVENTS, ET from openpilot.selfdrive.selfdrived.events import Alert, EVENTS, ET
from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS
AlertSize = log.SelfdriveState.AlertSize AlertSize = log.SelfdriveState.AlertSize
OFFROAD_ALERTS_PATH = os.path.join(BASEDIR, "selfdrive/controls/lib/alerts_offroad.json") OFFROAD_ALERTS_PATH = os.path.join(BASEDIR, "selfdrive/selfdrived/alerts_offroad.json")
# TODO: add callback alerts # TODO: add callback alerts
ALERTS = [] ALERTS = []
@ -33,12 +33,12 @@ class TestAlerts:
# Create fake objects for callback # Create fake objects for callback
cls.CS = car.CarState.new_message() cls.CS = car.CarState.new_message()
cls.CP = car.CarParams.new_message() cls.CP = car.CarParams.new_message()
cfg = [c for c in CONFIGS if c.proc_name == 'controlsd'][0] cfg = [c for c in CONFIGS if c.proc_name == 'selfdrived'][0]
cls.sm = SubMaster(cfg.pubs) cls.sm = SubMaster(cfg.pubs)
def test_events_defined(self): def test_events_defined(self):
# Ensure all events in capnp schema are defined in events.py # Ensure all events in capnp schema are defined in events.py
events = car.OnroadEvent.EventName.schema.enumerants events = log.OnroadEvent.EventName.schema.enumerants
for name, e in events.items(): for name, e in events.items():
if not name.endswith("DEPRECATED"): if not name.endswith("DEPRECATED"):

@ -0,0 +1,92 @@
from cereal import log
from openpilot.common.realtime import DT_CTRL
from openpilot.selfdrive.selfdrived.state import StateMachine, SOFT_DISABLE_TIME
from openpilot.selfdrive.selfdrived.events import Events, ET, EVENTS, NormalPermanentAlert
State = log.SelfdriveState.OpenpilotState
# The event types that maintain the current state
MAINTAIN_STATES = {State.enabled: (None,), State.disabled: (None,), State.softDisabling: (ET.SOFT_DISABLE,),
State.preEnabled: (ET.PRE_ENABLE,), State.overriding: (ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL)}
ALL_STATES = tuple(State.schema.enumerants.values())
# The event types checked in DISABLED section of state machine
ENABLE_EVENT_TYPES = (ET.ENABLE, ET.PRE_ENABLE, ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL)
def make_event(event_types):
event = {}
for ev in event_types:
event[ev] = NormalPermanentAlert("alert")
EVENTS[0] = event
return 0
class TestStateMachine:
def setup_method(self):
self.events = Events()
self.state_machine = StateMachine()
self.state_machine.soft_disable_timer = int(SOFT_DISABLE_TIME / DT_CTRL)
def test_immediate_disable(self):
for state in ALL_STATES:
for et in MAINTAIN_STATES[state]:
self.events.add(make_event([et, ET.IMMEDIATE_DISABLE]))
self.state_machine.state = state
self.state_machine.update(self.events)
assert State.disabled == self.state_machine.state
self.events.clear()
def test_user_disable(self):
for state in ALL_STATES:
for et in MAINTAIN_STATES[state]:
self.events.add(make_event([et, ET.USER_DISABLE]))
self.state_machine.state = state
self.state_machine.update(self.events)
assert State.disabled == self.state_machine.state
self.events.clear()
def test_soft_disable(self):
for state in ALL_STATES:
if state == State.preEnabled: # preEnabled considers NO_ENTRY instead
continue
for et in MAINTAIN_STATES[state]:
self.events.add(make_event([et, ET.SOFT_DISABLE]))
self.state_machine.state = state
self.state_machine.update(self.events)
assert self.state_machine.state == State.disabled if state == State.disabled else State.softDisabling
self.events.clear()
def test_soft_disable_timer(self):
self.state_machine.state = State.enabled
self.events.add(make_event([ET.SOFT_DISABLE]))
self.state_machine.update(self.events)
for _ in range(int(SOFT_DISABLE_TIME / DT_CTRL)):
assert self.state_machine.state == State.softDisabling
self.state_machine.update(self.events)
assert self.state_machine.state == State.disabled
def test_no_entry(self):
# Make sure noEntry keeps us disabled
for et in ENABLE_EVENT_TYPES:
self.events.add(make_event([ET.NO_ENTRY, et]))
self.state_machine.update(self.events)
assert self.state_machine.state == State.disabled
self.events.clear()
def test_no_entry_pre_enable(self):
# preEnabled with noEntry event
self.state_machine.state = State.preEnabled
self.events.add(make_event([ET.NO_ENTRY, ET.PRE_ENABLE]))
self.state_machine.update(self.events)
assert self.state_machine.state == State.preEnabled
def test_maintain_states(self):
# Given current state's event type, we should maintain state
for state in ALL_STATES:
for et in MAINTAIN_STATES[state]:
self.state_machine.state = state
self.events.add(make_event([et]))
self.state_machine.update(self.events)
assert self.state_machine.state == state
self.events.clear()

@ -4,8 +4,7 @@ import sys
from openpilot.common.prefix import OpenpilotPrefix from openpilot.common.prefix import OpenpilotPrefix
with OpenpilotPrefix(): with OpenpilotPrefix():
ret = subprocess.call(sys.argv[1:]) ret = subprocess.call(sys.argv[1:])
exit(ret) sys.exit(ret)

@ -12,11 +12,14 @@ class Maneuver:
self.breakpoints = kwargs.get("breakpoints", [0.0, duration]) self.breakpoints = kwargs.get("breakpoints", [0.0, duration])
self.speed_lead_values = kwargs.get("speed_lead_values", [0.0 for i in range(len(self.breakpoints))]) self.speed_lead_values = kwargs.get("speed_lead_values", [0.0 for i in range(len(self.breakpoints))])
self.prob_lead_values = kwargs.get("prob_lead_values", [1.0 for i in range(len(self.breakpoints))]) self.prob_lead_values = kwargs.get("prob_lead_values", [1.0 for i in range(len(self.breakpoints))])
self.prob_throttle_values = kwargs.get("prob_throttle_values", [1.0 for i in range(len(self.breakpoints))])
self.cruise_values = kwargs.get("cruise_values", [50.0 for i in range(len(self.breakpoints))]) self.cruise_values = kwargs.get("cruise_values", [50.0 for i in range(len(self.breakpoints))])
self.pitch_values = kwargs.get("pitch_values", [0.0 for i in range(len(self.breakpoints))])
self.only_lead2 = kwargs.get("only_lead2", False) self.only_lead2 = kwargs.get("only_lead2", False)
self.only_radar = kwargs.get("only_radar", False) self.only_radar = kwargs.get("only_radar", False)
self.ensure_start = kwargs.get("ensure_start", False) self.ensure_start = kwargs.get("ensure_start", False)
self.ensure_slowdown = kwargs.get("ensure_slowdown", False)
self.enabled = kwargs.get("enabled", True) self.enabled = kwargs.get("enabled", True)
self.e2e = kwargs.get("e2e", False) self.e2e = kwargs.get("e2e", False)
self.personality = kwargs.get("personality", 0) self.personality = kwargs.get("personality", 0)
@ -42,9 +45,11 @@ class Maneuver:
logs = [] logs = []
while plant.current_time < self.duration: while plant.current_time < self.duration:
speed_lead = np.interp(plant.current_time, self.breakpoints, self.speed_lead_values) speed_lead = np.interp(plant.current_time, self.breakpoints, self.speed_lead_values)
prob = np.interp(plant.current_time, self.breakpoints, self.prob_lead_values) prob_lead = np.interp(plant.current_time, self.breakpoints, self.prob_lead_values)
cruise = np.interp(plant.current_time, self.breakpoints, self.cruise_values) cruise = np.interp(plant.current_time, self.breakpoints, self.cruise_values)
log = plant.step(speed_lead, prob, cruise) pitch = np.interp(plant.current_time, self.breakpoints, self.pitch_values)
prob_throttle = np.interp(plant.current_time, self.breakpoints, self.prob_throttle_values)
log = plant.step(speed_lead, prob_lead, cruise, pitch, prob_throttle)
d_rel = log['distance_lead'] - log['distance'] if self.lead_relevancy else 200. d_rel = log['distance_lead'] - log['distance'] if self.lead_relevancy else 200.
v_rel = speed_lead - log['speed'] if self.lead_relevancy else 0. v_rel = speed_lead - log['speed'] if self.lead_relevancy else 0.
@ -57,13 +62,18 @@ class Maneuver:
speed_lead, speed_lead,
log['acceleration']])) log['acceleration']]))
if d_rel < .4 and (self.only_radar or prob > 0.5): if d_rel < .4 and (self.only_radar or prob_lead > 0.5):
print("Crashed!!!!") print("Crashed!!!!")
valid = False valid = False
if self.ensure_start and log['v_rel'] > 0 and log['speeds'][-1] <= 0.1: if self.ensure_start and log['v_rel'] > 0 and log['speeds'][-1] <= 0.1:
print('LongitudinalPlanner not starting!') print('LongitudinalPlanner not starting!')
valid = False valid = False
if self.ensure_slowdown and log['speed'] > 5.5:
print('LongitudinalPlanner not slowing down!')
valid = False
if self.force_decel and log['speed'] > 1e-1 and log['acceleration'] > -0.04: if self.force_decel and log['speed'] > 1e-1 and log['acceleration'] > -0.04:
print('Not stopping with force decel') print('Not stopping with force decel')
valid = False valid = False

@ -57,13 +57,14 @@ class Plant:
def current_time(self): def current_time(self):
return float(self.rk.frame) / self.rate return float(self.rk.frame) / self.rate
def step(self, v_lead=0.0, prob=1.0, v_cruise=50.): def step(self, v_lead=0.0, prob_lead=1.0, v_cruise=50., pitch=0.0, prob_throttle=1.0):
# ******** publish a fake model going straight and fake calibration ******** # ******** publish a fake model going straight and fake calibration ********
# note that this is worst case for MPC, since model will delay long mpc by one time step # note that this is worst case for MPC, since model will delay long mpc by one time step
radar = messaging.new_message('radarState') radar = messaging.new_message('radarState')
control = messaging.new_message('controlsState') control = messaging.new_message('controlsState')
ss = messaging.new_message('selfdriveState') ss = messaging.new_message('selfdriveState')
car_state = messaging.new_message('carState') car_state = messaging.new_message('carState')
car_control = messaging.new_message('carControl')
model = messaging.new_message('modelV2') model = messaging.new_message('modelV2')
a_lead = (v_lead - self.v_lead_prev)/self.ts a_lead = (v_lead - self.v_lead_prev)/self.ts
self.v_lead_prev = v_lead self.v_lead_prev = v_lead
@ -73,14 +74,14 @@ class Plant:
v_rel = v_lead - self.speed v_rel = v_lead - self.speed
if self.only_radar: if self.only_radar:
status = True status = True
elif prob > .5: elif prob_lead > .5:
status = True status = True
else: else:
status = False status = False
else: else:
d_rel = 200. d_rel = 200.
v_rel = 0. v_rel = 0.
prob = 0.0 prob_lead = 0.0
status = False status = False
lead = log.RadarState.LeadData.new_message() lead = log.RadarState.LeadData.new_message()
@ -94,7 +95,7 @@ class Plant:
# TODO use real radard logic for this # TODO use real radard logic for this
lead.aLeadTau = float(_LEAD_ACCEL_TAU) lead.aLeadTau = float(_LEAD_ACCEL_TAU)
lead.status = status lead.status = status
lead.modelProb = float(prob) lead.modelProb = float(prob_lead)
if not self.only_lead2: if not self.only_lead2:
radar.radarState.leadOne = lead radar.radarState.leadOne = lead
radar.radarState.leadTwo = lead radar.radarState.leadTwo = lead
@ -107,10 +108,12 @@ class Plant:
model.modelV2.position = position model.modelV2.position = position
velocity = log.XYZTData.new_message() velocity = log.XYZTData.new_message()
velocity.x = [float(x) for x in (self.speed + 0.5) * np.ones_like(ModelConstants.T_IDXS)] velocity.x = [float(x) for x in (self.speed + 0.5) * np.ones_like(ModelConstants.T_IDXS)]
velocity.x[0] = float(self.speed) # always start at current speed
model.modelV2.velocity = velocity model.modelV2.velocity = velocity
acceleration = log.XYZTData.new_message() acceleration = log.XYZTData.new_message()
acceleration.x = [float(x) for x in np.zeros_like(ModelConstants.T_IDXS)] acceleration.x = [float(x) for x in np.zeros_like(ModelConstants.T_IDXS)]
model.modelV2.acceleration = acceleration model.modelV2.acceleration = acceleration
model.modelV2.meta.disengagePredictions.gasPressProbs = [float(prob_throttle) for _ in range(6)]
control.controlsState.longControlState = LongCtrlState.pid if self.enabled else LongCtrlState.off control.controlsState.longControlState = LongCtrlState.pid if self.enabled else LongCtrlState.off
ss.selfdriveState.experimentalMode = self.e2e ss.selfdriveState.experimentalMode = self.e2e
@ -119,10 +122,12 @@ class Plant:
car_state.carState.vEgo = float(self.speed) car_state.carState.vEgo = float(self.speed)
car_state.carState.standstill = self.speed < 0.01 car_state.carState.standstill = self.speed < 0.01
car_state.carState.vCruise = float(v_cruise * 3.6) car_state.carState.vCruise = float(v_cruise * 3.6)
car_control.carControl.orientationNED = [0., float(pitch), 0.]
# ******** get controlsState messages for plotting *** # ******** get controlsState messages for plotting ***
sm = {'radarState': radar.radarState, sm = {'radarState': radar.radarState,
'carState': car_state.carState, 'carState': car_state.carState,
'carControl': car_control.carControl,
'controlsState': control.controlsState, 'controlsState': control.controlsState,
'selfdriveState': ss.selfdriveState, 'selfdriveState': ss.selfdriveState,
'modelV2': model.modelV2} 'modelV2': model.modelV2}

@ -82,6 +82,44 @@ def create_maneuvers(kwargs):
breakpoints=[0.0, 2., 2.01], breakpoints=[0.0, 2., 2.01],
**kwargs, **kwargs,
), ),
Maneuver(
"approach stopped car at 20m/s, with prob_throttle_values and pitch = -0.1",
duration=30.,
initial_speed=20.,
lead_relevancy=True,
initial_distance_lead=120.,
speed_lead_values=[0.0, 0., 0.],
prob_throttle_values=[1., 0., 0.],
cruise_values=[20., 20., 20.],
pitch_values=[0., -0.1, -0.1],
breakpoints=[0.0, 2., 2.01],
**kwargs,
),
Maneuver(
"approach stopped car at 20m/s, with prob_throttle_values and pitch = +0.1",
duration=30.,
initial_speed=20.,
lead_relevancy=True,
initial_distance_lead=120.,
speed_lead_values=[0.0, 0., 0.],
prob_throttle_values=[1., 0., 0.],
cruise_values=[20., 20., 20.],
pitch_values=[0., 0.1, 0.1],
breakpoints=[0.0, 2., 2.01],
**kwargs,
),
Maneuver(
"slow to 5m/s with allow_throttle = False and pitch = +0.1",
duration=25.,
initial_speed=20.,
lead_relevancy=False,
prob_throttle_values=[1., 0., 0.],
cruise_values=[20., 20., 20.],
pitch_values=[0., 0.1, 0.1],
breakpoints=[0.0, 2., 2.01],
ensure_slowdown=True,
**kwargs,
),
Maneuver( Maneuver(
"approach slower cut-in car at 20m/s", "approach slower cut-in car at 20m/s",
duration=20., duration=20.,

@ -30,7 +30,7 @@ optional arguments:
--whitelist-cars WHITELIST_CARS Whitelist given cars from the test (e.g. HONDA) --whitelist-cars WHITELIST_CARS Whitelist given cars from the test (e.g. HONDA)
--blacklist-procs BLACKLIST_PROCS Blacklist given processes from the test (e.g. controlsd) --blacklist-procs BLACKLIST_PROCS Blacklist given processes from the test (e.g. controlsd)
--blacklist-cars BLACKLIST_CARS Blacklist given cars from the test (e.g. HONDA) --blacklist-cars BLACKLIST_CARS Blacklist given cars from the test (e.g. HONDA)
--ignore-fields IGNORE_FIELDS Extra fields or msgs to ignore (e.g. carState.events) --ignore-fields IGNORE_FIELDS Extra fields or msgs to ignore (e.g. driverMonitoringState.events)
--ignore-msgs IGNORE_MSGS Msgs to ignore (e.g. onroadEvents) --ignore-msgs IGNORE_MSGS Msgs to ignore (e.g. onroadEvents)
--update-refs Updates reference logs using current commit --update-refs Updates reference logs using current commit
--upload-only Skips testing processes and uploads logs from previous test run --upload-only Skips testing processes and uploads logs from previous test run

@ -141,7 +141,7 @@ def format_diff(results, log_paths, ref_commit):
if __name__ == "__main__": if __name__ == "__main__":
log1 = list(LogReader(sys.argv[1])) log1 = list(LogReader(sys.argv[1]))
log2 = list(LogReader(sys.argv[2])) log2 = list(LogReader(sys.argv[2]))
ignore_fields = sys.argv[3:] or ["logMonoTime", "controlsState.cumLagMs"] ignore_fields = sys.argv[3:] or ["logMonoTime"]
results = {"segment": {"proc": compare_logs(log1, log2, ignore_fields)}} results = {"segment": {"proc": compare_logs(log1, log2, ignore_fields)}}
log_paths = {"segment": {"proc": {"ref": sys.argv[1], "new": sys.argv[2]}}} log_paths = {"segment": {"proc": {"ref": sys.argv[1], "new": sys.argv[2]}}}
diff_short, diff_long, failed = format_diff(results, log_paths, None) diff_short, diff_long, failed = format_diff(results, log_paths, None)

@ -1,67 +1,170 @@
from collections import defaultdict from collections import defaultdict
from collections.abc import Callable
import functools
import capnp
from cereal import messaging from cereal import messaging, car, log
from opendbc.car.fingerprints import MIGRATION from opendbc.car.fingerprints import MIGRATION
from opendbc.car.toyota.values import EPS_SCALE from opendbc.car.toyota.values import EPS_SCALE
from openpilot.selfdrive.modeld.constants import ModelConstants
from openpilot.selfdrive.modeld.fill_model_msg import fill_xyz_poly, fill_lane_line_meta
from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_encode_index from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_encode_index
from openpilot.system.manager.process_config import managed_processes from openpilot.system.manager.process_config import managed_processes
from openpilot.tools.lib.logreader import LogIterable
from panda import Panda from panda import Panda
MessageWithIndex = tuple[int, capnp.lib.capnp._DynamicStructReader]
# TODO: message migration should happen in-place MigrationOps = tuple[list[tuple[int, capnp.lib.capnp._DynamicStructReader]], list[capnp.lib.capnp._DynamicStructReader], list[int]]
def migrate_all(lr, manager_states=False, panda_states=False, camera_states=False): MigrationFunc = Callable[[list[MessageWithIndex]], MigrationOps]
msgs = migrate_sensorEvents(lr)
msgs = migrate_carParams(msgs)
msgs = migrate_gpsLocation(msgs) ## rules for migration functions
msgs = migrate_deviceState(msgs) ## 1. must use the decorator @migration(inputs=[...], product="...") and MigrationFunc signature
msgs = migrate_carOutput(msgs) ## 2. it only gets the messages that are in the inputs list
msgs = migrate_controlsState(msgs) ## 3. product is the message type created by the migration function, and the function will be skipped if product type already exists in lr
msgs = migrate_liveLocationKalman(msgs) ## 4. it must return a list of operations to be applied to the logreader (replace, add, delete)
## 5. all migration functions must be independent of each other
def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: bool = False, camera_states: bool = False):
migrations = [
migrate_sensorEvents,
migrate_carParams,
migrate_gpsLocation,
migrate_deviceState,
migrate_carOutput,
migrate_controlsState,
migrate_carState,
migrate_liveLocationKalman,
migrate_liveTracks,
migrate_driverAssistance,
migrate_drivingModelData,
migrate_onroadEvents,
migrate_driverMonitoringState,
]
if manager_states: if manager_states:
msgs = migrate_managerState(msgs) migrations.append(migrate_managerState)
if panda_states: if panda_states:
msgs = migrate_pandaStates(msgs) migrations.extend([migrate_pandaStates, migrate_peripheralState])
msgs = migrate_peripheralState(msgs)
if camera_states: if camera_states:
msgs = migrate_cameraStates(msgs) migrations.append(migrate_cameraStates)
return msgs return migrate(lr, migrations)
def migrate_liveLocationKalman(lr): def migrate(lr: LogIterable, migration_funcs: list[MigrationFunc]):
# migration needed only for routes before livePose lr = list(lr)
if any(msg.which() == 'livePose' for msg in lr): grouped = defaultdict(list)
return lr for i, msg in enumerate(lr):
grouped[msg.which()].append(i)
all_msgs = [] replace_ops, add_ops, del_ops = [], [], []
for msg in lr: for migration in migration_funcs:
if msg.which() != 'liveLocationKalmanDEPRECATED': assert hasattr(migration, "inputs") and hasattr(migration, "product"), "Migration functions must use @migration decorator"
all_msgs.append(msg) if migration.product in grouped: # skip if product already exists
continue continue
sorted_indices = sorted(ii for i in migration.inputs for ii in grouped[i])
msg_gen = [(i, lr[i]) for i in sorted_indices]
r_ops, a_ops, d_ops = migration(msg_gen)
replace_ops.extend(r_ops)
add_ops.extend(a_ops)
del_ops.extend(d_ops)
for index, msg in replace_ops:
lr[index] = msg
for index in sorted(del_ops, reverse=True):
del lr[index]
for msg in add_ops:
lr.append(msg)
lr = sorted(lr, key=lambda x: x.logMonoTime)
return lr
def migration(inputs: list[str], product: str|None=None):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper.inputs = inputs
wrapper.product = product
return wrapper
return decorator
@migration(inputs=["longitudinalPlan"], product="driverAssistance")
def migrate_driverAssistance(msgs):
add_ops = []
for _, msg in msgs:
new_msg = messaging.new_message('driverAssistance', valid=True, logMonoTime=msg.logMonoTime)
add_ops.append(new_msg.as_reader())
return [], add_ops, []
@migration(inputs=["modelV2"], product="drivingModelData")
def migrate_drivingModelData(msgs):
add_ops = []
for _, msg in msgs:
dmd = messaging.new_message('drivingModelData', valid=msg.valid, logMonoTime=msg.logMonoTime)
for field in ["frameId", "frameIdExtra", "frameDropPerc", "modelExecutionTime", "action"]:
setattr(dmd.drivingModelData, field, getattr(msg.modelV2, field))
for meta_field in ["laneChangeState", "laneChangeState"]:
setattr(dmd.drivingModelData.meta, meta_field, getattr(msg.modelV2.meta, meta_field))
if len(msg.modelV2.laneLines) and len(msg.modelV2.laneLineProbs):
fill_lane_line_meta(dmd.drivingModelData.laneLineMeta, msg.modelV2.laneLines, msg.modelV2.laneLineProbs)
if all(len(a) for a in [msg.modelV2.position.x, msg.modelV2.position.y, msg.modelV2.position.z]):
fill_xyz_poly(dmd.drivingModelData.path, ModelConstants.POLY_PATH_DEGREE, msg.modelV2.position.x, msg.modelV2.position.y, msg.modelV2.position.z)
add_ops.append( dmd.as_reader())
return [], add_ops, []
@migration(inputs=["liveTracksDEPRECATED"], product="liveTracks")
def migrate_liveTracks(msgs):
ops = []
for index, msg in msgs:
new_msg = messaging.new_message('liveTracks')
new_msg.valid = msg.valid
new_msg.logMonoTime = msg.logMonoTime
pts = []
for track in msg.liveTracksDEPRECATED:
pt = car.RadarData.RadarPoint()
pt.trackId = track.trackId
pt.dRel = track.dRel
pt.yRel = track.yRel
pt.vRel = track.vRel
pt.aRel = track.aRel
pt.measured = True
pts.append(pt)
new_msg.liveTracks.points = pts
ops.append((index, new_msg.as_reader()))
return ops, [], []
@migration(inputs=["liveLocationKalmanDEPRECATED"], product="livePose")
def migrate_liveLocationKalman(msgs):
nans = [float('nan')] * 3
ops = []
for index, msg in msgs:
m = messaging.new_message('livePose') m = messaging.new_message('livePose')
m.valid = msg.valid m.valid = msg.valid
m.logMonoTime = msg.logMonoTime m.logMonoTime = msg.logMonoTime
for field in ["orientationNED", "velocityDevice", "accelerationDevice", "angularVelocityDevice"]: for field in ["orientationNED", "velocityDevice", "accelerationDevice", "angularVelocityDevice"]:
lp_field, llk_field = getattr(m.livePose, field), getattr(msg.liveLocationKalmanDEPRECATED, field) lp_field, llk_field = getattr(m.livePose, field), getattr(msg.liveLocationKalmanDEPRECATED, field)
lp_field.x, lp_field.y, lp_field.z = llk_field.value lp_field.x, lp_field.y, lp_field.z = llk_field.value or nans
lp_field.xStd, lp_field.yStd, lp_field.zStd = llk_field.std lp_field.xStd, lp_field.yStd, lp_field.zStd = llk_field.std or nans
lp_field.valid = llk_field.valid lp_field.valid = llk_field.valid
for flag in ["inputsOK", "posenetOK", "sensorsOK"]: for flag in ["inputsOK", "posenetOK", "sensorsOK"]:
setattr(m.livePose, flag, getattr(msg.liveLocationKalmanDEPRECATED, flag)) setattr(m.livePose, flag, getattr(msg.liveLocationKalmanDEPRECATED, flag))
ops.append((index, m.as_reader()))
return ops, [], []
all_msgs.append(m.as_reader())
return all_msgs
def migrate_controlsState(lr):
ret = []
last_cs = None
for msg in lr:
if msg.which() == 'controlsState':
last_cs = msg
@migration(inputs=["controlsState"], product="selfdriveState")
def migrate_controlsState(msgs):
add_ops = []
for _, msg in msgs:
m = messaging.new_message('selfdriveState') m = messaging.new_message('selfdriveState')
m.valid = msg.valid m.valid = msg.valid
m.logMonoTime = msg.logMonoTime m.logMonoTime = msg.logMonoTime
@ -70,81 +173,77 @@ def migrate_controlsState(lr):
"alertStatus", "alertSize", "alertType", "experimentalMode", "alertStatus", "alertSize", "alertType", "experimentalMode",
"personality"): "personality"):
setattr(ss, field, getattr(msg.controlsState, field+"DEPRECATED")) setattr(ss, field, getattr(msg.controlsState, field+"DEPRECATED"))
ret.append(m.as_reader()) add_ops.append(m.as_reader())
return [], add_ops, []
@migration(inputs=["carState", "controlsState"])
def migrate_carState(msgs):
ops = []
last_cs = None
for index, msg in msgs:
if msg.which() == 'controlsState':
last_cs = msg
elif msg.which() == 'carState' and last_cs is not None: elif msg.which() == 'carState' and last_cs is not None:
if last_cs.controlsState.vCruiseDEPRECATED - msg.carState.vCruise > 0.1: if last_cs.controlsState.vCruiseDEPRECATED - msg.carState.vCruise > 0.1:
msg = msg.as_builder() msg = msg.as_builder()
msg.carState.vCruise = last_cs.controlsState.vCruiseDEPRECATED msg.carState.vCruise = last_cs.controlsState.vCruiseDEPRECATED
msg.carState.vCruiseCluster = last_cs.controlsState.vCruiseClusterDEPRECATED msg.carState.vCruiseCluster = last_cs.controlsState.vCruiseClusterDEPRECATED
msg = msg.as_reader() ops.append((index, msg.as_reader()))
return ops, [], []
ret.append(msg)
return ret
def migrate_managerState(lr): @migration(inputs=["managerState"])
all_msgs = [] def migrate_managerState(msgs):
for msg in lr: ops = []
if msg.which() != "managerState": for index, msg in msgs:
all_msgs.append(msg)
continue
new_msg = msg.as_builder() new_msg = msg.as_builder()
new_msg.managerState.processes = [{'name': name, 'running': True} for name in managed_processes] new_msg.managerState.processes = [{'name': name, 'running': True} for name in managed_processes]
all_msgs.append(new_msg.as_reader()) ops.append((index, new_msg.as_reader()))
return ops, [], []
return all_msgs
@migration(inputs=["gpsLocation", "gpsLocationExternal"])
def migrate_gpsLocation(lr): def migrate_gpsLocation(msgs):
all_msgs = [] ops = []
for msg in lr: for index, msg in msgs:
if msg.which() in ('gpsLocation', 'gpsLocationExternal'):
new_msg = msg.as_builder() new_msg = msg.as_builder()
g = getattr(new_msg, new_msg.which()) g = getattr(new_msg, new_msg.which())
# hasFix is a newer field # hasFix is a newer field
if not g.hasFix and g.flags == 1: if not g.hasFix and g.flags == 1:
g.hasFix = True g.hasFix = True
all_msgs.append(new_msg.as_reader()) ops.append((index, new_msg.as_reader()))
else: return ops, [], []
all_msgs.append(msg)
return all_msgs
def migrate_deviceState(lr): @migration(inputs=["deviceState", "initData"])
all_msgs = [] def migrate_deviceState(msgs):
ops = []
dt = None dt = None
for msg in lr: for i, msg in msgs:
if msg.which() == 'initData': if msg.which() == 'initData':
dt = msg.initData.deviceType dt = msg.initData.deviceType
if msg.which() == 'deviceState': if msg.which() == 'deviceState':
n = msg.as_builder() n = msg.as_builder()
n.deviceState.deviceType = dt n.deviceState.deviceType = dt
all_msgs.append(n.as_reader()) ops.append((i, n.as_reader()))
else: return ops, [], []
all_msgs.append(msg)
return all_msgs
def migrate_carOutput(lr):
# migration needed only for routes before carOutput
if any(msg.which() == 'carOutput' for msg in lr):
return lr
all_msgs = [] @migration(inputs=["carControl"], product="carOutput")
for msg in lr: def migrate_carOutput(msgs):
if msg.which() == 'carControl': add_ops = []
for _, msg in msgs:
co = messaging.new_message('carOutput') co = messaging.new_message('carOutput')
co.valid = msg.valid co.valid = msg.valid
co.logMonoTime = msg.logMonoTime co.logMonoTime = msg.logMonoTime
co.carOutput.actuatorsOutput = msg.carControl.actuatorsOutputDEPRECATED co.carOutput.actuatorsOutput = msg.carControl.actuatorsOutputDEPRECATED
all_msgs.append(co.as_reader()) add_ops.append(co.as_reader())
all_msgs.append(msg) return [], add_ops, []
return all_msgs
def migrate_pandaStates(lr): @migration(inputs=["pandaStates", "pandaStateDEPRECATED", "carParams"])
all_msgs = [] def migrate_pandaStates(msgs):
# TODO: safety param migration should be handled automatically # TODO: safety param migration should be handled automatically
safety_param_migration = { safety_param_migration = {
"TOYOTA_PRIUS": EPS_SCALE["TOYOTA_PRIUS"] | Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL, "TOYOTA_PRIUS": EPS_SCALE["TOYOTA_PRIUS"] | Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL,
@ -152,11 +251,12 @@ def migrate_pandaStates(lr):
"KIA_EV6": Panda.FLAG_HYUNDAI_EV_GAS | Panda.FLAG_HYUNDAI_CANFD_HDA2, "KIA_EV6": Panda.FLAG_HYUNDAI_EV_GAS | Panda.FLAG_HYUNDAI_CANFD_HDA2,
} }
# Migrate safety param base on carState # Migrate safety param base on carParams
CP = next((m.carParams for m in lr if m.which() == 'carParams'), None) CP = next((m.carParams for _, m in msgs if m.which() == 'carParams'), None)
assert CP is not None, "carParams message not found" assert CP is not None, "carParams message not found"
if CP.carFingerprint in safety_param_migration: fingerprint = MIGRATION.get(CP.carFingerprint, CP.carFingerprint)
safety_param = safety_param_migration[CP.carFingerprint] if fingerprint in safety_param_migration:
safety_param = safety_param_migration[fingerprint]
elif len(CP.safetyConfigs): elif len(CP.safetyConfigs):
safety_param = CP.safetyConfigs[0].safetyParam safety_param = CP.safetyConfigs[0].safetyParam
if CP.safetyConfigs[0].safetyParamDEPRECATED != 0: if CP.safetyConfigs[0].safetyParamDEPRECATED != 0:
@ -164,49 +264,45 @@ def migrate_pandaStates(lr):
else: else:
safety_param = CP.safetyParamDEPRECATED safety_param = CP.safetyParamDEPRECATED
for msg in lr: ops = []
for index, msg in msgs:
if msg.which() == 'pandaStateDEPRECATED': if msg.which() == 'pandaStateDEPRECATED':
new_msg = messaging.new_message('pandaStates', 1) new_msg = messaging.new_message('pandaStates', 1)
new_msg.valid = msg.valid new_msg.valid = msg.valid
new_msg.logMonoTime = msg.logMonoTime new_msg.logMonoTime = msg.logMonoTime
new_msg.pandaStates[0] = msg.pandaStateDEPRECATED new_msg.pandaStates[0] = msg.pandaStateDEPRECATED
new_msg.pandaStates[0].safetyParam = safety_param new_msg.pandaStates[0].safetyParam = safety_param
all_msgs.append(new_msg.as_reader()) ops.append((index, new_msg.as_reader()))
elif msg.which() == 'pandaStates': elif msg.which() == 'pandaStates':
new_msg = msg.as_builder() new_msg = msg.as_builder()
new_msg.pandaStates[-1].safetyParam = safety_param new_msg.pandaStates[-1].safetyParam = safety_param
all_msgs.append(new_msg.as_reader()) ops.append((index, new_msg.as_reader()))
else: return ops, [], []
all_msgs.append(msg)
return all_msgs
def migrate_peripheralState(lr): @migration(inputs=["pandaStates", "pandaStateDEPRECATED"], product="peripheralState")
if any(msg.which() == "peripheralState" for msg in lr): def migrate_peripheralState(msgs):
return lr add_ops = []
all_msg = [] which = "pandaStates" if any(msg.which() == "pandaStates" for _, msg in msgs) else "pandaStateDEPRECATED"
for msg in lr: for _, msg in msgs:
all_msg.append(msg) if msg.which() != which:
if msg.which() not in ["pandaStates", "pandaStateDEPRECATED"]:
continue continue
new_msg = messaging.new_message("peripheralState") new_msg = messaging.new_message("peripheralState")
new_msg.valid = msg.valid new_msg.valid = msg.valid
new_msg.logMonoTime = msg.logMonoTime new_msg.logMonoTime = msg.logMonoTime
all_msg.append(new_msg.as_reader()) add_ops.append(new_msg.as_reader())
return [], add_ops, []
return all_msg
def migrate_cameraStates(lr): @migration(inputs=["roadEncodeIdx", "wideRoadEncodeIdx", "driverEncodeIdx", "roadCameraState", "wideRoadCameraState", "driverCameraState"])
all_msgs = [] def migrate_cameraStates(msgs):
add_ops, del_ops = [], []
frame_to_encode_id = defaultdict(dict) frame_to_encode_id = defaultdict(dict)
# just for encodeId fallback mechanism # just for encodeId fallback mechanism
min_frame_id = defaultdict(lambda: float('inf')) min_frame_id = defaultdict(lambda: float('inf'))
for msg in lr: for _, msg in msgs:
if msg.which() not in ["roadEncodeIdx", "wideRoadEncodeIdx", "driverEncodeIdx"]: if msg.which() not in ["roadEncodeIdx", "wideRoadEncodeIdx", "driverEncodeIdx"]:
continue continue
@ -216,9 +312,8 @@ def migrate_cameraStates(lr):
assert encode_index.segmentId < 1200, f"Encoder index segmentId greater that 1200: {msg.which()} {encode_index.segmentId}" assert encode_index.segmentId < 1200, f"Encoder index segmentId greater that 1200: {msg.which()} {encode_index.segmentId}"
frame_to_encode_id[meta.camera_state][encode_index.frameId] = encode_index.segmentId frame_to_encode_id[meta.camera_state][encode_index.frameId] = encode_index.segmentId
for msg in lr: for index, msg in msgs:
if msg.which() not in ["roadCameraState", "wideRoadCameraState", "driverCameraState"]: if msg.which() not in ["roadCameraState", "wideRoadCameraState", "driverCameraState"]:
all_msgs.append(msg)
continue continue
camera_state = getattr(msg, msg.which()) camera_state = getattr(msg, msg.which())
@ -228,6 +323,7 @@ def migrate_cameraStates(lr):
if encode_id is None: if encode_id is None:
print(f"Missing encoded frame for camera feed {msg.which()} with frameId: {camera_state.frameId}") print(f"Missing encoded frame for camera feed {msg.which()} with frameId: {camera_state.frameId}")
if len(frame_to_encode_id[msg.which()]) != 0: if len(frame_to_encode_id[msg.which()]) != 0:
del_ops.append(index)
continue continue
# fallback mechanism for logs without encodeIdx (e.g. logs from before 2022 with dcamera recording disabled) # fallback mechanism for logs without encodeIdx (e.g. logs from before 2022 with dcamera recording disabled)
@ -248,33 +344,27 @@ def migrate_cameraStates(lr):
new_msg.logMonoTime = msg.logMonoTime new_msg.logMonoTime = msg.logMonoTime
new_msg.valid = msg.valid new_msg.valid = msg.valid
all_msgs.append(new_msg.as_reader()) del_ops.append(index)
add_ops.append(new_msg.as_reader())
return all_msgs return [], add_ops, del_ops
def migrate_carParams(lr): @migration(inputs=["carParams"])
all_msgs = [] def migrate_carParams(msgs):
for msg in lr: ops = []
if msg.which() == 'carParams': for index, msg in msgs:
CP = msg.as_builder() CP = msg.as_builder()
CP.carParams.carFingerprint = MIGRATION.get(CP.carParams.carFingerprint, CP.carParams.carFingerprint) CP.carParams.carFingerprint = MIGRATION.get(CP.carParams.carFingerprint, CP.carParams.carFingerprint)
for car_fw in CP.carParams.carFw: for car_fw in CP.carParams.carFw:
car_fw.brand = CP.carParams.carName car_fw.brand = CP.carParams.carName
CP.logMonoTime = msg.logMonoTime ops.append((index, CP.as_reader()))
msg = CP.as_reader() return ops, [], []
all_msgs.append(msg)
return all_msgs
def migrate_sensorEvents(lr):
all_msgs = []
for msg in lr:
if msg.which() != 'sensorEventsDEPRECATED':
all_msgs.append(msg)
continue
@migration(inputs=["sensorEventsDEPRECATED"], product="sensorEvents")
def migrate_sensorEvents(msgs):
add_ops, del_ops = [], []
for index, msg in msgs:
# migrate to split sensor events # migrate to split sensor events
for evt in msg.sensorEventsDEPRECATED: for evt in msg.sensorEventsDEPRECATED:
# build new message for each sensor type # build new message for each sensor type
@ -302,6 +392,34 @@ def migrate_sensorEvents(lr):
m_dat.timestamp = evt.timestamp m_dat.timestamp = evt.timestamp
setattr(m_dat, evt.which(), getattr(evt, evt.which())) setattr(m_dat, evt.which(), getattr(evt, evt.which()))
all_msgs.append(m.as_reader()) add_ops.append(m.as_reader())
del_ops.append(index)
return [], add_ops, del_ops
@migration(inputs=["onroadEventsDEPRECATED"], product="onroadEvents")
def migrate_onroadEvents(msgs):
ops = []
for index, msg in msgs:
new_msg = messaging.new_message('onroadEvents', len(msg.onroadEventsDEPRECATED))
new_msg.valid = msg.valid
new_msg.logMonoTime = msg.logMonoTime
# dict converts name enum into string representation
new_msg.onroadEvents = [log.OnroadEvent(**event.to_dict()) for event in msg.onroadEventsDEPRECATED]
ops.append((index, new_msg.as_reader()))
return ops, [], []
@migration(inputs=["driverMonitoringState"])
def migrate_driverMonitoringState(msgs):
ops = []
for index, msg in msgs:
msg = msg.as_builder()
# dict converts name enum into string representation
msg.driverMonitoringState.events = [log.OnroadEvent(**event.to_dict()) for event in
msg.driverMonitoringState.eventsDEPRECATED]
ops.append((index, msg.as_reader()))
return all_msgs return ops, [], []

@ -105,22 +105,34 @@ if __name__ == "__main__":
ignore = [ ignore = [
'logMonoTime', 'logMonoTime',
'drivingModelData.frameDropPerc', 'drivingModelData.frameDropPerc',
'drivingModelData.modelExecutionTime',
'modelV2.frameDropPerc', 'modelV2.frameDropPerc',
'modelV2.modelExecutionTime', 'modelV2.modelExecutionTime',
'driverStateV2.modelExecutionTime', 'driverStateV2.modelExecutionTime',
'driverStateV2.dspExecutionTime' 'driverStateV2.gpuExecutionTime'
] ]
if PC: if PC:
ignore += [ # TODO We ignore whole bunch so we can compare important stuff
'modelV2.laneLines.0.t', # like posenet with reasonable tolerance
'modelV2.laneLines.1.t', ignore += ['modelV2.acceleration.x',
'modelV2.laneLines.2.t', 'modelV2.position.x',
'modelV2.laneLines.3.t', 'modelV2.position.xStd',
'modelV2.roadEdges.0.t', 'modelV2.position.y',
'modelV2.roadEdges.1.t', 'modelV2.position.yStd',
] 'modelV2.position.z',
# TODO this tolerance is absurdly large 'modelV2.position.zStd',
tolerance = 2.0 if PC else None 'drivingModelData.path.xCoefficients',]
for i in range(3):
for field in ('x', 'y', 'v', 'a'):
ignore.append(f'modelV2.leadsV3.{i}.{field}')
ignore.append(f'modelV2.leadsV3.{i}.{field}Std')
for i in range(4):
for field in ('x', 'y', 'z', 't'):
ignore.append(f'modelV2.laneLines.{i}.{field}')
for i in range(2):
for field in ('x', 'y', 'z', 't'):
ignore.append(f'modelV2.roadEdges.{i}.{field}')
tolerance = .3 if PC else None
results: Any = {TEST_ROUTE: {}} results: Any = {TEST_ROUTE: {}}
log_paths: Any = {TEST_ROUTE: {"models": {'ref': BASE_URL + log_fn, 'new': log_fn}}} log_paths: Any = {TEST_ROUTE: {"models": {'ref': BASE_URL + log_fn, 'new': log_fn}}}
results[TEST_ROUTE]["models"] = compare_logs(cmp_log, log_msgs, tolerance=tolerance, ignore_fields=ignore) results[TEST_ROUTE]["models"] = compare_logs(cmp_log, log_msgs, tolerance=tolerance, ignore_fields=ignore)

@ -1 +1 @@
8726813f978d6baf519055f3105350cd071741f3 c4d60dfe4b677f9230eebb47614501ea8d0b99a3

@ -17,14 +17,13 @@ import cereal.messaging as messaging
from cereal import car from cereal import car
from cereal.services import SERVICE_LIST from cereal.services import SERVICE_LIST
from msgq.visionipc import VisionIpcServer, get_endpoint_name as vipc_get_endpoint_name from msgq.visionipc import VisionIpcServer, get_endpoint_name as vipc_get_endpoint_name
from opendbc.car import structs
from opendbc.car.car_helpers import get_car, interfaces from opendbc.car.car_helpers import get_car, interfaces
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.prefix import OpenpilotPrefix from openpilot.common.prefix import OpenpilotPrefix
from openpilot.common.timeout import Timeout from openpilot.common.timeout import Timeout
from openpilot.common.realtime import DT_CTRL from openpilot.common.realtime import DT_CTRL
from panda.python import ALTERNATIVE_EXPERIENCE from panda.python import ALTERNATIVE_EXPERIENCE
from openpilot.selfdrive.car.card import can_comm_callbacks, convert_to_capnp from openpilot.selfdrive.car.card import can_comm_callbacks
from openpilot.system.manager.process_config import managed_processes from openpilot.system.manager.process_config import managed_processes
from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams
from openpilot.selfdrive.test.process_replay.migration import migrate_all from openpilot.selfdrive.test.process_replay.migration import migrate_all
@ -363,17 +362,17 @@ def get_car_params_callback(rc, pm, msgs, fingerprint):
cached_params = None cached_params = None
if has_cached_cp: if has_cached_cp:
with car.CarParams.from_bytes(cached_params_raw) as _cached_params: with car.CarParams.from_bytes(cached_params_raw) as _cached_params:
cached_params = structs.CarParams(carName=_cached_params.carName, carFw=_cached_params.carFw, carVin=_cached_params.carVin) cached_params = _cached_params
CP = get_car(*can_callbacks, lambda obd: None, Params().get_bool("ExperimentalLongitudinalEnabled"), cached_params=cached_params).CP CP = get_car(*can_callbacks, lambda obd: None, Params().get_bool("ExperimentalLongitudinalEnabled"), cached_params=cached_params).CP
if not params.get_bool("DisengageOnAccelerator"): if not params.get_bool("DisengageOnAccelerator"):
CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
params.put("CarParams", convert_to_capnp(CP).to_bytes()) params.put("CarParams", CP.to_bytes())
def controlsd_rcv_callback(msg, cfg, frame): def selfdrived_rcv_callback(msg, cfg, frame):
return (frame - 1) == 0 or msg.which() == 'carState' return (frame - 1) == 0 or msg.which() == 'carState'
@ -452,7 +451,7 @@ class FrequencyBasedRcvCallback:
return bool(len(resp_sockets)) return bool(len(resp_sockets))
def controlsd_config_callback(params, cfg, lr): def selfdrived_config_callback(params, cfg, lr):
ublox = params.get_bool("UbloxAvailable") ublox = params.get_bool("UbloxAvailable")
sub_keys = ({"gpsLocation", } if ublox else {"gpsLocationExternal", }) sub_keys = ({"gpsLocation", } if ublox else {"gpsLocationExternal", })
@ -461,26 +460,37 @@ def controlsd_config_callback(params, cfg, lr):
CONFIGS = [ CONFIGS = [
ProcessConfig( ProcessConfig(
proc_name="controlsd", proc_name="selfdrived",
pubs=[ pubs=[
"carState", "deviceState", "pandaStates", "peripheralState", "liveCalibration", "driverMonitoringState", "carState", "deviceState", "pandaStates", "peripheralState", "liveCalibration", "driverMonitoringState",
"longitudinalPlan", "livePose", "liveParameters", "radarState", "longitudinalPlan", "livePose", "liveParameters", "radarState",
"modelV2", "driverCameraState", "roadCameraState", "wideRoadCameraState", "managerState", "modelV2", "driverCameraState", "roadCameraState", "wideRoadCameraState", "managerState",
"testJoystick", "liveTorqueParameters", "accelerometer", "gyroscope", "carOutput", "liveTorqueParameters", "accelerometer", "gyroscope", "carOutput",
"gpsLocationExternal", "gpsLocation", "gpsLocationExternal", "gpsLocation", "controlsState", "carControl", "driverAssistance", "alertDebug",
], ],
subs=["selfdriveState", "controlsState", "carControl", "onroadEvents"], subs=["selfdriveState", "onroadEvents"],
ignore=["logMonoTime", "controlsState.cumLagMs"], ignore=["logMonoTime"],
config_callback=controlsd_config_callback, config_callback=selfdrived_config_callback,
init_callback=get_car_params_callback, init_callback=get_car_params_callback,
should_recv_callback=controlsd_rcv_callback, should_recv_callback=selfdrived_rcv_callback,
tolerance=NUMPY_TOLERANCE, tolerance=NUMPY_TOLERANCE,
processing_time=0.004, processing_time=0.004,
), ),
ProcessConfig(
proc_name="controlsd",
pubs=["liveParameters", "liveTorqueParameters", "modelV2", "selfdriveState",
"liveCalibration", "livePose", "longitudinalPlan", "carState", "carOutput",
"driverMonitoringState", "onroadEvents", "driverAssistance"],
subs=["carControl", "controlsState"],
ignore=["logMonoTime", ],
init_callback=get_car_params_callback,
should_recv_callback=MessageBasedRcvCallback("selfdriveState"),
tolerance=NUMPY_TOLERANCE,
),
ProcessConfig( ProcessConfig(
proc_name="card", proc_name="card",
pubs=["pandaStates", "carControl", "onroadEvents", "can"], pubs=["pandaStates", "carControl", "onroadEvents", "can"],
subs=["sendcan", "carState", "carParams", "carOutput"], subs=["sendcan", "carState", "carParams", "carOutput", "liveTracks"],
ignore=["logMonoTime", "carState.cumLagMs"], ignore=["logMonoTime", "carState.cumLagMs"],
init_callback=card_fingerprint_callback, init_callback=card_fingerprint_callback,
should_recv_callback=card_rcv_callback, should_recv_callback=card_rcv_callback,
@ -490,17 +500,16 @@ CONFIGS = [
), ),
ProcessConfig( ProcessConfig(
proc_name="radard", proc_name="radard",
pubs=["can", "carState", "modelV2"], pubs=["liveTracks", "carState", "modelV2"],
subs=["radarState", "liveTracks"], subs=["radarState"],
ignore=["logMonoTime", "radarState.cumLagMs"], ignore=["logMonoTime"],
init_callback=get_car_params_callback, init_callback=get_car_params_callback,
should_recv_callback=MessageBasedRcvCallback("can"), should_recv_callback=FrequencyBasedRcvCallback("modelV2"),
main_pub="can",
), ),
ProcessConfig( ProcessConfig(
proc_name="plannerd", proc_name="plannerd",
pubs=["modelV2", "carControl", "carState", "controlsState", "radarState", "selfdriveState"], pubs=["modelV2", "carControl", "carState", "controlsState", "radarState", "selfdriveState"],
subs=["longitudinalPlan"], subs=["longitudinalPlan", "driverAssistance"],
ignore=["logMonoTime", "longitudinalPlan.processingDelay", "longitudinalPlan.solverExecutionTime"], ignore=["logMonoTime", "longitudinalPlan.processingDelay", "longitudinalPlan.solverExecutionTime"],
init_callback=get_car_params_callback, init_callback=get_car_params_callback,
should_recv_callback=FrequencyBasedRcvCallback("modelV2"), should_recv_callback=FrequencyBasedRcvCallback("modelV2"),
@ -559,9 +568,9 @@ CONFIGS = [
), ),
ProcessConfig( ProcessConfig(
proc_name="modeld", proc_name="modeld",
pubs=["deviceState", "roadCameraState", "wideRoadCameraState", "liveCalibration", "driverMonitoringState"], pubs=["deviceState", "roadCameraState", "wideRoadCameraState", "liveCalibration", "driverMonitoringState", "carState"],
subs=["modelV2", "drivingModelData", "cameraOdometry"], subs=["modelV2", "drivingModelData", "cameraOdometry"],
ignore=["logMonoTime", "modelV2.frameDropPerc", "modelV2.modelExecutionTime"], ignore=["logMonoTime", "modelV2.frameDropPerc", "modelV2.modelExecutionTime", "drivingModelData.frameDropPerc", "drivingModelData.modelExecutionTime"],
should_recv_callback=ModeldCameraSyncRcvCallback(), should_recv_callback=ModeldCameraSyncRcvCallback(),
tolerance=NUMPY_TOLERANCE, tolerance=NUMPY_TOLERANCE,
processing_time=0.020, processing_time=0.020,
@ -575,7 +584,7 @@ CONFIGS = [
proc_name="dmonitoringmodeld", proc_name="dmonitoringmodeld",
pubs=["liveCalibration", "driverCameraState"], pubs=["liveCalibration", "driverCameraState"],
subs=["driverStateV2"], subs=["driverStateV2"],
ignore=["logMonoTime", "driverStateV2.modelExecutionTime", "driverStateV2.dspExecutionTime"], ignore=["logMonoTime", "driverStateV2.modelExecutionTime", "driverStateV2.gpuExecutionTime"],
should_recv_callback=dmonitoringmodeld_rcv_callback, should_recv_callback=dmonitoringmodeld_rcv_callback,
tolerance=NUMPY_TOLERANCE, tolerance=NUMPY_TOLERANCE,
processing_time=0.020, processing_time=0.020,

@ -1 +1 @@
c2022c388da6eb2e26bb23ad6009be9d5314c0be bfbdd4706abcf5757790526d99d0000644017b1e

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

Loading…
Cancel
Save