mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-06-19 16:52:06 +08:00
Compare commits
276 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b24e762592 | |||
| ed8812e730 | |||
| 36f8192612 | |||
| 4b66cd0577 | |||
| ad742515f1 | |||
| 2426354d72 | |||
| cf55992c26 | |||
| 3da0dfd47f | |||
| 6edaf619bf | |||
| 87bc80d351 | |||
| 233dc24929 | |||
| dc73e6e2aa | |||
| 50aac48fba | |||
| 26b928596d | |||
| d9d57e5d6f | |||
| 3dc970960d | |||
| e8a2f199e8 | |||
| 3fffd6c466 | |||
| 4948f910f0 | |||
| 31cd290cbf | |||
| 96f6e186c4 | |||
| eda8a1feed | |||
| 7d5031a6d6 | |||
| def2a7382c | |||
| 5b9aa0b0f0 | |||
| 4176c74969 | |||
| 1ab2ec26e1 | |||
| 01a6dccc86 | |||
| f5dc1d08c9 | |||
| d050e0c2ac | |||
| 4170534c02 | |||
| 97c990dac6 | |||
| 13760968bb | |||
| a683b7d99c | |||
| f3e28275d9 | |||
| da952e9b64 | |||
| 127c922aed | |||
| f50ee8a67a | |||
| 5e4b423b5a | |||
| 67b483f880 | |||
| 3c22a1ee27 | |||
| 482234ed35 | |||
| 37d38960dc | |||
| e243acac3b | |||
| 5b0994c53c | |||
| 47bd0f0166 | |||
| 1c1e7330f0 | |||
| 3a2ca15164 | |||
| 4979182a2e | |||
| 44c889fa41 | |||
| 13d4b5f8bb | |||
| 7ecedbc39f | |||
| ce7317407f | |||
| a642973dc7 | |||
| a4848ceee9 | |||
| 700a5651bd | |||
| 3e88d680a5 | |||
| d899125b65 | |||
| 48fcb4dc60 | |||
| e0acd86ca1 | |||
| a342cef545 | |||
| b6a1530346 | |||
| 2ac776cfda | |||
| 4f9794097b | |||
| f2a1cce42b | |||
| 66beaceeec | |||
| 6a2ae346ab | |||
| c995d5b9ae | |||
| f13d1ae4d6 | |||
| 1965b2fd6e | |||
| aed8e5cbd5 | |||
| 1e3f8bec46 | |||
| c3ca9a26c8 | |||
| 3081dca9e5 | |||
| 09b68eef3e | |||
| 3474eb3d96 | |||
| 8fc36c26f2 | |||
| 6e8e51793e | |||
| 4d08e114d9 | |||
| 4b21e221dd | |||
| fd84970833 | |||
| e92ff96de3 | |||
| 954fa5e6da | |||
| 12fd9e441f | |||
| 83b60a7ba6 | |||
| 39dbee0329 | |||
| 5a06d1df43 | |||
| e7d4139b1d | |||
| 474607ba06 | |||
| 525482bf59 | |||
| 6a32430499 | |||
| 4f5ec14cf7 | |||
| 32c5254415 | |||
| 747b963a29 | |||
| d7fbf70237 | |||
| 02e71a8467 | |||
| 08c8986dfa | |||
| 421ee1cffb | |||
| 6fc14b5b93 | |||
| 4c0e2926e4 | |||
| 2af9f68147 | |||
| 61508e48a1 | |||
| 572e9a466c | |||
| df523385ca | |||
| 420927c8db | |||
| 736b6a2f8a | |||
| 7899b9964c | |||
| c979282cfa | |||
| 4716629d70 | |||
| 366d6abeb1 | |||
| 3e1e9892a0 | |||
| 183a058853 | |||
| da20b46839 | |||
| 78b83959e6 | |||
| 25d2555e49 | |||
| b470bef140 | |||
| 3907134282 | |||
| 5df9fcbde4 | |||
| 935e4b9c83 | |||
| 219fd82b2c | |||
| fe53185f64 | |||
| 3f09d19c95 | |||
| db98566084 | |||
| bce376f120 | |||
| e6afe355d4 | |||
| a054816885 | |||
| 22a1b40566 | |||
| fcbdd39aaf | |||
| 7fc7874804 | |||
| 628e0daee0 | |||
| 21bb0a2d7a | |||
| d2794f3dc9 | |||
| f5c26b4057 | |||
| 94d10ebf6a | |||
| 9fcd159141 | |||
| 1932aff0d9 | |||
| 72a88c9319 | |||
| 1570aa7961 | |||
| 251a9f028f | |||
| b943cbd421 | |||
| e42c203448 | |||
| 5f3625436c | |||
| 85c1b2b620 | |||
| 835a27e993 | |||
| 47aee33ad2 | |||
| 2f7d09bb01 | |||
| 24a32c3dec | |||
| db98ba88ab | |||
| 07ad6e27d0 | |||
| a8908b5c08 | |||
| 719c634668 | |||
| 8557b0440e | |||
| 0466d111d2 | |||
| 0b20007242 | |||
| c473688b2b | |||
| 0440c3a83b | |||
| 0ff498cc83 | |||
| cf420ed001 | |||
| fc5aed10d5 | |||
| fc5f761fa8 | |||
| 4f8b11257e | |||
| d26730ffd5 | |||
| b87a52a9a0 | |||
| f7f15d63dc | |||
| c78fc60d9b | |||
| 3396c59151 | |||
| 0d6baffcbf | |||
| 67ced42a15 | |||
| 89d5761329 | |||
| 24a8c7ee1a | |||
| 277f80dbad | |||
| 9daec14220 | |||
| 3e5e2b52ea | |||
| 371f60413a | |||
| 30853a2c41 | |||
| debd71ebec | |||
| c9f7e39d37 | |||
| 0ec1c8707b | |||
| c719d06b56 | |||
| c11d0a6518 | |||
| 66804173c1 | |||
| af73d6084d | |||
| 4537a9d2ac | |||
| 6175106b19 | |||
| 34dde0bb36 | |||
| 9ad2333546 | |||
| 53f514cbd3 | |||
| b4252404ec | |||
| 277691cf83 | |||
| 3a150e51e4 | |||
| ebe0dee2ee | |||
| af727d41bb | |||
| 9674c7b5af | |||
| 3c5d8da469 | |||
| bf04c4799a | |||
| fa4a3e3c63 | |||
| abdd3ad2e9 | |||
| 4d4df4822a | |||
| 363e020ce6 | |||
| c6c9a0da8f | |||
| eb0f637298 | |||
| 3420b2a02d | |||
| f0ccb78afa | |||
| 3e9127be06 | |||
| dc094792b7 | |||
| 8bf34d0dfb | |||
| 3dd127c5ad | |||
| d96a042722 | |||
| 37ba899000 | |||
| a66851aa91 | |||
| 7aa4ef5fd6 | |||
| 8e8f61ad35 | |||
| 6f40dec427 | |||
| 354e909ab1 | |||
| 747acaac71 | |||
| 7150c145ae | |||
| 21a8f9e9ba | |||
| f11e5492f8 | |||
| d7c0906d0b | |||
| 48abdf825b | |||
| 682857f0cd | |||
| 50baf37ddd | |||
| 596d8b13bb | |||
| 8a8d8c2272 | |||
| d72b59832c | |||
| 8cf1b79189 | |||
| 8b5df1e9ee | |||
| 196fb0a7ea | |||
| 19fdf90585 | |||
| 6547d9593c | |||
| 22bc50fee4 | |||
| 2e83e37984 | |||
| 8bcf930a28 | |||
| 939d306ac3 | |||
| 85278520ed | |||
| 853febe21d | |||
| 2040f04c45 | |||
| 4501d633ea | |||
| eca88f5ea2 | |||
| cf50d4ae19 | |||
| 91e4978984 | |||
| ad5933cefe | |||
| 77f8275b19 | |||
| 66ec788005 | |||
| 81ed1decc9 | |||
| 7556233cca | |||
| 4a50526064 | |||
| 68f4c8a986 | |||
| 9d52a5b485 | |||
| 8149f7cb11 | |||
| af774d894e | |||
| 72e19ccfc6 | |||
| b7a3887731 | |||
| f7c4aad8bd | |||
| 6baa541501 | |||
| aed1eaede5 | |||
| 3ca158ad3e | |||
| b2791a8e07 | |||
| 6f0927011c | |||
| 2b486b15c6 | |||
| 06f2ca1179 | |||
| 6aebec7b34 | |||
| 7fa8e3d8a6 | |||
| af609212f7 | |||
| c87f953396 | |||
| 98e1d840de | |||
| c19c18cfdb | |||
| aea6790e3c | |||
| 48bd5b9f6b | |||
| 9cbd34158f | |||
| 521c702a1e | |||
| 516aa59ee6 | |||
| 17edc5f660 | |||
| 999c86e8a2 | |||
| ba350aac4c | |||
| 3b6f7c9a6c |
@@ -24,7 +24,7 @@ jobs:
|
||||
# Check PR target branch
|
||||
- name: check branch
|
||||
uses: Vankka/pr-target-branch-action@def32ec9d93514138d6ac0132ee62e120a72aed5
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
if: github.repository == 'sunnypilot/sunnypilot'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -37,17 +37,17 @@ jobs:
|
||||
# Welcome comment
|
||||
- name: "First timers PR"
|
||||
uses: actions/first-interaction@v1
|
||||
if: github.event.pull_request.head.repo.full_name != 'commaai/openpilot'
|
||||
if: github.event.pull_request.head.repo.full_name != 'sunnypilot/sunnypilot'
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
pr-message: |
|
||||
<!-- _(run_id **${{ github.run_id }}**)_ -->
|
||||
Thanks for contributing to openpilot! In order for us to review your PR as quickly as possible, check the following:
|
||||
* Convert your PR to a draft unless it's ready to review
|
||||
* Read the [contributing docs](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md)
|
||||
* Read the [contributing docs](https://github.com/sunnypilot/sunnypilot/blob/master/docs/CONTRIBUTING.md)
|
||||
* Before marking as "ready for review", ensure:
|
||||
* the goal is clearly stated in the description
|
||||
* all the tests are passing
|
||||
* the change is [something we merge](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md#what-gets-merged)
|
||||
* the change is [something we merge](https://github.com/sunnypilot/sunnypilot/blob/master/docs/CONTRIBUTING.md#what-gets-merged)
|
||||
* include a route or your device' dongle ID if relevant
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
badges:
|
||||
name: create badges
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
if: github.repository == 'sunnypilot/sunnypilot'
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
git checkout --orphan badges
|
||||
git rm -rf --cached .
|
||||
git config user.email "badge-researcher@comma.ai"
|
||||
git config user.email "badge-researcher@sunnypilot.ai"
|
||||
git config user.name "Badge Researcher"
|
||||
|
||||
git add translation_badge.svg
|
||||
|
||||
@@ -15,7 +15,7 @@ env:
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
if: github.repository == 'sunnypilot/sunnypilot'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
ci_runs: ${{ steps.ci_runs_setup.outputs.matrix }}
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{fromJSON(needs.setup.outputs.ci_runs)}}
|
||||
uses: commaai/openpilot/.github/workflows/ci_weekly_run.yaml@master
|
||||
uses: sunnypilot/sunnypilot/.github/workflows/ci_weekly_run.yaml@master
|
||||
with:
|
||||
run_number: ${{ matrix.run_number }}
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
selfdrive_tests:
|
||||
uses: commaai/openpilot/.github/workflows/selfdrive_tests.yaml@master
|
||||
uses: sunnypilot/sunnypilot/.github/workflows/selfdrive_tests.yaml@master
|
||||
with:
|
||||
run_number: ${{ inputs.run_number }}
|
||||
tools_tests:
|
||||
uses: commaai/openpilot/.github/workflows/tools_tests.yaml@master
|
||||
uses: sunnypilot/sunnypilot/.github/workflows/tools_tests.yaml@master
|
||||
with:
|
||||
run_number: ${{ inputs.run_number }}
|
||||
|
||||
@@ -34,13 +34,13 @@ jobs:
|
||||
|
||||
# Push to docs.comma.ai
|
||||
- uses: actions/checkout@v4
|
||||
if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot'
|
||||
if: github.ref == 'refs/heads/master' && github.repository == 'sunnypilot/sunnypilot'
|
||||
with:
|
||||
path: openpilot-docs
|
||||
ssh-key: ${{ secrets.OPENPILOT_DOCS_KEY }}
|
||||
repository: commaai/openpilot-docs
|
||||
repository: sunnypilot/sunnypilot-docs
|
||||
- name: Push
|
||||
if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot'
|
||||
if: github.ref == 'refs/heads/master' && github.repository == 'sunnypilot/sunnypilot'
|
||||
run: |
|
||||
set -x
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
build_prebuilt:
|
||||
name: build prebuilt
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
if: github.repository == 'sunnypilot/sunnypilot'
|
||||
env:
|
||||
PUSH_IMAGE: true
|
||||
permissions:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: release
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 10 * * *'
|
||||
- cron: '0 9 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
container:
|
||||
image: ghcr.io/commaai/openpilot-base:latest
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
if: github.repository == 'sunnypilot/sunnypilot'
|
||||
permissions:
|
||||
checks: read
|
||||
contents: write
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/commaai/openpilot-base:latest
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
if: github.repository == 'sunnypilot/sunnypilot'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
||||
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- master-new
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
@@ -14,10 +15,11 @@ on:
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: selfdrive-tests-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
|
||||
group: selfdrive-tests-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/master-new') && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
REPORT_NAME: report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/master-new') && 'master' || github.event.number }}
|
||||
PYTHONWARNINGS: error
|
||||
BASE_IMAGE: openpilot-base
|
||||
AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }}
|
||||
@@ -31,6 +33,7 @@ env:
|
||||
|
||||
jobs:
|
||||
build_release:
|
||||
if: github.repository == 'commaai/openpilot' # build_release blocked for the time being to only comma as we may have a different process.
|
||||
name: build release
|
||||
runs-on: ${{ ((github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
@@ -41,14 +44,19 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- run: git lfs pull
|
||||
- name: Getting LFS files
|
||||
uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e
|
||||
with:
|
||||
timeout_minutes: 2
|
||||
max_attempts: 3
|
||||
command: git lfs pull
|
||||
- name: Build devel
|
||||
timeout-minutes: 1
|
||||
run: TARGET_DIR=$STRIPPED_DIR release/build_devel.sh
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Check submodules
|
||||
if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot'
|
||||
timeout-minutes: 1
|
||||
if: github.repository == 'sunnypilot/sunnypilot'
|
||||
timeout-minutes: 3
|
||||
run: release/check-submodules.sh
|
||||
- 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
|
||||
@@ -56,7 +64,7 @@ jobs:
|
||||
cd $STRIPPED_DIR
|
||||
${{ env.RUN }} "python3 system/manager/build.py"
|
||||
- name: Run tests
|
||||
timeout-minutes: 3
|
||||
timeout-minutes: 1
|
||||
run: |
|
||||
cd $STRIPPED_DIR
|
||||
${{ env.RUN }} "release/check-dirty.sh && \
|
||||
@@ -76,7 +84,9 @@ jobs:
|
||||
((github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && '["x86_64", "aarch64"]' || '["x86_64"]' ) }}
|
||||
runs-on: ${{ (matrix.arch == 'aarch64') && 'namespace-profile-arm64-2x8' || 'ubuntu-latest' }}
|
||||
runs-on: ${{ ((matrix.arch == 'aarch64') && 'namespace-profile-arm64-2x8') ||
|
||||
((matrix.arch == 'x86_64') && ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16') ||
|
||||
'ubuntu-latest'}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -94,27 +104,32 @@ jobs:
|
||||
timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 15 || 30) }} # allow more time when we missed the scons cache
|
||||
|
||||
build_mac:
|
||||
if: github.repository == 'commaai/openpilot' # Blocking macos builds as well since they have a 10x miltiplier for GH action minutes, waaaay too much!
|
||||
name: build macOS
|
||||
runs-on: 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:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- run: git lfs pull
|
||||
- name: Homebrew cache
|
||||
uses: ./.github/workflows/auto-cache
|
||||
with:
|
||||
path: ~/Library/Caches/Homebrew
|
||||
key: build_macos_${{ hashFiles('.github/workflows/selfdrive_tests.yaml') }}
|
||||
- name: Install dependencies
|
||||
run: ./tools/mac_setup.sh
|
||||
env:
|
||||
# package install has DeprecationWarnings
|
||||
PYTHONWARNINGS: default
|
||||
- run: git lfs pull
|
||||
- run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV
|
||||
- name: Getting scons cache
|
||||
uses: 'actions/cache@v4'
|
||||
uses: ./.github/workflows/auto-cache
|
||||
with:
|
||||
path: /tmp/scons_cache
|
||||
key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}
|
||||
scons-${{ runner.arch }}-macos
|
||||
key: build_macos_${{ hashFiles('.github/workflows/selfdrive_tests.yaml') }}
|
||||
- name: Building openpilot
|
||||
run: . .venv/bin/activate && scons -j$(nproc)
|
||||
|
||||
@@ -144,11 +159,12 @@ jobs:
|
||||
PYTHONWARNINGS: default
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup
|
||||
run: tools/op.sh setup
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Static analysis
|
||||
timeout-minutes: 1
|
||||
run: tools/op.sh lint
|
||||
run: ${{ env.RUN }} "scripts/lint/lint.sh"
|
||||
|
||||
unit_tests:
|
||||
name: unit tests
|
||||
@@ -165,14 +181,19 @@ jobs:
|
||||
- name: Build openpilot
|
||||
timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache
|
||||
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Setup cache
|
||||
uses: ./.github/workflows/auto-cache
|
||||
with:
|
||||
path: .ci_cache/comma_download_cache
|
||||
key: unit_tests_${{ hashFiles('.github/workflows/selfdrive_tests.yaml') }}
|
||||
- name: Run unit tests
|
||||
timeout-minutes: 15
|
||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && 1 || 20 }}
|
||||
run: |
|
||||
${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \
|
||||
$PYTEST --timeout 60 -m 'not slow' && \
|
||||
${{ env.RUN }} "$PYTEST --collect-only -m 'not slow' &> /dev/null && \
|
||||
MAX_EXAMPLES=1 $PYTEST -m 'not slow' && \
|
||||
./selfdrive/ui/tests/create_test_translations.sh && \
|
||||
QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \
|
||||
pytest ./selfdrive/ui/tests/test_translations.py"
|
||||
chmod -R 777 /tmp/comma_download_cache"
|
||||
- name: "Upload coverage to Codecov"
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
@@ -202,7 +223,7 @@ jobs:
|
||||
run: |
|
||||
${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Run replay
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && 1 || 20 }}
|
||||
run: |
|
||||
${{ env.RUN }} "coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \
|
||||
chmod -R 777 /tmp/comma_download_cache && \
|
||||
@@ -222,14 +243,8 @@ jobs:
|
||||
if: ${{ failure() && steps.print-diff.outcome == 'success' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }}
|
||||
run: |
|
||||
${{ env.RUN }} "unset PYTHONWARNINGS && AZURE_TOKEN='$AZURE_TOKEN' python3 selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only"
|
||||
# PYTHONWARNINGS triggers a SyntaxError in onnxruntime
|
||||
- name: Run model replay with ONNX
|
||||
timeout-minutes: 4
|
||||
run: |
|
||||
${{ env.RUN }} "unset PYTHONWARNINGS && \
|
||||
ONNXCPU=1 NO_NAV=1 coverage run selfdrive/test/process_replay/model_replay.py && \
|
||||
coverage combine && coverage xml"
|
||||
- name: Run regen
|
||||
if: false
|
||||
timeout-minutes: 4
|
||||
run: |
|
||||
${{ env.RUN }} "ONNXCPU=1 $PYTEST selfdrive/test/process_replay/test_regen.py && \
|
||||
@@ -249,7 +264,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job: [0, 1]
|
||||
job: [0, 1, 2, 3]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -264,12 +279,12 @@ jobs:
|
||||
- name: Build openpilot
|
||||
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Test car models
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && 1 || 20 }}
|
||||
run: |
|
||||
${{ env.RUN }} "$PYTEST selfdrive/car/tests/test_models.py && \
|
||||
${{ env.RUN }} "FILEREADER_CACHE=1 MAX_EXAMPLES=1 $PYTEST selfdrive/car/tests/test_models.py && \
|
||||
chmod -R 777 /tmp/comma_download_cache"
|
||||
env:
|
||||
NUM_JOBS: 2
|
||||
NUM_JOBS: 4
|
||||
JOB_ID: ${{ matrix.job }}
|
||||
- name: "Upload coverage to Codecov"
|
||||
uses: codecov/codecov-action@v4
|
||||
@@ -333,24 +348,54 @@ jobs:
|
||||
comment_id: ${{ steps.fc.outputs.comment-id }}
|
||||
})
|
||||
|
||||
simulator_driving:
|
||||
name: simulator driving
|
||||
runs-on: ${{ ((github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Build openpilot
|
||||
run: |
|
||||
${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Driving test
|
||||
timeout-minutes: 1
|
||||
run: |
|
||||
${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \
|
||||
source selfdrive/test/setup_vsound.sh && \
|
||||
CI=1 pytest tools/sim/tests/test_metadrive_bridge.py"
|
||||
|
||||
create_ui_report:
|
||||
# This job name needs to be the same as UI_JOB_NAME in ui_preview.yaml
|
||||
name: Create UI Report
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ ((github.repository == 'commaai/openpilot') &&
|
||||
((github.event_name != 'pull_request') ||
|
||||
(github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-24.04' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: caching frames
|
||||
id: frames-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .ci_cache/comma_download_cache
|
||||
key: ui_screenshots_test_${{ hashFiles('selfdrive/ui/tests/test_ui/run.py') }}
|
||||
- name: Build openpilot
|
||||
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Create Test Report
|
||||
timeout-minutes: ${{ ((steps.frames-cache.outputs.cache-hit == 'true') && 1 || 3) }}
|
||||
run: >
|
||||
${{ env.RUN }} "PYTHONWARNINGS=ignore &&
|
||||
source selfdrive/test/setup_xvfb.sh &&
|
||||
python3 selfdrive/ui/tests/test_ui/run.py"
|
||||
CACHE_ROOT=/tmp/comma_download_cache python3 selfdrive/ui/tests/test_ui/run.py &&
|
||||
chmod -R 777 /tmp/comma_download_cache"
|
||||
- name: Upload Test Report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: report-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
|
||||
name: ${{ env.REPORT_NAME }}
|
||||
path: selfdrive/ui/tests/test_ui/report_1/screenshots
|
||||
|
||||
@@ -20,6 +20,18 @@ runs:
|
||||
echo "You should not run this action directly. Use setup-with-retry instead"
|
||||
exit 1
|
||||
|
||||
- shell: bash
|
||||
name: No retries!
|
||||
run: |
|
||||
if [ "${{ github.run_attempt }}" -gt 1 ]; then
|
||||
echo -e "\033[31m"
|
||||
echo "##################################################"
|
||||
echo " Retries not allowed! Fix the flaky test! "
|
||||
echo "##################################################"
|
||||
echo -e "\033[0m"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# do this after checkout to ensure our custom LFS config is used to pull from GitLab
|
||||
- shell: bash
|
||||
run: git lfs pull
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
stale-pr-message: 'This PR has had no activity for ${{ env.DAYS_BEFORE_PR_STALE }} days. It will be automatically closed in ${{ env.DAYS_BEFORE_PR_CLOSE }} days if there is no activity.'
|
||||
close-pr-message: 'This PR has been automatically closed due to inactivity. Feel free to re-open once activity resumes.'
|
||||
stale-pr-label: stale
|
||||
delete-branch: ${{ github.event.pull_request.head.repo.full_name == 'commaai/openpilot' }} # only delete branches on the main repo
|
||||
delete-branch: ${{ github.event.pull_request.head.repo.full_name == 'sunnypilot/sunnypilot' }} # only delete branches on the main repo
|
||||
exempt-pr-labels: "ignore stale,needs testing" # if wip or it needs testing from the community, don't mark as stale
|
||||
days-before-pr-stale: ${{ env.DAYS_BEFORE_PR_STALE }}
|
||||
days-before-pr-close: ${{ env.DAYS_BEFORE_PR_CLOSE }}
|
||||
|
||||
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- master-new
|
||||
pull_request:
|
||||
workflow_call:
|
||||
inputs:
|
||||
@@ -25,24 +26,6 @@ env:
|
||||
|
||||
|
||||
jobs:
|
||||
simulator_driving:
|
||||
name: simulator driving
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Build openpilot
|
||||
run: |
|
||||
${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Run bridge test
|
||||
run: |
|
||||
${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \
|
||||
source selfdrive/test/setup_vsound.sh && \
|
||||
CI=1 pytest tools/sim/tests/test_metadrive_bridge.py"
|
||||
|
||||
devcontainer:
|
||||
name: devcontainer
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -3,23 +3,25 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- master-new
|
||||
pull_request_target:
|
||||
types: [assigned, opened, synchronize, reopened, edited]
|
||||
branches:
|
||||
- 'master'
|
||||
- 'master-new'
|
||||
paths:
|
||||
- 'selfdrive/ui/**'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
UI_JOB_NAME: "Create UI Report"
|
||||
REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
|
||||
SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.sha || github.event.pull_request.head.sha }}
|
||||
REPORT_NAME: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/master-new') && 'master' || github.event.number }}
|
||||
SHA: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/master-new') && github.sha || github.event.pull_request.head.sha }}
|
||||
BRANCH_NAME: "openpilot/pr-${{ github.event.number }}"
|
||||
|
||||
jobs:
|
||||
preview:
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
if: github.repository == 'sunnypilot/sunnypilot'
|
||||
name: preview
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
@@ -52,19 +54,19 @@ jobs:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run_id: ${{ steps.get_run_id.outputs.run_id }}
|
||||
search_artifacts: true
|
||||
name: report-${{ env.REPORT_NAME }}
|
||||
name: report-1-${{ env.REPORT_NAME }}
|
||||
path: ${{ github.workspace }}/pr_ui
|
||||
|
||||
- name: Getting master ui
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: commaai/ci-artifacts
|
||||
repository: sunnypilot/ci-artifacts
|
||||
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
||||
path: ${{ github.workspace }}/master_ui
|
||||
ref: openpilot_master_ui
|
||||
|
||||
- name: Saving new master ui
|
||||
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
||||
if: (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/master-new') && github.event_name == 'push'
|
||||
working-directory: ${{ github.workspace }}/master_ui
|
||||
run: |
|
||||
git checkout --orphan=new_master_ui
|
||||
@@ -84,37 +86,35 @@ jobs:
|
||||
run: >-
|
||||
sudo apt-get install -y imagemagick
|
||||
|
||||
scenes="homescreen settings_device settings_toggles offroad_alert update_available prime onroad onroad_disengaged onroad_override onroad_sidebar onroad_wide onroad_wide_sidebar onroad_alert_small onroad_alert_mid onroad_alert_full driver_camera body keyboard"
|
||||
scenes="homescreen settings_device settings_software settings_toggles settings_developer offroad_alert update_available prime onroad onroad_disengaged onroad_override onroad_sidebar onroad_wide onroad_wide_sidebar onroad_alert_small onroad_alert_mid onroad_alert_full driver_camera body keyboard"
|
||||
A=($scenes)
|
||||
|
||||
DIFF=""
|
||||
open=false
|
||||
TABLE="<summary>All Screenshots</summary>"
|
||||
TABLE="<details><summary>All Screenshots</summary>"
|
||||
TABLE="${TABLE}<table>"
|
||||
|
||||
for ((i=0; i<${#A[*]}; i=i+1));
|
||||
do
|
||||
|
||||
if ! compare -fuzz 2% -highlight-color DeepSkyBlue1 -lowlight-color Black -compose Src ${{ github.workspace }}/master_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png; then
|
||||
open=true
|
||||
convert ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png -transparent black mask.png
|
||||
composite mask.png ${{ github.workspace }}/master_ui/${A[$i]}.png composite_diff.png
|
||||
convert -delay 20 ${{ github.workspace }}/master_ui/${A[$i]}.png composite_diff.png -loop 0 ${{ github.workspace }}/pr_ui/${A[$i]}_diff.gif
|
||||
convert -delay 100 ${{ github.workspace }}/master_ui/${A[$i]}.png composite_diff.png -loop 0 ${{ github.workspace }}/pr_ui/${A[$i]}_diff.gif
|
||||
|
||||
mv ${{ github.workspace }}/master_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_master_ref.png
|
||||
|
||||
DIFF="${DIFF}<details>"
|
||||
DIFF="${DIFF}<details open>"
|
||||
DIFF="${DIFF}<summary>${A[$i]} : \$\${\\color{red}\\text{DIFFERENT}}\$\$</summary>"
|
||||
DIFF="${DIFF}<table>"
|
||||
|
||||
DIFF="${DIFF}<tr>"
|
||||
DIFF="${DIFF} <td> master <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_master_ref.png\"> </td>"
|
||||
DIFF="${DIFF} <td> proposed <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}.png\"> </td>"
|
||||
DIFF="${DIFF} <td> master <img src=\"https://raw.githubusercontent.com/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_master_ref.png\"> </td>"
|
||||
DIFF="${DIFF} <td> proposed <img src=\"https://raw.githubusercontent.com/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}.png\"> </td>"
|
||||
DIFF="${DIFF}</tr>"
|
||||
|
||||
DIFF="${DIFF}<tr>"
|
||||
DIFF="${DIFF} <td> diff <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_diff.png\"> </td>"
|
||||
DIFF="${DIFF} <td> composite diff <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_diff.gif\"> </td>"
|
||||
DIFF="${DIFF} <td> diff <img src=\"https://raw.githubusercontent.com/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_diff.png\"> </td>"
|
||||
DIFF="${DIFF} <td> composite diff <img src=\"https://raw.githubusercontent.com/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}_diff.gif\"> </td>"
|
||||
DIFF="${DIFF}</tr>"
|
||||
|
||||
DIFF="${DIFF}</table>"
|
||||
@@ -127,20 +127,13 @@ jobs:
|
||||
if [[ $INDEX -eq 0 ]]; then
|
||||
TABLE="${TABLE}<tr>"
|
||||
fi
|
||||
TABLE="${TABLE} <td> <img src=\"https://raw.githubusercontent.com/commaai/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}.png\"> </td>"
|
||||
TABLE="${TABLE} <td> <img src=\"https://raw.githubusercontent.com/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}.png\"> </td>"
|
||||
if [[ $INDEX -eq 1 || $(($i + 1)) -eq ${#A[*]} ]]; then
|
||||
TABLE="${TABLE}</tr>"
|
||||
fi
|
||||
done
|
||||
|
||||
TABLE="${TABLE}</table>"
|
||||
TABLE="${TABLE}</details>"
|
||||
|
||||
if $open; then
|
||||
TABLE="<details open>${TABLE}"
|
||||
else
|
||||
TABLE="<details>${TABLE}"
|
||||
fi
|
||||
TABLE="${TABLE}</table></details>"
|
||||
|
||||
echo "DIFF=$DIFF$TABLE" >> "$GITHUB_OUTPUT"
|
||||
|
||||
|
||||
+1
-3
@@ -55,9 +55,6 @@ selfdrive/test/longitudinal_maneuvers/out
|
||||
selfdrive/car/tests/cars_dump
|
||||
system/camerad/camerad
|
||||
system/camerad/test/ae_gray_test
|
||||
selfdrive/modeld/_modeld
|
||||
selfdrive/modeld/_dmonitoringmodeld
|
||||
/src/
|
||||
|
||||
notebooks
|
||||
hyperthneed
|
||||
@@ -79,6 +76,7 @@ selfdrive/modeld/models/*.thneed
|
||||
selfdrive/modeld/models/*.pkl
|
||||
|
||||
*.bz2
|
||||
*.zst
|
||||
|
||||
build/
|
||||
|
||||
|
||||
+6
-6
@@ -1,18 +1,18 @@
|
||||
[submodule "panda"]
|
||||
path = panda
|
||||
url = ../../commaai/panda.git
|
||||
url = https://github.com/sunnyhaibin/panda.git
|
||||
[submodule "opendbc"]
|
||||
path = opendbc_repo
|
||||
url = ../../commaai/opendbc.git
|
||||
url = https://github.com/sunnypilot/opendbc.git
|
||||
[submodule "msgq"]
|
||||
path = msgq_repo
|
||||
url = ../../commaai/msgq.git
|
||||
url = https://github.com/sunnypilot/msgq.git
|
||||
[submodule "rednose_repo"]
|
||||
path = rednose_repo
|
||||
url = ../../commaai/rednose.git
|
||||
url = https://github.com/commaai/rednose.git
|
||||
[submodule "teleoprtc_repo"]
|
||||
path = teleoprtc_repo
|
||||
url = ../../commaai/teleoprtc
|
||||
url = https://github.com/commaai/teleoprtc
|
||||
[submodule "tinygrad"]
|
||||
path = tinygrad_repo
|
||||
url = https://github.com/commaai/tinygrad.git
|
||||
url = https://github.com/tinygrad/tinygrad.git
|
||||
Vendored
+92
-41
@@ -12,10 +12,12 @@ def retryWithDelay(int maxRetries, int delay, Closure body) {
|
||||
def device(String ip, String step_label, String cmd) {
|
||||
withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) {
|
||||
def ssh_cmd = """
|
||||
ssh -tt -o ConnectTimeout=5 -o ServerAliveInterval=5 -o ServerAliveCountMax=2 -o BatchMode=yes -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' /usr/bin/bash <<'END'
|
||||
ssh -o ConnectTimeout=5 -o ServerAliveInterval=5 -o ServerAliveCountMax=2 -o BatchMode=yes -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' exec /usr/bin/bash <<'END'
|
||||
|
||||
set -e
|
||||
|
||||
export TERM=xterm-256color
|
||||
|
||||
shopt -s huponexit # kill all child processes when the shell exits
|
||||
|
||||
export CI=1
|
||||
@@ -25,6 +27,8 @@ export TEST_DIR=${env.TEST_DIR}
|
||||
export SOURCE_DIR=${env.SOURCE_DIR}
|
||||
export GIT_BRANCH=${env.GIT_BRANCH}
|
||||
export GIT_COMMIT=${env.GIT_COMMIT}
|
||||
export CI_ARTIFACTS_TOKEN=${env.CI_ARTIFACTS_TOKEN}
|
||||
export GITHUB_COMMENTS_TOKEN=${env.GITHUB_COMMENTS_TOKEN}
|
||||
export AZURE_TOKEN='${env.AZURE_TOKEN}'
|
||||
# only use 1 thread for tici tests since most require HIL
|
||||
export PYTEST_ADDOPTS="-n 0"
|
||||
@@ -62,9 +66,7 @@ fi
|
||||
ln -snf ${env.TEST_DIR} /data/pythonpath
|
||||
|
||||
cd ${env.TEST_DIR} || true
|
||||
${cmd}
|
||||
exit 0
|
||||
|
||||
time ${cmd}
|
||||
END"""
|
||||
|
||||
sh script: ssh_cmd, label: step_label
|
||||
@@ -79,15 +81,32 @@ def deviceStage(String stageName, String deviceType, List extra_env, def steps)
|
||||
|
||||
def extra = extra_env.collect { "export ${it}" }.join('\n');
|
||||
def branch = env.BRANCH_NAME ?: 'master';
|
||||
def gitDiff = sh returnStdout: true, script: 'curl -s -H "Authorization: Bearer ${GITHUB_COMMENTS_TOKEN}" https://api.github.com/repos/commaai/openpilot/compare/master...${GIT_BRANCH} | jq .files[].filename || echo "/"', label: 'Getting changes'
|
||||
|
||||
lock(resource: "", label: deviceType, inversePrecedence: true, variable: 'device_ip', quantity: 1, resourceSelectStrategy: 'random') {
|
||||
docker.image('ghcr.io/commaai/alpine-ssh').inside('--user=root') {
|
||||
timeout(time: 35, unit: 'MINUTES') {
|
||||
retry (3) {
|
||||
def date = sh(script: 'date', returnStdout: true).trim();
|
||||
device(device_ip, "set time", "date -s '" + date + "'")
|
||||
device(device_ip, "git checkout", extra + "\n" + readFile("selfdrive/test/setup_device_ci.sh"))
|
||||
}
|
||||
steps.each { item ->
|
||||
device(device_ip, item[0], item[1])
|
||||
def name = item[0]
|
||||
def cmd = item[1]
|
||||
|
||||
def args = item[2]
|
||||
def diffPaths = args.diffPaths ?: []
|
||||
def cmdTimeout = args.timeout ?: 9999
|
||||
|
||||
if (branch != "master" && diffPaths && !hasPathChanged(gitDiff, diffPaths)) {
|
||||
println "Skipping ${name}: no changes in ${diffPaths}."
|
||||
return
|
||||
} else {
|
||||
timeout(time: cmdTimeout, unit: 'SECONDS') {
|
||||
device(device_ip, name, cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,14 +114,38 @@ def deviceStage(String stageName, String deviceType, List extra_env, def steps)
|
||||
}
|
||||
}
|
||||
|
||||
def hasPathChanged(String gitDiff, List<String> paths) {
|
||||
for (path in paths) {
|
||||
if (gitDiff.contains(path)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
def setupCredentials() {
|
||||
withCredentials([
|
||||
string(credentialsId: 'azure_token', variable: 'AZURE_TOKEN'),
|
||||
]) {
|
||||
env.AZURE_TOKEN = "${AZURE_TOKEN}"
|
||||
}
|
||||
|
||||
withCredentials([
|
||||
string(credentialsId: 'ci_artifacts_pat', variable: 'CI_ARTIFACTS_TOKEN'),
|
||||
]) {
|
||||
env.CI_ARTIFACTS_TOKEN = "${CI_ARTIFACTS_TOKEN}"
|
||||
}
|
||||
|
||||
withCredentials([
|
||||
string(credentialsId: 'post_comments_github_pat', variable: 'GITHUB_COMMENTS_TOKEN'),
|
||||
]) {
|
||||
env.GITHUB_COMMENTS_TOKEN = "${GITHUB_COMMENTS_TOKEN}"
|
||||
}
|
||||
}
|
||||
|
||||
def step(String name, String cmd, Map args = [:]) {
|
||||
return [name, cmd, args]
|
||||
}
|
||||
|
||||
node {
|
||||
env.CI = "1"
|
||||
@@ -127,82 +170,90 @@ node {
|
||||
try {
|
||||
if (env.BRANCH_NAME == 'devel-staging') {
|
||||
deviceStage("build release3-staging", "tici-needs-can", [], [
|
||||
["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"),
|
||||
])
|
||||
}
|
||||
|
||||
if (env.BRANCH_NAME == 'master-ci') {
|
||||
deviceStage("build nightly", "tici-needs-can", [], [
|
||||
["build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"],
|
||||
])
|
||||
parallel (
|
||||
'nightly': {
|
||||
deviceStage("build nightly", "tici-needs-can", [], [
|
||||
step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"),
|
||||
])
|
||||
},
|
||||
'nightly-dev': {
|
||||
deviceStage("build nightly-dev", "tici-needs-can", [], [
|
||||
step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/release/build_release.sh"),
|
||||
])
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (!env.BRANCH_NAME.matches(excludeRegex)) {
|
||||
parallel (
|
||||
// tici tests
|
||||
'onroad tests': {
|
||||
deviceStage("onroad", "tici-needs-can", [], [
|
||||
deviceStage("onroad", "tici-needs-can", ["UNSAFE=1"], [
|
||||
// TODO: ideally, this test runs in master-ci, but it takes 5+m to build it
|
||||
//["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR $SOURCE_DIR/scripts/retry.sh ./build_devel.sh"],
|
||||
["build openpilot", "cd system/manager && ./build.py"],
|
||||
["check dirty", "release/check-dirty.sh"],
|
||||
["onroad tests", "pytest selfdrive/test/test_onroad.py -s"],
|
||||
["time to onroad", "pytest selfdrive/test/test_time_to_onroad.py"],
|
||||
step("build openpilot", "cd system/manager && ./build.py"),
|
||||
step("check dirty", "release/check-dirty.sh"),
|
||||
step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]),
|
||||
//["time to onroad", "pytest selfdrive/test/test_time_to_onroad.py"],
|
||||
])
|
||||
},
|
||||
'HW + Unit Tests': {
|
||||
deviceStage("tici-hardware", "tici-common", ["UNSAFE=1"], [
|
||||
["build", "cd system/manager && ./build.py"],
|
||||
["test pandad", "pytest selfdrive/pandad/tests/test_pandad.py"],
|
||||
["test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"],
|
||||
["test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py"],
|
||||
["test pigeond", "pytest system/ubloxd/tests/test_pigeond.py"],
|
||||
["test manager", "pytest system/manager/test/test_manager.py"],
|
||||
step("build", "cd system/manager && ./build.py"),
|
||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
||||
step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"),
|
||||
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py"),
|
||||
step("test pigeond", "pytest system/ubloxd/tests/test_pigeond.py"),
|
||||
step("test manager", "pytest system/manager/test/test_manager.py"),
|
||||
])
|
||||
},
|
||||
'loopback': {
|
||||
deviceStage("loopback", "tici-loopback", ["UNSAFE=1"], [
|
||||
["build openpilot", "cd system/manager && ./build.py"],
|
||||
["test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"],
|
||||
step("build openpilot", "cd system/manager && ./build.py"),
|
||||
step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||
])
|
||||
},
|
||||
'camerad': {
|
||||
deviceStage("AR0231", "tici-ar0231", ["UNSAFE=1"], [
|
||||
["build", "cd system/manager && ./build.py"],
|
||||
["test camerad", "pytest system/camerad/test/test_camerad.py"],
|
||||
["test exposure", "pytest system/camerad/test/test_exposure.py"],
|
||||
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"),
|
||||
])
|
||||
deviceStage("OX03C10", "tici-ox03c10", ["UNSAFE=1"], [
|
||||
["build", "cd system/manager && ./build.py"],
|
||||
["test camerad", "pytest system/camerad/test/test_camerad.py"],
|
||||
["test exposure", "pytest system/camerad/test/test_exposure.py"],
|
||||
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"),
|
||||
])
|
||||
},
|
||||
'sensord': {
|
||||
deviceStage("LSM + MMC", "tici-lsmc", ["UNSAFE=1"], [
|
||||
["build", "cd system/manager && ./build.py"],
|
||||
["test sensord", "pytest system/sensord/tests/test_sensord.py"],
|
||||
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"], [
|
||||
["build", "cd system/manager && ./build.py"],
|
||||
["test sensord", "pytest system/sensord/tests/test_sensord.py"],
|
||||
step("build", "cd system/manager && ./build.py"),
|
||||
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
|
||||
])
|
||||
},
|
||||
'replay': {
|
||||
deviceStage("model-replay", "tici-replay", ["UNSAFE=1"], [
|
||||
["build", "cd system/manager && ./build.py"],
|
||||
["model replay", "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/"]]),
|
||||
])
|
||||
},
|
||||
'tizi': {
|
||||
deviceStage("tizi", "tizi", ["UNSAFE=1"], [
|
||||
["build openpilot", "cd system/manager && ./build.py"],
|
||||
["test pandad loopback", "SINGLE_PANDA=1 pytest selfdrive/pandad/tests/test_pandad_loopback.py"],
|
||||
["test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"],
|
||||
["test pandad", "pytest selfdrive/pandad/tests/test_pandad.py"],
|
||||
["test amp", "pytest system/hardware/tici/tests/test_amplifier.py"],
|
||||
["test hw", "pytest system/hardware/tici/tests/test_hardware.py"],
|
||||
["test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.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 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 qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py"),
|
||||
])
|
||||
},
|
||||
|
||||
|
||||
+9
-1
@@ -1,7 +1,15 @@
|
||||
Version 0.9.8 (2024-XX-XX)
|
||||
========================
|
||||
* New Tomb Raider driving model
|
||||
* New driving model
|
||||
* Trained in brand new ML simulator
|
||||
* Model now gates applying positive accel in Chill mode
|
||||
* New driving monitoring model
|
||||
* Reduced false positives related to passengers
|
||||
* Image processing pipeline moved to the ISP
|
||||
* More GPU time for driving models
|
||||
* Power draw reduced 0.5W, which means your device runs cooler
|
||||
* Added toggle to enable driver monitoring even when openpilot is not engaged
|
||||
* Enable openpilot longitudinal control for Ford Q3 vehicles
|
||||
* New Toyota TSS2 longitudinal tune
|
||||
|
||||
Version 0.9.7 (2024-06-13)
|
||||
|
||||
@@ -1,718 +0,0 @@
|
||||
using Cxx = import "./include/c++.capnp";
|
||||
$Cxx.namespace("cereal");
|
||||
|
||||
@0x8e2af1e708af8b8d;
|
||||
|
||||
# ******* events causing controls state machine transition *******
|
||||
|
||||
# FIXME: OnroadEvent shouldn't be in car.capnp, but can't immediately
|
||||
# move due to being referenced by structs in this file
|
||||
struct OnroadEvent @0x9b1657f34caf3ad3 {
|
||||
name @0 :EventName;
|
||||
|
||||
# event types
|
||||
enable @1 :Bool;
|
||||
noEntry @2 :Bool;
|
||||
warning @3 :Bool; # alerts presented only when enabled or soft disabling
|
||||
userDisable @4 :Bool;
|
||||
softDisable @5 :Bool;
|
||||
immediateDisable @6 :Bool;
|
||||
preEnable @7 :Bool;
|
||||
permanent @8 :Bool; # alerts presented regardless of openpilot state
|
||||
overrideLateral @10 :Bool;
|
||||
overrideLongitudinal @9 :Bool;
|
||||
|
||||
enum EventName @0xbaa8c5d505f727de {
|
||||
canError @0;
|
||||
steerUnavailable @1;
|
||||
wrongGear @4;
|
||||
doorOpen @5;
|
||||
seatbeltNotLatched @6;
|
||||
espDisabled @7;
|
||||
wrongCarMode @8;
|
||||
steerTempUnavailable @9;
|
||||
reverseGear @10;
|
||||
buttonCancel @11;
|
||||
buttonEnable @12;
|
||||
pedalPressed @13; # exits active state
|
||||
preEnableStandstill @73; # added during pre-enable state with brake
|
||||
gasPressedOverride @108; # added when user is pressing gas with no disengage on gas
|
||||
steerOverride @114;
|
||||
cruiseDisabled @14;
|
||||
speedTooLow @17;
|
||||
outOfSpace @18;
|
||||
overheat @19;
|
||||
calibrationIncomplete @20;
|
||||
calibrationInvalid @21;
|
||||
calibrationRecalibrating @117;
|
||||
controlsMismatch @22;
|
||||
pcmEnable @23;
|
||||
pcmDisable @24;
|
||||
radarFault @26;
|
||||
brakeHold @28;
|
||||
parkBrake @29;
|
||||
manualRestart @30;
|
||||
lowSpeedLockout @31;
|
||||
joystickDebug @34;
|
||||
longitudinalManeuver @124;
|
||||
steerTempUnavailableSilent @35;
|
||||
resumeRequired @36;
|
||||
preDriverDistracted @37;
|
||||
promptDriverDistracted @38;
|
||||
driverDistracted @39;
|
||||
preDriverUnresponsive @43;
|
||||
promptDriverUnresponsive @44;
|
||||
driverUnresponsive @45;
|
||||
belowSteerSpeed @46;
|
||||
lowBattery @48;
|
||||
accFaulted @51;
|
||||
sensorDataInvalid @52;
|
||||
commIssue @53;
|
||||
commIssueAvgFreq @109;
|
||||
tooDistracted @54;
|
||||
posenetInvalid @55;
|
||||
soundsUnavailable @56;
|
||||
preLaneChangeLeft @57;
|
||||
preLaneChangeRight @58;
|
||||
laneChange @59;
|
||||
lowMemory @63;
|
||||
stockAeb @64;
|
||||
ldw @65;
|
||||
carUnrecognized @66;
|
||||
invalidLkasSetting @69;
|
||||
speedTooHigh @70;
|
||||
laneChangeBlocked @71;
|
||||
relayMalfunction @72;
|
||||
stockFcw @74;
|
||||
startup @75;
|
||||
startupNoCar @76;
|
||||
startupNoControl @77;
|
||||
startupMaster @78;
|
||||
fcw @79;
|
||||
steerSaturated @80;
|
||||
belowEngageSpeed @84;
|
||||
noGps @85;
|
||||
wrongCruiseMode @87;
|
||||
modeldLagging @89;
|
||||
deviceFalling @90;
|
||||
fanMalfunction @91;
|
||||
cameraMalfunction @92;
|
||||
cameraFrameRate @110;
|
||||
processNotRunning @95;
|
||||
dashcamMode @96;
|
||||
selfdriveInitializing @98;
|
||||
usbError @99;
|
||||
cruiseMismatch @106;
|
||||
lkasDisabled @107;
|
||||
canBusMissing @111;
|
||||
selfdrivedLagging @112;
|
||||
resumeBlocked @113;
|
||||
steerTimeLimit @115;
|
||||
vehicleSensorsInvalid @116;
|
||||
locationdTemporaryError @103;
|
||||
locationdPermanentError @118;
|
||||
paramsdTemporaryError @50;
|
||||
paramsdPermanentError @119;
|
||||
actuatorsApiUnavailable @120;
|
||||
espActive @121;
|
||||
personalityChanged @122;
|
||||
aeb @123;
|
||||
|
||||
radarCanErrorDEPRECATED @15;
|
||||
communityFeatureDisallowedDEPRECATED @62;
|
||||
radarCommIssueDEPRECATED @67;
|
||||
driverMonitorLowAccDEPRECATED @68;
|
||||
gasUnavailableDEPRECATED @3;
|
||||
dataNeededDEPRECATED @16;
|
||||
modelCommIssueDEPRECATED @27;
|
||||
ipasOverrideDEPRECATED @33;
|
||||
geofenceDEPRECATED @40;
|
||||
driverMonitorOnDEPRECATED @41;
|
||||
driverMonitorOffDEPRECATED @42;
|
||||
calibrationProgressDEPRECATED @47;
|
||||
invalidGiraffeHondaDEPRECATED @49;
|
||||
invalidGiraffeToyotaDEPRECATED @60;
|
||||
internetConnectivityNeededDEPRECATED @61;
|
||||
whitePandaUnsupportedDEPRECATED @81;
|
||||
commIssueWarningDEPRECATED @83;
|
||||
focusRecoverActiveDEPRECATED @86;
|
||||
neosUpdateRequiredDEPRECATED @88;
|
||||
modelLagWarningDEPRECATED @93;
|
||||
startupOneplusDEPRECATED @82;
|
||||
startupFuzzyFingerprintDEPRECATED @97;
|
||||
noTargetDEPRECATED @25;
|
||||
brakeUnavailableDEPRECATED @2;
|
||||
plannerErrorDEPRECATED @32;
|
||||
gpsMalfunctionDEPRECATED @94;
|
||||
roadCameraErrorDEPRECATED @100;
|
||||
driverCameraErrorDEPRECATED @101;
|
||||
wideRoadCameraErrorDEPRECATED @102;
|
||||
highCpuUsageDEPRECATED @105;
|
||||
startupNoFwDEPRECATED @104;
|
||||
}
|
||||
}
|
||||
|
||||
# ******* main car state @ 100hz *******
|
||||
# all speeds in m/s
|
||||
|
||||
struct CarState {
|
||||
events @13 :List(OnroadEvent);
|
||||
|
||||
# CAN health
|
||||
canValid @26 :Bool; # invalid counter/checksums
|
||||
canTimeout @40 :Bool; # CAN bus dropped out
|
||||
canErrorCounter @48 :UInt32;
|
||||
|
||||
# car speed
|
||||
vEgo @1 :Float32; # best estimate of speed
|
||||
aEgo @16 :Float32; # best estimate of aCAN cceleration
|
||||
vEgoRaw @17 :Float32; # unfiltered speed from wheel speed sensors
|
||||
vEgoCluster @44 :Float32; # best estimate of speed shown on car's instrument cluster, used for UI
|
||||
|
||||
vCruise @53 :Float32; # actual set speed
|
||||
vCruiseCluster @54 :Float32; # set speed to display in the UI
|
||||
|
||||
yawRate @22 :Float32; # best estimate of yaw rate
|
||||
standstill @18 :Bool;
|
||||
wheelSpeeds @2 :WheelSpeeds;
|
||||
|
||||
# gas pedal, 0.0-1.0
|
||||
gas @3 :Float32; # this is user pedal only
|
||||
gasPressed @4 :Bool; # this is user pedal only
|
||||
|
||||
engineRpm @46 :Float32;
|
||||
|
||||
# brake pedal, 0.0-1.0
|
||||
brake @5 :Float32; # this is user pedal only
|
||||
brakePressed @6 :Bool; # this is user pedal only
|
||||
regenBraking @45 :Bool; # this is user pedal only
|
||||
parkingBrake @39 :Bool;
|
||||
brakeHoldActive @38 :Bool;
|
||||
|
||||
# steering wheel
|
||||
steeringAngleDeg @7 :Float32;
|
||||
steeringAngleOffsetDeg @37 :Float32; # Offset betweens sensors in case there multiple
|
||||
steeringRateDeg @15 :Float32;
|
||||
steeringTorque @8 :Float32; # TODO: standardize units
|
||||
steeringTorqueEps @27 :Float32; # TODO: standardize units
|
||||
steeringPressed @9 :Bool; # if the user is using the steering wheel
|
||||
steerFaultTemporary @35 :Bool; # temporary EPS fault
|
||||
steerFaultPermanent @36 :Bool; # permanent EPS fault
|
||||
stockAeb @30 :Bool;
|
||||
stockFcw @31 :Bool;
|
||||
espDisabled @32 :Bool;
|
||||
accFaulted @42 :Bool;
|
||||
carFaultedNonCritical @47 :Bool; # some ECU is faulted, but car remains controllable
|
||||
espActive @51 :Bool;
|
||||
vehicleSensorsInvalid @52 :Bool; # invalid steering angle readings, etc.
|
||||
|
||||
# cruise state
|
||||
cruiseState @10 :CruiseState;
|
||||
|
||||
# gear
|
||||
gearShifter @14 :GearShifter;
|
||||
|
||||
# button presses
|
||||
buttonEvents @11 :List(ButtonEvent);
|
||||
leftBlinker @20 :Bool;
|
||||
rightBlinker @21 :Bool;
|
||||
genericToggle @23 :Bool;
|
||||
|
||||
# lock info
|
||||
doorOpen @24 :Bool;
|
||||
seatbeltUnlatched @25 :Bool;
|
||||
|
||||
# clutch (manual transmission only)
|
||||
clutchPressed @28 :Bool;
|
||||
|
||||
# blindspot sensors
|
||||
leftBlindspot @33 :Bool; # Is there something blocking the left lane change
|
||||
rightBlindspot @34 :Bool; # Is there something blocking the right lane change
|
||||
|
||||
fuelGauge @41 :Float32; # battery or fuel tank level from 0.0 to 1.0
|
||||
charging @43 :Bool;
|
||||
|
||||
# process meta
|
||||
cumLagMs @50 :Float32;
|
||||
|
||||
struct WheelSpeeds {
|
||||
# optional wheel speeds
|
||||
fl @0 :Float32;
|
||||
fr @1 :Float32;
|
||||
rl @2 :Float32;
|
||||
rr @3 :Float32;
|
||||
}
|
||||
|
||||
struct CruiseState {
|
||||
enabled @0 :Bool;
|
||||
speed @1 :Float32;
|
||||
speedCluster @6 :Float32; # Set speed as shown on instrument cluster
|
||||
available @2 :Bool;
|
||||
speedOffset @3 :Float32;
|
||||
standstill @4 :Bool;
|
||||
nonAdaptive @5 :Bool;
|
||||
}
|
||||
|
||||
enum GearShifter {
|
||||
unknown @0;
|
||||
park @1;
|
||||
drive @2;
|
||||
neutral @3;
|
||||
reverse @4;
|
||||
sport @5;
|
||||
low @6;
|
||||
brake @7;
|
||||
eco @8;
|
||||
manumatic @9;
|
||||
}
|
||||
|
||||
# send on change
|
||||
struct ButtonEvent {
|
||||
pressed @0 :Bool;
|
||||
type @1 :Type;
|
||||
|
||||
enum Type {
|
||||
unknown @0;
|
||||
leftBlinker @1;
|
||||
rightBlinker @2;
|
||||
accelCruise @3;
|
||||
decelCruise @4;
|
||||
cancel @5;
|
||||
altButton1 @6;
|
||||
altButton2 @7;
|
||||
altButton3 @8;
|
||||
setCruise @9;
|
||||
resumeCruise @10;
|
||||
gapAdjustCruise @11;
|
||||
}
|
||||
}
|
||||
|
||||
# deprecated
|
||||
errorsDEPRECATED @0 :List(OnroadEvent.EventName);
|
||||
brakeLightsDEPRECATED @19 :Bool;
|
||||
steeringRateLimitedDEPRECATED @29 :Bool;
|
||||
canMonoTimesDEPRECATED @12: List(UInt64);
|
||||
canRcvTimeoutDEPRECATED @49 :Bool;
|
||||
}
|
||||
|
||||
# ******* radar state @ 20hz *******
|
||||
|
||||
struct RadarData @0x888ad6581cf0aacb {
|
||||
errors @0 :List(Error);
|
||||
points @1 :List(RadarPoint);
|
||||
|
||||
enum Error {
|
||||
canError @0;
|
||||
fault @1;
|
||||
wrongConfig @2;
|
||||
}
|
||||
|
||||
# similar to LiveTracks
|
||||
# is one timestamp valid for all? I think so
|
||||
struct RadarPoint {
|
||||
trackId @0 :UInt64; # no trackId reuse
|
||||
|
||||
# these 3 are the minimum required
|
||||
dRel @1 :Float32; # m from the front bumper of the car
|
||||
yRel @2 :Float32; # m
|
||||
vRel @3 :Float32; # m/s
|
||||
|
||||
# these are optional and valid if they are not NaN
|
||||
aRel @4 :Float32; # m/s^2
|
||||
yvRel @5 :Float32; # m/s
|
||||
|
||||
# some radars flag measurements VS estimates
|
||||
measured @6 :Bool;
|
||||
}
|
||||
|
||||
# deprecated
|
||||
canMonoTimesDEPRECATED @2 :List(UInt64);
|
||||
}
|
||||
|
||||
# ******* car controls @ 100hz *******
|
||||
|
||||
struct CarControl {
|
||||
# must be true for any actuator commands to work
|
||||
enabled @0 :Bool;
|
||||
latActive @11: Bool;
|
||||
longActive @12: Bool;
|
||||
|
||||
# Final actuator commands
|
||||
actuators @6 :Actuators;
|
||||
|
||||
# Blinker controls
|
||||
leftBlinker @15: Bool;
|
||||
rightBlinker @16: Bool;
|
||||
|
||||
orientationNED @13 :List(Float32);
|
||||
angularVelocity @14 :List(Float32);
|
||||
|
||||
cruiseControl @4 :CruiseControl;
|
||||
hudControl @5 :HUDControl;
|
||||
|
||||
struct Actuators {
|
||||
# lateral commands, mutually exclusive
|
||||
steer @2: Float32; # [0.0, 1.0]
|
||||
steeringAngleDeg @3: Float32;
|
||||
curvature @7: Float32;
|
||||
|
||||
# longitudinal commands
|
||||
accel @4: Float32; # m/s^2
|
||||
longControlState @5: LongControlState;
|
||||
|
||||
# these are only for logging the actual values sent to the car over CAN
|
||||
gas @0: Float32; # [0.0, 1.0]
|
||||
brake @1: Float32; # [0.0, 1.0]
|
||||
steerOutputCan @8: Float32; # value sent over can to the car
|
||||
speed @6: Float32; # m/s
|
||||
|
||||
enum LongControlState @0xe40f3a917d908282{
|
||||
off @0;
|
||||
pid @1;
|
||||
stopping @2;
|
||||
starting @3;
|
||||
}
|
||||
}
|
||||
|
||||
struct CruiseControl {
|
||||
cancel @0: Bool;
|
||||
resume @1: Bool;
|
||||
override @4: Bool;
|
||||
speedOverrideDEPRECATED @2: Float32;
|
||||
accelOverrideDEPRECATED @3: Float32;
|
||||
}
|
||||
|
||||
struct HUDControl {
|
||||
speedVisible @0: Bool;
|
||||
setSpeed @1: Float32;
|
||||
lanesVisible @2: Bool;
|
||||
leadVisible @3: Bool;
|
||||
visualAlert @4: VisualAlert;
|
||||
audibleAlert @5: AudibleAlert;
|
||||
rightLaneVisible @6: Bool;
|
||||
leftLaneVisible @7: Bool;
|
||||
rightLaneDepart @8: Bool;
|
||||
leftLaneDepart @9: Bool;
|
||||
leadDistanceBars @10: Int8; # 1-3: 1 is closest, 3 is farthest. some ports may utilize 2-4 bars instead
|
||||
|
||||
enum VisualAlert {
|
||||
# these are the choices from the Honda
|
||||
# map as good as you can for your car
|
||||
none @0;
|
||||
fcw @1;
|
||||
steerRequired @2;
|
||||
brakePressed @3;
|
||||
wrongGear @4;
|
||||
seatbeltUnbuckled @5;
|
||||
speedTooHigh @6;
|
||||
ldw @7;
|
||||
}
|
||||
|
||||
enum AudibleAlert {
|
||||
none @0;
|
||||
|
||||
engage @1;
|
||||
disengage @2;
|
||||
refuse @3;
|
||||
|
||||
warningSoft @4;
|
||||
warningImmediate @5;
|
||||
|
||||
prompt @6;
|
||||
promptRepeat @7;
|
||||
promptDistracted @8;
|
||||
}
|
||||
}
|
||||
|
||||
gasDEPRECATED @1 :Float32;
|
||||
brakeDEPRECATED @2 :Float32;
|
||||
steeringTorqueDEPRECATED @3 :Float32;
|
||||
activeDEPRECATED @7 :Bool;
|
||||
rollDEPRECATED @8 :Float32;
|
||||
pitchDEPRECATED @9 :Float32;
|
||||
actuatorsOutputDEPRECATED @10 :Actuators;
|
||||
}
|
||||
|
||||
struct CarOutput {
|
||||
# Any car specific rate limits or quirks applied by
|
||||
# the CarController are reflected in actuatorsOutput
|
||||
# and matches what is sent to the car
|
||||
actuatorsOutput @0 :CarControl.Actuators;
|
||||
}
|
||||
|
||||
# ****** car param ******
|
||||
|
||||
struct CarParams {
|
||||
carName @0 :Text;
|
||||
carFingerprint @1 :Text;
|
||||
fuzzyFingerprint @55 :Bool;
|
||||
|
||||
notCar @66 :Bool; # flag for non-car robotics platforms
|
||||
|
||||
pcmCruise @3 :Bool; # is openpilot's state tied to the PCM's cruise state?
|
||||
enableDsu @5 :Bool; # driving support unit
|
||||
enableBsm @56 :Bool; # blind spot monitoring
|
||||
flags @64 :UInt32; # flags for car specific quirks
|
||||
experimentalLongitudinalAvailable @71 :Bool;
|
||||
|
||||
minEnableSpeed @7 :Float32;
|
||||
minSteerSpeed @8 :Float32;
|
||||
safetyConfigs @62 :List(SafetyConfig);
|
||||
alternativeExperience @65 :Int16; # panda flag for features like no disengage on gas
|
||||
|
||||
# Car docs fields
|
||||
maxLateralAccel @68 :Float32;
|
||||
autoResumeSng @69 :Bool; # describes whether car can resume from a stop automatically
|
||||
|
||||
# things about the car in the manual
|
||||
mass @17 :Float32; # [kg] curb weight: all fluids no cargo
|
||||
wheelbase @18 :Float32; # [m] distance from rear axle to front axle
|
||||
centerToFront @19 :Float32; # [m] distance from center of mass to front axle
|
||||
steerRatio @20 :Float32; # [] ratio of steering wheel angle to front wheel angle
|
||||
steerRatioRear @21 :Float32; # [] ratio of steering wheel angle to rear wheel angle (usually 0)
|
||||
|
||||
# things we can derive
|
||||
rotationalInertia @22 :Float32; # [kg*m2] body rotational inertia
|
||||
tireStiffnessFactor @72 :Float32; # scaling factor used in calculating tireStiffness[Front,Rear]
|
||||
tireStiffnessFront @23 :Float32; # [N/rad] front tire coeff of stiff
|
||||
tireStiffnessRear @24 :Float32; # [N/rad] rear tire coeff of stiff
|
||||
|
||||
longitudinalTuning @25 :LongitudinalPIDTuning;
|
||||
lateralParams @48 :LateralParams;
|
||||
lateralTuning :union {
|
||||
pid @26 :LateralPIDTuning;
|
||||
indiDEPRECATED @27 :LateralINDITuning;
|
||||
lqrDEPRECATED @40 :LateralLQRTuning;
|
||||
torque @67 :LateralTorqueTuning;
|
||||
}
|
||||
|
||||
steerLimitAlert @28 :Bool;
|
||||
steerLimitTimer @47 :Float32; # time before steerLimitAlert is issued
|
||||
|
||||
vEgoStopping @29 :Float32; # Speed at which the car goes into stopping state
|
||||
vEgoStarting @59 :Float32; # Speed at which the car goes into starting state
|
||||
stoppingControl @31 :Bool; # Does the car allow full control even at lows speeds when stopping
|
||||
steerControlType @34 :SteerControlType;
|
||||
radarUnavailable @35 :Bool; # True when radar objects aren't visible on CAN or aren't parsed out
|
||||
stopAccel @60 :Float32; # Required acceleration to keep vehicle stationary
|
||||
stoppingDecelRate @52 :Float32; # m/s^2/s while trying to stop
|
||||
startAccel @32 :Float32; # Required acceleration to get car moving
|
||||
startingState @70 :Bool; # Does this car make use of special starting state
|
||||
|
||||
steerActuatorDelay @36 :Float32; # Steering wheel actuator delay in seconds
|
||||
longitudinalActuatorDelay @58 :Float32; # Gas/Brake actuator delay in seconds
|
||||
openpilotLongitudinalControl @37 :Bool; # is openpilot doing the longitudinal control?
|
||||
carVin @38 :Text; # VIN number queried during fingerprinting
|
||||
dashcamOnly @41: Bool;
|
||||
passive @73: Bool; # is openpilot in control?
|
||||
transmissionType @43 :TransmissionType;
|
||||
carFw @44 :List(CarFw);
|
||||
|
||||
radarTimeStep @45: Float32 = 0.05; # time delta between radar updates, 20Hz is very standard
|
||||
radarDelay @74 :Float32;
|
||||
fingerprintSource @49: FingerprintSource;
|
||||
networkLocation @50 :NetworkLocation; # Where Panda/C2 is integrated into the car's CAN network
|
||||
|
||||
wheelSpeedFactor @63 :Float32; # Multiplier on wheels speeds to computer actual speeds
|
||||
|
||||
struct SafetyConfig {
|
||||
safetyModel @0 :SafetyModel;
|
||||
safetyParam @3 :UInt16;
|
||||
safetyParamDEPRECATED @1 :Int16;
|
||||
safetyParam2DEPRECATED @2 :UInt32;
|
||||
}
|
||||
|
||||
struct LateralParams {
|
||||
torqueBP @0 :List(Int32);
|
||||
torqueV @1 :List(Int32);
|
||||
}
|
||||
|
||||
struct LateralPIDTuning {
|
||||
kpBP @0 :List(Float32);
|
||||
kpV @1 :List(Float32);
|
||||
kiBP @2 :List(Float32);
|
||||
kiV @3 :List(Float32);
|
||||
kf @4 :Float32;
|
||||
}
|
||||
|
||||
struct LateralTorqueTuning {
|
||||
useSteeringAngle @0 :Bool;
|
||||
kp @1 :Float32;
|
||||
ki @2 :Float32;
|
||||
friction @3 :Float32;
|
||||
kf @4 :Float32;
|
||||
steeringAngleDeadzoneDeg @5 :Float32;
|
||||
latAccelFactor @6 :Float32;
|
||||
latAccelOffset @7 :Float32;
|
||||
}
|
||||
|
||||
struct LongitudinalPIDTuning {
|
||||
kpBP @0 :List(Float32);
|
||||
kpV @1 :List(Float32);
|
||||
kiBP @2 :List(Float32);
|
||||
kiV @3 :List(Float32);
|
||||
kf @6 :Float32;
|
||||
deadzoneBPDEPRECATED @4 :List(Float32);
|
||||
deadzoneVDEPRECATED @5 :List(Float32);
|
||||
}
|
||||
|
||||
struct LateralINDITuning {
|
||||
outerLoopGainBP @4 :List(Float32);
|
||||
outerLoopGainV @5 :List(Float32);
|
||||
innerLoopGainBP @6 :List(Float32);
|
||||
innerLoopGainV @7 :List(Float32);
|
||||
timeConstantBP @8 :List(Float32);
|
||||
timeConstantV @9 :List(Float32);
|
||||
actuatorEffectivenessBP @10 :List(Float32);
|
||||
actuatorEffectivenessV @11 :List(Float32);
|
||||
|
||||
outerLoopGainDEPRECATED @0 :Float32;
|
||||
innerLoopGainDEPRECATED @1 :Float32;
|
||||
timeConstantDEPRECATED @2 :Float32;
|
||||
actuatorEffectivenessDEPRECATED @3 :Float32;
|
||||
}
|
||||
|
||||
struct LateralLQRTuning {
|
||||
scale @0 :Float32;
|
||||
ki @1 :Float32;
|
||||
dcGain @2 :Float32;
|
||||
|
||||
# State space system
|
||||
a @3 :List(Float32);
|
||||
b @4 :List(Float32);
|
||||
c @5 :List(Float32);
|
||||
|
||||
k @6 :List(Float32); # LQR gain
|
||||
l @7 :List(Float32); # Kalman gain
|
||||
}
|
||||
|
||||
enum SafetyModel {
|
||||
silent @0;
|
||||
hondaNidec @1;
|
||||
toyota @2;
|
||||
elm327 @3;
|
||||
gm @4;
|
||||
hondaBoschGiraffe @5;
|
||||
ford @6;
|
||||
cadillac @7;
|
||||
hyundai @8;
|
||||
chrysler @9;
|
||||
tesla @10;
|
||||
subaru @11;
|
||||
gmPassive @12;
|
||||
mazda @13;
|
||||
nissan @14;
|
||||
volkswagen @15;
|
||||
toyotaIpas @16;
|
||||
allOutput @17;
|
||||
gmAscm @18;
|
||||
noOutput @19; # like silent but without silent CAN TXs
|
||||
hondaBosch @20;
|
||||
volkswagenPq @21;
|
||||
subaruPreglobal @22; # pre-Global platform
|
||||
hyundaiLegacy @23;
|
||||
hyundaiCommunity @24;
|
||||
volkswagenMlb @25;
|
||||
hongqi @26;
|
||||
body @27;
|
||||
hyundaiCanfd @28;
|
||||
volkswagenMqbEvo @29;
|
||||
chryslerCusw @30;
|
||||
psa @31;
|
||||
fcaGiorgio @32;
|
||||
}
|
||||
|
||||
enum SteerControlType {
|
||||
torque @0;
|
||||
angle @1;
|
||||
|
||||
curvatureDEPRECATED @2;
|
||||
}
|
||||
|
||||
enum TransmissionType {
|
||||
unknown @0;
|
||||
automatic @1; # Traditional auto, including DSG
|
||||
manual @2; # True "stick shift" only
|
||||
direct @3; # Electric vehicle or other direct drive
|
||||
cvt @4;
|
||||
}
|
||||
|
||||
struct CarFw {
|
||||
ecu @0 :Ecu;
|
||||
fwVersion @1 :Data;
|
||||
address @2 :UInt32;
|
||||
subAddress @3 :UInt8;
|
||||
responseAddress @4 :UInt32;
|
||||
request @5 :List(Data);
|
||||
brand @6 :Text;
|
||||
bus @7 :UInt8;
|
||||
logging @8 :Bool;
|
||||
obdMultiplexing @9 :Bool;
|
||||
}
|
||||
|
||||
enum Ecu {
|
||||
eps @0;
|
||||
abs @1;
|
||||
fwdRadar @2;
|
||||
fwdCamera @3;
|
||||
engine @4;
|
||||
unknown @5;
|
||||
transmission @8; # Transmission Control Module
|
||||
hybrid @18; # hybrid control unit, e.g. Chrysler's HCP, Honda's IMA Control Unit, Toyota's hybrid control computer
|
||||
srs @9; # airbag
|
||||
gateway @10; # can gateway
|
||||
hud @11; # heads up display
|
||||
combinationMeter @12; # instrument cluster
|
||||
electricBrakeBooster @15;
|
||||
shiftByWire @16;
|
||||
adas @19;
|
||||
cornerRadar @21;
|
||||
hvac @20;
|
||||
parkingAdas @7; # parking assist system ECU, e.g. Toyota's IPAS, Hyundai's RSPA, etc.
|
||||
epb @22; # electronic parking brake
|
||||
telematics @23;
|
||||
body @24; # body control module
|
||||
|
||||
# Toyota only
|
||||
dsu @6;
|
||||
|
||||
# Honda only
|
||||
vsa @13; # Vehicle Stability Assist
|
||||
programmedFuelInjection @14;
|
||||
|
||||
debug @17;
|
||||
}
|
||||
|
||||
enum FingerprintSource {
|
||||
can @0;
|
||||
fw @1;
|
||||
fixed @2;
|
||||
}
|
||||
|
||||
enum NetworkLocation {
|
||||
fwdCamera @0; # Standard/default integration at LKAS camera
|
||||
gateway @1; # Integration at vehicle's CAN gateway
|
||||
}
|
||||
|
||||
enableGasInterceptorDEPRECATED @2 :Bool;
|
||||
enableCameraDEPRECATED @4 :Bool;
|
||||
enableApgsDEPRECATED @6 :Bool;
|
||||
steerRateCostDEPRECATED @33 :Float32;
|
||||
isPandaBlackDEPRECATED @39 :Bool;
|
||||
hasStockCameraDEPRECATED @57 :Bool;
|
||||
safetyParamDEPRECATED @10 :Int16;
|
||||
safetyModelDEPRECATED @9 :SafetyModel;
|
||||
safetyModelPassiveDEPRECATED @42 :SafetyModel = silent;
|
||||
minSpeedCanDEPRECATED @51 :Float32;
|
||||
communityFeatureDEPRECATED @46: Bool;
|
||||
startingAccelRateDEPRECATED @53 :Float32;
|
||||
steerMaxBPDEPRECATED @11 :List(Float32);
|
||||
steerMaxVDEPRECATED @12 :List(Float32);
|
||||
gasMaxBPDEPRECATED @13 :List(Float32);
|
||||
gasMaxVDEPRECATED @14 :List(Float32);
|
||||
brakeMaxBPDEPRECATED @15 :List(Float32);
|
||||
brakeMaxVDEPRECATED @16 :List(Float32);
|
||||
directAccelControlDEPRECATED @30 :Bool;
|
||||
maxSteeringAngleDegDEPRECATED @54 :Float32;
|
||||
longitudinalActuatorDelayLowerBoundDEPRECATED @61 :Float32;
|
||||
}
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../opendbc_repo/opendbc/car/car.capnp
|
||||
+120
-5
@@ -17,6 +17,119 @@ struct Map(Key, Value) {
|
||||
}
|
||||
}
|
||||
|
||||
struct OnroadEvent @0xc4fa6047f024e718 {
|
||||
name @0 :EventName;
|
||||
|
||||
# event types
|
||||
enable @1 :Bool;
|
||||
noEntry @2 :Bool;
|
||||
warning @3 :Bool; # alerts presented only when enabled or soft disabling
|
||||
userDisable @4 :Bool;
|
||||
softDisable @5 :Bool;
|
||||
immediateDisable @6 :Bool;
|
||||
preEnable @7 :Bool;
|
||||
permanent @8 :Bool; # alerts presented regardless of openpilot state
|
||||
overrideLateral @10 :Bool;
|
||||
overrideLongitudinal @9 :Bool;
|
||||
|
||||
enum EventName @0x91f1992a1f77fb03 {
|
||||
canError @0;
|
||||
steerUnavailable @1;
|
||||
wrongGear @2;
|
||||
doorOpen @3;
|
||||
seatbeltNotLatched @4;
|
||||
espDisabled @5;
|
||||
wrongCarMode @6;
|
||||
steerTempUnavailable @7;
|
||||
reverseGear @8;
|
||||
buttonCancel @9;
|
||||
buttonEnable @10;
|
||||
pedalPressed @11; # exits active state
|
||||
preEnableStandstill @12; # added during pre-enable state with brake
|
||||
gasPressedOverride @13; # added when user is pressing gas with no disengage on gas
|
||||
steerOverride @14;
|
||||
cruiseDisabled @15;
|
||||
speedTooLow @16;
|
||||
outOfSpace @17;
|
||||
overheat @18;
|
||||
calibrationIncomplete @19;
|
||||
calibrationInvalid @20;
|
||||
calibrationRecalibrating @21;
|
||||
controlsMismatch @22;
|
||||
pcmEnable @23;
|
||||
pcmDisable @24;
|
||||
radarFault @25;
|
||||
brakeHold @26;
|
||||
parkBrake @27;
|
||||
manualRestart @28;
|
||||
joystickDebug @29;
|
||||
longitudinalManeuver @30;
|
||||
steerTempUnavailableSilent @31;
|
||||
resumeRequired @32;
|
||||
preDriverDistracted @33;
|
||||
promptDriverDistracted @34;
|
||||
driverDistracted @35;
|
||||
preDriverUnresponsive @36;
|
||||
promptDriverUnresponsive @37;
|
||||
driverUnresponsive @38;
|
||||
belowSteerSpeed @39;
|
||||
lowBattery @40;
|
||||
accFaulted @41;
|
||||
sensorDataInvalid @42;
|
||||
commIssue @43;
|
||||
commIssueAvgFreq @44;
|
||||
tooDistracted @45;
|
||||
posenetInvalid @46;
|
||||
preLaneChangeLeft @48;
|
||||
preLaneChangeRight @49;
|
||||
laneChange @50;
|
||||
lowMemory @51;
|
||||
stockAeb @52;
|
||||
ldw @53;
|
||||
carUnrecognized @54;
|
||||
invalidLkasSetting @55;
|
||||
speedTooHigh @56;
|
||||
laneChangeBlocked @57;
|
||||
relayMalfunction @58;
|
||||
stockFcw @59;
|
||||
startup @60;
|
||||
startupNoCar @61;
|
||||
startupNoControl @62;
|
||||
startupNoSecOcKey @63;
|
||||
startupMaster @64;
|
||||
fcw @65;
|
||||
steerSaturated @66;
|
||||
belowEngageSpeed @67;
|
||||
noGps @68;
|
||||
wrongCruiseMode @69;
|
||||
modeldLagging @70;
|
||||
deviceFalling @71;
|
||||
fanMalfunction @72;
|
||||
cameraMalfunction @73;
|
||||
cameraFrameRate @74;
|
||||
processNotRunning @75;
|
||||
dashcamMode @76;
|
||||
selfdriveInitializing @77;
|
||||
usbError @78;
|
||||
cruiseMismatch @79;
|
||||
canBusMissing @80;
|
||||
selfdrivedLagging @81;
|
||||
resumeBlocked @82;
|
||||
steerTimeLimit @83;
|
||||
vehicleSensorsInvalid @84;
|
||||
locationdTemporaryError @85;
|
||||
locationdPermanentError @86;
|
||||
paramsdTemporaryError @87;
|
||||
paramsdPermanentError @88;
|
||||
actuatorsApiUnavailable @89;
|
||||
espActive @90;
|
||||
personalityChanged @91;
|
||||
aeb @92;
|
||||
|
||||
soundsUnavailableDEPRECATED @47;
|
||||
}
|
||||
}
|
||||
|
||||
enum LongitudinalPersonality {
|
||||
aggressive @0;
|
||||
standard @1;
|
||||
@@ -731,7 +844,6 @@ struct ControlsState @0x97ff69c53601abf1 {
|
||||
lateralPlanMonoTime @50 :UInt64;
|
||||
|
||||
longControlState @30 :Car.CarControl.Actuators.LongControlState;
|
||||
vTargetLead @3 :Float32;
|
||||
upAccelCmd @4 :Float32;
|
||||
uiAccelCmd @5 :Float32;
|
||||
ufAccelCmd @33 :Float32;
|
||||
@@ -740,7 +852,6 @@ struct ControlsState @0x97ff69c53601abf1 {
|
||||
forceDecel @51 :Bool;
|
||||
|
||||
lateralControlState :union {
|
||||
indiState @52 :LateralINDIState;
|
||||
pidState @53 :LateralPIDState;
|
||||
angleState @58 :LateralAngleState;
|
||||
debugState @59 :LateralDebugState;
|
||||
@@ -748,6 +859,7 @@ struct ControlsState @0x97ff69c53601abf1 {
|
||||
|
||||
curvatureStateDEPRECATED @65 :LateralCurvatureState;
|
||||
lqrStateDEPRECATED @55 :LateralLQRState;
|
||||
indiStateDEPRECATED @52 :LateralINDIState;
|
||||
}
|
||||
|
||||
struct LateralINDIState {
|
||||
@@ -881,6 +993,7 @@ struct ControlsState @0x97ff69c53601abf1 {
|
||||
startMonoTimeDEPRECATED @48 :UInt64;
|
||||
cumLagMsDEPRECATED @15 :Float32;
|
||||
aTargetDEPRECATED @35 :Float32;
|
||||
vTargetLeadDEPRECATED @3 :Float32;
|
||||
}
|
||||
|
||||
struct DrivingModelData {
|
||||
@@ -1157,7 +1270,7 @@ struct LongitudinalPlan @0xe00b5b3eba12876c {
|
||||
radarValidDEPRECATED @28 :Bool;
|
||||
radarCanErrorDEPRECATED @30 :Bool;
|
||||
commIssueDEPRECATED @31 :Bool;
|
||||
eventsDEPRECATED @13 :List(Car.OnroadEvent);
|
||||
eventsDEPRECATED @13 :List(Car.OnroadEventDEPRECATED);
|
||||
gpsTrajectoryDEPRECATED @12 :GpsTrajectory;
|
||||
gpsPlannerActiveDEPRECATED @19 :Bool;
|
||||
personalityDEPRECATED @36 :LongitudinalPersonality;
|
||||
@@ -2072,7 +2185,7 @@ struct DriverStateDEPRECATED @0xb83c6cc593ed0a00 {
|
||||
}
|
||||
|
||||
struct DriverMonitoringState @0xb83cda094a1da284 {
|
||||
events @0 :List(Car.OnroadEvent);
|
||||
events @18 :List(OnroadEvent);
|
||||
faceDetected @1 :Bool;
|
||||
isDistracted @2 :Bool;
|
||||
distractedType @17 :UInt32;
|
||||
@@ -2091,6 +2204,7 @@ struct DriverMonitoringState @0xb83cda094a1da284 {
|
||||
|
||||
isPreviewDEPRECATED @15 :Bool;
|
||||
rhdCheckedDEPRECATED @5 :Bool;
|
||||
eventsDEPRECATED @0 :List(Car.OnroadEventDEPRECATED);
|
||||
}
|
||||
|
||||
struct Boot {
|
||||
@@ -2369,7 +2483,7 @@ struct Event {
|
||||
liveTorqueParameters @94 :LiveTorqueParametersData;
|
||||
cameraOdometry @63 :CameraOdometry;
|
||||
thumbnail @66: Thumbnail;
|
||||
onroadEvents @68: List(Car.OnroadEvent);
|
||||
onroadEvents @134: List(OnroadEvent);
|
||||
carParams @69: Car.CarParams;
|
||||
driverMonitoringState @71: DriverMonitoringState;
|
||||
livePose @129 :LivePose;
|
||||
@@ -2484,5 +2598,6 @@ struct Event {
|
||||
uiPlanDEPRECATED @106 :UiPlan;
|
||||
liveLocationKalmanDEPRECATED @72 :LiveLocationKalman;
|
||||
liveTracksDEPRECATED @16 :List(LiveTracksDEPRECATED);
|
||||
onroadEventsDEPRECATED @68: List(Car.OnroadEventDEPRECATED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,11 +48,12 @@ void MsgqToZmq::run(const std::vector<std::string> &endpoints, const std::string
|
||||
|
||||
for (auto sub_sock : msgq_poller->poll(100)) {
|
||||
// Process messages for each socket
|
||||
ZMQPubSocket *pub_sock = sub2pub.at(sub_sock);
|
||||
for (int i = 0; i < MAX_MESSAGES_PER_SOCKET; ++i) {
|
||||
auto msg = std::unique_ptr<Message>(sub_sock->receive(true));
|
||||
if (!msg) break;
|
||||
|
||||
while (sub2pub[sub_sock]->sendMessage(msg.get()) == -1) {
|
||||
while (pub_sock->sendMessage(msg.get()) == -1) {
|
||||
if (errno != EINTR) break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,18 +168,18 @@ class TestMessaging:
|
||||
|
||||
# this test doesn't work with ZMQ since multiprocessing interrupts it
|
||||
if "ZMQ" not in os.environ:
|
||||
# wait 15 socket timeouts and make sure it's still retrying
|
||||
# wait 5 socket timeouts and make sure it's still retrying
|
||||
p = multiprocessing.Process(target=messaging.recv_one_retry, args=(sub_sock,))
|
||||
p.start()
|
||||
time.sleep(sock_timeout*15)
|
||||
time.sleep(sock_timeout*5)
|
||||
assert p.is_alive()
|
||||
p.terminate()
|
||||
|
||||
# wait 15 socket timeouts before sending
|
||||
# wait 5 socket timeouts before sending
|
||||
msg = random_carstate()
|
||||
delayed_send(sock_timeout*15, pub_sock, msg.to_bytes())
|
||||
delayed_send(sock_timeout*5, pub_sock, msg.to_bytes())
|
||||
start_time = time.monotonic()
|
||||
recvd = messaging.recv_one_retry(sub_sock)
|
||||
assert (time.monotonic() - start_time) >= sock_timeout*15
|
||||
assert (time.monotonic() - start_time) >= sock_timeout*5
|
||||
assert isinstance(recvd, capnp._DynamicStructReader)
|
||||
assert_carstate(msg.carState, recvd.carState)
|
||||
|
||||
@@ -63,14 +63,13 @@ class TestSubMaster:
|
||||
def test_update_timeout(self):
|
||||
sock = random_sock()
|
||||
sm = messaging.SubMaster([sock,])
|
||||
for _ in range(5):
|
||||
timeout = random.randrange(1000, 5000)
|
||||
start_time = time.monotonic()
|
||||
sm.update(timeout)
|
||||
t = time.monotonic() - start_time
|
||||
assert t >= timeout/1000.
|
||||
assert t < 5
|
||||
assert not any(sm.updated.values())
|
||||
timeout = random.randrange(1000, 3000)
|
||||
start_time = time.monotonic()
|
||||
sm.update(timeout)
|
||||
t = time.monotonic() - start_time
|
||||
assert t >= timeout/1000.
|
||||
assert t < 3
|
||||
assert not any(sm.updated.values())
|
||||
|
||||
def test_avg_frequency_checks(self):
|
||||
for poll in (True, False):
|
||||
|
||||
@@ -182,6 +182,7 @@ std::unordered_map<std::string, uint32_t> keys = {
|
||||
{"PrimeType", PERSISTENT},
|
||||
{"RecordFront", PERSISTENT},
|
||||
{"RecordFrontLock", PERSISTENT}, // for the internal fleet
|
||||
{"SecOCKey", PERSISTENT | DONT_LOG},
|
||||
{"RouteCount", PERSISTENT},
|
||||
{"SnoozeUpdate", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION},
|
||||
{"SshEnabled", PERSISTENT},
|
||||
|
||||
@@ -36,11 +36,16 @@ class UnknownKeyName(Exception):
|
||||
|
||||
cdef class Params:
|
||||
cdef c_Params* p
|
||||
cdef str d
|
||||
|
||||
def __cinit__(self, d=""):
|
||||
cdef string path = <string>d.encode()
|
||||
with nogil:
|
||||
self.p = new c_Params(path)
|
||||
self.d = d
|
||||
|
||||
def __reduce__(self):
|
||||
return (type(self), (self.d,))
|
||||
|
||||
def __dealloc__(self):
|
||||
del self.p
|
||||
|
||||
+6
-8
@@ -59,15 +59,13 @@ class PIDController:
|
||||
if override:
|
||||
self.i -= self.i_unwind_rate * float(np.sign(self.i))
|
||||
else:
|
||||
i = self.i + error * self.k_i * self.i_rate
|
||||
control = self.p + i + self.d + self.f
|
||||
if not freeze_integrator:
|
||||
self.i = self.i + error * self.k_i * self.i_rate
|
||||
|
||||
# Update when changing i will move the control away from the limits
|
||||
# or when i will move towards the sign of the error
|
||||
if ((error >= 0 and (control <= self.pos_limit or i < 0.0)) or
|
||||
(error <= 0 and (control >= self.neg_limit or i > 0.0))) and \
|
||||
not freeze_integrator:
|
||||
self.i = i
|
||||
# Clip i to prevent exceeding control limits
|
||||
control_no_i = self.p + self.d + self.f
|
||||
control_no_i = clip(control_no_i, self.neg_limit, self.pos_limit)
|
||||
self.i = clip(self.i, self.neg_limit - control_no_i, self.pos_limit - control_no_i)
|
||||
|
||||
control = self.p + self.i + self.d + self.f
|
||||
|
||||
|
||||
+13
@@ -8,6 +8,19 @@ from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.system.manager import manager
|
||||
from openpilot.system.hardware import TICI, HARDWARE
|
||||
|
||||
# TODO: pytest-cpp doesn't support FAIL, and we need to create test translations in sessionstart
|
||||
# pending https://github.com/pytest-dev/pytest-cpp/pull/147
|
||||
collect_ignore = [
|
||||
"selfdrive/ui/tests/test_translations",
|
||||
"selfdrive/test/process_replay/test_processes.py",
|
||||
"selfdrive/test/process_replay/test_regen.py",
|
||||
"selfdrive/test/test_time_to_onroad.py",
|
||||
]
|
||||
collect_ignore_glob = [
|
||||
"selfdrive/debug/*.py",
|
||||
"selfdrive/modeld/*.py",
|
||||
]
|
||||
|
||||
|
||||
def pytest_sessionstart(session):
|
||||
# TODO: fix tests and enable test order randomization
|
||||
|
||||
+18
-18
@@ -30,21 +30,21 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|comma|body|All|openpilot|0 mph|0 mph|[](##)|[](##)|None||
|
||||
|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</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<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=CUPRA&model=Ateca 2018-23">Buy Here</a></sub></details>||
|
||||
|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Dodge&model=Durango 2020-21">Buy Here</a></sub></details>||
|
||||
|Ford|Bronco Sport 2021-23|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Bronco Sport 2021-23">Buy Here</a></sub></details>||
|
||||
|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape Plug-in Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Explorer 2020-23|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Explorer 2020-23">Buy Here</a></sub></details>||
|
||||
|Ford|Explorer Hybrid 2020-23|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Explorer Hybrid 2020-23">Buy Here</a></sub></details>||
|
||||
|Ford|Focus 2018[<sup>3</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Focus 2018">Buy Here</a></sub></details>||
|
||||
|Ford|Focus Hybrid 2018[<sup>3</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Focus Hybrid 2018">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga 2020-22|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Kuga 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga Hybrid 2020-22|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Kuga Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga Plug-in Hybrid 2020-22|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Kuga Plug-in Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Maverick 2022|LARIAT Luxury|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick 2022">Buy Here</a></sub></details>||
|
||||
|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick 2023-24">Buy Here</a></sub></details>||
|
||||
|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick Hybrid 2022">Buy Here</a></sub></details>||
|
||||
|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick Hybrid 2023-24">Buy Here</a></sub></details>||
|
||||
|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Bronco Sport 2021-24">Buy Here</a></sub></details>||
|
||||
|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape Plug-in Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Explorer 2020-24">Buy Here</a></sub></details>||
|
||||
|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Explorer Hybrid 2020-24">Buy Here</a></sub></details>||
|
||||
|Ford|Focus 2018[<sup>3</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Focus 2018">Buy Here</a></sub></details>||
|
||||
|Ford|Focus Hybrid 2018[<sup>3</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Focus Hybrid 2018">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga 2020-22|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Kuga 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga Hybrid 2020-22|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Kuga Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Kuga Plug-in Hybrid 2020-22|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Kuga Plug-in Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Ford|Maverick 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick 2022">Buy Here</a></sub></details>||
|
||||
|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick 2023-24">Buy Here</a></sub></details>||
|
||||
|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick Hybrid 2022">Buy Here</a></sub></details>||
|
||||
|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick Hybrid 2023-24">Buy Here</a></sub></details>||
|
||||
|Genesis|G70 2018|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G70 2018">Buy Here</a></sub></details>||
|
||||
|Genesis|G70 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G70 2019-21">Buy Here</a></sub></details>||
|
||||
|Genesis|G70 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G70 2022-23">Buy Here</a></sub></details>||
|
||||
@@ -68,7 +68,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|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 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=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|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=CR-V 2015-16">Buy Here</a></sub></details>||
|
||||
|Honda|CR-V 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 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=CR-V 2017-22">Buy Here</a></sub></details>||
|
||||
|Honda|CR-V Hybrid 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 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=CR-V Hybrid 2017-21">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 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=CR-V Hybrid 2017-22">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 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=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 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=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 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Freed 2020">Buy Here</a></sub></details>||
|
||||
@@ -184,8 +184,8 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Lexus|RX Hybrid 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=RX Hybrid 2017-19">Buy Here</a></sub></details>||
|
||||
|Lexus|RX Hybrid 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=RX Hybrid 2020-22">Buy Here</a></sub></details>||
|
||||
|Lexus|UX Hybrid 2019-23|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=UX Hybrid 2019-23">Buy Here</a></sub></details>||
|
||||
|Lincoln|Aviator 2020-23|Co-Pilot360 Plus|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lincoln&model=Aviator 2020-23">Buy Here</a></sub></details>||
|
||||
|Lincoln|Aviator Plug-in Hybrid 2020-23|Co-Pilot360 Plus|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lincoln&model=Aviator Plug-in Hybrid 2020-23">Buy Here</a></sub></details>||
|
||||
|Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lincoln&model=Aviator 2020-24">Buy Here</a></sub></details>||
|
||||
|Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lincoln&model=Aviator Plug-in Hybrid 2020-24">Buy Here</a></sub></details>||
|
||||
|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=MAN&model=eTGE 2020-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=MAN&model=TGE 2017-24">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Mazda|CX-5 2022-24|All|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Mazda connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Mazda&model=CX-5 2022-24">Buy Here</a></sub></details>||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# openpilot glossary
|
||||
|
||||
* **route**:
|
||||
* **onroad**: openpilot's system state while ignition is on
|
||||
* **offroad**: openpilot's system state while ignition is off
|
||||
* **route**: a route is a recording of an onroad session
|
||||
* **segment**: routes are split into one minute chunks called segments.
|
||||
* **panda**: this is . See the repo.
|
||||
* **onroad**:
|
||||
* **offroad**:
|
||||
* **comma 3X**:
|
||||
* **comma connect**: the web viewer for all your routes; check it out at [connect.comma.ai](https://connect.comma.ai).
|
||||
* **panda**: this is the secondary processor on the device that implements the functional safety and directly talks to the car over CAN. See the [panda repo](https://github.com/commaai/panda).
|
||||
* **comma 3X**: the latest hardware by comma.ai for running openpilot. more info at [comma.ai/shop](https://comma.ai/shop).
|
||||
|
||||
@@ -11,6 +11,9 @@ On the comma three, the serial console is exposed through a UART-to-USB chip, an
|
||||
|
||||
On the comma 3X, the serial console is accessible through the [panda](https://github.com/commaai/panda) using the `panda/tests/som_debug.sh` script.
|
||||
|
||||
* Username: `comma`
|
||||
* Password: `comma`
|
||||
|
||||
## SSH
|
||||
|
||||
In order to SSH into your device, you'll need a GitHub account with SSH keys. See this [GitHub article](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh) for getting your account setup with SSH keys.
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1
|
||||
export VECLIB_MAXIMUM_THREADS=1
|
||||
|
||||
if [ -z "$AGNOS_VERSION" ]; then
|
||||
export AGNOS_VERSION="10.1"
|
||||
export AGNOS_VERSION="11.2"
|
||||
fi
|
||||
|
||||
export STAGING_ROOT="/data/safe_staging"
|
||||
|
||||
@@ -18,10 +18,12 @@ nav:
|
||||
- How-to:
|
||||
- Turn the speed blue: how-to/turn-the-speed-blue.md
|
||||
- Connect to a comma 3/3X: how-to/connect-to-comma.md
|
||||
# - Make your first pull request: how-to/make-first-pr.md
|
||||
#- Replay a drive: how-to/replay-a-drive.md
|
||||
- Concepts:
|
||||
- Logs: concepts/logs.md
|
||||
- Safety: concepts/safety.md
|
||||
- Glossary: concepts/glossary.md
|
||||
- Car Porting:
|
||||
- What is a car port?: car-porting/what-is-a-car-port.md
|
||||
- Porting a car brand: car-porting/brand-port.md
|
||||
|
||||
+1
-1
Submodule msgq_repo updated: cdcf84f44a...3e17f865bb
+1
-1
Submodule opendbc_repo updated: 1bd57253b3...67da959b46
+1
-1
Submodule panda updated: 1ac593f964...bc28d3cc51
+2
-9
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "openpilot"
|
||||
requires-python = ">= 3.11"
|
||||
requires-python = ">= 3.11, <= 3.12"
|
||||
license = {text = "MIT License"}
|
||||
version = "0.1.0"
|
||||
description = "an open source driver assistance system"
|
||||
@@ -148,14 +148,7 @@ markers = [
|
||||
]
|
||||
testpaths = [
|
||||
"common",
|
||||
"selfdrive/pandad",
|
||||
"selfdrive/car",
|
||||
"selfdrive/opcar",
|
||||
"selfdrive/controls",
|
||||
"selfdrive/locationd",
|
||||
"selfdrive/monitoring",
|
||||
"selfdrive/test/longitudinal_maneuvers",
|
||||
"selfdrive/test/process_replay/test_fuzzy.py",
|
||||
"selfdrive",
|
||||
"system/updated",
|
||||
"system/athena",
|
||||
"system/camerad",
|
||||
|
||||
@@ -50,8 +50,13 @@ git commit -a -m "openpilot v$VERSION release"
|
||||
export PYTHONPATH="$BUILD_DIR"
|
||||
scons -j$(nproc) --minimal
|
||||
|
||||
# release panda fw
|
||||
CERT=/data/pandaextra/certs/release RELEASE=1 scons -j$(nproc) panda/
|
||||
if [ -z "$PANDA_DEBUG_BUILD" ]; then
|
||||
# release panda fw
|
||||
CERT=/data/pandaextra/certs/release RELEASE=1 scons -j$(nproc) panda/
|
||||
else
|
||||
# build with ALLOW_DEBUG=1 to enable features like experimental longitudinal
|
||||
scons -j$(nproc) panda/
|
||||
fi
|
||||
|
||||
# Ensure no submodules in release
|
||||
if test "$(git submodule--helper list | wc -l)" -gt "0"; then
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
while read hash submodule ref; do
|
||||
git -C $submodule fetch --depth 4000 origin master
|
||||
if [ "$submodule" = "tinygrad_repo" ]; then
|
||||
echo "Skipping $submodule"
|
||||
continue
|
||||
fi
|
||||
|
||||
git -C $submodule fetch --depth 100 origin master
|
||||
git -C $submodule branch -r --contains $hash | grep "origin/master"
|
||||
if [ "$?" -eq 0 ]; then
|
||||
echo "$submodule ok"
|
||||
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
FAIL=0
|
||||
|
||||
if grep -n '\(#\|//\)\([[:space:]]*\)NOMERGE' $@; then
|
||||
echo -e "NOMERGE comments found! Remove them before merging\n"
|
||||
FAIL=1
|
||||
fi
|
||||
|
||||
exit $FAIL
|
||||
@@ -13,7 +13,7 @@ cd $ROOT
|
||||
|
||||
FAILED=0
|
||||
|
||||
IGNORED_FILES="uv\.lock|docs\/CARS.md"
|
||||
IGNORED_FILES="uv\.lock|docs\/CARS.md|LICENSE\.md"
|
||||
IGNORED_DIRS="^third_party.*|^msgq.*|^msgq_repo.*|^opendbc.*|^opendbc_repo.*|^cereal.*|^panda.*|^rednose.*|^rednose_repo.*|^tinygrad.*|^tinygrad_repo.*|^teleoprtc.*|^teleoprtc_repo.*"
|
||||
|
||||
function run() {
|
||||
@@ -52,6 +52,7 @@ function run_tests() {
|
||||
run "check_added_large_files" python3 -m pre_commit_hooks.check_added_large_files --enforce-all $ALL_FILES --maxkb=120
|
||||
run "check_shebang_scripts_are_executable" python3 -m pre_commit_hooks.check_shebang_scripts_are_executable $ALL_FILES
|
||||
run "check_shebang_format" $DIR/check_shebang_format.sh $ALL_FILES
|
||||
run "check_nomerge_comments" $DIR/check_nomerge_comments.sh $ALL_FILES
|
||||
|
||||
if [[ -z "$FAST" ]]; then
|
||||
run "mypy" mypy $PYTHON_FILES
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
from cereal import car
|
||||
from collections import deque
|
||||
from cereal import car, log
|
||||
import cereal.messaging as messaging
|
||||
from opendbc.car import DT_CTRL, structs
|
||||
from opendbc.car.interfaces import MAX_CTRL_SPEED, CarStateBase, CarControllerBase
|
||||
from opendbc.car.interfaces import MAX_CTRL_SPEED
|
||||
from opendbc.car.volkswagen.values import CarControllerParams as VWCarControllerParams
|
||||
from opendbc.car.hyundai.interface import ENABLE_BUTTONS as HYUNDAI_ENABLE_BUTTONS
|
||||
from opendbc.car.hyundai.carstate import PREV_BUTTON_SAMPLES as HYUNDAI_PREV_BUTTON_SAMPLES
|
||||
|
||||
from openpilot.selfdrive.selfdrived.events import Events
|
||||
|
||||
ButtonType = structs.CarState.ButtonEvent.Type
|
||||
GearShifter = structs.CarState.GearShifter
|
||||
EventName = car.OnroadEvent.EventName
|
||||
EventName = log.OnroadEvent.EventName
|
||||
NetworkLocation = structs.CarParams.NetworkLocation
|
||||
|
||||
|
||||
@@ -37,132 +39,121 @@ class CarSpecificEvents:
|
||||
self.no_steer_warning = False
|
||||
self.silent_steer_warning = True
|
||||
|
||||
def update(self, CS: CarStateBase, CS_prev: car.CarState, CC: CarControllerBase, CC_prev: car.CarControl):
|
||||
self.cruise_buttons: deque = deque([], maxlen=HYUNDAI_PREV_BUTTON_SAMPLES)
|
||||
|
||||
def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl):
|
||||
if self.CP.carName in ('body', 'mock'):
|
||||
events = Events()
|
||||
|
||||
elif self.CP.carName == 'subaru':
|
||||
events = self.create_common_events(CS.out, CS_prev)
|
||||
elif self.CP.carName in ('subaru', 'mazda'):
|
||||
events = self.create_common_events(CS, CS_prev)
|
||||
|
||||
elif self.CP.carName == 'ford':
|
||||
events = self.create_common_events(CS.out, CS_prev, extra_gears=[GearShifter.manumatic])
|
||||
events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.manumatic])
|
||||
|
||||
elif self.CP.carName == 'nissan':
|
||||
events = self.create_common_events(CS.out, CS_prev, extra_gears=[GearShifter.brake])
|
||||
|
||||
if CS.lkas_enabled: # type: ignore[attr-defined]
|
||||
events.add(EventName.invalidLkasSetting)
|
||||
|
||||
elif self.CP.carName == 'mazda':
|
||||
events = self.create_common_events(CS.out, CS_prev)
|
||||
|
||||
if CS.lkas_disabled: # type: ignore[attr-defined]
|
||||
events.add(EventName.lkasDisabled)
|
||||
elif CS.low_speed_alert: # type: ignore[attr-defined]
|
||||
events.add(EventName.belowSteerSpeed)
|
||||
events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.brake])
|
||||
|
||||
elif self.CP.carName == 'chrysler':
|
||||
events = self.create_common_events(CS.out, CS_prev, extra_gears=[GearShifter.low])
|
||||
events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.low])
|
||||
|
||||
# Low speed steer alert hysteresis logic
|
||||
if self.CP.minSteerSpeed > 0. and CS.out.vEgo < (self.CP.minSteerSpeed + 0.5):
|
||||
if self.CP.minSteerSpeed > 0. and CS.vEgo < (self.CP.minSteerSpeed + 0.5):
|
||||
self.low_speed_alert = True
|
||||
elif CS.out.vEgo > (self.CP.minSteerSpeed + 1.):
|
||||
elif CS.vEgo > (self.CP.minSteerSpeed + 1.):
|
||||
self.low_speed_alert = False
|
||||
if self.low_speed_alert:
|
||||
events.add(EventName.belowSteerSpeed)
|
||||
|
||||
elif self.CP.carName == 'honda':
|
||||
events = self.create_common_events(CS.out, CS_prev, pcm_enable=False)
|
||||
events = self.create_common_events(CS, CS_prev, pcm_enable=False)
|
||||
|
||||
if self.CP.pcmCruise and CS.out.vEgo < self.CP.minEnableSpeed:
|
||||
if self.CP.pcmCruise and CS.vEgo < self.CP.minEnableSpeed:
|
||||
events.add(EventName.belowEngageSpeed)
|
||||
|
||||
if self.CP.pcmCruise:
|
||||
# we engage when pcm is active (rising edge)
|
||||
if CS.out.cruiseState.enabled and not CS_prev.cruiseState.enabled:
|
||||
if CS.cruiseState.enabled and not CS_prev.cruiseState.enabled:
|
||||
events.add(EventName.pcmEnable)
|
||||
elif not CS.out.cruiseState.enabled and (CC_prev.actuators.accel >= 0. or not self.CP.openpilotLongitudinalControl):
|
||||
elif not CS.cruiseState.enabled and (CC.actuators.accel >= 0. or not self.CP.openpilotLongitudinalControl):
|
||||
# it can happen that car cruise disables while comma system is enabled: need to
|
||||
# keep braking if needed or if the speed is very low
|
||||
if CS.out.vEgo < self.CP.minEnableSpeed + 2.:
|
||||
if CS.vEgo < self.CP.minEnableSpeed + 2.:
|
||||
# non loud alert if cruise disables below 25mph as expected (+ a little margin)
|
||||
events.add(EventName.speedTooLow)
|
||||
else:
|
||||
events.add(EventName.cruiseDisabled)
|
||||
if self.CP.minEnableSpeed > 0 and CS.out.vEgo < 0.001:
|
||||
if self.CP.minEnableSpeed > 0 and CS.vEgo < 0.001:
|
||||
events.add(EventName.manualRestart)
|
||||
|
||||
elif self.CP.carName == 'toyota':
|
||||
events = self.create_common_events(CS.out, CS_prev)
|
||||
events = self.create_common_events(CS, CS_prev)
|
||||
|
||||
if self.CP.openpilotLongitudinalControl:
|
||||
if CS.out.cruiseState.standstill and not CS.out.brakePressed:
|
||||
if CS.cruiseState.standstill and not CS.brakePressed:
|
||||
events.add(EventName.resumeRequired)
|
||||
if CS.low_speed_lockout: # type: ignore[attr-defined]
|
||||
events.add(EventName.lowSpeedLockout)
|
||||
if CS.out.vEgo < self.CP.minEnableSpeed:
|
||||
if CS.vEgo < self.CP.minEnableSpeed:
|
||||
events.add(EventName.belowEngageSpeed)
|
||||
if CC_prev.actuators.accel > 0.3:
|
||||
if CC.actuators.accel > 0.3:
|
||||
# some margin on the actuator to not false trigger cancellation while stopping
|
||||
events.add(EventName.speedTooLow)
|
||||
if CS.out.vEgo < 0.001:
|
||||
if CS.vEgo < 0.001:
|
||||
# while in standstill, send a user alert
|
||||
events.add(EventName.manualRestart)
|
||||
|
||||
elif self.CP.carName == 'gm':
|
||||
# The ECM allows enabling on falling edge of set, but only rising edge of resume
|
||||
events = self.create_common_events(CS.out, CS_prev, extra_gears=[GearShifter.sport, GearShifter.low,
|
||||
GearShifter.eco, GearShifter.manumatic],
|
||||
events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.sport, GearShifter.low,
|
||||
GearShifter.eco, GearShifter.manumatic],
|
||||
pcm_enable=self.CP.pcmCruise, enable_buttons=(ButtonType.decelCruise,))
|
||||
if not self.CP.pcmCruise:
|
||||
if any(b.type == ButtonType.accelCruise and b.pressed for b in CS.out.buttonEvents):
|
||||
if any(b.type == ButtonType.accelCruise and b.pressed for b in CS.buttonEvents):
|
||||
events.add(EventName.buttonEnable)
|
||||
|
||||
# Enabling at a standstill with brake is allowed
|
||||
# TODO: verify 17 Volt can enable for the first time at a stop and allow for all GMs
|
||||
below_min_enable_speed = CS.out.vEgo < self.CP.minEnableSpeed or CS.moving_backward # type: ignore[attr-defined]
|
||||
if below_min_enable_speed and not (CS.out.standstill and CS.out.brake >= 20 and
|
||||
self.CP.networkLocation == NetworkLocation.fwdCamera):
|
||||
if CS.vEgo < self.CP.minEnableSpeed and not (CS.standstill and CS.brake >= 20 and
|
||||
self.CP.networkLocation == NetworkLocation.fwdCamera):
|
||||
events.add(EventName.belowEngageSpeed)
|
||||
if CS.out.cruiseState.standstill:
|
||||
if CS.cruiseState.standstill:
|
||||
events.add(EventName.resumeRequired)
|
||||
if CS.out.vEgo < self.CP.minSteerSpeed:
|
||||
if CS.vEgo < self.CP.minSteerSpeed:
|
||||
events.add(EventName.belowSteerSpeed)
|
||||
|
||||
elif self.CP.carName == 'volkswagen':
|
||||
events = self.create_common_events(CS.out, CS_prev, extra_gears=[GearShifter.eco, GearShifter.sport, GearShifter.manumatic],
|
||||
events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.eco, GearShifter.sport, GearShifter.manumatic],
|
||||
pcm_enable=not self.CP.openpilotLongitudinalControl,
|
||||
enable_buttons=(ButtonType.setCruise, ButtonType.resumeCruise))
|
||||
|
||||
# Low speed steer alert hysteresis logic
|
||||
if (self.CP.minSteerSpeed - 1e-3) > VWCarControllerParams.DEFAULT_MIN_STEER_SPEED and CS.out.vEgo < (self.CP.minSteerSpeed + 1.):
|
||||
if (self.CP.minSteerSpeed - 1e-3) > VWCarControllerParams.DEFAULT_MIN_STEER_SPEED and CS.vEgo < (self.CP.minSteerSpeed + 1.):
|
||||
self.low_speed_alert = True
|
||||
elif CS.out.vEgo > (self.CP.minSteerSpeed + 2.):
|
||||
elif CS.vEgo > (self.CP.minSteerSpeed + 2.):
|
||||
self.low_speed_alert = False
|
||||
if self.low_speed_alert:
|
||||
events.add(EventName.belowSteerSpeed)
|
||||
|
||||
if self.CP.openpilotLongitudinalControl:
|
||||
if CS.out.vEgo < self.CP.minEnableSpeed + 0.5:
|
||||
if CS.vEgo < self.CP.minEnableSpeed + 0.5:
|
||||
events.add(EventName.belowEngageSpeed)
|
||||
if CC_prev.enabled and CS.out.vEgo < self.CP.minEnableSpeed:
|
||||
if CC.enabled and CS.vEgo < self.CP.minEnableSpeed:
|
||||
events.add(EventName.speedTooLow)
|
||||
|
||||
if CC.eps_timer_soft_disable_alert: # type: ignore[attr-defined]
|
||||
events.add(EventName.steerTimeLimit)
|
||||
# TODO: this needs to be implemented generically in carState struct
|
||||
# if CC.eps_timer_soft_disable_alert: # type: ignore[attr-defined]
|
||||
# events.add(EventName.steerTimeLimit)
|
||||
|
||||
elif self.CP.carName == 'hyundai':
|
||||
# On some newer model years, the CANCEL button acts as a pause/resume button based on the PCM state
|
||||
# To avoid re-engaging when openpilot cancels, check user engagement intention via buttons
|
||||
# Main button also can trigger an engagement on these cars
|
||||
allow_enable = any(btn in HYUNDAI_ENABLE_BUTTONS for btn in CS.cruise_buttons) or any(CS.main_buttons) # type: ignore[attr-defined]
|
||||
events = self.create_common_events(CS.out, CS_prev, pcm_enable=self.CP.pcmCruise, allow_enable=allow_enable)
|
||||
self.cruise_buttons.append(any(ev.type in HYUNDAI_ENABLE_BUTTONS for ev in CS.buttonEvents))
|
||||
events = self.create_common_events(CS, CS_prev, pcm_enable=self.CP.pcmCruise, allow_enable=any(self.cruise_buttons))
|
||||
|
||||
# low speed steer alert hysteresis logic (only for cars with steer cut off above 10 m/s)
|
||||
if CS.out.vEgo < (self.CP.minSteerSpeed + 2.) and self.CP.minSteerSpeed > 10.:
|
||||
if CS.vEgo < (self.CP.minSteerSpeed + 2.) and self.CP.minSteerSpeed > 10.:
|
||||
self.low_speed_alert = True
|
||||
if CS.out.vEgo > (self.CP.minSteerSpeed + 4.):
|
||||
if CS.vEgo > (self.CP.minSteerSpeed + 4.):
|
||||
self.low_speed_alert = False
|
||||
if self.low_speed_alert:
|
||||
events.add(EventName.belowSteerSpeed)
|
||||
@@ -213,6 +204,10 @@ class CarSpecificEvents:
|
||||
events.add(EventName.gasPressedOverride)
|
||||
if CS.vehicleSensorsInvalid:
|
||||
events.add(EventName.vehicleSensorsInvalid)
|
||||
if CS.invalidLkasSetting:
|
||||
events.add(EventName.invalidLkasSetting)
|
||||
if CS.lowSpeedAlert:
|
||||
events.add(EventName.belowSteerSpeed)
|
||||
|
||||
# Handle button presses
|
||||
for b in CS.buttonEvents:
|
||||
|
||||
+29
-53
@@ -5,7 +5,7 @@ import threading
|
||||
|
||||
import cereal.messaging as messaging
|
||||
|
||||
from cereal import car
|
||||
from cereal import car, log
|
||||
|
||||
from panda import ALTERNATIVE_EXPERIENCE
|
||||
|
||||
@@ -20,13 +20,11 @@ from opendbc.car.car_helpers import get_car, get_radar_interface
|
||||
from opendbc.car.interfaces import CarInterfaceBase, RadarInterfaceBase
|
||||
from openpilot.selfdrive.pandad import can_capnp_to_list, can_list_to_can_capnp
|
||||
from openpilot.selfdrive.car.cruise import VCruiseHelper
|
||||
from openpilot.selfdrive.car.car_specific import CarSpecificEvents, MockCarState
|
||||
from openpilot.selfdrive.car.helpers import convert_carControl, convert_to_capnp
|
||||
from openpilot.selfdrive.selfdrived.events import Events, ET
|
||||
from openpilot.selfdrive.car.car_specific import MockCarState
|
||||
|
||||
REPLAY = "REPLAY" in os.environ
|
||||
|
||||
EventName = car.OnroadEvent.EventName
|
||||
EventName = log.OnroadEvent.EventName
|
||||
|
||||
# forward
|
||||
carlog.addHandler(ForwardingHandler(cloudlog))
|
||||
@@ -64,8 +62,7 @@ def can_comm_callbacks(logcan: messaging.SubSocket, sendcan: messaging.PubSocket
|
||||
class Car:
|
||||
CI: CarInterfaceBase
|
||||
RI: RadarInterfaceBase
|
||||
CP: structs.CarParams
|
||||
CP_capnp: car.CarParams
|
||||
CP: car.CarParams
|
||||
|
||||
def __init__(self, CI=None, RI=None) -> None:
|
||||
self.can_sock = messaging.sub_sock('can', timeout=20)
|
||||
@@ -75,7 +72,6 @@ class Car:
|
||||
self.can_rcv_cum_timeout_counter = 0
|
||||
|
||||
self.CC_prev = car.CarControl.new_message()
|
||||
self.CS_prev = car.CarState.new_message()
|
||||
self.initialized_prev = False
|
||||
|
||||
self.last_actuators_output = structs.CarControl.Actuators()
|
||||
@@ -99,7 +95,7 @@ class Car:
|
||||
cached_params_raw = self.params.get("CarParamsCache")
|
||||
if cached_params_raw is not None:
|
||||
with car.CarParams.from_bytes(cached_params_raw) as _cached_params:
|
||||
cached_params = structs.CarParams(carName=_cached_params.carName, carFw=_cached_params.carFw, carVin=_cached_params.carVin)
|
||||
cached_params = _cached_params
|
||||
|
||||
self.CI = get_car(*self.can_callbacks, obd_callback(self.params), experimental_long_allowed, num_pandas, cached_params)
|
||||
self.RI = get_radar_interface(self.CI.CP)
|
||||
@@ -112,9 +108,9 @@ class Car:
|
||||
self.RI = RI
|
||||
|
||||
# set alternative experiences from parameters
|
||||
self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
|
||||
disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
|
||||
self.CP.alternativeExperience = 0
|
||||
if not self.disengage_on_accelerator:
|
||||
if not disengage_on_accelerator:
|
||||
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
|
||||
|
||||
openpilot_enabled_toggle = self.params.get_bool("OpenpilotEnabledToggle")
|
||||
@@ -127,22 +123,29 @@ class Car:
|
||||
safety_config.safetyModel = structs.CarParams.SafetyModel.noOutput
|
||||
self.CP.safetyConfigs = [safety_config]
|
||||
|
||||
if self.CP.secOcRequired and not self.params.get_bool("IsReleaseBranch"):
|
||||
secoc_key = self.params.get("SecOCKey", encoding='utf8')
|
||||
if secoc_key is not None:
|
||||
saved_secoc_key = bytes.fromhex(secoc_key.strip())
|
||||
if len(saved_secoc_key) == 16:
|
||||
self.CP.secOcKeyAvailable = True
|
||||
self.CI.CS.secoc_key = saved_secoc_key
|
||||
if controller_available:
|
||||
self.CI.CC.secoc_key = saved_secoc_key
|
||||
else:
|
||||
cloudlog.warning("Saved SecOC key is invalid")
|
||||
|
||||
# Write previous route's CarParams
|
||||
prev_cp = self.params.get("CarParamsPersistent")
|
||||
if prev_cp is not None:
|
||||
self.params.put("CarParamsPrevRoute", prev_cp)
|
||||
|
||||
# Write CarParams for controls and radard
|
||||
# convert to pycapnp representation for caching and logging
|
||||
self.CP_capnp = convert_to_capnp(self.CP)
|
||||
cp_bytes = self.CP_capnp.to_bytes()
|
||||
cp_bytes = self.CP.to_bytes()
|
||||
self.params.put("CarParams", cp_bytes)
|
||||
self.params.put_nonblocking("CarParamsCache", cp_bytes)
|
||||
self.params.put_nonblocking("CarParamsPersistent", cp_bytes)
|
||||
|
||||
self.events = Events()
|
||||
|
||||
self.car_events = CarSpecificEvents(self.CP)
|
||||
self.mock_carstate = MockCarState()
|
||||
self.v_cruise_helper = VCruiseHelper(self.CP)
|
||||
|
||||
@@ -152,19 +155,19 @@ class Car:
|
||||
# card is driven by can recv, expected at 100Hz
|
||||
self.rk = Ratekeeper(100, print_delay_threshold=None)
|
||||
|
||||
def state_update(self) -> tuple[car.CarState, structs.RadarData | None]:
|
||||
def state_update(self) -> tuple[car.CarState, structs.RadarDataT | None]:
|
||||
"""carState update loop, driven by can"""
|
||||
|
||||
can_strs = messaging.drain_sock_raw(self.can_sock, wait_for_one=True)
|
||||
can_list = can_capnp_to_list(can_strs)
|
||||
|
||||
# Update carState from CAN
|
||||
CS = convert_to_capnp(self.CI.update(can_list))
|
||||
CS = self.CI.update(can_list)
|
||||
if self.CP.carName == 'mock':
|
||||
CS = self.mock_carstate.update(CS)
|
||||
|
||||
# Update radar tracks from CAN
|
||||
RD: structs.RadarData | None = self.RI.update(can_list)
|
||||
RD: structs.RadarDataT | None = self.RI.update(can_list)
|
||||
|
||||
self.sm.update(0)
|
||||
|
||||
@@ -184,44 +187,20 @@ class Car:
|
||||
|
||||
return CS, RD
|
||||
|
||||
def update_events(self, CS: car.CarState, RD: structs.RadarData | None):
|
||||
self.events.clear()
|
||||
|
||||
CS.events = self.car_events.update(self.CI.CS, self.CS_prev, self.CI.CC, self.CC_prev).to_msg()
|
||||
|
||||
self.events.add_from_msg(CS.events)
|
||||
|
||||
if self.CP.notCar:
|
||||
# wait for everything to init first
|
||||
if self.sm.frame > int(5. / DT_CTRL) and self.initialized_prev:
|
||||
# body always wants to enable
|
||||
self.events.add(EventName.pcmEnable)
|
||||
|
||||
# Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0
|
||||
if (CS.gasPressed and not self.CS_prev.gasPressed and self.disengage_on_accelerator) or \
|
||||
(CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or \
|
||||
(CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)):
|
||||
self.events.add(EventName.pedalPressed)
|
||||
|
||||
if RD is not None and len(RD.errors):
|
||||
self.events.add(EventName.radarFault)
|
||||
|
||||
CS.events = self.events.to_msg()
|
||||
|
||||
def state_publish(self, CS: car.CarState, RD: structs.RadarData | None):
|
||||
def state_publish(self, CS: car.CarState, RD: structs.RadarDataT | None):
|
||||
"""carState and carParams publish loop"""
|
||||
|
||||
# carParams - logged every 50 seconds (> 1 per segment)
|
||||
if self.sm.frame % int(50. / DT_CTRL) == 0:
|
||||
cp_send = messaging.new_message('carParams')
|
||||
cp_send.valid = True
|
||||
cp_send.carParams = self.CP_capnp
|
||||
cp_send.carParams = self.CP
|
||||
self.pm.send('carParams', cp_send)
|
||||
|
||||
# publish new carOutput
|
||||
co_send = messaging.new_message('carOutput')
|
||||
co_send.valid = self.sm.all_checks(['carControl'])
|
||||
co_send.carOutput.actuatorsOutput = convert_to_capnp(self.last_actuators_output)
|
||||
co_send.carOutput.actuatorsOutput = self.last_actuators_output
|
||||
self.pm.send('carOutput', co_send)
|
||||
|
||||
# kick off controlsd step while we actuate the latest carControl packet
|
||||
@@ -235,7 +214,7 @@ class Car:
|
||||
if RD is not None:
|
||||
tracks_msg = messaging.new_message('liveTracks')
|
||||
tracks_msg.valid = len(RD.errors) == 0
|
||||
tracks_msg.liveTracks = convert_to_capnp(RD)
|
||||
tracks_msg.liveTracks = RD
|
||||
self.pm.send('liveTracks', tracks_msg)
|
||||
|
||||
def controls_update(self, CS: car.CarState, CC: car.CarControl):
|
||||
@@ -251,7 +230,7 @@ class Car:
|
||||
if self.sm.all_alive(['carControl']):
|
||||
# send car controls over can
|
||||
now_nanos = self.can_log_mono_time if REPLAY else int(time.monotonic() * 1e9)
|
||||
self.last_actuators_output, can_sends = self.CI.apply(convert_carControl(CC), now_nanos)
|
||||
self.last_actuators_output, can_sends = self.CI.apply(CC, now_nanos)
|
||||
self.pm.send('sendcan', can_list_to_can_capnp(can_sends, msgtype='sendcan', valid=CS.canValid))
|
||||
|
||||
self.CC_prev = CC
|
||||
@@ -259,9 +238,7 @@ class Car:
|
||||
def step(self):
|
||||
CS, RD = self.state_update()
|
||||
|
||||
self.update_events(CS, RD)
|
||||
|
||||
if not self.sm['carControl'].enabled and self.events.contains(ET.ENABLE):
|
||||
if self.sm['carControl'].enabled and not self.CC_prev.enabled:
|
||||
self.v_cruise_helper.initialize_v_cruise(CS, self.experimental_mode)
|
||||
|
||||
self.state_publish(CS, RD)
|
||||
@@ -272,7 +249,6 @@ class Car:
|
||||
self.controls_update(CS, self.sm['carControl'])
|
||||
|
||||
self.initialized_prev = initialized
|
||||
self.CS_prev = CS.as_reader()
|
||||
|
||||
def params_thread(self, evt):
|
||||
while not evt.is_set():
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import capnp
|
||||
from typing import Any
|
||||
|
||||
from cereal import car
|
||||
from opendbc.car import structs
|
||||
|
||||
_FIELDS = '__dataclass_fields__' # copy of dataclasses._FIELDS
|
||||
|
||||
|
||||
def is_dataclass(obj):
|
||||
"""Similar to dataclasses.is_dataclass without instance type check checking"""
|
||||
return hasattr(obj, _FIELDS)
|
||||
|
||||
|
||||
def _asdictref_inner(obj) -> dict[str, Any] | Any:
|
||||
if is_dataclass(obj):
|
||||
ret = {}
|
||||
for field in getattr(obj, _FIELDS): # similar to dataclasses.fields()
|
||||
ret[field] = _asdictref_inner(getattr(obj, field))
|
||||
return ret
|
||||
elif isinstance(obj, (tuple, list)):
|
||||
return type(obj)(_asdictref_inner(v) for v in obj)
|
||||
else:
|
||||
return obj
|
||||
|
||||
|
||||
def asdictref(obj) -> dict[str, Any]:
|
||||
"""
|
||||
Similar to dataclasses.asdict without recursive type checking and copy.deepcopy
|
||||
Note that the resulting dict will contain references to the original struct as a result
|
||||
"""
|
||||
if not is_dataclass(obj):
|
||||
raise TypeError("asdictref() should be called on dataclass instances")
|
||||
|
||||
return _asdictref_inner(obj)
|
||||
|
||||
|
||||
def convert_to_capnp(struct: structs.CarParams | structs.CarState | structs.CarControl.Actuators | structs.RadarData) -> capnp.lib.capnp._DynamicStructBuilder:
|
||||
struct_dict = asdictref(struct)
|
||||
|
||||
if isinstance(struct, structs.CarParams):
|
||||
del struct_dict['lateralTuning']
|
||||
struct_capnp = car.CarParams.new_message(**struct_dict)
|
||||
|
||||
# this is the only union, special handling
|
||||
which = struct.lateralTuning.which()
|
||||
struct_capnp.lateralTuning.init(which)
|
||||
lateralTuning_dict = asdictref(getattr(struct.lateralTuning, which))
|
||||
setattr(struct_capnp.lateralTuning, which, lateralTuning_dict)
|
||||
elif isinstance(struct, structs.CarState):
|
||||
struct_capnp = car.CarState.new_message(**struct_dict)
|
||||
elif isinstance(struct, structs.CarControl.Actuators):
|
||||
struct_capnp = car.CarControl.Actuators.new_message(**struct_dict)
|
||||
elif isinstance(struct, structs.RadarData):
|
||||
struct_capnp = car.RadarData.new_message(**struct_dict)
|
||||
else:
|
||||
raise ValueError(f"Unsupported struct type: {type(struct)}")
|
||||
|
||||
return struct_capnp
|
||||
|
||||
|
||||
def convert_carControl(struct: capnp.lib.capnp._DynamicStructReader) -> structs.CarControl:
|
||||
# TODO: recursively handle any car struct as needed
|
||||
def remove_deprecated(s: dict) -> dict:
|
||||
return {k: v for k, v in s.items() if not k.endswith('DEPRECATED')}
|
||||
|
||||
struct_dict = struct.to_dict()
|
||||
struct_dataclass = structs.CarControl(**remove_deprecated({k: v for k, v in struct_dict.items() if not isinstance(k, dict)}))
|
||||
|
||||
struct_dataclass.actuators = structs.CarControl.Actuators(**remove_deprecated(struct_dict.get('actuators', {})))
|
||||
struct_dataclass.cruiseControl = structs.CarControl.CruiseControl(**remove_deprecated(struct_dict.get('cruiseControl', {})))
|
||||
struct_dataclass.hudControl = structs.CarControl.HUDControl(**remove_deprecated(struct_dict.get('hudControl', {})))
|
||||
|
||||
return struct_dataclass
|
||||
@@ -12,7 +12,6 @@ from opendbc.car.tests.test_car_interfaces import get_fuzzy_car_interface_args
|
||||
from opendbc.car.fingerprints import all_known_cars
|
||||
from opendbc.car.fw_versions import FW_VERSIONS, FW_QUERY_CONFIGS
|
||||
from opendbc.car.mock.values import CAR as MOCK
|
||||
from openpilot.selfdrive.car.card import convert_carControl, convert_to_capnp
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
|
||||
@@ -41,6 +40,7 @@ class TestCarInterfaces:
|
||||
|
||||
car_params = CarInterface.get_params(car_name, args['fingerprints'], args['car_fw'],
|
||||
experimental_long=args['experimental_long'], docs=False)
|
||||
car_params = car_params.as_reader()
|
||||
car_interface = CarInterface(car_params, CarController, CarState)
|
||||
assert car_params
|
||||
assert car_interface
|
||||
@@ -72,7 +72,7 @@ class TestCarInterfaces:
|
||||
# Run car interface
|
||||
now_nanos = 0
|
||||
CC = car.CarControl.new_message(**cc_msg)
|
||||
CC = convert_carControl(CC.as_reader())
|
||||
CC = CC.as_reader()
|
||||
for _ in range(10):
|
||||
car_interface.update([])
|
||||
car_interface.apply(CC, now_nanos)
|
||||
@@ -80,7 +80,7 @@ class TestCarInterfaces:
|
||||
|
||||
CC = car.CarControl.new_message(**cc_msg)
|
||||
CC.enabled = True
|
||||
CC = convert_carControl(CC.as_reader())
|
||||
CC = CC.as_reader()
|
||||
for _ in range(10):
|
||||
car_interface.update([])
|
||||
car_interface.apply(CC, now_nanos)
|
||||
@@ -89,11 +89,10 @@ class TestCarInterfaces:
|
||||
# Test controller initialization
|
||||
# TODO: wait until card refactor is merged to run controller a few times,
|
||||
# hypothesis also slows down significantly with just one more message draw
|
||||
car_params_capnp = convert_to_capnp(car_params).as_reader()
|
||||
LongControl(car_params_capnp)
|
||||
LongControl(car_params)
|
||||
if car_params.steerControlType == CarParams.SteerControlType.angle:
|
||||
LatControlAngle(car_params_capnp, car_interface)
|
||||
LatControlAngle(car_params, car_interface)
|
||||
elif car_params.lateralTuning.which() == 'pid':
|
||||
LatControlPID(car_params_capnp, car_interface)
|
||||
LatControlPID(car_params, car_interface)
|
||||
elif car_params.lateralTuning.which() == 'torque':
|
||||
LatControlTorque(car_params_capnp, car_interface)
|
||||
LatControlTorque(car_params, car_interface)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import capnp
|
||||
import copy
|
||||
import dataclasses
|
||||
import os
|
||||
import pytest
|
||||
import random
|
||||
@@ -20,16 +18,18 @@ from opendbc.car.car_helpers import FRAME_FINGERPRINT, interfaces
|
||||
from opendbc.car.honda.values import CAR as HONDA, HondaFlags
|
||||
from opendbc.car.values import Platform
|
||||
from opendbc.car.tests.routes import non_tested_cars, routes, CarTestRoute
|
||||
from openpilot.selfdrive.car.card import Car, convert_to_capnp
|
||||
from openpilot.selfdrive.selfdrived.events import ET
|
||||
from openpilot.selfdrive.selfdrived.selfdrived import SelfdriveD
|
||||
from openpilot.selfdrive.pandad import can_capnp_to_list
|
||||
from openpilot.selfdrive.test.helpers import read_segment_list
|
||||
from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT
|
||||
from openpilot.tools.lib.logreader import LogReader, auto_source, internal_source, openpilotci_source, openpilotci_source_zst
|
||||
from openpilot.tools.lib.logreader import LogReader, LogsUnavailable, openpilotci_source_zst, openpilotci_source, internal_source, \
|
||||
internal_source_zst, comma_api_source, auto_source
|
||||
from openpilot.tools.lib.route import SegmentName
|
||||
|
||||
from panda.tests.libpanda import libpanda_py
|
||||
|
||||
EventName = car.OnroadEvent.EventName
|
||||
EventName = log.OnroadEvent.EventName
|
||||
PandaType = log.PandaState.PandaType
|
||||
SafetyModel = car.CarParams.SafetyModel
|
||||
|
||||
@@ -69,7 +69,6 @@ def get_test_cases() -> list[tuple[str, CarTestRoute | None]]:
|
||||
class TestCarModelBase(unittest.TestCase):
|
||||
platform: Platform | None = None
|
||||
test_route: CarTestRoute | None = None
|
||||
test_route_on_bucket: bool = True # whether the route is on the preserved CI bucket
|
||||
|
||||
can_msgs: list[capnp.lib.capnp._DynamicStructReader]
|
||||
fingerprint: dict[int, dict[int, int]]
|
||||
@@ -96,7 +95,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
car_fw = msg.carParams.carFw
|
||||
if msg.carParams.openpilotLongitudinalControl:
|
||||
experimental_long = True
|
||||
if cls.platform is None and not cls.test_route_on_bucket:
|
||||
if cls.platform is None:
|
||||
live_fingerprint = msg.carParams.carFingerprint
|
||||
cls.platform = MIGRATION.get(live_fingerprint, live_fingerprint)
|
||||
|
||||
@@ -116,10 +115,8 @@ class TestCarModelBase(unittest.TestCase):
|
||||
(SafetyModel.elm327, SafetyModel.noOutput):
|
||||
cls.car_safety_mode_frame = len(can_msgs)
|
||||
|
||||
if len(can_msgs) > int(50 / DT_CTRL):
|
||||
return car_fw, can_msgs, experimental_long
|
||||
|
||||
raise Exception("no can data found")
|
||||
assert len(can_msgs) > int(50 / DT_CTRL), "no can data found"
|
||||
return car_fw, can_msgs, experimental_long
|
||||
|
||||
@classmethod
|
||||
def get_testing_data(cls):
|
||||
@@ -127,31 +124,17 @@ class TestCarModelBase(unittest.TestCase):
|
||||
if cls.test_route.segment is not None:
|
||||
test_segs = (cls.test_route.segment,)
|
||||
|
||||
is_internal = len(INTERNAL_SEG_LIST)
|
||||
|
||||
for seg in test_segs:
|
||||
segment_range = f"{cls.test_route.route}/{seg}"
|
||||
|
||||
try:
|
||||
source = partial(auto_source, sources=[internal_source] if is_internal else [openpilotci_source, openpilotci_source_zst])
|
||||
source = partial(auto_source, sources=[internal_source, internal_source_zst] if len(INTERNAL_SEG_LIST) else \
|
||||
[openpilotci_source_zst, openpilotci_source, comma_api_source])
|
||||
lr = LogReader(segment_range, source=source)
|
||||
return cls.get_testing_data_from_logreader(lr)
|
||||
except Exception:
|
||||
except (LogsUnavailable, AssertionError):
|
||||
pass
|
||||
|
||||
# Route is not in CI bucket, assume either user has access (private), or it is public
|
||||
# test_route_on_ci_bucket will fail when running in CI
|
||||
if not is_internal:
|
||||
cls.test_route_on_bucket = False
|
||||
|
||||
for seg in test_segs:
|
||||
segment_range = f"{cls.test_route.route}/{seg}"
|
||||
try:
|
||||
lr = LogReader(segment_range)
|
||||
return cls.get_testing_data_from_logreader(lr)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
raise Exception(f"Route: {repr(cls.test_route.route)} with segments: {test_segs} not found or no CAN msgs found. Is it uploaded and public?")
|
||||
|
||||
|
||||
@@ -185,7 +168,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
del cls.can_msgs
|
||||
|
||||
def setUp(self):
|
||||
self.CI = self.CarInterface(copy.deepcopy(self.CP), self.CarController, self.CarState)
|
||||
self.CI = self.CarInterface(self.CP.copy(), self.CarController, self.CarState)
|
||||
assert self.CI
|
||||
|
||||
Params().put_bool("OpenpilotEnabledToggle", self.openpilot_enabled)
|
||||
@@ -193,7 +176,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
# TODO: check safetyModel is in release panda build
|
||||
self.safety = libpanda_py.libpanda
|
||||
|
||||
cfg = car.CarParams.SafetyConfig(**dataclasses.asdict(self.CP.safetyConfigs[-1]))
|
||||
cfg = self.CP.safetyConfigs[-1]
|
||||
set_status = self.safety.set_safety_hooks(cfg.safetyModel.raw, cfg.safetyParam)
|
||||
self.assertEqual(0, set_status, f"failed to set safetyModel {cfg}")
|
||||
self.safety.init_tests()
|
||||
@@ -218,7 +201,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
# TODO: also check for checksum violations from can parser
|
||||
can_invalid_cnt = 0
|
||||
can_valid = False
|
||||
CC = structs.CarControl()
|
||||
CC = structs.CarControl().as_reader()
|
||||
|
||||
for i, msg in enumerate(self.can_msgs):
|
||||
CS = self.CI.update(can_capnp_to_list((msg.as_builder().to_bytes(),)))
|
||||
@@ -310,17 +293,17 @@ class TestCarModelBase(unittest.TestCase):
|
||||
|
||||
# Make sure we can send all messages while inactive
|
||||
CC = structs.CarControl()
|
||||
test_car_controller(CC)
|
||||
test_car_controller(CC.as_reader())
|
||||
|
||||
# Test cancel + general messages (controls_allowed=False & cruise_engaged=True)
|
||||
self.safety.set_cruise_engaged_prev(True)
|
||||
CC = structs.CarControl(cruiseControl=structs.CarControl.CruiseControl(cancel=True))
|
||||
test_car_controller(CC)
|
||||
test_car_controller(CC.as_reader())
|
||||
|
||||
# Test resume + general messages (controls_allowed=True & cruise_engaged=True)
|
||||
self.safety.set_controls_allowed(True)
|
||||
CC = structs.CarControl(cruiseControl=structs.CarControl.CruiseControl(resume=True))
|
||||
test_car_controller(CC)
|
||||
test_car_controller(CC.as_reader())
|
||||
|
||||
# Skip stdout/stderr capture with pytest, causes elevated memory usage
|
||||
@pytest.mark.nocapture
|
||||
@@ -404,9 +387,10 @@ class TestCarModelBase(unittest.TestCase):
|
||||
controls_allowed_prev = False
|
||||
CS_prev = car.CarState.new_message()
|
||||
checks = defaultdict(int)
|
||||
card = Car(CI=self.CI)
|
||||
selfdrived = SelfdriveD(CP=self.CP)
|
||||
selfdrived.initialized = True
|
||||
for idx, can in enumerate(self.can_msgs):
|
||||
CS = convert_to_capnp(self.CI.update(can_capnp_to_list((can.as_builder().to_bytes(), ))))
|
||||
CS = self.CI.update(can_capnp_to_list((can.as_builder().to_bytes(), ))).as_reader()
|
||||
for msg in filter(lambda m: m.src in range(64), can.can):
|
||||
to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat)
|
||||
ret = self.safety.safety_rx_hook(to_send)
|
||||
@@ -449,10 +433,10 @@ class TestCarModelBase(unittest.TestCase):
|
||||
checks['cruiseState'] += CS.cruiseState.enabled != self.safety.get_cruise_engaged_prev()
|
||||
else:
|
||||
# Check for enable events on rising edge of controls allowed
|
||||
card.update_events(CS, None)
|
||||
card.CS_prev = CS
|
||||
button_enable = (any(evt.enable for evt in CS.events) and
|
||||
not any(evt == EventName.pedalPressed for evt in card.events.names))
|
||||
selfdrived.update_events(CS)
|
||||
selfdrived.CS_prev = CS
|
||||
button_enable = (selfdrived.events.contains(ET.ENABLE) and
|
||||
EventName.pedalPressed not in selfdrived.events.names)
|
||||
mismatch = button_enable != (self.safety.get_controls_allowed() and not controls_allowed_prev)
|
||||
checks['controlsAllowed'] += mismatch
|
||||
controls_allowed_prev = self.safety.get_controls_allowed()
|
||||
@@ -467,11 +451,6 @@ class TestCarModelBase(unittest.TestCase):
|
||||
failed_checks = {k: v for k, v in checks.items() if v > 0}
|
||||
self.assertFalse(len(failed_checks), f"panda safety doesn't agree with openpilot: {failed_checks}")
|
||||
|
||||
@unittest.skipIf(not CI, "Accessing non CI-bucket routes is allowed only when not in CI")
|
||||
def test_route_on_ci_bucket(self):
|
||||
self.assertTrue(self.test_route_on_bucket, "Route not on CI bucket. " +
|
||||
"This is fine to fail for WIP car ports, just let us know and we can upload your routes to the CI bucket.")
|
||||
|
||||
|
||||
@parameterized_class(('platform', 'test_route'), get_test_cases())
|
||||
@pytest.mark.xdist_group_class_property('test_route')
|
||||
|
||||
@@ -87,7 +87,7 @@ class Controls:
|
||||
CC.enabled = self.sm['selfdriveState'].enabled
|
||||
|
||||
# Check which actuators can be enabled
|
||||
standstill = CS.vEgo <= max(self.CP.minSteerSpeed, MIN_LATERAL_CONTROL_SPEED) or CS.standstill
|
||||
standstill = abs(CS.vEgo) <= max(self.CP.minSteerSpeed, MIN_LATERAL_CONTROL_SPEED) or CS.standstill
|
||||
CC.latActive = self.sm['selfdriveState'].active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and not standstill
|
||||
CC.longActive = CC.enabled and not any(e.overrideLongitudinal for e in self.sm['onroadEvents']) and self.CP.openpilotLongitudinalControl
|
||||
|
||||
@@ -209,7 +209,7 @@ class Controls:
|
||||
self.update()
|
||||
CC, lac_log = self.state_control()
|
||||
self.publish(CC, lac_log)
|
||||
rk.keep_time()
|
||||
rk.monitor_time()
|
||||
|
||||
def main():
|
||||
config_realtime_process(4, Priority.CTRL_HIGH)
|
||||
|
||||
@@ -347,7 +347,8 @@ class LongitudinalMpc:
|
||||
lead_1_obstacle = lead_xv_1[:,0] + get_stopped_equivalence_factor(lead_xv_1[:,1])
|
||||
|
||||
self.params[:,0] = ACCEL_MIN
|
||||
self.params[:,1] = self.max_a
|
||||
# negative accel constraint causes problems because negative speed is not allowed
|
||||
self.params[:,1] = max(0.0, self.max_a)
|
||||
|
||||
# Update in ACC mode or ACC/e2e blend
|
||||
if self.mode == 'acc':
|
||||
@@ -356,6 +357,7 @@ class LongitudinalMpc:
|
||||
# Fake an obstacle for cruise, this ensures smooth acceleration to set speed
|
||||
# when the leads are no factor.
|
||||
v_lower = v_ego + (T_IDXS * self.cruise_min_a * 1.05)
|
||||
# TODO does this make sense when max_a is negative?
|
||||
v_upper = v_ego + (T_IDXS * self.max_a * 1.05)
|
||||
v_cruise_clipped = np.clip(v_cruise * np.ones(N+1),
|
||||
v_lower,
|
||||
|
||||
@@ -22,7 +22,7 @@ A_CRUISE_MAX_VALS = [1.6, 1.2, 0.8, 0.6]
|
||||
A_CRUISE_MAX_BP = [0., 10.0, 25., 40.]
|
||||
CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
|
||||
ALLOW_THROTTLE_THRESHOLD = 0.5
|
||||
ACCEL_LIMIT_MARGIN = 0.05
|
||||
MIN_ALLOW_THROTTLE_SPEED = 2.5
|
||||
|
||||
# Lookup table for turns
|
||||
_A_TOTAL_MAX_V = [1.7, 3.2]
|
||||
@@ -56,7 +56,10 @@ def get_accel_from_plan(CP, speeds, accels):
|
||||
a_target_now = interp(DT_MDL, CONTROL_N_T_IDX, accels)
|
||||
|
||||
v_target = interp(CP.longitudinalActuatorDelay + DT_MDL, CONTROL_N_T_IDX, speeds)
|
||||
a_target = 2 * (v_target - v_target_now) / CP.longitudinalActuatorDelay - a_target_now
|
||||
if v_target != v_target_now:
|
||||
a_target = 2 * (v_target - v_target_now) / CP.longitudinalActuatorDelay - a_target_now
|
||||
else:
|
||||
a_target = a_target_now
|
||||
|
||||
v_target_1sec = interp(CP.longitudinalActuatorDelay + DT_MDL + 1.0, CONTROL_N_T_IDX, speeds)
|
||||
else:
|
||||
@@ -131,7 +134,8 @@ class LongitudinalPlanner:
|
||||
|
||||
if self.mpc.mode == 'acc':
|
||||
accel_limits = [A_CRUISE_MIN, get_max_accel(v_ego)]
|
||||
accel_limits_turns = limit_accel_in_turns(v_ego, sm['carState'].steeringAngleDeg, accel_limits, self.CP)
|
||||
steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg
|
||||
accel_limits_turns = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_limits, self.CP)
|
||||
else:
|
||||
accel_limits = [ACCEL_MIN, ACCEL_MAX]
|
||||
accel_limits_turns = [ACCEL_MIN, ACCEL_MAX]
|
||||
@@ -146,12 +150,13 @@ class LongitudinalPlanner:
|
||||
# Compute model v_ego error
|
||||
self.v_model_error = get_speed_error(sm['modelV2'], v_ego)
|
||||
x, v, a, j, throttle_prob = self.parse_model(sm['modelV2'], self.v_model_error)
|
||||
self.allow_throttle = throttle_prob > ALLOW_THROTTLE_THRESHOLD
|
||||
# Don't clip at low speeds since throttle_prob doesn't account for creep
|
||||
self.allow_throttle = throttle_prob > ALLOW_THROTTLE_THRESHOLD or v_ego <= MIN_ALLOW_THROTTLE_SPEED
|
||||
|
||||
if not self.allow_throttle and v_ego > 5.0: # Don't clip at low speeds since throttle_prob doesn't account for creep
|
||||
# MPC breaks when accel limits would cause negative velocity within the MPC horizon, so we clip the max accel limit at vEgo/T_MAX plus a bit of margin
|
||||
clipped_accel_coast = max(accel_coast, accel_limits_turns[0], -v_ego / T_IDXS_MPC[-1] + ACCEL_LIMIT_MARGIN)
|
||||
accel_limits_turns[1] = min(accel_limits_turns[1], clipped_accel_coast)
|
||||
if not self.allow_throttle:
|
||||
clipped_accel_coast = max(accel_coast, accel_limits_turns[0])
|
||||
clipped_accel_coast_interp = interp(v_ego, [MIN_ALLOW_THROTTLE_SPEED, MIN_ALLOW_THROTTLE_SPEED*2], [accel_limits_turns[1], clipped_accel_coast])
|
||||
accel_limits_turns[1] = min(accel_limits_turns[1], clipped_accel_coast_interp)
|
||||
|
||||
if force_slow_decel:
|
||||
v_cruise = 0.0
|
||||
|
||||
@@ -5,7 +5,6 @@ from opendbc.car.car_helpers import interfaces
|
||||
from opendbc.car.honda.values import CAR as HONDA
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA
|
||||
from opendbc.car.nissan.values import CAR as NISSAN
|
||||
from openpilot.selfdrive.car.card import convert_to_capnp
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle
|
||||
@@ -21,7 +20,6 @@ class TestLatControl:
|
||||
CarInterface, CarController, CarState, RadarInterface = interfaces[car_name]
|
||||
CP = CarInterface.get_non_essential_params(car_name)
|
||||
CI = CarInterface(CP, CarController, CarState)
|
||||
CP = convert_to_capnp(CP)
|
||||
VM = VehicleModel(CP)
|
||||
|
||||
controller = controller(CP.as_reader(), CI)
|
||||
|
||||
@@ -5,14 +5,13 @@ import numpy as np
|
||||
|
||||
from opendbc.car.honda.interface import CarInterface
|
||||
from opendbc.car.honda.values import CAR
|
||||
from openpilot.selfdrive.car.card import convert_to_capnp
|
||||
from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel, dyn_ss_sol, create_dyn_state_matrices
|
||||
|
||||
|
||||
class TestVehicleModel:
|
||||
def setup_method(self):
|
||||
CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC)
|
||||
self.VM = VehicleModel(convert_to_capnp(CP))
|
||||
self.VM = VehicleModel(CP)
|
||||
|
||||
def test_round_trip_yaw_rate(self):
|
||||
# TODO: fix VM to work at zero speed
|
||||
|
||||
@@ -19,7 +19,7 @@ def main():
|
||||
ldw = LaneDepartureWarning()
|
||||
longitudinal_planner = LongitudinalPlanner(CP)
|
||||
pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance'])
|
||||
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'radarState', 'modelV2', 'selfdriveState'],
|
||||
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState'],
|
||||
poll='modelV2', ignore_avg_freq=['radarState'])
|
||||
|
||||
while True:
|
||||
@@ -30,7 +30,7 @@ def main():
|
||||
|
||||
ldw.update(sm.frame, sm['modelV2'], sm['carState'], sm['carControl'])
|
||||
msg = messaging.new_message('driverAssistance')
|
||||
msg.valid = sm.all_checks(['carState', 'carControl', 'modelV2'])
|
||||
msg.valid = sm.all_checks(['carState', 'carControl', 'modelV2', 'liveParameters'])
|
||||
msg.driverAssistance.leftLaneDeparture = ldw.left
|
||||
msg.driverAssistance.rightLaneDeparture = ldw.right
|
||||
pm.send('driverAssistance', msg)
|
||||
|
||||
@@ -80,10 +80,6 @@ class Track:
|
||||
|
||||
self.cnt += 1
|
||||
|
||||
def get_key_for_cluster(self):
|
||||
# Weigh y higher since radar is inaccurate in this dimension
|
||||
return [self.dRel, self.yRel*2, self.vRel]
|
||||
|
||||
def reset_a_lead(self, aLeadK: float, aLeadTau: float):
|
||||
self.kf = KF1D([[self.vLead], [aLeadK]], self.K_A, self.K_C, self.K_K)
|
||||
self.aLeadK = aLeadK
|
||||
|
||||
@@ -14,7 +14,6 @@ N_RUNS = 10
|
||||
|
||||
class CarModelTestCase(TestCarModelBase):
|
||||
test_route = CarTestRoute(DEMO_ROUTE, None)
|
||||
ci = False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -10,7 +10,7 @@ from openpilot.selfdrive.selfdrived.events import ET, Events
|
||||
from openpilot.selfdrive.selfdrived.alertmanager import AlertManager
|
||||
from openpilot.system.manager.process_config import managed_processes
|
||||
|
||||
EventName = car.OnroadEvent.EventName
|
||||
EventName = log.OnroadEvent.EventName
|
||||
|
||||
def randperc() -> float:
|
||||
return 100. * random.random()
|
||||
|
||||
@@ -8,10 +8,12 @@ def get_fingerprint(lr):
|
||||
# TODO: make this a nice tool for car ports. should also work with qlogs for FW
|
||||
|
||||
fw = None
|
||||
vin = None
|
||||
msgs = {}
|
||||
for msg in lr:
|
||||
if msg.which() == 'carParams':
|
||||
fw = msg.carParams.carFw
|
||||
vin = msg.carParams.carVin
|
||||
elif msg.which() == 'can':
|
||||
for c in msg.can:
|
||||
# read also msgs sent by EON on CAN bus 0x80 and filter out the
|
||||
@@ -32,6 +34,7 @@ def get_fingerprint(lr):
|
||||
print(f" {f.fwVersion},")
|
||||
print(" ],")
|
||||
print()
|
||||
print(f"VIN: {vin}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -2,12 +2,16 @@
|
||||
import jinja2
|
||||
import os
|
||||
|
||||
from cereal import car
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from opendbc.car.interfaces import get_interface_attr
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
|
||||
CARS = get_interface_attr('CAR')
|
||||
FW_VERSIONS = get_interface_attr('FW_VERSIONS')
|
||||
FINGERPRINTS = get_interface_attr('FINGERPRINTS')
|
||||
ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()}
|
||||
|
||||
FINGERPRINTS_PY_TEMPLATE = jinja2.Template("""
|
||||
{%- if FINGERPRINTS[brand] %}
|
||||
@@ -45,7 +49,7 @@ FW_VERSIONS{% if not FW_VERSIONS[brand] %}: dict[str, dict[tuple, list[bytes]]]{
|
||||
{% for car, _ in FW_VERSIONS[brand].items() %}
|
||||
CAR.{{car.name}}: {
|
||||
{% for key, fw_versions in FW_VERSIONS[brand][car].items() %}
|
||||
(Ecu.{{key[0]}}, 0x{{"%0x" | format(key[1] | int)}}, \
|
||||
(Ecu.{{ECU_NAME[key[0]]}}, 0x{{"%0x" | format(key[1] | int)}}, \
|
||||
{% if key[2] %}0x{{"%0x" | format(key[2] | int)}}{% else %}{{key[2]}}{% endif %}): [
|
||||
{% for fw_version in (fw_versions + extra_fw_versions.get(car, {}).get(key, [])) | unique | sort %}
|
||||
{{fw_version}},
|
||||
@@ -67,8 +71,9 @@ def format_brand_fw_versions(brand, extra_fw_versions: None | dict[str, dict[tup
|
||||
comments = [line for line in f.readlines() if line.startswith("#") and "noqa" not in line]
|
||||
|
||||
with open(fingerprints_file, "w") as f:
|
||||
f.write(FINGERPRINTS_PY_TEMPLATE.render(brand=brand, comments=comments, FINGERPRINTS=FINGERPRINTS,
|
||||
FW_VERSIONS=FW_VERSIONS, extra_fw_versions=extra_fw_versions))
|
||||
f.write(FINGERPRINTS_PY_TEMPLATE.render(brand=brand, comments=comments, ECU_NAME=ECU_NAME,
|
||||
FINGERPRINTS=FINGERPRINTS, FW_VERSIONS=FW_VERSIONS,
|
||||
extra_fw_versions=extra_fw_versions))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -139,7 +139,6 @@ def main():
|
||||
pm = PubMaster(["driverStateV2"])
|
||||
|
||||
calib = np.zeros(CALIB_LEN, dtype=np.float32)
|
||||
# last = 0
|
||||
|
||||
while True:
|
||||
buf = vipc_client.recv()
|
||||
@@ -155,8 +154,6 @@ def main():
|
||||
t2 = time.perf_counter()
|
||||
|
||||
pm.send("driverStateV2", get_driverstate_packet(model_output, vipc_client.frame_id, vipc_client.timestamp_sof, t2 - t1, gpu_execution_time))
|
||||
# print("dmonitoring process: %.2fms, from last %.2fms\n" % (t2 - t1, t1 - last))
|
||||
# last = t1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -17,7 +17,6 @@ from openpilot.common.realtime import config_realtime_process
|
||||
from openpilot.common.transformations.camera import DEVICE_CAMERAS
|
||||
from openpilot.common.transformations.model import get_warp_matrix
|
||||
from openpilot.system import sentry
|
||||
from openpilot.selfdrive.car.card import convert_to_capnp
|
||||
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
|
||||
from openpilot.selfdrive.modeld.runners import ModelRunner, Runtime
|
||||
from openpilot.selfdrive.modeld.parse_model_outputs import Parser
|
||||
@@ -184,7 +183,7 @@ def main(demo=False):
|
||||
|
||||
|
||||
if demo:
|
||||
CP = convert_to_capnp(get_demo_car_params())
|
||||
CP = get_demo_car_params()
|
||||
else:
|
||||
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
|
||||
cloudlog.info("modeld got CarParams: %s", CP.carName)
|
||||
@@ -231,7 +230,8 @@ def main(demo=False):
|
||||
desire = DH.desire
|
||||
is_rhd = sm["driverMonitoringState"].isRHD
|
||||
frame_id = sm["roadCameraState"].frameId
|
||||
lateral_control_params = np.array([sm["carState"].vEgo, steer_delay], dtype=np.float32)
|
||||
v_ego = max(sm["carState"].vEgo, 0.)
|
||||
lateral_control_params = np.array([v_ego, steer_delay], dtype=np.float32)
|
||||
if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']:
|
||||
device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32)
|
||||
dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))]
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:dd55319c8e3e9ac120f2b1bb1131cb67018669dfc0f7ebd31c27cc6de3e9f959
|
||||
size 50309976
|
||||
oid sha256:c829d824ebc73d15da82516592c07d9784369ccbf710698e919e06a702e70924
|
||||
size 50320138
|
||||
|
||||
@@ -7,7 +7,6 @@ from opendbc.car.car_helpers import get_demo_car_params
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.transformations.camera import DEVICE_CAMERAS
|
||||
from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.selfdrive.car.card import convert_to_capnp
|
||||
from openpilot.system.manager.process_config import managed_processes
|
||||
from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state
|
||||
|
||||
@@ -20,11 +19,11 @@ class TestModeld:
|
||||
|
||||
def setup_method(self):
|
||||
self.vipc_server = VisionIpcServer("camerad")
|
||||
self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 40, False, CAM.width, CAM.height)
|
||||
self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_DRIVER, 40, False, CAM.width, CAM.height)
|
||||
self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 40, False, CAM.width, CAM.height)
|
||||
self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 40, CAM.width, CAM.height)
|
||||
self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_DRIVER, 40, CAM.width, CAM.height)
|
||||
self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 40, CAM.width, CAM.height)
|
||||
self.vipc_server.start_listener()
|
||||
Params().put("CarParams", convert_to_capnp(get_demo_car_params()).to_bytes())
|
||||
Params().put("CarParams", get_demo_car_params().to_bytes())
|
||||
|
||||
self.sm = messaging.SubMaster(['modelV2', 'cameraOdometry'])
|
||||
self.pm = messaging.PubMaster(['roadCameraState', 'wideRoadCameraState', 'liveCalibration'])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from math import atan2
|
||||
|
||||
from cereal import car
|
||||
from cereal import car, log
|
||||
import cereal.messaging as messaging
|
||||
from openpilot.selfdrive.selfdrived.events import Events
|
||||
from openpilot.common.numpy_fast import interp
|
||||
@@ -9,7 +9,7 @@ from openpilot.common.filter_simple import FirstOrderFilter
|
||||
from openpilot.common.stat_live import RunningStatFilter
|
||||
from openpilot.common.transformations.camera import DEVICE_CAMERAS
|
||||
|
||||
EventName = car.OnroadEvent.EventName
|
||||
EventName = log.OnroadEvent.EventName
|
||||
|
||||
# ******************************************************************************************
|
||||
# NOTE: To fork maintainers.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import numpy as np
|
||||
|
||||
from cereal import car, log
|
||||
from cereal import log
|
||||
from openpilot.common.realtime import DT_DMON
|
||||
from openpilot.selfdrive.monitoring.helpers import DriverMonitoring, DRIVER_MONITOR_SETTINGS
|
||||
|
||||
EventName = car.OnroadEvent.EventName
|
||||
EventName = log.OnroadEvent.EventName
|
||||
dm_settings = DRIVER_MONITOR_SETTINGS()
|
||||
|
||||
TEST_TIMESPAN = 120 # seconds
|
||||
|
||||
@@ -306,6 +306,16 @@ void send_peripheral_state(Panda *panda, PubMaster *pm) {
|
||||
LOGW("reading hwmon took %lfms", read_time);
|
||||
}
|
||||
|
||||
// fall back to panda's voltage and current measurement
|
||||
if (ps.getVoltage() == 0 && ps.getCurrent() == 0) {
|
||||
auto health_opt = panda->get_state();
|
||||
if (health_opt) {
|
||||
health_t health = *health_opt;
|
||||
ps.setVoltage(health.voltage_pkt);
|
||||
ps.setCurrent(health.current_pkt);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t fan_speed_rpm = panda->get_fan_speed();
|
||||
ps.setFanSpeedRpm(fan_speed_rpm);
|
||||
|
||||
|
||||
@@ -103,9 +103,8 @@ class TestPandad:
|
||||
# 5s for USB (due to enumeration)
|
||||
# - 0.2s pandad -> pandad
|
||||
# - plus some buffer
|
||||
assert 0.1 < (sum(ts)/len(ts)) < (0.5 if self.spi else 5.0)
|
||||
print("startup times", ts, sum(ts) / len(ts))
|
||||
|
||||
assert 0.1 < (sum(ts)/len(ts)) < (0.7 if self.spi else 5.0)
|
||||
|
||||
def test_protocol_version_check(self):
|
||||
if not self.spi:
|
||||
|
||||
@@ -27,10 +27,13 @@ class AlertEntry:
|
||||
alert: Alert | None = None
|
||||
start_frame: int = -1
|
||||
end_frame: int = -1
|
||||
added_frame: int = -1
|
||||
|
||||
def active(self, frame: int) -> bool:
|
||||
return frame <= self.end_frame
|
||||
|
||||
def just_added(self, frame: int) -> bool:
|
||||
return self.active(frame) and frame == (self.added_frame + 1)
|
||||
|
||||
class AlertManager:
|
||||
def __init__(self):
|
||||
@@ -41,10 +44,11 @@ class AlertManager:
|
||||
for alert in alerts:
|
||||
entry = self.alerts[alert.alert_type]
|
||||
entry.alert = alert
|
||||
if not entry.active(frame):
|
||||
if not entry.just_added(frame):
|
||||
entry.start_frame = frame
|
||||
min_end_frame = entry.start_frame + alert.duration
|
||||
entry.end_frame = max(frame + 1, min_end_frame)
|
||||
entry.added_frame = frame
|
||||
|
||||
def process_alerts(self, frame: int, clear_event_types: set):
|
||||
ae = AlertEntry()
|
||||
|
||||
@@ -16,7 +16,7 @@ AlertSize = log.SelfdriveState.AlertSize
|
||||
AlertStatus = log.SelfdriveState.AlertStatus
|
||||
VisualAlert = car.CarControl.HUDControl.VisualAlert
|
||||
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
|
||||
EventName = car.OnroadEvent.EventName
|
||||
EventName = log.OnroadEvent.EventName
|
||||
|
||||
|
||||
# Alert priorities
|
||||
@@ -98,7 +98,7 @@ class Events:
|
||||
def to_msg(self):
|
||||
ret = []
|
||||
for event_name in self.events:
|
||||
event = car.OnroadEvent.new_message()
|
||||
event = log.OnroadEvent.new_message()
|
||||
event.name = event_name
|
||||
for event_type in EVENTS.get(event_name, {}):
|
||||
setattr(event, event_type, True)
|
||||
@@ -382,14 +382,21 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
||||
ET.PERMANENT: StartupAlert("Dashcam mode for unsupported car"),
|
||||
},
|
||||
|
||||
EventName.startupNoSecOcKey: {
|
||||
ET.PERMANENT: NormalPermanentAlert("Dashcam Mode",
|
||||
"Security Key Not Available",
|
||||
priority=Priority.HIGH),
|
||||
},
|
||||
|
||||
EventName.dashcamMode: {
|
||||
ET.PERMANENT: NormalPermanentAlert("Dashcam Mode",
|
||||
priority=Priority.LOWEST),
|
||||
},
|
||||
|
||||
EventName.invalidLkasSetting: {
|
||||
ET.PERMANENT: NormalPermanentAlert("Stock LKAS is on",
|
||||
"Turn off stock LKAS to engage"),
|
||||
ET.PERMANENT: NormalPermanentAlert("Invalid LKAS setting",
|
||||
"Toggle stock LKAS on or off to engage"),
|
||||
ET.NO_ENTRY: NoEntryAlert("Invalid LKAS setting"),
|
||||
},
|
||||
|
||||
EventName.cruiseMismatch: {
|
||||
@@ -717,11 +724,6 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
||||
Priority.LOWER, VisualAlert.none, AudibleAlert.none, .2, creation_delay=600.)
|
||||
},
|
||||
|
||||
EventName.soundsUnavailable: {
|
||||
ET.PERMANENT: NormalPermanentAlert("Speaker not found", "Reboot your Device"),
|
||||
ET.NO_ENTRY: NoEntryAlert("Speaker not found"),
|
||||
},
|
||||
|
||||
EventName.tooDistracted: {
|
||||
ET.NO_ENTRY: NoEntryAlert("Distraction Level Too High"),
|
||||
},
|
||||
@@ -939,16 +941,6 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
||||
ET.NO_ENTRY: NoEntryAlert("Slow down to engage"),
|
||||
},
|
||||
|
||||
EventName.lowSpeedLockout: {
|
||||
ET.PERMANENT: NormalPermanentAlert("Cruise Fault: Restart the car to engage"),
|
||||
ET.NO_ENTRY: NoEntryAlert("Cruise Fault: Restart the Car"),
|
||||
},
|
||||
|
||||
EventName.lkasDisabled: {
|
||||
ET.PERMANENT: NormalPermanentAlert("LKAS Disabled: Enable LKAS to engage"),
|
||||
ET.NO_ENTRY: NoEntryAlert("LKAS Disabled"),
|
||||
},
|
||||
|
||||
EventName.vehicleSensorsInvalid: {
|
||||
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Vehicle Sensors Invalid"),
|
||||
ET.PERMANENT: NormalPermanentAlert("Vehicle Sensors Calibrating", "Drive to Calibrate"),
|
||||
|
||||
@@ -7,6 +7,7 @@ import cereal.messaging as messaging
|
||||
|
||||
from cereal import car, log
|
||||
from msgq.visionipc import VisionIpcClient, VisionStreamType
|
||||
from panda import ALTERNATIVE_EXPERIENCE
|
||||
|
||||
|
||||
from openpilot.common.params import Params
|
||||
@@ -14,12 +15,12 @@ from openpilot.common.realtime import config_realtime_process, Priority, Ratekee
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.common.gps import get_gps_location_service
|
||||
|
||||
from openpilot.selfdrive.car.car_specific import CarSpecificEvents
|
||||
from openpilot.selfdrive.selfdrived.events import Events, ET
|
||||
from openpilot.selfdrive.selfdrived.state import StateMachine
|
||||
from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroad_alert
|
||||
from openpilot.selfdrive.controls.lib.latcontrol import MIN_LATERAL_CONTROL_SPEED
|
||||
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.version import get_build_metadata
|
||||
|
||||
REPLAY = "REPLAY" in os.environ
|
||||
@@ -33,7 +34,7 @@ State = log.SelfdriveState.OpenpilotState
|
||||
PandaType = log.PandaState.PandaType
|
||||
LaneChangeState = log.LaneChangeState
|
||||
LaneChangeDirection = log.LaneChangeDirection
|
||||
EventName = car.OnroadEvent.EventName
|
||||
EventName = log.OnroadEvent.EventName
|
||||
ButtonType = car.CarState.ButtonEvent.Type
|
||||
SafetyModel = car.CarParams.SafetyModel
|
||||
|
||||
@@ -41,15 +42,21 @@ IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
|
||||
|
||||
|
||||
class SelfdriveD:
|
||||
def __init__(self):
|
||||
def __init__(self, CP=None):
|
||||
self.params = Params()
|
||||
|
||||
# Ensure the current branch is cached, otherwise the first cycle lags
|
||||
build_metadata = get_build_metadata()
|
||||
|
||||
cloudlog.info("selfdrived is waiting for CarParams")
|
||||
self.CP = messaging.log_from_bytes(self.params.get("CarParams", block=True), car.CarParams)
|
||||
cloudlog.info("selfdrived got CarParams")
|
||||
if CP is None:
|
||||
cloudlog.info("selfdrived is waiting for CarParams")
|
||||
self.CP = messaging.log_from_bytes(self.params.get("CarParams", block=True), car.CarParams)
|
||||
cloudlog.info("selfdrived got CarParams")
|
||||
else:
|
||||
self.CP = CP
|
||||
|
||||
self.car_events = CarSpecificEvents(self.CP)
|
||||
self.disengage_on_accelerator = not (self.CP.alternativeExperience & ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS)
|
||||
|
||||
# Setup sockets
|
||||
self.pm = messaging.PubMaster(['selfdriveState', 'onroadEvents'])
|
||||
@@ -80,9 +87,6 @@ class SelfdriveD:
|
||||
self.is_metric = self.params.get_bool("IsMetric")
|
||||
self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled")
|
||||
|
||||
# detect sound card presence and ensure successful init
|
||||
sounds_available = HARDWARE.get_sound_card_online()
|
||||
|
||||
car_recognized = self.CP.carName != 'mock'
|
||||
|
||||
# cleanup old params
|
||||
@@ -118,9 +122,9 @@ class SelfdriveD:
|
||||
self.startup_event = EventName.startupNoCar
|
||||
elif car_recognized and self.CP.passive:
|
||||
self.startup_event = EventName.startupNoControl
|
||||
elif self.CP.secOcRequired and not self.CP.secOcKeyAvailable:
|
||||
self.startup_event = EventName.startupNoSecOcKey
|
||||
|
||||
if not sounds_available:
|
||||
self.events.add(EventName.soundsUnavailable, static=True)
|
||||
if not car_recognized:
|
||||
self.events.add(EventName.carUnrecognized, static=True)
|
||||
set_offroad_alert("Offroad_CarUnrecognized", True)
|
||||
@@ -164,7 +168,20 @@ class SelfdriveD:
|
||||
|
||||
# Add car events, ignore if CAN isn't valid
|
||||
if CS.canValid:
|
||||
self.events.add_from_msg(CS.events)
|
||||
car_events = self.car_events.update(CS, self.CS_prev, self.sm['carControl']).to_msg()
|
||||
self.events.add_from_msg(car_events)
|
||||
|
||||
if self.CP.notCar:
|
||||
# wait for everything to init first
|
||||
if self.sm.frame > int(5. / DT_CTRL) and self.initialized:
|
||||
# body always wants to enable
|
||||
self.events.add(EventName.pcmEnable)
|
||||
|
||||
# Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0
|
||||
if (CS.gasPressed and not self.CS_prev.gasPressed and self.disengage_on_accelerator) or \
|
||||
(CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or \
|
||||
(CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)):
|
||||
self.events.add(EventName.pedalPressed)
|
||||
|
||||
# Create events for temperature, disk space, and memory
|
||||
if self.sm['deviceState'].thermalStatus >= ThermalStatus.red:
|
||||
@@ -238,11 +255,12 @@ class SelfdriveD:
|
||||
num_events = len(self.events)
|
||||
|
||||
not_running = {p.name for p in self.sm['managerState'].processes if not p.running and p.shouldBeRunning}
|
||||
if self.sm.recv_frame['managerState'] and (not_running - IGNORE_PROCESSES):
|
||||
self.events.add(EventName.processNotRunning)
|
||||
if self.sm.recv_frame['managerState'] and len(not_running):
|
||||
if not_running != self.not_running_prev:
|
||||
cloudlog.event("process_not_running", not_running=not_running, error=True)
|
||||
self.not_running_prev = not_running
|
||||
if self.sm.recv_frame['managerState'] and (not_running - IGNORE_PROCESSES):
|
||||
self.events.add(EventName.processNotRunning)
|
||||
else:
|
||||
if not SIMULATION and not self.rk.lagging:
|
||||
if not self.sm.all_alive(self.camera_packets):
|
||||
@@ -332,7 +350,7 @@ class SelfdriveD:
|
||||
self.events.add(EventName.noGps)
|
||||
if gps_ok:
|
||||
self.distance_traveled = 0
|
||||
self.distance_traveled += CS.vEgo * DT_CTRL
|
||||
self.distance_traveled += abs(CS.vEgo) * DT_CTRL
|
||||
|
||||
if self.sm['modelV2'].frameDropPerc > 20:
|
||||
self.events.add(EventName.modeldLagging)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import random
|
||||
|
||||
from openpilot.selfdrive.selfdrived.events import Alert, EVENTS
|
||||
from openpilot.selfdrive.selfdrived.events import Alert, EmptyAlert, EVENTS
|
||||
from openpilot.selfdrive.selfdrived.alertmanager import AlertManager
|
||||
|
||||
|
||||
@@ -32,8 +32,27 @@ class TestAlertManager:
|
||||
for frame in range(duration+10):
|
||||
if frame < add_duration:
|
||||
AM.add_many(frame, [alert, ])
|
||||
AM.process_alerts(frame, {})
|
||||
AM.process_alerts(frame, set())
|
||||
|
||||
shown = AM.current_alert is not None
|
||||
shown = AM.current_alert != EmptyAlert
|
||||
should_show = frame <= show_duration
|
||||
assert shown == should_show, f"{frame=} {add_duration=} {duration=}"
|
||||
|
||||
# check one case:
|
||||
# - if alert is re-added to AM before it ends the duration is extended
|
||||
if duration > 1:
|
||||
AM = AlertManager()
|
||||
show_duration = duration * 2
|
||||
for frame in range(duration * 2 + 10):
|
||||
if frame == 0:
|
||||
AM.add_many(frame, [alert, ])
|
||||
|
||||
if frame == duration:
|
||||
# add alert one frame before it ends
|
||||
assert AM.current_alert == alert
|
||||
AM.add_many(frame, [alert, ])
|
||||
AM.process_alerts(frame, set())
|
||||
|
||||
shown = AM.current_alert != EmptyAlert
|
||||
should_show = frame <= show_duration
|
||||
assert shown == should_show, f"{frame=} {duration=}"
|
||||
|
||||
@@ -33,12 +33,12 @@ class TestAlerts:
|
||||
# Create fake objects for callback
|
||||
cls.CS = car.CarState.new_message()
|
||||
cls.CP = car.CarParams.new_message()
|
||||
cfg = [c for c in CONFIGS if c.proc_name == 'controlsd'][0]
|
||||
cfg = [c for c in CONFIGS if c.proc_name == 'selfdrived'][0]
|
||||
cls.sm = SubMaster(cfg.pubs)
|
||||
|
||||
def test_events_defined(self):
|
||||
# Ensure all events in capnp schema are defined in events.py
|
||||
events = car.OnroadEvent.EventName.schema.enumerants
|
||||
events = log.OnroadEvent.EventName.schema.enumerants
|
||||
|
||||
for name, e in events.items():
|
||||
if not name.endswith("DEPRECATED"):
|
||||
|
||||
@@ -4,8 +4,7 @@ import sys
|
||||
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
|
||||
|
||||
with OpenpilotPrefix():
|
||||
ret = subprocess.call(sys.argv[1:])
|
||||
|
||||
exit(ret)
|
||||
sys.exit(ret)
|
||||
|
||||
@@ -64,6 +64,7 @@ class Plant:
|
||||
control = messaging.new_message('controlsState')
|
||||
ss = messaging.new_message('selfdriveState')
|
||||
car_state = messaging.new_message('carState')
|
||||
lp = messaging.new_message('liveParameters')
|
||||
car_control = messaging.new_message('carControl')
|
||||
model = messaging.new_message('modelV2')
|
||||
a_lead = (v_lead - self.v_lead_prev)/self.ts
|
||||
@@ -130,6 +131,7 @@ class Plant:
|
||||
'carControl': car_control.carControl,
|
||||
'controlsState': control.controlsState,
|
||||
'selfdriveState': ss.selfdriveState,
|
||||
'liveParameters': lp.liveParameters,
|
||||
'modelV2': model.modelV2}
|
||||
self.planner.update(sm)
|
||||
self.speed = self.planner.v_desired_filter.x
|
||||
|
||||
@@ -108,18 +108,6 @@ def create_maneuvers(kwargs):
|
||||
breakpoints=[0.0, 2., 2.01],
|
||||
**kwargs,
|
||||
),
|
||||
Maneuver(
|
||||
"slow to 5m/s with allow_throttle = False and pitch = +0.1",
|
||||
duration=25.,
|
||||
initial_speed=20.,
|
||||
lead_relevancy=False,
|
||||
prob_throttle_values=[1., 0., 0.],
|
||||
cruise_values=[20., 20., 20.],
|
||||
pitch_values=[0., 0.1, 0.1],
|
||||
breakpoints=[0.0, 2., 2.01],
|
||||
ensure_slowdown=True,
|
||||
**kwargs,
|
||||
),
|
||||
Maneuver(
|
||||
"approach slower cut-in car at 20m/s",
|
||||
duration=20.,
|
||||
@@ -163,6 +151,20 @@ def create_maneuvers(kwargs):
|
||||
**kwargs,
|
||||
),
|
||||
]
|
||||
if not kwargs['e2e']:
|
||||
# allow_throttle won't trigger with e2e
|
||||
maneuvers.append(Maneuver(
|
||||
"slow to 5m/s with allow_throttle = False and pitch = +0.1",
|
||||
duration=30.,
|
||||
initial_speed=20.,
|
||||
lead_relevancy=False,
|
||||
prob_throttle_values=[1., 0., 0.],
|
||||
cruise_values=[20., 20., 20.],
|
||||
pitch_values=[0., 0.1, 0.1],
|
||||
breakpoints=[0.0, 2., 2.01],
|
||||
ensure_slowdown=True,
|
||||
**kwargs,
|
||||
))
|
||||
if not kwargs['force_decel']:
|
||||
# controls relies on planner commanding to move for stock-ACC resume spamming
|
||||
maneuvers.append(Maneuver(
|
||||
|
||||
@@ -30,7 +30,7 @@ optional arguments:
|
||||
--whitelist-cars WHITELIST_CARS Whitelist given cars from the test (e.g. HONDA)
|
||||
--blacklist-procs BLACKLIST_PROCS Blacklist given processes from the test (e.g. controlsd)
|
||||
--blacklist-cars BLACKLIST_CARS Blacklist given cars from the test (e.g. HONDA)
|
||||
--ignore-fields IGNORE_FIELDS Extra fields or msgs to ignore (e.g. carState.events)
|
||||
--ignore-fields IGNORE_FIELDS Extra fields or msgs to ignore (e.g. driverMonitoringState.events)
|
||||
--ignore-msgs IGNORE_MSGS Msgs to ignore (e.g. onroadEvents)
|
||||
--update-refs Updates reference logs using current commit
|
||||
--upload-only Skips testing processes and uploads logs from previous test run
|
||||
|
||||
@@ -1,76 +1,147 @@
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
import functools
|
||||
import capnp
|
||||
|
||||
from cereal import messaging, car
|
||||
from cereal import messaging, car, log
|
||||
from opendbc.car.fingerprints import MIGRATION
|
||||
from opendbc.car.toyota.values import EPS_SCALE
|
||||
from opendbc.car.ford.values import CAR as FORD, FordFlags
|
||||
from openpilot.selfdrive.modeld.constants import ModelConstants
|
||||
from openpilot.selfdrive.modeld.fill_model_msg import fill_xyz_poly, fill_lane_line_meta
|
||||
from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_encode_index
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_planner import get_accel_from_plan
|
||||
from openpilot.system.manager.process_config import managed_processes
|
||||
from openpilot.tools.lib.logreader import LogIterable
|
||||
from panda import Panda
|
||||
|
||||
MessageWithIndex = tuple[int, capnp.lib.capnp._DynamicStructReader]
|
||||
MigrationOps = tuple[list[tuple[int, capnp.lib.capnp._DynamicStructReader]], list[capnp.lib.capnp._DynamicStructReader], list[int]]
|
||||
MigrationFunc = Callable[[list[MessageWithIndex]], MigrationOps]
|
||||
|
||||
# TODO: message migration should happen in-place
|
||||
def migrate_all(lr, manager_states=False, panda_states=False, camera_states=False):
|
||||
msgs = migrate_sensorEvents(lr)
|
||||
msgs = migrate_carParams(msgs)
|
||||
msgs = migrate_gpsLocation(msgs)
|
||||
msgs = migrate_deviceState(msgs)
|
||||
msgs = migrate_carOutput(msgs)
|
||||
msgs = migrate_controlsState(msgs)
|
||||
msgs = migrate_liveLocationKalman(msgs)
|
||||
msgs = migrate_liveTracks(msgs)
|
||||
msgs = migrate_driverAssistance(msgs)
|
||||
msgs = migrate_drivingModelData(msgs)
|
||||
|
||||
## rules for migration functions
|
||||
## 1. must use the decorator @migration(inputs=[...], product="...") and MigrationFunc signature
|
||||
## 2. it only gets the messages that are in the inputs list
|
||||
## 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)
|
||||
## 5. all migration functions must be independent of each other
|
||||
def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: bool = False, camera_states: bool = False):
|
||||
migrations = [
|
||||
migrate_sensorEvents,
|
||||
migrate_carParams,
|
||||
migrate_gpsLocation,
|
||||
migrate_deviceState,
|
||||
migrate_carOutput,
|
||||
migrate_controlsState,
|
||||
migrate_carState,
|
||||
migrate_liveLocationKalman,
|
||||
migrate_liveTracks,
|
||||
migrate_driverAssistance,
|
||||
migrate_drivingModelData,
|
||||
migrate_onroadEvents,
|
||||
migrate_driverMonitoringState,
|
||||
migrate_longitudinalPlan,
|
||||
]
|
||||
if manager_states:
|
||||
msgs = migrate_managerState(msgs)
|
||||
migrations.append(migrate_managerState)
|
||||
if panda_states:
|
||||
msgs = migrate_pandaStates(msgs)
|
||||
msgs = migrate_peripheralState(msgs)
|
||||
migrations.extend([migrate_pandaStates, migrate_peripheralState])
|
||||
if camera_states:
|
||||
msgs = migrate_cameraStates(msgs)
|
||||
migrations.append(migrate_cameraStates)
|
||||
|
||||
return msgs
|
||||
return migrate(lr, migrations)
|
||||
|
||||
|
||||
def migrate_driverAssistance(lr):
|
||||
all_msgs = []
|
||||
for msg in lr:
|
||||
all_msgs.append(msg)
|
||||
if msg.which() == 'longitudinalPlan':
|
||||
all_msgs.append(messaging.new_message('driverAssistance', valid=True, logMonoTime=msg.logMonoTime).as_reader())
|
||||
if msg.which() == 'driverAssistance':
|
||||
return lr
|
||||
return all_msgs
|
||||
def migrate(lr: LogIterable, migration_funcs: list[MigrationFunc]):
|
||||
lr = list(lr)
|
||||
grouped = defaultdict(list)
|
||||
for i, msg in enumerate(lr):
|
||||
grouped[msg.which()].append(i)
|
||||
|
||||
|
||||
def migrate_drivingModelData(lr):
|
||||
all_msgs = []
|
||||
for msg in lr:
|
||||
all_msgs.append(msg)
|
||||
if msg.which() == "modelV2":
|
||||
dmd = messaging.new_message('drivingModelData', valid=msg.valid, logMonoTime=msg.logMonoTime)
|
||||
for field in ["frameId", "frameIdExtra", "frameDropPerc", "modelExecutionTime", "action"]:
|
||||
setattr(dmd.drivingModelData, field, getattr(msg.modelV2, field))
|
||||
for meta_field in ["laneChangeState", "laneChangeState"]:
|
||||
setattr(dmd.drivingModelData.meta, meta_field, getattr(msg.modelV2.meta, meta_field))
|
||||
if len(msg.modelV2.laneLines) and len(msg.modelV2.laneLineProbs):
|
||||
fill_lane_line_meta(dmd.drivingModelData.laneLineMeta, msg.modelV2.laneLines, msg.modelV2.laneLineProbs)
|
||||
if all(len(a) for a in [msg.modelV2.position.x, msg.modelV2.position.y, msg.modelV2.position.z]):
|
||||
fill_xyz_poly(dmd.drivingModelData.path, ModelConstants.POLY_PATH_DEGREE, msg.modelV2.position.x, msg.modelV2.position.y, msg.modelV2.position.z)
|
||||
all_msgs.append(dmd.as_reader())
|
||||
elif msg.which() == "drivingModelData":
|
||||
return lr
|
||||
return all_msgs
|
||||
|
||||
|
||||
def migrate_liveTracks(lr):
|
||||
all_msgs = []
|
||||
for msg in lr:
|
||||
if msg.which() != "liveTracksDEPRECATED":
|
||||
all_msgs.append(msg)
|
||||
replace_ops, add_ops, del_ops = [], [], []
|
||||
for migration in migration_funcs:
|
||||
assert hasattr(migration, "inputs") and hasattr(migration, "product"), "Migration functions must use @migration decorator"
|
||||
if migration.product in grouped: # skip if product already exists
|
||||
continue
|
||||
|
||||
sorted_indices = sorted(ii for i in migration.inputs for ii in grouped[i])
|
||||
msg_gen = [(i, lr[i]) for i in sorted_indices]
|
||||
r_ops, a_ops, d_ops = migration(msg_gen)
|
||||
replace_ops.extend(r_ops)
|
||||
add_ops.extend(a_ops)
|
||||
del_ops.extend(d_ops)
|
||||
|
||||
for index, msg in replace_ops:
|
||||
lr[index] = msg
|
||||
for index in sorted(del_ops, reverse=True):
|
||||
del lr[index]
|
||||
for msg in add_ops:
|
||||
lr.append(msg)
|
||||
lr = sorted(lr, key=lambda x: x.logMonoTime)
|
||||
|
||||
return lr
|
||||
|
||||
|
||||
def migration(inputs: list[str], product: str|None=None):
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
wrapper.inputs = inputs
|
||||
wrapper.product = product
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
@migration(inputs=["longitudinalPlan", "carParams"])
|
||||
def migrate_longitudinalPlan(msgs):
|
||||
ops = []
|
||||
|
||||
needs_migration = all(msg.longitudinalPlan.aTarget == 0.0 for _, msg in msgs if msg.which() == 'longitudinalPlan')
|
||||
CP = next((m.carParams for _, m in msgs if m.which() == 'carParams'), None)
|
||||
if not needs_migration or CP is None:
|
||||
return [], [], []
|
||||
|
||||
for index, msg in msgs:
|
||||
if msg.which() != 'longitudinalPlan':
|
||||
continue
|
||||
new_msg = msg.as_builder()
|
||||
new_msg.longitudinalPlan.aTarget, new_msg.longitudinalPlan.shouldStop = get_accel_from_plan(CP, msg.longitudinalPlan.speeds, msg.longitudinalPlan.accels)
|
||||
ops.append((index, new_msg.as_reader()))
|
||||
return ops, [], []
|
||||
|
||||
|
||||
@migration(inputs=["longitudinalPlan"], product="driverAssistance")
|
||||
def migrate_driverAssistance(msgs):
|
||||
add_ops = []
|
||||
for _, msg in msgs:
|
||||
new_msg = messaging.new_message('driverAssistance', valid=True, logMonoTime=msg.logMonoTime)
|
||||
add_ops.append(new_msg.as_reader())
|
||||
return [], add_ops, []
|
||||
|
||||
|
||||
@migration(inputs=["modelV2"], product="drivingModelData")
|
||||
def migrate_drivingModelData(msgs):
|
||||
add_ops = []
|
||||
for _, msg in msgs:
|
||||
dmd = messaging.new_message('drivingModelData', valid=msg.valid, logMonoTime=msg.logMonoTime)
|
||||
for field in ["frameId", "frameIdExtra", "frameDropPerc", "modelExecutionTime", "action"]:
|
||||
setattr(dmd.drivingModelData, field, getattr(msg.modelV2, field))
|
||||
for meta_field in ["laneChangeState", "laneChangeState"]:
|
||||
setattr(dmd.drivingModelData.meta, meta_field, getattr(msg.modelV2.meta, meta_field))
|
||||
if len(msg.modelV2.laneLines) and len(msg.modelV2.laneLineProbs):
|
||||
fill_lane_line_meta(dmd.drivingModelData.laneLineMeta, msg.modelV2.laneLines, msg.modelV2.laneLineProbs)
|
||||
if all(len(a) for a in [msg.modelV2.position.x, msg.modelV2.position.y, msg.modelV2.position.z]):
|
||||
fill_xyz_poly(dmd.drivingModelData.path, ModelConstants.POLY_PATH_DEGREE, msg.modelV2.position.x, msg.modelV2.position.y, msg.modelV2.position.z)
|
||||
add_ops.append( dmd.as_reader())
|
||||
return [], add_ops, []
|
||||
|
||||
|
||||
@migration(inputs=["liveTracksDEPRECATED"], product="liveTracks")
|
||||
def migrate_liveTracks(msgs):
|
||||
ops = []
|
||||
for index, msg in msgs:
|
||||
new_msg = messaging.new_message('liveTracks')
|
||||
new_msg.valid = msg.valid
|
||||
new_msg.logMonoTime = msg.logMonoTime
|
||||
@@ -88,140 +159,127 @@ def migrate_liveTracks(lr):
|
||||
pts.append(pt)
|
||||
|
||||
new_msg.liveTracks.points = pts
|
||||
all_msgs.append(new_msg.as_reader())
|
||||
|
||||
return all_msgs
|
||||
ops.append((index, new_msg.as_reader()))
|
||||
return ops, [], []
|
||||
|
||||
|
||||
def migrate_liveLocationKalman(lr):
|
||||
# migration needed only for routes before livePose
|
||||
if any(msg.which() == 'livePose' for msg in lr):
|
||||
return lr
|
||||
|
||||
all_msgs = []
|
||||
for msg in lr:
|
||||
if msg.which() != 'liveLocationKalmanDEPRECATED':
|
||||
all_msgs.append(msg)
|
||||
continue
|
||||
|
||||
@migration(inputs=["liveLocationKalmanDEPRECATED"], product="livePose")
|
||||
def migrate_liveLocationKalman(msgs):
|
||||
nans = [float('nan')] * 3
|
||||
ops = []
|
||||
for index, msg in msgs:
|
||||
m = messaging.new_message('livePose')
|
||||
m.valid = msg.valid
|
||||
m.logMonoTime = msg.logMonoTime
|
||||
for field in ["orientationNED", "velocityDevice", "accelerationDevice", "angularVelocityDevice"]:
|
||||
lp_field, llk_field = getattr(m.livePose, field), getattr(msg.liveLocationKalmanDEPRECATED, field)
|
||||
lp_field.x, lp_field.y, lp_field.z = llk_field.value
|
||||
lp_field.xStd, lp_field.yStd, lp_field.zStd = llk_field.std
|
||||
lp_field.x, lp_field.y, lp_field.z = llk_field.value or nans
|
||||
lp_field.xStd, lp_field.yStd, lp_field.zStd = llk_field.std or nans
|
||||
lp_field.valid = llk_field.valid
|
||||
for flag in ["inputsOK", "posenetOK", "sensorsOK"]:
|
||||
setattr(m.livePose, flag, getattr(msg.liveLocationKalmanDEPRECATED, flag))
|
||||
|
||||
all_msgs.append(m.as_reader())
|
||||
|
||||
return all_msgs
|
||||
ops.append((index, m.as_reader()))
|
||||
return ops, [], []
|
||||
|
||||
|
||||
def migrate_controlsState(lr):
|
||||
ret = []
|
||||
@migration(inputs=["controlsState"], product="selfdriveState")
|
||||
def migrate_controlsState(msgs):
|
||||
add_ops = []
|
||||
for _, msg in msgs:
|
||||
m = messaging.new_message('selfdriveState')
|
||||
m.valid = msg.valid
|
||||
m.logMonoTime = msg.logMonoTime
|
||||
ss = m.selfdriveState
|
||||
for field in ("enabled", "active", "state", "engageable", "alertText1", "alertText2",
|
||||
"alertStatus", "alertSize", "alertType", "experimentalMode",
|
||||
"personality"):
|
||||
setattr(ss, field, getattr(msg.controlsState, field+"DEPRECATED"))
|
||||
add_ops.append(m.as_reader())
|
||||
return [], add_ops, []
|
||||
|
||||
|
||||
@migration(inputs=["carState", "controlsState"])
|
||||
def migrate_carState(msgs):
|
||||
ops = []
|
||||
last_cs = None
|
||||
for msg in lr:
|
||||
for index, msg in msgs:
|
||||
if msg.which() == 'controlsState':
|
||||
last_cs = msg
|
||||
|
||||
m = messaging.new_message('selfdriveState')
|
||||
m.valid = msg.valid
|
||||
m.logMonoTime = msg.logMonoTime
|
||||
ss = m.selfdriveState
|
||||
for field in ("enabled", "active", "state", "engageable", "alertText1", "alertText2",
|
||||
"alertStatus", "alertSize", "alertType", "experimentalMode",
|
||||
"personality"):
|
||||
setattr(ss, field, getattr(msg.controlsState, field+"DEPRECATED"))
|
||||
ret.append(m.as_reader())
|
||||
elif msg.which() == 'carState' and last_cs is not None:
|
||||
if last_cs.controlsState.vCruiseDEPRECATED - msg.carState.vCruise > 0.1:
|
||||
msg = msg.as_builder()
|
||||
msg.carState.vCruise = last_cs.controlsState.vCruiseDEPRECATED
|
||||
msg.carState.vCruiseCluster = last_cs.controlsState.vCruiseClusterDEPRECATED
|
||||
msg = msg.as_reader()
|
||||
|
||||
ret.append(msg)
|
||||
return ret
|
||||
ops.append((index, msg.as_reader()))
|
||||
return ops, [], []
|
||||
|
||||
|
||||
def migrate_managerState(lr):
|
||||
all_msgs = []
|
||||
for msg in lr:
|
||||
if msg.which() != "managerState":
|
||||
all_msgs.append(msg)
|
||||
continue
|
||||
|
||||
@migration(inputs=["managerState"])
|
||||
def migrate_managerState(msgs):
|
||||
ops = []
|
||||
for index, msg in msgs:
|
||||
new_msg = msg.as_builder()
|
||||
new_msg.managerState.processes = [{'name': name, 'running': True} for name in managed_processes]
|
||||
all_msgs.append(new_msg.as_reader())
|
||||
|
||||
return all_msgs
|
||||
ops.append((index, new_msg.as_reader()))
|
||||
return ops, [], []
|
||||
|
||||
|
||||
def migrate_gpsLocation(lr):
|
||||
all_msgs = []
|
||||
for msg in lr:
|
||||
if msg.which() in ('gpsLocation', 'gpsLocationExternal'):
|
||||
new_msg = msg.as_builder()
|
||||
g = getattr(new_msg, new_msg.which())
|
||||
# hasFix is a newer field
|
||||
if not g.hasFix and g.flags == 1:
|
||||
g.hasFix = True
|
||||
all_msgs.append(new_msg.as_reader())
|
||||
else:
|
||||
all_msgs.append(msg)
|
||||
return all_msgs
|
||||
@migration(inputs=["gpsLocation", "gpsLocationExternal"])
|
||||
def migrate_gpsLocation(msgs):
|
||||
ops = []
|
||||
for index, msg in msgs:
|
||||
new_msg = msg.as_builder()
|
||||
g = getattr(new_msg, new_msg.which())
|
||||
# hasFix is a newer field
|
||||
if not g.hasFix and g.flags == 1:
|
||||
g.hasFix = True
|
||||
ops.append((index, new_msg.as_reader()))
|
||||
return ops, [], []
|
||||
|
||||
|
||||
def migrate_deviceState(lr):
|
||||
all_msgs = []
|
||||
@migration(inputs=["deviceState", "initData"])
|
||||
def migrate_deviceState(msgs):
|
||||
ops = []
|
||||
dt = None
|
||||
for msg in lr:
|
||||
for i, msg in msgs:
|
||||
if msg.which() == 'initData':
|
||||
dt = msg.initData.deviceType
|
||||
if msg.which() == 'deviceState':
|
||||
n = msg.as_builder()
|
||||
n.deviceState.deviceType = dt
|
||||
all_msgs.append(n.as_reader())
|
||||
else:
|
||||
all_msgs.append(msg)
|
||||
return all_msgs
|
||||
ops.append((i, n.as_reader()))
|
||||
return ops, [], []
|
||||
|
||||
|
||||
def migrate_carOutput(lr):
|
||||
# migration needed only for routes before carOutput
|
||||
if any(msg.which() == 'carOutput' for msg in lr):
|
||||
return lr
|
||||
|
||||
all_msgs = []
|
||||
for msg in lr:
|
||||
if msg.which() == 'carControl':
|
||||
co = messaging.new_message('carOutput')
|
||||
co.valid = msg.valid
|
||||
co.logMonoTime = msg.logMonoTime
|
||||
co.carOutput.actuatorsOutput = msg.carControl.actuatorsOutputDEPRECATED
|
||||
all_msgs.append(co.as_reader())
|
||||
all_msgs.append(msg)
|
||||
return all_msgs
|
||||
@migration(inputs=["carControl"], product="carOutput")
|
||||
def migrate_carOutput(msgs):
|
||||
add_ops = []
|
||||
for _, msg in msgs:
|
||||
co = messaging.new_message('carOutput')
|
||||
co.valid = msg.valid
|
||||
co.logMonoTime = msg.logMonoTime
|
||||
co.carOutput.actuatorsOutput = msg.carControl.actuatorsOutputDEPRECATED
|
||||
add_ops.append(co.as_reader())
|
||||
return [], add_ops, []
|
||||
|
||||
|
||||
def migrate_pandaStates(lr):
|
||||
all_msgs = []
|
||||
@migration(inputs=["pandaStates", "pandaStateDEPRECATED", "carParams"])
|
||||
def migrate_pandaStates(msgs):
|
||||
# TODO: safety param migration should be handled automatically
|
||||
safety_param_migration = {
|
||||
"TOYOTA_PRIUS": EPS_SCALE["TOYOTA_PRIUS"] | Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL,
|
||||
"TOYOTA_RAV4": EPS_SCALE["TOYOTA_RAV4"] | Panda.FLAG_TOYOTA_ALT_BRAKE,
|
||||
"KIA_EV6": Panda.FLAG_HYUNDAI_EV_GAS | Panda.FLAG_HYUNDAI_CANFD_HDA2,
|
||||
}
|
||||
# TODO: get new Ford route
|
||||
safety_param_migration |= {car: Panda.FLAG_FORD_LONG_CONTROL for car in (set(FORD) - FORD.with_flags(FordFlags.CANFD))}
|
||||
|
||||
# Migrate safety param base on carState
|
||||
CP = next((m.carParams for m in lr if m.which() == 'carParams'), None)
|
||||
# Migrate safety param base on carParams
|
||||
CP = next((m.carParams for _, m in msgs if m.which() == 'carParams'), None)
|
||||
assert CP is not None, "carParams message not found"
|
||||
if CP.carFingerprint in safety_param_migration:
|
||||
safety_param = safety_param_migration[CP.carFingerprint]
|
||||
fingerprint = MIGRATION.get(CP.carFingerprint, CP.carFingerprint)
|
||||
if fingerprint in safety_param_migration:
|
||||
safety_param = safety_param_migration[fingerprint]
|
||||
elif len(CP.safetyConfigs):
|
||||
safety_param = CP.safetyConfigs[0].safetyParam
|
||||
if CP.safetyConfigs[0].safetyParamDEPRECATED != 0:
|
||||
@@ -229,49 +287,45 @@ def migrate_pandaStates(lr):
|
||||
else:
|
||||
safety_param = CP.safetyParamDEPRECATED
|
||||
|
||||
for msg in lr:
|
||||
ops = []
|
||||
for index, msg in msgs:
|
||||
if msg.which() == 'pandaStateDEPRECATED':
|
||||
new_msg = messaging.new_message('pandaStates', 1)
|
||||
new_msg.valid = msg.valid
|
||||
new_msg.logMonoTime = msg.logMonoTime
|
||||
new_msg.pandaStates[0] = msg.pandaStateDEPRECATED
|
||||
new_msg.pandaStates[0].safetyParam = safety_param
|
||||
all_msgs.append(new_msg.as_reader())
|
||||
ops.append((index, new_msg.as_reader()))
|
||||
elif msg.which() == 'pandaStates':
|
||||
new_msg = msg.as_builder()
|
||||
new_msg.pandaStates[-1].safetyParam = safety_param
|
||||
all_msgs.append(new_msg.as_reader())
|
||||
else:
|
||||
all_msgs.append(msg)
|
||||
|
||||
return all_msgs
|
||||
ops.append((index, new_msg.as_reader()))
|
||||
return ops, [], []
|
||||
|
||||
|
||||
def migrate_peripheralState(lr):
|
||||
if any(msg.which() == "peripheralState" for msg in lr):
|
||||
return lr
|
||||
@migration(inputs=["pandaStates", "pandaStateDEPRECATED"], product="peripheralState")
|
||||
def migrate_peripheralState(msgs):
|
||||
add_ops = []
|
||||
|
||||
all_msg = []
|
||||
for msg in lr:
|
||||
all_msg.append(msg)
|
||||
if msg.which() not in ["pandaStates", "pandaStateDEPRECATED"]:
|
||||
which = "pandaStates" if any(msg.which() == "pandaStates" for _, msg in msgs) else "pandaStateDEPRECATED"
|
||||
for _, msg in msgs:
|
||||
if msg.which() != which:
|
||||
continue
|
||||
|
||||
new_msg = messaging.new_message("peripheralState")
|
||||
new_msg.valid = msg.valid
|
||||
new_msg.logMonoTime = msg.logMonoTime
|
||||
all_msg.append(new_msg.as_reader())
|
||||
|
||||
return all_msg
|
||||
add_ops.append(new_msg.as_reader())
|
||||
return [], add_ops, []
|
||||
|
||||
|
||||
def migrate_cameraStates(lr):
|
||||
all_msgs = []
|
||||
@migration(inputs=["roadEncodeIdx", "wideRoadEncodeIdx", "driverEncodeIdx", "roadCameraState", "wideRoadCameraState", "driverCameraState"])
|
||||
def migrate_cameraStates(msgs):
|
||||
add_ops, del_ops = [], []
|
||||
frame_to_encode_id = defaultdict(dict)
|
||||
# just for encodeId fallback mechanism
|
||||
min_frame_id = defaultdict(lambda: float('inf'))
|
||||
|
||||
for msg in lr:
|
||||
for _, msg in msgs:
|
||||
if msg.which() not in ["roadEncodeIdx", "wideRoadEncodeIdx", "driverEncodeIdx"]:
|
||||
continue
|
||||
|
||||
@@ -281,9 +335,8 @@ def migrate_cameraStates(lr):
|
||||
assert encode_index.segmentId < 1200, f"Encoder index segmentId greater that 1200: {msg.which()} {encode_index.segmentId}"
|
||||
frame_to_encode_id[meta.camera_state][encode_index.frameId] = encode_index.segmentId
|
||||
|
||||
for msg in lr:
|
||||
for index, msg in msgs:
|
||||
if msg.which() not in ["roadCameraState", "wideRoadCameraState", "driverCameraState"]:
|
||||
all_msgs.append(msg)
|
||||
continue
|
||||
|
||||
camera_state = getattr(msg, msg.which())
|
||||
@@ -293,6 +346,7 @@ def migrate_cameraStates(lr):
|
||||
if encode_id is None:
|
||||
print(f"Missing encoded frame for camera feed {msg.which()} with frameId: {camera_state.frameId}")
|
||||
if len(frame_to_encode_id[msg.which()]) != 0:
|
||||
del_ops.append(index)
|
||||
continue
|
||||
|
||||
# fallback mechanism for logs without encodeIdx (e.g. logs from before 2022 with dcamera recording disabled)
|
||||
@@ -313,33 +367,27 @@ def migrate_cameraStates(lr):
|
||||
new_msg.logMonoTime = msg.logMonoTime
|
||||
new_msg.valid = msg.valid
|
||||
|
||||
all_msgs.append(new_msg.as_reader())
|
||||
|
||||
return all_msgs
|
||||
del_ops.append(index)
|
||||
add_ops.append(new_msg.as_reader())
|
||||
return [], add_ops, del_ops
|
||||
|
||||
|
||||
def migrate_carParams(lr):
|
||||
all_msgs = []
|
||||
for msg in lr:
|
||||
if msg.which() == 'carParams':
|
||||
CP = msg.as_builder()
|
||||
CP.carParams.carFingerprint = MIGRATION.get(CP.carParams.carFingerprint, CP.carParams.carFingerprint)
|
||||
for car_fw in CP.carParams.carFw:
|
||||
car_fw.brand = CP.carParams.carName
|
||||
CP.logMonoTime = msg.logMonoTime
|
||||
msg = CP.as_reader()
|
||||
all_msgs.append(msg)
|
||||
|
||||
return all_msgs
|
||||
@migration(inputs=["carParams"])
|
||||
def migrate_carParams(msgs):
|
||||
ops = []
|
||||
for index, msg in msgs:
|
||||
CP = msg.as_builder()
|
||||
CP.carParams.carFingerprint = MIGRATION.get(CP.carParams.carFingerprint, CP.carParams.carFingerprint)
|
||||
for car_fw in CP.carParams.carFw:
|
||||
car_fw.brand = CP.carParams.carName
|
||||
ops.append((index, CP.as_reader()))
|
||||
return ops, [], []
|
||||
|
||||
|
||||
def migrate_sensorEvents(lr):
|
||||
all_msgs = []
|
||||
for msg in lr:
|
||||
if msg.which() != 'sensorEventsDEPRECATED':
|
||||
all_msgs.append(msg)
|
||||
continue
|
||||
|
||||
@migration(inputs=["sensorEventsDEPRECATED"], product="sensorEvents")
|
||||
def migrate_sensorEvents(msgs):
|
||||
add_ops, del_ops = [], []
|
||||
for index, msg in msgs:
|
||||
# migrate to split sensor events
|
||||
for evt in msg.sensorEventsDEPRECATED:
|
||||
# build new message for each sensor type
|
||||
@@ -367,6 +415,36 @@ def migrate_sensorEvents(lr):
|
||||
m_dat.timestamp = evt.timestamp
|
||||
setattr(m_dat, evt.which(), getattr(evt, evt.which()))
|
||||
|
||||
all_msgs.append(m.as_reader())
|
||||
add_ops.append(m.as_reader())
|
||||
del_ops.append(index)
|
||||
return [], add_ops, del_ops
|
||||
|
||||
return all_msgs
|
||||
|
||||
@migration(inputs=["onroadEventsDEPRECATED"], product="onroadEvents")
|
||||
def migrate_onroadEvents(msgs):
|
||||
ops = []
|
||||
for index, msg in msgs:
|
||||
new_msg = messaging.new_message('onroadEvents', len(msg.onroadEventsDEPRECATED))
|
||||
new_msg.valid = msg.valid
|
||||
new_msg.logMonoTime = msg.logMonoTime
|
||||
|
||||
# dict converts name enum into string representation
|
||||
new_msg.onroadEvents = [log.OnroadEvent(**event.to_dict()) for event in msg.onroadEventsDEPRECATED if
|
||||
not str(event.name).endswith('DEPRECATED')]
|
||||
ops.append((index, new_msg.as_reader()))
|
||||
|
||||
return ops, [], []
|
||||
|
||||
|
||||
@migration(inputs=["driverMonitoringState"])
|
||||
def migrate_driverMonitoringState(msgs):
|
||||
ops = []
|
||||
for index, msg in msgs:
|
||||
msg = msg.as_builder()
|
||||
# dict converts name enum into string representation
|
||||
msg.driverMonitoringState.events = [log.OnroadEvent(**event.to_dict()) for event in
|
||||
msg.driverMonitoringState.eventsDEPRECATED if
|
||||
not str(event.name).endswith('DEPRECATED')]
|
||||
ops.append((index, msg.as_reader()))
|
||||
|
||||
return ops, [], []
|
||||
|
||||
@@ -3,14 +3,19 @@ import os
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from typing import Any
|
||||
import tempfile
|
||||
from itertools import zip_longest
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from openpilot.common.git import get_commit
|
||||
from openpilot.system.hardware import PC
|
||||
from openpilot.tools.lib.openpilotci import BASE_URL, get_url
|
||||
from openpilot.tools.lib.openpilotci import get_url
|
||||
from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_diff
|
||||
from openpilot.selfdrive.test.process_replay.process_replay import get_process_config, replay_process
|
||||
from openpilot.tools.lib.framereader import FrameReader
|
||||
from openpilot.tools.lib.logreader import LogReader, save_log
|
||||
from openpilot.tools.lib.github_utils import GithubUtils
|
||||
|
||||
TEST_ROUTE = "2f4452b03ccb98f0|2022-12-03--13-45-30"
|
||||
SEGMENT = 6
|
||||
@@ -19,10 +24,93 @@ MAX_FRAMES = 100 if PC else 600
|
||||
NO_MODEL = "NO_MODEL" in os.environ
|
||||
SEND_EXTRA_INPUTS = bool(int(os.getenv("SEND_EXTRA_INPUTS", "0")))
|
||||
|
||||
DATA_TOKEN = os.getenv("CI_ARTIFACTS_TOKEN","")
|
||||
API_TOKEN = os.getenv("GITHUB_COMMENTS_TOKEN","")
|
||||
MODEL_REPLAY_BUCKET="model_replay_master"
|
||||
GITHUB = GithubUtils(API_TOKEN, DATA_TOKEN)
|
||||
|
||||
def get_log_fn(ref_commit, test_route):
|
||||
return f"{test_route}_model_tici_{ref_commit}.bz2"
|
||||
|
||||
def get_log_fn(test_route, ref="master"):
|
||||
return f"{test_route}_model_tici_{ref}.bz2"
|
||||
|
||||
def plot(proposed, master, title, tmp):
|
||||
proposed = list(proposed)
|
||||
master = list(master)
|
||||
fig, ax = plt.subplots()
|
||||
ax.plot(proposed, label='PROPOSED')
|
||||
ax.plot(master, label='MASTER')
|
||||
plt.legend(loc='best')
|
||||
plt.title(title)
|
||||
plt.savefig(f'{tmp}/{title}.png')
|
||||
return (title + '.png', proposed == master)
|
||||
|
||||
def get_event(logs, event):
|
||||
return (getattr(m, m.which()) for m in filter(lambda m: m.which() == event, logs))
|
||||
|
||||
def zl(array, fill):
|
||||
return zip_longest(array, [], fillvalue=fill)
|
||||
|
||||
def generate_report(proposed, master, tmp, commit):
|
||||
ModelV2_Plots = zl([
|
||||
(lambda x: x.velocity.x[0], "velocity.x"),
|
||||
(lambda x: x.action.desiredCurvature, "desiredCurvature"),
|
||||
(lambda x: x.leadsV3[0].x[0], "leadsV3.x"),
|
||||
(lambda x: x.laneLines[1].y[0], "laneLines.y"),
|
||||
(lambda x: x.meta.disengagePredictions.gasPressProbs[1], "gasPressProbs")
|
||||
], "modelV2")
|
||||
DriverStateV2_Plots = zl([
|
||||
(lambda x: x.wheelOnRightProb, "wheelOnRightProb"),
|
||||
(lambda x: x.leftDriverData.faceProb, "leftDriverData.faceProb"),
|
||||
(lambda x: x.leftDriverData.faceOrientation[0], "leftDriverData.faceOrientation0"),
|
||||
(lambda x: x.leftDriverData.leftBlinkProb, "leftDriverData.leftBlinkProb"),
|
||||
(lambda x: x.leftDriverData.notReadyProb[0], "leftDriverData.notReadyProb0"),
|
||||
(lambda x: x.rightDriverData.faceProb, "rightDriverData.faceProb"),
|
||||
], "driverStateV2")
|
||||
|
||||
return [plot(map(v[0], get_event(proposed, event)), \
|
||||
map(v[0], get_event(master, event)), f"{v[1]}_{commit[:7]}", tmp) \
|
||||
for v,event in ([*ModelV2_Plots] + [*DriverStateV2_Plots])]
|
||||
|
||||
def create_table(title, files, link, open_table=False):
|
||||
if not files:
|
||||
return ""
|
||||
table = [f'<details {"open" if open_table else ""}><summary>{title}</summary><table>']
|
||||
for i,f in enumerate(files):
|
||||
if not (i % 2):
|
||||
table.append("<tr>")
|
||||
table.append(f'<td><img src=\\"{link}/{f[0]}\\"></td>')
|
||||
if (i % 2):
|
||||
table.append("</tr>")
|
||||
table.append("</table></details>")
|
||||
table = "".join(table)
|
||||
return table
|
||||
|
||||
def comment_replay_report(proposed, master, full_logs):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
PR_BRANCH = os.getenv("GIT_BRANCH","")
|
||||
DATA_BUCKET = f"model_replay_{PR_BRANCH}"
|
||||
|
||||
try:
|
||||
GITHUB.get_pr_number(PR_BRANCH)
|
||||
except Exception:
|
||||
print("No PR associated with this branch. Skipping report.")
|
||||
return
|
||||
|
||||
commit = get_commit()
|
||||
files = generate_report(proposed, master, tmp, commit)
|
||||
|
||||
GITHUB.upload_files(DATA_BUCKET, [(x[0], tmp + '/' + x[0]) for x in files])
|
||||
|
||||
log_name = get_log_fn(TEST_ROUTE, commit)
|
||||
save_log(log_name, full_logs)
|
||||
GITHUB.upload_file(DATA_BUCKET, os.path.basename(log_name), log_name)
|
||||
|
||||
diff_files = [x for x in files if not x[1]]
|
||||
link = GITHUB.get_bucket_link(DATA_BUCKET)
|
||||
diff_plots = create_table("Model Replay Differences", diff_files, link, open_table=True)
|
||||
all_plots = create_table("All Model Replay Plots", files, link)
|
||||
comment = f"ref for commit {commit}: {link}/{log_name}" + diff_plots + all_plots
|
||||
GITHUB.comment_on_pr(comment, PR_BRANCH)
|
||||
|
||||
def trim_logs_to_max_frames(logs, max_frames, frs_types, include_all_types):
|
||||
all_msgs = []
|
||||
@@ -68,9 +156,8 @@ def model_replay(lr, frs):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
update = "--update" in sys.argv
|
||||
update = "--update" in sys.argv or (os.getenv("GIT_BRANCH", "") == 'master')
|
||||
replay_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
ref_commit_fn = os.path.join(replay_dir, "model_replay_ref_commit")
|
||||
|
||||
# load logs
|
||||
lr = list(LogReader(get_url(TEST_ROUTE, SEGMENT, "rlog.bz2")))
|
||||
@@ -88,11 +175,9 @@ if __name__ == "__main__":
|
||||
# get diff
|
||||
failed = False
|
||||
if not update:
|
||||
with open(ref_commit_fn) as f:
|
||||
ref_commit = f.read().strip()
|
||||
log_fn = get_log_fn(ref_commit, TEST_ROUTE)
|
||||
log_fn = get_log_fn(TEST_ROUTE)
|
||||
try:
|
||||
all_logs = list(LogReader(BASE_URL + log_fn))
|
||||
all_logs = list(LogReader(GITHUB.get_file_url(MODEL_REPLAY_BUCKET, log_fn)))
|
||||
cmp_log = []
|
||||
|
||||
# logs are ordered based on type: modelV2, drivingModelData, driverStateV2
|
||||
@@ -134,11 +219,13 @@ if __name__ == "__main__":
|
||||
ignore.append(f'modelV2.roadEdges.{i}.{field}')
|
||||
tolerance = .3 if PC else None
|
||||
results: Any = {TEST_ROUTE: {}}
|
||||
log_paths: Any = {TEST_ROUTE: {"models": {'ref': BASE_URL + log_fn, 'new': log_fn}}}
|
||||
log_paths: Any = {TEST_ROUTE: {"models": {'ref': log_fn, 'new': log_fn}}}
|
||||
results[TEST_ROUTE]["models"] = compare_logs(cmp_log, log_msgs, tolerance=tolerance, ignore_fields=ignore)
|
||||
diff_short, diff_long, failed = format_diff(results, log_paths, ref_commit)
|
||||
diff_short, diff_long, failed = format_diff(results, log_paths, 'master')
|
||||
|
||||
if "CI" in os.environ:
|
||||
comment_replay_report(log_msgs, cmp_log, log_msgs)
|
||||
failed = False
|
||||
print(diff_long)
|
||||
print('-------------\n'*5)
|
||||
print(diff_short)
|
||||
@@ -149,22 +236,13 @@ if __name__ == "__main__":
|
||||
failed = True
|
||||
|
||||
# upload new refs
|
||||
if (update or failed) and not PC:
|
||||
from openpilot.tools.lib.openpilotci import upload_file
|
||||
|
||||
if update and not PC:
|
||||
print("Uploading new refs")
|
||||
|
||||
new_commit = get_commit()
|
||||
log_fn = get_log_fn(new_commit, TEST_ROUTE)
|
||||
log_fn = get_log_fn(TEST_ROUTE)
|
||||
save_log(log_fn, log_msgs)
|
||||
try:
|
||||
upload_file(log_fn, os.path.basename(log_fn))
|
||||
GITHUB.upload_file(MODEL_REPLAY_BUCKET, os.path.basename(log_fn), log_fn)
|
||||
except Exception as e:
|
||||
print("failed to upload", e)
|
||||
|
||||
with open(ref_commit_fn, 'w') as f:
|
||||
f.write(str(new_commit))
|
||||
|
||||
print("\n\nNew ref commit: ", new_commit)
|
||||
|
||||
sys.exit(int(failed))
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
05b1cb87e32f280e46e0f45bbd6d76d5fd3f57a7
|
||||
@@ -17,14 +17,13 @@ import cereal.messaging as messaging
|
||||
from cereal import car
|
||||
from cereal.services import SERVICE_LIST
|
||||
from msgq.visionipc import VisionIpcServer, get_endpoint_name as vipc_get_endpoint_name
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.car_helpers import get_car, interfaces
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.common.timeout import Timeout
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from panda.python import ALTERNATIVE_EXPERIENCE
|
||||
from openpilot.selfdrive.car.card import can_comm_callbacks, convert_to_capnp
|
||||
from openpilot.selfdrive.car.card import can_comm_callbacks
|
||||
from openpilot.system.manager.process_config import managed_processes
|
||||
from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams
|
||||
from openpilot.selfdrive.test.process_replay.migration import migrate_all
|
||||
@@ -213,7 +212,7 @@ class ProcessContainer:
|
||||
for meta in streams_metas:
|
||||
if meta.camera_state in self.cfg.vision_pubs:
|
||||
frame_size = (frs[meta.camera_state].w, frs[meta.camera_state].h)
|
||||
vipc_server.create_buffers(meta.stream, 2, False, *frame_size)
|
||||
vipc_server.create_buffers(meta.stream, 2, *frame_size)
|
||||
vipc_server.start_listener()
|
||||
|
||||
self.vipc_server = vipc_server
|
||||
@@ -363,14 +362,14 @@ def get_car_params_callback(rc, pm, msgs, fingerprint):
|
||||
cached_params = None
|
||||
if has_cached_cp:
|
||||
with car.CarParams.from_bytes(cached_params_raw) as _cached_params:
|
||||
cached_params = structs.CarParams(carName=_cached_params.carName, carFw=_cached_params.carFw, carVin=_cached_params.carVin)
|
||||
cached_params = _cached_params
|
||||
|
||||
CP = get_car(*can_callbacks, lambda obd: None, Params().get_bool("ExperimentalLongitudinalEnabled"), cached_params=cached_params).CP
|
||||
|
||||
if not params.get_bool("DisengageOnAccelerator"):
|
||||
CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
|
||||
|
||||
params.put("CarParams", convert_to_capnp(CP).to_bytes())
|
||||
params.put("CarParams", CP.to_bytes())
|
||||
|
||||
|
||||
def selfdrived_rcv_callback(msg, cfg, frame):
|
||||
@@ -467,7 +466,7 @@ CONFIGS = [
|
||||
"longitudinalPlan", "livePose", "liveParameters", "radarState",
|
||||
"modelV2", "driverCameraState", "roadCameraState", "wideRoadCameraState", "managerState",
|
||||
"liveTorqueParameters", "accelerometer", "gyroscope", "carOutput",
|
||||
"gpsLocationExternal", "gpsLocation", "controlsState", "carControl", "driverAssistance",
|
||||
"gpsLocationExternal", "gpsLocation", "controlsState", "carControl", "driverAssistance", "alertDebug",
|
||||
],
|
||||
subs=["selfdriveState", "onroadEvents"],
|
||||
ignore=["logMonoTime"],
|
||||
@@ -509,7 +508,7 @@ CONFIGS = [
|
||||
),
|
||||
ProcessConfig(
|
||||
proc_name="plannerd",
|
||||
pubs=["modelV2", "carControl", "carState", "controlsState", "radarState", "selfdriveState"],
|
||||
pubs=["modelV2", "carControl", "carState", "controlsState", "liveParameters", "radarState", "selfdriveState"],
|
||||
subs=["longitudinalPlan", "driverAssistance"],
|
||||
ignore=["logMonoTime", "longitudinalPlan.processingDelay", "longitudinalPlan.solverExecutionTime"],
|
||||
init_callback=get_car_params_callback,
|
||||
|
||||
@@ -1 +1 @@
|
||||
d8a02c23f530dd236553b47ff7f0355929fe15fc
|
||||
dd0a40182707975c94a40d19198cd60c88735199
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import copy
|
||||
import os
|
||||
from hypothesis import given, HealthCheck, Phase, settings
|
||||
import hypothesis.strategies as st
|
||||
from parameterized import parameterized
|
||||
@@ -14,13 +15,15 @@ import openpilot.selfdrive.test.process_replay.process_replay as pr
|
||||
NOT_TESTED = ['selfdrived', 'controlsd', 'card', 'plannerd', 'calibrationd', 'dmonitoringd', 'paramsd', 'dmonitoringmodeld', 'modeld']
|
||||
|
||||
TEST_CASES = [(cfg.proc_name, copy.deepcopy(cfg)) for cfg in pr.CONFIGS if cfg.proc_name not in NOT_TESTED]
|
||||
MAX_EXAMPLES = int(os.environ.get("MAX_EXAMPLES", "10"))
|
||||
|
||||
class TestFuzzProcesses:
|
||||
|
||||
# TODO: make this faster and increase examples
|
||||
@parameterized.expand(TEST_CASES)
|
||||
@given(st.data())
|
||||
@settings(phases=[Phase.generate, Phase.target], max_examples=10, deadline=1000, suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large])
|
||||
@settings(phases=[Phase.generate, Phase.target], max_examples=MAX_EXAMPLES, deadline=1000,
|
||||
suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large])
|
||||
def test_fuzz_process(self, proc_name, cfg, data):
|
||||
msgs = FuzzyGenerator.get_random_event_msg(data.draw, events=cfg.pubs, real_floats=True)
|
||||
lr = [log.Event.new_message(**m).as_reader() for m in msgs]
|
||||
|
||||
@@ -42,7 +42,7 @@ source_segments = [
|
||||
segments = [
|
||||
("BODY", "regenA67A128BCD8|2024-08-30--02-36-22--0"),
|
||||
("HYUNDAI", "regen9CBD921E93E|2024-08-30--02-38-51--0"),
|
||||
("HYUNDAI2", "regen12E0C4EA1A7|2024-08-30--02-42-40--0"),
|
||||
("HYUNDAI2", "regen306779F6870|2024-10-03--04-03-23--0"),
|
||||
("TOYOTA", "regen1CA7A48E6F7|2024-08-30--02-45-08--0"),
|
||||
("TOYOTA2", "regen6E484EDAB96|2024-08-30--02-47-37--0"),
|
||||
("TOYOTA3", "regen4CE950B0267|2024-08-30--02-51-30--0"),
|
||||
@@ -56,11 +56,11 @@ segments = [
|
||||
("NISSAN", "regen58464878D07|2024-08-30--03-15-31--0"),
|
||||
("VOLKSWAGEN", "regenED976DEB757|2024-08-30--03-18-02--0"),
|
||||
("MAZDA", "regenACF84CCF482|2024-08-30--03-21-55--0"),
|
||||
("FORD", "regen6ECC59A6307|2024-08-30--03-25-42--0"),
|
||||
("FORD", "regen756F8230C21|2024-11-07--00-08-24--0"),
|
||||
]
|
||||
|
||||
# dashcamOnly makes don't need to be tested until a full port is done
|
||||
excluded_interfaces = ["mock"]
|
||||
excluded_interfaces = ["mock", "tesla"]
|
||||
|
||||
BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/"
|
||||
REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit")
|
||||
@@ -135,7 +135,7 @@ if __name__ == "__main__":
|
||||
parser.add_argument("--blacklist-cars", type=str, nargs="*", default=[],
|
||||
help="Blacklist given cars from the test (e.g. HONDA)")
|
||||
parser.add_argument("--ignore-fields", type=str, nargs="*", default=[],
|
||||
help="Extra fields or msgs to ignore (e.g. carState.events)")
|
||||
help="Extra fields or msgs to ignore (e.g. driverMonitoringState.events)")
|
||||
parser.add_argument("--ignore-msgs", type=str, nargs="*", default=[],
|
||||
help="Msgs to ignore (e.g. carEvents)")
|
||||
parser.add_argument("--update-refs", action="store_true",
|
||||
@@ -193,6 +193,10 @@ if __name__ == "__main__":
|
||||
if cfg.proc_name not in tested_procs:
|
||||
continue
|
||||
|
||||
# to speed things up, we only test all segments on card
|
||||
if cfg.proc_name != 'card' and car_brand not in ('HYUNDAI', 'TOYOTA', 'HONDA', 'SUBARU', 'FORD'):
|
||||
continue
|
||||
|
||||
cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{cur_commit}.zst")
|
||||
if args.update_refs: # reference logs will not exist if routes were just regenerated
|
||||
ref_log_path = get_url(*segment.rsplit("--", 1,), "rlog.zst")
|
||||
|
||||
@@ -86,7 +86,7 @@ safe_checkout() {
|
||||
rsync -a --delete $SOURCE_DIR $TEST_DIR
|
||||
}
|
||||
|
||||
unsafe_checkout() {
|
||||
unsafe_checkout() {( set -e
|
||||
# checkout directly in test dir, leave old build products
|
||||
|
||||
cd $TEST_DIR
|
||||
@@ -105,7 +105,7 @@ unsafe_checkout() {
|
||||
|
||||
git lfs pull
|
||||
(ulimit -n 65535 && git lfs prune)
|
||||
}
|
||||
)}
|
||||
|
||||
export GIT_PACK_THREADS=8
|
||||
|
||||
@@ -115,8 +115,13 @@ if [ ! -d "$SOURCE_DIR" ]; then
|
||||
fi
|
||||
|
||||
if [ ! -z "$UNSAFE" ]; then
|
||||
echo "doing unsafe checkout"
|
||||
echo "trying unsafe checkout"
|
||||
set +e
|
||||
unsafe_checkout
|
||||
if [[ "$?" -ne 0 ]]; then
|
||||
safe_checkout
|
||||
fi
|
||||
set -e
|
||||
else
|
||||
echo "doing safe checkout"
|
||||
safe_checkout
|
||||
|
||||
+95
-102
@@ -10,10 +10,10 @@ import time
|
||||
import numpy as np
|
||||
import zstandard as zstd
|
||||
from collections import Counter, defaultdict
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from tabulate import tabulate
|
||||
|
||||
from cereal import car
|
||||
from cereal import car, log
|
||||
import cereal.messaging as messaging
|
||||
from cereal.services import SERVICE_LIST
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
@@ -33,12 +33,15 @@ CPU usage budget
|
||||
should not exceed MAX_TOTAL_CPU
|
||||
"""
|
||||
|
||||
TEST_DURATION = 25
|
||||
LOG_OFFSET = 8
|
||||
|
||||
MAX_TOTAL_CPU = 265. # total for all 8 cores
|
||||
PROCS = {
|
||||
# Baseline CPU usage by process
|
||||
"selfdrive.controls.controlsd": 16.0,
|
||||
"selfdrive.selfdrived.selfdrived": 16.0,
|
||||
"selfdrive.car.card": 30.0,
|
||||
"selfdrive.car.card": 26.0,
|
||||
"./loggerd": 14.0,
|
||||
"./encoderd": 17.0,
|
||||
"./camerad": 14.5,
|
||||
@@ -49,28 +52,28 @@ PROCS = {
|
||||
"selfdrive.controls.radard": 2.0,
|
||||
"selfdrive.modeld.modeld": 17.0,
|
||||
"selfdrive.modeld.dmonitoringmodeld": 11.0,
|
||||
"system.hardware.hardwared": 3.87,
|
||||
"system.hardware.hardwared": 4.0,
|
||||
"selfdrive.locationd.calibrationd": 2.0,
|
||||
"selfdrive.locationd.torqued": 5.0,
|
||||
"selfdrive.locationd.locationd": 25.0,
|
||||
"selfdrive.ui.soundd": 3.38,
|
||||
"selfdrive.ui.soundd": 3.0,
|
||||
"selfdrive.monitoring.dmonitoringd": 4.0,
|
||||
"./proclogd": 1.54,
|
||||
"system.logmessaged": 0.2,
|
||||
"./proclogd": 2.0,
|
||||
"system.logmessaged": 1.0,
|
||||
"system.tombstoned": 0,
|
||||
"./logcatd": 0,
|
||||
"./logcatd": 1.0,
|
||||
"system.micd": 5.0,
|
||||
"system.timed": 0,
|
||||
"selfdrive.pandad.pandad": 0,
|
||||
"system.statsd": 0.4,
|
||||
"system.loggerd.uploader": (0.5, 15.0),
|
||||
"system.loggerd.deleter": 0.1,
|
||||
"system.statsd": 1.0,
|
||||
"system.loggerd.uploader": 15.0,
|
||||
"system.loggerd.deleter": 1.0,
|
||||
}
|
||||
|
||||
PROCS.update({
|
||||
"tici": {
|
||||
"./pandad": 4.0,
|
||||
"./ubloxd": 0.02,
|
||||
"./ubloxd": 1.0,
|
||||
"system.ubloxd.pigeond": 6.0,
|
||||
},
|
||||
"tizi": {
|
||||
@@ -98,6 +101,13 @@ TIMINGS = {
|
||||
"wideRoadCameraState": [1.5, 0.35],
|
||||
}
|
||||
|
||||
LOGS_SIZE_RATE = {
|
||||
"qlog": 0.0083,
|
||||
"rlog": 0.1528,
|
||||
"qcamera.ts": 0.03828,
|
||||
}
|
||||
LOGS_SIZE_RATE.update(dict.fromkeys(['ecamera.hevc', 'fcamera.hevc'], 1.2740))
|
||||
|
||||
|
||||
def cputime_total(ct):
|
||||
return ct.cpuUser + ct.cpuSystem + ct.cpuChildrenUser + ct.cpuChildrenSystem
|
||||
@@ -124,7 +134,7 @@ class TestOnroad:
|
||||
if os.path.exists(Paths.log_root()):
|
||||
shutil.rmtree(Paths.log_root())
|
||||
|
||||
# start manager and run openpilot for a minute
|
||||
# start manager and run openpilot for TEST_DURATION
|
||||
proc = None
|
||||
try:
|
||||
manager_path = os.path.join(BASEDIR, "system/manager/manager.py")
|
||||
@@ -135,23 +145,24 @@ class TestOnroad:
|
||||
while sm.recv_frame['carState'] < 0:
|
||||
sm.update(1000)
|
||||
|
||||
# make sure we get at least two full segments
|
||||
route = None
|
||||
cls.segments = []
|
||||
with Timeout(300, "timed out waiting for logs"):
|
||||
while route is None:
|
||||
route = params.get("CurrentRoute", encoding="utf-8")
|
||||
time.sleep(0.1)
|
||||
time.sleep(0.01)
|
||||
|
||||
while len(cls.segments) < 3:
|
||||
# test car params caching
|
||||
params.put("CarParamsCache", car.CarParams().to_bytes())
|
||||
|
||||
while len(cls.segments) < 1:
|
||||
segs = set()
|
||||
if Path(Paths.log_root()).exists():
|
||||
segs = set(Path(Paths.log_root()).glob(f"{route}--*"))
|
||||
cls.segments = sorted(segs, key=lambda s: int(str(s).rsplit('--')[-1]))
|
||||
time.sleep(2)
|
||||
time.sleep(0.01)
|
||||
|
||||
# chop off last, incomplete segment
|
||||
cls.segments = cls.segments[:-1]
|
||||
time.sleep(TEST_DURATION)
|
||||
|
||||
finally:
|
||||
cls.gpu_procs = {psutil.Process(int(f.name)).name() for f in pathlib.Path('/sys/devices/virtual/kgsl/kgsl/proc/').iterdir() if f.is_dir()}
|
||||
@@ -163,9 +174,8 @@ class TestOnroad:
|
||||
|
||||
cls.lrs = [list(LogReader(os.path.join(str(s), "rlog"))) for s in cls.segments]
|
||||
|
||||
# use the second segment by default as it's the first full segment
|
||||
cls.lr = list(LogReader(os.path.join(str(cls.segments[1]), "rlog")))
|
||||
cls.log_path = cls.segments[1]
|
||||
cls.lr = list(LogReader(os.path.join(str(cls.segments[0]), "rlog")))
|
||||
cls.log_path = cls.segments[0]
|
||||
|
||||
cls.log_sizes = {}
|
||||
for f in cls.log_path.iterdir():
|
||||
@@ -175,16 +185,13 @@ class TestOnroad:
|
||||
with open(f, 'rb') as ff:
|
||||
cls.log_sizes[f] = len(zstd.compress(ff.read(), LOG_COMPRESSION_LEVEL)) / 1e6
|
||||
|
||||
cls.msgs = defaultdict(list)
|
||||
for m in cls.lr:
|
||||
cls.msgs[m.which()].append(m)
|
||||
|
||||
@cached_property
|
||||
def service_msgs(self):
|
||||
msgs = defaultdict(list)
|
||||
for m in self.lr:
|
||||
msgs[m.which()].append(m)
|
||||
return msgs
|
||||
|
||||
def test_service_frequencies(self, subtests):
|
||||
for s, msgs in self.service_msgs.items():
|
||||
for s, msgs in self.msgs.items():
|
||||
if s in ('initData', 'sentinel'):
|
||||
continue
|
||||
|
||||
@@ -193,10 +200,10 @@ class TestOnroad:
|
||||
continue
|
||||
|
||||
with subtests.test(service=s):
|
||||
assert len(msgs) >= math.floor(SERVICE_LIST[s].frequency*55)
|
||||
assert len(msgs) >= math.floor(SERVICE_LIST[s].frequency*int(TEST_DURATION*0.8))
|
||||
|
||||
def test_cloudlog_size(self):
|
||||
msgs = [m for m in self.lr if m.which() == 'logMessage']
|
||||
msgs = self.msgs['logMessage']
|
||||
|
||||
total_size = sum(len(m.as_builder().to_bytes()) for m in msgs)
|
||||
assert total_size < 3.5e5
|
||||
@@ -207,16 +214,10 @@ class TestOnroad:
|
||||
|
||||
def test_log_sizes(self):
|
||||
for f, sz in self.log_sizes.items():
|
||||
if f.name == "qcamera.ts":
|
||||
assert 2.15 < sz < 2.35
|
||||
elif f.name == "qlog":
|
||||
assert 0.4 < sz < 0.55
|
||||
elif f.name == "rlog":
|
||||
assert 5 < sz < 50
|
||||
elif f.name.endswith('.hevc'):
|
||||
assert 70 < sz < 77
|
||||
else:
|
||||
raise NotImplementedError
|
||||
rate = LOGS_SIZE_RATE[f.name]
|
||||
minn = rate * TEST_DURATION * 0.8
|
||||
maxx = rate * TEST_DURATION * 1.2
|
||||
assert minn < sz < maxx
|
||||
|
||||
def test_ui_timings(self):
|
||||
result = "\n"
|
||||
@@ -224,7 +225,7 @@ class TestOnroad:
|
||||
result += "-------------- UI Draw Timing ------------------\n"
|
||||
result += "------------------------------------------------\n"
|
||||
|
||||
ts = [m.uiDebug.drawTimeMillis for m in self.service_msgs['uiDebug']]
|
||||
ts = [m.uiDebug.drawTimeMillis for m in self.msgs['uiDebug']]
|
||||
result += f"min {min(ts):.2f}ms\n"
|
||||
result += f"max {max(ts):.2f}ms\n"
|
||||
result += f"std {np.std(ts):.2f}ms\n"
|
||||
@@ -241,53 +242,44 @@ class TestOnroad:
|
||||
assert len(veryslow) < 5, f"Too many slow frame draw times: {veryslow}"
|
||||
|
||||
def test_cpu_usage(self, subtests):
|
||||
result = "\n"
|
||||
result += "------------------------------------------------\n"
|
||||
result += "------------------ CPU Usage -------------------\n"
|
||||
result += "------------------------------------------------\n"
|
||||
print("\n------------------------------------------------")
|
||||
print("------------------ CPU Usage -------------------")
|
||||
print("------------------------------------------------")
|
||||
|
||||
plogs_by_proc = defaultdict(list)
|
||||
for pl in self.service_msgs['procLog']:
|
||||
for pl in self.msgs['procLog']:
|
||||
for x in pl.procLog.procs:
|
||||
if len(x.cmdline) > 0:
|
||||
n = list(x.cmdline)[0]
|
||||
plogs_by_proc[n].append(x)
|
||||
print(plogs_by_proc.keys())
|
||||
|
||||
cpu_ok = True
|
||||
dt = (self.service_msgs['procLog'][-1].logMonoTime - self.service_msgs['procLog'][0].logMonoTime) / 1e9
|
||||
for proc_name, expected_cpu in PROCS.items():
|
||||
dt = (self.msgs['procLog'][-1].logMonoTime - self.msgs['procLog'][0].logMonoTime) / 1e9
|
||||
header = ['process', 'usage', 'expected', 'max allowed', 'test result']
|
||||
rows = []
|
||||
for proc_name, expected in PROCS.items():
|
||||
|
||||
err = ""
|
||||
exp = "???"
|
||||
cpu_usage = 0.
|
||||
error = ""
|
||||
usage = 0.
|
||||
x = plogs_by_proc[proc_name]
|
||||
if len(x) > 2:
|
||||
cpu_time = cputime_total(x[-1]) - cputime_total(x[0])
|
||||
cpu_usage = cpu_time / dt * 100.
|
||||
usage = cpu_time / dt * 100.
|
||||
|
||||
if isinstance(expected_cpu, tuple):
|
||||
exp = str(expected_cpu)
|
||||
minn, maxx = expected_cpu
|
||||
else:
|
||||
exp = f"{expected_cpu:5.2f}"
|
||||
minn = min(expected_cpu * 0.65, max(expected_cpu - 1.0, 0.0))
|
||||
maxx = max(expected_cpu * 1.15, expected_cpu + 5.0)
|
||||
max_allowed = max(expected * 1.8, expected + 5.0)
|
||||
if usage > max_allowed:
|
||||
error = "❌ USING MORE CPU THAN EXPECTED ❌"
|
||||
cpu_ok = False
|
||||
|
||||
if cpu_usage > maxx:
|
||||
err = "using more CPU than expected"
|
||||
elif cpu_usage < minn:
|
||||
err = "using less CPU than expected"
|
||||
else:
|
||||
err = "NO METRICS FOUND"
|
||||
|
||||
result += f"{proc_name.ljust(35)} {cpu_usage:5.2f}% ({exp}%) {err}\n"
|
||||
if len(err) > 0:
|
||||
error = "❌ NO METRICS FOUND ❌"
|
||||
cpu_ok = False
|
||||
result += "------------------------------------------------\n"
|
||||
|
||||
rows.append([proc_name, usage, expected, max_allowed, error or "✅"])
|
||||
print(tabulate(rows, header, tablefmt="simple_grid", stralign="center", numalign="center", floatfmt=".2f"))
|
||||
|
||||
# Ensure there's no missing procs
|
||||
all_procs = {p.name for p in self.service_msgs['managerState'][0].managerState.processes if p.shouldBeRunning}
|
||||
all_procs = {p.name for p in self.msgs['managerState'][0].managerState.processes if p.shouldBeRunning}
|
||||
for p in all_procs:
|
||||
with subtests.test(proc=p):
|
||||
assert any(p in pp for pp in PROCS.keys()), f"Expected CPU usage missing for {p}"
|
||||
@@ -296,16 +288,15 @@ class TestOnroad:
|
||||
procs_tot = sum([(max(x) if isinstance(x, tuple) else x) for x in PROCS.values()])
|
||||
with subtests.test(name="total CPU"):
|
||||
assert procs_tot < MAX_TOTAL_CPU, "Total CPU budget exceeded"
|
||||
result += "------------------------------------------------\n"
|
||||
result += f"Total allocated CPU usage is {procs_tot}%, budget is {MAX_TOTAL_CPU}%, {MAX_TOTAL_CPU-procs_tot:.1f}% left\n"
|
||||
result += "------------------------------------------------\n"
|
||||
|
||||
print(result)
|
||||
print("------------------------------------------------")
|
||||
print(f"Total allocated CPU usage is {procs_tot}%, budget is {MAX_TOTAL_CPU}%, {MAX_TOTAL_CPU-procs_tot:.1f}% left")
|
||||
print("------------------------------------------------")
|
||||
|
||||
assert cpu_ok
|
||||
|
||||
def test_memory_usage(self):
|
||||
mems = [m.deviceState.memoryUsagePercent for m in self.service_msgs['deviceState']]
|
||||
offset = int(SERVICE_LIST['deviceState'].frequency * LOG_OFFSET)
|
||||
mems = [m.deviceState.memoryUsagePercent for m in self.msgs['deviceState'][offset:]]
|
||||
print("Memory usage: ", mems)
|
||||
|
||||
# check for big leaks. note that memory usage is
|
||||
@@ -321,7 +312,9 @@ class TestOnroad:
|
||||
result += "-------------- ImgProc Timing ------------------\n"
|
||||
result += "------------------------------------------------\n"
|
||||
|
||||
ts = [getattr(m, m.which()).processingTime for m in self.lr if 'CameraState' in m.which()]
|
||||
ts = []
|
||||
for s in ['roadCameraState', 'driverCameraState', 'wideCameraState']:
|
||||
ts.extend(getattr(m, s).processingTime for m in self.msgs[s])
|
||||
assert min(ts) < 0.025, f"high execution time: {min(ts)}"
|
||||
result += f"execution time: min {min(ts):.5f}s\n"
|
||||
result += f"execution time: max {max(ts):.5f}s\n"
|
||||
@@ -354,7 +347,7 @@ class TestOnroad:
|
||||
|
||||
cfgs = [("longitudinalPlan", 0.05, 0.05),]
|
||||
for (s, instant_max, avg_max) in cfgs:
|
||||
ts = [getattr(m, s).solverExecutionTime for m in self.service_msgs[s]]
|
||||
ts = [getattr(m, s).solverExecutionTime for m in self.msgs[s]]
|
||||
assert max(ts) < instant_max, f"high '{s}' execution time: {max(ts)}"
|
||||
assert np.mean(ts) < avg_max, f"high avg '{s}' execution time: {np.mean(ts)}"
|
||||
result += f"'{s}' execution time: min {min(ts):.5f}s\n"
|
||||
@@ -374,7 +367,7 @@ class TestOnroad:
|
||||
("driverStateV2", 0.050, 0.026),
|
||||
]
|
||||
for (s, instant_max, avg_max) in cfgs:
|
||||
ts = [getattr(m, s).modelExecutionTime for m in self.service_msgs[s]]
|
||||
ts = [getattr(m, s).modelExecutionTime for m in self.msgs[s]]
|
||||
assert max(ts) < instant_max, f"high '{s}' execution time: {max(ts)}"
|
||||
assert np.mean(ts) < avg_max, f"high avg '{s}' execution time: {np.mean(ts)}"
|
||||
result += f"'{s}' execution time: min {min(ts):.5f}s\n"
|
||||
@@ -385,33 +378,32 @@ class TestOnroad:
|
||||
|
||||
def test_timings(self):
|
||||
passed = True
|
||||
result = "\n"
|
||||
result += "------------------------------------------------\n"
|
||||
result += "----------------- Service Timings --------------\n"
|
||||
result += "------------------------------------------------\n"
|
||||
print("\n------------------------------------------------")
|
||||
print("----------------- Service Timings --------------")
|
||||
print("------------------------------------------------")
|
||||
|
||||
header = ['service', 'max', 'min', 'mean', 'expected mean', 'rsd', 'max allowed rsd', 'test result']
|
||||
rows = []
|
||||
for s, (maxmin, rsd) in TIMINGS.items():
|
||||
msgs = [m.logMonoTime for m in self.service_msgs[s]]
|
||||
offset = int(SERVICE_LIST[s].frequency * LOG_OFFSET)
|
||||
msgs = [m.logMonoTime for m in self.msgs[s][offset:]]
|
||||
if not len(msgs):
|
||||
raise Exception(f"missing {s}")
|
||||
|
||||
ts = np.diff(msgs) / 1e9
|
||||
dt = 1 / SERVICE_LIST[s].frequency
|
||||
|
||||
try:
|
||||
np.testing.assert_allclose(np.mean(ts), dt, rtol=0.03, err_msg=f"{s} - failed mean timing check")
|
||||
np.testing.assert_allclose([np.max(ts), np.min(ts)], dt, rtol=maxmin, err_msg=f"{s} - failed max/min timing check")
|
||||
except Exception as e:
|
||||
result += str(e) + "\n"
|
||||
passed = False
|
||||
errors = []
|
||||
if not np.allclose(np.mean(ts), dt, rtol=0.03, atol=0):
|
||||
errors.append("❌ FAILED MEAN TIMING CHECK ❌")
|
||||
if not np.allclose([np.max(ts), np.min(ts)], dt, rtol=maxmin, atol=0):
|
||||
errors.append("❌ FAILED MAX/MIN TIMING CHECK ❌")
|
||||
if (np.std(ts)/dt) > rsd:
|
||||
errors.append("❌ FAILED RSD TIMING CHECK ❌")
|
||||
passed = not errors and passed
|
||||
rows.append([s, *(np.array([np.max(ts), np.min(ts), np.mean(ts), dt])*1e3), np.std(ts)/dt, rsd, "\n".join(errors) or "✅"])
|
||||
|
||||
if np.std(ts) / dt > rsd:
|
||||
result += f"{s} - failed RSD timing check\n"
|
||||
passed = False
|
||||
|
||||
result += f"{s.ljust(40)}: {np.array([np.mean(ts), np.max(ts), np.min(ts)])*1e3}\n"
|
||||
result += f"{''.ljust(40)} {np.max(np.absolute([np.max(ts)/dt, np.min(ts)/dt]))} {np.std(ts)/dt}\n"
|
||||
result += "="*67
|
||||
print(result)
|
||||
print(tabulate(rows, header, tablefmt="simple_grid", stralign="center", numalign="center", floatfmt=".2f"))
|
||||
assert passed
|
||||
|
||||
@release_only
|
||||
@@ -422,16 +414,17 @@ class TestOnroad:
|
||||
if msg.which() == "selfdriveState":
|
||||
startup_alert = msg.selfdriveState.alertText1
|
||||
break
|
||||
expected = EVENTS[car.OnroadEvent.EventName.startup][ET.PERMANENT].alert_text_1
|
||||
expected = EVENTS[log.OnroadEvent.EventName.startup][ET.PERMANENT].alert_text_1
|
||||
assert startup_alert == expected, "wrong startup alert"
|
||||
|
||||
def test_engagable(self):
|
||||
no_entries = Counter()
|
||||
for m in self.service_msgs['onroadEvents']:
|
||||
for m in self.msgs['onroadEvents']:
|
||||
for evt in m.onroadEvents:
|
||||
if evt.noEntry:
|
||||
no_entries[evt.name] += 1
|
||||
|
||||
eng = [m.selfdriveState.engageable for m in self.service_msgs['selfdriveState']]
|
||||
offset = int(SERVICE_LIST['selfdriveState'].frequency * LOG_OFFSET)
|
||||
eng = [m.selfdriveState.engageable for m in self.msgs['selfdriveState'][offset:]]
|
||||
assert all(eng), \
|
||||
f"Not engageable for whole segment:\n- selfdriveState.engageable: {Counter(eng)}\n- No entry events: {no_entries}"
|
||||
|
||||
@@ -3,13 +3,13 @@ import pytest
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
from cereal import car
|
||||
from cereal import log
|
||||
import cereal.messaging as messaging
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.common.timeout import Timeout
|
||||
from openpilot.selfdrive.test.helpers import set_params_enabled
|
||||
|
||||
EventName = car.OnroadEvent.EventName
|
||||
EventName = log.OnroadEvent.EventName
|
||||
|
||||
|
||||
@pytest.mark.tici
|
||||
|
||||
@@ -28,9 +28,9 @@ qt_libs = [widgets, qt_util] + base_libs
|
||||
|
||||
qt_src = ["main.cc", "ui.cc", "qt/sidebar.cc", "qt/body.cc",
|
||||
"qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc",
|
||||
"qt/offroad/software_settings.cc", "qt/offroad/onboarding.cc",
|
||||
"qt/offroad/software_settings.cc", "qt/offroad/developer_panel.cc", "qt/offroad/onboarding.cc",
|
||||
"qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc",
|
||||
"qt/onroad/onroad_home.cc", "qt/onroad/annotated_camera.cc",
|
||||
"qt/onroad/onroad_home.cc", "qt/onroad/annotated_camera.cc", "qt/onroad/model.cc",
|
||||
"qt/onroad/buttons.cc", "qt/onroad/alerts.cc", "qt/onroad/driver_monitoring.cc", "qt/onroad/hud.cc"]
|
||||
|
||||
# build translation files
|
||||
@@ -92,7 +92,7 @@ if GetOption('extras') and arch != "Darwin":
|
||||
("openpilot", release),
|
||||
("openpilot_test", f"{release}-staging"),
|
||||
("openpilot_nightly", "nightly"),
|
||||
("openpilot_internal", "master"),
|
||||
("openpilot_internal", "nightly-dev"),
|
||||
]
|
||||
|
||||
cont = senv.Command(f"installer/continue_openpilot.o", f"installer/continue_openpilot.sh",
|
||||
@@ -110,3 +110,5 @@ if GetOption('extras') and arch != "Darwin":
|
||||
# build watch3
|
||||
if arch in ['x86_64', 'aarch64', 'Darwin'] or GetOption('extras'):
|
||||
qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'msgq', 'visionipc'])
|
||||
|
||||
SConscript(['raylib/SConscript'])
|
||||
|
||||
@@ -33,11 +33,6 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) {
|
||||
body = new BodyWindow(this);
|
||||
slayout->addWidget(body);
|
||||
|
||||
driver_view = new DriverViewWindow(this);
|
||||
connect(driver_view, &DriverViewWindow::done, [=] {
|
||||
showDriverView(false);
|
||||
});
|
||||
slayout->addWidget(driver_view);
|
||||
setAttribute(Qt::WA_NoSystemBackground);
|
||||
QObject::connect(uiState(), &UIState::uiUpdate, this, &HomeWindow::updateState);
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, this, &HomeWindow::offroadTransition);
|
||||
@@ -68,16 +63,6 @@ void HomeWindow::offroadTransition(bool offroad) {
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWindow::showDriverView(bool show) {
|
||||
if (show) {
|
||||
emit closeSettings();
|
||||
slayout->setCurrentWidget(driver_view);
|
||||
} else {
|
||||
slayout->setCurrentWidget(home);
|
||||
}
|
||||
sidebar->setVisible(show == false);
|
||||
}
|
||||
|
||||
void HomeWindow::mousePressEvent(QMouseEvent* e) {
|
||||
// Handle sidebar collapsing
|
||||
if ((onroad->isVisible() || body->isVisible()) && (!sidebar->isVisible() || e->x() > sidebar->width())) {
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <QWidget>
|
||||
|
||||
#include "common/params.h"
|
||||
#include "selfdrive/ui/qt/offroad/driverview.h"
|
||||
#include "selfdrive/ui/qt/body.h"
|
||||
#include "selfdrive/ui/qt/onroad/onroad_home.h"
|
||||
#include "selfdrive/ui/qt/sidebar.h"
|
||||
@@ -53,7 +52,6 @@ signals:
|
||||
|
||||
public slots:
|
||||
void offroadTransition(bool offroad);
|
||||
void showDriverView(bool show);
|
||||
void showSidebar(bool show);
|
||||
|
||||
protected:
|
||||
@@ -65,7 +63,6 @@ private:
|
||||
OffroadHome *home;
|
||||
OnroadWindow *onroad;
|
||||
BodyWindow *body;
|
||||
DriverViewWindow *driver_view;
|
||||
QStackedLayout *slayout;
|
||||
|
||||
private slots:
|
||||
|
||||
@@ -149,10 +149,6 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
|
||||
ipLabel = new LabelControl(tr("IP Address"), wifi->ipv4_address);
|
||||
list->addItem(ipLabel);
|
||||
|
||||
// SSH keys
|
||||
list->addItem(new SshToggle());
|
||||
list->addItem(new SshControl());
|
||||
|
||||
// Roaming toggle
|
||||
const bool roamingEnabled = params.getBool("GsmRoaming");
|
||||
roamingToggle = new ToggleControl(tr("Enable Roaming"), "", "", roamingEnabled);
|
||||
|
||||
@@ -448,6 +448,7 @@ void WifiManager::tetheringActivated(QDBusPendingCallWatcher *call) {
|
||||
});
|
||||
}
|
||||
call->deleteLater();
|
||||
tethering_on = true;
|
||||
}
|
||||
|
||||
void WifiManager::setTetheringEnabled(bool enabled) {
|
||||
@@ -465,6 +466,7 @@ void WifiManager::setTetheringEnabled(bool enabled) {
|
||||
|
||||
} else {
|
||||
deactivateConnectionBySsid(tethering_ssid);
|
||||
tethering_on = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ public:
|
||||
QMap<QString, Network> seenNetworks;
|
||||
QMap<QDBusObjectPath, QString> knownConnections;
|
||||
QString ipv4_address;
|
||||
bool tethering_on = false;
|
||||
bool ipv4_forward = false;
|
||||
|
||||
explicit WifiManager(QObject* parent);
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#include <QDebug>
|
||||
|
||||
#include "selfdrive/ui/qt/offroad/developer_panel.h"
|
||||
#include "selfdrive/ui/qt/widgets/ssh_keys.h"
|
||||
#include "selfdrive/ui/qt/widgets/controls.h"
|
||||
#include "common/util.h"
|
||||
|
||||
DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) {
|
||||
// SSH keys
|
||||
addItem(new SshToggle());
|
||||
addItem(new SshControl());
|
||||
|
||||
joystickToggle = new ParamControl("JoystickDebugMode", tr("Joystick Debug Mode"), "", "");
|
||||
QObject::connect(joystickToggle, &ParamControl::toggleFlipped, [=](bool state) {
|
||||
params.putBool("LongitudinalManeuverMode", false);
|
||||
longManeuverToggle->refresh();
|
||||
});
|
||||
addItem(joystickToggle);
|
||||
|
||||
longManeuverToggle = new ParamControl("LongitudinalManeuverMode", tr("Longitudinal Maneuver Mode"), "", "");
|
||||
QObject::connect(longManeuverToggle, &ParamControl::toggleFlipped, [=](bool state) {
|
||||
params.putBool("JoystickDebugMode", false);
|
||||
joystickToggle->refresh();
|
||||
});
|
||||
addItem(longManeuverToggle);
|
||||
|
||||
// Joystick and longitudinal maneuvers should be hidden on release branches
|
||||
is_release = params.getBool("IsReleaseBranch");
|
||||
|
||||
// Toggles should be not available to change in onroad state
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, this, &DeveloperPanel::updateToggles);
|
||||
}
|
||||
|
||||
void DeveloperPanel::updateToggles(bool _offroad) {
|
||||
for (auto btn : findChildren<ParamControl *>()) {
|
||||
btn->setVisible(!is_release);
|
||||
btn->setEnabled(_offroad);
|
||||
}
|
||||
|
||||
// longManeuverToggle should not be toggleable if the car don't have longitudinal control
|
||||
auto cp_bytes = params.get("CarParamsPersistent");
|
||||
if (!cp_bytes.empty()) {
|
||||
AlignedBuffer aligned_buf;
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
||||
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||
longManeuverToggle->setEnabled(hasLongitudinalControl(CP) && _offroad);
|
||||
} else {
|
||||
longManeuverToggle->setEnabled(false);
|
||||
}
|
||||
|
||||
offroad = _offroad;
|
||||
}
|
||||
|
||||
void DeveloperPanel::showEvent(QShowEvent *event) {
|
||||
updateToggles(offroad);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/qt/offroad/settings.h"
|
||||
|
||||
class DeveloperPanel : public ListWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeveloperPanel(SettingsWindow *parent);
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
private:
|
||||
Params params;
|
||||
ParamControl* joystickToggle;
|
||||
ParamControl* longManeuverToggle;
|
||||
bool is_release;
|
||||
bool offroad;
|
||||
|
||||
private slots:
|
||||
void updateToggles(bool _offroad);
|
||||
};
|
||||
@@ -5,28 +5,7 @@
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
|
||||
const int FACE_IMG_SIZE = 130;
|
||||
|
||||
DriverViewWindow::DriverViewWindow(QWidget* parent) : CameraWidget("camerad", VISION_STREAM_DRIVER, parent) {
|
||||
face_img = loadPixmap("../assets/img_driver_face_static.png", {FACE_IMG_SIZE, FACE_IMG_SIZE});
|
||||
QObject::connect(this, &CameraWidget::clicked, this, &DriverViewWindow::done);
|
||||
QObject::connect(device(), &Device::interactiveTimeout, this, [this]() {
|
||||
if (isVisible()) {
|
||||
emit done();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void DriverViewWindow::showEvent(QShowEvent* event) {
|
||||
params.putBool("IsDriverViewEnabled", true);
|
||||
device()->resetInteractiveTimeout(60);
|
||||
CameraWidget::showEvent(event);
|
||||
}
|
||||
|
||||
void DriverViewWindow::hideEvent(QHideEvent* event) {
|
||||
params.putBool("IsDriverViewEnabled", false);
|
||||
stopVipcThread();
|
||||
CameraWidget::hideEvent(event);
|
||||
}
|
||||
|
||||
void DriverViewWindow::paintGL() {
|
||||
@@ -68,12 +47,8 @@ void DriverViewWindow::paintGL() {
|
||||
p.drawRoundedRect(fbox_x - box_size / 2, fbox_y - box_size / 2, box_size, box_size, 35.0, 35.0);
|
||||
}
|
||||
|
||||
// icon
|
||||
const int img_offset = 60;
|
||||
const int img_x = is_rhd ? rect().right() - FACE_IMG_SIZE - img_offset : rect().left() + img_offset;
|
||||
const int img_y = rect().bottom() - FACE_IMG_SIZE - img_offset;
|
||||
p.setOpacity(face_detected ? 1.0 : 0.2);
|
||||
p.drawPixmap(img_x, img_y, face_img);
|
||||
driver_monitor.updateState(*uiState());
|
||||
driver_monitor.draw(p, rect());
|
||||
}
|
||||
|
||||
mat4 DriverViewWindow::calcFrameMatrix() {
|
||||
@@ -87,3 +62,20 @@ mat4 DriverViewWindow::calcFrameMatrix() {
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
}};
|
||||
}
|
||||
|
||||
DriverViewDialog::DriverViewDialog(QWidget *parent) : DialogBase(parent) {
|
||||
Params().putBool("IsDriverViewEnabled", true);
|
||||
device()->resetInteractiveTimeout(60);
|
||||
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->setContentsMargins(0, 0, 0, 0);
|
||||
auto camera = new DriverViewWindow(this);
|
||||
main_layout->addWidget(camera);
|
||||
QObject::connect(camera, &DriverViewWindow::clicked, this, &DialogBase::accept);
|
||||
QObject::connect(device(), &Device::interactiveTimeout, this, &DialogBase::accept);
|
||||
}
|
||||
|
||||
void DriverViewDialog::done(int r) {
|
||||
Params().putBool("IsDriverViewEnabled", false);
|
||||
QDialog::done(r);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/qt/widgets/cameraview.h"
|
||||
#include "selfdrive/ui/qt/onroad/driver_monitoring.h"
|
||||
#include "selfdrive/ui/qt/widgets/input.h"
|
||||
|
||||
class DriverViewWindow : public CameraWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DriverViewWindow(QWidget *parent);
|
||||
|
||||
signals:
|
||||
void done();
|
||||
|
||||
protected:
|
||||
mat4 calcFrameMatrix() override;
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void hideEvent(QHideEvent *event) override;
|
||||
void paintGL() override;
|
||||
|
||||
Params params;
|
||||
QPixmap face_img;
|
||||
mat4 calcFrameMatrix() override;
|
||||
DriverMonitorRenderer driver_monitor;
|
||||
};
|
||||
|
||||
class DriverViewDialog : public DialogBase {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DriverViewDialog(QWidget *parent);
|
||||
void done(int r) override;
|
||||
};
|
||||
|
||||
@@ -8,12 +8,13 @@
|
||||
|
||||
#include "common/watchdog.h"
|
||||
#include "common/util.h"
|
||||
#include "selfdrive/ui/qt/offroad/driverview.h"
|
||||
#include "selfdrive/ui/qt/network/networking.h"
|
||||
#include "selfdrive/ui/qt/offroad/settings.h"
|
||||
#include "selfdrive/ui/qt/qt_window.h"
|
||||
#include "selfdrive/ui/qt/widgets/prime.h"
|
||||
#include "selfdrive/ui/qt/widgets/scrollview.h"
|
||||
#include "selfdrive/ui/qt/widgets/ssh_keys.h"
|
||||
#include "selfdrive/ui/qt/offroad/developer_panel.h"
|
||||
|
||||
TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
|
||||
// param, title, desc, icon
|
||||
@@ -204,7 +205,12 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
|
||||
|
||||
auto dcamBtn = new ButtonControl(tr("Driver Camera"), tr("PREVIEW"),
|
||||
tr("Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)"));
|
||||
connect(dcamBtn, &ButtonControl::clicked, [=]() { emit showDriverView(); });
|
||||
connect(dcamBtn, &ButtonControl::clicked, [this, dcamBtn]() {
|
||||
dcamBtn->setEnabled(false);
|
||||
DriverViewDialog driver_view(this);
|
||||
driver_view.exec();
|
||||
dcamBtn->setEnabled(true);
|
||||
});
|
||||
addItem(dcamBtn);
|
||||
|
||||
auto resetCalibBtn = new ButtonControl(tr("Reset Calibration"), tr("RESET"), "");
|
||||
@@ -376,7 +382,6 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
|
||||
// setup panels
|
||||
DevicePanel *device = new DevicePanel(this);
|
||||
QObject::connect(device, &DevicePanel::reviewTrainingGuide, this, &SettingsWindow::reviewTrainingGuide);
|
||||
QObject::connect(device, &DevicePanel::showDriverView, this, &SettingsWindow::showDriverView);
|
||||
|
||||
TogglesPanel *toggles = new TogglesPanel(this);
|
||||
QObject::connect(this, &SettingsWindow::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription);
|
||||
@@ -389,6 +394,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
|
||||
{tr("Network"), networking},
|
||||
{tr("Toggles"), toggles},
|
||||
{tr("Software"), new SoftwarePanel(this)},
|
||||
{tr("Developer"), new DeveloperPanel(this)},
|
||||
};
|
||||
|
||||
nav_btns = new QButtonGroup(this);
|
||||
|
||||
@@ -28,7 +28,6 @@ protected:
|
||||
signals:
|
||||
void closeSettings();
|
||||
void reviewTrainingGuide();
|
||||
void showDriverView();
|
||||
void expandToggleDescription(const QString ¶m);
|
||||
|
||||
private:
|
||||
@@ -45,7 +44,6 @@ public:
|
||||
|
||||
signals:
|
||||
void reviewTrainingGuide();
|
||||
void showDriverView();
|
||||
|
||||
private slots:
|
||||
void poweroff();
|
||||
|
||||
@@ -54,7 +54,7 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
|
||||
connect(targetBranchBtn, &ButtonControl::clicked, [=]() {
|
||||
auto current = params.get("GitBranch");
|
||||
QStringList branches = QString::fromStdString(params.get("UpdaterAvailableBranches")).split(",");
|
||||
for (QString b : {current.c_str(), "devel-staging", "devel", "nightly", "master-ci", "master"}) {
|
||||
for (QString b : {current.c_str(), "devel-staging", "devel", "nightly", "nightly-dev", "master-ci", "master"}) {
|
||||
auto i = branches.indexOf(b);
|
||||
if (i >= 0) {
|
||||
branches.removeAt(i);
|
||||
|
||||
@@ -75,8 +75,7 @@ mat4 AnnotatedCameraWidget::calcFrameMatrix() {
|
||||
0.0f, zoom, (h / 2 - y_offset) - (center_y * zoom),
|
||||
0.0f, 0.0f, 1.0f).finished();
|
||||
|
||||
s->car_space_transform = video_transform * calib_transform;
|
||||
s->clip_region = rect().adjusted(-500, -500, 500, 500);
|
||||
model.setTransform(video_transform * calib_transform);
|
||||
|
||||
float zx = zoom * 2 * center_x / w;
|
||||
float zy = zoom * 2 * center_y / h;
|
||||
@@ -88,106 +87,10 @@ mat4 AnnotatedCameraWidget::calcFrameMatrix() {
|
||||
}};
|
||||
}
|
||||
|
||||
void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) {
|
||||
painter.save();
|
||||
|
||||
const UIScene &scene = s->scene;
|
||||
SubMaster &sm = *(s->sm);
|
||||
|
||||
// lanelines
|
||||
for (int i = 0; i < std::size(scene.lane_line_vertices); ++i) {
|
||||
painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp<float>(scene.lane_line_probs[i], 0.0, 0.7)));
|
||||
painter.drawPolygon(scene.lane_line_vertices[i]);
|
||||
}
|
||||
|
||||
// road edges
|
||||
for (int i = 0; i < std::size(scene.road_edge_vertices); ++i) {
|
||||
painter.setBrush(QColor::fromRgbF(1.0, 0, 0, std::clamp<float>(1.0 - scene.road_edge_stds[i], 0.0, 1.0)));
|
||||
painter.drawPolygon(scene.road_edge_vertices[i]);
|
||||
}
|
||||
|
||||
// paint path
|
||||
QLinearGradient bg(0, height(), 0, 0);
|
||||
if (sm["selfdriveState"].getSelfdriveState().getExperimentalMode()) {
|
||||
// The first half of track_vertices are the points for the right side of the path
|
||||
const auto &acceleration = sm["modelV2"].getModelV2().getAcceleration().getX();
|
||||
const int max_len = std::min<int>(scene.track_vertices.length() / 2, acceleration.size());
|
||||
|
||||
for (int i = 0; i < max_len; ++i) {
|
||||
// Some points are out of frame
|
||||
int track_idx = max_len - i - 1; // flip idx to start from bottom right
|
||||
if (scene.track_vertices[track_idx].y() < 0 || scene.track_vertices[track_idx].y() > height()) continue;
|
||||
|
||||
// Flip so 0 is bottom of frame
|
||||
float lin_grad_point = (height() - scene.track_vertices[track_idx].y()) / height();
|
||||
|
||||
// speed up: 120, slow down: 0
|
||||
float path_hue = fmax(fmin(60 + acceleration[i] * 35, 120), 0);
|
||||
// FIXME: painter.drawPolygon can be slow if hue is not rounded
|
||||
path_hue = int(path_hue * 100 + 0.5) / 100;
|
||||
|
||||
float saturation = fmin(fabs(acceleration[i] * 1.5), 1);
|
||||
float lightness = util::map_val(saturation, 0.0f, 1.0f, 0.95f, 0.62f); // lighter when grey
|
||||
float alpha = util::map_val(lin_grad_point, 0.75f / 2.f, 0.75f, 0.4f, 0.0f); // matches previous alpha fade
|
||||
bg.setColorAt(lin_grad_point, QColor::fromHslF(path_hue / 360., saturation, lightness, alpha));
|
||||
|
||||
// Skip a point, unless next is last
|
||||
i += (i + 2) < max_len ? 1 : 0;
|
||||
}
|
||||
|
||||
} else {
|
||||
bg.setColorAt(0.0, QColor::fromHslF(148 / 360., 0.94, 0.51, 0.4));
|
||||
bg.setColorAt(0.5, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.35));
|
||||
bg.setColorAt(1.0, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.0));
|
||||
}
|
||||
|
||||
painter.setBrush(bg);
|
||||
painter.drawPolygon(scene.track_vertices);
|
||||
|
||||
painter.restore();
|
||||
}
|
||||
|
||||
void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd) {
|
||||
painter.save();
|
||||
|
||||
const float speedBuff = 10.;
|
||||
const float leadBuff = 40.;
|
||||
const float d_rel = lead_data.getDRel();
|
||||
const float v_rel = lead_data.getVRel();
|
||||
|
||||
float fillAlpha = 0;
|
||||
if (d_rel < leadBuff) {
|
||||
fillAlpha = 255 * (1.0 - (d_rel / leadBuff));
|
||||
if (v_rel < 0) {
|
||||
fillAlpha += 255 * (-1 * (v_rel / speedBuff));
|
||||
}
|
||||
fillAlpha = (int)(fmin(fillAlpha, 255));
|
||||
}
|
||||
|
||||
float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35;
|
||||
float x = std::clamp((float)vd.x(), 0.f, width() - sz / 2);
|
||||
float y = std::fmin(height() - sz * .6, (float)vd.y());
|
||||
|
||||
float g_xo = sz / 5;
|
||||
float g_yo = sz / 10;
|
||||
|
||||
QPointF glow[] = {{x + (sz * 1.35) + g_xo, y + sz + g_yo}, {x, y - g_yo}, {x - (sz * 1.35) - g_xo, y + sz + g_yo}};
|
||||
painter.setBrush(QColor(218, 202, 37, 255));
|
||||
painter.drawPolygon(glow, std::size(glow));
|
||||
|
||||
// chevron
|
||||
QPointF chevron[] = {{x + (sz * 1.25), y + sz}, {x, y}, {x - (sz * 1.25), y + sz}};
|
||||
painter.setBrush(redColor(fillAlpha));
|
||||
painter.drawPolygon(chevron, std::size(chevron));
|
||||
|
||||
painter.restore();
|
||||
}
|
||||
|
||||
void AnnotatedCameraWidget::paintGL() {
|
||||
UIState *s = uiState();
|
||||
SubMaster &sm = *(s->sm);
|
||||
const double start_draw_t = millis_since_boot();
|
||||
const cereal::ModelDataV2::Reader &model = sm["modelV2"].getModelV2();
|
||||
|
||||
// draw camera frame
|
||||
{
|
||||
@@ -218,7 +121,7 @@ void AnnotatedCameraWidget::paintGL() {
|
||||
wide_cam_requested = wide_cam_requested && sm["selfdriveState"].getSelfdriveState().getExperimentalMode();
|
||||
}
|
||||
CameraWidget::setStreamType(wide_cam_requested ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD);
|
||||
CameraWidget::setFrameId(model.getFrameId());
|
||||
CameraWidget::setFrameId(sm["modelV2"].getModelV2().getFrameId());
|
||||
CameraWidget::paintGL();
|
||||
}
|
||||
|
||||
@@ -226,24 +129,7 @@ void AnnotatedCameraWidget::paintGL() {
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setPen(Qt::NoPen);
|
||||
|
||||
if (s->scene.world_objects_visible) {
|
||||
update_model(s, model);
|
||||
drawLaneLines(painter, s);
|
||||
|
||||
if (s->scene.longitudinal_control && sm.rcv_frame("radarState") > s->scene.started_frame) {
|
||||
auto radar_state = sm["radarState"].getRadarState();
|
||||
update_leads(s, radar_state, model.getPosition());
|
||||
auto lead_one = radar_state.getLeadOne();
|
||||
auto lead_two = radar_state.getLeadTwo();
|
||||
if (lead_one.getStatus()) {
|
||||
drawLead(painter, lead_one, s->scene.lead_vertices[0]);
|
||||
}
|
||||
if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) {
|
||||
drawLead(painter, lead_two, s->scene.lead_vertices[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
model.draw(painter, rect());
|
||||
dmon.draw(painter, rect());
|
||||
hud.updateState(*s);
|
||||
hud.draw(painter, rect());
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "selfdrive/ui/qt/onroad/hud.h"
|
||||
#include "selfdrive/ui/qt/onroad/buttons.h"
|
||||
#include "selfdrive/ui/qt/onroad/driver_monitoring.h"
|
||||
#include "selfdrive/ui/qt/onroad/model.h"
|
||||
#include "selfdrive/ui/qt/widgets/cameraview.h"
|
||||
|
||||
class AnnotatedCameraWidget : public CameraWidget {
|
||||
@@ -19,6 +20,7 @@ private:
|
||||
ExperimentalButton *experimental_btn;
|
||||
DriverMonitorRenderer dmon;
|
||||
HudRenderer hud;
|
||||
ModelRenderer model;
|
||||
std::unique_ptr<PubMaster> pm;
|
||||
|
||||
int skip_frame_count = 0;
|
||||
@@ -29,9 +31,6 @@ protected:
|
||||
void initializeGL() override;
|
||||
void showEvent(QShowEvent *event) override;
|
||||
mat4 calcFrameMatrix() override;
|
||||
void drawLaneLines(QPainter &painter, const UIState *s);
|
||||
void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd);
|
||||
inline QColor redColor(int alpha = 255) { return QColor(201, 34, 49, alpha); }
|
||||
|
||||
double prev_draw_t = 0;
|
||||
FirstOrderFilter fps_filter;
|
||||
|
||||
@@ -13,7 +13,7 @@ void HudRenderer::updateState(const UIState &s) {
|
||||
status = s.status;
|
||||
|
||||
const SubMaster &sm = *(s.sm);
|
||||
if (!sm.alive("carState")) {
|
||||
if (sm.rcv_frame("carState") < s.scene.started_frame) {
|
||||
is_cruise_set = false;
|
||||
set_speed = SET_SPEED_NA;
|
||||
speed = 0.0;
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
#include "selfdrive/ui/qt/onroad/model.h"
|
||||
|
||||
constexpr int CLIP_MARGIN = 500;
|
||||
constexpr float MIN_DRAW_DISTANCE = 10.0;
|
||||
constexpr float MAX_DRAW_DISTANCE = 100.0;
|
||||
|
||||
static int get_path_length_idx(const cereal::XYZTData::Reader &line, const float path_height) {
|
||||
const auto &line_x = line.getX();
|
||||
int max_idx = 0;
|
||||
for (int i = 1; i < line_x.size() && line_x[i] <= path_height; ++i) {
|
||||
max_idx = i;
|
||||
}
|
||||
return max_idx;
|
||||
}
|
||||
|
||||
void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) {
|
||||
auto *s = uiState();
|
||||
auto &sm = *(s->sm);
|
||||
// Check if data is up-to-date
|
||||
if (sm.rcv_frame("liveCalibration") < s->scene.started_frame ||
|
||||
sm.rcv_frame("modelV2") < s->scene.started_frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
clip_region = surface_rect.adjusted(-CLIP_MARGIN, -CLIP_MARGIN, CLIP_MARGIN, CLIP_MARGIN);
|
||||
experimental_mode = sm["selfdriveState"].getSelfdriveState().getExperimentalMode();
|
||||
longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl();
|
||||
path_offset_z = sm["liveCalibration"].getLiveCalibration().getHeight()[0];
|
||||
|
||||
painter.save();
|
||||
|
||||
const auto &model = sm["modelV2"].getModelV2();
|
||||
const auto &radar_state = sm["radarState"].getRadarState();
|
||||
const auto &lead_one = radar_state.getLeadOne();
|
||||
|
||||
update_model(model, lead_one);
|
||||
drawLaneLines(painter);
|
||||
drawPath(painter, model, surface_rect.height());
|
||||
|
||||
if (longitudinal_control && sm.alive("radarState")) {
|
||||
update_leads(radar_state, model.getPosition());
|
||||
const auto &lead_two = radar_state.getLeadTwo();
|
||||
if (lead_one.getStatus()) {
|
||||
drawLead(painter, lead_one, lead_vertices[0], surface_rect);
|
||||
}
|
||||
if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) {
|
||||
drawLead(painter, lead_two, lead_vertices[1], surface_rect);
|
||||
}
|
||||
}
|
||||
|
||||
painter.restore();
|
||||
}
|
||||
|
||||
void ModelRenderer::update_leads(const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
const auto &lead_data = (i == 0) ? radar_state.getLeadOne() : radar_state.getLeadTwo();
|
||||
if (lead_data.getStatus()) {
|
||||
float z = line.getZ()[get_path_length_idx(line, lead_data.getDRel())];
|
||||
mapToScreen(lead_data.getDRel(), -lead_data.getYRel(), z + path_offset_z, &lead_vertices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelRenderer::update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead) {
|
||||
const auto &model_position = model.getPosition();
|
||||
float max_distance = std::clamp(*(model_position.getX().end() - 1), MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE);
|
||||
|
||||
// update lane lines
|
||||
const auto &lane_lines = model.getLaneLines();
|
||||
const auto &line_probs = model.getLaneLineProbs();
|
||||
int max_idx = get_path_length_idx(lane_lines[0], max_distance);
|
||||
for (int i = 0; i < std::size(lane_line_vertices); i++) {
|
||||
lane_line_probs[i] = line_probs[i];
|
||||
mapLineToPolygon(lane_lines[i], 0.025 * lane_line_probs[i], 0, &lane_line_vertices[i], max_idx);
|
||||
}
|
||||
|
||||
// update road edges
|
||||
const auto &road_edges = model.getRoadEdges();
|
||||
const auto &edge_stds = model.getRoadEdgeStds();
|
||||
for (int i = 0; i < std::size(road_edge_vertices); i++) {
|
||||
road_edge_stds[i] = edge_stds[i];
|
||||
mapLineToPolygon(road_edges[i], 0.025, 0, &road_edge_vertices[i], max_idx);
|
||||
}
|
||||
|
||||
// update path
|
||||
if (lead.getStatus()) {
|
||||
const float lead_d = lead.getDRel() * 2.;
|
||||
max_distance = std::clamp((float)(lead_d - fmin(lead_d * 0.35, 10.)), 0.0f, max_distance);
|
||||
}
|
||||
max_idx = get_path_length_idx(model_position, max_distance);
|
||||
mapLineToPolygon(model_position, 0.9, path_offset_z, &track_vertices, max_idx, false);
|
||||
}
|
||||
|
||||
void ModelRenderer::drawLaneLines(QPainter &painter) {
|
||||
// lanelines
|
||||
for (int i = 0; i < std::size(lane_line_vertices); ++i) {
|
||||
painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp<float>(lane_line_probs[i], 0.0, 0.7)));
|
||||
painter.drawPolygon(lane_line_vertices[i]);
|
||||
}
|
||||
|
||||
// road edges
|
||||
for (int i = 0; i < std::size(road_edge_vertices); ++i) {
|
||||
painter.setBrush(QColor::fromRgbF(1.0, 0, 0, std::clamp<float>(1.0 - road_edge_stds[i], 0.0, 1.0)));
|
||||
painter.drawPolygon(road_edge_vertices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelRenderer::drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, int height) {
|
||||
QLinearGradient bg(0, height, 0, 0);
|
||||
if (experimental_mode) {
|
||||
// The first half of track_vertices are the points for the right side of the path
|
||||
const auto &acceleration = model.getAcceleration().getX();
|
||||
const int max_len = std::min<int>(track_vertices.length() / 2, acceleration.size());
|
||||
|
||||
for (int i = 0; i < max_len; ++i) {
|
||||
// Some points are out of frame
|
||||
int track_idx = max_len - i - 1; // flip idx to start from bottom right
|
||||
if (track_vertices[track_idx].y() < 0 || track_vertices[track_idx].y() > height) continue;
|
||||
|
||||
// Flip so 0 is bottom of frame
|
||||
float lin_grad_point = (height - track_vertices[track_idx].y()) / height;
|
||||
|
||||
// speed up: 120, slow down: 0
|
||||
float path_hue = fmax(fmin(60 + acceleration[i] * 35, 120), 0);
|
||||
// FIXME: painter.drawPolygon can be slow if hue is not rounded
|
||||
path_hue = int(path_hue * 100 + 0.5) / 100;
|
||||
|
||||
float saturation = fmin(fabs(acceleration[i] * 1.5), 1);
|
||||
float lightness = util::map_val(saturation, 0.0f, 1.0f, 0.95f, 0.62f); // lighter when grey
|
||||
float alpha = util::map_val(lin_grad_point, 0.75f / 2.f, 0.75f, 0.4f, 0.0f); // matches previous alpha fade
|
||||
bg.setColorAt(lin_grad_point, QColor::fromHslF(path_hue / 360., saturation, lightness, alpha));
|
||||
|
||||
// Skip a point, unless next is last
|
||||
i += (i + 2) < max_len ? 1 : 0;
|
||||
}
|
||||
|
||||
} else {
|
||||
updatePathGradient(bg);
|
||||
}
|
||||
|
||||
painter.setBrush(bg);
|
||||
painter.drawPolygon(track_vertices);
|
||||
}
|
||||
|
||||
void ModelRenderer::updatePathGradient(QLinearGradient &bg) {
|
||||
static const QColor throttle_colors[] = {
|
||||
QColor::fromHslF(148. / 360., 0.94, 0.51, 0.4),
|
||||
QColor::fromHslF(112. / 360., 1.0, 0.68, 0.35),
|
||||
QColor::fromHslF(112. / 360., 1.0, 0.68, 0.0)};
|
||||
|
||||
static const QColor no_throttle_colors[] = {
|
||||
QColor::fromHslF(148. / 360., 0.0, 0.95, 0.4),
|
||||
QColor::fromHslF(112. / 360., 0.0, 0.95, 0.35),
|
||||
QColor::fromHslF(112. / 360., 0.0, 0.95, 0.0),
|
||||
};
|
||||
|
||||
// Transition speed; 0.1 corresponds to 0.5 seconds at UI_FREQ
|
||||
constexpr float transition_speed = 0.1f;
|
||||
|
||||
// Start transition if throttle state changes
|
||||
bool allow_throttle = (*uiState()->sm)["longitudinalPlan"].getLongitudinalPlan().getAllowThrottle() || !longitudinal_control;
|
||||
if (allow_throttle != prev_allow_throttle) {
|
||||
prev_allow_throttle = allow_throttle;
|
||||
// Invert blend factor for a smooth transition when the state changes mid-animation
|
||||
blend_factor = std::max(1.0f - blend_factor, 0.0f);
|
||||
}
|
||||
|
||||
const QColor *begin_colors = allow_throttle ? no_throttle_colors : throttle_colors;
|
||||
const QColor *end_colors = allow_throttle ? throttle_colors : no_throttle_colors;
|
||||
if (blend_factor < 1.0f) {
|
||||
blend_factor = std::min(blend_factor + transition_speed, 1.0f);
|
||||
}
|
||||
|
||||
// Set gradient colors by blending the start and end colors
|
||||
bg.setColorAt(0.0f, blendColors(begin_colors[0], end_colors[0], blend_factor));
|
||||
bg.setColorAt(0.5f, blendColors(begin_colors[1], end_colors[1], blend_factor));
|
||||
bg.setColorAt(1.0f, blendColors(begin_colors[2], end_colors[2], blend_factor));
|
||||
}
|
||||
|
||||
QColor ModelRenderer::blendColors(const QColor &start, const QColor &end, float t) {
|
||||
if (t == 1.0f) return end;
|
||||
return QColor::fromRgbF(
|
||||
(1 - t) * start.redF() + t * end.redF(),
|
||||
(1 - t) * start.greenF() + t * end.greenF(),
|
||||
(1 - t) * start.blueF() + t * end.blueF(),
|
||||
(1 - t) * start.alphaF() + t * end.alphaF());
|
||||
}
|
||||
|
||||
void ModelRenderer::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data,
|
||||
const QPointF &vd, const QRect &surface_rect) {
|
||||
const float speedBuff = 10.;
|
||||
const float leadBuff = 40.;
|
||||
const float d_rel = lead_data.getDRel();
|
||||
const float v_rel = lead_data.getVRel();
|
||||
|
||||
float fillAlpha = 0;
|
||||
if (d_rel < leadBuff) {
|
||||
fillAlpha = 255 * (1.0 - (d_rel / leadBuff));
|
||||
if (v_rel < 0) {
|
||||
fillAlpha += 255 * (-1 * (v_rel / speedBuff));
|
||||
}
|
||||
fillAlpha = (int)(fmin(fillAlpha, 255));
|
||||
}
|
||||
|
||||
float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35;
|
||||
float x = std::clamp<float>(vd.x(), 0.f, surface_rect.width() - sz / 2);
|
||||
float y = std::min<float>(vd.y(), surface_rect.height() - sz * 0.6);
|
||||
|
||||
float g_xo = sz / 5;
|
||||
float g_yo = sz / 10;
|
||||
|
||||
QPointF glow[] = {{x + (sz * 1.35) + g_xo, y + sz + g_yo}, {x, y - g_yo}, {x - (sz * 1.35) - g_xo, y + sz + g_yo}};
|
||||
painter.setBrush(QColor(218, 202, 37, 255));
|
||||
painter.drawPolygon(glow, std::size(glow));
|
||||
|
||||
// chevron
|
||||
QPointF chevron[] = {{x + (sz * 1.25), y + sz}, {x, y}, {x - (sz * 1.25), y + sz}};
|
||||
painter.setBrush(QColor(201, 34, 49, fillAlpha));
|
||||
painter.drawPolygon(chevron, std::size(chevron));
|
||||
}
|
||||
|
||||
// Projects a point in car to space to the corresponding point in full frame image space.
|
||||
bool ModelRenderer::mapToScreen(float in_x, float in_y, float in_z, QPointF *out) {
|
||||
Eigen::Vector3f input(in_x, in_y, in_z);
|
||||
auto pt = car_space_transform * input;
|
||||
*out = QPointF(pt.x() / pt.z(), pt.y() / pt.z());
|
||||
return clip_region.contains(*out);
|
||||
}
|
||||
|
||||
void ModelRenderer::mapLineToPolygon(const cereal::XYZTData::Reader &line, float y_off, float z_off,
|
||||
QPolygonF *pvd, int max_idx, bool allow_invert) {
|
||||
const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ();
|
||||
QPointF left, right;
|
||||
pvd->clear();
|
||||
for (int i = 0; i <= max_idx; i++) {
|
||||
// highly negative x positions are drawn above the frame and cause flickering, clip to zy plane of camera
|
||||
if (line_x[i] < 0) continue;
|
||||
|
||||
bool l = mapToScreen(line_x[i], line_y[i] - y_off, line_z[i] + z_off, &left);
|
||||
bool r = mapToScreen(line_x[i], line_y[i] + y_off, line_z[i] + z_off, &right);
|
||||
if (l && r) {
|
||||
// For wider lines the drawn polygon will "invert" when going over a hill and cause artifacts
|
||||
if (!allow_invert && pvd->size() && left.y() > pvd->back().y()) {
|
||||
continue;
|
||||
}
|
||||
pvd->push_back(left);
|
||||
pvd->push_front(right);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user