mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-08-22 13:28:15 +08:00
Compare commits
399 Commits
rafa
...
fca-temp-fix
| Author | SHA1 | Date | |
|---|---|---|---|
| c590c39362 | |||
| 1b0ae07a0c | |||
| a564824c85 | |||
| 78635eee88 | |||
| 97c7eacbf4 | |||
| 4a204b8a22 | |||
| ae658b82d7 | |||
| 11aeebf452 | |||
| f6be0110b8 | |||
| 8981993def | |||
| 209cbaed01 | |||
| 028c3691f6 | |||
| de64b99740 | |||
| 0e1de37281 | |||
| 5a309daee6 | |||
| 520649b893 | |||
| 18abe218d9 | |||
| cca3be3a96 | |||
| 282a8b093d | |||
| 1e7fc15a04 | |||
| e999839a57 | |||
| 1bfecbc9c2 | |||
| dcd382ffb8 | |||
| 09d165a85b | |||
| 4c4964a740 | |||
| 225ce45d31 | |||
| 92214b69d8 | |||
| f3ed577870 | |||
| 49e58a2532 | |||
| ae901d1562 | |||
| 85d2653fda | |||
| 0f4828df82 | |||
| dc0fd4ca96 | |||
| 90adc18032 | |||
| db65937fc7 | |||
| 8ebe9b69af | |||
| 4c40be8b1f | |||
| 082ea8119b | |||
| 1465e38c7b | |||
| ecee67dd64 | |||
| ea6178e53e | |||
| 01a0ad496d | |||
| b64d5a0fa4 | |||
| 005c6aed95 | |||
| 2fa66d6f4d | |||
| d5a873ed86 | |||
| 563ae65443 | |||
| 2efe78a4ef | |||
| 569a9216db | |||
| 629cfd845f | |||
| 2892dc05c8 | |||
| 632b416f2a | |||
| 5f3821c1f9 | |||
| 55b7529ca4 | |||
| c248f307f8 | |||
| bdb83b6be1 | |||
| c55f40e77d | |||
| ddf63701e8 | |||
| 28098bb7c4 | |||
| 60e056cc0a | |||
| fb743d313e | |||
| 4441671227 | |||
| 1f8941367d | |||
| 784e1d6658 | |||
| cb94d3b055 | |||
| 94f93a9f26 | |||
| 747460363f | |||
| b32c6dafee | |||
| bffb2fb6fa | |||
| 688b694266 | |||
| ec8f036850 | |||
| 4f44d6e643 | |||
| 1be13fdc55 | |||
| 810a2d9448 | |||
| c9dbf97649 | |||
| 1bb4ca2547 | |||
| 2c04a27a2a | |||
| b7f8dd11a5 | |||
| 70c0592e84 | |||
| 572c03dbac | |||
| fa498221da | |||
| 67238d5045 | |||
| 994170ddb5 | |||
| 3c28188d7a | |||
| 4ccd17903b | |||
| 0e1b573f89 | |||
| 10580aca92 | |||
| 6b13175338 | |||
| d0171084b5 | |||
| 2bfdd0d61d | |||
| ea53111afc | |||
| 0739d4ac2d | |||
| 698e0ca00f | |||
| 8dca43881a | |||
| a885111c0c | |||
| bd73664f4c | |||
| 608c16007e | |||
| 275abc1eb5 | |||
| ff34b8af76 | |||
| ff4d1923f0 | |||
| 3a91ae08a9 | |||
| b161764b1e | |||
| 03e9777c3f | |||
| 1033d3d80e | |||
| 7057c57419 | |||
| 1f1efec4c9 | |||
| 7d4df73ea5 | |||
| 29fe152bd3 | |||
| 31918c067a | |||
| daf5ea2783 | |||
| f0f04d4b5b | |||
| 2b7707ecf6 | |||
| ef870d5533 | |||
| fd7295c980 | |||
| 220cfff04d | |||
| ee0fb6bf8e | |||
| 0cd2bbf6c0 | |||
| 0871abcf55 | |||
| 8ccb777192 | |||
| 0593667601 | |||
| a5044302a2 | |||
| 6a4f685d04 | |||
| 355499a8de | |||
| 288a5e14da | |||
| 9447aa0e3d | |||
| 67fd6c80dd | |||
| 43c12ae7b3 | |||
| 1894a312d3 | |||
| 3e9545670b | |||
| 45ee58b1f6 | |||
| 3f3c293559 | |||
| 5d110bcee5 | |||
| 62bf9fcc27 | |||
| 205863b71f | |||
| c990515eaf | |||
| ba1da60c25 | |||
| 54174d1ef0 | |||
| 76e91da3ad | |||
| 9fcac06297 | |||
| 3e2549f2b8 | |||
| 63961dec45 | |||
| 7a19a11001 | |||
| 93f7925c4d | |||
| a2c5fca787 | |||
| bb06468ead | |||
| 1d8dc8a69a | |||
| a254a05df0 | |||
| 2aa7648bb8 | |||
| b309bf4173 | |||
| a3fcde2ae8 | |||
| f8ff156869 | |||
| 375dfe16a8 | |||
| b976135d2f | |||
| f40f7f9ece | |||
| ea6677c464 | |||
| 8258257658 | |||
| f5d67b5eee | |||
| 8ee3c7b485 | |||
| 8450f9f333 | |||
| 342ff24510 | |||
| 4cd76f4966 | |||
| ec254074d1 | |||
| 8059106cae | |||
| 23b4aaf2a5 | |||
| 5359f6d354 | |||
| a70e4c3074 | |||
| 7a2f2ddf32 | |||
| 2dc0f97c93 | |||
| 6bbf42c16a | |||
| 73e66c4a0b | |||
| 15fcbf24f1 | |||
| e89c6b3b88 | |||
| c4a7f25b62 | |||
| 1d74a97ba6 | |||
| aea467ff02 | |||
| 9579d331fc | |||
| 7c6d887187 | |||
| f89796033c | |||
| c8249f3657 | |||
| 798e9071d8 | |||
| b501ad4d51 | |||
| d6af554db4 | |||
| 9e50a1b660 | |||
| 2b86cc1373 | |||
| dd7de180ea | |||
| 2b46e1450a | |||
| 7ed8abb66c | |||
| ef2bb7f2fc | |||
| de56b21103 | |||
| 5491a61384 | |||
| ae3b74245f | |||
| c0a74f7a20 | |||
| 7f035bae39 | |||
| cea3572b74 | |||
| cd9ec6b240 | |||
| b4cc4ea8e2 | |||
| 154f655335 | |||
| 638dfb68ba | |||
| 2ff707d82f | |||
| 8320934d91 | |||
| 63441c048c | |||
| d0069c136b | |||
| fa0017fafc | |||
| 870d19f33d | |||
| 60c34a0837 | |||
| 22e79479d2 | |||
| 3570022b9a | |||
| dd5f5fdb98 | |||
| 18b7ddef8f | |||
| 5ec9aee216 | |||
| 927548621b | |||
| 6005b12f94 | |||
| 09aa21390d | |||
| d097a0c201 | |||
| 560c503871 | |||
| 3d24225cc1 | |||
| 51314fa9fe | |||
| 803b54ebdb | |||
| c085b8af19 | |||
| 2cec2587be | |||
| 9e3a35035a | |||
| 2148e2dff2 | |||
| f55f3bb7cd | |||
| 4d55671b17 | |||
| dfc66d7807 | |||
| 31101ecaab | |||
| 80d702c866 | |||
| 3d879dd1ae | |||
| ef9e430992 | |||
| 63fa250f29 | |||
| ceb557058c | |||
| 6a67f9e56f | |||
| 372682d4a9 | |||
| 102ac66fab | |||
| ab44c9a4ff | |||
| 1805a47139 | |||
| 5417efaa1d | |||
| 4536719353 | |||
| 34f4aadca5 | |||
| b54d5997de | |||
| 385ad9e839 | |||
| 7c6bc70312 | |||
| 8ec61991ee | |||
| 1eef956cad | |||
| daef43f620 | |||
| aa91a02db8 | |||
| a6d0a88b1e | |||
| f2c806f8a0 | |||
| 349c0ec662 | |||
| 741ea44aba | |||
| 56a89eb4fb | |||
| 3f830827b2 | |||
| a2a385336e | |||
| be934b3881 | |||
| 3d6dfc864d | |||
| 3cf81c081c | |||
| 8910668e4e | |||
| bed8da06dc | |||
| c490b3ca12 | |||
| 6c4c0c00b4 | |||
| 68625222b6 | |||
| 8deb1bf285 | |||
| 28b17858be | |||
| 91aec49cee | |||
| cd087a561e | |||
| 10cc87b80b | |||
| 13d4c6a167 | |||
| 455a6a586a | |||
| c78b302b93 | |||
| a11a8591e4 | |||
| ec3f044c83 | |||
| 6eaae848c9 | |||
| fc9a79406f | |||
| 0bbceb8539 | |||
| e97ae07589 | |||
| f45ad6bab9 | |||
| b5cbb980fb | |||
| 876738e96a | |||
| 4116b412a3 | |||
| 75509aec16 | |||
| 97c2d7e655 | |||
| 1bc12f1e21 | |||
| 430079113c | |||
| b4f19d4860 | |||
| 1c46640ea6 | |||
| 2d8030de0b | |||
| 80a4ace1ab | |||
| b391708b3d | |||
| 6ef386da3d | |||
| 6ae668e987 | |||
| 72b71d57bc | |||
| 5339a89e81 | |||
| 2f60026c22 | |||
| e0f51bdbb6 | |||
| 63d8c6c7f7 | |||
| 83f6843a48 | |||
| 4bb5986c14 | |||
| e596704644 | |||
| bcc416c7eb | |||
| 38de06232e | |||
| 567c5459db | |||
| fd32fcd20d | |||
| a79a5e5a16 | |||
| 5117a8c3a6 | |||
| 1555c0b5fe | |||
| 0e9de8f1b1 | |||
| 7bfac9d050 | |||
| a800c129b0 | |||
| f13ec6cb27 | |||
| f04bb6b9fa | |||
| ed0346980c | |||
| 6cf710d4cb | |||
| 8b90c210f8 | |||
| 62bbf6db8d | |||
| a51477d40d | |||
| a84089c6e5 | |||
| bb8a2ff65b | |||
| 3a78eee2f9 | |||
| 52a4b52628 | |||
| 839a773345 | |||
| c236f472a9 | |||
| ac3d96d2fd | |||
| eca2f40341 | |||
| 5e6f942234 | |||
| 69ca699773 | |||
| d1e0a60408 | |||
| 999db5b426 | |||
| e8135c5431 | |||
| 978d1c38f1 | |||
| 8c7d53004f | |||
| 7413982f0d | |||
| c95cac3b06 | |||
| fbbb2ef5d0 | |||
| d15d3c73b8 | |||
| 96313fa4c0 | |||
| c35494c19f | |||
| c321fa72e2 | |||
| 2c654500d2 | |||
| bb4f9651ba | |||
| c23537b4d5 | |||
| 865e6fa9d8 | |||
| d7b0a5fa7e | |||
| 112d615ac9 | |||
| fb34e7ccd3 | |||
| f08d95b95a | |||
| 1c9bbb290a | |||
| 2c8415f81c | |||
| bae7a610fa | |||
| aecb6d13e7 | |||
| 1ca8a4ca75 | |||
| c316c400f8 | |||
| 408cef2d46 | |||
| be0626f7e3 | |||
| f06c98018f | |||
| 976dfa3982 | |||
| 623de0e22a | |||
| 86146981c4 | |||
| 56dcf71774 | |||
| a1f073921c | |||
| cccd60a28b | |||
| 181ea39a83 | |||
| 8cce8cf3f3 | |||
| 0b855a93d7 | |||
| 8c78749846 | |||
| aa2a3b3c8f | |||
| ba2dced54c | |||
| 2e15ac5f4f | |||
| c92add1280 | |||
| bab251b287 | |||
| 9dc98b36be | |||
| 313f36712c | |||
| 3ff874d6c2 | |||
| eb751a3804 | |||
| 5a8e3470ff | |||
| 07909906d4 | |||
| 7c87ada8d8 | |||
| bdd6ff4f3e | |||
| f2e100b0e1 | |||
| 8b0bfd7910 | |||
| db55f1275d | |||
| 8f9ee43d34 | |||
| 37c4ee1532 | |||
| 0ebee55050 | |||
| cb5299be5a | |||
| 5c73681be8 | |||
| dd09c4f341 | |||
| 4d01b7bec8 | |||
| 42ebab1334 | |||
| 9117a414bb | |||
| 1966845fc9 | |||
| 889e386dbc | |||
| 4e97a29e83 | |||
| b695715753 | |||
| f5991caf6f | |||
| 2e4de9b7d8 | |||
| f2c17dd688 | |||
| c4298ce287 | |||
| 1de1640689 | |||
| fc58c866c6 |
@@ -1,6 +1,6 @@
|
|||||||
ci:
|
ci:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-all-files: "{.github/**,**/test_*,Jenkinsfile}"
|
- any-glob-to-all-files: "{.github/**,**/test_*,**/test/**,Jenkinsfile}"
|
||||||
|
|
||||||
chore:
|
chore:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
|
|||||||
@@ -40,10 +40,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: (github.event.pull_request.head.repo.fork && (contains(github.event_name, 'pull_request') && github.event.action == 'synchronize'))
|
if: (github.event.pull_request.head.repo.fork && (contains(github.event_name, 'pull_request') && github.event.action == 'synchronize'))
|
||||||
env:
|
env:
|
||||||
PR_LABEL: 'dev-c3'
|
PR_LABEL: 'dev'
|
||||||
TRUST_FORK_PR_LABEL: 'trust-fork-pr'
|
TRUST_FORK_PR_LABEL: 'trust-fork-pr'
|
||||||
steps:
|
steps:
|
||||||
- name: Check if PR has dev-c3 label
|
- name: Check if PR has dev label
|
||||||
id: check-labels
|
id: check-labels
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
@@ -62,11 +62,11 @@ jobs:
|
|||||||
console.log(`PR #${prNumber} has ${process.env.PR_LABEL} label: ${hasDevC3Label}`);
|
console.log(`PR #${prNumber} has ${process.env.PR_LABEL} label: ${hasDevC3Label}`);
|
||||||
console.log(`PR #${prNumber} has ${process.env.TRUST_FORK_PR_LABEL} label: ${hasTrustLabel}`);
|
console.log(`PR #${prNumber} has ${process.env.TRUST_FORK_PR_LABEL} label: ${hasTrustLabel}`);
|
||||||
|
|
||||||
core.setOutput('has-dev-c3', hasDevC3Label ? 'true' : 'false');
|
core.setOutput('has-dev', hasDevC3Label ? 'true' : 'false');
|
||||||
core.setOutput('has-trust', hasTrustLabel ? 'true' : 'false');
|
core.setOutput('has-trust', hasTrustLabel ? 'true' : 'false');
|
||||||
|
|
||||||
- name: Remove trust-fork-pr label if present
|
- name: Remove trust-fork-pr label if present
|
||||||
if: steps.check-labels.outputs.has-dev-c3 == 'true' && steps.check-labels.outputs.has-trust == 'true'
|
if: steps.check-labels.outputs.has-dev == 'true' && steps.check-labels.outputs.has-trust == 'true'
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -16,7 +16,24 @@ jobs:
|
|||||||
recompiled_dir: ${{ steps.create-recompiled-dir.outputs.recompiled_dir }}
|
recompiled_dir: ${{ steps.create-recompiled-dir.outputs.recompiled_dir }}
|
||||||
json_file: ${{ steps.get-json.outputs.json_file }}
|
json_file: ${{ steps.get-json.outputs.json_file }}
|
||||||
model_matrix: ${{ steps.set-matrix.outputs.model_matrix }}
|
model_matrix: ${{ steps.set-matrix.outputs.model_matrix }}
|
||||||
|
tinygrad_ref: ${{ steps.get-tinygrad-ref.outputs.tinygrad_ref }}
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout sunnypilot repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: sunnypilot/sunnypilot
|
||||||
|
path: sunnypilot
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Get tinygrad_repo ref
|
||||||
|
id: get-tinygrad-ref
|
||||||
|
run: |
|
||||||
|
cd sunnypilot
|
||||||
|
export PYTHONPATH=$(pwd)
|
||||||
|
ref=$(python3 sunnypilot/models/tinygrad_ref.py)
|
||||||
|
echo "tinygrad_ref=$ref" >> $GITHUB_OUTPUT
|
||||||
|
echo "tinygrad_ref is $ref"
|
||||||
|
|
||||||
- name: Checkout docs repo (sunnypilot-docs, gh-pages)
|
- name: Checkout docs repo (sunnypilot-docs, gh-pages)
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -110,7 +127,7 @@ jobs:
|
|||||||
retry_failed_models:
|
retry_failed_models:
|
||||||
needs: [setup, get_and_build]
|
needs: [setup, get_and_build]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ needs.setup.result != 'failure' && (failure() && !cancelled()) }}
|
if: ${{ needs.setup.result != 'failure' && !cancelled() }}
|
||||||
outputs:
|
outputs:
|
||||||
retry_matrix: ${{ steps.set-retry-matrix.outputs.retry_matrix }}
|
retry_matrix: ${{ steps.set-retry-matrix.outputs.retry_matrix }}
|
||||||
steps:
|
steps:
|
||||||
@@ -269,6 +286,7 @@ jobs:
|
|||||||
ARGS=""
|
ARGS=""
|
||||||
[ -n "${{ inputs.set_min_version }}" ] && ARGS="$ARGS --set-min-version \"${{ inputs.set_min_version }}\""
|
[ -n "${{ inputs.set_min_version }}" ] && ARGS="$ARGS --set-min-version \"${{ inputs.set_min_version }}\""
|
||||||
ARGS="$ARGS --sort-by-date"
|
ARGS="$ARGS --sort-by-date"
|
||||||
|
ARGS="$ARGS --tinygrad-ref \"${{ needs.setup.outputs.tinygrad_ref }}\""
|
||||||
eval python3 docs/json_parser.py \
|
eval python3 docs/json_parser.py \
|
||||||
--json-path "$JSON_FILE" \
|
--json-path "$JSON_FILE" \
|
||||||
--recompiled-dir "gitlab_docs/models/$RECOMPILED_DIR" \
|
--recompiled-dir "gitlab_docs/models/$RECOMPILED_DIR" \
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ on:
|
|||||||
recompiled_dir:
|
recompiled_dir:
|
||||||
description: 'Existing recompiled directory number (e.g. 3 for recompiled3)'
|
description: 'Existing recompiled directory number (e.g. 3 for recompiled3)'
|
||||||
required: true
|
required: true
|
||||||
type: number
|
type: string
|
||||||
json_version:
|
json_version:
|
||||||
description: 'driving_models version number to update (e.g. 5 for driving_models_v5.json)'
|
description: 'driving_models version number to update (e.g. 5 for driving_models_v5.json)'
|
||||||
required: true
|
required: true
|
||||||
type: number
|
type: string
|
||||||
model_folder:
|
model_folder:
|
||||||
description: 'Model folder'
|
description: 'Model folder'
|
||||||
type: choice
|
type: choice
|
||||||
@@ -67,11 +67,11 @@ on:
|
|||||||
generation:
|
generation:
|
||||||
description: 'Model generation'
|
description: 'Model generation'
|
||||||
required: false
|
required: false
|
||||||
type: number
|
type: string
|
||||||
version:
|
version:
|
||||||
description: 'Minimum selector version'
|
description: 'Minimum selector version'
|
||||||
required: false
|
required: false
|
||||||
type: number
|
type: string
|
||||||
env:
|
env:
|
||||||
RECOMPILED_DIR: recompiled${{ inputs.recompiled_dir }}
|
RECOMPILED_DIR: recompiled${{ inputs.recompiled_dir }}
|
||||||
JSON_FILE: docs/docs/driving_models_v${{ inputs.json_version }}.json
|
JSON_FILE: docs/docs/driving_models_v${{ inputs.json_version }}.json
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ concurrency:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHONWARNINGS: error
|
PYTHONWARNINGS: error
|
||||||
BASE_IMAGE: sunnypilot-base
|
BASE_IMAGE: openpilot-base
|
||||||
BUILD: release/ci/docker_build_sp.sh base
|
BUILD: selfdrive/test/docker_build.sh base
|
||||||
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
|
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: 'sunnypilot/sunnypilot'
|
repository: 'commaai/openpilot'
|
||||||
submodules: true
|
submodules: true
|
||||||
ref: "refs/heads/master"
|
ref: "refs/heads/master"
|
||||||
- uses: ./.github/workflows/setup-with-retry
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ jobs:
|
|||||||
scan-comments:
|
scan-comments:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event.issue.pull_request }}
|
if: ${{ github.event.issue.pull_request }}
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
- name: Check for trigger phrase
|
- name: Check for trigger phrase
|
||||||
id: check_comment
|
id: check_comment
|
||||||
@@ -43,3 +46,14 @@ jobs:
|
|||||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
git checkout -b tmp-jenkins-${{ github.event.issue.number }}
|
git checkout -b tmp-jenkins-${{ github.event.issue.number }}
|
||||||
GIT_LFS_SKIP_PUSH=1 git push -f origin tmp-jenkins-${{ github.event.issue.number }}
|
GIT_LFS_SKIP_PUSH=1 git push -f origin tmp-jenkins-${{ github.event.issue.number }}
|
||||||
|
|
||||||
|
- name: Delete trigger comment
|
||||||
|
if: steps.check_comment.outputs.result == 'true' && always()
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
await github.rest.issues.deleteComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
comment_id: context.payload.comment.id,
|
||||||
|
});
|
||||||
|
|||||||
@@ -39,4 +39,4 @@ jobs:
|
|||||||
git config --global --add safe.directory '*'
|
git config --global --add safe.directory '*'
|
||||||
git lfs pull
|
git lfs pull
|
||||||
- name: Push __nightly
|
- name: Push __nightly
|
||||||
run: BRANCH=__nightly release/build_devel.sh
|
run: BRANCH=__nightly release/build_stripped.sh
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: uv lock
|
- name: uv lock
|
||||||
|
if: github.repository == 'commaai/openpilot'
|
||||||
run: |
|
run: |
|
||||||
python3 -m ensurepip --upgrade
|
python3 -m ensurepip --upgrade
|
||||||
pip3 install uv
|
pip3 install uv
|
||||||
@@ -50,6 +51,7 @@ jobs:
|
|||||||
- name: bump submodules
|
- name: bump submodules
|
||||||
run: |
|
run: |
|
||||||
git config --global --add safe.directory '*'
|
git config --global --add safe.directory '*'
|
||||||
|
git config submodule.tinygrad.update none
|
||||||
git submodule update --remote
|
git submodule update --remote
|
||||||
git add .
|
git add .
|
||||||
- name: update car docs
|
- name: update car docs
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ env:
|
|||||||
|
|
||||||
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
|
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
|
||||||
|
|
||||||
PYTEST: pytest --continue-on-collection-errors --durations=0 --durations-min=5 -n logical
|
PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_release:
|
build_release:
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
command: git lfs pull
|
command: git lfs pull
|
||||||
- name: Build devel
|
- name: Build devel
|
||||||
timeout-minutes: 1
|
timeout-minutes: 1
|
||||||
run: TARGET_DIR=$STRIPPED_DIR release/build_devel.sh
|
run: TARGET_DIR=$STRIPPED_DIR release/build_stripped.sh
|
||||||
- uses: ./.github/workflows/setup-with-retry
|
- uses: ./.github/workflows/setup-with-retry
|
||||||
- name: Build openpilot and run checks
|
- name: Build openpilot and run checks
|
||||||
timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache
|
timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache
|
||||||
@@ -107,7 +107,6 @@ jobs:
|
|||||||
|
|
||||||
build_mac:
|
build_mac:
|
||||||
name: build macOS
|
name: build macOS
|
||||||
if: false # temp disable since homebrew install is getting stuck
|
|
||||||
runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }}
|
runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -116,7 +115,9 @@ jobs:
|
|||||||
- run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV
|
- run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV
|
||||||
- name: Homebrew cache
|
- name: Homebrew cache
|
||||||
uses: ./.github/workflows/auto-cache
|
uses: ./.github/workflows/auto-cache
|
||||||
|
if: false # disabling the cache for now because it is breaking macos builds...
|
||||||
with:
|
with:
|
||||||
|
save: false # No need save here if we manually save it later conditionally
|
||||||
path: ~/Library/Caches/Homebrew
|
path: ~/Library/Caches/Homebrew
|
||||||
key: brew-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
key: brew-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
@@ -125,8 +126,8 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: ./tools/mac_setup.sh
|
run: ./tools/mac_setup.sh
|
||||||
env:
|
env:
|
||||||
# package install has DeprecationWarnings
|
PYTHONWARNINGS: default # package install has DeprecationWarnings
|
||||||
PYTHONWARNINGS: default
|
HOMEBREW_DISPLAY_INSTALL_TIMES: 1
|
||||||
- name: Save Homebrew cache
|
- name: Save Homebrew cache
|
||||||
uses: actions/cache/save@v4
|
uses: actions/cache/save@v4
|
||||||
if: github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/master'
|
||||||
@@ -137,6 +138,7 @@ jobs:
|
|||||||
- name: Getting scons cache
|
- name: Getting scons cache
|
||||||
uses: ./.github/workflows/auto-cache
|
uses: ./.github/workflows/auto-cache
|
||||||
with:
|
with:
|
||||||
|
save: false # No need save here if we manually save it later conditionally
|
||||||
path: /tmp/scons_cache
|
path: /tmp/scons_cache
|
||||||
key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
@@ -189,7 +191,9 @@ jobs:
|
|||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 999 }}
|
timeout-minutes: ${{ contains(runner.name, 'nsc') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 999 }}
|
||||||
run: |
|
run: |
|
||||||
${{ env.RUN }} "$PYTEST --collect-only -m 'not slow' &> /dev/null && \
|
${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \
|
||||||
|
# Pre-compile Python bytecode so each pytest worker doesn't need to
|
||||||
|
$PYTEST --collect-only -m 'not slow' -qq && \
|
||||||
MAX_EXAMPLES=1 $PYTEST -m 'not slow' && \
|
MAX_EXAMPLES=1 $PYTEST -m 'not slow' && \
|
||||||
./selfdrive/ui/tests/create_test_translations.sh && \
|
./selfdrive/ui/tests/create_test_translations.sh && \
|
||||||
QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \
|
QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \
|
||||||
|
|||||||
@@ -8,21 +8,15 @@ env:
|
|||||||
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
|
PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot"
|
||||||
|
|
||||||
# Branch configurations
|
# Branch configurations
|
||||||
STAGING_C3_SOURCE_BRANCH: ${{ vars.STAGING_C3_SOURCE_BRANCH || 'master' }} # vars are set on repo settings.
|
STAGING_SOURCE_BRANCH: 'master'
|
||||||
DEV_C3_SOURCE_BRANCH: ${{ vars.DEV_C3_SOURCE_BRANCH || 'master-dev-c3-new' }} # vars are set on repo settings.
|
|
||||||
|
|
||||||
# Target branch configurations
|
|
||||||
STAGING_TARGET_BRANCH: ${{ vars.STAGING_TARGET_BRANCH || 'staging-c3-new' }} # vars are set on repo settings.
|
|
||||||
DEV_TARGET_BRANCH: ${{ vars.DEV_TARGET_BRANCH || 'dev-c3-new' }} # vars are set on repo settings.
|
|
||||||
RELEASE_TARGET_BRANCH: ${{ vars.RELEASE_TARGET_BRANCH || 'release-c3-new' }} # vars are set on repo settings.
|
|
||||||
|
|
||||||
# Runtime configuration
|
# Runtime configuration
|
||||||
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
|
SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master, master-dev-c3-new ]
|
branches: [ master, master-dev ]
|
||||||
tags: [ '*' ]
|
tags: [ 'release/*' ]
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [ labeled ]
|
types: [ labeled ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -34,9 +28,72 @@ on:
|
|||||||
default: false
|
default: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
prepare_strategy:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
if: (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
|
||||||
|
outputs:
|
||||||
|
environment: ${{ steps.strategy.outputs.environment }}
|
||||||
|
new_branch: ${{ steps.strategy.outputs.new_branch }}
|
||||||
|
extra_version_identifier: ${{ steps.strategy.outputs.extra_version_identifier }}
|
||||||
|
version: ${{ steps.strategy.outputs.version }}
|
||||||
|
cancel_publish_in_progress: ${{ steps.strategy.outputs.cancel_publish_in_progress }}
|
||||||
|
publish_concurrency_group: ${{ steps.strategy.outputs.publish_concurrency_group }}
|
||||||
|
is_stable_branch: ${{ steps.strategy.outputs.is_stable_branch }}
|
||||||
|
build: ${{ steps.strategy.outputs.build }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Extract deploy strategy
|
||||||
|
id: strategy
|
||||||
|
run: |
|
||||||
|
echo '::group::Strategy Extraction'
|
||||||
|
BRANCH="${{ github.head_ref || github.ref_name }}"
|
||||||
|
echo "Current branch: $BRANCH"
|
||||||
|
|
||||||
|
STRATEGY_JSON='${{ vars.DEPLOY_STRATEGY }}'
|
||||||
|
CONFIG=$(echo "$STRATEGY_JSON" | jq -r --arg branch "$BRANCH" '
|
||||||
|
.configs[] | select(.branch == $branch)
|
||||||
|
')
|
||||||
|
|
||||||
|
BUILD="$(date '+%Y.%m.%d')-${{ github.run_number }}"
|
||||||
|
if [[ -z "$CONFIG" || "$CONFIG" == "null" ]]; then
|
||||||
|
echo "No exact strategy match found. Falling back to feature/fork logic."
|
||||||
|
IS_FORK="${{ github.event.pull_request.head.repo.fork && 'true' || 'false' }}"
|
||||||
|
FORK_SUFFIX=$( [[ "$IS_FORK" == "true" ]] && echo "-fork" || echo "" )
|
||||||
|
NEW_BRANCH="${BRANCH}${FORK_SUFFIX}-prebuilt"
|
||||||
|
|
||||||
|
echo "new_branch=$NEW_BRANCH" >> $GITHUB_OUTPUT
|
||||||
|
echo "version=$BUILD" >> $GITHUB_OUTPUT
|
||||||
|
echo "cancel_publish_in_progress=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "publish_concurrency_group=publish-${BRANCH}" >> $GITHUB_OUTPUT
|
||||||
|
echo "environment=feature-branch" >> $GITHUB_OUTPUT
|
||||||
|
echo "extra_version_identifier=feature-branch" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "Matched config: $CONFIG"
|
||||||
|
environment=$(echo "$CONFIG" | jq -r '.environment')
|
||||||
|
echo "environment=$environment" >> $GITHUB_OUTPUT
|
||||||
|
echo "new_branch=$(echo "$CONFIG" | jq -r '.target_branch')" >> $GITHUB_OUTPUT
|
||||||
|
cancel="$(echo "$CONFIG" | jq -r '.cancel_publish_in_progress')";
|
||||||
|
echo "cancel_publish_in_progress=$( [ "$cancel" = "null" ] && echo "true" || echo $cancel)" >> $GITHUB_OUTPUT
|
||||||
|
echo "publish_concurrency_group=publish-${BRANCH}$( [ "$cancel" = "null" ] || [ "$cancel" = "true" ] || echo "${{ github.sha }}" )" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
is_stable_branch="$(echo "$CONFIG" | jq -r '.stable_branch // false')";
|
||||||
|
echo "is_stable_branch=$is_stable_branch" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
stable_version=$(cat common/version.h | grep COMMA_VERSION | sed -e 's/[^0-9|.]//g');
|
||||||
|
echo "version=$([ "$is_stable_branch" = "true" ] && echo "$stable_version" || echo "$BUILD")" >> $GITHUB_OUTPUT
|
||||||
|
echo "extra_version_identifier=${environment}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
echo "build=$BUILD" >> $GITHUB_OUTPUT
|
||||||
|
cat $GITHUB_OUTPUT
|
||||||
|
|
||||||
validate_tests:
|
validate_tests:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
if: ((github.event_name == 'workflow_dispatch' && inputs.wait_for_tests) || contains(github.event_name, 'pull_request') && (github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
|
needs: [ prepare_strategy ]
|
||||||
|
if: ${{
|
||||||
|
((github.event_name == 'workflow_dispatch' && inputs.wait_for_tests) ||
|
||||||
|
(github.event_name == 'push' && needs.prepare_strategy.outputs.is_stable_branch == 'true') ||
|
||||||
|
contains(github.event_name, 'pull_request') && (github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
|
||||||
|
}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Wait for Tests
|
- name: Wait for Tests
|
||||||
@@ -44,19 +101,26 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
workflow: selfdrive_tests.yaml # The workflow file to monitor
|
workflow: selfdrive_tests.yaml # The workflow file to monitor
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
should-wait-for-start: ${{ github.event_name == 'push' && 'true' || 'false' }}
|
||||||
|
|
||||||
build:
|
build:
|
||||||
needs: [ validate_tests ]
|
needs: [ validate_tests, prepare_strategy ]
|
||||||
concurrency:
|
concurrency:
|
||||||
group: build-${{ github.head_ref || github.ref_name }}
|
group: build-${{ github.head_ref || github.ref_name }}
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
runs-on: [self-hosted, tici]
|
runs-on: [self-hosted, tici]
|
||||||
outputs:
|
outputs:
|
||||||
new_branch: ${{ steps.set-env.outputs.new_branch }}
|
new_branch: ${{ needs.prepare_strategy.outputs.new_branch }}
|
||||||
version: ${{ steps.set-env.outputs.version }}
|
version: ${{ needs.prepare_strategy.outputs.version }}
|
||||||
extra_version_identifier: ${{ steps.set-env.outputs.extra_version_identifier }}
|
extra_version_identifier: ${{ needs.prepare_strategy.outputs.extra_version_identifier }}
|
||||||
commit_sha: ${{ steps.set-env.outputs.commit_sha }}
|
commit_sha: ${{ github.sha }}
|
||||||
if: ${{ (always() && !failure() && !cancelled()) && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
|
if: ${{
|
||||||
|
(always() && !cancelled() && !failure()) &&
|
||||||
|
needs.prepare_strategy.result == 'success' &&
|
||||||
|
(needs.validate_tests.result == 'success' || needs.validate_tests.result == 'skipped') &&
|
||||||
|
(!contains(github.event_name, 'pull_request') ||
|
||||||
|
(github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
|
||||||
|
}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -74,64 +138,15 @@ jobs:
|
|||||||
# for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden.
|
# for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden.
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}
|
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.STAGING_C3_SOURCE_BRANCH }}
|
scons-${{ runner.os }}-${{ runner.arch }}-${{ env.STAGING_SOURCE_BRANCH }}
|
||||||
scons-${{ runner.os }}-${{ runner.arch }}
|
scons-${{ runner.os }}-${{ runner.arch }}
|
||||||
|
|
||||||
- name: Set Feature Branch Prebuilt Configuration
|
|
||||||
id: set_feature_configuration
|
|
||||||
if: (
|
|
||||||
!(env.SOURCE_BRANCH == env.DEV_C3_SOURCE_BRANCH) &&
|
|
||||||
!(env.SOURCE_BRANCH == env.STAGING_C3_SOURCE_BRANCH) &&
|
|
||||||
!(startsWith(github.ref, 'refs/tags/'))
|
|
||||||
)
|
|
||||||
run: |
|
|
||||||
echo "NEW_BRANCH=${{ env.SOURCE_BRANCH }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}-prebuilt" >> $GITHUB_ENV
|
|
||||||
echo "VERSION=$(date '+%Y.%m.%d')-${{ github.run_number }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Set dev-c3-new prebuilt Configuration
|
|
||||||
id: set_dev_configuration
|
|
||||||
if: (
|
|
||||||
steps.set_feature_configuration.outcome == 'skipped' &&
|
|
||||||
env.SOURCE_BRANCH == env.DEV_C3_SOURCE_BRANCH
|
|
||||||
)
|
|
||||||
run: |
|
|
||||||
echo "NEW_BRANCH=${{ env.DEV_TARGET_BRANCH }}" >> $GITHUB_ENV
|
|
||||||
echo "VERSION=$(date '+%Y.%m.%d')-${{ github.run_number }}" >> $GITHUB_ENV
|
|
||||||
echo "EXTRA_VERSION_IDENTIFIER=${{ github.run_number }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Set staging-c3-new prebuilt Configuration
|
|
||||||
id: set_staging_configuration
|
|
||||||
if: (
|
|
||||||
steps.set_feature_configuration.outcome == 'skipped' &&
|
|
||||||
!contains(github.event_name, 'pull_request') &&
|
|
||||||
steps.set_dev_configuration.outcome == 'skipped' &&
|
|
||||||
(env.SOURCE_BRANCH == env.STAGING_C3_SOURCE_BRANCH)
|
|
||||||
)
|
|
||||||
run: |
|
|
||||||
echo "NEW_BRANCH=${{ env.STAGING_TARGET_BRANCH }}" >> $GITHUB_ENV
|
|
||||||
echo "EXTRA_VERSION_IDENTIFIER=staging" >> $GITHUB_ENV
|
|
||||||
echo "VERSION=$(cat common/version.h | grep COMMA_VERSION | sed -e 's/[^0-9|.]//g')-staging" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Set release-c3-new prebuilt Configuration
|
|
||||||
id: set_tag_configuration
|
|
||||||
if: (
|
|
||||||
steps.set_feature_configuration.outcome == 'skipped' &&
|
|
||||||
!contains(github.event_name, 'pull_request') &&
|
|
||||||
steps.set_staging_configuration.outcome == 'skipped' &&
|
|
||||||
startsWith(github.ref, 'refs/tags/')
|
|
||||||
)
|
|
||||||
run: |
|
|
||||||
echo "NEW_BRANCH=${{ env.RELEASE_TARGET_BRANCH }}" >> $GITHUB_ENV
|
|
||||||
echo "EXTRA_VERSION_IDENTIFIER=release" >> $GITHUB_ENV
|
|
||||||
echo "VERSION=$(cat common/version.h | grep COMMA_VERSION | sed -e 's/[^0-9|.]//g')-release" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Set environment variables
|
- name: Set environment variables
|
||||||
id: set-env
|
id: set-env
|
||||||
run: |
|
run: |
|
||||||
# Write to GITHUB_OUTPUT from environment variables
|
echo "new_branch=${{ needs.prepare_strategy.outputs.new_branch }}" >> $GITHUB_OUTPUT
|
||||||
echo "new_branch=$NEW_BRANCH" >> $GITHUB_OUTPUT
|
echo "version=${{ needs.prepare_strategy.outputs.version }}" >> $GITHUB_OUTPUT
|
||||||
[[ ! -z "$EXTRA_VERSION_IDENTIFIER" ]] && echo "extra_version_identifier=$EXTRA_VERSION_IDENTIFIER" >> $GITHUB_OUTPUT
|
echo "extra_version_identifier=${{ needs.prepare_strategy.outputs.extra_version_identifier }}" >> $GITHUB_OUTPUT
|
||||||
[[ ! -z "$VERSION" ]] && echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
||||||
echo "commit_sha=${{ github.sha }}" >> $GITHUB_OUTPUT
|
echo "commit_sha=${{ github.sha }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
# Set up common environment
|
# Set up common environment
|
||||||
@@ -226,15 +241,19 @@ jobs:
|
|||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
||||||
|
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
concurrency:
|
concurrency:
|
||||||
group: publish-${{ github.head_ref || github.ref_name }}
|
# We do a bit of a hack here to avoid canceling the publishing job if a new commit comes in while we're publishing by adding the sha to the group name.
|
||||||
cancel-in-progress: true
|
# This means that if multiple commits come in while we're publishing, they will be queued up and publish one after the other.
|
||||||
if: ${{ (always() && !failure() && !cancelled()) && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
|
# Otherwise, if a job is waiting to be published due to environment wait time, it would be canceled by a new commit and restart the wait time.
|
||||||
needs: [ build ]
|
group: ${{ needs.prepare_strategy.outputs.publish_concurrency_group }}
|
||||||
|
cancel-in-progress: ${{ needs.prepare_strategy.outputs.cancel_publish_in_progress == 'true' }}
|
||||||
|
if: ${{ (always() && !cancelled() && !failure()) && needs.build.result == 'success' && needs.prepare_strategy.result == 'success' && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
|
||||||
|
needs: [ build, prepare_strategy ]
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
environment: ${{ (contains(fromJSON(vars.AUTO_DEPLOY_PREBUILT_BRANCHES), github.head_ref || github.ref_name) || contains(github.event.pull_request.labels.*.name, 'prebuilt')) && 'auto-deploy' || 'feature-branch' }}
|
environment: ${{ needs.prepare_strategy.outputs.environment }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
@@ -266,7 +285,7 @@ jobs:
|
|||||||
"${{ needs.build.outputs.new_branch }}" \
|
"${{ needs.build.outputs.new_branch }}" \
|
||||||
"${{ needs.build.outputs.version }}" \
|
"${{ needs.build.outputs.version }}" \
|
||||||
"https://x-access-token:${{github.token}}@github.com/sunnypilot/sunnypilot.git" \
|
"https://x-access-token:${{github.token}}@github.com/sunnypilot/sunnypilot.git" \
|
||||||
"-${{ needs.build.outputs.extra_version_identifier }}"
|
"${{ needs.build.outputs.extra_version_identifier }}"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "---- ℹ️ To update the list of branches that auto deploy prebuilts -----"
|
echo "---- ℹ️ To update the list of branches that auto deploy prebuilts -----"
|
||||||
@@ -274,11 +293,18 @@ jobs:
|
|||||||
echo "1. Go to: ${{ github.server_url }}/${{ github.repository }}/settings/variables/actions/AUTO_DEPLOY_PREBUILT_BRANCHES"
|
echo "1. Go to: ${{ github.server_url }}/${{ github.repository }}/settings/variables/actions/AUTO_DEPLOY_PREBUILT_BRANCHES"
|
||||||
echo "2. Current value: ${{ vars.AUTO_DEPLOY_PREBUILT_BRANCHES }}"
|
echo "2. Current value: ${{ vars.AUTO_DEPLOY_PREBUILT_BRANCHES }}"
|
||||||
echo "3. Update as needed (JSON array with no spaces)"
|
echo "3. Update as needed (JSON array with no spaces)"
|
||||||
|
|
||||||
|
- name: Tag ${{ needs.prepare_strategy.outputs.environment }}
|
||||||
|
if: ${{ needs.prepare_strategy.outputs.is_stable_branch == 'true' && (github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/')) }}
|
||||||
|
run: |
|
||||||
|
TAG="${{ needs.prepare_strategy.outputs.environment }}/${{ needs.prepare_strategy.outputs.version }}/${{ needs.prepare_strategy.outputs.build }}"
|
||||||
|
git tag -f -a ${TAG} -m "${{ needs.prepare_strategy.outputs.environment }} @ ${{ needs.prepare_strategy.outputs.version }} of build ${{ needs.build.outputs.build }}."
|
||||||
|
git push -f origin ${TAG}
|
||||||
|
|
||||||
notify:
|
notify:
|
||||||
needs: [ build, publish ]
|
needs: [ build, publish ]
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
if: ${{ (always() && !failure() && !cancelled()) && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
|
if: ${{ (always() && !cancelled() && !failure()) && needs.publish.result == 'success' && !failure() && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Setup Alpine Linux environment
|
- name: Setup Alpine Linux environment
|
||||||
|
|||||||
+6
-6
@@ -1,9 +1,9 @@
|
|||||||
name: Build dev-c3-new
|
name: Build dev
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DEFAULT_SOURCE_BRANCH: "master"
|
DEFAULT_SOURCE_BRANCH: "master"
|
||||||
DEFAULT_TARGET_BRANCH: "master-dev-c3-new"
|
DEFAULT_TARGET_BRANCH: "master-dev"
|
||||||
PR_LABEL: "dev-c3"
|
PR_LABEL: "dev"
|
||||||
LFS_URL: 'https://gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git/info/lfs'
|
LFS_URL: 'https://gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git/info/lfs'
|
||||||
LFS_PUSH_URL: 'ssh://git@gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git'
|
LFS_PUSH_URL: 'ssh://git@gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git'
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ on:
|
|||||||
target_branch:
|
target_branch:
|
||||||
description: 'Target branch to reset and squash into'
|
description: 'Target branch to reset and squash into'
|
||||||
required: true
|
required: true
|
||||||
default: 'master-dev-c3-new'
|
default: 'master-dev'
|
||||||
type: string
|
type: string
|
||||||
cancel_in_progress:
|
cancel_in_progress:
|
||||||
description: 'Cancel any in-progress runs of this workflow'
|
description: 'Cancel any in-progress runs of this workflow'
|
||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
if: (
|
if: (
|
||||||
(github.event_name == 'workflow_dispatch')
|
(github.event_name == 'workflow_dispatch')
|
||||||
|| (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|
|| (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|
||||||
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev-c3' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev-c3'))))
|
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev'))))
|
||||||
)
|
)
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
uses: ./.github/workflows/wait-for-action # Path to where you place the action
|
uses: ./.github/workflows/wait-for-action # Path to where you place the action
|
||||||
if: (
|
if: (
|
||||||
(github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|
(github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|
||||||
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev-c3' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev-c3'))))
|
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev'))))
|
||||||
)
|
)
|
||||||
with:
|
with:
|
||||||
workflow: selfdrive_tests.yaml # The workflow file to monitor
|
workflow: selfdrive_tests.yaml # The workflow file to monitor
|
||||||
+2
-1
@@ -13,9 +13,11 @@ venv/
|
|||||||
model2.png
|
model2.png
|
||||||
a.out
|
a.out
|
||||||
.hypothesis
|
.hypothesis
|
||||||
|
.cache/
|
||||||
|
|
||||||
/docs_site/
|
/docs_site/
|
||||||
|
|
||||||
|
*.mp4
|
||||||
*.dylib
|
*.dylib
|
||||||
*.DSYM
|
*.DSYM
|
||||||
*.d
|
*.d
|
||||||
@@ -48,7 +50,6 @@ cereal/services.h
|
|||||||
cereal/gen
|
cereal/gen
|
||||||
cereal/messaging/bridge
|
cereal/messaging/bridge
|
||||||
selfdrive/mapd/default_speeds_by_region.json
|
selfdrive/mapd/default_speeds_by_region.json
|
||||||
system/proclogd/proclogd
|
|
||||||
selfdrive/ui/translations/tmp
|
selfdrive/ui/translations/tmp
|
||||||
selfdrive/test/longitudinal_maneuvers/out
|
selfdrive/test/longitudinal_maneuvers/out
|
||||||
selfdrive/car/tests/cars_dump
|
selfdrive/car/tests/cars_dump
|
||||||
|
|||||||
Vendored
+40
-1
@@ -23,6 +23,11 @@
|
|||||||
"id": "args",
|
"id": "args",
|
||||||
"description": "Arguments to pass to the process",
|
"description": "Arguments to pass to the process",
|
||||||
"type": "promptString"
|
"type": "promptString"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "replayArg",
|
||||||
|
"type": "promptString",
|
||||||
|
"description": "Enter route or segment to replay."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"configurations": [
|
"configurations": [
|
||||||
@@ -40,7 +45,41 @@
|
|||||||
"type": "cppdbg",
|
"type": "cppdbg",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${workspaceFolder}/${input:cpp_process}",
|
"program": "${workspaceFolder}/${input:cpp_process}",
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Attach LLDB to Replay drive",
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "attach",
|
||||||
|
"pid": "${command:pickMyProcess}",
|
||||||
|
"initCommands": [
|
||||||
|
"script import time; time.sleep(3)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Replay drive",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/opendbc/safety/tests/safety_replay/replay_drive.py",
|
||||||
|
"args": [
|
||||||
|
"${input:replayArg}"
|
||||||
|
],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"justMyCode": false,
|
||||||
|
"env": {
|
||||||
|
"PYTHONPATH": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
"subProcess": true,
|
||||||
|
"stopOnEntry": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compounds": [
|
||||||
|
{
|
||||||
|
"name": "Replay drive + Safety LLDB",
|
||||||
|
"configurations": [
|
||||||
|
"Replay drive",
|
||||||
|
"Attach LLDB to Replay drive"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Vendored
+10
-24
@@ -167,7 +167,7 @@ node {
|
|||||||
env.GIT_COMMIT = checkout(scm).GIT_COMMIT
|
env.GIT_COMMIT = checkout(scm).GIT_COMMIT
|
||||||
|
|
||||||
def excludeBranches = ['__nightly', 'devel', 'devel-staging', 'release3', 'release3-staging',
|
def excludeBranches = ['__nightly', 'devel', 'devel-staging', 'release3', 'release3-staging',
|
||||||
'testing-closet*', 'hotfix-*']
|
'release-tici', 'testing-closet*', 'hotfix-*']
|
||||||
def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*')
|
def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*')
|
||||||
|
|
||||||
if (env.BRANCH_NAME != 'master' && !env.BRANCH_NAME.contains('__jenkins_loop_')) {
|
if (env.BRANCH_NAME != 'master' && !env.BRANCH_NAME.contains('__jenkins_loop_')) {
|
||||||
@@ -178,7 +178,7 @@ node {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (env.BRANCH_NAME == 'devel-staging') {
|
if (env.BRANCH_NAME == 'devel-staging') {
|
||||||
deviceStage("build release3-staging", "tici-needs-can", [], [
|
deviceStage("build release3-staging", "tizi-needs-can", [], [
|
||||||
step("build release3-staging", "RELEASE_BRANCH=release3-staging $SOURCE_DIR/release/build_release.sh"),
|
step("build release3-staging", "RELEASE_BRANCH=release3-staging $SOURCE_DIR/release/build_release.sh"),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@@ -186,12 +186,12 @@ node {
|
|||||||
if (env.BRANCH_NAME == '__nightly') {
|
if (env.BRANCH_NAME == '__nightly') {
|
||||||
parallel (
|
parallel (
|
||||||
'nightly': {
|
'nightly': {
|
||||||
deviceStage("build nightly", "tici-needs-can", [], [
|
deviceStage("build nightly", "tizi-needs-can", [], [
|
||||||
step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"),
|
step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'nightly-dev': {
|
'nightly-dev': {
|
||||||
deviceStage("build nightly-dev", "tici-needs-can", [], [
|
deviceStage("build nightly-dev", "tizi-needs-can", [], [
|
||||||
step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/release/build_release.sh"),
|
step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/release/build_release.sh"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
@@ -200,39 +200,30 @@ node {
|
|||||||
|
|
||||||
if (!env.BRANCH_NAME.matches(excludeRegex)) {
|
if (!env.BRANCH_NAME.matches(excludeRegex)) {
|
||||||
parallel (
|
parallel (
|
||||||
// tici tests
|
|
||||||
'onroad tests': {
|
'onroad tests': {
|
||||||
deviceStage("onroad", "tici-needs-can", ["UNSAFE=1"], [
|
deviceStage("onroad", "tizi-needs-can", ["UNSAFE=1"], [
|
||||||
step("build openpilot", "cd system/manager && ./build.py"),
|
step("build openpilot", "cd system/manager && ./build.py"),
|
||||||
step("check dirty", "release/check-dirty.sh"),
|
step("check dirty", "release/check-dirty.sh"),
|
||||||
step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]),
|
step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'HW + Unit Tests': {
|
'HW + Unit Tests': {
|
||||||
deviceStage("tici-hardware", "tici-common", ["UNSAFE=1"], [
|
deviceStage("tizi-hardware", "tizi-common", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py"),
|
step("build", "cd system/manager && ./build.py"),
|
||||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
||||||
step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"),
|
step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"),
|
||||||
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py", [diffPaths: ["system/loggerd/"]]),
|
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py", [diffPaths: ["system/loggerd/"]]),
|
||||||
step("test pigeond", "pytest system/ubloxd/tests/test_pigeond.py", [diffPaths: ["system/ubloxd/"]]),
|
|
||||||
step("test manager", "pytest system/manager/test/test_manager.py"),
|
step("test manager", "pytest system/manager/test/test_manager.py"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'loopback': {
|
'loopback': {
|
||||||
deviceStage("loopback", "tici-loopback", ["UNSAFE=1"], [
|
deviceStage("loopback", "tizi-loopback", ["UNSAFE=1"], [
|
||||||
step("build openpilot", "cd system/manager && ./build.py"),
|
step("build openpilot", "cd system/manager && ./build.py"),
|
||||||
step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'camerad AR0231': {
|
|
||||||
deviceStage("AR0231", "tici-ar0231", ["UNSAFE=1"], [
|
|
||||||
step("build", "cd system/manager && ./build.py"),
|
|
||||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
|
|
||||||
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
|
|
||||||
])
|
|
||||||
},
|
|
||||||
'camerad OX03C10': {
|
'camerad OX03C10': {
|
||||||
deviceStage("OX03C10", "tici-ox03c10", ["UNSAFE=1"], [
|
deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py"),
|
step("build", "cd system/manager && ./build.py"),
|
||||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
|
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
|
||||||
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
|
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
|
||||||
@@ -246,17 +237,13 @@ node {
|
|||||||
])
|
])
|
||||||
},
|
},
|
||||||
'sensord': {
|
'sensord': {
|
||||||
deviceStage("LSM + MMC", "tici-lsmc", ["UNSAFE=1"], [
|
deviceStage("LSM + MMC", "tizi-lsmc", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py"),
|
|
||||||
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
|
|
||||||
])
|
|
||||||
deviceStage("BMX + LSM", "tici-bmx-lsm", ["UNSAFE=1"], [
|
|
||||||
step("build", "cd system/manager && ./build.py"),
|
step("build", "cd system/manager && ./build.py"),
|
||||||
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
|
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
'replay': {
|
'replay': {
|
||||||
deviceStage("model-replay", "tici-replay", ["UNSAFE=1"], [
|
deviceStage("model-replay", "tizi-replay", ["UNSAFE=1"], [
|
||||||
step("build", "cd system/manager && ./build.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
step("build", "cd system/manager && ./build.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
||||||
step("model replay", "selfdrive/test/process_replay/model_replay.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
step("model replay", "selfdrive/test/process_replay/model_replay.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
||||||
])
|
])
|
||||||
@@ -266,7 +253,6 @@ node {
|
|||||||
step("build openpilot", "cd system/manager && ./build.py"),
|
step("build openpilot", "cd system/manager && ./build.py"),
|
||||||
step("test pandad loopback", "SINGLE_PANDA=1 pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
step("test pandad loopback", "SINGLE_PANDA=1 pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||||
step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"),
|
step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"),
|
||||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
|
||||||
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
|
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
|
||||||
// TODO: enable once new AGNOS is available
|
// TODO: enable once new AGNOS is available
|
||||||
// step("test esim", "pytest system/hardware/tici/tests/test_esim.py"),
|
// step("test esim", "pytest system/hardware/tici/tests/test_esim.py"),
|
||||||
|
|||||||
@@ -22,34 +22,24 @@ https://docs.sunnypilot.ai/ is your one stop shop for everything from features t
|
|||||||
Detailed instructions for [how to mount the device in a car](https://comma.ai/setup).
|
Detailed instructions for [how to mount the device in a car](https://comma.ai/setup).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
Please refer to [Recommended Branches](#-recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `release-c3` branch.
|
Please refer to [Recommended Branches](#-recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `staging-c3-new` branch.
|
||||||
|
|
||||||
|
### If you want to use our newest branches (our rewrite)
|
||||||
|
> [!TIP]
|
||||||
|
>You can see the rewrite state on our [rewrite project board](https://github.com/orgs/sunnypilot/projects/2), and to install the new branches, you can use the following links
|
||||||
|
|
||||||
* sunnypilot not installed or you installed a version before 0.8.17?
|
* sunnypilot not installed or you installed a version before 0.8.17?
|
||||||
1. [Factory reset/uninstall](https://github.com/commaai/openpilot/wiki/FAQ#how-can-i-reset-the-device) the previous software if you have another software/fork installed.
|
1. [Factory reset/uninstall](https://github.com/commaai/openpilot/wiki/FAQ#how-can-i-reset-the-device) the previous software if you have another software/fork installed.
|
||||||
2. After factory reset/uninstall and upon reboot, select `Custom Software` when given the option.
|
2. After factory reset/uninstall and upon reboot, select `Custom Software` when given the option.
|
||||||
3. Input the installation URL per [Recommended Branches](#-recommended-branches). Example: ```release-c3.sunnypilot.ai```.
|
3. Input the installation URL per [Recommended Branches](#-recommended-branches). Example: ```https://staging-c3-new.sunnypilot.ai```.
|
||||||
4. Complete the rest of the installation following the onscreen instructions.
|
4. Complete the rest of the installation following the onscreen instructions.
|
||||||
|
|
||||||
* sunnypilot already installed and you installed a version after 0.8.17?
|
* sunnypilot already installed and you installed a version after 0.8.17?
|
||||||
1. On the comma three, go to `Settings` ▶️ `Software`.
|
1. On the comma three, go to `Settings` ▶️ `Software`.
|
||||||
2. At the `Download` option, press `CHECK`. This will fetch the list of latest branches from sunnypilot.
|
2. At the `Download` option, press `CHECK`. This will fetch the list of latest branches from sunnypilot.
|
||||||
3. At the `Target Branch` option, press `SELECT` to open the Target Branch selector.
|
3. At the `Target Branch` option, press `SELECT` to open the Target Branch selector.
|
||||||
4. Scroll to select the desired branch per Recommended Branches (see below). Example: `release-c3`
|
4. Scroll to select the desired branch per Recommended Branches (see below). Example: `staging-c3-new`
|
||||||
|
|
||||||
| Branch | Installation URL |
|
|
||||||
|:------------:|:--------------------------------:|
|
|
||||||
| `release-c3` | https://release-c3.sunnypilot.ai |
|
|
||||||
| `staging-c3` | https://staging-c3.sunnypilot.ai |
|
|
||||||
| `dev-c3` | https://dev-c3.sunnypilot.ai |
|
|
||||||
|
|
||||||
### If you want to use our newest branches (our rewrite)
|
|
||||||
> [!TIP]
|
|
||||||
>You can see the rewrite state on our [rewrite project board](https://github.com/orgs/sunnypilot/projects/2), and to install the new branches, you can use the following links
|
|
||||||
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> It is recommended to [re-flash AGNOS](https://flash.comma.ai/) if you intend to downgrade from the new branches.
|
|
||||||
> You can still restore the latest sunnylink backup made on the old branches.
|
|
||||||
|
|
||||||
| Branch | Installation URL |
|
| Branch | Installation URL |
|
||||||
|:----------------:|:---------------------------------------------:|
|
|:----------------:|:---------------------------------------------:|
|
||||||
@@ -59,8 +49,31 @@ Please refer to [Recommended Branches](#-recommended-branches) to find your pref
|
|||||||
| `release-c3-new` | **Not yet available**. |
|
| `release-c3-new` | **Not yet available**. |
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
|
> You can use sunnypilot/targetbranch as an install URL. Example: 'sunnypilot/staging-c3-new'.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
> Do you require further assistance with software installation? Join the [sunnypilot Discord server](https://discord.sunnypilot.com) and message us in the `#installation-help` channel.
|
> Do you require further assistance with software installation? Join the [sunnypilot Discord server](https://discord.sunnypilot.com) and message us in the `#installation-help` channel.
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>Older legacy branches</summary>
|
||||||
|
|
||||||
|
### If you want to use our older legacy branches (*not recommended*)
|
||||||
|
|
||||||
|
> [**IMPORTANT**]
|
||||||
|
> It is recommended to [re-flash AGNOS](https://flash.comma.ai/) if you intend to downgrade from the new branches.
|
||||||
|
> You can still restore the latest sunnylink backup made on the old branches.
|
||||||
|
|
||||||
|
| Branch | Installation URL |
|
||||||
|
|:------------:|:--------------------------------:|
|
||||||
|
| `release-c3` | https://release-c3.sunnypilot.ai |
|
||||||
|
| `staging-c3` | https://staging-c3.sunnypilot.ai |
|
||||||
|
| `dev-c3` | https://dev-c3.sunnypilot.ai |
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
## 🎆 Pull Requests
|
## 🎆 Pull Requests
|
||||||
We welcome both pull requests and issues on GitHub. Bug fixes are encouraged.
|
We welcome both pull requests and issues on GitHub. Bug fixes are encouraged.
|
||||||
|
|
||||||
|
|||||||
+21
-4
@@ -1,11 +1,28 @@
|
|||||||
Version 0.10.0 (2025-07-07)
|
Version 0.10.1 (2025-09-08)
|
||||||
========================
|
========================
|
||||||
* New driving model
|
* New driving model
|
||||||
* Lead car ground-truth fixes
|
* World Model: removed global localization inputs
|
||||||
* Ported over VAE from the MLSIM stack
|
* World Model: 2x the number of parameters
|
||||||
* New training architecture described in CVPR paper
|
* World Model: trained on 4x the number of segments
|
||||||
|
* Driving Vision Model: trained on 4x the number of segments
|
||||||
|
* Honda City 2023 support thanks to vanillagorillaa and drFritz!
|
||||||
|
* Honda N-Box 2018 support thanks to miettal!
|
||||||
|
* Honda Odyssey 2021-25 support thanks to csouers and MVL!
|
||||||
|
|
||||||
|
Version 0.10.0 (2025-08-05)
|
||||||
|
========================
|
||||||
|
* New driving model
|
||||||
|
* New training architecture
|
||||||
|
* Described in our CVPR paper: "Learning to Drive from a World Model"
|
||||||
|
* Longitudinal MPC replaced by E2E planning from World Model in Experimental Mode
|
||||||
|
* Action from lateral MPC as training objective replaced by E2E planning from World Model
|
||||||
|
* Low-speed lead car ground-truth fixes
|
||||||
* Enable live-learned steering actuation delay
|
* Enable live-learned steering actuation delay
|
||||||
* Opt-in audio recording for dashcam video
|
* Opt-in audio recording for dashcam video
|
||||||
|
* Acura MDX 2025 support thanks to vanillagorillaa and MVL!
|
||||||
|
* Honda Accord 2023-25 support thanks to vanillagorillaa and MVL!
|
||||||
|
* Honda CR-V 2023-25 support thanks to vanillagorillaa and MVL!
|
||||||
|
* Honda Pilot 2023-25 support thanks to vanillagorillaa and MVL!
|
||||||
|
|
||||||
Version 0.9.9 (2025-05-23)
|
Version 0.9.9 (2025-05-23)
|
||||||
========================
|
========================
|
||||||
|
|||||||
+1
-6
@@ -17,7 +17,7 @@ AGNOS = TICI
|
|||||||
|
|
||||||
Decider('MD5-timestamp')
|
Decider('MD5-timestamp')
|
||||||
|
|
||||||
SetOption('num_jobs', int(os.cpu_count()/2))
|
SetOption('num_jobs', max(1, int(os.cpu_count()/2)))
|
||||||
|
|
||||||
AddOption('--kaitai',
|
AddOption('--kaitai',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
@@ -359,11 +359,6 @@ SConscript([
|
|||||||
'system/ubloxd/SConscript',
|
'system/ubloxd/SConscript',
|
||||||
'system/loggerd/SConscript',
|
'system/loggerd/SConscript',
|
||||||
])
|
])
|
||||||
if arch != "Darwin":
|
|
||||||
SConscript([
|
|
||||||
'system/logcatd/SConscript',
|
|
||||||
'system/proclogd/SConscript',
|
|
||||||
])
|
|
||||||
|
|
||||||
if arch == "larch64":
|
if arch == "larch64":
|
||||||
SConscript(['system/camerad/SConscript'])
|
SConscript(['system/camerad/SConscript'])
|
||||||
|
|||||||
+153
-2
@@ -25,6 +25,26 @@ struct ModularAssistiveDrivingSystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct IntelligentCruiseButtonManagement {
|
||||||
|
state @0 :IntelligentCruiseButtonManagementState;
|
||||||
|
sendButton @1 :SendButtonState;
|
||||||
|
vTarget @2 :Float32;
|
||||||
|
|
||||||
|
enum IntelligentCruiseButtonManagementState {
|
||||||
|
inactive @0; # No button press or default state
|
||||||
|
preActive @1; # Pre-active state before transitioning to increasing or decreasing
|
||||||
|
increasing @2; # Increasing speed
|
||||||
|
decreasing @3; # Decreasing speed
|
||||||
|
holding @4; # Holding steady speed
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SendButtonState {
|
||||||
|
none @0;
|
||||||
|
increase @1;
|
||||||
|
decrease @2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Same struct as Log.RadarState.LeadData
|
# Same struct as Log.RadarState.LeadData
|
||||||
struct LeadData {
|
struct LeadData {
|
||||||
dRel @0 :Float32;
|
dRel @0 :Float32;
|
||||||
@@ -48,6 +68,7 @@ struct LeadData {
|
|||||||
|
|
||||||
struct SelfdriveStateSP @0x81c2f05a394cf4af {
|
struct SelfdriveStateSP @0x81c2f05a394cf4af {
|
||||||
mads @0 :ModularAssistiveDrivingSystem;
|
mads @0 :ModularAssistiveDrivingSystem;
|
||||||
|
intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ModelManagerSP @0xaedffd8f31e7b55d {
|
struct ModelManagerSP @0xaedffd8f31e7b55d {
|
||||||
@@ -122,6 +143,13 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
|
|||||||
|
|
||||||
struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
|
struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
|
||||||
dec @0 :DynamicExperimentalControl;
|
dec @0 :DynamicExperimentalControl;
|
||||||
|
longitudinalPlanSource @1 :LongitudinalPlanSource;
|
||||||
|
smartCruiseControl @2 :SmartCruiseControl;
|
||||||
|
speedLimit @3 :SpeedLimit;
|
||||||
|
vTarget @4 :Float32;
|
||||||
|
aTarget @5 :Float32;
|
||||||
|
events @6 :List(OnroadEventSP.Event);
|
||||||
|
e2eAlerts @7 :E2eAlerts;
|
||||||
|
|
||||||
struct DynamicExperimentalControl {
|
struct DynamicExperimentalControl {
|
||||||
state @0 :DynamicExperimentalControlState;
|
state @0 :DynamicExperimentalControlState;
|
||||||
@@ -133,6 +161,97 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
|
|||||||
blended @1;
|
blended @1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SmartCruiseControl {
|
||||||
|
vision @0 :Vision;
|
||||||
|
map @1 :Map;
|
||||||
|
|
||||||
|
struct Vision {
|
||||||
|
state @0 :VisionState;
|
||||||
|
vTarget @1 :Float32;
|
||||||
|
aTarget @2 :Float32;
|
||||||
|
currentLateralAccel @3 :Float32;
|
||||||
|
maxPredictedLateralAccel @4 :Float32;
|
||||||
|
enabled @5 :Bool;
|
||||||
|
active @6 :Bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Map {
|
||||||
|
state @0 :MapState;
|
||||||
|
vTarget @1 :Float32;
|
||||||
|
aTarget @2 :Float32;
|
||||||
|
enabled @3 :Bool;
|
||||||
|
active @4 :Bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum VisionState {
|
||||||
|
disabled @0; # System disabled or inactive.
|
||||||
|
enabled @1; # No predicted substantial turn on vision range.
|
||||||
|
entering @2; # A substantial turn is predicted ahead, adapting speed to turn comfort levels.
|
||||||
|
turning @3; # Actively turning. Managing acceleration to provide a roll on turn feeling.
|
||||||
|
leaving @4; # Road ahead straightens. Start to allow positive acceleration.
|
||||||
|
overriding @5; # System overriding with manual control.
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MapState {
|
||||||
|
disabled @0; # System disabled or inactive.
|
||||||
|
enabled @1; # No predicted substantial turn on map range.
|
||||||
|
turning @2; # Actively turning. Managing acceleration to provide a roll on turn feeling.
|
||||||
|
overriding @3; # System overriding with manual control.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SpeedLimit {
|
||||||
|
resolver @0 :Resolver;
|
||||||
|
assist @1 :Assist;
|
||||||
|
|
||||||
|
struct Resolver {
|
||||||
|
speedLimit @0 :Float32;
|
||||||
|
distToSpeedLimit @1 :Float32;
|
||||||
|
source @2 :Source;
|
||||||
|
speedLimitOffset @3 :Float32;
|
||||||
|
speedLimitLast @4 :Float32;
|
||||||
|
speedLimitFinal @5 :Float32;
|
||||||
|
speedLimitFinalLast @6 :Float32;
|
||||||
|
speedLimitValid @7 :Bool;
|
||||||
|
speedLimitLastValid @8 :Bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Assist {
|
||||||
|
state @0 :AssistState;
|
||||||
|
enabled @1 :Bool;
|
||||||
|
active @2 :Bool;
|
||||||
|
vTarget @3 :Float32;
|
||||||
|
aTarget @4 :Float32;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Source {
|
||||||
|
none @0;
|
||||||
|
car @1;
|
||||||
|
map @2;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AssistState {
|
||||||
|
disabled @0;
|
||||||
|
inactive @1; # No speed limit set or not enabled by parameter.
|
||||||
|
preActive @2;
|
||||||
|
pending @3; # Awaiting new speed limit.
|
||||||
|
adapting @4; # Reducing speed to match new speed limit.
|
||||||
|
active @5; # Cruising at speed limit.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LongitudinalPlanSource {
|
||||||
|
cruise @0;
|
||||||
|
sccVision @1;
|
||||||
|
sccMap @2;
|
||||||
|
speedLimitAssist @3;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct E2eAlerts {
|
||||||
|
greenLightAlert @0 :Bool;
|
||||||
|
leadDepartAlert @1 :Bool;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct OnroadEventSP @0xda96579883444c35 {
|
struct OnroadEventSP @0xda96579883444c35 {
|
||||||
@@ -172,12 +291,22 @@ struct OnroadEventSP @0xda96579883444c35 {
|
|||||||
experimentalModeSwitched @14;
|
experimentalModeSwitched @14;
|
||||||
wrongCarModeAlertOnly @15;
|
wrongCarModeAlertOnly @15;
|
||||||
pedalPressedAlertOnly @16;
|
pedalPressedAlertOnly @16;
|
||||||
|
laneTurnLeft @17;
|
||||||
|
laneTurnRight @18;
|
||||||
|
speedLimitPreActive @19;
|
||||||
|
speedLimitActive @20;
|
||||||
|
speedLimitChanged @21;
|
||||||
|
speedLimitPending @22;
|
||||||
|
e2eChime @23;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CarParamsSP @0x80ae746ee2596b11 {
|
struct CarParamsSP @0x80ae746ee2596b11 {
|
||||||
flags @0 :UInt32; # flags for car specific quirks in sunnypilot
|
flags @0 :UInt32; # flags for car specific quirks in sunnypilot
|
||||||
safetyParam @1 : Int16; # flags for sunnypilot's custom safety flags
|
safetyParam @1 : Int16; # flags for sunnypilot's custom safety flags
|
||||||
|
pcmCruiseSpeed @3 :Bool;
|
||||||
|
intelligentCruiseButtonManagementAvailable @4 :Bool;
|
||||||
|
enableGasInterceptor @5 :Bool;
|
||||||
|
|
||||||
neuralNetworkLateralControl @2 :NeuralNetworkLateralControl;
|
neuralNetworkLateralControl @2 :NeuralNetworkLateralControl;
|
||||||
|
|
||||||
@@ -197,10 +326,24 @@ struct CarControlSP @0xa5cd762cd951a455 {
|
|||||||
params @1 :List(Param);
|
params @1 :List(Param);
|
||||||
leadOne @2 :LeadData;
|
leadOne @2 :LeadData;
|
||||||
leadTwo @3 :LeadData;
|
leadTwo @3 :LeadData;
|
||||||
|
intelligentCruiseButtonManagement @4 :IntelligentCruiseButtonManagement;
|
||||||
|
|
||||||
struct Param {
|
struct Param {
|
||||||
key @0 :Text;
|
key @0 :Text;
|
||||||
value @1 :Text;
|
type @2 :ParamType;
|
||||||
|
value @3 :Data;
|
||||||
|
|
||||||
|
valueDEPRECATED @1 :Text; # The data type change may cause issues with backwards compatibility.
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ParamType {
|
||||||
|
string @0;
|
||||||
|
bool @1;
|
||||||
|
int @2;
|
||||||
|
float @3;
|
||||||
|
time @4;
|
||||||
|
json @5;
|
||||||
|
bytes @6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,6 +390,7 @@ struct BackupManagerSP @0xf98d843bfd7004a3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct CarStateSP @0xb86e6369214c01c8 {
|
struct CarStateSP @0xb86e6369214c01c8 {
|
||||||
|
speedLimit @0 :Float32;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LiveMapDataSP @0xf416ec09499d9d19 {
|
struct LiveMapDataSP @0xf416ec09499d9d19 {
|
||||||
@@ -258,9 +402,16 @@ struct LiveMapDataSP @0xf416ec09499d9d19 {
|
|||||||
roadName @5 :Text;
|
roadName @5 :Text;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CustomReserved9 @0xa1680744031fdb2d {
|
struct ModelDataV2SP @0xa1680744031fdb2d {
|
||||||
|
laneTurnDirection @0 :TurnDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum TurnDirection {
|
||||||
|
none @0;
|
||||||
|
turnLeft @1;
|
||||||
|
turnRight @2;
|
||||||
|
}
|
||||||
|
|
||||||
struct CustomReserved10 @0xcb9fd56c7057593a {
|
struct CustomReserved10 @0xcb9fd56c7057593a {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-6
@@ -127,8 +127,9 @@ struct OnroadEvent @0xc4fa6047f024e718 {
|
|||||||
espActive @90;
|
espActive @90;
|
||||||
personalityChanged @91;
|
personalityChanged @91;
|
||||||
aeb @92;
|
aeb @92;
|
||||||
userFlag @95;
|
userBookmark @95;
|
||||||
excessiveActuation @96;
|
excessiveActuation @96;
|
||||||
|
audioFeedback @97;
|
||||||
|
|
||||||
soundsUnavailableDEPRECATED @47;
|
soundsUnavailableDEPRECATED @47;
|
||||||
}
|
}
|
||||||
@@ -584,7 +585,6 @@ struct PandaState @0xa7649e2575e4591e {
|
|||||||
heartbeatLost @22 :Bool;
|
heartbeatLost @22 :Bool;
|
||||||
interruptLoad @25 :Float32;
|
interruptLoad @25 :Float32;
|
||||||
fanPower @28 :UInt8;
|
fanPower @28 :UInt8;
|
||||||
fanStallCount @34 :UInt8;
|
|
||||||
|
|
||||||
spiErrorCount @33 :UInt16;
|
spiErrorCount @33 :UInt16;
|
||||||
|
|
||||||
@@ -713,6 +713,7 @@ struct PandaState @0xa7649e2575e4591e {
|
|||||||
usbPowerModeDEPRECATED @12 :PeripheralState.UsbPowerModeDEPRECATED;
|
usbPowerModeDEPRECATED @12 :PeripheralState.UsbPowerModeDEPRECATED;
|
||||||
safetyParamDEPRECATED @20 :Int16;
|
safetyParamDEPRECATED @20 :Int16;
|
||||||
safetyParam2DEPRECATED @26 :UInt32;
|
safetyParam2DEPRECATED @26 :UInt32;
|
||||||
|
fanStallCountDEPRECATED @34 :UInt8;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PeripheralState {
|
struct PeripheralState {
|
||||||
@@ -2468,7 +2469,7 @@ struct DebugAlert {
|
|||||||
alertText2 @1 :Text;
|
alertText2 @1 :Text;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UserFlag {
|
struct UserBookmark @0xfe346a9de48d9b50 {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SoundPressure @0xdc24138990726023 {
|
struct SoundPressure @0xdc24138990726023 {
|
||||||
@@ -2486,6 +2487,11 @@ struct AudioData {
|
|||||||
sampleRate @1 :UInt32;
|
sampleRate @1 :UInt32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct AudioFeedback {
|
||||||
|
audio @0 :AudioData;
|
||||||
|
blockNum @1 :UInt16;
|
||||||
|
}
|
||||||
|
|
||||||
struct Touch {
|
struct Touch {
|
||||||
sec @0 :Int64;
|
sec @0 :Int64;
|
||||||
usec @1 :Int64;
|
usec @1 :Int64;
|
||||||
@@ -2586,9 +2592,13 @@ struct Event {
|
|||||||
mapRenderState @105: MapRenderState;
|
mapRenderState @105: MapRenderState;
|
||||||
|
|
||||||
# UI services
|
# UI services
|
||||||
userFlag @93 :UserFlag;
|
|
||||||
uiDebug @102 :UIDebug;
|
uiDebug @102 :UIDebug;
|
||||||
|
|
||||||
|
# driving feedback
|
||||||
|
userBookmark @93 :UserBookmark;
|
||||||
|
bookmarkButton @148 :UserBookmark;
|
||||||
|
audioFeedback @149 :AudioFeedback;
|
||||||
|
|
||||||
# *********** debug ***********
|
# *********** debug ***********
|
||||||
testJoystick @52 :Joystick;
|
testJoystick @52 :Joystick;
|
||||||
roadEncodeData @86 :EncodeData;
|
roadEncodeData @86 :EncodeData;
|
||||||
@@ -2621,7 +2631,7 @@ struct Event {
|
|||||||
backupManagerSP @113 :Custom.BackupManagerSP;
|
backupManagerSP @113 :Custom.BackupManagerSP;
|
||||||
carStateSP @114 :Custom.CarStateSP;
|
carStateSP @114 :Custom.CarStateSP;
|
||||||
liveMapDataSP @115 :Custom.LiveMapDataSP;
|
liveMapDataSP @115 :Custom.LiveMapDataSP;
|
||||||
customReserved9 @116 :Custom.CustomReserved9;
|
modelDataV2SP @116 :Custom.ModelDataV2SP;
|
||||||
customReserved10 @136 :Custom.CustomReserved10;
|
customReserved10 @136 :Custom.CustomReserved10;
|
||||||
customReserved11 @137 :Custom.CustomReserved11;
|
customReserved11 @137 :Custom.CustomReserved11;
|
||||||
customReserved12 @138 :Custom.CustomReserved12;
|
customReserved12 @138 :Custom.CustomReserved12;
|
||||||
@@ -2674,7 +2684,7 @@ struct Event {
|
|||||||
lateralPlanDEPRECATED @64 :LateralPlan;
|
lateralPlanDEPRECATED @64 :LateralPlan;
|
||||||
navModelDEPRECATED @104 :NavModelData;
|
navModelDEPRECATED @104 :NavModelData;
|
||||||
uiPlanDEPRECATED @106 :UiPlan;
|
uiPlanDEPRECATED @106 :UiPlan;
|
||||||
liveLocationKalmanDEPRECATED @72 :LiveLocationKalman;
|
liveLocationKalman @72 :LiveLocationKalman;
|
||||||
liveTracksDEPRECATED @16 :List(LiveTracksDEPRECATED);
|
liveTracksDEPRECATED @16 :List(LiveTracksDEPRECATED);
|
||||||
onroadEventsDEPRECATED @68: List(Car.OnroadEventDEPRECATED);
|
onroadEventsDEPRECATED @68: List(Car.OnroadEventDEPRECATED);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ MessageContext message_context;
|
|||||||
struct SubMaster::SubMessage {
|
struct SubMaster::SubMessage {
|
||||||
std::string name;
|
std::string name;
|
||||||
SubSocket *socket = nullptr;
|
SubSocket *socket = nullptr;
|
||||||
int freq = 0;
|
float freq = 0.0f;
|
||||||
bool updated = false, alive = false, valid = false, 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;
|
||||||
|
|||||||
@@ -177,8 +177,8 @@ class TestMessaging:
|
|||||||
|
|
||||||
# wait 5 socket timeouts before sending
|
# wait 5 socket timeouts before sending
|
||||||
msg = random_carstate()
|
msg = random_carstate()
|
||||||
delayed_send(sock_timeout*5, pub_sock, msg.to_bytes())
|
|
||||||
start_time = time.monotonic()
|
start_time = time.monotonic()
|
||||||
|
delayed_send(sock_timeout*5, pub_sock, msg.to_bytes())
|
||||||
recvd = messaging.recv_one_retry(sub_sock)
|
recvd = messaging.recv_one_retry(sub_sock)
|
||||||
assert (time.monotonic() - start_time) >= sock_timeout*5
|
assert (time.monotonic() - start_time) >= sock_timeout*5
|
||||||
assert isinstance(recvd, capnp._DynamicStructReader)
|
assert isinstance(recvd, capnp._DynamicStructReader)
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class TestSubMaster:
|
|||||||
"cameraOdometry": (20, 10),
|
"cameraOdometry": (20, 10),
|
||||||
"liveCalibration": (4, 4),
|
"liveCalibration": (4, 4),
|
||||||
"carParams": (None, None),
|
"carParams": (None, None),
|
||||||
"userFlag": (None, None),
|
"userBookmark": (None, None),
|
||||||
}
|
}
|
||||||
|
|
||||||
for service, (max_freq, min_freq) in checks.items():
|
for service, (max_freq, min_freq) in checks.items():
|
||||||
|
|||||||
+7
-3
@@ -72,9 +72,11 @@ _services: dict[str, tuple] = {
|
|||||||
"navRoute": (True, 0.),
|
"navRoute": (True, 0.),
|
||||||
"navThumbnail": (True, 0.),
|
"navThumbnail": (True, 0.),
|
||||||
"qRoadEncodeIdx": (False, 20.),
|
"qRoadEncodeIdx": (False, 20.),
|
||||||
"userFlag": (True, 0., 1),
|
"userBookmark": (True, 0., 1),
|
||||||
"soundPressure": (True, 10., 10),
|
"soundPressure": (True, 10., 10),
|
||||||
"rawAudioData": (False, 20.),
|
"rawAudioData": (False, 20.),
|
||||||
|
"bookmarkButton": (True, 0., 1),
|
||||||
|
"audioFeedback": (True, 0., 1),
|
||||||
|
|
||||||
# sunnypilot
|
# sunnypilot
|
||||||
"modelManagerSP": (False, 1., 1),
|
"modelManagerSP": (False, 1., 1),
|
||||||
@@ -86,6 +88,8 @@ _services: dict[str, tuple] = {
|
|||||||
"carControlSP": (True, 100., 10),
|
"carControlSP": (True, 100., 10),
|
||||||
"carStateSP": (True, 100., 10),
|
"carStateSP": (True, 100., 10),
|
||||||
"liveMapDataSP": (True, 1., 1),
|
"liveMapDataSP": (True, 1., 1),
|
||||||
|
"modelDataV2SP": (True, 20.),
|
||||||
|
"liveLocationKalman": (True, 20.),
|
||||||
|
|
||||||
# debug
|
# debug
|
||||||
"uiDebug": (True, 0., 1),
|
"uiDebug": (True, 0., 1),
|
||||||
@@ -118,12 +122,12 @@ def build_header():
|
|||||||
h += "#include <map>\n"
|
h += "#include <map>\n"
|
||||||
h += "#include <string>\n"
|
h += "#include <string>\n"
|
||||||
|
|
||||||
h += "struct service { std::string name; bool should_log; int frequency; int decimation; };\n"
|
h += "struct service { std::string name; bool should_log; float frequency; int decimation; };\n"
|
||||||
h += "static std::map<std::string, service> services = {\n"
|
h += "static std::map<std::string, service> services = {\n"
|
||||||
for k, v in SERVICE_LIST.items():
|
for k, v in SERVICE_LIST.items():
|
||||||
should_log = "true" if v.should_log else "false"
|
should_log = "true" if v.should_log else "false"
|
||||||
decimation = -1 if v.decimation is None else v.decimation
|
decimation = -1 if v.decimation is None else v.decimation
|
||||||
h += ' { "%s", {"%s", %s, %d, %d}},\n' % \
|
h += ' { "%s", {"%s", %s, %f, %d}},\n' % \
|
||||||
(k, k, should_log, v.frequency, decimation)
|
(k, k, should_log, v.frequency, decimation)
|
||||||
h += "};\n"
|
h += "};\n"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
class Conversions:
|
# conversions
|
||||||
|
class CV:
|
||||||
# Speed
|
# Speed
|
||||||
MPH_TO_KPH = 1.609344
|
MPH_TO_KPH = 1.609344
|
||||||
KPH_TO_MPH = 1. / MPH_TO_KPH
|
KPH_TO_MPH = 1. / MPH_TO_KPH
|
||||||
@@ -17,3 +18,6 @@ class Conversions:
|
|||||||
|
|
||||||
# Mass
|
# Mass
|
||||||
LB_TO_KG = 0.453592
|
LB_TO_KG = 0.453592
|
||||||
|
|
||||||
|
|
||||||
|
ACCELERATION_DUE_TO_GRAVITY = 9.81 # m/s^2
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
#define DEFAULT_MODEL "Space Lab 2 (Default)"
|
#define DEFAULT_MODEL "Firehose (Default)"
|
||||||
|
|||||||
+52
-15
@@ -12,7 +12,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"ApiCache_Device", {PERSISTENT, STRING}},
|
{"ApiCache_Device", {PERSISTENT, STRING}},
|
||||||
{"ApiCache_FirehoseStats", {PERSISTENT, JSON}},
|
{"ApiCache_FirehoseStats", {PERSISTENT, JSON}},
|
||||||
{"AssistNowToken", {PERSISTENT, STRING}},
|
{"AssistNowToken", {PERSISTENT, STRING}},
|
||||||
{"AthenadPid", {PERSISTENT, STRING}},
|
{"AthenadPid", {PERSISTENT, INT}},
|
||||||
{"AthenadUploadQueue", {PERSISTENT, JSON}},
|
{"AthenadUploadQueue", {PERSISTENT, JSON}},
|
||||||
{"AthenadRecentlyViewedRoutes", {PERSISTENT, STRING}},
|
{"AthenadRecentlyViewedRoutes", {PERSISTENT, STRING}},
|
||||||
{"BootCount", {PERSISTENT, INT}},
|
{"BootCount", {PERSISTENT, INT}},
|
||||||
@@ -73,9 +73,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"LastOffroadStatusPacket", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, JSON}},
|
{"LastOffroadStatusPacket", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, JSON}},
|
||||||
{"LastPowerDropDetected", {CLEAR_ON_MANAGER_START, STRING}},
|
{"LastPowerDropDetected", {CLEAR_ON_MANAGER_START, STRING}},
|
||||||
{"LastUpdateException", {CLEAR_ON_MANAGER_START, STRING}},
|
{"LastUpdateException", {CLEAR_ON_MANAGER_START, STRING}},
|
||||||
{"LastUpdateRouteCount", {PERSISTENT, INT}},
|
{"LastUpdateRouteCount", {PERSISTENT, INT, "0"}},
|
||||||
{"LastUpdateTime", {PERSISTENT, TIME}},
|
{"LastUpdateTime", {PERSISTENT, TIME}},
|
||||||
{"LastUpdateUptimeOnroad", {PERSISTENT, FLOAT}},
|
{"LastUpdateUptimeOnroad", {PERSISTENT, FLOAT, "0.0"}},
|
||||||
{"LiveDelay", {PERSISTENT | BACKUP, BYTES}},
|
{"LiveDelay", {PERSISTENT | BACKUP, BYTES}},
|
||||||
{"LiveParameters", {PERSISTENT, JSON}},
|
{"LiveParameters", {PERSISTENT, JSON}},
|
||||||
{"LiveParametersV2", {PERSISTENT, BYTES}},
|
{"LiveParametersV2", {PERSISTENT, BYTES}},
|
||||||
@@ -94,7 +94,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"Offroad_NeosUpdate", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_NeosUpdate", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"Offroad_NoFirmware", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
{"Offroad_NoFirmware", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
||||||
{"Offroad_Recalibration", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
{"Offroad_Recalibration", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
||||||
{"Offroad_StorageMissing", {CLEAR_ON_MANAGER_START, JSON}},
|
|
||||||
{"Offroad_TemperatureTooHigh", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_TemperatureTooHigh", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"Offroad_UnregisteredHardware", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_UnregisteredHardware", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"Offroad_UpdateFailed", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_UpdateFailed", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
@@ -105,6 +104,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"PandaSignatures", {CLEAR_ON_MANAGER_START, BYTES}},
|
{"PandaSignatures", {CLEAR_ON_MANAGER_START, BYTES}},
|
||||||
{"PrimeType", {PERSISTENT, INT}},
|
{"PrimeType", {PERSISTENT, INT}},
|
||||||
{"RecordAudio", {PERSISTENT | BACKUP, BOOL}},
|
{"RecordAudio", {PERSISTENT | BACKUP, BOOL}},
|
||||||
|
{"RecordAudioFeedback", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"RecordFront", {PERSISTENT | BACKUP, BOOL}},
|
{"RecordFront", {PERSISTENT | BACKUP, BOOL}},
|
||||||
{"RecordFrontLock", {PERSISTENT, BOOL}}, // for the internal fleet
|
{"RecordFrontLock", {PERSISTENT, BOOL}}, // for the internal fleet
|
||||||
{"SecOCKey", {PERSISTENT | DONT_LOG | BACKUP, STRING}},
|
{"SecOCKey", {PERSISTENT | DONT_LOG | BACKUP, STRING}},
|
||||||
@@ -139,21 +139,34 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"CarParamsSP", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BYTES}},
|
{"CarParamsSP", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BYTES}},
|
||||||
{"CarParamsSPCache", {CLEAR_ON_MANAGER_START, BYTES}},
|
{"CarParamsSPCache", {CLEAR_ON_MANAGER_START, BYTES}},
|
||||||
{"CarParamsSPPersistent", {PERSISTENT, BYTES}},
|
{"CarParamsSPPersistent", {PERSISTENT, BYTES}},
|
||||||
{"CarPlatformBundle", {PERSISTENT | BACKUP, STRING}},
|
{"CarPlatformBundle", {PERSISTENT | BACKUP, JSON}},
|
||||||
{"ChevronInfo", {PERSISTENT | BACKUP, INT, "4"}},
|
{"ChevronInfo", {PERSISTENT | BACKUP, INT, "4"}},
|
||||||
{"CustomAccIncrementsEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"CustomAccIncrementsEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"CustomAccLongPressIncrement", {PERSISTENT | BACKUP, INT, "5"}},
|
{"CustomAccLongPressIncrement", {PERSISTENT | BACKUP, INT, "5"}},
|
||||||
{"CustomAccShortPressIncrement", {PERSISTENT | BACKUP, INT, "1"}},
|
{"CustomAccShortPressIncrement", {PERSISTENT | BACKUP, INT, "1"}},
|
||||||
{"DeviceBootMode", {PERSISTENT | BACKUP, INT, "0"}},
|
{"DeviceBootMode", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
|
{"DevUIInfo", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
|
{"EnableCopyparty", {PERSISTENT | BACKUP, BOOL}},
|
||||||
{"EnableGithubRunner", {PERSISTENT | BACKUP, BOOL}},
|
{"EnableGithubRunner", {PERSISTENT | BACKUP, BOOL}},
|
||||||
|
{"GreenLightAlert", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
{"GithubRunnerSufficientVoltage", {CLEAR_ON_MANAGER_START , BOOL}},
|
||||||
|
{"IntelligentCruiseButtonManagement", {PERSISTENT | BACKUP , BOOL}},
|
||||||
{"InteractivityTimeout", {PERSISTENT | BACKUP, INT, "0"}},
|
{"InteractivityTimeout", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
{"IsDevelopmentBranch", {CLEAR_ON_MANAGER_START, BOOL}},
|
{"IsDevelopmentBranch", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
|
{"LastGPSPositionLLK", {PERSISTENT, STRING}},
|
||||||
|
{"LeadDepartAlert", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"MaxTimeOffroad", {PERSISTENT | BACKUP, INT, "1800"}},
|
{"MaxTimeOffroad", {PERSISTENT | BACKUP, INT, "1800"}},
|
||||||
{"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}},
|
{"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}},
|
||||||
{"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}},
|
{"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
|
{"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
|
{"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "100"}},
|
||||||
|
{"OnroadScreenOffControl", {PERSISTENT | BACKUP, BOOL}},
|
||||||
|
{"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
{"RainbowMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
{"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
|
||||||
// MADS params
|
// MADS params
|
||||||
{"Mads", {PERSISTENT | BACKUP, BOOL, "1"}},
|
{"Mads", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||||
@@ -162,11 +175,12 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"MadsUnifiedEngagementMode", {PERSISTENT | BACKUP, BOOL, "1"}},
|
{"MadsUnifiedEngagementMode", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||||
|
|
||||||
// Model Manager params
|
// Model Manager params
|
||||||
{"ModelManager_ActiveBundle", {PERSISTENT, STRING}},
|
{"ModelManager_ActiveBundle", {PERSISTENT, JSON}},
|
||||||
{"ModelManager_ClearCache", {CLEAR_ON_MANAGER_START, BOOL}},
|
{"ModelManager_ClearCache", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
{"ModelManager_DownloadIndex", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, INT, "0"}},
|
{"ModelManager_DownloadIndex", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, INT, "0"}},
|
||||||
|
{"ModelManager_Favs", {PERSISTENT | BACKUP, STRING}},
|
||||||
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
||||||
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, STRING}},
|
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
|
||||||
|
|
||||||
// Neural Network Lateral Control
|
// Neural Network Lateral Control
|
||||||
{"NeuralNetworkLateralControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"NeuralNetworkLateralControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
@@ -177,8 +191,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"SunnylinkCache_Roles", {PERSISTENT, STRING}},
|
{"SunnylinkCache_Roles", {PERSISTENT, STRING}},
|
||||||
{"SunnylinkCache_Users", {PERSISTENT, STRING}},
|
{"SunnylinkCache_Users", {PERSISTENT, STRING}},
|
||||||
{"SunnylinkDongleId", {PERSISTENT, STRING}},
|
{"SunnylinkDongleId", {PERSISTENT, STRING}},
|
||||||
{"SunnylinkdPid", {PERSISTENT, STRING}},
|
{"SunnylinkdPid", {PERSISTENT, INT}},
|
||||||
{"SunnylinkEnabled", {PERSISTENT, BOOL}},
|
{"SunnylinkEnabled", {PERSISTENT, BOOL, "1"}},
|
||||||
|
|
||||||
// Backup Manager params
|
// Backup Manager params
|
||||||
{"BackupManager_CreateBackup", {PERSISTENT, BOOL}},
|
{"BackupManager_CreateBackup", {PERSISTENT, BOOL}},
|
||||||
@@ -190,21 +204,23 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
|
||||||
// model panel params
|
// sunnypilot model params
|
||||||
{"LagdToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
|
{"LagdToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||||
{"LagdToggleDesc", {PERSISTENT, STRING}},
|
|
||||||
{"LagdToggleDelay", {PERSISTENT | BACKUP, FLOAT, "0.2"}},
|
{"LagdToggleDelay", {PERSISTENT | BACKUP, FLOAT, "0.2"}},
|
||||||
|
{"LagdValueCache", {PERSISTENT, FLOAT, "0.2"}},
|
||||||
|
{"LaneTurnDesire", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
{"LaneTurnValue", {PERSISTENT | BACKUP, FLOAT, "19.0"}},
|
||||||
|
|
||||||
// mapd
|
// mapd
|
||||||
{"MapAdvisorySpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT}},
|
{"MapAdvisorySpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT}},
|
||||||
{"MapdVersion", {PERSISTENT, STRING, ""}},
|
{"MapdVersion", {PERSISTENT, STRING}},
|
||||||
{"MapSpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT, "0.0"}},
|
{"MapSpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT, "0.0"}},
|
||||||
{"NextMapSpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
{"NextMapSpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, JSON}},
|
||||||
{"Offroad_OSMUpdateRequired", {CLEAR_ON_MANAGER_START, JSON}},
|
{"Offroad_OSMUpdateRequired", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"OsmDbUpdatesCheck", {CLEAR_ON_MANAGER_START, BOOL}}, // mapd database update happens with device ON, reset on boot
|
{"OsmDbUpdatesCheck", {CLEAR_ON_MANAGER_START, BOOL}}, // mapd database update happens with device ON, reset on boot
|
||||||
{"OSMDownloadBounds", {PERSISTENT, STRING}},
|
{"OSMDownloadBounds", {PERSISTENT, STRING}},
|
||||||
{"OsmDownloadedDate", {PERSISTENT, STRING, "0.0"}},
|
{"OsmDownloadedDate", {PERSISTENT, STRING, "0.0"}},
|
||||||
{"OSMDownloadLocations", {PERSISTENT, STRING}},
|
{"OSMDownloadLocations", {PERSISTENT, JSON}},
|
||||||
{"OSMDownloadProgress", {CLEAR_ON_MANAGER_START, JSON}},
|
{"OSMDownloadProgress", {CLEAR_ON_MANAGER_START, JSON}},
|
||||||
{"OsmLocal", {PERSISTENT, BOOL}},
|
{"OsmLocal", {PERSISTENT, BOOL}},
|
||||||
{"OsmLocationName", {PERSISTENT, STRING}},
|
{"OsmLocationName", {PERSISTENT, STRING}},
|
||||||
@@ -213,5 +229,26 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"OsmStateName", {PERSISTENT, STRING, "All"}},
|
{"OsmStateName", {PERSISTENT, STRING, "All"}},
|
||||||
{"OsmStateTitle", {PERSISTENT, STRING}},
|
{"OsmStateTitle", {PERSISTENT, STRING}},
|
||||||
{"OsmWayTest", {PERSISTENT, STRING}},
|
{"OsmWayTest", {PERSISTENT, STRING}},
|
||||||
{"RoadName", {CLEAR_ON_ONROAD_TRANSITION, STRING, ""}},
|
{"RoadName", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
||||||
|
{"RoadNameToggle", {PERSISTENT, STRING}},
|
||||||
|
|
||||||
|
// Speed Limit
|
||||||
|
{"SpeedLimitMode", {PERSISTENT | BACKUP, INT, "1"}},
|
||||||
|
{"SpeedLimitOffsetType", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
|
{"SpeedLimitPolicy", {PERSISTENT | BACKUP, INT, "3"}},
|
||||||
|
{"SpeedLimitValueOffset", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
|
|
||||||
|
// Smart Cruise Control
|
||||||
|
{"MapTargetVelocities", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
||||||
|
{"SmartCruiseControlMap", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
{"SmartCruiseControlVision", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
|
||||||
|
// Torque lateral control custom params
|
||||||
|
{"CustomTorqueParams", {PERSISTENT | BACKUP , BOOL}},
|
||||||
|
{"EnforceTorqueControl", {PERSISTENT | BACKUP, BOOL}},
|
||||||
|
{"LiveTorqueParamsToggle", {PERSISTENT | BACKUP , BOOL}},
|
||||||
|
{"LiveTorqueParamsRelaxedToggle", {PERSISTENT | BACKUP , BOOL}},
|
||||||
|
{"TorqueParamsOverrideEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
{"TorqueParamsOverrideFriction", {PERSISTENT | BACKUP, FLOAT, "0.1"}},
|
||||||
|
{"TorqueParamsOverrideLatAccelFactor", {PERSISTENT | BACKUP, FLOAT, "2.5"}},
|
||||||
};
|
};
|
||||||
|
|||||||
+10
-5
@@ -103,14 +103,14 @@ cdef class Params:
|
|||||||
return cast(value)
|
return cast(value)
|
||||||
raise TypeError(f"Type mismatch while writing param {key}: {proposed_type=} {expected_type=} {value=}")
|
raise TypeError(f"Type mismatch while writing param {key}: {proposed_type=} {expected_type=} {value=}")
|
||||||
|
|
||||||
def cpp2python(self, t, value, default, key):
|
def _cpp2python(self, t, value, default, key):
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
return CPP_2_PYTHON[t](value)
|
return CPP_2_PYTHON[t](value)
|
||||||
except (KeyError, TypeError, ValueError):
|
except (KeyError, TypeError, ValueError):
|
||||||
cloudlog.warning(f"Failed to cast param {key} with {value=} from type {t=}")
|
cloudlog.warning(f"Failed to cast param {key} with {value=} from type {t=}")
|
||||||
return self.cpp2python(t, default, None, key)
|
return self._cpp2python(t, default, None, key)
|
||||||
|
|
||||||
def get(self, key, bool block=False, bool return_default=False):
|
def get(self, key, bool block=False, bool return_default=False):
|
||||||
cdef string k = self.check_key(key)
|
cdef string k = self.check_key(key)
|
||||||
@@ -127,8 +127,8 @@ cdef class Params:
|
|||||||
# it means we got an interrupt while waiting
|
# it means we got an interrupt while waiting
|
||||||
raise KeyboardInterrupt
|
raise KeyboardInterrupt
|
||||||
else:
|
else:
|
||||||
return self.cpp2python(t, default_val, None, key)
|
return self._cpp2python(t, default_val, None, key)
|
||||||
return self.cpp2python(t, val, default_val, key)
|
return self._cpp2python(t, val, default_val, key)
|
||||||
|
|
||||||
def get_bool(self, key, bool block=False):
|
def get_bool(self, key, bool block=False):
|
||||||
cdef string k = self.check_key(key)
|
cdef string k = self.check_key(key)
|
||||||
@@ -189,4 +189,9 @@ cdef class Params:
|
|||||||
cdef string k = self.check_key(key)
|
cdef string k = self.check_key(key)
|
||||||
cdef ParamKeyType t = self.p.getKeyType(k)
|
cdef ParamKeyType t = self.p.getKeyType(k)
|
||||||
cdef optional[string] default = self.p.getKeyDefaultValue(k)
|
cdef optional[string] default = self.p.getKeyDefaultValue(k)
|
||||||
return self.cpp2python(t, default.value(), None, key) if default.has_value() else None
|
return self._cpp2python(t, default.value(), None, key) if default.has_value() else None
|
||||||
|
|
||||||
|
def cpp2python(self, key, value):
|
||||||
|
cdef string k = self.check_key(key)
|
||||||
|
cdef ParamKeyType t = self.p.getKeyType(k)
|
||||||
|
return self._cpp2python(t, value, None, key)
|
||||||
|
|||||||
+13
-19
@@ -14,10 +14,8 @@ class PIDController:
|
|||||||
if isinstance(self._k_d, Number):
|
if isinstance(self._k_d, Number):
|
||||||
self._k_d = [[0], [self._k_d]]
|
self._k_d = [[0], [self._k_d]]
|
||||||
|
|
||||||
self.pos_limit = pos_limit
|
self.set_limits(pos_limit, neg_limit)
|
||||||
self.neg_limit = neg_limit
|
|
||||||
|
|
||||||
self.i_unwind_rate = 0.3 / rate
|
|
||||||
self.i_rate = 1.0 / rate
|
self.i_rate = 1.0 / rate
|
||||||
self.speed = 0.0
|
self.speed = 0.0
|
||||||
|
|
||||||
@@ -35,10 +33,6 @@ class PIDController:
|
|||||||
def k_d(self):
|
def k_d(self):
|
||||||
return np.interp(self.speed, self._k_d[0], self._k_d[1])
|
return np.interp(self.speed, self._k_d[0], self._k_d[1])
|
||||||
|
|
||||||
@property
|
|
||||||
def error_integral(self):
|
|
||||||
return self.i/self.k_i
|
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.p = 0.0
|
self.p = 0.0
|
||||||
self.i = 0.0
|
self.i = 0.0
|
||||||
@@ -46,25 +40,25 @@ class PIDController:
|
|||||||
self.f = 0.0
|
self.f = 0.0
|
||||||
self.control = 0
|
self.control = 0
|
||||||
|
|
||||||
def update(self, error, error_rate=0.0, speed=0.0, override=False, feedforward=0., freeze_integrator=False):
|
def set_limits(self, pos_limit, neg_limit):
|
||||||
self.speed = speed
|
self.pos_limit = pos_limit
|
||||||
|
self.neg_limit = neg_limit
|
||||||
|
|
||||||
|
def update(self, error, error_rate=0.0, speed=0.0, feedforward=0., freeze_integrator=False):
|
||||||
|
self.speed = speed
|
||||||
self.p = float(error) * self.k_p
|
self.p = float(error) * self.k_p
|
||||||
self.f = feedforward * self.k_f
|
self.f = feedforward * self.k_f
|
||||||
self.d = error_rate * self.k_d
|
self.d = error_rate * self.k_d
|
||||||
|
|
||||||
if override:
|
if not freeze_integrator:
|
||||||
self.i -= self.i_unwind_rate * float(np.sign(self.i))
|
i = self.i + error * self.k_i * self.i_rate
|
||||||
else:
|
|
||||||
if not freeze_integrator:
|
|
||||||
self.i = self.i + error * self.k_i * self.i_rate
|
|
||||||
|
|
||||||
# Clip i to prevent exceeding control limits
|
# Don't allow windup if already clipping
|
||||||
control_no_i = self.p + self.d + self.f
|
test_control = self.p + i + self.d + self.f
|
||||||
control_no_i = np.clip(control_no_i, self.neg_limit, self.pos_limit)
|
i_upperbound = self.i if test_control > self.pos_limit else self.pos_limit
|
||||||
self.i = np.clip(self.i, self.neg_limit - control_no_i, self.pos_limit - control_no_i)
|
i_lowerbound = self.i if test_control < self.neg_limit else self.neg_limit
|
||||||
|
self.i = np.clip(i, i_lowerbound, i_upperbound)
|
||||||
|
|
||||||
control = self.p + self.i + self.d + self.f
|
control = self.p + self.i + self.d + self.f
|
||||||
|
|
||||||
self.control = np.clip(control, self.neg_limit, self.pos_limit)
|
self.control = np.clip(control, self.neg_limit, self.pos_limit)
|
||||||
return self.control
|
return self.control
|
||||||
|
|||||||
+12
-6
@@ -9,20 +9,19 @@ from openpilot.system.hardware.hw import Paths
|
|||||||
from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT
|
from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT
|
||||||
|
|
||||||
class OpenpilotPrefix:
|
class OpenpilotPrefix:
|
||||||
def __init__(self, prefix: str = None, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False):
|
def __init__(self, prefix: str = None, create_dirs_on_enter: bool = True, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False):
|
||||||
self.prefix = prefix if prefix else str(uuid.uuid4().hex[0:15])
|
self.prefix = prefix if prefix else str(uuid.uuid4().hex[0:15])
|
||||||
self.msgq_path = os.path.join(Paths.shm_path(), self.prefix)
|
self.msgq_path = os.path.join(Paths.shm_path(), self.prefix)
|
||||||
|
self.create_dirs_on_enter = create_dirs_on_enter
|
||||||
self.clean_dirs_on_exit = clean_dirs_on_exit
|
self.clean_dirs_on_exit = clean_dirs_on_exit
|
||||||
self.shared_download_cache = shared_download_cache
|
self.shared_download_cache = shared_download_cache
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.original_prefix = os.environ.get('OPENPILOT_PREFIX', None)
|
self.original_prefix = os.environ.get('OPENPILOT_PREFIX', None)
|
||||||
os.environ['OPENPILOT_PREFIX'] = self.prefix
|
os.environ['OPENPILOT_PREFIX'] = self.prefix
|
||||||
try:
|
|
||||||
os.mkdir(self.msgq_path)
|
if self.create_dirs_on_enter:
|
||||||
except FileExistsError:
|
self.create_dirs()
|
||||||
pass
|
|
||||||
os.makedirs(Paths.log_root(), exist_ok=True)
|
|
||||||
|
|
||||||
if self.shared_download_cache:
|
if self.shared_download_cache:
|
||||||
os.environ["COMMA_CACHE"] = DEFAULT_DOWNLOAD_CACHE_ROOT
|
os.environ["COMMA_CACHE"] = DEFAULT_DOWNLOAD_CACHE_ROOT
|
||||||
@@ -40,6 +39,13 @@ class OpenpilotPrefix:
|
|||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def create_dirs(self):
|
||||||
|
try:
|
||||||
|
os.mkdir(self.msgq_path)
|
||||||
|
except FileExistsError:
|
||||||
|
pass
|
||||||
|
os.makedirs(Paths.log_root(), exist_ok=True)
|
||||||
|
|
||||||
def clean_dirs(self):
|
def clean_dirs(self):
|
||||||
symlink_path = Params().get_param_path()
|
symlink_path = Params().get_param_path()
|
||||||
if os.path.exists(symlink_path):
|
if os.path.exists(symlink_path):
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from subprocess import Popen, PIPE, TimeoutExpired
|
||||||
|
|
||||||
|
|
||||||
def run_cmd(cmd: list[str], cwd=None, env=None) -> str:
|
def run_cmd(cmd: list[str], cwd=None, env=None) -> str:
|
||||||
@@ -11,3 +13,16 @@ def run_cmd_default(cmd: list[str], default: str = "", cwd=None, env=None) -> st
|
|||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def managed_proc(cmd: list[str], env: dict[str, str]):
|
||||||
|
proc = Popen(cmd, env=env, stdout=PIPE, stderr=PIPE)
|
||||||
|
try:
|
||||||
|
yield proc
|
||||||
|
finally:
|
||||||
|
if proc.poll() is None:
|
||||||
|
proc.terminate()
|
||||||
|
try:
|
||||||
|
proc.wait(timeout=5)
|
||||||
|
except TimeoutExpired:
|
||||||
|
proc.kill()
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ class TestParams:
|
|||||||
|
|
||||||
def test_params_two_things(self):
|
def test_params_two_things(self):
|
||||||
self.params.put("DongleId", "bob")
|
self.params.put("DongleId", "bob")
|
||||||
self.params.put("AthenadPid", "123")
|
self.params.put("AthenadPid", 123)
|
||||||
assert self.params.get("DongleId") == "bob"
|
assert self.params.get("DongleId") == "bob"
|
||||||
assert self.params.get("AthenadPid") == "123"
|
assert self.params.get("AthenadPid") == 123
|
||||||
|
|
||||||
def test_params_get_block(self):
|
def test_params_get_block(self):
|
||||||
def _delayed_writer():
|
def _delayed_writer():
|
||||||
|
|||||||
+7
-1
@@ -1,4 +1,5 @@
|
|||||||
#include "common/util.h"
|
#include "common/util.h"
|
||||||
|
#include "common/swaglog.h"
|
||||||
|
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
@@ -151,11 +152,16 @@ int safe_fflush(FILE *stream) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int safe_ioctl(int fd, unsigned long request, void *argp) {
|
int safe_ioctl(int fd, unsigned long request, void *argp, const char* exception_msg) {
|
||||||
int ret;
|
int ret;
|
||||||
do {
|
do {
|
||||||
ret = ioctl(fd, request, argp);
|
ret = ioctl(fd, request, argp);
|
||||||
} while ((ret == -1) && (errno == EINTR));
|
} while ((ret == -1) && (errno == EINTR));
|
||||||
|
|
||||||
|
if (ret == -1 && exception_msg) {
|
||||||
|
LOGE("safe_ioctl error: %s %s(%d) (fd: %d request: %lx argp: %p)", exception_msg, strerror(errno), errno, fd, request, argp);
|
||||||
|
throw std::runtime_error(exception_msg);
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -36,6 +36,7 @@ const double MS_TO_KPH = 3.6;
|
|||||||
const double MS_TO_MPH = MS_TO_KPH * KM_TO_MILE;
|
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;
|
||||||
|
const double METER_TO_KM = 1. / 1000.0;
|
||||||
|
|
||||||
#define ALIGNED_SIZE(x, align) (((x) + (align)-1) & ~((align)-1))
|
#define ALIGNED_SIZE(x, align) (((x) + (align)-1) & ~((align)-1))
|
||||||
|
|
||||||
@@ -88,7 +89,7 @@ int write_file(const char* path, const void* data, size_t size, int flags = O_WR
|
|||||||
FILE* safe_fopen(const char* filename, const char* mode);
|
FILE* safe_fopen(const char* filename, const char* mode);
|
||||||
size_t safe_fwrite(const void * ptr, size_t size, size_t count, FILE * stream);
|
size_t safe_fwrite(const void * ptr, size_t size, size_t count, FILE * stream);
|
||||||
int safe_fflush(FILE *stream);
|
int safe_fflush(FILE *stream);
|
||||||
int safe_ioctl(int fd, unsigned long request, void *argp);
|
int safe_ioctl(int fd, unsigned long request, void *argp, const char* exception_msg = nullptr);
|
||||||
|
|
||||||
std::string readlink(const std::string& path);
|
std::string readlink(const std::string& path);
|
||||||
bool file_exists(const std::string& fn);
|
bool file_exists(const std::string& fn);
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
#define COMMA_VERSION "0.10.0"
|
#define COMMA_VERSION "0.10.1"
|
||||||
|
|||||||
+25
-4
@@ -4,12 +4,13 @@
|
|||||||
|
|
||||||
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.
|
||||||
|
|
||||||
# 313 Supported Cars
|
# 334 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> |Video|Setup Video|
|
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br> |Video|Setup Video|
|
||||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||||
|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura ILX 2016-18">Buy Here</a></sub></details>|||
|
|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura ILX 2016-18">Buy Here</a></sub></details>|||
|
||||||
|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura ILX 2019">Buy Here</a></sub></details>|||
|
|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura ILX 2019">Buy Here</a></sub></details>|||
|
||||||
|
|Acura|MDX 2025|All except Type S|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura MDX 2025">Buy Here</a></sub></details>|||
|
||||||
|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2016-18">Buy Here</a></sub></details>|||
|
|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2016-18">Buy Here</a></sub></details>|||
|
||||||
|Acura|RDX 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2019-21">Buy Here</a></sub></details>|||
|
|Acura|RDX 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Acura RDX 2019-21">Buy Here</a></sub></details>|||
|
||||||
|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 2014-19">Buy Here</a></sub></details>|||
|
|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Audi A3 2014-19">Buy Here</a></sub></details>|||
|
||||||
@@ -72,18 +73,25 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Genesis|GV80 2023[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis GV80 2023">Buy Here</a></sub></details>|||
|
|Genesis|GV80 2023[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai M connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Genesis 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|[](##)|[](##)|<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?harness=GMC 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|[](##)|[](##)|<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?harness=GMC 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|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 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|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2018-22">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=mrUwlj3Mi58" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|
|Honda|Accord 2023-25|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord 2023-25">Buy Here</a></sub></details>|||
|
||||||
|Honda|Accord Hybrid 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2018-22">Buy Here</a></sub></details>|||
|
|Honda|Accord Hybrid 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2018-22">Buy Here</a></sub></details>|||
|
||||||
|
|Honda|Accord Hybrid 2023-25|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Accord Hybrid 2023-25">Buy Here</a></sub></details>|||
|
||||||
|
|Honda|City (Brazil only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|14 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda City (Brazil only) 2023">Buy Here</a></sub></details>|||
|
||||||
|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2016-18">Buy Here</a></sub></details>|<a href="https://youtu.be/-IkImTe1NYE" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2016-18">Buy Here</a></sub></details>|<a href="https://youtu.be/-IkImTe1NYE" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Honda|Civic 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|2 mph[<sup>5</sup>](#footnotes)|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=4Iz1Mz5LGF8" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
|Honda|Civic 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|2 mph[<sup>5</sup>](#footnotes)|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=4Iz1Mz5LGF8" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Honda|Civic 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
|Honda|Civic 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2017-21">Buy Here</a></sub></details>|||
|
|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2017-18">Buy Here</a></sub></details>|||
|
||||||
|
|Honda|Civic Hatchback 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2019-21">Buy Here</a></sub></details>|||
|
||||||
|Honda|Civic Hatchback 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
|Honda|Civic Hatchback 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Honda|Civic Hatchback Hybrid 2025|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid 2025">Buy Here</a></sub></details>|||
|
|Honda|Civic Hatchback Hybrid 2025|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid 2025">Buy Here</a></sub></details>|||
|
||||||
|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid (Europe only) 2023">Buy Here</a></sub></details>|||
|
|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hatchback Hybrid (Europe only) 2023">Buy Here</a></sub></details>|||
|
||||||
|Honda|Civic Hybrid 2025|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hybrid 2025">Buy Here</a></sub></details>|||
|
|Honda|Civic Hybrid 2025|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Civic Hybrid 2025">Buy Here</a></sub></details>|||
|
||||||
|
|Honda|Clarity 2018-21|Honda Sensing|openpilot|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector + Honda Clarity Proxy Board<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://shop.retropilot.org/product/honda-clarity-proxy-board-kit">Buy Here</a></sub></details>|||
|
||||||
|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2015-16">Buy Here</a></sub></details>|||
|
|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2015-16">Buy Here</a></sub></details>|||
|
||||||
|Honda|CR-V 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|15 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2017-22">Buy Here</a></sub></details>|||
|
|Honda|CR-V 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|15 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2017-22">Buy Here</a></sub></details>|||
|
||||||
|
|Honda|CR-V 2023-25|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V 2023-25">Buy Here</a></sub></details>|||
|
||||||
|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2017-22">Buy Here</a></sub></details>|||
|
|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2017-22">Buy Here</a></sub></details>|||
|
||||||
|
|Honda|CR-V Hybrid 2023-25|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda CR-V Hybrid 2023-25">Buy Here</a></sub></details>|||
|
||||||
|Honda|e 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda e 2020">Buy Here</a></sub></details>|||
|
|Honda|e 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda e 2020">Buy Here</a></sub></details>|||
|
||||||
|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Fit 2018-20">Buy Here</a></sub></details>|||
|
|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Fit 2018-20">Buy Here</a></sub></details>|||
|
||||||
|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Freed 2020">Buy Here</a></sub></details>|||
|
|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Freed 2020">Buy Here</a></sub></details>|||
|
||||||
@@ -94,6 +102,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2018-20">Buy Here</a></sub></details>|||
|
|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Odyssey 2018-20">Buy Here</a></sub></details>|||
|
||||||
|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Passport 2019-25">Buy Here</a></sub></details>|||
|
|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Passport 2019-25">Buy Here</a></sub></details>|||
|
||||||
|Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2016-22">Buy Here</a></sub></details>|||
|
|Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2016-22">Buy Here</a></sub></details>|||
|
||||||
|
|Honda|Pilot 2023-25|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch C connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Pilot 2023-25">Buy Here</a></sub></details>|||
|
||||||
|Honda|Ridgeline 2017-25|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Ridgeline 2017-25">Buy Here</a></sub></details>|||
|
|Honda|Ridgeline 2017-25|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Honda Ridgeline 2017-25">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Azera 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera 2022">Buy Here</a></sub></details>|||
|
|Hyundai|Azera 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera 2022">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Azera Hybrid 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera Hybrid 2019">Buy Here</a></sub></details>|||
|
|Hyundai|Azera Hybrid 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Azera Hybrid 2019">Buy Here</a></sub></details>|||
|
||||||
@@ -104,6 +113,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Hyundai|Elantra GT 2017-20|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra GT 2017-20">Buy Here</a></sub></details>|||
|
|Hyundai|Elantra GT 2017-20|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra GT 2017-20">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra Hybrid 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra Hybrid 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|
|Hyundai|Elantra Non-SCC 2022|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Elantra Non-SCC 2022">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai J connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Genesis 2015-16">Buy Here</a></sub></details>|||
|
|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai J connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Genesis 2015-16">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai i30 2017-19">Buy Here</a></sub></details>|||
|
|Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai i30 2017-19">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Ioniq 5 (Southeast Asia and Europe only) 2022-24[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq 5 (Southeast Asia and Europe only) 2022-24">Buy Here</a></sub></details>|||
|
|Hyundai|Ioniq 5 (Southeast Asia and Europe only) 2022-24[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai Q connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq 5 (Southeast Asia and Europe only) 2022-24">Buy Here</a></sub></details>|||
|
||||||
@@ -117,10 +127,13 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Plug-in Hybrid 2019">Buy Here</a></sub></details>|||
|
|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Plug-in Hybrid 2019">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Plug-in Hybrid 2020-22">Buy Here</a></sub></details>|||
|
|Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Ioniq Plug-in Hybrid 2020-22">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|6 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona 2020">Buy Here</a></sub></details>|||
|
|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|6 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona 2020">Buy Here</a></sub></details>|||
|
||||||
|
|Hyundai|Kona 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona 2022-23">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric 2018-21">Buy Here</a></sub></details>|||
|
|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric 2018-21">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric 2022-23">Buy Here</a></sub></details>|||
|
|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric 2022-23">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Kona Electric (with HDA II, Korea only) 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai R connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric (with HDA II, Korea only) 2023">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=U2fOCmcQ8hw" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
|Hyundai|Kona Electric (with HDA II, Korea only) 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai R connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric (with HDA II, Korea only) 2023">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=U2fOCmcQ8hw" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|
|Hyundai|Kona Electric Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Electric Non-SCC 2019">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai I connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Hybrid 2020">Buy Here</a></sub></details>|||
|
|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai I connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Hybrid 2020">Buy Here</a></sub></details>|||
|
||||||
|
|Hyundai|Kona Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Kona Non-SCC 2019">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Nexo 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Nexo 2021">Buy Here</a></sub></details>|||
|
|Hyundai|Nexo 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Nexo 2021">Buy Here</a></sub></details>|||
|
||||||
|Hyundai|Palisade 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Palisade 2020-22">Buy Here</a></sub></details>|<a href="https://youtu.be/TAnDqjF4fDY?t=456" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
|Hyundai|Palisade 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Palisade 2020-22">Buy Here</a></sub></details>|<a href="https://youtu.be/TAnDqjF4fDY?t=456" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Hyundai|Santa Cruz 2022-24[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Santa Cruz 2022-24">Buy Here</a></sub></details>|||
|
|Hyundai|Santa Cruz 2022-24[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Hyundai Santa Cruz 2022-24">Buy Here</a></sub></details>|||
|
||||||
@@ -144,6 +157,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Kia|Carnival 2022-24[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Carnival 2022-24">Buy Here</a></sub></details>|||
|
|Kia|Carnival 2022-24[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Carnival 2022-24">Buy Here</a></sub></details>|||
|
||||||
|Kia|Carnival (China only) 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Carnival (China only) 2023">Buy Here</a></sub></details>|||
|
|Kia|Carnival (China only) 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Carnival (China only) 2023">Buy Here</a></sub></details>|||
|
||||||
|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Ceed 2019-21">Buy Here</a></sub></details>|||
|
|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Ceed 2019-21">Buy Here</a></sub></details>|||
|
||||||
|
|Kia|Ceed Plug-in Hybrid Non-SCC 2022|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai I connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Ceed Plug-in Hybrid Non-SCC 2022">Buy Here</a></sub></details>|||
|
||||||
|Kia|EV6 (Southeast Asia only) 2022-24[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia EV6 (Southeast Asia only) 2022-24">Buy Here</a></sub></details>|||
|
|Kia|EV6 (Southeast Asia only) 2022-24[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia EV6 (Southeast Asia only) 2022-24">Buy Here</a></sub></details>|||
|
||||||
|Kia|EV6 (with HDA II) 2022-24[<sup>6</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia EV6 (with HDA II) 2022-24">Buy Here</a></sub></details>|||
|
|Kia|EV6 (with HDA II) 2022-24[<sup>6</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai P connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia EV6 (with HDA II) 2022-24">Buy Here</a></sub></details>|||
|
||||||
|Kia|EV6 (without HDA II) 2022-24[<sup>6</sup>](#footnotes)|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia EV6 (without HDA II) 2022-24">Buy Here</a></sub></details>|||
|
|Kia|EV6 (without HDA II) 2022-24[<sup>6</sup>](#footnotes)|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia EV6 (without HDA II) 2022-24">Buy Here</a></sub></details>|||
|
||||||
@@ -179,7 +193,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Kia|Sportage Hybrid 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sportage Hybrid 2023">Buy Here</a></sub></details>|||
|
|Kia|Sportage Hybrid 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Sportage Hybrid 2023">Buy Here</a></sub></details>|||
|
||||||
|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Stinger 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=MJ94qoofYw0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Stinger 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=MJ94qoofYw0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Kia|Stinger 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Stinger 2022-23">Buy Here</a></sub></details>|||
|
|Kia|Stinger 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Stinger 2022-23">Buy Here</a></sub></details>|||
|
||||||
|Kia|Telluride 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Telluride 2020-22">Buy Here</a></sub></details>|||
|
|Kia|Telluride 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Kia Telluride 2020-22">Buy Here</a></sub></details>|||
|
||||||
|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus CT Hybrid 2017-18">Buy Here</a></sub></details>|||
|
|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus CT Hybrid 2017-18">Buy Here</a></sub></details>|||
|
||||||
|Lexus|ES 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus ES 2017-18">Buy Here</a></sub></details>|||
|
|Lexus|ES 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus ES 2017-18">Buy Here</a></sub></details>|||
|
||||||
|Lexus|ES 2019-25|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus ES 2019-25">Buy Here</a></sub></details>|||
|
|Lexus|ES 2019-25|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Lexus ES 2019-25">Buy Here</a></sub></details>|||
|
||||||
@@ -212,7 +226,9 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Nissan[<sup>7</sup>](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Leaf 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
|Nissan[<sup>7</sup>](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Leaf 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Nissan[<sup>7</sup>](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Rogue 2018-20">Buy Here</a></sub></details>|||
|
|Nissan[<sup>7</sup>](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan Rogue 2018-20">Buy Here</a></sub></details>|||
|
||||||
|Nissan[<sup>7</sup>](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan X-Trail 2017">Buy Here</a></sub></details>|||
|
|Nissan[<sup>7</sup>](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Nissan X-Trail 2017">Buy Here</a></sub></details>|||
|
||||||
|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ram connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Ram 1500 2019-24">Buy Here</a></sub></details>|||
|
|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|32 mph|1 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ram connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Ram 1500 2019-24">Buy Here</a></sub></details>|||
|
||||||
|
|Ram|2500 2020-24|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ram connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Ram 2500 2020-24">Buy Here</a></sub></details>|||
|
||||||
|
|Ram|3500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ram connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Ram 3500 2019-22">Buy Here</a></sub></details>|||
|
||||||
|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Rivian A connector<br>- 1 USB-C coupler<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1S 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Rivian A connector<br>- 1 USB-C coupler<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1S 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||||
|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Rivian A connector<br>- 1 USB-C coupler<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1T 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Rivian A connector<br>- 1 USB-C coupler<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1T 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||||
|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Ateca 2016-23">Buy Here</a></sub></details>|||
|
|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Ateca 2016-23">Buy Here</a></sub></details>|||
|
||||||
@@ -220,10 +236,14 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Subaru|Ascent 2019-21|All[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|Ascent 2019-21|All[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2020-23">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2020-23">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|
|Subaru|Forester 2017-18|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Forester 2017-18">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|Subaru|Forester 2019-21|All[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Forester 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|Forester 2019-21|All[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Forester 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|Subaru|Impreza 2017-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2017-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|Impreza 2017-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2017-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|Subaru|Impreza 2020-22|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|Impreza 2020-22|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|
|Subaru|Legacy 2015-18|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Legacy 2015-18">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|Subaru|Legacy 2020-22|All[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Legacy 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|Legacy 2020-22|All[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Legacy 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|
|Subaru|Outback 2015-17|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2015-17">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|
|Subaru|Outback 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|Subaru|Outback 2020-22|All[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|Outback 2020-22|All[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
|Subaru|XV 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
|Subaru|XV 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Subaru|XV 2020-21|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2020-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
|Subaru|XV 2020-21|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2020-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||||
@@ -285,6 +305,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
|||||||
|Toyota|RAV4 Hybrid 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2022">Buy Here</a></sub></details>|<a href="https://youtu.be/U0nH9cnrFB0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
|Toyota|RAV4 Hybrid 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2022">Buy Here</a></sub></details>|<a href="https://youtu.be/U0nH9cnrFB0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2023-25">Buy Here</a></sub></details>|<a href="https://youtu.be/4eIsEq4L4Ng" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2023-25">Buy Here</a></sub></details>|<a href="https://youtu.be/4eIsEq4L4Ng" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Toyota|Sienna 2018-20|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Sienna 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=q1UPOo4Sh68" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
|Toyota|Sienna 2018-20|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Sienna 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=q1UPOo4Sh68" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|
|Toyota|Wildlander PHEV 2021|All|openpilot|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Wildlander PHEV 2021">Buy Here</a></sub></details>|||
|
||||||
|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon eHybrid 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon eHybrid 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon R 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon R 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Development is coordinated through [Discord](https://discord.comma.ai) and GitHu
|
|||||||
|
|
||||||
### Getting Started
|
### Getting Started
|
||||||
|
|
||||||
* Setup your [development environment](../tools/)
|
* Set up your [development environment](/tools/)
|
||||||
* Join our [Discord](https://discord.comma.ai)
|
* Join our [Discord](https://discord.comma.ai)
|
||||||
* Docs are at https://docs.comma.ai and https://blog.comma.ai
|
* Docs are at https://docs.comma.ai and https://blog.comma.ai
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ All of these are examples of good PRs:
|
|||||||
### First contribution
|
### First contribution
|
||||||
|
|
||||||
[Projects / openpilot bounties](https://github.com/orgs/commaai/projects/26/views/1?pane=info) is the best place to get started and goes in-depth on what's expected when working on a bounty.
|
[Projects / openpilot bounties](https://github.com/orgs/commaai/projects/26/views/1?pane=info) is the best place to get started and goes in-depth on what's expected when working on a bounty.
|
||||||
There's lot of bounties that don't require a comma 3/3X or a car.
|
There's lot of bounties that don't require a comma 3X or a car.
|
||||||
|
|
||||||
## Pull Requests
|
## Pull Requests
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Debugging Panda Safety with Replay Drive + LLDB
|
||||||
|
|
||||||
|
## 1. Start the debugger in VS Code
|
||||||
|
|
||||||
|
* Select **Replay drive + Safety LLDB**.
|
||||||
|
* Enter the route or segment when prompted.
|
||||||
|
[<img src="https://github.com/user-attachments/assets/b0cc320a-083e-46a7-a9f8-ca775bbe5604">](https://github.com/user-attachments/assets/b0cc320a-083e-46a7-a9f8-ca775bbe5604)
|
||||||
|
|
||||||
|
## 2. Attach LLDB
|
||||||
|
|
||||||
|
* When prompted, pick the running **`replay_drive` process**.
|
||||||
|
* ⚠️ Attach quickly, or `replay_drive` will start consuming messages.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> Add a Python breakpoint at the start of `replay_drive.py` to pause execution and give yourself time to attach LLDB.
|
||||||
|
|
||||||
|
## 3. Set breakpoints in VS Code
|
||||||
|
Breakpoints can be set directly in `modes/xxx.h` (or any C file).
|
||||||
|
No extra LLDB commands are required — just place breakpoints in the editor.
|
||||||
|
|
||||||
|
## 4. Resume execution
|
||||||
|
Once attached, you can step through both Python (on the replay) and C safety code as CAN logs are replayed.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> * Use short routes for quicker iteration.
|
||||||
|
> * Pause `replay_drive` early to avoid wasting log messages.
|
||||||
|
|
||||||
|
## Video
|
||||||
|
|
||||||
|
View a demo of this workflow on the PR that added it: https://github.com/commaai/openpilot/pull/36055#issue-3352911578
|
||||||
+14
-4
@@ -16,7 +16,7 @@ industry standards of safety for Level 2 Driver Assistance Systems. In particula
|
|||||||
ISO26262 guidelines, including those from [pertinent documents](https://www.nhtsa.gov/sites/nhtsa.dot.gov/files/documents/13498a_812_573_alcsystemreport.pdf)
|
ISO26262 guidelines, including those from [pertinent documents](https://www.nhtsa.gov/sites/nhtsa.dot.gov/files/documents/13498a_812_573_alcsystemreport.pdf)
|
||||||
released by NHTSA. In addition, we impose strict coding guidelines (like [MISRA C : 2012](https://www.misra.org.uk/what-is-misra/))
|
released by NHTSA. In addition, we impose strict coding guidelines (like [MISRA C : 2012](https://www.misra.org.uk/what-is-misra/))
|
||||||
on parts of openpilot that are safety relevant. We also perform software-in-the-loop,
|
on parts of openpilot that are safety relevant. We also perform software-in-the-loop,
|
||||||
hardware-in-the-loop and in-vehicle tests before each software release.
|
hardware-in-the-loop, and in-vehicle tests before each software release.
|
||||||
|
|
||||||
Following Hazard and Risk Analysis and FMEA, at a very high level, we have designed openpilot
|
Following Hazard and Risk Analysis and FMEA, at a very high level, we have designed openpilot
|
||||||
ensuring two main safety requirements.
|
ensuring two main safety requirements.
|
||||||
@@ -29,8 +29,18 @@ ensuring two main safety requirements.
|
|||||||
|
|
||||||
For additional safety implementation details, refer to [panda safety model](https://github.com/commaai/panda#safety-model). For vehicle specific implementation of the safety concept, refer to [opendbc/safety/safety](https://github.com/commaai/opendbc/tree/master/opendbc/safety/safety).
|
For additional safety implementation details, refer to [panda safety model](https://github.com/commaai/panda#safety-model). For vehicle specific implementation of the safety concept, refer to [opendbc/safety/safety](https://github.com/commaai/opendbc/tree/master/opendbc/safety/safety).
|
||||||
|
|
||||||
**Extra note**: comma.ai strongly discourages the use of openpilot forks with safety code either missing or
|
[^1]: For these actuator limits we observe ISO11270 and ISO15622. Lateral limits described there translate to 0.9 seconds of maximum actuation to achieve a 1m lateral deviation.
|
||||||
not fully meeting the above requirements.
|
|
||||||
|
|
||||||
[^1]: For these actuator limits we observe ISO11270 and ISO15622. Lateral limits described there translate to 0.9 seconds of maximum actuation to achieve a 1m lateral deviation.
|
---
|
||||||
|
|
||||||
|
### Forks of openpilot
|
||||||
|
|
||||||
|
* Do not disable or nerf [driver monitoring](https://github.com/commaai/openpilot/tree/master/selfdrive/monitoring)
|
||||||
|
* Do not disable or nerf [excessive actuation checks](https://github.com/commaai/openpilot/tree/master/selfdrive/selfdrived/helpers.py)
|
||||||
|
* If your fork modifies any of the code in `opendbc/safety/`:
|
||||||
|
* your fork cannot use the openpilot trademark
|
||||||
|
* your fork must preserve the full [safety test suite](https://github.com/commaai/opendbc/tree/master/opendbc/safety/tests) and all tests must pass, including any new coverage required by the fork's changes
|
||||||
|
|
||||||
|
Failure to comply with these standards will get you and your users banned from comma.ai servers.
|
||||||
|
|
||||||
|
**comma.ai strongly discourages the use of openpilot forks with safety code either missing or not fully meeting the above requirements.**
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# connect to a comma 3/3X
|
# connect to a comma 3X
|
||||||
|
|
||||||
A comma 3/3X is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console).
|
A comma 3X is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console).
|
||||||
|
|
||||||
## Serial Console
|
## Serial Console
|
||||||
|
|
||||||
On both the comma three and 3X, the serial console is accessible from the main OBD-C port.
|
On both the comma three and 3X, the serial console is accessible from the main OBD-C port.
|
||||||
Connect the comma 3/3X to your computer with a normal USB C cable, or use a [comma serial](https://comma.ai/shop/comma-serial) for steady 12V power.
|
Connect the comma 3X to your computer with a normal USB C cable, or use a [comma serial](https://comma.ai/shop/comma-serial) for steady 12V power.
|
||||||
|
|
||||||
On the comma three, the serial console is exposed through a UART-to-USB chip, and `tools/scripts/serial.sh` can be used to connect.
|
On the comma three, the serial console is exposed through a UART-to-USB chip, and `tools/scripts/serial.sh` can be used to connect.
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ In order to use ADB on your device, you'll need to perform the following steps u
|
|||||||
* Here's an example command for connecting to your device using its tethered connection: `adb connect 192.168.43.1:5555`
|
* Here's an example command for connecting to your device using its tethered connection: `adb connect 192.168.43.1:5555`
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> The default port for ADB is 5555 on the comma 3/3X.
|
> The default port for ADB is 5555 on the comma 3X.
|
||||||
|
|
||||||
For more info on ADB, see the [Android Debug Bridge (ADB) documentation](https://developer.android.com/tools/adb).
|
For more info on ADB, see the [Android Debug Bridge (ADB) documentation](https://developer.android.com/tools/adb).
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Replaying is a critical tool for openpilot development and debugging.
|
|||||||
Just run `tools/replay/replay --demo`.
|
Just run `tools/replay/replay --demo`.
|
||||||
|
|
||||||
## Replaying CAN data
|
## Replaying CAN data
|
||||||
*Hardware required: jungle and comma 3/3X*
|
*Hardware required: jungle and comma 3X*
|
||||||
|
|
||||||
1. Connect your PC to a jungle.
|
1. Connect your PC to a jungle.
|
||||||
2.
|
2.
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# Turn the speed blue
|
# Turn the speed blue
|
||||||
*A getting started guide for openpilot development*
|
*A getting started guide for openpilot development*
|
||||||
|
|
||||||
In 30 minutes, we'll get an openpilot development environment setup on your computer and make some changes to openpilot's UI.
|
In 30 minutes, we'll get an openpilot development environment set up on your computer and make some changes to openpilot's UI.
|
||||||
|
|
||||||
And if you have a comma 3/3X, we'll deploy the change to your device for testing.
|
And if you have a comma 3X, we'll deploy the change to your device for testing.
|
||||||
|
|
||||||
## 1. Setup your development environment
|
## 1. Set up your development environment
|
||||||
|
|
||||||
Run this to clone openpilot and install all the dependencies:
|
Run this to clone openpilot and install all the dependencies:
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1
|
|||||||
export VECLIB_MAXIMUM_THREADS=1
|
export VECLIB_MAXIMUM_THREADS=1
|
||||||
|
|
||||||
if [ -z "$AGNOS_VERSION" ]; then
|
if [ -z "$AGNOS_VERSION" ]; then
|
||||||
export AGNOS_VERSION="12.4"
|
export AGNOS_VERSION="13.1"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export STAGING_ROOT="/data/safe_staging"
|
export STAGING_ROOT="/data/safe_staging"
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
|
||||||
|
# On any failure, run the fallback launcher
|
||||||
|
trap 'exec ./launch_chffrplus.sh' ERR
|
||||||
|
C3_LAUNCH_SH="./sunnypilot/system/hardware/c3/launch_chffrplus.sh"
|
||||||
|
|
||||||
|
MODEL="$(tr -d '\0' < "/sys/firmware/devicetree/base/model")"
|
||||||
|
export MODEL
|
||||||
|
|
||||||
|
if [ "$MODEL" = "comma tici" ]; then
|
||||||
|
# Force a failure if the launcher doesn't exist
|
||||||
|
[ -x "$C3_LAUNCH_SH" ] || false
|
||||||
|
|
||||||
|
# If it exists, run it
|
||||||
|
exec "$C3_LAUNCH_SH"
|
||||||
|
fi
|
||||||
|
|
||||||
exec ./launch_chffrplus.sh
|
exec ./launch_chffrplus.sh
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ nav:
|
|||||||
- What is openpilot?: getting-started/what-is-openpilot.md
|
- What is openpilot?: getting-started/what-is-openpilot.md
|
||||||
- How-to:
|
- How-to:
|
||||||
- Turn the speed blue: how-to/turn-the-speed-blue.md
|
- Turn the speed blue: how-to/turn-the-speed-blue.md
|
||||||
- Connect to a comma 3/3X: how-to/connect-to-comma.md
|
- Connect to a comma 3X: how-to/connect-to-comma.md
|
||||||
# - Make your first pull request: how-to/make-first-pr.md
|
# - Make your first pull request: how-to/make-first-pr.md
|
||||||
#- Replay a drive: how-to/replay-a-drive.md
|
#- Replay a drive: how-to/replay-a-drive.md
|
||||||
- Concepts:
|
- Concepts:
|
||||||
|
|||||||
+1
-1
Submodule opendbc_repo updated: 5509df5eb3...29fe003b2f
+1
-1
Submodule panda updated: e46680ff6f...69ab12ee2a
+9
-4
@@ -36,6 +36,9 @@ dependencies = [
|
|||||||
"pyopenssl < 24.3.0",
|
"pyopenssl < 24.3.0",
|
||||||
"pyaudio",
|
"pyaudio",
|
||||||
|
|
||||||
|
# ubloxd (TODO: just use struct)
|
||||||
|
"kaitaistruct",
|
||||||
|
|
||||||
# panda
|
# panda
|
||||||
"libusb1",
|
"libusb1",
|
||||||
"spidev; platform_system == 'Linux'",
|
"spidev; platform_system == 'Linux'",
|
||||||
@@ -101,8 +104,9 @@ dev = [
|
|||||||
"av",
|
"av",
|
||||||
"azure-identity",
|
"azure-identity",
|
||||||
"azure-storage-blob",
|
"azure-storage-blob",
|
||||||
"dbus-next",
|
"dbus-next", # TODO: remove once we moved everything to jeepney
|
||||||
"dictdiffer",
|
"dictdiffer",
|
||||||
|
"jeepney",
|
||||||
"matplotlib",
|
"matplotlib",
|
||||||
"opencv-python-headless",
|
"opencv-python-headless",
|
||||||
"parameterized >=0.8, <0.9",
|
"parameterized >=0.8, <0.9",
|
||||||
@@ -120,7 +124,7 @@ dev = [
|
|||||||
|
|
||||||
tools = [
|
tools = [
|
||||||
"metadrive-simulator @ https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl ; (platform_machine != 'aarch64')",
|
"metadrive-simulator @ https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl ; (platform_machine != 'aarch64')",
|
||||||
#"rerun-sdk >= 0.18", # this is pretty big, so only enable once we use it
|
"dearpygui>=2.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
@@ -152,12 +156,12 @@ markers = [
|
|||||||
testpaths = [
|
testpaths = [
|
||||||
"common",
|
"common",
|
||||||
"selfdrive",
|
"selfdrive",
|
||||||
|
"system/manager",
|
||||||
"system/updated",
|
"system/updated",
|
||||||
"system/athena",
|
"system/athena",
|
||||||
"system/camerad",
|
"system/camerad",
|
||||||
"system/hardware",
|
"system/hardware",
|
||||||
"system/loggerd",
|
"system/loggerd",
|
||||||
"system/proclogd",
|
|
||||||
"system/tests",
|
"system/tests",
|
||||||
"system/ubloxd",
|
"system/ubloxd",
|
||||||
"system/webrtc",
|
"system/webrtc",
|
||||||
@@ -173,7 +177,7 @@ quiet-level = 3
|
|||||||
# if you've got a short variable name that's getting flagged, add it here
|
# if you've got a short variable name that's getting flagged, add it here
|
||||||
ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl,lite"
|
ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl,lite"
|
||||||
builtin = "clear,rare,informal,code,names,en-GB_to_en-US"
|
builtin = "clear,rare,informal,code,names,en-GB_to_en-US"
|
||||||
skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.ts, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*"
|
skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.ts, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*"
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
python_version = "3.11"
|
python_version = "3.11"
|
||||||
@@ -247,6 +251,7 @@ exclude = [
|
|||||||
"teleoprtc_repo",
|
"teleoprtc_repo",
|
||||||
"third_party",
|
"third_party",
|
||||||
"*.ipynb",
|
"*.ipynb",
|
||||||
|
"generated",
|
||||||
]
|
]
|
||||||
lint.flake8-implicit-str-concat.allow-multiline = false
|
lint.flake8-implicit-str-concat.allow-multiline = false
|
||||||
|
|
||||||
|
|||||||
+15
-10
@@ -3,29 +3,34 @@
|
|||||||
```
|
```
|
||||||
## release checklist
|
## release checklist
|
||||||
|
|
||||||
**Go to `devel-staging`**
|
### Go to staging
|
||||||
|
- [ ] make a GitHub issue to track release
|
||||||
|
- [ ] create release master branch
|
||||||
- [ ] update RELEASES.md
|
- [ ] update RELEASES.md
|
||||||
- [ ] update `devel-staging`: `git reset --hard origin/master-ci`
|
|
||||||
- [ ] open a pull request from `devel-staging` to `devel`
|
|
||||||
- [ ] post on Discord
|
|
||||||
|
|
||||||
**Go to `devel`**
|
|
||||||
- [ ] bump version on master: `common/version.h` and `RELEASES.md`
|
- [ ] bump version on master: `common/version.h` and `RELEASES.md`
|
||||||
- [ ] before merging the pull request
|
- [ ] build new userdata partition from `release3-staging`
|
||||||
|
- [ ] post on Discord, tag `@release crew`
|
||||||
|
|
||||||
|
Updating staging:
|
||||||
|
1. either rebase on master or cherry-pick changes
|
||||||
|
2. run this to update: `BRANCH=devel-staging release/build_devel.sh`
|
||||||
|
3. build new userdata partition from `release3-staging`
|
||||||
|
|
||||||
|
### Go to release
|
||||||
|
- [ ] before going to release, test the following:
|
||||||
- [ ] update from previous release -> new release
|
- [ ] update from previous release -> new release
|
||||||
- [ ] update from new release -> previous release
|
- [ ] update from new release -> previous release
|
||||||
- [ ] fresh install with `openpilot-test.comma.ai`
|
- [ ] fresh install with `openpilot-test.comma.ai`
|
||||||
- [ ] drive on fresh install
|
- [ ] drive on fresh install
|
||||||
- [ ] no submodules or LFS
|
- [ ] no submodules or LFS
|
||||||
- [ ] check sentry, MTBF, etc.
|
- [ ] check sentry, MTBF, etc.
|
||||||
|
- [ ] stress test passes in production
|
||||||
**Go to `release3`**
|
|
||||||
- [ ] publish the blog post
|
- [ ] publish the blog post
|
||||||
- [ ] `git reset --hard origin/release3-staging`
|
- [ ] `git reset --hard origin/release3-staging`
|
||||||
- [ ] tag the release: `git tag v0.X.X <commit-hash> && git push origin v0.X.X`
|
- [ ] tag the release: `git tag v0.X.X <commit-hash> && git push origin v0.X.X`
|
||||||
- [ ] create GitHub release
|
- [ ] create GitHub release
|
||||||
- [ ] final test install on `openpilot.comma.ai`
|
- [ ] final test install on `openpilot.comma.ai`
|
||||||
- [ ] update factory provisioning
|
- [ ] update factory provisioning
|
||||||
- [ ] close out milestone
|
- [ ] close out milestone and issue
|
||||||
- [ ] post on Discord, X, etc.
|
- [ ] post on Discord, X, etc.
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -17,28 +17,23 @@ rm -rf $TARGET_DIR
|
|||||||
mkdir -p $TARGET_DIR
|
mkdir -p $TARGET_DIR
|
||||||
cd $TARGET_DIR
|
cd $TARGET_DIR
|
||||||
cp -r $SOURCE_DIR/.git $TARGET_DIR
|
cp -r $SOURCE_DIR/.git $TARGET_DIR
|
||||||
pre-commit uninstall || true
|
|
||||||
|
|
||||||
echo "[-] bringing __nightly and devel in sync T=$SECONDS"
|
echo "[-] setting up stripped branch sync T=$SECONDS"
|
||||||
cd $TARGET_DIR
|
cd $TARGET_DIR
|
||||||
|
|
||||||
git fetch --depth 1 origin __nightly
|
# tmp branch
|
||||||
git fetch --depth 1 origin devel
|
git checkout --orphan tmp
|
||||||
|
|
||||||
git checkout -f --track origin/__nightly
|
|
||||||
git reset --hard __nightly
|
|
||||||
git checkout __nightly
|
|
||||||
git reset --hard origin/devel
|
|
||||||
git clean -xdff
|
|
||||||
git lfs uninstall
|
|
||||||
|
|
||||||
# remove everything except .git
|
# remove everything except .git
|
||||||
echo "[-] erasing old sunnypilot T=$SECONDS"
|
echo "[-] erasing old sunnypilot T=$SECONDS"
|
||||||
|
git submodule deinit -f --all
|
||||||
|
git rm -rf --cached .
|
||||||
find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm -rf '{}' \;
|
find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm -rf '{}' \;
|
||||||
|
|
||||||
# reset source tree
|
# cleanup before the copy
|
||||||
cd $SOURCE_DIR
|
cd $SOURCE_DIR
|
||||||
git clean -xdff
|
git clean -xdff
|
||||||
|
git submodule foreach --recursive git clean -xdff
|
||||||
|
|
||||||
# do the files copy
|
# do the files copy
|
||||||
echo "[-] copying files T=$SECONDS"
|
echo "[-] copying files T=$SECONDS"
|
||||||
@@ -47,6 +42,7 @@ cp -pR --parents $(./release/release_files.py) $TARGET_DIR/
|
|||||||
|
|
||||||
# in the directory
|
# in the directory
|
||||||
cd $TARGET_DIR
|
cd $TARGET_DIR
|
||||||
|
rm -rf .git/modules/
|
||||||
rm -f panda/board/obj/panda.bin.signed
|
rm -f panda/board/obj/panda.bin.signed
|
||||||
|
|
||||||
# include source commit hash and build date in commit
|
# include source commit hash and build date in commit
|
||||||
@@ -85,7 +81,7 @@ fi
|
|||||||
|
|
||||||
if [ ! -z "$BRANCH" ]; then
|
if [ ! -z "$BRANCH" ]; then
|
||||||
echo "[-] Pushing to $BRANCH T=$SECONDS"
|
echo "[-] Pushing to $BRANCH T=$SECONDS"
|
||||||
git push -f origin __nightly:$BRANCH
|
git push -f origin tmp:$BRANCH
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[-] done T=$SECONDS, ready at $TARGET_DIR"
|
echo "[-] done T=$SECONDS, ready at $TARGET_DIR"
|
||||||
+8
-15
@@ -51,26 +51,19 @@ git fetch origin $DEV_BRANCH || (git checkout -b $DEV_BRANCH && git commit --all
|
|||||||
|
|
||||||
echo "[-] committing version $VERSION T=$SECONDS"
|
echo "[-] committing version $VERSION T=$SECONDS"
|
||||||
git add -f .
|
git add -f .
|
||||||
git commit -a -m "sunnypilot v$VERSION release"
|
|
||||||
git branch --set-upstream-to=origin/$DEV_BRANCH
|
|
||||||
|
|
||||||
# include source commit hash and build date in commit
|
# include source commit hash and build date in commit
|
||||||
GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD)
|
GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD)
|
||||||
DATETIME=$(date '+%Y-%m-%dT%H:%M:%S')
|
DATETIME=$(date '+%Y-%m-%dT%H:%M:%S')
|
||||||
SP_VERSION=$(cat $SOURCE_DIR/common/version.h | awk -F\" '{print $2}')
|
SP_VERSION=$(awk -F\" '{print $2}' $SOURCE_DIR/common/version.h)
|
||||||
|
|
||||||
# Add built files to git
|
# Commit with detailed message
|
||||||
git add -f .
|
git commit -a -m "sunnypilot v$VERSION
|
||||||
if [ "$EXTRA_VERSION_IDENTIFIER" = "-release" ] || [ "$EXTRA_VERSION_IDENTIFIER" = "-staging" ]; then
|
version: sunnypilot v$SP_VERSION (${EXTRA_VERSION_IDENTIFIER})
|
||||||
export VERSION=${VERSION%"$EXTRA_VERSION_IDENTIFIER"}
|
date: $DATETIME
|
||||||
git commit --amend -m "sunnypilot v$VERSION"
|
master commit: $GIT_HASH
|
||||||
else
|
"
|
||||||
git commit --amend -m "sunnypilot v$VERSION
|
git branch --set-upstream-to=origin/$DEV_BRANCH
|
||||||
version: sunnypilot v$SP_VERSION release
|
|
||||||
date: $DATETIME
|
|
||||||
master commit: $GIT_HASH
|
|
||||||
"
|
|
||||||
fi
|
|
||||||
git branch -m $DEV_BRANCH
|
git branch -m $DEV_BRANCH
|
||||||
|
|
||||||
# Push!
|
# Push!
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ def setup_argument_parser():
|
|||||||
parser.add_argument('--pr-data', type=str, help='PR data in JSON format')
|
parser.add_argument('--pr-data', type=str, help='PR data in JSON format')
|
||||||
parser.add_argument('--source-branch', type=str, default='master',
|
parser.add_argument('--source-branch', type=str, default='master',
|
||||||
help='Source branch for merging')
|
help='Source branch for merging')
|
||||||
parser.add_argument('--target-branch', type=str, default='master-dev-c3-new-test',
|
parser.add_argument('--target-branch', type=str, default='master-dev-test',
|
||||||
help='Target branch for merging')
|
help='Target branch for merging')
|
||||||
parser.add_argument('--squash-script-path', type=str, required=True,
|
parser.add_argument('--squash-script-path', type=str, required=True,
|
||||||
help='Path to the squash_and_merge.py script')
|
help='Path to the squash_and_merge.py script')
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:a69514fe68dd1b4c73f28771640dcba10fc40989d1c7e771cb48bfd830fef206
|
||||||
|
size 5713
|
||||||
@@ -57,7 +57,7 @@ class CarSpecificEvents:
|
|||||||
events.add(EventName.belowSteerSpeed)
|
events.add(EventName.belowSteerSpeed)
|
||||||
|
|
||||||
elif self.CP.brand == 'honda':
|
elif self.CP.brand == 'honda':
|
||||||
events = self.create_common_events(CS, CS_prev, pcm_enable=False)
|
events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.sport], pcm_enable=False)
|
||||||
|
|
||||||
if self.CP.pcmCruise and CS.vEgo < self.CP.minEnableSpeed:
|
if self.CP.pcmCruise and CS.vEgo < self.CP.minEnableSpeed:
|
||||||
events.add(EventName.belowEngageSpeed)
|
events.add(EventName.belowEngageSpeed)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
@@ -72,7 +71,7 @@ class Car:
|
|||||||
|
|
||||||
def __init__(self, CI=None, RI=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'] + ['carControlSP'])
|
self.sm = messaging.SubMaster(['pandaStates', 'carControl', 'onroadEvents'] + ['carControlSP', 'longitudinalPlanSP'])
|
||||||
self.pm = messaging.PubMaster(['sendcan', 'carState', 'carParams', 'carOutput', 'liveTracks'] + ['carParamsSP', 'carStateSP'])
|
self.pm = messaging.PubMaster(['sendcan', 'carState', 'carParams', 'carOutput', 'liveTracks'] + ['carParamsSP', 'carStateSP'])
|
||||||
|
|
||||||
self.can_rcv_cum_timeout_counter = 0
|
self.can_rcv_cum_timeout_counter = 0
|
||||||
@@ -107,9 +106,11 @@ class Car:
|
|||||||
with car.CarParams.from_bytes(cached_params_raw) as _cached_params:
|
with car.CarParams.from_bytes(cached_params_raw) as _cached_params:
|
||||||
cached_params = _cached_params
|
cached_params = _cached_params
|
||||||
|
|
||||||
fixed_fingerprint = json.loads(self.params.get("CarPlatformBundle") or "{}").get("platform", None)
|
fixed_fingerprint = (self.params.get("CarPlatformBundle") or {}).get("platform", None)
|
||||||
|
init_params_list_sp = sunnypilot_interfaces.initialize_params(self.params)
|
||||||
|
|
||||||
self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, num_pandas, cached_params, fixed_fingerprint)
|
self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, num_pandas, cached_params,
|
||||||
|
fixed_fingerprint, init_params_list_sp)
|
||||||
sunnypilot_interfaces.setup_interfaces(self.CI, self.params)
|
sunnypilot_interfaces.setup_interfaces(self.CI, self.params)
|
||||||
self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP, self.CI.CP_SP)
|
self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP, self.CI.CP_SP)
|
||||||
self.CP = self.CI.CP
|
self.CP = self.CI.CP
|
||||||
@@ -123,7 +124,7 @@ class Car:
|
|||||||
|
|
||||||
self.CP.alternativeExperience = 0
|
self.CP.alternativeExperience = 0
|
||||||
# mads
|
# mads
|
||||||
set_alternative_experience(self.CP, self.params)
|
set_alternative_experience(self.CP, self.CP_SP, self.params)
|
||||||
set_car_specific_params(self.CP, self.CP_SP, self.params)
|
set_car_specific_params(self.CP, self.CP_SP, self.params)
|
||||||
|
|
||||||
# Dynamic Experimental Control
|
# Dynamic Experimental Control
|
||||||
@@ -178,7 +179,7 @@ class Car:
|
|||||||
self.params.put_nonblocking("CarParamsSPPersistent", cp_sp_bytes)
|
self.params.put_nonblocking("CarParamsSPPersistent", cp_sp_bytes)
|
||||||
|
|
||||||
self.mock_carstate = MockCarState()
|
self.mock_carstate = MockCarState()
|
||||||
self.v_cruise_helper = VCruiseHelper(self.CP)
|
self.v_cruise_helper = VCruiseHelper(self.CP, self.CP_SP)
|
||||||
|
|
||||||
self.is_metric = self.params.get_bool("IsMetric")
|
self.is_metric = self.params.get_bool("IsMetric")
|
||||||
self.experimental_mode = self.params.get_bool("ExperimentalMode")
|
self.experimental_mode = self.params.get_bool("ExperimentalMode")
|
||||||
@@ -215,6 +216,7 @@ class Car:
|
|||||||
if can_rcv_valid and REPLAY:
|
if can_rcv_valid and REPLAY:
|
||||||
self.can_log_mono_time = messaging.log_from_bytes(can_strs[0]).logMonoTime
|
self.can_log_mono_time = messaging.log_from_bytes(can_strs[0]).logMonoTime
|
||||||
|
|
||||||
|
self.v_cruise_helper.update_speed_limit_assist(self.is_metric, self.sm['longitudinalPlanSP'])
|
||||||
self.v_cruise_helper.update_v_cruise(CS, self.sm['carControl'].enabled, self.is_metric)
|
self.v_cruise_helper.update_v_cruise(CS, self.sm['carControl'].enabled, self.is_metric)
|
||||||
if self.sm['carControl'].enabled and not self.CC_prev.enabled:
|
if self.sm['carControl'].enabled and not self.CC_prev.enabled:
|
||||||
# Use CarState w/ buttons from the step selfdrived enables on
|
# Use CarState w/ buttons from the step selfdrived enables on
|
||||||
|
|||||||
+16
-6
@@ -2,7 +2,7 @@ import math
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from cereal import car
|
from cereal import car
|
||||||
from openpilot.common.conversions import Conversions as CV
|
from openpilot.common.constants import CV
|
||||||
from openpilot.sunnypilot.selfdrive.car.cruise_ext import VCruiseHelperSP
|
from openpilot.sunnypilot.selfdrive.car.cruise_ext import VCruiseHelperSP
|
||||||
|
|
||||||
|
|
||||||
@@ -30,8 +30,8 @@ CRUISE_INTERVAL_SIGN = {
|
|||||||
|
|
||||||
|
|
||||||
class VCruiseHelper(VCruiseHelperSP):
|
class VCruiseHelper(VCruiseHelperSP):
|
||||||
def __init__(self, CP):
|
def __init__(self, CP, CP_SP):
|
||||||
VCruiseHelperSP.__init__(self)
|
VCruiseHelperSP.__init__(self, CP, CP_SP)
|
||||||
self.CP = CP
|
self.CP = CP
|
||||||
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
|
||||||
@@ -46,10 +46,14 @@ class VCruiseHelper(VCruiseHelperSP):
|
|||||||
def update_v_cruise(self, CS, enabled, is_metric):
|
def update_v_cruise(self, CS, enabled, is_metric):
|
||||||
self.v_cruise_kph_last = self.v_cruise_kph
|
self.v_cruise_kph_last = self.v_cruise_kph
|
||||||
|
|
||||||
|
self.get_minimum_set_speed(is_metric)
|
||||||
|
|
||||||
if CS.cruiseState.available:
|
if CS.cruiseState.available:
|
||||||
if not self.CP.pcmCruise:
|
_enabled = self.update_enabled_state(CS, enabled)
|
||||||
|
if not self.CP.pcmCruise or (not self.CP_SP.pcmCruiseSpeed and _enabled):
|
||||||
# if stock cruise is completely disabled, then we can use our own set speed logic
|
# if stock cruise is completely disabled, then we can use our own set speed logic
|
||||||
self._update_v_cruise_non_pcm(CS, enabled, is_metric)
|
self._update_v_cruise_non_pcm(CS, _enabled, is_metric)
|
||||||
|
self.update_speed_limit_assist_v_cruise_non_pcm()
|
||||||
self.v_cruise_cluster_kph = self.v_cruise_kph
|
self.v_cruise_cluster_kph = self.v_cruise_kph
|
||||||
self.update_button_timers(CS, enabled)
|
self.update_button_timers(CS, enabled)
|
||||||
else:
|
else:
|
||||||
@@ -101,6 +105,12 @@ class VCruiseHelper(VCruiseHelperSP):
|
|||||||
if not self.button_change_states[button_type]["enabled"]:
|
if not self.button_change_states[button_type]["enabled"]:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Speed Limit Assist for Non PCM long cars.
|
||||||
|
# True: Disallow set speed changes when user confirmed the target set speed during preActive state
|
||||||
|
# False: Allow set speed changes as SLA is not requesting user confirmation
|
||||||
|
if self.update_speed_limit_assist_pre_active_confirmed(button_type):
|
||||||
|
return
|
||||||
|
|
||||||
long_press, v_cruise_delta = VCruiseHelperSP.update_v_cruise_delta(self, long_press, v_cruise_delta)
|
long_press, v_cruise_delta = VCruiseHelperSP.update_v_cruise_delta(self, long_press, v_cruise_delta)
|
||||||
if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval
|
if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval
|
||||||
self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta
|
self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta
|
||||||
@@ -111,7 +121,7 @@ class VCruiseHelper(VCruiseHelperSP):
|
|||||||
if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise):
|
if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise):
|
||||||
self.v_cruise_kph = max(self.v_cruise_kph, CS.vEgo * CV.MS_TO_KPH)
|
self.v_cruise_kph = max(self.v_cruise_kph, CS.vEgo * CV.MS_TO_KPH)
|
||||||
|
|
||||||
self.v_cruise_kph = np.clip(round(self.v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX)
|
self.v_cruise_kph = np.clip(round(self.v_cruise_kph, 1), self.v_cruise_min, V_CRUISE_MAX)
|
||||||
|
|
||||||
def update_button_timers(self, CS, enabled):
|
def update_button_timers(self, CS, enabled):
|
||||||
# increment timer for buttons still pressed
|
# increment timer for buttons still pressed
|
||||||
|
|||||||
@@ -60,5 +60,8 @@ def convert_carControlSP(struct: capnp.lib.capnp._DynamicStructReader) -> struct
|
|||||||
struct_dataclass.params = [structs.CarControlSP.Param(**remove_deprecated(p)) for p in struct_dict.get('params', [])]
|
struct_dataclass.params = [structs.CarControlSP.Param(**remove_deprecated(p)) for p in struct_dict.get('params', [])]
|
||||||
struct_dataclass.leadOne = structs.LeadData(**remove_deprecated(struct_dict.get('leadOne', {})))
|
struct_dataclass.leadOne = structs.LeadData(**remove_deprecated(struct_dict.get('leadOne', {})))
|
||||||
struct_dataclass.leadTwo = structs.LeadData(**remove_deprecated(struct_dict.get('leadTwo', {})))
|
struct_dataclass.leadTwo = structs.LeadData(**remove_deprecated(struct_dict.get('leadTwo', {})))
|
||||||
|
struct_dataclass.intelligentCruiseButtonManagement = structs.IntelligentCruiseButtonManagement(
|
||||||
|
**remove_deprecated(struct_dict.get('intelligentCruiseButtonManagement', {}))
|
||||||
|
)
|
||||||
|
|
||||||
return struct_dataclass
|
return struct_dataclass
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import os
|
import os
|
||||||
import math
|
|
||||||
import hypothesis.strategies as st
|
import hypothesis.strategies as st
|
||||||
from hypothesis import Phase, given, settings
|
from hypothesis import Phase, given, settings
|
||||||
from parameterized import parameterized
|
from parameterized import parameterized
|
||||||
|
|
||||||
from cereal import car, custom
|
from cereal import car, custom
|
||||||
from opendbc.car import DT_CTRL
|
from opendbc.car import DT_CTRL
|
||||||
from opendbc.car.car_helpers import interfaces
|
|
||||||
from opendbc.car.structs import CarParams
|
from opendbc.car.structs import CarParams
|
||||||
from opendbc.car.tests.test_car_interfaces import get_fuzzy_car_interface_args
|
from opendbc.car.tests.test_car_interfaces import get_fuzzy_car_interface
|
||||||
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 opendbc.car.values import PLATFORMS
|
from opendbc.car.values import PLATFORMS
|
||||||
from openpilot.selfdrive.car.helpers import convert_carControlSP
|
from openpilot.selfdrive.car.helpers import convert_carControlSP
|
||||||
@@ -21,11 +18,6 @@ from openpilot.selfdrive.test.fuzzy_generation import FuzzyGenerator
|
|||||||
|
|
||||||
from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfaces
|
from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfaces
|
||||||
|
|
||||||
ALL_ECUS = {ecu for ecus in FW_VERSIONS.values() for ecu in ecus.keys()}
|
|
||||||
ALL_ECUS |= {ecu for config in FW_QUERY_CONFIGS.values() for ecu in config.extra_ecus}
|
|
||||||
|
|
||||||
ALL_REQUESTS = {tuple(r.request) for config in FW_QUERY_CONFIGS.values() for r in config.requests}
|
|
||||||
|
|
||||||
MAX_EXAMPLES = int(os.environ.get('MAX_EXAMPLES', '60'))
|
MAX_EXAMPLES = int(os.environ.get('MAX_EXAMPLES', '60'))
|
||||||
|
|
||||||
|
|
||||||
@@ -37,43 +29,10 @@ class TestCarInterfaces:
|
|||||||
phases=(Phase.reuse, Phase.generate, Phase.shrink))
|
phases=(Phase.reuse, Phase.generate, Phase.shrink))
|
||||||
@given(data=st.data())
|
@given(data=st.data())
|
||||||
def test_car_interfaces(self, car_name, data):
|
def test_car_interfaces(self, car_name, data):
|
||||||
CarInterface = interfaces[car_name]
|
car_interface = get_fuzzy_car_interface(car_name, data.draw)
|
||||||
|
car_params = car_interface.CP.as_reader()
|
||||||
args = get_fuzzy_car_interface_args(data.draw)
|
car_params_sp = car_interface.CP_SP
|
||||||
|
|
||||||
car_params = CarInterface.get_params(car_name, args['fingerprints'], args['car_fw'],
|
|
||||||
alpha_long=args['alpha_long'], is_release=False, docs=False)
|
|
||||||
car_params_sp = CarInterface.get_params_sp(car_params, car_name, args['fingerprints'], args['car_fw'],
|
|
||||||
alpha_long=args['alpha_long'], docs=False)
|
|
||||||
car_params = car_params.as_reader()
|
|
||||||
car_interface = CarInterface(car_params, car_params_sp)
|
|
||||||
sunnypilot_interfaces.setup_interfaces(car_interface)
|
sunnypilot_interfaces.setup_interfaces(car_interface)
|
||||||
assert car_params
|
|
||||||
assert car_params_sp
|
|
||||||
assert car_interface
|
|
||||||
|
|
||||||
assert car_params.mass > 1
|
|
||||||
assert car_params.wheelbase > 0
|
|
||||||
# centerToFront is center of gravity to front wheels, assert a reasonable range
|
|
||||||
assert car_params.wheelbase * 0.3 < car_params.centerToFront < car_params.wheelbase * 0.7
|
|
||||||
assert car_params.maxLateralAccel > 0
|
|
||||||
|
|
||||||
# Longitudinal sanity checks
|
|
||||||
assert len(car_params.longitudinalTuning.kpV) == len(car_params.longitudinalTuning.kpBP)
|
|
||||||
assert len(car_params.longitudinalTuning.kiV) == len(car_params.longitudinalTuning.kiBP)
|
|
||||||
|
|
||||||
# Lateral sanity checks
|
|
||||||
if car_params.steerControlType != CarParams.SteerControlType.angle:
|
|
||||||
tune = car_params.lateralTuning
|
|
||||||
if tune.which() == 'pid':
|
|
||||||
if car_name != MOCK.MOCK:
|
|
||||||
assert not math.isnan(tune.pid.kf) and tune.pid.kf > 0
|
|
||||||
assert len(tune.pid.kpV) > 0 and len(tune.pid.kpV) == len(tune.pid.kpBP)
|
|
||||||
assert len(tune.pid.kiV) > 0 and len(tune.pid.kiV) == len(tune.pid.kiBP)
|
|
||||||
|
|
||||||
elif tune.which() == 'torque':
|
|
||||||
assert not math.isnan(tune.torque.kf) and tune.torque.kf > 0
|
|
||||||
assert not math.isnan(tune.torque.friction) and tune.torque.friction > 0
|
|
||||||
|
|
||||||
cc_msg = FuzzyGenerator.get_random_msg(data.draw, car.CarControl, real_floats=True)
|
cc_msg = FuzzyGenerator.get_random_msg(data.draw, car.CarControl, real_floats=True)
|
||||||
cc_sp_msg = FuzzyGenerator.get_random_msg(data.draw, custom.CarControlSP, real_floats=True)
|
cc_sp_msg = FuzzyGenerator.get_random_msg(data.draw, custom.CarControlSP, real_floats=True)
|
||||||
@@ -101,7 +60,7 @@ 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
|
||||||
LongControl(car_params)
|
LongControl(car_params, car_params_sp)
|
||||||
if car_params.steerControlType == CarParams.SteerControlType.angle:
|
if car_params.steerControlType == CarParams.SteerControlType.angle:
|
||||||
LatControlAngle(car_params, car_params_sp, car_interface)
|
LatControlAngle(car_params, car_params_sp, car_interface)
|
||||||
elif car_params.lateralTuning.which() == 'pid':
|
elif car_params.lateralTuning.which() == 'pid':
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import numpy as np
|
|||||||
from parameterized import parameterized_class
|
from parameterized import parameterized_class
|
||||||
from cereal import log
|
from cereal import log
|
||||||
from openpilot.selfdrive.car.cruise import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_INITIAL, IMPERIAL_INCREMENT
|
from openpilot.selfdrive.car.cruise import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_INITIAL, IMPERIAL_INCREMENT
|
||||||
from cereal import car
|
from cereal import car, custom
|
||||||
from openpilot.common.conversions import Conversions as CV
|
from openpilot.common.constants import CV
|
||||||
from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
|
from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
|
||||||
|
|
||||||
ButtonEvent = car.CarState.ButtonEvent
|
ButtonEvent = car.CarState.ButtonEvent
|
||||||
@@ -44,12 +44,13 @@ class TestCruiseSpeed:
|
|||||||
assert simulation_steady_state == pytest.approx(cruise_speed, abs=.01), f'Did not reach {self.speed} m/s'
|
assert simulation_steady_state == pytest.approx(cruise_speed, abs=.01), f'Did not reach {self.speed} m/s'
|
||||||
|
|
||||||
|
|
||||||
# TODO: test pcmCruise
|
# TODO: test pcmCruise and pcmCruiseSpeed
|
||||||
@parameterized_class(('pcm_cruise',), [(False,)])
|
@parameterized_class(('pcm_cruise', 'pcm_cruise_speed'), [(False, True)])
|
||||||
class TestVCruiseHelper:
|
class TestVCruiseHelper:
|
||||||
def setup_method(self):
|
def setup_method(self):
|
||||||
self.CP = car.CarParams(pcmCruise=self.pcm_cruise)
|
self.CP = car.CarParams(pcmCruise=self.pcm_cruise)
|
||||||
self.v_cruise_helper = VCruiseHelper(self.CP)
|
self.CP_SP = custom.CarParamsSP(pcmCruiseSpeed=self.pcm_cruise_speed)
|
||||||
|
self.v_cruise_helper = VCruiseHelper(self.CP, self.CP_SP)
|
||||||
self.reset_cruise_speed_state()
|
self.reset_cruise_speed_state()
|
||||||
|
|
||||||
def reset_cruise_speed_state(self):
|
def reset_cruise_speed_state(self):
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
import math
|
import math
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from typing import SupportsFloat
|
from numbers import Number
|
||||||
|
|
||||||
from cereal import car, log
|
from cereal import car, log
|
||||||
import cereal.messaging as messaging
|
import cereal.messaging as messaging
|
||||||
from openpilot.common.conversions import Conversions as CV
|
from openpilot.common.constants import CV
|
||||||
from openpilot.common.params import Params
|
from openpilot.common.params import Params
|
||||||
from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper
|
from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper
|
||||||
from openpilot.common.swaglog import cloudlog
|
from openpilot.common.swaglog import cloudlog
|
||||||
@@ -21,6 +21,8 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
|
|||||||
from openpilot.selfdrive.controls.lib.longcontrol import LongControl
|
from openpilot.selfdrive.controls.lib.longcontrol import LongControl
|
||||||
from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose
|
from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose
|
||||||
|
|
||||||
|
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
||||||
|
from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase
|
||||||
from openpilot.sunnypilot.selfdrive.controls.controlsd_ext import ControlsExt
|
from openpilot.sunnypilot.selfdrive.controls.controlsd_ext import ControlsExt
|
||||||
|
|
||||||
State = log.SelfdriveState.OpenpilotState
|
State = log.SelfdriveState.OpenpilotState
|
||||||
@@ -30,15 +32,16 @@ LaneChangeDirection = log.LaneChangeDirection
|
|||||||
ACTUATOR_FIELDS = tuple(car.CarControl.Actuators.schema.fields.keys())
|
ACTUATOR_FIELDS = tuple(car.CarControl.Actuators.schema.fields.keys())
|
||||||
|
|
||||||
|
|
||||||
class Controls(ControlsExt):
|
class Controls(ControlsExt, ModelStateBase):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.params = Params()
|
self.params = Params()
|
||||||
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")
|
||||||
|
|
||||||
# Initialize sunnypilot controlsd extension
|
# Initialize sunnypilot controlsd extension and base model state
|
||||||
ControlsExt.__init__(self, self.CP, self.params)
|
ControlsExt.__init__(self, self.CP, self.params)
|
||||||
|
ModelStateBase.__init__(self)
|
||||||
|
|
||||||
self.CI = interfaces[self.CP.carFingerprint](self.CP, self.CP_SP)
|
self.CI = interfaces[self.CP.carFingerprint](self.CP, self.CP_SP)
|
||||||
|
|
||||||
@@ -48,14 +51,14 @@ class Controls(ControlsExt):
|
|||||||
poll='selfdriveState')
|
poll='selfdriveState')
|
||||||
self.pm = messaging.PubMaster(['carControl', 'controlsState'] + self.pm_services_ext)
|
self.pm = messaging.PubMaster(['carControl', 'controlsState'] + self.pm_services_ext)
|
||||||
|
|
||||||
self.steer_limited_by_controls = False
|
self.steer_limited_by_safety = False
|
||||||
self.curvature = 0.0
|
self.curvature = 0.0
|
||||||
self.desired_curvature = 0.0
|
self.desired_curvature = 0.0
|
||||||
|
|
||||||
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.CP_SP)
|
||||||
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:
|
||||||
@@ -92,9 +95,12 @@ class Controls(ControlsExt):
|
|||||||
self.LaC.update_live_torque_params(torque_params.latAccelFactorFiltered, torque_params.latAccelOffsetFiltered,
|
self.LaC.update_live_torque_params(torque_params.latAccelFactorFiltered, torque_params.latAccelOffsetFiltered,
|
||||||
torque_params.frictionCoefficientFiltered)
|
torque_params.frictionCoefficientFiltered)
|
||||||
|
|
||||||
|
self.LaC.extension.update_limits()
|
||||||
|
|
||||||
self.LaC.extension.update_model_v2(self.sm['modelV2'])
|
self.LaC.extension.update_model_v2(self.sm['modelV2'])
|
||||||
calculated_lag = self.LaC.extension.lagd_torqued_main(self.CP, self.sm['liveDelay'])
|
|
||||||
self.LaC.extension.update_lateral_lag(calculated_lag)
|
self.lat_delay = get_lat_delay(self.params, self.sm["liveDelay"].lateralDelay)
|
||||||
|
self.LaC.extension.update_lateral_lag(self.lat_delay)
|
||||||
|
|
||||||
long_plan = self.sm['longitudinalPlan']
|
long_plan = self.sm['longitudinalPlan']
|
||||||
model_v2 = self.sm['modelV2']
|
model_v2 = self.sm['modelV2']
|
||||||
@@ -110,7 +116,8 @@ class Controls(ControlsExt):
|
|||||||
|
|
||||||
CC.latActive = _lat_active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \
|
CC.latActive = _lat_active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \
|
||||||
(not standstill or self.CP.steerAtStandstill)
|
(not standstill or self.CP.steerAtStandstill)
|
||||||
CC.longActive = CC.enabled and not any(e.overrideLongitudinal for e in self.sm['onroadEvents']) and self.CP.openpilotLongitudinalControl
|
CC.longActive = CC.enabled and not any(e.overrideLongitudinal for e in self.sm['onroadEvents']) and \
|
||||||
|
(self.CP.openpilotLongitudinalControl or not self.CP_SP.pcmCruiseSpeed)
|
||||||
|
|
||||||
actuators = CC.actuators
|
actuators = CC.actuators
|
||||||
actuators.longControlState = self.LoC.long_control_state
|
actuators.longControlState = self.LoC.long_control_state
|
||||||
@@ -136,14 +143,14 @@ class Controls(ControlsExt):
|
|||||||
|
|
||||||
actuators.curvature = self.desired_curvature
|
actuators.curvature = self.desired_curvature
|
||||||
steer, steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp,
|
steer, steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp,
|
||||||
self.steer_limited_by_controls, self.desired_curvature,
|
self.steer_limited_by_safety, self.desired_curvature,
|
||||||
self.calibrated_pose, curvature_limited) # TODO what if not available
|
self.calibrated_pose, curvature_limited) # TODO what if not available
|
||||||
actuators.torque = float(steer)
|
actuators.torque = float(steer)
|
||||||
actuators.steeringAngleDeg = float(steeringAngleDeg)
|
actuators.steeringAngleDeg = float(steeringAngleDeg)
|
||||||
# Ensure no NaNs/Infs
|
# Ensure no NaNs/Infs
|
||||||
for p in ACTUATOR_FIELDS:
|
for p in ACTUATOR_FIELDS:
|
||||||
attr = getattr(actuators, p)
|
attr = getattr(actuators, p)
|
||||||
if not isinstance(attr, SupportsFloat):
|
if not isinstance(attr, Number):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not math.isfinite(attr):
|
if not math.isfinite(attr):
|
||||||
@@ -162,7 +169,7 @@ class Controls(ControlsExt):
|
|||||||
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 = CC.enabled and not CC.longActive and self.CP.openpilotLongitudinalControl
|
CC.cruiseControl.override = CC.enabled and not CC.longActive and (self.CP.openpilotLongitudinalControl or not self.CP_SP.pcmCruiseSpeed)
|
||||||
CC.cruiseControl.cancel = CS.cruiseState.enabled and (not CC.enabled or not self.CP.pcmCruise)
|
CC.cruiseControl.cancel = CS.cruiseState.enabled and (not CC.enabled or not self.CP.pcmCruise)
|
||||||
CC.cruiseControl.resume = CC.enabled and CS.cruiseState.standstill and not self.sm['longitudinalPlan'].shouldStop
|
CC.cruiseControl.resume = CC.enabled and CS.cruiseState.standstill and not self.sm['longitudinalPlan'].shouldStop
|
||||||
|
|
||||||
@@ -183,10 +190,10 @@ class Controls(ControlsExt):
|
|||||||
if self.sm['selfdriveState'].active:
|
if self.sm['selfdriveState'].active:
|
||||||
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_by_controls = abs(CC.actuators.steeringAngleDeg - CO.actuatorsOutput.steeringAngleDeg) > \
|
self.steer_limited_by_safety = abs(CC.actuators.steeringAngleDeg - CO.actuatorsOutput.steeringAngleDeg) > \
|
||||||
STEER_ANGLE_SATURATION_THRESHOLD
|
STEER_ANGLE_SATURATION_THRESHOLD
|
||||||
else:
|
else:
|
||||||
self.steer_limited_by_controls = abs(CC.actuators.torque - CO.actuatorsOutput.torque) > 1e-2
|
self.steer_limited_by_safety = abs(CC.actuators.torque - CO.actuatorsOutput.torque) > 1e-2
|
||||||
|
|
||||||
# TODO: both controlsState and carControl valids should be set by
|
# TODO: both controlsState and carControl valids should be set by
|
||||||
# sm.all_checks(), but this creates a circular dependency
|
# sm.all_checks(), but this creates a circular dependency
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
from cereal import log
|
from cereal import log, custom
|
||||||
from openpilot.common.conversions import Conversions as CV
|
from openpilot.common.constants import CV
|
||||||
from openpilot.common.realtime import DT_MDL
|
from openpilot.common.realtime import DT_MDL
|
||||||
from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeController, AutoLaneChangeMode
|
from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeController, AutoLaneChangeMode
|
||||||
|
from openpilot.sunnypilot.selfdrive.controls.lib.lane_turn_desire import LaneTurnController
|
||||||
|
|
||||||
LaneChangeState = log.LaneChangeState
|
LaneChangeState = log.LaneChangeState
|
||||||
LaneChangeDirection = log.LaneChangeDirection
|
LaneChangeDirection = log.LaneChangeDirection
|
||||||
@@ -30,6 +31,12 @@ DESIRES = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TURN_DESIRES = {
|
||||||
|
custom.TurnDirection.none: log.Desire.none,
|
||||||
|
custom.TurnDirection.turnLeft: log.Desire.turnLeft,
|
||||||
|
custom.TurnDirection.turnRight: log.Desire.turnRight,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class DesireHelper:
|
class DesireHelper:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -41,13 +48,25 @@ class DesireHelper:
|
|||||||
self.prev_one_blinker = False
|
self.prev_one_blinker = False
|
||||||
self.desire = log.Desire.none
|
self.desire = log.Desire.none
|
||||||
self.alc = AutoLaneChangeController(self)
|
self.alc = AutoLaneChangeController(self)
|
||||||
|
self.lane_turn_controller = LaneTurnController(self)
|
||||||
|
self.lane_turn_direction = custom.TurnDirection.none
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_lane_change_direction(CS):
|
||||||
|
return LaneChangeDirection.left if CS.leftBlinker else LaneChangeDirection.right
|
||||||
|
|
||||||
def update(self, carstate, lateral_active, lane_change_prob):
|
def update(self, carstate, lateral_active, lane_change_prob):
|
||||||
self.alc.update_params()
|
self.alc.update_params()
|
||||||
|
self.lane_turn_controller.update_params()
|
||||||
v_ego = carstate.vEgo
|
v_ego = carstate.vEgo
|
||||||
one_blinker = carstate.leftBlinker != carstate.rightBlinker
|
one_blinker = carstate.leftBlinker != carstate.rightBlinker
|
||||||
below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN
|
below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN
|
||||||
|
|
||||||
|
# Lane turn controller update
|
||||||
|
self.lane_turn_controller.update_lane_turn(blindspot_left=carstate.leftBlindspot, blindspot_right=carstate.rightBlindspot,
|
||||||
|
left_blinker=carstate.leftBlinker, right_blinker=carstate.rightBlinker, v_ego=v_ego)
|
||||||
|
self.lane_turn_direction = self.lane_turn_controller.get_turn_direction()
|
||||||
|
|
||||||
if not lateral_active or self.lane_change_timer > LANE_CHANGE_TIME_MAX or self.alc.lane_change_set_timer == AutoLaneChangeMode.OFF:
|
if not lateral_active or self.lane_change_timer > LANE_CHANGE_TIME_MAX or self.alc.lane_change_set_timer == AutoLaneChangeMode.OFF:
|
||||||
self.lane_change_state = LaneChangeState.off
|
self.lane_change_state = LaneChangeState.off
|
||||||
self.lane_change_direction = LaneChangeDirection.none
|
self.lane_change_direction = LaneChangeDirection.none
|
||||||
@@ -56,12 +75,13 @@ class DesireHelper:
|
|||||||
if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed:
|
if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed:
|
||||||
self.lane_change_state = LaneChangeState.preLaneChange
|
self.lane_change_state = LaneChangeState.preLaneChange
|
||||||
self.lane_change_ll_prob = 1.0
|
self.lane_change_ll_prob = 1.0
|
||||||
|
# Initialize lane change direction to prevent UI alert flicker
|
||||||
|
self.lane_change_direction = self.get_lane_change_direction(carstate)
|
||||||
|
|
||||||
# LaneChangeState.preLaneChange
|
# LaneChangeState.preLaneChange
|
||||||
elif self.lane_change_state == LaneChangeState.preLaneChange:
|
elif self.lane_change_state == LaneChangeState.preLaneChange:
|
||||||
# Set lane change direction
|
# Update lane change direction
|
||||||
self.lane_change_direction = LaneChangeDirection.left if \
|
self.lane_change_direction = self.get_lane_change_direction(carstate)
|
||||||
carstate.leftBlinker else LaneChangeDirection.right
|
|
||||||
|
|
||||||
torque_applied = carstate.steeringPressed and \
|
torque_applied = carstate.steeringPressed and \
|
||||||
((carstate.steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
|
((carstate.steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
|
||||||
@@ -106,7 +126,10 @@ class DesireHelper:
|
|||||||
|
|
||||||
self.prev_one_blinker = one_blinker
|
self.prev_one_blinker = one_blinker
|
||||||
|
|
||||||
self.desire = DESIRES[self.lane_change_direction][self.lane_change_state]
|
if self.lane_turn_direction != custom.TurnDirection.none:
|
||||||
|
self.desire = TURN_DESIRES[self.lane_turn_direction]
|
||||||
|
else:
|
||||||
|
self.desire = DESIRES[self.lane_change_direction][self.lane_change_state]
|
||||||
|
|
||||||
# Send keep pulse once per second during LaneChangeStart.preLaneChange
|
# Send keep pulse once per second during LaneChangeStart.preLaneChange
|
||||||
if self.lane_change_state in (LaneChangeState.off, LaneChangeState.laneChangeStarting):
|
if self.lane_change_state in (LaneChangeState.off, LaneChangeState.laneChangeStarting):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from opendbc.car.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
|
from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY
|
||||||
from openpilot.common.realtime import DT_CTRL, DT_MDL
|
from openpilot.common.realtime import DT_CTRL, DT_MDL
|
||||||
|
|
||||||
MIN_SPEED = 1.0
|
MIN_SPEED = 1.0
|
||||||
|
|||||||
@@ -15,15 +15,15 @@ class LatControl(ABC):
|
|||||||
self.steer_max = 1.0
|
self.steer_max = 1.0
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited):
|
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.sat_count = 0.
|
self.sat_count = 0.
|
||||||
|
|
||||||
def _check_saturation(self, saturated, CS, steer_limited_by_controls, curvature_limited):
|
def _check_saturation(self, saturated, CS, steer_limited_by_safety, curvature_limited):
|
||||||
# Saturated only if control output is not being limited by car torque/angle rate limits
|
# Saturated only if control output is not being limited by car torque/angle rate limits
|
||||||
if (saturated or curvature_limited) and CS.vEgo > self.sat_check_min_speed and not steer_limited_by_controls and not CS.steeringPressed:
|
if (saturated or curvature_limited) and CS.vEgo > self.sat_check_min_speed and not steer_limited_by_safety and not CS.steeringPressed:
|
||||||
self.sat_count += self.sat_count_rate
|
self.sat_count += self.sat_count_rate
|
||||||
else:
|
else:
|
||||||
self.sat_count -= self.sat_count_rate
|
self.sat_count -= self.sat_count_rate
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import math
|
|||||||
from cereal import log
|
from cereal import log
|
||||||
from openpilot.selfdrive.controls.lib.latcontrol import LatControl
|
from openpilot.selfdrive.controls.lib.latcontrol import LatControl
|
||||||
|
|
||||||
|
# TODO This is speed dependent
|
||||||
STEER_ANGLE_SATURATION_THRESHOLD = 2.5 # Degrees
|
STEER_ANGLE_SATURATION_THRESHOLD = 2.5 # Degrees
|
||||||
|
|
||||||
|
|
||||||
@@ -10,9 +11,9 @@ class LatControlAngle(LatControl):
|
|||||||
def __init__(self, CP, CP_SP, CI):
|
def __init__(self, CP, CP_SP, CI):
|
||||||
super().__init__(CP, CP_SP, CI)
|
super().__init__(CP, CP_SP, CI)
|
||||||
self.sat_check_min_speed = 5.
|
self.sat_check_min_speed = 5.
|
||||||
self.use_steer_limited_by_controls = CP.brand == "tesla"
|
self.use_steer_limited_by_safety = CP.brand == "tesla"
|
||||||
|
|
||||||
def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited):
|
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited):
|
||||||
angle_log = log.ControlsState.LateralAngleState.new_message()
|
angle_log = log.ControlsState.LateralAngleState.new_message()
|
||||||
|
|
||||||
if not active:
|
if not active:
|
||||||
@@ -23,9 +24,9 @@ class LatControlAngle(LatControl):
|
|||||||
angle_steers_des = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo, params.roll))
|
angle_steers_des = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo, params.roll))
|
||||||
angle_steers_des += params.angleOffsetDeg
|
angle_steers_des += params.angleOffsetDeg
|
||||||
|
|
||||||
if self.use_steer_limited_by_controls:
|
if self.use_steer_limited_by_safety:
|
||||||
# these cars' carcontrollers calculate max lateral accel and jerk, so we can rely on carOutput for saturation
|
# these cars' carcontrollers calculate max lateral accel and jerk, so we can rely on carOutput for saturation
|
||||||
angle_control_saturated = steer_limited_by_controls
|
angle_control_saturated = steer_limited_by_safety
|
||||||
else:
|
else:
|
||||||
# for cars which use a method of limiting torque such as a torque signal (Nissan and Toyota)
|
# for cars which use a method of limiting torque such as a torque signal (Nissan and Toyota)
|
||||||
# or relying on EPS (Ford Q3), carOutput does not capture maxing out torque # TODO: this can be improved
|
# or relying on EPS (Ford Q3), carOutput does not capture maxing out torque # TODO: this can be improved
|
||||||
|
|||||||
@@ -13,11 +13,7 @@ class LatControlPID(LatControl):
|
|||||||
k_f=CP.lateralTuning.pid.kf, pos_limit=self.steer_max, neg_limit=-self.steer_max)
|
k_f=CP.lateralTuning.pid.kf, pos_limit=self.steer_max, neg_limit=-self.steer_max)
|
||||||
self.get_steer_feedforward = CI.get_steer_feedforward_function()
|
self.get_steer_feedforward = CI.get_steer_feedforward_function()
|
||||||
|
|
||||||
def reset(self):
|
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited):
|
||||||
super().reset()
|
|
||||||
self.pid.reset()
|
|
||||||
|
|
||||||
def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited):
|
|
||||||
pid_log = log.ControlsState.LateralPIDState.new_message()
|
pid_log = log.ControlsState.LateralPIDState.new_message()
|
||||||
pid_log.steeringAngleDeg = float(CS.steeringAngleDeg)
|
pid_log.steeringAngleDeg = float(CS.steeringAngleDeg)
|
||||||
pid_log.steeringRateDeg = float(CS.steeringRateDeg)
|
pid_log.steeringRateDeg = float(CS.steeringRateDeg)
|
||||||
@@ -29,20 +25,24 @@ class LatControlPID(LatControl):
|
|||||||
pid_log.steeringAngleDesiredDeg = angle_steers_des
|
pid_log.steeringAngleDesiredDeg = angle_steers_des
|
||||||
pid_log.angleError = error
|
pid_log.angleError = error
|
||||||
if not active:
|
if not active:
|
||||||
output_steer = 0.0
|
output_torque = 0.0
|
||||||
pid_log.active = False
|
pid_log.active = False
|
||||||
self.pid.reset()
|
|
||||||
else:
|
else:
|
||||||
# offset does not contribute to resistive torque
|
# offset does not contribute to resistive torque
|
||||||
steer_feedforward = self.get_steer_feedforward(angle_steers_des_no_offset, CS.vEgo)
|
ff = self.get_steer_feedforward(angle_steers_des_no_offset, CS.vEgo)
|
||||||
|
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
|
||||||
|
|
||||||
|
output_torque = self.pid.update(error,
|
||||||
|
feedforward=ff,
|
||||||
|
speed=CS.vEgo,
|
||||||
|
freeze_integrator=freeze_integrator)
|
||||||
|
|
||||||
output_steer = self.pid.update(error, override=CS.steeringPressed,
|
|
||||||
feedforward=steer_feedforward, speed=CS.vEgo)
|
|
||||||
pid_log.active = True
|
pid_log.active = True
|
||||||
pid_log.p = float(self.pid.p)
|
pid_log.p = float(self.pid.p)
|
||||||
pid_log.i = float(self.pid.i)
|
pid_log.i = float(self.pid.i)
|
||||||
pid_log.f = float(self.pid.f)
|
pid_log.f = float(self.pid.f)
|
||||||
pid_log.output = float(output_steer)
|
pid_log.output = float(output_torque)
|
||||||
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_steer) < 1e-3, CS, steer_limited_by_controls, curvature_limited))
|
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited))
|
||||||
|
|
||||||
return output_steer, angle_steers_des, pid_log
|
return output_torque, angle_steers_des, pid_log
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ import math
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from cereal import log
|
from cereal import log
|
||||||
from opendbc.car import FRICTION_THRESHOLD, get_friction
|
from opendbc.car.lateral import FRICTION_THRESHOLD, get_friction
|
||||||
from opendbc.car.interfaces import LatControlInputs
|
from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY
|
||||||
from opendbc.car.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
|
|
||||||
from openpilot.selfdrive.controls.lib.latcontrol import LatControl
|
from openpilot.selfdrive.controls.lib.latcontrol import LatControl
|
||||||
from openpilot.common.pid import PIDController
|
from openpilot.common.pid import PIDController
|
||||||
|
|
||||||
@@ -29,19 +28,30 @@ class LatControlTorque(LatControl):
|
|||||||
def __init__(self, CP, CP_SP, CI):
|
def __init__(self, CP, CP_SP, CI):
|
||||||
super().__init__(CP, CP_SP, CI)
|
super().__init__(CP, CP_SP, CI)
|
||||||
self.torque_params = CP.lateralTuning.torque.as_builder()
|
self.torque_params = CP.lateralTuning.torque.as_builder()
|
||||||
self.pid = PIDController(self.torque_params.kp, self.torque_params.ki,
|
|
||||||
k_f=self.torque_params.kf, pos_limit=self.steer_max, neg_limit=-self.steer_max)
|
|
||||||
self.torque_from_lateral_accel = CI.torque_from_lateral_accel()
|
self.torque_from_lateral_accel = CI.torque_from_lateral_accel()
|
||||||
|
self.lateral_accel_from_torque = CI.lateral_accel_from_torque()
|
||||||
|
self.pid = PIDController(self.torque_params.kp, self.torque_params.ki,
|
||||||
|
k_f=self.torque_params.kf)
|
||||||
|
self.update_limits()
|
||||||
self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg
|
self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg
|
||||||
|
|
||||||
self.extension = LatControlTorqueExt(self, CP, CP_SP)
|
self.extension = LatControlTorqueExt(self, CP, CP_SP, CI)
|
||||||
|
|
||||||
def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction):
|
def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction):
|
||||||
self.torque_params.latAccelFactor = latAccelFactor
|
self.torque_params.latAccelFactor = latAccelFactor
|
||||||
self.torque_params.latAccelOffset = latAccelOffset
|
self.torque_params.latAccelOffset = latAccelOffset
|
||||||
self.torque_params.friction = friction
|
self.torque_params.friction = friction
|
||||||
|
self.update_limits()
|
||||||
|
|
||||||
|
def update_limits(self):
|
||||||
|
self.pid.set_limits(self.lateral_accel_from_torque(self.steer_max, self.torque_params),
|
||||||
|
self.lateral_accel_from_torque(-self.steer_max, self.torque_params))
|
||||||
|
|
||||||
|
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited):
|
||||||
|
# Override torque params from extension
|
||||||
|
if self.extension.update_override_torque_params(self.torque_params):
|
||||||
|
self.update_limits()
|
||||||
|
|
||||||
def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited):
|
|
||||||
pid_log = log.ControlsState.LateralTorqueState.new_message()
|
pid_log = log.ControlsState.LateralTorqueState.new_message()
|
||||||
if not active:
|
if not active:
|
||||||
output_torque = 0.0
|
output_torque = 0.0
|
||||||
@@ -50,10 +60,8 @@ class LatControlTorque(LatControl):
|
|||||||
actual_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll)
|
actual_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll)
|
||||||
roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY
|
roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY
|
||||||
curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0))
|
curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0))
|
||||||
desired_lateral_accel = desired_curvature * CS.vEgo ** 2
|
|
||||||
|
|
||||||
# desired rate is the desired rate of change in the setpoint, not the absolute desired curvature
|
desired_lateral_accel = desired_curvature * CS.vEgo ** 2
|
||||||
# desired_lateral_jerk = desired_curvature_rate * CS.vEgo ** 2
|
|
||||||
actual_lateral_accel = actual_curvature * CS.vEgo ** 2
|
actual_lateral_accel = actual_curvature * CS.vEgo ** 2
|
||||||
lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2
|
lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2
|
||||||
|
|
||||||
@@ -61,36 +69,36 @@ class LatControlTorque(LatControl):
|
|||||||
setpoint = desired_lateral_accel + low_speed_factor * desired_curvature
|
setpoint = desired_lateral_accel + low_speed_factor * desired_curvature
|
||||||
measurement = actual_lateral_accel + low_speed_factor * actual_curvature
|
measurement = actual_lateral_accel + low_speed_factor * actual_curvature
|
||||||
gravity_adjusted_lateral_accel = desired_lateral_accel - roll_compensation
|
gravity_adjusted_lateral_accel = desired_lateral_accel - roll_compensation
|
||||||
torque_from_setpoint = self.torque_from_lateral_accel(LatControlInputs(setpoint, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params,
|
|
||||||
gravity_adjusted=False)
|
# do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly
|
||||||
torque_from_measurement = self.torque_from_lateral_accel(LatControlInputs(measurement, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params,
|
pid_log.error = float(setpoint - measurement)
|
||||||
gravity_adjusted=False)
|
ff = gravity_adjusted_lateral_accel
|
||||||
pid_log.error = float(torque_from_setpoint - torque_from_measurement)
|
# latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll
|
||||||
ff = self.torque_from_lateral_accel(LatControlInputs(gravity_adjusted_lateral_accel, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params,
|
ff -= self.torque_params.latAccelOffset
|
||||||
gravity_adjusted=True)
|
|
||||||
ff += get_friction(desired_lateral_accel - actual_lateral_accel, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params)
|
ff += get_friction(desired_lateral_accel - actual_lateral_accel, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params)
|
||||||
|
|
||||||
# Lateral acceleration torque controller extension updates
|
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
|
||||||
# Overrides stock ff and pid_log.error
|
output_lataccel = self.pid.update(pid_log.error,
|
||||||
ff, pid_log = self.extension.update(CS, VM, params, ff, pid_log, setpoint, measurement, calibrated_pose, roll_compensation,
|
|
||||||
desired_lateral_accel, actual_lateral_accel, lateral_accel_deadzone, gravity_adjusted_lateral_accel,
|
|
||||||
desired_curvature, actual_curvature)
|
|
||||||
|
|
||||||
freeze_integrator = steer_limited_by_controls or CS.steeringPressed or CS.vEgo < 5
|
|
||||||
output_torque = self.pid.update(pid_log.error,
|
|
||||||
feedforward=ff,
|
feedforward=ff,
|
||||||
speed=CS.vEgo,
|
speed=CS.vEgo,
|
||||||
freeze_integrator=freeze_integrator)
|
freeze_integrator=freeze_integrator)
|
||||||
|
output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params)
|
||||||
|
|
||||||
|
# Lateral acceleration torque controller extension updates
|
||||||
|
# Overrides pid_log.error and output_torque
|
||||||
|
pid_log, output_torque = self.extension.update(CS, VM, self.pid, params, ff, pid_log, setpoint, measurement, calibrated_pose, roll_compensation,
|
||||||
|
desired_lateral_accel, actual_lateral_accel, lateral_accel_deadzone, gravity_adjusted_lateral_accel,
|
||||||
|
desired_curvature, actual_curvature, steer_limited_by_safety, output_torque)
|
||||||
|
|
||||||
pid_log.active = True
|
pid_log.active = True
|
||||||
pid_log.p = float(self.pid.p)
|
pid_log.p = float(self.pid.p)
|
||||||
pid_log.i = float(self.pid.i)
|
pid_log.i = float(self.pid.i)
|
||||||
pid_log.d = float(self.pid.d)
|
pid_log.d = float(self.pid.d)
|
||||||
pid_log.f = float(self.pid.f)
|
pid_log.f = float(self.pid.f)
|
||||||
pid_log.output = float(-output_torque)
|
pid_log.output = float(-output_torque) # TODO: log lat accel?
|
||||||
pid_log.actualLateralAccel = float(actual_lateral_accel)
|
pid_log.actualLateralAccel = float(actual_lateral_accel)
|
||||||
pid_log.desiredLateralAccel = float(desired_lateral_accel)
|
pid_log.desiredLateralAccel = float(desired_lateral_accel)
|
||||||
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_controls, curvature_limited))
|
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited))
|
||||||
|
|
||||||
# TODO left is positive in this convention
|
# TODO left is positive in this convention
|
||||||
return -output_torque, 0.0, pid_log
|
return -output_torque, 0.0, pid_log
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from cereal import log
|
from cereal import log
|
||||||
from openpilot.common.realtime import DT_CTRL
|
from openpilot.common.realtime import DT_CTRL
|
||||||
from openpilot.common.conversions import Conversions as CV
|
from openpilot.common.constants import CV
|
||||||
|
|
||||||
|
|
||||||
CAMERA_OFFSET = 0.04
|
CAMERA_OFFSET = 0.04
|
||||||
|
|||||||
@@ -10,8 +10,11 @@ CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
|
|||||||
LongCtrlState = car.CarControl.Actuators.LongControlState
|
LongCtrlState = car.CarControl.Actuators.LongControlState
|
||||||
|
|
||||||
|
|
||||||
def long_control_state_trans(CP, active, long_control_state, v_ego,
|
def long_control_state_trans(CP, CP_SP, active, long_control_state, v_ego,
|
||||||
should_stop, brake_pressed, cruise_standstill):
|
should_stop, brake_pressed, cruise_standstill):
|
||||||
|
# Gas Interceptor
|
||||||
|
cruise_standstill = cruise_standstill and not CP_SP.enableGasInterceptor
|
||||||
|
|
||||||
stopping_condition = should_stop
|
stopping_condition = should_stop
|
||||||
starting_condition = (not should_stop and
|
starting_condition = (not should_stop and
|
||||||
not cruise_standstill and
|
not cruise_standstill and
|
||||||
@@ -45,8 +48,9 @@ def long_control_state_trans(CP, active, long_control_state, v_ego,
|
|||||||
return long_control_state
|
return long_control_state
|
||||||
|
|
||||||
class LongControl:
|
class LongControl:
|
||||||
def __init__(self, CP):
|
def __init__(self, CP, CP_SP):
|
||||||
self.CP = CP
|
self.CP = CP
|
||||||
|
self.CP_SP = CP_SP
|
||||||
self.long_control_state = LongCtrlState.off
|
self.long_control_state = LongCtrlState.off
|
||||||
self.pid = PIDController((CP.longitudinalTuning.kpBP, CP.longitudinalTuning.kpV),
|
self.pid = PIDController((CP.longitudinalTuning.kpBP, CP.longitudinalTuning.kpV),
|
||||||
(CP.longitudinalTuning.kiBP, CP.longitudinalTuning.kiV),
|
(CP.longitudinalTuning.kiBP, CP.longitudinalTuning.kiV),
|
||||||
@@ -61,7 +65,7 @@ class LongControl:
|
|||||||
self.pid.neg_limit = accel_limits[0]
|
self.pid.neg_limit = accel_limits[0]
|
||||||
self.pid.pos_limit = accel_limits[1]
|
self.pid.pos_limit = accel_limits[1]
|
||||||
|
|
||||||
self.long_control_state = long_control_state_trans(self.CP, active, self.long_control_state, CS.vEgo,
|
self.long_control_state = long_control_state_trans(self.CP, self.CP_SP, active, self.long_control_state, CS.vEgo,
|
||||||
should_stop, CS.brakePressed,
|
should_stop, CS.brakePressed,
|
||||||
CS.cruiseState.standstill)
|
CS.cruiseState.standstill)
|
||||||
if self.long_control_state == LongCtrlState.off:
|
if self.long_control_state == LongCtrlState.off:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import numpy as np
|
|||||||
|
|
||||||
import cereal.messaging as messaging
|
import cereal.messaging as messaging
|
||||||
from opendbc.car.interfaces import ACCEL_MIN, ACCEL_MAX
|
from opendbc.car.interfaces import ACCEL_MIN, ACCEL_MAX
|
||||||
from openpilot.common.conversions import Conversions as CV
|
from openpilot.common.constants import CV
|
||||||
from openpilot.common.filter_simple import FirstOrderFilter
|
from openpilot.common.filter_simple import FirstOrderFilter
|
||||||
from openpilot.common.realtime import DT_MDL
|
from openpilot.common.realtime import DT_MDL
|
||||||
from openpilot.selfdrive.modeld.constants import ModelConstants
|
from openpilot.selfdrive.modeld.constants import ModelConstants
|
||||||
@@ -21,7 +21,7 @@ LON_MPC_STEP = 0.2 # first step is 0.2s
|
|||||||
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
|
ALLOW_THROTTLE_THRESHOLD = 0.4
|
||||||
MIN_ALLOW_THROTTLE_SPEED = 2.5
|
MIN_ALLOW_THROTTLE_SPEED = 2.5
|
||||||
|
|
||||||
# Lookup table for turns
|
# Lookup table for turns
|
||||||
@@ -93,12 +93,12 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
|||||||
return x, v, a, j, throttle_prob
|
return x, v, a, j, throttle_prob
|
||||||
|
|
||||||
def update(self, sm):
|
def update(self, sm):
|
||||||
self.mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc'
|
mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc'
|
||||||
if not self.mlsim:
|
if not self.mlsim:
|
||||||
self.mpc.mode = self.mode
|
self.mpc.mode = mode
|
||||||
LongitudinalPlannerSP.update(self, sm)
|
LongitudinalPlannerSP.update(self, sm)
|
||||||
if dec_mpc_mode := self.get_mpc_mode():
|
if dec_mpc_mode := self.get_mpc_mode():
|
||||||
self.mode = dec_mpc_mode
|
mode = dec_mpc_mode
|
||||||
if not self.mlsim:
|
if not self.mlsim:
|
||||||
self.mpc.mode = dec_mpc_mode
|
self.mpc.mode = dec_mpc_mode
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
|||||||
# 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)
|
||||||
|
|
||||||
if self.mode == 'acc':
|
if mode == 'acc':
|
||||||
accel_clip = [ACCEL_MIN, get_max_accel(v_ego)]
|
accel_clip = [ACCEL_MIN, get_max_accel(v_ego)]
|
||||||
steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg
|
steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg
|
||||||
accel_clip = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_clip, self.CP)
|
accel_clip = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_clip, self.CP)
|
||||||
@@ -146,6 +146,9 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
|||||||
clipped_accel_coast_interp = np.interp(v_ego, [MIN_ALLOW_THROTTLE_SPEED, MIN_ALLOW_THROTTLE_SPEED*2], [accel_clip[1], clipped_accel_coast])
|
clipped_accel_coast_interp = np.interp(v_ego, [MIN_ALLOW_THROTTLE_SPEED, MIN_ALLOW_THROTTLE_SPEED*2], [accel_clip[1], clipped_accel_coast])
|
||||||
accel_clip[1] = min(accel_clip[1], clipped_accel_coast_interp)
|
accel_clip[1] = min(accel_clip[1], clipped_accel_coast_interp)
|
||||||
|
|
||||||
|
# Get new v_cruise and a_desired from Smart Cruise Control and Speed Limit Assist
|
||||||
|
v_cruise, self.a_desired = LongitudinalPlannerSP.update_targets(self, sm, self.v_desired_filter.x, self.a_desired, v_cruise)
|
||||||
|
|
||||||
if force_slow_decel:
|
if force_slow_decel:
|
||||||
v_cruise = 0.0
|
v_cruise = 0.0
|
||||||
|
|
||||||
@@ -173,7 +176,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
|||||||
output_a_target_e2e = sm['modelV2'].action.desiredAcceleration
|
output_a_target_e2e = sm['modelV2'].action.desiredAcceleration
|
||||||
output_should_stop_e2e = sm['modelV2'].action.shouldStop
|
output_should_stop_e2e = sm['modelV2'].action.shouldStop
|
||||||
|
|
||||||
if self.mode == 'acc' or not self.mlsim:
|
if mode == 'acc' or not self.mlsim:
|
||||||
output_a_target = output_a_target_mpc
|
output_a_target = output_a_target_mpc
|
||||||
self.output_should_stop = output_should_stop_mpc
|
self.output_should_stop = output_should_stop_mpc
|
||||||
else:
|
else:
|
||||||
@@ -188,7 +191,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
|||||||
def publish(self, sm, pm):
|
def publish(self, sm, pm):
|
||||||
plan_send = messaging.new_message('longitudinalPlan')
|
plan_send = messaging.new_message('longitudinalPlan')
|
||||||
|
|
||||||
plan_send.valid = sm.all_checks(service_list=['carState', 'controlsState', 'selfdriveState'])
|
plan_send.valid = sm.all_checks(service_list=['carState', 'controlsState', 'selfdriveState', 'radarState'])
|
||||||
|
|
||||||
longitudinalPlan = plan_send.longitudinalPlan
|
longitudinalPlan = plan_send.longitudinalPlan
|
||||||
longitudinalPlan.modelMonoTime = sm.logMonoTime['modelV2']
|
longitudinalPlan.modelMonoTime = sm.logMonoTime['modelV2']
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from cereal import car
|
from cereal import car
|
||||||
|
from openpilot.common.gps import get_gps_location_service
|
||||||
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
|
||||||
@@ -16,14 +17,18 @@ def main():
|
|||||||
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.brand)
|
cloudlog.info("plannerd got CarParams: %s", CP.brand)
|
||||||
|
|
||||||
|
gps_location_service = get_gps_location_service(params)
|
||||||
|
|
||||||
ldw = LaneDepartureWarning()
|
ldw = LaneDepartureWarning()
|
||||||
longitudinal_planner = LongitudinalPlanner(CP)
|
longitudinal_planner = LongitudinalPlanner(CP)
|
||||||
pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP'])
|
pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP'])
|
||||||
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState'],
|
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState',
|
||||||
poll='modelV2')
|
'liveMapDataSP', 'carStateSP', gps_location_service],
|
||||||
|
poll='carState')
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
sm.update()
|
sm.update()
|
||||||
|
longitudinal_planner.sla.update_car_state(sm['carState'])
|
||||||
if sm.updated['modelV2']:
|
if sm.updated['modelV2']:
|
||||||
longitudinal_planner.update(sm)
|
longitudinal_planner.update(sm)
|
||||||
longitudinal_planner.publish(sm, pm)
|
longitudinal_planner.publish(sm, pm)
|
||||||
|
|||||||
+3
-1
@@ -5,6 +5,7 @@ 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 opendbc.car.gm.values import CAR as GM
|
||||||
from opendbc.car.vehicle_model import VehicleModel
|
from opendbc.car.vehicle_model import VehicleModel
|
||||||
from openpilot.selfdrive.car.helpers import convert_to_capnp
|
from openpilot.selfdrive.car.helpers import convert_to_capnp
|
||||||
from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
|
from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
|
||||||
@@ -17,7 +18,8 @@ from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfac
|
|||||||
|
|
||||||
class TestLatControl:
|
class TestLatControl:
|
||||||
|
|
||||||
@parameterized.expand([(HONDA.HONDA_CIVIC, LatControlPID), (TOYOTA.TOYOTA_RAV4, LatControlTorque), (NISSAN.NISSAN_LEAF, LatControlAngle)])
|
@parameterized.expand([(HONDA.HONDA_CIVIC, LatControlPID), (TOYOTA.TOYOTA_RAV4, LatControlTorque),
|
||||||
|
(NISSAN.NISSAN_LEAF, LatControlAngle), (GM.CHEVROLET_BOLT_EUV, LatControlTorque)])
|
||||||
def test_saturation(self, car_name, controller):
|
def test_saturation(self, car_name, controller):
|
||||||
CarInterface = interfaces[car_name]
|
CarInterface = interfaces[car_name]
|
||||||
CP = CarInterface.get_non_essential_params(car_name)
|
CP = CarInterface.get_non_essential_params(car_name)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from cereal import car
|
from cereal import car, custom
|
||||||
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState, long_control_state_trans
|
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState, long_control_state_trans
|
||||||
|
|
||||||
|
|
||||||
@@ -8,49 +8,52 @@ class TestLongControlStateTransition:
|
|||||||
|
|
||||||
def test_stay_stopped(self):
|
def test_stay_stopped(self):
|
||||||
CP = car.CarParams.new_message()
|
CP = car.CarParams.new_message()
|
||||||
|
CP_SP = custom.CarParamsSP.new_message()
|
||||||
active = True
|
active = True
|
||||||
current_state = LongCtrlState.stopping
|
current_state = LongCtrlState.stopping
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||||
should_stop=True, brake_pressed=False, cruise_standstill=False)
|
should_stop=True, brake_pressed=False, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.stopping
|
assert next_state == LongCtrlState.stopping
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||||
should_stop=False, brake_pressed=True, cruise_standstill=False)
|
should_stop=False, brake_pressed=True, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.stopping
|
assert next_state == LongCtrlState.stopping
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||||
should_stop=False, brake_pressed=False, cruise_standstill=True)
|
should_stop=False, brake_pressed=False, cruise_standstill=True)
|
||||||
assert next_state == LongCtrlState.stopping
|
assert next_state == LongCtrlState.stopping
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=1.0,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=1.0,
|
||||||
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.pid
|
assert next_state == LongCtrlState.pid
|
||||||
active = False
|
active = False
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=1.0,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=1.0,
|
||||||
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.off
|
assert next_state == LongCtrlState.off
|
||||||
|
|
||||||
def test_engage():
|
def test_engage():
|
||||||
CP = car.CarParams.new_message()
|
CP = car.CarParams.new_message()
|
||||||
|
CP_SP = custom.CarParamsSP.new_message()
|
||||||
active = True
|
active = True
|
||||||
current_state = LongCtrlState.off
|
current_state = LongCtrlState.off
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||||
should_stop=True, brake_pressed=False, cruise_standstill=False)
|
should_stop=True, brake_pressed=False, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.stopping
|
assert next_state == LongCtrlState.stopping
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||||
should_stop=False, brake_pressed=True, cruise_standstill=False)
|
should_stop=False, brake_pressed=True, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.stopping
|
assert next_state == LongCtrlState.stopping
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||||
should_stop=False, brake_pressed=False, cruise_standstill=True)
|
should_stop=False, brake_pressed=False, cruise_standstill=True)
|
||||||
assert next_state == LongCtrlState.stopping
|
assert next_state == LongCtrlState.stopping
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||||
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.pid
|
assert next_state == LongCtrlState.pid
|
||||||
|
|
||||||
def test_starting():
|
def test_starting():
|
||||||
CP = car.CarParams.new_message(startingState=True, vEgoStarting=0.5)
|
CP = car.CarParams.new_message(startingState=True, vEgoStarting=0.5)
|
||||||
|
CP_SP = custom.CarParamsSP.new_message()
|
||||||
active = True
|
active = True
|
||||||
current_state = LongCtrlState.starting
|
current_state = LongCtrlState.starting
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||||
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.starting
|
assert next_state == LongCtrlState.starting
|
||||||
next_state = long_control_state_trans(CP, active, current_state, v_ego=1.0,
|
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=1.0,
|
||||||
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
||||||
assert next_state == LongCtrlState.pid
|
assert next_state == LongCtrlState.pid
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import numpy as np
|
||||||
|
from cereal import car, messaging
|
||||||
|
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY
|
||||||
|
from opendbc.car import structs
|
||||||
|
from opendbc.car.lateral import get_friction, FRICTION_THRESHOLD
|
||||||
|
from openpilot.common.realtime import DT_MDL
|
||||||
|
from openpilot.selfdrive.locationd.torqued import TorqueEstimator, MIN_BUCKET_POINTS, POINTS_PER_BUCKET, STEER_BUCKET_BOUNDS
|
||||||
|
|
||||||
|
np.random.seed(0)
|
||||||
|
|
||||||
|
LA_ERR_STD = 1.0
|
||||||
|
INPUT_NOISE_STD = 0.08
|
||||||
|
V_EGO = 30.0
|
||||||
|
|
||||||
|
WARMUP_BUCKET_POINTS = (1.5*MIN_BUCKET_POINTS).astype(int)
|
||||||
|
STRAIGHT_ROAD_LA_BOUNDS = (0.02, 0.03)
|
||||||
|
|
||||||
|
ROLL_BIAS_DEG = 2.0
|
||||||
|
ROLL_COMPENSATION_BIAS = ACCELERATION_DUE_TO_GRAVITY*float(np.sin(np.deg2rad(ROLL_BIAS_DEG)))
|
||||||
|
TORQUE_TUNE = structs.CarParams.LateralTorqueTuning(latAccelFactor=2.0, latAccelOffset=0.0, friction=0.2)
|
||||||
|
TORQUE_TUNE_BIASED = structs.CarParams.LateralTorqueTuning(latAccelFactor=2.0, latAccelOffset=-ROLL_COMPENSATION_BIAS, friction=0.2)
|
||||||
|
|
||||||
|
def generate_inputs(torque_tune, la_err_std, input_noise_std=None):
|
||||||
|
rng = np.random.default_rng(0)
|
||||||
|
steer_torques = np.concat([rng.uniform(bnd[0], bnd[1], pts) for bnd, pts in zip(STEER_BUCKET_BOUNDS, WARMUP_BUCKET_POINTS, strict=True)])
|
||||||
|
la_errs = rng.normal(scale=la_err_std, size=steer_torques.size)
|
||||||
|
frictions = np.array([get_friction(la_err, 0.0, FRICTION_THRESHOLD, torque_tune) for la_err in la_errs])
|
||||||
|
lat_accels = torque_tune.latAccelFactor*steer_torques + torque_tune.latAccelOffset + frictions
|
||||||
|
if input_noise_std is not None:
|
||||||
|
steer_torques += rng.normal(scale=input_noise_std, size=steer_torques.size)
|
||||||
|
lat_accels += rng.normal(scale=input_noise_std, size=steer_torques.size)
|
||||||
|
return steer_torques, lat_accels
|
||||||
|
|
||||||
|
def get_warmed_up_estimator(steer_torques, lat_accels):
|
||||||
|
est = TorqueEstimator(car.CarParams())
|
||||||
|
for steer_torque, lat_accel in zip(steer_torques, lat_accels, strict=True):
|
||||||
|
est.filtered_points.add_point(steer_torque, lat_accel)
|
||||||
|
return est
|
||||||
|
|
||||||
|
def simulate_straight_road_msgs(est):
|
||||||
|
carControl = messaging.new_message('carControl').carControl
|
||||||
|
carOutput = messaging.new_message('carOutput').carOutput
|
||||||
|
carState = messaging.new_message('carState').carState
|
||||||
|
livePose = messaging.new_message('livePose').livePose
|
||||||
|
carControl.latActive = True
|
||||||
|
carState.vEgo = V_EGO
|
||||||
|
carState.steeringPressed = False
|
||||||
|
ts = DT_MDL*np.arange(2*POINTS_PER_BUCKET)
|
||||||
|
steer_torques = np.concat((np.linspace(-0.03, -0.02, POINTS_PER_BUCKET), np.linspace(0.02, 0.03, POINTS_PER_BUCKET)))
|
||||||
|
lat_accels = TORQUE_TUNE.latAccelFactor * steer_torques
|
||||||
|
for t, steer_torque, lat_accel in zip(ts, steer_torques, lat_accels, strict=True):
|
||||||
|
carOutput.actuatorsOutput.torque = float(-steer_torque)
|
||||||
|
livePose.orientationNED.x = float(np.deg2rad(ROLL_BIAS_DEG))
|
||||||
|
livePose.angularVelocityDevice.z = float(lat_accel / V_EGO)
|
||||||
|
for which, msg in (('carControl', carControl), ('carOutput', carOutput), ('carState', carState), ('livePose', livePose)):
|
||||||
|
est.handle_log(t, which, msg)
|
||||||
|
|
||||||
|
def test_estimated_offset():
|
||||||
|
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE_BIASED, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
|
||||||
|
est = get_warmed_up_estimator(steer_torques, lat_accels)
|
||||||
|
msg = est.get_msg()
|
||||||
|
# TODO add lataccelfactor and friction check when we have more accurate estimates
|
||||||
|
assert abs(msg.liveTorqueParameters.latAccelOffsetRaw - TORQUE_TUNE_BIASED.latAccelOffset) < 0.1
|
||||||
|
|
||||||
|
def test_straight_road_roll_bias():
|
||||||
|
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
|
||||||
|
est = get_warmed_up_estimator(steer_torques, lat_accels)
|
||||||
|
simulate_straight_road_msgs(est)
|
||||||
|
msg = est.get_msg()
|
||||||
|
assert (msg.liveTorqueParameters.latAccelOffsetRaw < -0.05) and np.isfinite(msg.liveTorqueParameters.latAccelOffsetRaw)
|
||||||
@@ -1,23 +1,25 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, replay_process
|
from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, replay_process
|
||||||
|
from openpilot.selfdrive.test.process_replay.test_processes import EXCLUDED_PROCS
|
||||||
from openpilot.tools.lib.logreader import LogReader, save_log
|
from openpilot.tools.lib.logreader import LogReader, save_log
|
||||||
|
|
||||||
|
ALLOW_PROCS = {c.proc_name for c in CONFIGS}
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(description="Run process on route and create new logs",
|
parser = argparse.ArgumentParser(description="Run process on route and create new logs",
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
parser.add_argument("--fingerprint", help="The fingerprint to use")
|
|
||||||
parser.add_argument("route", help="The route name to use")
|
parser.add_argument("route", help="The route name to use")
|
||||||
parser.add_argument("process", nargs='+', help="The process(s) to run")
|
parser.add_argument("--fingerprint", help="The fingerprint to use")
|
||||||
|
parser.add_argument("--whitelist-procs", nargs='*', default=ALLOW_PROCS, help="Whitelist given processes (e.g. controlsd)")
|
||||||
|
parser.add_argument("--blacklist-procs", nargs='*', default=EXCLUDED_PROCS, help="Blacklist given processes (e.g. controlsd)")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
cfgs = [c for c in CONFIGS if c.proc_name in args.process]
|
allowed_procs = set(args.whitelist_procs) - set(args.blacklist_procs)
|
||||||
|
cfgs = [c for c in CONFIGS if c.proc_name in allowed_procs]
|
||||||
lr = LogReader(args.route)
|
|
||||||
inputs = list(lr)
|
|
||||||
|
|
||||||
|
inputs = list(LogReader(args.route))
|
||||||
outputs = replay_process(cfgs, inputs, fingerprint=args.fingerprint)
|
outputs = replay_process(cfgs, inputs, fingerprint=args.fingerprint)
|
||||||
|
|
||||||
# Remove message generated by the process under test and merge in the new messages
|
# Remove message generated by the process under test and merge in the new messages
|
||||||
@@ -25,6 +27,6 @@ if __name__ == "__main__":
|
|||||||
inputs = [i for i in inputs if i.which() not in produces]
|
inputs = [i for i in inputs if i.which() not in produces]
|
||||||
outputs = sorted(inputs + outputs, key=lambda x: x.logMonoTime)
|
outputs = sorted(inputs + outputs, key=lambda x: x.logMonoTime)
|
||||||
|
|
||||||
fn = f"{args.route.replace('/', '_')}_{'_'.join(args.process)}.zst"
|
fn = f"{args.route.replace('/', '_')}_{'_'.join(allowed_procs)}.zst"
|
||||||
print(f"Saving log to {fn}")
|
print(f"Saving log to {fn}")
|
||||||
save_log(fn, outputs)
|
save_log(fn, outputs)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from typing import NoReturn
|
|||||||
from cereal import log, car
|
from cereal import log, car
|
||||||
import cereal.messaging as messaging
|
import cereal.messaging as messaging
|
||||||
from openpilot.system.hardware import HARDWARE
|
from openpilot.system.hardware import HARDWARE
|
||||||
from openpilot.common.conversions import Conversions as CV
|
from openpilot.common.constants import CV
|
||||||
from openpilot.common.params import Params
|
from openpilot.common.params import Params
|
||||||
from openpilot.common.realtime import config_realtime_process
|
from openpilot.common.realtime import config_realtime_process
|
||||||
from openpilot.common.transformations.orientation import rot_from_euler, euler_from_rot
|
from openpilot.common.transformations.orientation import rot_from_euler, euler_from_rot
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from openpilot.common.params import Params
|
|||||||
from openpilot.common.realtime import config_realtime_process
|
from openpilot.common.realtime import config_realtime_process
|
||||||
from openpilot.common.swaglog import cloudlog
|
from openpilot.common.swaglog import cloudlog
|
||||||
from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose, fft_next_good_size, parabolic_peak_interp
|
from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose, fft_next_good_size, parabolic_peak_interp
|
||||||
|
from openpilot.sunnypilot.livedelay.lagd_toggle import LagdToggle
|
||||||
|
|
||||||
BLOCK_SIZE = 100
|
BLOCK_SIZE = 100
|
||||||
BLOCK_NUM = 50
|
BLOCK_NUM = 50
|
||||||
@@ -374,6 +375,8 @@ def main():
|
|||||||
lag, valid_blocks = initial_lag_params
|
lag, valid_blocks = initial_lag_params
|
||||||
lag_learner.reset(lag, valid_blocks)
|
lag_learner.reset(lag, valid_blocks)
|
||||||
|
|
||||||
|
lagd_toggle = LagdToggle(CP)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
sm.update()
|
sm.update()
|
||||||
if sm.all_checks():
|
if sm.all_checks():
|
||||||
@@ -392,3 +395,6 @@ def main():
|
|||||||
|
|
||||||
if sm.frame % 1200 == 0: # cache every 60 seconds
|
if sm.frame % 1200 == 0: # cache every 60 seconds
|
||||||
params.put_nonblocking("LiveDelay", lag_msg_dat)
|
params.put_nonblocking("LiveDelay", lag_msg_dat)
|
||||||
|
|
||||||
|
if sm.frame % 60 == 0: # read from and write to params every 3 seconds
|
||||||
|
lagd_toggle.update(lag_msg)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from typing import Any
|
|||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from opendbc.car.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
|
from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY
|
||||||
from openpilot.selfdrive.locationd.models.constants import ObservationKind
|
from openpilot.selfdrive.locationd.models.constants import ObservationKind
|
||||||
from openpilot.common.swaglog import cloudlog
|
from openpilot.common.swaglog import cloudlog
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ from collections import deque, defaultdict
|
|||||||
|
|
||||||
import cereal.messaging as messaging
|
import cereal.messaging as messaging
|
||||||
from cereal import car, log
|
from cereal import car, log
|
||||||
from opendbc.car.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
|
from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY
|
||||||
from openpilot.common.params import Params
|
from openpilot.common.params import Params
|
||||||
from openpilot.common.realtime import config_realtime_process, DT_MDL
|
from openpilot.common.realtime import config_realtime_process, DT_MDL
|
||||||
from openpilot.common.filter_simple import FirstOrderFilter
|
from openpilot.common.filter_simple import FirstOrderFilter
|
||||||
from openpilot.common.swaglog import cloudlog
|
from openpilot.common.swaglog import cloudlog
|
||||||
from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator, PoseCalibrator, Pose
|
from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator, PoseCalibrator, Pose
|
||||||
|
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
||||||
from openpilot.sunnypilot.livedelay.lagd_toggle import LagdToggle
|
from openpilot.sunnypilot.selfdrive.locationd.torqued_ext import TorqueEstimatorExt
|
||||||
|
|
||||||
HISTORY = 5 # secs
|
HISTORY = 5 # secs
|
||||||
POINTS_PER_BUCKET = 1500
|
POINTS_PER_BUCKET = 1500
|
||||||
@@ -34,7 +34,7 @@ MIN_BUCKET_POINTS = np.array([100, 300, 500, 500, 500, 500, 300, 100])
|
|||||||
MIN_ENGAGE_BUFFER = 2 # secs
|
MIN_ENGAGE_BUFFER = 2 # secs
|
||||||
|
|
||||||
VERSION = 1 # bump this to invalidate old parameter caches
|
VERSION = 1 # bump this to invalidate old parameter caches
|
||||||
ALLOWED_CARS = ['toyota', 'hyundai', 'rivian']
|
ALLOWED_CARS = ['toyota', 'hyundai', 'rivian', 'honda']
|
||||||
|
|
||||||
|
|
||||||
def slope2rot(slope):
|
def slope2rot(slope):
|
||||||
@@ -51,9 +51,10 @@ class TorqueBuckets(PointBuckets):
|
|||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
class TorqueEstimator(ParameterEstimator, LagdToggle):
|
class TorqueEstimator(ParameterEstimator, TorqueEstimatorExt):
|
||||||
def __init__(self, CP, decimated=False, track_all_points=False):
|
def __init__(self, CP, decimated=False, track_all_points=False):
|
||||||
super().__init__()
|
ParameterEstimator.__init__(self)
|
||||||
|
TorqueEstimatorExt.__init__(self, CP)
|
||||||
self.CP = CP
|
self.CP = CP
|
||||||
self.hist_len = int(HISTORY / DT_MDL)
|
self.hist_len = int(HISTORY / DT_MDL)
|
||||||
self.lag = 0.0
|
self.lag = 0.0
|
||||||
@@ -83,6 +84,8 @@ class TorqueEstimator(ParameterEstimator, LagdToggle):
|
|||||||
|
|
||||||
self.calibrator = PoseCalibrator()
|
self.calibrator = PoseCalibrator()
|
||||||
|
|
||||||
|
TorqueEstimatorExt.initialize_custom_params(self, decimated)
|
||||||
|
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
initial_params = {
|
initial_params = {
|
||||||
@@ -99,6 +102,7 @@ class TorqueEstimator(ParameterEstimator, LagdToggle):
|
|||||||
|
|
||||||
# try to restore cached params
|
# try to restore cached params
|
||||||
params = Params()
|
params = Params()
|
||||||
|
self.params = params
|
||||||
params_cache = params.get("CarParamsPrevRoute")
|
params_cache = params.get("CarParamsPrevRoute")
|
||||||
torque_cache = params.get("LiveTorqueParameters")
|
torque_cache = params.get("LiveTorqueParameters")
|
||||||
if params_cache is not None and torque_cache is not None:
|
if params_cache is not None and torque_cache is not None:
|
||||||
@@ -180,7 +184,7 @@ class TorqueEstimator(ParameterEstimator, LagdToggle):
|
|||||||
elif which == "liveCalibration":
|
elif which == "liveCalibration":
|
||||||
self.calibrator.feed_live_calib(msg)
|
self.calibrator.feed_live_calib(msg)
|
||||||
elif which == "liveDelay":
|
elif which == "liveDelay":
|
||||||
self.lag = self.lagd_torqued_main(self.CP, msg)
|
self.lag = get_lat_delay(self.params, msg.lateralDelay)
|
||||||
# calculate lateral accel from past steering torque
|
# calculate lateral accel from past steering torque
|
||||||
elif which == "livePose":
|
elif which == "livePose":
|
||||||
if len(self.raw_points['steer_torque']) == self.hist_len:
|
if len(self.raw_points['steer_torque']) == self.hist_len:
|
||||||
@@ -260,6 +264,8 @@ def main(demo=False):
|
|||||||
t = sm.logMonoTime[which] * 1e-9
|
t = sm.logMonoTime[which] * 1e-9
|
||||||
estimator.handle_log(t, which, sm[which])
|
estimator.handle_log(t, which, sm[which])
|
||||||
|
|
||||||
|
TorqueEstimatorExt.update_use_params(estimator)
|
||||||
|
|
||||||
# 4Hz driven by livePose
|
# 4Hz driven by livePose
|
||||||
if sm.frame % 5 == 0:
|
if sm.frame % 5 == 0:
|
||||||
pm.send('liveTorqueParameters', estimator.get_msg(valid=sm.all_checks()))
|
pm.send('liveTorqueParameters', estimator.get_msg(valid=sm.all_checks()))
|
||||||
|
|||||||
@@ -13,12 +13,9 @@ class ModelConstants:
|
|||||||
META_T_IDXS = [2., 4., 6., 8., 10.]
|
META_T_IDXS = [2., 4., 6., 8., 10.]
|
||||||
|
|
||||||
# model inputs constants
|
# model inputs constants
|
||||||
MODEL_FREQ = 20
|
N_FRAMES = 2
|
||||||
HISTORY_FREQ = 5
|
MODEL_RUN_FREQ = 20
|
||||||
HISTORY_LEN_SECONDS = 5
|
MODEL_CONTEXT_FREQ = 5 # "model_trained_fps"
|
||||||
TEMPORAL_SKIP = MODEL_FREQ // HISTORY_FREQ
|
|
||||||
FULL_HISTORY_BUFFER_LEN = MODEL_FREQ * HISTORY_LEN_SECONDS
|
|
||||||
INPUT_HISTORY_BUFFER_LEN = HISTORY_FREQ * HISTORY_LEN_SECONDS
|
|
||||||
|
|
||||||
FEATURE_LEN = 512
|
FEATURE_LEN = 512
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import capnp
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from cereal import log
|
from cereal import log
|
||||||
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan, Meta
|
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan, Meta
|
||||||
|
from openpilot.sunnypilot.models.helpers import plan_x_idxs_helper
|
||||||
|
|
||||||
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
||||||
|
|
||||||
@@ -95,8 +96,8 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
|
|||||||
# action
|
# action
|
||||||
modelV2.action = action
|
modelV2.action = action
|
||||||
|
|
||||||
# times at X_IDXS of edges and lines aren't used
|
# times at X_IDXS of edges and lines
|
||||||
LINE_T_IDXS: list[float] = []
|
LINE_T_IDXS: list[float] = plan_x_idxs_helper(ModelConstants, Plan, net_output_data)
|
||||||
|
|
||||||
# lane lines
|
# lane lines
|
||||||
modelV2.init('laneLines', 4)
|
modelV2.init('laneLines', 4)
|
||||||
@@ -149,7 +150,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
|
|||||||
meta.hardBrakePredicted = hard_brake_predicted.item()
|
meta.hardBrakePredicted = hard_brake_predicted.item()
|
||||||
|
|
||||||
# confidence
|
# confidence
|
||||||
if vipc_frame_id % (2*ModelConstants.MODEL_FREQ) == 0:
|
if vipc_frame_id % (2*ModelConstants.MODEL_RUN_FREQ) == 0:
|
||||||
# any disengage prob
|
# any disengage prob
|
||||||
brake_disengage_probs = net_output_data['meta'][0,Meta.BRAKE_DISENGAGE]
|
brake_disengage_probs = net_output_data['meta'][0,Meta.BRAKE_DISENGAGE]
|
||||||
gas_disengage_probs = net_output_data['meta'][0,Meta.GAS_DISENGAGE]
|
gas_disengage_probs = net_output_data['meta'][0,Meta.GAS_DISENGAGE]
|
||||||
|
|||||||
+84
-40
@@ -31,7 +31,8 @@ from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
|
|||||||
from openpilot.selfdrive.modeld.models.commonmodel_pyx import DrivingModelFrame, CLContext
|
from openpilot.selfdrive.modeld.models.commonmodel_pyx import DrivingModelFrame, CLContext
|
||||||
from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address
|
from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address
|
||||||
|
|
||||||
from openpilot.sunnypilot.livedelay.lagd_toggle import LagdToggle
|
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
||||||
|
from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase
|
||||||
|
|
||||||
|
|
||||||
PROCESS_NAME = "selfdrive.modeld.modeld"
|
PROCESS_NAME = "selfdrive.modeld.modeld"
|
||||||
@@ -79,13 +80,72 @@ class FrameMeta:
|
|||||||
if vipc is not None:
|
if vipc is not None:
|
||||||
self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof
|
self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof
|
||||||
|
|
||||||
class ModelState:
|
class InputQueues:
|
||||||
|
def __init__ (self, model_fps, env_fps, n_frames_input):
|
||||||
|
assert env_fps % model_fps == 0
|
||||||
|
assert env_fps >= model_fps
|
||||||
|
self.model_fps = model_fps
|
||||||
|
self.env_fps = env_fps
|
||||||
|
self.n_frames_input = n_frames_input
|
||||||
|
|
||||||
|
self.dtypes = {}
|
||||||
|
self.shapes = {}
|
||||||
|
self.q = {}
|
||||||
|
|
||||||
|
def update_dtypes_and_shapes(self, input_dtypes, input_shapes) -> None:
|
||||||
|
self.dtypes.update(input_dtypes)
|
||||||
|
if self.env_fps == self.model_fps:
|
||||||
|
self.shapes.update(input_shapes)
|
||||||
|
else:
|
||||||
|
for k in input_shapes:
|
||||||
|
shape = list(input_shapes[k])
|
||||||
|
if 'img' in k:
|
||||||
|
n_channels = shape[1] // self.n_frames_input
|
||||||
|
shape[1] = (self.env_fps // self.model_fps + (self.n_frames_input - 1)) * n_channels
|
||||||
|
else:
|
||||||
|
shape[1] = (self.env_fps // self.model_fps) * shape[1]
|
||||||
|
self.shapes[k] = tuple(shape)
|
||||||
|
|
||||||
|
def reset(self) -> None:
|
||||||
|
self.q = {k: np.zeros(self.shapes[k], dtype=self.dtypes[k]) for k in self.dtypes.keys()}
|
||||||
|
|
||||||
|
def enqueue(self, inputs:dict[str, np.ndarray]) -> None:
|
||||||
|
for k in inputs.keys():
|
||||||
|
if inputs[k].dtype != self.dtypes[k]:
|
||||||
|
raise ValueError(f'supplied input <{k}({inputs[k].dtype})> has wrong dtype, expected {self.dtypes[k]}')
|
||||||
|
input_shape = list(self.shapes[k])
|
||||||
|
input_shape[1] = -1
|
||||||
|
single_input = inputs[k].reshape(tuple(input_shape))
|
||||||
|
sz = single_input.shape[1]
|
||||||
|
self.q[k][:,:-sz] = self.q[k][:,sz:]
|
||||||
|
self.q[k][:,-sz:] = single_input
|
||||||
|
|
||||||
|
def get(self, *names) -> dict[str, np.ndarray]:
|
||||||
|
if self.env_fps == self.model_fps:
|
||||||
|
return {k: self.q[k] for k in names}
|
||||||
|
else:
|
||||||
|
out = {}
|
||||||
|
for k in names:
|
||||||
|
shape = self.shapes[k]
|
||||||
|
if 'img' in k:
|
||||||
|
n_channels = shape[1] // (self.env_fps // self.model_fps + (self.n_frames_input - 1))
|
||||||
|
out[k] = np.concatenate([self.q[k][:, s:s+n_channels] for s in np.linspace(0, shape[1] - n_channels, self.n_frames_input, dtype=int)], axis=1)
|
||||||
|
elif 'pulse' in k:
|
||||||
|
# any pulse within interval counts
|
||||||
|
out[k] = self.q[k].reshape((shape[0], shape[1] * self.model_fps // self.env_fps, self.env_fps // self.model_fps, -1)).max(axis=2)
|
||||||
|
else:
|
||||||
|
idxs = np.arange(-1, -shape[1], -self.env_fps // self.model_fps)[::-1]
|
||||||
|
out[k] = self.q[k][:, idxs]
|
||||||
|
return out
|
||||||
|
|
||||||
|
class ModelState(ModelStateBase):
|
||||||
frames: dict[str, DrivingModelFrame]
|
frames: dict[str, DrivingModelFrame]
|
||||||
inputs: dict[str, np.ndarray]
|
inputs: dict[str, np.ndarray]
|
||||||
output: np.ndarray
|
output: np.ndarray
|
||||||
prev_desire: np.ndarray # for tracking the rising edge of the pulse
|
prev_desire: np.ndarray # for tracking the rising edge of the pulse
|
||||||
|
|
||||||
def __init__(self, context: CLContext):
|
def __init__(self, context: CLContext):
|
||||||
|
ModelStateBase.__init__(self)
|
||||||
self.LAT_SMOOTH_SECONDS = LAT_SMOOTH_SECONDS
|
self.LAT_SMOOTH_SECONDS = LAT_SMOOTH_SECONDS
|
||||||
with open(VISION_METADATA_PATH, 'rb') as f:
|
with open(VISION_METADATA_PATH, 'rb') as f:
|
||||||
vision_metadata = pickle.load(f)
|
vision_metadata = pickle.load(f)
|
||||||
@@ -100,22 +160,15 @@ class ModelState:
|
|||||||
self.policy_output_slices = policy_metadata['output_slices']
|
self.policy_output_slices = policy_metadata['output_slices']
|
||||||
policy_output_size = policy_metadata['output_shapes']['outputs'][1]
|
policy_output_size = policy_metadata['output_shapes']['outputs'][1]
|
||||||
|
|
||||||
self.frames = {name: DrivingModelFrame(context, ModelConstants.TEMPORAL_SKIP) for name in self.vision_input_names}
|
self.frames = {name: DrivingModelFrame(context, ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ) for name in self.vision_input_names}
|
||||||
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_buffer = np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32)
|
|
||||||
self.full_desire = np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.DESIRE_LEN), dtype=np.float32)
|
|
||||||
self.full_prev_desired_curv = np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.PREV_DESIRED_CURV_LEN), dtype=np.float32)
|
|
||||||
self.temporal_idxs = slice(-1-(ModelConstants.TEMPORAL_SKIP*(ModelConstants.INPUT_HISTORY_BUFFER_LEN-1)), None, ModelConstants.TEMPORAL_SKIP)
|
|
||||||
|
|
||||||
# policy inputs
|
# policy inputs
|
||||||
self.numpy_inputs = {
|
self.numpy_inputs = {k: np.zeros(self.policy_input_shapes[k], dtype=np.float32) for k in self.policy_input_shapes}
|
||||||
'desire': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.DESIRE_LEN), dtype=np.float32),
|
self.full_input_queues = InputQueues(ModelConstants.MODEL_CONTEXT_FREQ, ModelConstants.MODEL_RUN_FREQ, ModelConstants.N_FRAMES)
|
||||||
'traffic_convention': np.zeros((1, ModelConstants.TRAFFIC_CONVENTION_LEN), dtype=np.float32),
|
for k in ['desire_pulse', 'features_buffer']:
|
||||||
'lateral_control_params': np.zeros((1, ModelConstants.LATERAL_CONTROL_PARAMS_LEN), dtype=np.float32),
|
self.full_input_queues.update_dtypes_and_shapes({k: self.numpy_inputs[k].dtype}, {k: self.numpy_inputs[k].shape})
|
||||||
'prev_desired_curv': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.PREV_DESIRED_CURV_LEN), dtype=np.float32),
|
self.full_input_queues.reset()
|
||||||
'features_buffer': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32),
|
|
||||||
}
|
|
||||||
|
|
||||||
# img buffers are managed in openCL transform code
|
# img buffers are managed in openCL transform code
|
||||||
self.vision_inputs: dict[str, Tensor] = {}
|
self.vision_inputs: dict[str, Tensor] = {}
|
||||||
@@ -137,16 +190,10 @@ class ModelState:
|
|||||||
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
|
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
|
||||||
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_pulse'][0] = 0
|
||||||
new_desire = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0)
|
new_desire = np.where(inputs['desire_pulse'] - self.prev_desire > .99, inputs['desire_pulse'], 0)
|
||||||
self.prev_desire[:] = inputs['desire']
|
self.prev_desire[:] = inputs['desire_pulse']
|
||||||
|
|
||||||
self.full_desire[0,:-1] = self.full_desire[0,1:]
|
|
||||||
self.full_desire[0,-1] = new_desire
|
|
||||||
self.numpy_inputs['desire'][:] = self.full_desire.reshape((1,ModelConstants.INPUT_HISTORY_BUFFER_LEN,ModelConstants.TEMPORAL_SKIP,-1)).max(axis=2)
|
|
||||||
|
|
||||||
self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention']
|
|
||||||
self.numpy_inputs['lateral_control_params'][:] = inputs['lateral_control_params']
|
|
||||||
imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.vision_input_names}
|
imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.vision_input_names}
|
||||||
|
|
||||||
if TICI and not USBGPU:
|
if TICI and not USBGPU:
|
||||||
@@ -165,18 +212,14 @@ class ModelState:
|
|||||||
self.vision_output = self.vision_run(**self.vision_inputs).contiguous().realize().uop.base.buffer.numpy()
|
self.vision_output = self.vision_run(**self.vision_inputs).contiguous().realize().uop.base.buffer.numpy()
|
||||||
vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices))
|
vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices))
|
||||||
|
|
||||||
self.full_features_buffer[0,:-1] = self.full_features_buffer[0,1:]
|
self.full_input_queues.enqueue({'features_buffer': vision_outputs_dict['hidden_state'], 'desire_pulse': new_desire})
|
||||||
self.full_features_buffer[0,-1] = vision_outputs_dict['hidden_state'][0, :]
|
for k in ['desire_pulse', 'features_buffer']:
|
||||||
self.numpy_inputs['features_buffer'][:] = self.full_features_buffer[0, self.temporal_idxs]
|
self.numpy_inputs[k][:] = self.full_input_queues.get(k)[k]
|
||||||
|
self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention']
|
||||||
|
|
||||||
self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy()
|
self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy()
|
||||||
policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices))
|
policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices))
|
||||||
|
|
||||||
# TODO model only uses last value now
|
|
||||||
self.full_prev_desired_curv[0,:-1] = self.full_prev_desired_curv[0,1:]
|
|
||||||
self.full_prev_desired_curv[0,-1,:] = policy_outputs_dict['desired_curvature'][0, :]
|
|
||||||
self.numpy_inputs['prev_desired_curv'][:] = 0*self.full_prev_desired_curv[0, self.temporal_idxs]
|
|
||||||
|
|
||||||
combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict}
|
combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict}
|
||||||
if SEND_RAW_PRED:
|
if SEND_RAW_PRED:
|
||||||
combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy()])
|
combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy()])
|
||||||
@@ -223,14 +266,14 @@ def main(demo=False):
|
|||||||
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
|
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
|
||||||
|
|
||||||
# messaging
|
# messaging
|
||||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
|
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"])
|
||||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
||||||
|
|
||||||
publish_state = PublishState()
|
publish_state = PublishState()
|
||||||
params = Params()
|
params = Params()
|
||||||
|
|
||||||
# setup filter to track dropped frames
|
# setup filter to track dropped frames
|
||||||
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / ModelConstants.MODEL_FREQ)
|
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / ModelConstants.MODEL_RUN_FREQ)
|
||||||
frame_id = 0
|
frame_id = 0
|
||||||
last_vipc_frame_id = 0
|
last_vipc_frame_id = 0
|
||||||
run_count = 0
|
run_count = 0
|
||||||
@@ -249,8 +292,6 @@ def main(demo=False):
|
|||||||
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.brand)
|
cloudlog.info("modeld got CarParams: %s", CP.brand)
|
||||||
|
|
||||||
modeld_lagd = LagdToggle()
|
|
||||||
|
|
||||||
# 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
|
||||||
# TODO Move smooth seconds to action function
|
# TODO Move smooth seconds to action function
|
||||||
long_delay = CP.longitudinalActuatorDelay + LONG_SMOOTH_SECONDS
|
long_delay = CP.longitudinalActuatorDelay + LONG_SMOOTH_SECONDS
|
||||||
@@ -296,8 +337,9 @@ def main(demo=False):
|
|||||||
is_rhd = sm["driverMonitoringState"].isRHD
|
is_rhd = sm["driverMonitoringState"].isRHD
|
||||||
frame_id = sm["roadCameraState"].frameId
|
frame_id = sm["roadCameraState"].frameId
|
||||||
v_ego = max(sm["carState"].vEgo, 0.)
|
v_ego = max(sm["carState"].vEgo, 0.)
|
||||||
lat_delay = modeld_lagd.lagd_main(CP, sm, model)
|
if sm.frame % 60 == 0:
|
||||||
lateral_control_params = np.array([v_ego, lat_delay], dtype=np.float32)
|
model.lat_delay = get_lat_delay(params, sm["liveDelay"].lateralDelay)
|
||||||
|
lat_delay = model.lat_delay + LAT_SMOOTH_SECONDS
|
||||||
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))]
|
||||||
@@ -328,9 +370,8 @@ def main(demo=False):
|
|||||||
bufs = {name: buf_extra if 'big' in name else buf_main for name in model.vision_input_names}
|
bufs = {name: buf_extra if 'big' in name else buf_main for name in model.vision_input_names}
|
||||||
transforms = {name: model_transform_extra if 'big' in name else model_transform_main for name in model.vision_input_names}
|
transforms = {name: model_transform_extra if 'big' in name else model_transform_main for name in model.vision_input_names}
|
||||||
inputs:dict[str, np.ndarray] = {
|
inputs:dict[str, np.ndarray] = {
|
||||||
'desire': vec_desire,
|
'desire_pulse': vec_desire,
|
||||||
'traffic_convention': traffic_convention,
|
'traffic_convention': traffic_convention,
|
||||||
'lateral_control_params': lateral_control_params,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mt1 = time.perf_counter()
|
mt1 = time.perf_counter()
|
||||||
@@ -342,6 +383,7 @@ def main(demo=False):
|
|||||||
modelv2_send = messaging.new_message('modelV2')
|
modelv2_send = messaging.new_message('modelV2')
|
||||||
drivingdata_send = messaging.new_message('drivingModelData')
|
drivingdata_send = messaging.new_message('drivingModelData')
|
||||||
posenet_send = messaging.new_message('cameraOdometry')
|
posenet_send = messaging.new_message('cameraOdometry')
|
||||||
|
mdv2sp_send = messaging.new_message('modelDataV2SP')
|
||||||
|
|
||||||
action = get_action_from_model(model_output, prev_action, lat_delay + DT_MDL, long_delay + DT_MDL, v_ego)
|
action = get_action_from_model(model_output, prev_action, lat_delay + DT_MDL, long_delay + DT_MDL, v_ego)
|
||||||
prev_action = action
|
prev_action = action
|
||||||
@@ -356,6 +398,7 @@ def main(demo=False):
|
|||||||
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
|
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
|
||||||
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
|
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
|
||||||
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
|
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
|
||||||
|
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction
|
||||||
drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state
|
drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state
|
||||||
drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction
|
drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction
|
||||||
|
|
||||||
@@ -363,6 +406,7 @@ def main(demo=False):
|
|||||||
pm.send('modelV2', modelv2_send)
|
pm.send('modelV2', modelv2_send)
|
||||||
pm.send('drivingModelData', drivingdata_send)
|
pm.send('drivingModelData', drivingdata_send)
|
||||||
pm.send('cameraOdometry', posenet_send)
|
pm.send('cameraOdometry', posenet_send)
|
||||||
|
pm.send('modelDataV2SP', mdv2sp_send)
|
||||||
last_vipc_frame_id = meta_main.frame_id
|
last_vipc_frame_id = meta_main.frame_id
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:22aec22a10ce09384d4a4af2a0bbff08d54af7e0c888503508f356fae4ff0e29
|
oid sha256:ebb38a934d6472c061cc6010f46d9720ca132d631a47e585a893bdd41ade2419
|
||||||
size 15583374
|
size 12343535
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c824f68646a3b94f117f01c70dc8316fb466e05fbd42ccdba440b8a8dc86914b
|
oid sha256:befac016a247b7ad5dc5b55d339d127774ed7bd2b848f1583f72aa4caee37781
|
||||||
size 46265993
|
size 46271991
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ class Parser:
|
|||||||
self.ignore_missing = ignore_missing
|
self.ignore_missing = ignore_missing
|
||||||
|
|
||||||
def check_missing(self, outs, name):
|
def check_missing(self, outs, name):
|
||||||
if name not in outs and not self.ignore_missing:
|
missing = name not in outs
|
||||||
|
if missing and not self.ignore_missing:
|
||||||
raise ValueError(f"Missing output {name}")
|
raise ValueError(f"Missing output {name}")
|
||||||
return name not in outs
|
return missing
|
||||||
|
|
||||||
def parse_categorical_crossentropy(self, name, outs, out_shape=None):
|
def parse_categorical_crossentropy(self, name, outs, out_shape=None):
|
||||||
if self.check_missing(outs, name):
|
if self.check_missing(outs, name):
|
||||||
@@ -84,6 +85,13 @@ class Parser:
|
|||||||
outs[name] = pred_mu_final.reshape(final_shape)
|
outs[name] = pred_mu_final.reshape(final_shape)
|
||||||
outs[name + '_stds'] = pred_std_final.reshape(final_shape)
|
outs[name + '_stds'] = pred_std_final.reshape(final_shape)
|
||||||
|
|
||||||
|
def is_mhp(self, outs, name, shape):
|
||||||
|
if self.check_missing(outs, name):
|
||||||
|
return False
|
||||||
|
if outs[name].shape[1] == 2 * shape:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def parse_vision_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
|
def parse_vision_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
|
||||||
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('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,))
|
||||||
@@ -94,17 +102,17 @@ class Parser:
|
|||||||
self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH))
|
self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH))
|
||||||
self.parse_binary_crossentropy('meta', outs)
|
self.parse_binary_crossentropy('meta', outs)
|
||||||
self.parse_binary_crossentropy('lead_prob', outs)
|
self.parse_binary_crossentropy('lead_prob', outs)
|
||||||
self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION,
|
lead_mhp = self.is_mhp(outs, 'lead', ModelConstants.LEAD_MHP_SELECTION * ModelConstants.LEAD_TRAJ_LEN * ModelConstants.LEAD_WIDTH)
|
||||||
out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH))
|
lead_in_N, lead_out_N = (ModelConstants.LEAD_MHP_N, ModelConstants.LEAD_MHP_SELECTION) if lead_mhp else (0, 0)
|
||||||
|
lead_out_shape = (ModelConstants.LEAD_TRAJ_LEN, ModelConstants.LEAD_WIDTH) if lead_mhp else \
|
||||||
|
(ModelConstants.LEAD_MHP_SELECTION, ModelConstants.LEAD_TRAJ_LEN, ModelConstants.LEAD_WIDTH)
|
||||||
|
self.parse_mdn('lead', outs, in_N=lead_in_N, out_N=lead_out_N, out_shape=lead_out_shape)
|
||||||
return outs
|
return outs
|
||||||
|
|
||||||
def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
|
def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
|
||||||
self.parse_mdn('plan', outs, in_N=ModelConstants.PLAN_MHP_N, out_N=ModelConstants.PLAN_MHP_SELECTION,
|
plan_mhp = self.is_mhp(outs, 'plan', ModelConstants.IDX_N * ModelConstants.PLAN_WIDTH)
|
||||||
out_shape=(ModelConstants.IDX_N,ModelConstants.PLAN_WIDTH))
|
plan_in_N, plan_out_N = (ModelConstants.PLAN_MHP_N, ModelConstants.PLAN_MHP_SELECTION) if plan_mhp else (0, 0)
|
||||||
if 'lat_planner_solution' in outs:
|
self.parse_mdn('plan', outs, in_N=plan_in_N, out_N=plan_out_N, out_shape=(ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH))
|
||||||
self.parse_mdn('lat_planner_solution', outs, in_N=0, out_N=0, out_shape=(ModelConstants.IDX_N,ModelConstants.LAT_PLANNER_SOLUTION_WIDTH))
|
|
||||||
if 'desired_curvature' in outs:
|
|
||||||
self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,))
|
|
||||||
self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,))
|
self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,))
|
||||||
return outs
|
return outs
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,13 @@ Panda *connect(std::string serial="", uint32_t index=0) {
|
|||||||
panda->set_can_fd_auto(i, true);
|
panda->set_can_fd_auto(i, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_supported_panda = std::find(SUPPORTED_PANDA_TYPES.begin(), SUPPORTED_PANDA_TYPES.end(), panda->hw_type) != SUPPORTED_PANDA_TYPES.end();
|
||||||
|
|
||||||
|
if (!is_supported_panda) {
|
||||||
|
LOGW("panda %s is not supported (hw_type: %i), skipping firmware check...", panda->hw_serial().c_str(), static_cast<uint16_t>(panda->hw_type));
|
||||||
|
return panda.release();
|
||||||
|
}
|
||||||
|
|
||||||
if (!panda->up_to_date() && !getenv("BOARDD_SKIP_FW_CHECK")) {
|
if (!panda->up_to_date() && !getenv("BOARDD_SKIP_FW_CHECK")) {
|
||||||
throw std::runtime_error("Panda firmware out of date. Run pandad.py to update.");
|
throw std::runtime_error("Panda firmware out of date. Run pandad.py to update.");
|
||||||
}
|
}
|
||||||
@@ -166,7 +173,6 @@ void fill_panda_state(cereal::PandaState::Builder &ps, cereal::PandaState::Panda
|
|||||||
ps.setHarnessStatus(cereal::PandaState::HarnessStatus(health.car_harness_status_pkt));
|
ps.setHarnessStatus(cereal::PandaState::HarnessStatus(health.car_harness_status_pkt));
|
||||||
ps.setInterruptLoad(health.interrupt_load_pkt);
|
ps.setInterruptLoad(health.interrupt_load_pkt);
|
||||||
ps.setFanPower(health.fan_power);
|
ps.setFanPower(health.fan_power);
|
||||||
ps.setFanStallCount(health.fan_stall_count);
|
|
||||||
ps.setSafetyRxChecksInvalid((bool)(health.safety_rx_checks_invalid_pkt));
|
ps.setSafetyRxChecksInvalid((bool)(health.safety_rx_checks_invalid_pkt));
|
||||||
ps.setSpiErrorCount(health.spi_error_count_pkt);
|
ps.setSpiErrorCount(health.spi_error_count_pkt);
|
||||||
ps.setSbu1Voltage(health.sbu1_voltage_mV / 1000.0f);
|
ps.setSbu1Voltage(health.sbu1_voltage_mV / 1000.0f);
|
||||||
|
|||||||
@@ -8,6 +8,14 @@
|
|||||||
|
|
||||||
void pandad_main_thread(std::vector<std::string> serials);
|
void pandad_main_thread(std::vector<std::string> serials);
|
||||||
|
|
||||||
|
// deprecated devices
|
||||||
|
static const std::vector<cereal::PandaState::PandaType> SUPPORTED_PANDA_TYPES = {
|
||||||
|
cereal::PandaState::PandaType::RED_PANDA,
|
||||||
|
cereal::PandaState::PandaType::TRES,
|
||||||
|
cereal::PandaState::PandaType::CUATRO,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
class PandaSafety {
|
class PandaSafety {
|
||||||
public:
|
public:
|
||||||
PandaSafety(const std::vector<Panda *> &pandas) : pandas_(pandas) {}
|
PandaSafety(const std::vector<Panda *> &pandas) : pandas_(pandas) {}
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ def flash_panda(panda_serial: str) -> Panda:
|
|||||||
HARDWARE.recover_internal_panda()
|
HARDWARE.recover_internal_panda()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
# skip flashing if the detected panda is not supported
|
||||||
|
supported_panda = check_panda_support(panda)
|
||||||
|
if not supported_panda:
|
||||||
|
cloudlog.warning(f"Panda {panda_serial} is not supported (hw_type: {panda.get_type()}), skipping flash...")
|
||||||
|
return panda
|
||||||
|
|
||||||
fw_signature = get_expected_signature(panda)
|
fw_signature = get_expected_signature(panda)
|
||||||
internal_panda = panda.is_internal()
|
internal_panda = panda.is_internal()
|
||||||
|
|
||||||
@@ -61,6 +67,14 @@ def flash_panda(panda_serial: str) -> Panda:
|
|||||||
return panda
|
return panda
|
||||||
|
|
||||||
|
|
||||||
|
def check_panda_support(panda) -> bool:
|
||||||
|
hw_type = panda.get_type()
|
||||||
|
if hw_type in Panda.SUPPORTED_DEVICES:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
# signal pandad to close the relay and exit
|
# signal pandad to close the relay and exit
|
||||||
def signal_handler(signum, frame):
|
def signal_handler(signum, frame):
|
||||||
@@ -85,11 +99,6 @@ def main() -> None:
|
|||||||
cloudlog.event("pandad.flash_and_connect", count=count)
|
cloudlog.event("pandad.flash_and_connect", count=count)
|
||||||
params.remove("PandaSignatures")
|
params.remove("PandaSignatures")
|
||||||
|
|
||||||
# TODO: remove this in the next AGNOS
|
|
||||||
# wait until USB is up before counting
|
|
||||||
if time.monotonic() < 35.:
|
|
||||||
no_internal_panda_count = 0
|
|
||||||
|
|
||||||
# Handle missing internal panda
|
# Handle missing internal panda
|
||||||
if no_internal_panda_count > 0:
|
if no_internal_panda_count > 0:
|
||||||
if no_internal_panda_count == 3:
|
if no_internal_panda_count == 3:
|
||||||
@@ -139,6 +148,12 @@ def main() -> None:
|
|||||||
params.put("PandaSignatures", b','.join(p.get_signature() for p in pandas))
|
params.put("PandaSignatures", b','.join(p.get_signature() for p in pandas))
|
||||||
|
|
||||||
for panda in pandas:
|
for panda in pandas:
|
||||||
|
# skip health check if the detected panda is not supported
|
||||||
|
supported_panda = check_panda_support(panda)
|
||||||
|
if not supported_panda:
|
||||||
|
cloudlog.warning(f"Panda {panda.get_usb_serial()} is not supported (hw_type: {panda.get_type()}), skipping health check...")
|
||||||
|
continue
|
||||||
|
|
||||||
# check health for lost heartbeat
|
# check health for lost heartbeat
|
||||||
health = panda.health()
|
health = panda.health()
|
||||||
if health["heartbeat_lost"]:
|
if health["heartbeat_lost"]:
|
||||||
|
|||||||
+34
-48
@@ -66,58 +66,44 @@ PandaSpiHandle::PandaSpiHandle(std::string serial) : PandaCommsHandle(serial) {
|
|||||||
// 50MHz is the max of the 845. note that some older
|
// 50MHz is the max of the 845. note that some older
|
||||||
// revs of the comma three may not support this speed
|
// revs of the comma three may not support this speed
|
||||||
uint32_t spi_speed = 50000000;
|
uint32_t spi_speed = 50000000;
|
||||||
|
try {
|
||||||
if (!util::file_exists(SPI_DEVICE)) {
|
if (!util::file_exists(SPI_DEVICE)) {
|
||||||
goto fail;
|
throw std::runtime_error("Error connecting to panda: SPI device not found");
|
||||||
}
|
|
||||||
|
|
||||||
spi_fd = open(SPI_DEVICE.c_str(), O_RDWR);
|
|
||||||
if (spi_fd < 0) {
|
|
||||||
LOGE("failed opening SPI device %d", spi_fd);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SPI settings
|
|
||||||
ret = util::safe_ioctl(spi_fd, SPI_IOC_WR_MODE, &spi_mode);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("failed setting SPI mode %d", ret);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = util::safe_ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("failed setting SPI speed");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = util::safe_ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits_per_word);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("failed setting SPI bits per word");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get hw UID/serial
|
|
||||||
ret = control_read(0xc3, 0, 0, uid, uid_len, 100);
|
|
||||||
if (ret == uid_len) {
|
|
||||||
std::stringstream stream;
|
|
||||||
for (int i = 0; i < uid_len; i++) {
|
|
||||||
stream << std::hex << std::setw(2) << std::setfill('0') << int(uid[i]);
|
|
||||||
}
|
}
|
||||||
hw_serial = stream.str();
|
|
||||||
} else {
|
|
||||||
LOGD("failed to get serial %d", ret);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!serial.empty() && (serial != hw_serial)) {
|
spi_fd = open(SPI_DEVICE.c_str(), O_RDWR);
|
||||||
goto fail;
|
if (spi_fd < 0) {
|
||||||
}
|
LOGE("failed opening SPI device %d", spi_fd);
|
||||||
|
throw std::runtime_error("Error connecting to panda: failed to open SPI device");
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPI settings
|
||||||
|
util::safe_ioctl(spi_fd, SPI_IOC_WR_MODE, &spi_mode, "failed setting SPI mode");
|
||||||
|
util::safe_ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed, "failed setting SPI speed");
|
||||||
|
util::safe_ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits_per_word, "failed setting SPI bits per word");
|
||||||
|
|
||||||
|
// get hw UID/serial
|
||||||
|
ret = control_read(0xc3, 0, 0, uid, uid_len, 100);
|
||||||
|
if (ret == uid_len) {
|
||||||
|
std::stringstream stream;
|
||||||
|
for (int i = 0; i < uid_len; i++) {
|
||||||
|
stream << std::hex << std::setw(2) << std::setfill('0') << int(uid[i]);
|
||||||
|
}
|
||||||
|
hw_serial = stream.str();
|
||||||
|
} else {
|
||||||
|
LOGD("failed to get serial %d", ret);
|
||||||
|
throw std::runtime_error("Error connecting to panda: failed to get serial");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!serial.empty() && (serial != hw_serial)) {
|
||||||
|
throw std::runtime_error("Error connecting to panda: serial mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (...) {
|
||||||
|
cleanup();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
|
|
||||||
fail:
|
|
||||||
cleanup();
|
|
||||||
throw std::runtime_error("Error connecting to panda");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PandaSpiHandle::~PandaSpiHandle() {
|
PandaSpiHandle::~PandaSpiHandle() {
|
||||||
|
|||||||
Binary file not shown.
@@ -22,8 +22,6 @@ class TestPandad:
|
|||||||
if len(Panda.list()) == 0:
|
if len(Panda.list()) == 0:
|
||||||
self._run_test(60)
|
self._run_test(60)
|
||||||
|
|
||||||
self.spi = HARDWARE.get_device_type() != 'tici'
|
|
||||||
|
|
||||||
def teardown_method(self):
|
def teardown_method(self):
|
||||||
managed_processes['pandad'].stop()
|
managed_processes['pandad'].stop()
|
||||||
|
|
||||||
@@ -106,11 +104,9 @@ class TestPandad:
|
|||||||
# - 0.2s pandad -> pandad
|
# - 0.2s pandad -> pandad
|
||||||
# - plus some buffer
|
# - plus some buffer
|
||||||
print("startup times", ts, sum(ts) / len(ts))
|
print("startup times", ts, sum(ts) / len(ts))
|
||||||
assert 0.1 < (sum(ts)/len(ts)) < (0.7 if self.spi else 5.0)
|
assert 0.1 < (sum(ts)/len(ts)) < 0.7
|
||||||
|
|
||||||
def test_protocol_version_check(self):
|
def test_protocol_version_check(self):
|
||||||
if not self.spi:
|
|
||||||
pytest.skip("SPI test")
|
|
||||||
# flash old fw
|
# flash old fw
|
||||||
fn = os.path.join(HERE, "bootstub.panda_h7_spiv0.bin")
|
fn = os.path.join(HERE, "bootstub.panda_h7_spiv0.bin")
|
||||||
self._flash_bootstub_and_test(fn, expect_mismatch=True)
|
self._flash_bootstub_and_test(fn, expect_mismatch=True)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import random
|
|||||||
|
|
||||||
import cereal.messaging as messaging
|
import cereal.messaging as messaging
|
||||||
from cereal.services import SERVICE_LIST
|
from cereal.services import SERVICE_LIST
|
||||||
from openpilot.system.hardware import HARDWARE
|
|
||||||
from openpilot.selfdrive.test.helpers import with_processes
|
from openpilot.selfdrive.test.helpers import with_processes
|
||||||
from openpilot.selfdrive.pandad.tests.test_pandad_loopback import setup_pandad, send_random_can_messages
|
from openpilot.selfdrive.pandad.tests.test_pandad_loopback import setup_pandad, send_random_can_messages
|
||||||
|
|
||||||
@@ -16,8 +15,6 @@ JUNGLE_SPAM = "JUNGLE_SPAM" in os.environ
|
|||||||
class TestBoarddSpi:
|
class TestBoarddSpi:
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup_class(cls):
|
def setup_class(cls):
|
||||||
if HARDWARE.get_device_type() == 'tici':
|
|
||||||
pytest.skip("only for spi pandas")
|
|
||||||
os.environ['STARTED'] = '1'
|
os.environ['STARTED'] = '1'
|
||||||
os.environ['SPI_ERR_PROB'] = '0.001'
|
os.environ['SPI_ERR_PROB'] = '0.001'
|
||||||
if not JUNGLE_SPAM:
|
if not JUNGLE_SPAM:
|
||||||
|
|||||||
@@ -29,10 +29,6 @@
|
|||||||
"text": "Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support.",
|
"text": "Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support.",
|
||||||
"severity": 1
|
"severity": 1
|
||||||
},
|
},
|
||||||
"Offroad_StorageMissing": {
|
|
||||||
"text": "NVMe drive not mounted.",
|
|
||||||
"severity": 1
|
|
||||||
},
|
|
||||||
"Offroad_CarUnrecognized": {
|
"Offroad_CarUnrecognized": {
|
||||||
"text": "sunnypilot 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": "sunnypilot 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
|
||||||
@@ -46,8 +42,13 @@
|
|||||||
"severity": 0
|
"severity": 0
|
||||||
},
|
},
|
||||||
"Offroad_ExcessiveActuation": {
|
"Offroad_ExcessiveActuation": {
|
||||||
"text": "openpilot has detected excessive %1 actuation. This may be due to a software bug. Please contact support at https://comma.ai/support.",
|
"text": "openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting.",
|
||||||
"severity": 1,
|
"severity": 1,
|
||||||
"_comment": "Set extra field to lateral or longitudinal."
|
"_comment": "Set extra field to lateral or longitudinal."
|
||||||
|
},
|
||||||
|
"Offroad_TiciSupport": {
|
||||||
|
"text": "<b>Unsupported branch detected</b> - The current version of <b><u>%1</u></b> branch is no longer supported on the comma three. Please go to <b>[Device > Software]</b> and install a supported branch with <b><u>-tici</u></b> in the branch name for the comma three.",
|
||||||
|
"severity": 1,
|
||||||
|
"_comment": "Set extra field to the current branch name."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import os
|
|||||||
|
|
||||||
from cereal import log, car
|
from cereal import log, car
|
||||||
import cereal.messaging as messaging
|
import cereal.messaging as messaging
|
||||||
from openpilot.common.conversions import Conversions as CV
|
from openpilot.common.constants import CV
|
||||||
from openpilot.common.git import get_short_branch
|
from openpilot.common.git import get_short_branch
|
||||||
from openpilot.common.realtime import DT_CTRL
|
from openpilot.common.realtime import DT_CTRL
|
||||||
from openpilot.selfdrive.locationd.calibrationd import MIN_SPEED_FILTER
|
from openpilot.selfdrive.locationd.calibrationd import MIN_SPEED_FILTER
|
||||||
|
from openpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER
|
||||||
|
from openpilot.selfdrive.ui.feedback.feedbackd import FEEDBACK_MAX_DURATION
|
||||||
|
|
||||||
from openpilot.sunnypilot.selfdrive.selfdrived.events_base import EventsBase, Priority, ET, Alert, \
|
from openpilot.sunnypilot.selfdrive.selfdrived.events_base import EventsBase, Priority, ET, Alert, \
|
||||||
NoEntryAlert, SoftDisableAlert, UserSoftDisableAlert, ImmediateDisableAlert, EngagementAlert, NormalPermanentAlert, \
|
NoEntryAlert, SoftDisableAlert, UserSoftDisableAlert, ImmediateDisableAlert, EngagementAlert, NormalPermanentAlert, \
|
||||||
@@ -40,6 +42,7 @@ class Events(EventsBase):
|
|||||||
return log.OnroadEvent
|
return log.OnroadEvent
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ********** helper functions **********
|
# ********** helper functions **********
|
||||||
def get_display_speed(speed_ms: float, metric: bool) -> str:
|
def get_display_speed(speed_ms: float, metric: bool) -> str:
|
||||||
speed = int(round(speed_ms * (CV.MS_TO_KPH if metric else CV.MS_TO_MPH)))
|
speed = int(round(speed_ms * (CV.MS_TO_KPH if metric else CV.MS_TO_MPH)))
|
||||||
@@ -80,7 +83,7 @@ def below_steer_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.S
|
|||||||
f"Steer Unavailable Below {get_display_speed(CP.minSteerSpeed, metric)}",
|
f"Steer Unavailable Below {get_display_speed(CP.minSteerSpeed, metric)}",
|
||||||
"",
|
"",
|
||||||
AlertStatus.userPrompt, AlertSize.small,
|
AlertStatus.userPrompt, AlertSize.small,
|
||||||
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.prompt, 0.4)
|
Priority.LOW, VisualAlert.none, AudibleAlert.prompt, 0.4)
|
||||||
|
|
||||||
|
|
||||||
def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
@@ -92,6 +95,14 @@ def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messag
|
|||||||
Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2)
|
Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2)
|
||||||
|
|
||||||
|
|
||||||
|
def audio_feedback_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
|
duration = FEEDBACK_MAX_DURATION - ((sm['audioFeedback'].blockNum + 1) * SAMPLE_BUFFER / SAMPLE_RATE)
|
||||||
|
return NormalPermanentAlert(
|
||||||
|
"Recording Audio Feedback",
|
||||||
|
f"{round(duration)} second{'s' if round(duration) != 1 else ''} remaining. Press again to save early.",
|
||||||
|
priority=Priority.LOW)
|
||||||
|
|
||||||
|
|
||||||
# *** debug alerts ***
|
# *** debug alerts ***
|
||||||
|
|
||||||
def out_of_space_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def out_of_space_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
@@ -203,6 +214,7 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin
|
|||||||
return NormalPermanentAlert("Invalid LKAS setting", text)
|
return NormalPermanentAlert("Invalid LKAS setting", text)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
||||||
# ********** events with no alerts **********
|
# ********** events with no alerts **********
|
||||||
|
|
||||||
@@ -824,9 +836,13 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
ET.WARNING: personality_changed_alert,
|
ET.WARNING: personality_changed_alert,
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.userFlag: {
|
EventName.userBookmark: {
|
||||||
ET.PERMANENT: NormalPermanentAlert("Bookmark Saved", duration=1.5),
|
ET.PERMANENT: NormalPermanentAlert("Bookmark Saved", duration=1.5),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
EventName.audioFeedback: {
|
||||||
|
ET.PERMANENT: audio_feedback_alert,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import math
|
||||||
|
from enum import StrEnum, auto
|
||||||
|
|
||||||
|
from cereal import car, messaging
|
||||||
|
from openpilot.common.realtime import DT_CTRL
|
||||||
|
from openpilot.selfdrive.locationd.helpers import Pose
|
||||||
|
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY
|
||||||
|
from opendbc.car.lateral import ISO_LATERAL_ACCEL
|
||||||
|
from opendbc.car.interfaces import ACCEL_MIN, ACCEL_MAX
|
||||||
|
|
||||||
|
MIN_EXCESSIVE_ACTUATION_COUNT = int(0.25 / DT_CTRL)
|
||||||
|
MIN_LATERAL_ENGAGE_BUFFER = int(1 / DT_CTRL)
|
||||||
|
|
||||||
|
|
||||||
|
class ExcessiveActuationType(StrEnum):
|
||||||
|
LONGITUDINAL = auto()
|
||||||
|
LATERAL = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class ExcessiveActuationCheck:
|
||||||
|
def __init__(self):
|
||||||
|
self._excessive_counter = 0
|
||||||
|
self._engaged_counter = 0
|
||||||
|
|
||||||
|
def update(self, sm: messaging.SubMaster, CS: car.CarState, calibrated_pose: Pose) -> ExcessiveActuationType | None:
|
||||||
|
# CS.aEgo can be noisy to bumps in the road, transitioning from standstill, losing traction, etc.
|
||||||
|
# longitudinal
|
||||||
|
accel_calibrated = calibrated_pose.acceleration.x
|
||||||
|
excessive_long_actuation = sm['carControl'].longActive and (accel_calibrated > ACCEL_MAX * 2 or accel_calibrated < ACCEL_MIN * 2)
|
||||||
|
|
||||||
|
# lateral
|
||||||
|
yaw_rate = calibrated_pose.angular_velocity.yaw
|
||||||
|
roll = sm['liveParameters'].roll
|
||||||
|
roll_compensated_lateral_accel = (CS.vEgo * yaw_rate) - (math.sin(roll) * ACCELERATION_DUE_TO_GRAVITY)
|
||||||
|
|
||||||
|
# Prevent false positives after overriding
|
||||||
|
excessive_lat_actuation = False
|
||||||
|
self._engaged_counter = self._engaged_counter + 1 if sm['carControl'].latActive and not CS.steeringPressed else 0
|
||||||
|
if self._engaged_counter > MIN_LATERAL_ENGAGE_BUFFER:
|
||||||
|
if abs(roll_compensated_lateral_accel) > ISO_LATERAL_ACCEL * 2:
|
||||||
|
excessive_lat_actuation = True
|
||||||
|
|
||||||
|
# livePose acceleration can be noisy due to bad mounting or aliased livePose measurements
|
||||||
|
livepose_valid = abs(CS.aEgo - accel_calibrated) < 2
|
||||||
|
self._excessive_counter = self._excessive_counter + 1 if livepose_valid and (excessive_long_actuation or excessive_lat_actuation) else 0
|
||||||
|
|
||||||
|
excessive_type = None
|
||||||
|
if self._excessive_counter > MIN_EXCESSIVE_ACTUATION_COUNT:
|
||||||
|
if excessive_long_actuation:
|
||||||
|
excessive_type = ExcessiveActuationType.LONGITUDINAL
|
||||||
|
else:
|
||||||
|
excessive_type = ExcessiveActuationType.LATERAL
|
||||||
|
|
||||||
|
return excessive_type
|
||||||
@@ -7,7 +7,6 @@ import cereal.messaging as messaging
|
|||||||
|
|
||||||
from cereal import car, log, custom
|
from cereal import car, log, custom
|
||||||
from msgq.visionipc import VisionIpcClient, VisionStreamType
|
from msgq.visionipc import VisionIpcClient, VisionStreamType
|
||||||
from opendbc.car.interfaces import ACCEL_MIN, ACCEL_MAX
|
|
||||||
|
|
||||||
|
|
||||||
from openpilot.common.params import Params
|
from openpilot.common.params import Params
|
||||||
@@ -18,15 +17,16 @@ from openpilot.common.gps import get_gps_location_service
|
|||||||
from openpilot.selfdrive.car.car_specific import CarSpecificEvents
|
from openpilot.selfdrive.car.car_specific import CarSpecificEvents
|
||||||
from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose
|
from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose
|
||||||
from openpilot.selfdrive.selfdrived.events import Events, ET
|
from openpilot.selfdrive.selfdrived.events import Events, ET
|
||||||
|
from openpilot.selfdrive.selfdrived.helpers import ExcessiveActuationCheck
|
||||||
from openpilot.selfdrive.selfdrived.state import StateMachine
|
from openpilot.selfdrive.selfdrived.state import StateMachine
|
||||||
from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroad_alert
|
from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroad_alert
|
||||||
|
|
||||||
from openpilot.system.hardware import HARDWARE
|
|
||||||
from openpilot.system.version import get_build_metadata
|
from openpilot.system.version import get_build_metadata
|
||||||
|
|
||||||
from openpilot.sunnypilot.mads.mads import ModularAssistiveDrivingSystem
|
from openpilot.sunnypilot.mads.mads import ModularAssistiveDrivingSystem
|
||||||
from openpilot.sunnypilot.selfdrive.car.car_specific import CarSpecificEventsSP
|
from openpilot.sunnypilot.selfdrive.car.car_specific import CarSpecificEventsSP
|
||||||
from openpilot.sunnypilot.selfdrive.car.cruise_helpers import CruiseHelper
|
from openpilot.sunnypilot.selfdrive.car.cruise_helpers import CruiseHelper
|
||||||
|
from openpilot.sunnypilot.selfdrive.car.intelligent_cruise_button_management.controller import IntelligentCruiseButtonManagement
|
||||||
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
|
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
|
||||||
|
|
||||||
REPLAY = "REPLAY" in os.environ
|
REPLAY = "REPLAY" in os.environ
|
||||||
@@ -34,7 +34,6 @@ SIMULATION = "SIMULATION" in os.environ
|
|||||||
TESTING_CLOSET = "TESTING_CLOSET" in os.environ
|
TESTING_CLOSET = "TESTING_CLOSET" in os.environ
|
||||||
|
|
||||||
LONGITUDINAL_PERSONALITY_MAP = {v: k for k, v in log.LongitudinalPersonality.schema.enumerants.items()}
|
LONGITUDINAL_PERSONALITY_MAP = {v: k for k, v in log.LongitudinalPersonality.schema.enumerants.items()}
|
||||||
MIN_EXCESSIVE_ACTUATION_COUNT = int(0.25 / DT_CTRL)
|
|
||||||
|
|
||||||
ThermalStatus = log.DeviceState.ThermalStatus
|
ThermalStatus = log.DeviceState.ThermalStatus
|
||||||
State = log.SelfdriveState.OpenpilotState
|
State = log.SelfdriveState.OpenpilotState
|
||||||
@@ -48,21 +47,6 @@ SafetyModel = car.CarParams.SafetyModel
|
|||||||
IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
|
IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
|
||||||
|
|
||||||
|
|
||||||
def check_excessive_actuation(sm: messaging.SubMaster, CS: car.CarState, calibrator: PoseCalibrator, counter: int) -> tuple[int, bool]:
|
|
||||||
# CS.aEgo can be noisy to bumps in the road, transitioning from standstill, losing traction, etc.
|
|
||||||
device_pose = Pose.from_live_pose(sm['livePose'])
|
|
||||||
calibrated_pose = calibrator.build_calibrated_pose(device_pose)
|
|
||||||
accel_calibrated = calibrated_pose.acceleration.x
|
|
||||||
|
|
||||||
# livePose acceleration can be noisy due to bad mounting or aliased livePose measurements
|
|
||||||
accel_valid = abs(CS.aEgo - accel_calibrated) < 2
|
|
||||||
|
|
||||||
excessive_actuation = accel_calibrated > ACCEL_MAX * 2 or accel_calibrated < ACCEL_MIN * 2
|
|
||||||
counter = counter + 1 if sm['carControl'].longActive and excessive_actuation and accel_valid else 0
|
|
||||||
|
|
||||||
return counter, counter > MIN_EXCESSIVE_ACTUATION_COUNT
|
|
||||||
|
|
||||||
|
|
||||||
class SelfdriveD(CruiseHelper):
|
class SelfdriveD(CruiseHelper):
|
||||||
def __init__(self, CP=None, CP_SP=None):
|
def __init__(self, CP=None, CP_SP=None):
|
||||||
self.params = Params()
|
self.params = Params()
|
||||||
@@ -85,7 +69,11 @@ class SelfdriveD(CruiseHelper):
|
|||||||
self.CP_SP = CP_SP
|
self.CP_SP = CP_SP
|
||||||
|
|
||||||
self.car_events = CarSpecificEvents(self.CP)
|
self.car_events = CarSpecificEvents(self.CP)
|
||||||
self.calibrator = PoseCalibrator()
|
|
||||||
|
self.pose_calibrator = PoseCalibrator()
|
||||||
|
self.calibrated_pose: Pose | None = None
|
||||||
|
self.excessive_actuation_check = ExcessiveActuationCheck()
|
||||||
|
self.excessive_actuation = self.params.get("Offroad_ExcessiveActuation") is not None
|
||||||
|
|
||||||
# Setup sockets
|
# Setup sockets
|
||||||
self.pm = messaging.PubMaster(['selfdriveState', 'onroadEvents'] + ['selfdriveStateSP', 'onroadEventsSP'])
|
self.pm = messaging.PubMaster(['selfdriveState', 'onroadEvents'] + ['selfdriveStateSP', 'onroadEventsSP'])
|
||||||
@@ -98,7 +86,7 @@ class SelfdriveD(CruiseHelper):
|
|||||||
# TODO: de-couple selfdrived with card/conflate on carState without introducing controls mismatches
|
# TODO: de-couple selfdrived with card/conflate on carState without introducing controls mismatches
|
||||||
self.car_state_sock = messaging.sub_sock('carState', timeout=20)
|
self.car_state_sock = messaging.sub_sock('carState', timeout=20)
|
||||||
|
|
||||||
ignore = self.sensor_packets + self.gps_packets + ['alertDebug']
|
ignore = self.sensor_packets + self.gps_packets + ['alertDebug'] + ['modelDataV2SP']
|
||||||
if SIMULATION:
|
if SIMULATION:
|
||||||
ignore += ['driverCameraState', 'managerState']
|
ignore += ['driverCameraState', 'managerState']
|
||||||
if REPLAY:
|
if REPLAY:
|
||||||
@@ -107,7 +95,8 @@ class SelfdriveD(CruiseHelper):
|
|||||||
self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration',
|
self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration',
|
||||||
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay',
|
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay',
|
||||||
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
|
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
|
||||||
'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userFlag'] + \
|
'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback',
|
||||||
|
'modelDataV2SP', 'longitudinalPlanSP'] + \
|
||||||
self.camera_packets + self.sensor_packets + self.gps_packets,
|
self.camera_packets + self.sensor_packets + self.gps_packets,
|
||||||
ignore_alive=ignore, ignore_avg_freq=ignore,
|
ignore_alive=ignore, ignore_avg_freq=ignore,
|
||||||
ignore_valid=ignore, frequency=int(1/DT_CTRL))
|
ignore_valid=ignore, frequency=int(1/DT_CTRL))
|
||||||
@@ -143,18 +132,10 @@ class SelfdriveD(CruiseHelper):
|
|||||||
self.experimental_mode = False
|
self.experimental_mode = False
|
||||||
self.personality = self.params.get("LongitudinalPersonality", return_default=True)
|
self.personality = self.params.get("LongitudinalPersonality", return_default=True)
|
||||||
self.recalibrating_seen = False
|
self.recalibrating_seen = False
|
||||||
self.excessive_actuation = self.params.get("Offroad_ExcessiveActuation") is not None
|
|
||||||
self.excessive_actuation_counter = 0
|
|
||||||
self.state_machine = StateMachine()
|
self.state_machine = StateMachine()
|
||||||
self.rk = Ratekeeper(100, print_delay_threshold=None)
|
self.rk = Ratekeeper(100, print_delay_threshold=None)
|
||||||
|
|
||||||
# some comma three with NVMe experience NVMe dropouts mid-drive that
|
self.ignored_processes = {'mapd', }
|
||||||
# cause loggerd to crash on write, so ignore it only on that platform
|
|
||||||
self.ignored_processes = set()
|
|
||||||
nvme_expected = os.path.exists('/dev/nvme0n1') or (not os.path.isfile("/persist/comma/living-in-the-moment"))
|
|
||||||
if HARDWARE.get_device_type() == 'tici' and nvme_expected:
|
|
||||||
self.ignored_processes = {'loggerd', }
|
|
||||||
self.ignored_processes.update({'mapd'})
|
|
||||||
|
|
||||||
# Determine startup event
|
# Determine startup event
|
||||||
is_remote = build_metadata.openpilot.comma_remote or build_metadata.openpilot.sunnypilot_remote
|
is_remote = build_metadata.openpilot.comma_remote or build_metadata.openpilot.sunnypilot_remote
|
||||||
@@ -176,8 +157,9 @@ class SelfdriveD(CruiseHelper):
|
|||||||
self.events_sp_prev = []
|
self.events_sp_prev = []
|
||||||
|
|
||||||
self.mads = ModularAssistiveDrivingSystem(self)
|
self.mads = ModularAssistiveDrivingSystem(self)
|
||||||
|
self.icbm = IntelligentCruiseButtonManagement(self.CP, self.CP_SP)
|
||||||
|
|
||||||
self.car_events_sp = CarSpecificEventsSP(self.CP, self.params)
|
self.car_events_sp = CarSpecificEventsSP(self.CP, self.CP_SP)
|
||||||
|
|
||||||
CruiseHelper.__init__(self, self.CP)
|
CruiseHelper.__init__(self, self.CP)
|
||||||
|
|
||||||
@@ -205,9 +187,12 @@ class SelfdriveD(CruiseHelper):
|
|||||||
self.events.add(EventName.selfdriveInitializing)
|
self.events.add(EventName.selfdriveInitializing)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check for user flag (bookmark) press
|
# Check for user bookmark press (bookmark button or end of LKAS button feedback)
|
||||||
if self.sm.updated['userFlag']:
|
if self.sm.updated['userBookmark']:
|
||||||
self.events.add(EventName.userFlag)
|
self.events.add(EventName.userBookmark)
|
||||||
|
|
||||||
|
if self.sm.updated['audioFeedback']:
|
||||||
|
self.events.add(EventName.audioFeedback)
|
||||||
|
|
||||||
# Don't add any more events while in dashcam mode
|
# Don't add any more events while in dashcam mode
|
||||||
if self.CP.passive:
|
if self.CP.passive:
|
||||||
@@ -220,6 +205,7 @@ class SelfdriveD(CruiseHelper):
|
|||||||
|
|
||||||
if not self.CP.notCar:
|
if not self.CP.notCar:
|
||||||
self.events.add_from_msg(self.sm['driverMonitoringState'].events)
|
self.events.add_from_msg(self.sm['driverMonitoringState'].events)
|
||||||
|
self.events_sp.add_from_msg(self.sm['longitudinalPlanSP'].events)
|
||||||
|
|
||||||
# Add car events, ignore if CAN isn't valid
|
# Add car events, ignore if CAN isn't valid
|
||||||
if CS.canValid:
|
if CS.canValid:
|
||||||
@@ -276,17 +262,25 @@ class SelfdriveD(CruiseHelper):
|
|||||||
if self.sm['driverAssistance'].leftLaneDeparture or self.sm['driverAssistance'].rightLaneDeparture:
|
if self.sm['driverAssistance'].leftLaneDeparture or self.sm['driverAssistance'].rightLaneDeparture:
|
||||||
self.events.add(EventName.ldw)
|
self.events.add(EventName.ldw)
|
||||||
|
|
||||||
# Check for excessive (longitudinal) actuation
|
# ******************************************************************************************
|
||||||
|
# NOTE: To fork maintainers.
|
||||||
|
# Disabling or nerfing safety features will get you and your users banned from our servers.
|
||||||
|
# We recommend that you do not change these numbers from the defaults.
|
||||||
if self.sm.updated['liveCalibration']:
|
if self.sm.updated['liveCalibration']:
|
||||||
self.calibrator.feed_live_calib(self.sm['liveCalibration'])
|
self.pose_calibrator.feed_live_calib(self.sm['liveCalibration'])
|
||||||
|
if self.sm.updated['livePose']:
|
||||||
|
device_pose = Pose.from_live_pose(self.sm['livePose'])
|
||||||
|
self.calibrated_pose = self.pose_calibrator.build_calibrated_pose(device_pose)
|
||||||
|
|
||||||
self.excessive_actuation_counter, excessive_actuation = check_excessive_actuation(self.sm, CS, self.calibrator, self.excessive_actuation_counter)
|
if self.calibrated_pose is not None:
|
||||||
if not self.excessive_actuation and excessive_actuation:
|
excessive_actuation = self.excessive_actuation_check.update(self.sm, CS, self.calibrated_pose)
|
||||||
set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text="longitudinal")
|
if not self.excessive_actuation and excessive_actuation is not None:
|
||||||
self.excessive_actuation = True
|
set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text=str(excessive_actuation))
|
||||||
|
self.excessive_actuation = True
|
||||||
|
|
||||||
if self.excessive_actuation:
|
if self.excessive_actuation:
|
||||||
self.events.add(EventName.excessiveActuation)
|
self.events.add(EventName.excessiveActuation)
|
||||||
|
# ******************************************************************************************
|
||||||
|
|
||||||
# Handle lane change
|
# Handle lane change
|
||||||
if self.sm['modelV2'].meta.laneChangeState == LaneChangeState.preLaneChange:
|
if self.sm['modelV2'].meta.laneChangeState == LaneChangeState.preLaneChange:
|
||||||
@@ -303,6 +297,13 @@ class SelfdriveD(CruiseHelper):
|
|||||||
LaneChangeState.laneChangeFinishing):
|
LaneChangeState.laneChangeFinishing):
|
||||||
self.events.add(EventName.laneChange)
|
self.events.add(EventName.laneChange)
|
||||||
|
|
||||||
|
# Handle lane turn
|
||||||
|
lane_turn_direction = self.sm['modelDataV2SP'].laneTurnDirection
|
||||||
|
if lane_turn_direction == custom.TurnDirection.turnLeft:
|
||||||
|
self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnLeft)
|
||||||
|
elif lane_turn_direction == custom.TurnDirection.turnRight:
|
||||||
|
self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnRight)
|
||||||
|
|
||||||
for i, pandaState in enumerate(self.sm['pandaStates']):
|
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
|
# All pandas must match the list of safetyConfigs, and if outside this list, must be silent or noOutput
|
||||||
if i < len(self.CP.safetyConfigs):
|
if i < len(self.CP.safetyConfigs):
|
||||||
@@ -339,13 +340,12 @@ class SelfdriveD(CruiseHelper):
|
|||||||
self.events.add(EventName.cameraFrameRate)
|
self.events.add(EventName.cameraFrameRate)
|
||||||
if not REPLAY and self.rk.lagging:
|
if not REPLAY and self.rk.lagging:
|
||||||
self.events.add(EventName.selfdrivedLagging)
|
self.events.add(EventName.selfdrivedLagging)
|
||||||
if not self.sm.valid['radarState']:
|
if self.sm['radarState'].radarErrors.canError:
|
||||||
if self.sm['radarState'].radarErrors.canError:
|
self.events.add(EventName.canError)
|
||||||
self.events.add(EventName.canError)
|
elif self.sm['radarState'].radarErrors.radarUnavailableTemporary:
|
||||||
elif self.sm['radarState'].radarErrors.radarUnavailableTemporary:
|
self.events.add(EventName.radarTempUnavailable)
|
||||||
self.events.add(EventName.radarTempUnavailable)
|
elif any(self.sm['radarState'].radarErrors.to_dict().values()):
|
||||||
else:
|
self.events.add(EventName.radarFault)
|
||||||
self.events.add(EventName.radarFault)
|
|
||||||
if not self.sm.valid['pandaStates']:
|
if not self.sm.valid['pandaStates']:
|
||||||
self.events.add(EventName.usbError)
|
self.events.add(EventName.usbError)
|
||||||
if CS.canTimeout:
|
if CS.canTimeout:
|
||||||
@@ -445,6 +445,8 @@ class SelfdriveD(CruiseHelper):
|
|||||||
self.events.add(EventName.personalityChanged)
|
self.events.add(EventName.personalityChanged)
|
||||||
self.experimental_mode_switched = False
|
self.experimental_mode_switched = False
|
||||||
|
|
||||||
|
self.icbm.run(CS, self.sm['carControl'], self.sm['longitudinalPlanSP'], self.is_metric)
|
||||||
|
|
||||||
def data_sample(self):
|
def data_sample(self):
|
||||||
_car_state = messaging.recv_one(self.car_state_sock)
|
_car_state = messaging.recv_one(self.car_state_sock)
|
||||||
CS = _car_state.carState if _car_state else self.CS_prev
|
CS = _car_state.carState if _car_state else self.CS_prev
|
||||||
@@ -549,6 +551,11 @@ class SelfdriveD(CruiseHelper):
|
|||||||
mads.active = self.mads.active
|
mads.active = self.mads.active
|
||||||
mads.available = self.mads.enabled_toggle
|
mads.available = self.mads.enabled_toggle
|
||||||
|
|
||||||
|
icbm = ss_sp.intelligentCruiseButtonManagement
|
||||||
|
icbm.state = self.icbm.state
|
||||||
|
icbm.sendButton = self.icbm.cruise_button
|
||||||
|
icbm.vTarget = self.icbm.v_target
|
||||||
|
|
||||||
self.pm.send('selfdriveStateSP', ss_sp_msg)
|
self.pm.send('selfdriveStateSP', ss_sp_msg)
|
||||||
|
|
||||||
# onroadEventsSP - logged every second or on change
|
# onroadEventsSP - logged every second or on change
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ class Plant:
|
|||||||
lp = messaging.new_message('liveParameters')
|
lp = messaging.new_message('liveParameters')
|
||||||
car_control = messaging.new_message('carControl')
|
car_control = messaging.new_message('carControl')
|
||||||
model = messaging.new_message('modelV2')
|
model = messaging.new_message('modelV2')
|
||||||
|
car_state_sp = messaging.new_message('carStateSP')
|
||||||
|
live_map_data_sp = messaging.new_message('liveMapDataSP')
|
||||||
|
gps_data = messaging.new_message('gpsLocation')
|
||||||
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
|
||||||
|
|
||||||
@@ -133,7 +136,10 @@ class Plant:
|
|||||||
'controlsState': control.controlsState,
|
'controlsState': control.controlsState,
|
||||||
'selfdriveState': ss.selfdriveState,
|
'selfdriveState': ss.selfdriveState,
|
||||||
'liveParameters': lp.liveParameters,
|
'liveParameters': lp.liveParameters,
|
||||||
'modelV2': model.modelV2}
|
'modelV2': model.modelV2,
|
||||||
|
'carStateSP': car_state_sp.carStateSP,
|
||||||
|
'liveMapDataSP': live_map_data_sp.liveMapDataSP,
|
||||||
|
'gpsLocation': gps_data.gpsLocation}
|
||||||
self.planner.update(sm)
|
self.planner.update(sm)
|
||||||
self.acceleration = self.planner.output_a_target
|
self.acceleration = self.planner.output_a_target
|
||||||
self.speed = self.speed + self.acceleration * self.ts
|
self.speed = self.speed + self.acceleration * self.ts
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ MigrationFunc = Callable[[list[MessageWithIndex]], MigrationOps]
|
|||||||
# 3. product is the message type created by the migration function, and the function will be skipped if product type already exists in lr
|
# 3. product is the message type created by the migration function, and the function will be skipped if product type already exists in lr
|
||||||
# 4. it must return a list of operations to be applied to the logreader (replace, add, delete)
|
# 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
|
# 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):
|
def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: bool = False, camera_states: bool = False,
|
||||||
|
live_location_kalman: bool = True):
|
||||||
migrations = [
|
migrations = [
|
||||||
migrate_sensorEvents,
|
migrate_sensorEvents,
|
||||||
migrate_carParams,
|
migrate_carParams,
|
||||||
@@ -37,7 +38,6 @@ def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: boo
|
|||||||
migrate_carOutput,
|
migrate_carOutput,
|
||||||
migrate_controlsState,
|
migrate_controlsState,
|
||||||
migrate_carState,
|
migrate_carState,
|
||||||
migrate_liveLocationKalman,
|
|
||||||
migrate_liveTracks,
|
migrate_liveTracks,
|
||||||
migrate_driverAssistance,
|
migrate_driverAssistance,
|
||||||
migrate_drivingModelData,
|
migrate_drivingModelData,
|
||||||
@@ -51,6 +51,8 @@ def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: boo
|
|||||||
migrations.extend([migrate_pandaStates, migrate_peripheralState])
|
migrations.extend([migrate_pandaStates, migrate_peripheralState])
|
||||||
if camera_states:
|
if camera_states:
|
||||||
migrations.append(migrate_cameraStates)
|
migrations.append(migrate_cameraStates)
|
||||||
|
if live_location_kalman:
|
||||||
|
migrations.append(migrate_liveLocationKalman)
|
||||||
|
|
||||||
return migrate(lr, migrations)
|
return migrate(lr, migrations)
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import time
|
|||||||
import copy
|
import copy
|
||||||
import heapq
|
import heapq
|
||||||
import signal
|
import signal
|
||||||
from collections import Counter, OrderedDict
|
from collections import Counter
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
from itertools import islice
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from collections.abc import Callable, Iterable
|
from collections.abc import Callable, Iterable
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
@@ -16,12 +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.can_definitions import CanData
|
||||||
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 openpilot.selfdrive.car.card import can_comm_callbacks, convert_to_capnp
|
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, 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
|
||||||
@@ -30,22 +32,10 @@ from openpilot.tools.lib.logreader import LogIterable
|
|||||||
from openpilot.tools.lib.framereader import FrameReader
|
from openpilot.tools.lib.framereader import FrameReader
|
||||||
|
|
||||||
# Numpy gives different results based on CPU features after version 19
|
# Numpy gives different results based on CPU features after version 19
|
||||||
NUMPY_TOLERANCE = 1e-7
|
NUMPY_TOLERANCE = 1e-2
|
||||||
PROC_REPLAY_DIR = os.path.dirname(os.path.abspath(__file__))
|
PROC_REPLAY_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
FAKEDATA = os.path.join(PROC_REPLAY_DIR, "fakedata/")
|
FAKEDATA = os.path.join(PROC_REPLAY_DIR, "fakedata/")
|
||||||
|
|
||||||
class DummySocket:
|
|
||||||
def __init__(self):
|
|
||||||
self.data: list[bytes] = []
|
|
||||||
|
|
||||||
def receive(self, non_blocking: bool = False) -> bytes | None:
|
|
||||||
if non_blocking:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return self.data.pop()
|
|
||||||
|
|
||||||
def send(self, data: bytes):
|
|
||||||
self.data.append(data)
|
|
||||||
|
|
||||||
class LauncherWithCapture:
|
class LauncherWithCapture:
|
||||||
def __init__(self, capture: ProcessOutputCapture, launcher: Callable):
|
def __init__(self, capture: ProcessOutputCapture, launcher: Callable):
|
||||||
@@ -63,8 +53,7 @@ class ReplayContext:
|
|||||||
self.pubs = cfg.pubs
|
self.pubs = cfg.pubs
|
||||||
self.main_pub = cfg.main_pub
|
self.main_pub = cfg.main_pub
|
||||||
self.main_pub_drained = cfg.main_pub_drained
|
self.main_pub_drained = cfg.main_pub_drained
|
||||||
self.unlocked_pubs = cfg.unlocked_pubs
|
assert len(self.pubs) != 0 or self.main_pub is not None
|
||||||
assert(len(self.pubs) != 0 or self.main_pub is not None)
|
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.open_context()
|
self.open_context()
|
||||||
@@ -79,9 +68,8 @@ class ReplayContext:
|
|||||||
messaging.set_fake_prefix(self.proc_name)
|
messaging.set_fake_prefix(self.proc_name)
|
||||||
|
|
||||||
if self.main_pub is None:
|
if self.main_pub is None:
|
||||||
self.events = OrderedDict()
|
self.events = {}
|
||||||
pubs_with_events = [pub for pub in self.pubs if pub not in self.unlocked_pubs]
|
for pub in self.pubs:
|
||||||
for pub in pubs_with_events:
|
|
||||||
self.events[pub] = messaging.fake_event_handle(pub, enable=True)
|
self.events[pub] = messaging.fake_event_handle(pub, enable=True)
|
||||||
else:
|
else:
|
||||||
self.events = {self.main_pub: messaging.fake_event_handle(self.main_pub, enable=True)}
|
self.events = {self.main_pub: messaging.fake_event_handle(self.main_pub, enable=True)}
|
||||||
@@ -138,16 +126,21 @@ class ProcessConfig:
|
|||||||
processing_time: float = 0.001
|
processing_time: float = 0.001
|
||||||
timeout: int = 30
|
timeout: int = 30
|
||||||
simulation: bool = True
|
simulation: bool = True
|
||||||
|
# Set to service process receives on first
|
||||||
main_pub: str | None = None
|
main_pub: str | None = None
|
||||||
main_pub_drained: bool = True
|
main_pub_drained: bool = False
|
||||||
vision_pubs: list[str] = field(default_factory=list)
|
vision_pubs: list[str] = field(default_factory=list)
|
||||||
ignore_alive_pubs: list[str] = field(default_factory=list)
|
ignore_alive_pubs: list[str] = field(default_factory=list)
|
||||||
unlocked_pubs: list[str] = field(default_factory=list)
|
|
||||||
|
def __post_init__(self):
|
||||||
|
# If the process is polling a service, we can just lock that one to speed up replay
|
||||||
|
if self.main_pub is None and isinstance(self.should_recv_callback, MessageBasedRcvCallback):
|
||||||
|
self.main_pub = self.should_recv_callback.trigger_msg_type
|
||||||
|
|
||||||
|
|
||||||
class ProcessContainer:
|
class ProcessContainer:
|
||||||
def __init__(self, cfg: ProcessConfig):
|
def __init__(self, cfg: ProcessConfig):
|
||||||
self.prefix = OpenpilotPrefix(clean_dirs_on_exit=False)
|
self.prefix = OpenpilotPrefix(create_dirs_on_enter=False, clean_dirs_on_exit=False)
|
||||||
self.cfg = copy.deepcopy(cfg)
|
self.cfg = copy.deepcopy(cfg)
|
||||||
self.process = copy.deepcopy(managed_processes[cfg.proc_name])
|
self.process = copy.deepcopy(managed_processes[cfg.proc_name])
|
||||||
self.msg_queue: list[capnp._DynamicStructReader] = []
|
self.msg_queue: list[capnp._DynamicStructReader] = []
|
||||||
@@ -229,6 +222,7 @@ class ProcessContainer:
|
|||||||
fingerprint: str | None, capture_output: bool
|
fingerprint: str | None, capture_output: bool
|
||||||
):
|
):
|
||||||
with self.prefix as p:
|
with self.prefix as p:
|
||||||
|
self.prefix.create_dirs()
|
||||||
self._setup_env(params_config, environ_config)
|
self._setup_env(params_config, environ_config)
|
||||||
|
|
||||||
if self.cfg.config_callback is not None:
|
if self.cfg.config_callback is not None:
|
||||||
@@ -254,11 +248,6 @@ class ProcessContainer:
|
|||||||
if self.cfg.init_callback is not None:
|
if self.cfg.init_callback is not None:
|
||||||
self.cfg.init_callback(self.rc, self.pm, all_msgs, fingerprint)
|
self.cfg.init_callback(self.rc, self.pm, all_msgs, fingerprint)
|
||||||
|
|
||||||
# wait for process to startup
|
|
||||||
with Timeout(10, error_msg=f"timed out waiting for process to start: {repr(self.cfg.proc_name)}"):
|
|
||||||
while not all(self.pm.all_readers_updated(s) for s in self.cfg.pubs if s not in self.cfg.ignore_alive_pubs):
|
|
||||||
time.sleep(0)
|
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
with self.prefix:
|
with self.prefix:
|
||||||
self.process.signal(signal.SIGKILL)
|
self.process.signal(signal.SIGKILL)
|
||||||
@@ -267,28 +256,42 @@ class ProcessContainer:
|
|||||||
self.prefix.clean_dirs()
|
self.prefix.clean_dirs()
|
||||||
self._clean_env()
|
self._clean_env()
|
||||||
|
|
||||||
|
def get_output_msgs(self, start_time: int):
|
||||||
|
assert self.rc and self.sockets
|
||||||
|
|
||||||
|
output_msgs = []
|
||||||
|
self.rc.wait_for_recv_called()
|
||||||
|
for socket in self.sockets:
|
||||||
|
ms = messaging.drain_sock(socket)
|
||||||
|
for m in ms:
|
||||||
|
m = m.as_builder()
|
||||||
|
m.logMonoTime = start_time + int(self.cfg.processing_time * 1e9)
|
||||||
|
output_msgs.append(m.as_reader())
|
||||||
|
return output_msgs
|
||||||
|
|
||||||
def run_step(self, msg: capnp._DynamicStructReader, frs: dict[str, FrameReader] | None) -> list[capnp._DynamicStructReader]:
|
def run_step(self, msg: capnp._DynamicStructReader, frs: dict[str, FrameReader] | None) -> list[capnp._DynamicStructReader]:
|
||||||
assert self.rc and self.pm and self.sockets and self.process.proc
|
assert self.rc and self.pm and self.sockets and self.process.proc
|
||||||
|
|
||||||
output_msgs = []
|
output_msgs = []
|
||||||
with self.prefix, Timeout(self.cfg.timeout, error_msg=f"timed out testing process {repr(self.cfg.proc_name)}"):
|
end_of_cycle = True
|
||||||
end_of_cycle = True
|
if self.cfg.should_recv_callback is not None:
|
||||||
if self.cfg.should_recv_callback is not None:
|
end_of_cycle = self.cfg.should_recv_callback(msg, self.cfg, self.cnt)
|
||||||
end_of_cycle = self.cfg.should_recv_callback(msg, self.cfg, self.cnt)
|
|
||||||
|
|
||||||
self.msg_queue.append(msg)
|
|
||||||
if end_of_cycle:
|
|
||||||
self.rc.wait_for_recv_called()
|
|
||||||
|
|
||||||
|
self.msg_queue.append(msg)
|
||||||
|
if end_of_cycle:
|
||||||
|
with self.prefix, Timeout(self.cfg.timeout, error_msg=f"timed out testing process {repr(self.cfg.proc_name)}"):
|
||||||
# call recv to let sub-sockets reconnect, after we know the process is ready
|
# call recv to let sub-sockets reconnect, after we know the process is ready
|
||||||
if self.cnt == 0:
|
if self.cnt == 0:
|
||||||
for s in self.sockets:
|
for s in self.sockets:
|
||||||
messaging.recv_one_or_none(s)
|
messaging.recv_one_or_none(s)
|
||||||
|
|
||||||
# empty recv on drained pub indicates the end of messages, only do that if there're any
|
# certain processes use drain_sock. need to cause empty recv to break from this loop
|
||||||
trigger_empty_recv = False
|
trigger_empty_recv = False
|
||||||
if self.cfg.main_pub and self.cfg.main_pub_drained:
|
if self.cfg.main_pub and self.cfg.main_pub_drained:
|
||||||
trigger_empty_recv = next((True for m in self.msg_queue if m.which() == self.cfg.main_pub), False)
|
trigger_empty_recv = any(m.which() == self.cfg.main_pub for m in self.msg_queue)
|
||||||
|
|
||||||
|
# get output msgs from previous inputs
|
||||||
|
output_msgs = self.get_output_msgs(msg.logMonoTime)
|
||||||
|
|
||||||
for m in self.msg_queue:
|
for m in self.msg_queue:
|
||||||
self.pm.send(m.which(), m.as_builder())
|
self.pm.send(m.which(), m.as_builder())
|
||||||
@@ -303,14 +306,8 @@ class ProcessContainer:
|
|||||||
self.msg_queue = []
|
self.msg_queue = []
|
||||||
|
|
||||||
self.rc.unlock_sockets()
|
self.rc.unlock_sockets()
|
||||||
self.rc.wait_for_next_recv(trigger_empty_recv)
|
if trigger_empty_recv:
|
||||||
|
self.rc.unlock_sockets()
|
||||||
for socket in self.sockets:
|
|
||||||
ms = messaging.drain_sock(socket)
|
|
||||||
for m in ms:
|
|
||||||
m = m.as_builder()
|
|
||||||
m.logMonoTime = msg.logMonoTime + int(self.cfg.processing_time * 1e9)
|
|
||||||
output_msgs.append(m.as_reader())
|
|
||||||
self.cnt += 1
|
self.cnt += 1
|
||||||
assert self.process.proc.is_alive()
|
assert self.process.proc.is_alive()
|
||||||
|
|
||||||
@@ -320,7 +317,7 @@ class ProcessContainer:
|
|||||||
def card_fingerprint_callback(rc, pm, msgs, fingerprint):
|
def card_fingerprint_callback(rc, pm, msgs, fingerprint):
|
||||||
print("start fingerprinting")
|
print("start fingerprinting")
|
||||||
params = Params()
|
params = Params()
|
||||||
canmsgs = [msg for msg in msgs if msg.which() == "can"][:300]
|
canmsgs = list(islice((m for m in msgs if m.which() == "can"), 300))
|
||||||
|
|
||||||
# card expects one arbitrary can and pandaState
|
# card expects one arbitrary can and pandaState
|
||||||
rc.send_sync(pm, "can", messaging.new_message("can", 1))
|
rc.send_sync(pm, "can", messaging.new_message("can", 1))
|
||||||
@@ -345,36 +342,27 @@ def get_car_params_callback(rc, pm, msgs, fingerprint):
|
|||||||
CP = CarInterface.get_non_essential_params(fingerprint)
|
CP = CarInterface.get_non_essential_params(fingerprint)
|
||||||
CP_SP = CarInterface.get_non_essential_params_sp(CP, fingerprint)
|
CP_SP = CarInterface.get_non_essential_params_sp(CP, fingerprint)
|
||||||
else:
|
else:
|
||||||
can = DummySocket()
|
can_msgs = ([CanData(can.address, can.dat, can.src) for can in m.can] for m in msgs if m.which() == "can")
|
||||||
sendcan = DummySocket()
|
|
||||||
|
|
||||||
canmsgs = [msg for msg in msgs if msg.which() == "can"]
|
|
||||||
cached_params_raw = params.get("CarParamsCache")
|
cached_params_raw = params.get("CarParamsCache")
|
||||||
has_cached_cp = cached_params_raw is not None
|
assert next(can_msgs, None), "CAN messages are required for fingerprinting"
|
||||||
assert len(canmsgs) != 0, "CAN messages are required for fingerprinting"
|
assert os.environ.get("SKIP_FW_QUERY", False) or cached_params_raw is not None, \
|
||||||
assert os.environ.get("SKIP_FW_QUERY", False) or has_cached_cp, \
|
|
||||||
"CarParamsCache is required for fingerprinting. Make sure to keep carParams msgs in the logs."
|
"CarParamsCache is required for fingerprinting. Make sure to keep carParams msgs in the logs."
|
||||||
|
|
||||||
for m in canmsgs[:300]:
|
def can_recv(wait_for_one: bool = False) -> list[list[CanData]]:
|
||||||
can.send(m.as_builder().to_bytes())
|
return [next(can_msgs, [])]
|
||||||
can_callbacks = can_comm_callbacks(can, sendcan)
|
|
||||||
|
|
||||||
cached_params = None
|
cached_params = None
|
||||||
if has_cached_cp:
|
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 = _cached_params
|
cached_params = _cached_params
|
||||||
|
|
||||||
_CI = get_car(*can_callbacks, lambda obd: None, Params().get_bool("AlphaLongitudinalEnabled"), False, cached_params=cached_params)
|
_CI = get_car(can_recv, lambda _msgs: None, lambda obd: None, params.get_bool("AlphaLongitudinalEnabled"), False, cached_params=cached_params)
|
||||||
CP, CP_SP = _CI.CP, _CI.CP_SP
|
CP, CP_SP = _CI.CP, _CI.CP_SP
|
||||||
|
|
||||||
params.put("CarParams", CP.to_bytes())
|
params.put("CarParams", CP.to_bytes())
|
||||||
params.put("CarParamsSP", convert_to_capnp(CP_SP).to_bytes())
|
params.put("CarParamsSP", convert_to_capnp(CP_SP).to_bytes())
|
||||||
|
|
||||||
|
|
||||||
def selfdrived_rcv_callback(msg, cfg, frame):
|
|
||||||
return (frame - 1) == 0 or msg.which() == 'carState'
|
|
||||||
|
|
||||||
|
|
||||||
def card_rcv_callback(msg, cfg, frame):
|
def card_rcv_callback(msg, cfg, frame):
|
||||||
# no sendcan until card is initialized
|
# no sendcan until card is initialized
|
||||||
if msg.which() != "can":
|
if msg.which() != "can":
|
||||||
@@ -389,21 +377,6 @@ def card_rcv_callback(msg, cfg, frame):
|
|||||||
return len(socks) > 0
|
return len(socks) > 0
|
||||||
|
|
||||||
|
|
||||||
def calibration_rcv_callback(msg, cfg, frame):
|
|
||||||
# calibrationd publishes 1 calibrationData every 5 cameraOdometry packets.
|
|
||||||
# should_recv always true to increment frame
|
|
||||||
return (frame - 1) == 0 or msg.which() == 'cameraOdometry'
|
|
||||||
|
|
||||||
|
|
||||||
def torqued_rcv_callback(msg, cfg, frame):
|
|
||||||
# should_recv always true to increment frame
|
|
||||||
return (frame - 1) == 0 or msg.which() == 'livePose'
|
|
||||||
|
|
||||||
|
|
||||||
def dmonitoringmodeld_rcv_callback(msg, cfg, frame):
|
|
||||||
return msg.which() == "driverCameraState"
|
|
||||||
|
|
||||||
|
|
||||||
class ModeldCameraSyncRcvCallback:
|
class ModeldCameraSyncRcvCallback:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.road_present = False
|
self.road_present = False
|
||||||
@@ -428,26 +401,13 @@ class ModeldCameraSyncRcvCallback:
|
|||||||
|
|
||||||
|
|
||||||
class MessageBasedRcvCallback:
|
class MessageBasedRcvCallback:
|
||||||
def __init__(self, trigger_msg_type):
|
def __init__(self, trigger_msg_type: str, first_frame: bool = False):
|
||||||
self.trigger_msg_type = trigger_msg_type
|
self.trigger_msg_type = trigger_msg_type
|
||||||
|
self.first_frame = first_frame
|
||||||
|
|
||||||
def __call__(self, msg, cfg, frame):
|
def __call__(self, msg, cfg, frame):
|
||||||
return msg.which() == self.trigger_msg_type
|
# publish on first frame or trigger msg
|
||||||
|
return ((frame - 1) == 0 and self.first_frame) or msg.which() == self.trigger_msg_type
|
||||||
|
|
||||||
class FrequencyBasedRcvCallback:
|
|
||||||
def __init__(self, trigger_msg_type):
|
|
||||||
self.trigger_msg_type = trigger_msg_type
|
|
||||||
|
|
||||||
def __call__(self, msg, cfg, frame):
|
|
||||||
if msg.which() != self.trigger_msg_type:
|
|
||||||
return False
|
|
||||||
|
|
||||||
resp_sockets = [
|
|
||||||
s for s in cfg.subs
|
|
||||||
if frame % max(1, int(SERVICE_LIST[msg.which()].frequency / SERVICE_LIST[s].frequency)) == 0
|
|
||||||
]
|
|
||||||
return bool(len(resp_sockets))
|
|
||||||
|
|
||||||
|
|
||||||
def selfdrived_config_callback(params, cfg, lr):
|
def selfdrived_config_callback(params, cfg, lr):
|
||||||
@@ -462,16 +422,16 @@ CONFIGS = [
|
|||||||
proc_name="selfdrived",
|
proc_name="selfdrived",
|
||||||
pubs=[
|
pubs=[
|
||||||
"carState", "deviceState", "pandaStates", "peripheralState", "liveCalibration", "driverMonitoringState",
|
"carState", "deviceState", "pandaStates", "peripheralState", "liveCalibration", "driverMonitoringState",
|
||||||
"longitudinalPlan", "livePose", "liveDelay", "liveParameters", "radarState",
|
"longitudinalPlan", "livePose", "liveDelay", "liveParameters", "radarState", "modelV2",
|
||||||
"modelV2", "driverCameraState", "roadCameraState", "wideRoadCameraState", "managerState",
|
"driverCameraState", "roadCameraState", "wideRoadCameraState", "managerState", "liveTorqueParameters",
|
||||||
"liveTorqueParameters", "accelerometer", "gyroscope", "carOutput",
|
"accelerometer", "gyroscope", "carOutput", "gpsLocationExternal", "gpsLocation", "controlsState",
|
||||||
"gpsLocationExternal", "gpsLocation", "controlsState", "carControl", "driverAssistance", "alertDebug",
|
"carControl", "driverAssistance", "alertDebug", "audioFeedback",
|
||||||
],
|
],
|
||||||
subs=["selfdriveState", "onroadEvents"],
|
subs=["selfdriveState", "onroadEvents"],
|
||||||
ignore=["logMonoTime"],
|
ignore=["logMonoTime"],
|
||||||
config_callback=selfdrived_config_callback,
|
config_callback=selfdrived_config_callback,
|
||||||
init_callback=get_car_params_callback,
|
init_callback=get_car_params_callback,
|
||||||
should_recv_callback=selfdrived_rcv_callback,
|
should_recv_callback=MessageBasedRcvCallback("carState", True),
|
||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
processing_time=0.004,
|
processing_time=0.004,
|
||||||
),
|
),
|
||||||
@@ -496,6 +456,7 @@ CONFIGS = [
|
|||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
processing_time=0.004,
|
processing_time=0.004,
|
||||||
main_pub="can",
|
main_pub="can",
|
||||||
|
main_pub_drained=True,
|
||||||
),
|
),
|
||||||
ProcessConfig(
|
ProcessConfig(
|
||||||
proc_name="radard",
|
proc_name="radard",
|
||||||
@@ -503,7 +464,7 @@ CONFIGS = [
|
|||||||
subs=["radarState"],
|
subs=["radarState"],
|
||||||
ignore=["logMonoTime"],
|
ignore=["logMonoTime"],
|
||||||
init_callback=get_car_params_callback,
|
init_callback=get_car_params_callback,
|
||||||
should_recv_callback=FrequencyBasedRcvCallback("modelV2"),
|
should_recv_callback=MessageBasedRcvCallback("modelV2"),
|
||||||
),
|
),
|
||||||
ProcessConfig(
|
ProcessConfig(
|
||||||
proc_name="plannerd",
|
proc_name="plannerd",
|
||||||
@@ -511,7 +472,7 @@ CONFIGS = [
|
|||||||
subs=["longitudinalPlan", "driverAssistance"],
|
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=MessageBasedRcvCallback("modelV2"),
|
||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
),
|
),
|
||||||
ProcessConfig(
|
ProcessConfig(
|
||||||
@@ -520,14 +481,14 @@ CONFIGS = [
|
|||||||
subs=["liveCalibration"],
|
subs=["liveCalibration"],
|
||||||
ignore=["logMonoTime"],
|
ignore=["logMonoTime"],
|
||||||
init_callback=get_car_params_callback,
|
init_callback=get_car_params_callback,
|
||||||
should_recv_callback=calibration_rcv_callback,
|
should_recv_callback=MessageBasedRcvCallback("cameraOdometry", True),
|
||||||
),
|
),
|
||||||
ProcessConfig(
|
ProcessConfig(
|
||||||
proc_name="dmonitoringd",
|
proc_name="dmonitoringd",
|
||||||
pubs=["driverStateV2", "liveCalibration", "carState", "modelV2", "selfdriveState"],
|
pubs=["driverStateV2", "liveCalibration", "carState", "modelV2", "selfdriveState"],
|
||||||
subs=["driverMonitoringState"],
|
subs=["driverMonitoringState"],
|
||||||
ignore=["logMonoTime"],
|
ignore=["logMonoTime"],
|
||||||
should_recv_callback=FrequencyBasedRcvCallback("driverStateV2"),
|
should_recv_callback=MessageBasedRcvCallback("driverStateV2"),
|
||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
),
|
),
|
||||||
ProcessConfig(
|
ProcessConfig(
|
||||||
@@ -535,11 +496,10 @@ CONFIGS = [
|
|||||||
pubs=[
|
pubs=[
|
||||||
"cameraOdometry", "accelerometer", "gyroscope", "liveCalibration", "carState"
|
"cameraOdometry", "accelerometer", "gyroscope", "liveCalibration", "carState"
|
||||||
],
|
],
|
||||||
subs=["livePose"],
|
subs=["liveLocationKalman", "livePose"],
|
||||||
ignore=["logMonoTime"],
|
ignore=["logMonoTime"],
|
||||||
should_recv_callback=MessageBasedRcvCallback("cameraOdometry"),
|
should_recv_callback=MessageBasedRcvCallback("cameraOdometry"),
|
||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
unlocked_pubs=["accelerometer", "gyroscope"],
|
|
||||||
),
|
),
|
||||||
ProcessConfig(
|
ProcessConfig(
|
||||||
proc_name="paramsd",
|
proc_name="paramsd",
|
||||||
@@ -547,7 +507,7 @@ CONFIGS = [
|
|||||||
subs=["liveParameters"],
|
subs=["liveParameters"],
|
||||||
ignore=["logMonoTime"],
|
ignore=["logMonoTime"],
|
||||||
init_callback=get_car_params_callback,
|
init_callback=get_car_params_callback,
|
||||||
should_recv_callback=FrequencyBasedRcvCallback("livePose"),
|
should_recv_callback=MessageBasedRcvCallback("livePose"),
|
||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
processing_time=0.004,
|
processing_time=0.004,
|
||||||
),
|
),
|
||||||
@@ -572,7 +532,7 @@ CONFIGS = [
|
|||||||
subs=["liveTorqueParameters"],
|
subs=["liveTorqueParameters"],
|
||||||
ignore=["logMonoTime"],
|
ignore=["logMonoTime"],
|
||||||
init_callback=get_car_params_callback,
|
init_callback=get_car_params_callback,
|
||||||
should_recv_callback=torqued_rcv_callback,
|
should_recv_callback=MessageBasedRcvCallback("livePose", True),
|
||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
),
|
),
|
||||||
ProcessConfig(
|
ProcessConfig(
|
||||||
@@ -584,7 +544,6 @@ CONFIGS = [
|
|||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
processing_time=0.020,
|
processing_time=0.020,
|
||||||
main_pub=vipc_get_endpoint_name("camerad", meta_from_camera_state("roadCameraState").stream),
|
main_pub=vipc_get_endpoint_name("camerad", meta_from_camera_state("roadCameraState").stream),
|
||||||
main_pub_drained=False,
|
|
||||||
vision_pubs=["roadCameraState", "wideRoadCameraState"],
|
vision_pubs=["roadCameraState", "wideRoadCameraState"],
|
||||||
ignore_alive_pubs=["wideRoadCameraState"],
|
ignore_alive_pubs=["wideRoadCameraState"],
|
||||||
init_callback=get_car_params_callback,
|
init_callback=get_car_params_callback,
|
||||||
@@ -594,11 +553,10 @@ CONFIGS = [
|
|||||||
pubs=["liveCalibration", "driverCameraState"],
|
pubs=["liveCalibration", "driverCameraState"],
|
||||||
subs=["driverStateV2"],
|
subs=["driverStateV2"],
|
||||||
ignore=["logMonoTime", "driverStateV2.modelExecutionTime", "driverStateV2.gpuExecutionTime"],
|
ignore=["logMonoTime", "driverStateV2.modelExecutionTime", "driverStateV2.gpuExecutionTime"],
|
||||||
should_recv_callback=dmonitoringmodeld_rcv_callback,
|
should_recv_callback=MessageBasedRcvCallback("driverCameraState"),
|
||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
processing_time=0.020,
|
processing_time=0.020,
|
||||||
main_pub=vipc_get_endpoint_name("camerad", meta_from_camera_state("driverCameraState").stream),
|
main_pub=vipc_get_endpoint_name("camerad", meta_from_camera_state("driverCameraState").stream),
|
||||||
main_pub_drained=False,
|
|
||||||
vision_pubs=["driverCameraState"],
|
vision_pubs=["driverCameraState"],
|
||||||
ignore_alive_pubs=["driverCameraState"],
|
ignore_alive_pubs=["driverCameraState"],
|
||||||
),
|
),
|
||||||
@@ -637,7 +595,7 @@ def get_custom_params_from_lr(lr: LogIterable, initial_state: str = "first") ->
|
|||||||
if len(live_calibration) > 0:
|
if len(live_calibration) > 0:
|
||||||
custom_params["CalibrationParams"] = live_calibration[msg_index].as_builder().to_bytes()
|
custom_params["CalibrationParams"] = live_calibration[msg_index].as_builder().to_bytes()
|
||||||
if len(live_parameters) > 0:
|
if len(live_parameters) > 0:
|
||||||
custom_params["LiveParameters"] = live_parameters[msg_index].as_builder().to_bytes()
|
custom_params["LiveParametersV2"] = live_parameters[msg_index].as_builder().to_bytes()
|
||||||
if len(live_torque_parameters) > 0:
|
if len(live_torque_parameters) > 0:
|
||||||
custom_params["LiveTorqueParameters"] = live_torque_parameters[msg_index].as_builder().to_bytes()
|
custom_params["LiveTorqueParameters"] = live_torque_parameters[msg_index].as_builder().to_bytes()
|
||||||
|
|
||||||
@@ -706,8 +664,8 @@ def _replay_multi_process(
|
|||||||
|
|
||||||
all_msgs = sorted(lr, key=lambda msg: msg.logMonoTime)
|
all_msgs = sorted(lr, key=lambda msg: msg.logMonoTime)
|
||||||
log_msgs = []
|
log_msgs = []
|
||||||
|
containers = []
|
||||||
try:
|
try:
|
||||||
containers = []
|
|
||||||
for cfg in cfgs:
|
for cfg in cfgs:
|
||||||
container = ProcessContainer(cfg)
|
container = ProcessContainer(cfg)
|
||||||
containers.append(container)
|
containers.append(container)
|
||||||
@@ -742,6 +700,11 @@ def _replay_multi_process(
|
|||||||
internal_pub_queue.append(m)
|
internal_pub_queue.append(m)
|
||||||
heapq.heappush(internal_pub_index_heap, (m.logMonoTime, len(internal_pub_queue) - 1))
|
heapq.heappush(internal_pub_index_heap, (m.logMonoTime, len(internal_pub_queue) - 1))
|
||||||
log_msgs.extend(output_msgs)
|
log_msgs.extend(output_msgs)
|
||||||
|
|
||||||
|
# flush last set of messages from each process
|
||||||
|
for container in containers:
|
||||||
|
last_time = log_msgs[-1].logMonoTime if len(log_msgs) > 0 else int(time.monotonic() * 1e9)
|
||||||
|
log_msgs.extend(container.get_output_msgs(last_time))
|
||||||
finally:
|
finally:
|
||||||
for container in containers:
|
for container in containers:
|
||||||
container.stop()
|
container.stop()
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
c289a0359d1b1f26cf4d9e73a2c04b2bbfec840f
|
afcab1abb62b9d5678342956cced4712f44e909e
|
||||||
@@ -63,7 +63,7 @@ segments = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
# dashcamOnly makes don't need to be tested until a full port is done
|
# dashcamOnly makes don't need to be tested until a full port is done
|
||||||
excluded_interfaces = ["mock", "body"]
|
excluded_interfaces = ["mock", "body", "psa"]
|
||||||
|
|
||||||
BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/"
|
BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/"
|
||||||
REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit")
|
REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit")
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ while true; do
|
|||||||
# /data/ciui.py &
|
# /data/ciui.py &
|
||||||
#fi
|
#fi
|
||||||
|
|
||||||
|
awk '{print \$1}' /proc/uptime > /var/tmp/power_watchdog
|
||||||
sleep 5s
|
sleep 5s
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user